1 /*! 2 * \file CMakeGenerator.cxx 3 * \brief 4 * \author Thomas Helfer 5 * \date 16/08/2015 6 * \copyright Copyright (C) 2006-2018 CEA/DEN, EDF R&D. All rights 7 * reserved. 8 * This project is publicly released under either the GNU GPL Licence 9 * or the CECILL-A licence. A copy of thoses licences are delivered 10 * with the sources of TFEL. CEA or EDF may also distribute this 11 * project under specific licensing conditions. 12 */ 13 14 #include <set> 15 #include <cstring> 16 #include <ostream> 17 #include <sstream> 18 #include <fstream> 19 #include <iterator> 20 #include <algorithm> 21 22 #include <sys/types.h> 23 #include <sys/stat.h> 24 #if defined _WIN32 || defined _WIN64 25 #ifndef NOMINMAX 26 #define NOMINMAX 27 #endif 28 #include <io.h> 29 #include <conio.h> 30 #include <windows.h> 31 #include <process.h> 32 #ifdef small 33 #undef small 34 #endif /* small */ 35 #else 36 #include <dlfcn.h> 37 #include <sys/wait.h> 38 #include <dirent.h> 39 #include <unistd.h> 40 #endif 41 42 #include "TFEL/Raise.hxx" 43 #include "TFEL/Utilities/StringAlgorithms.hxx" 44 #include "TFEL/System/System.hxx" 45 #include "MFront/MFrontHeader.hxx" 46 #include "MFront/MFrontLogStream.hxx" 47 #include "MFront/InstallPath.hxx" 48 #include "MFront/SearchPathsHandler.hxx" 49 #include "MFront/MFrontLock.hxx" 50 #include "MFront/MFrontDebugMode.hxx" 51 #include "MFront/TargetsDescription.hxx" 52 #include "MFront/GeneratorOptions.hxx" 53 #include "MFront/CMakeGenerator.hxx" 54 55 namespace mfront { 56 getCMakeCommand()57 static const char* getCMakeCommand() { 58 const auto* cmake = ::getenv("CMAKE"); 59 if (cmake != nullptr) { 60 return cmake; 61 } 62 #if defined _WIN32 || defined _WIN64 63 return "cmake.exe"; 64 #else 65 return "cmake"; 66 #endif 67 } 68 getCMakeDefaultGenerator()69 static std::string getCMakeDefaultGenerator() { 70 #ifdef TFEL_CMAKE_GENERATOR 71 return TFEL_CMAKE_GENERATOR; 72 #else /* TFEL_CMAKE_GENERATOR */ 73 return "\"Unix Makefiles\""; 74 #endif /* TFEL_CMAKE_GENERATOR */ 75 } 76 generateCMakeListsFile(const TargetsDescription & t,const GeneratorOptions & o,const std::string &)77 void generateCMakeListsFile(const TargetsDescription& t, 78 const GeneratorOptions& o, 79 const std::string&) { 80 auto throw_if = [](const bool b, const std::string& m) { 81 tfel::raise_if(b, "generateCMakeListFile: " + m); 82 }; 83 if (getVerboseMode() >= VERBOSE_LEVEL2) { 84 getLogStream() << "generating 'src/CMakeList.txt'\n"; 85 } 86 MFrontLockGuard lock; 87 std::ofstream m("src/CMakeLists.txt"); 88 m.exceptions(std::ios::badbit | std::ios::failbit); 89 throw_if(!m, "can't open file 'src/CMakeList.txt'"); 90 auto append = [&m](const std::string& n, const std::string& v) { 91 if ((tfel::utilities::starts_with(v, "$(shell ")) || 92 (tfel::utilities::ends_with(v, ")"))) { 93 m << "append_spawn(" << n << " " << v.substr(8, v.size() - 9) << ")\n"; 94 } else { 95 m << "list(APPEND " << n << " " << v << ")\n"; 96 } 97 }; 98 // spawn without calling seperate_arguments 99 auto append2 = [&m](const std::string& n, const std::string& v) { 100 if ((tfel::utilities::starts_with(v, "$(shell ")) || 101 (tfel::utilities::ends_with(v, ")"))) { 102 m << "append_spawn2(" << n << " " << v.substr(8, v.size() - 9) << ")\n"; 103 } else { 104 m << "list(APPEND " << n << " " << v << ")\n"; 105 } 106 }; 107 // some target' names are reserved by CMake 108 for (const auto& st : t.specific_targets) { 109 const auto rnames = {"all", "clean", "install", "test"}; 110 if (std::find(rnames.begin(), rnames.end(), st.first) != rnames.end()) { 111 throw_if(true, "target name '" + st.first + "' is reserved"); 112 } 113 } 114 // 115 throw_if(!t.specific_targets.empty(), "specific targets are not supported"); 116 m << "# CMakeList.txt generated by mfront.\n" 117 << MFrontHeader::getHeader("# ") << "\n" 118 << "\n" 119 << "cmake_minimum_required(VERSION 2.4)\n" 120 << "project(\"mfront-sources\")\n"; 121 if (o.sys != "apple") { 122 m << "set(MACOSX_RPATH ON)\n"; 123 m << "set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON)\n"; 124 } 125 m << "function(spawn res cmd)\n" 126 << " execute_process(COMMAND ${cmd} ${ARGN}\n" 127 << " OUTPUT_VARIABLE SPAWN_RESULT\n" 128 << " OUTPUT_STRIP_TRAILING_WHITESPACE)\n" 129 << " separate_arguments(SPAWN_RESULT) \n" 130 << " set(${res} ${SPAWN_RESULT} PARENT_SCOPE)\n" 131 << "endfunction(spawn)\n" 132 << "function(append_spawn res cmd)\n" 133 << " spawn(SPAWN_RESULTS ${cmd} ${ARGN})\n" 134 << " list(APPEND SPAWN_RESULTS ${${res}})\n" 135 << " set(${res} ${SPAWN_RESULTS} PARENT_SCOPE)\n" 136 << "endfunction(append_spawn)\n" 137 << "function(spawn2 res cmd)\n" 138 << " execute_process(COMMAND ${cmd} ${ARGN}\n" 139 << " OUTPUT_VARIABLE SPAWN_RESULT\n" 140 << " OUTPUT_STRIP_TRAILING_WHITESPACE)\n" 141 << " set(${res} ${SPAWN_RESULT} PARENT_SCOPE)\n" 142 << "endfunction(spawn2)\n" 143 << "function(append_spawn2 res cmd)\n" 144 << " spawn2(SPAWN_RESULTS ${cmd} ${ARGN})\n" 145 << " list(APPEND SPAWN_RESULTS ${${res}})\n" 146 << " set(${res} ${SPAWN_RESULTS} PARENT_SCOPE)\n" 147 << "endfunction(append_spawn2)\n" 148 << "\n" 149 << "if(TFEL_INSTALL_PATH)\n" 150 << " set(TFELHOME \"${TFEL_INSTALL_PATH}\")\n" 151 << "else(TFEL_INSTALL_PATH)\n" 152 << " set(TFELHOME $ENV{TFELHOME})\n" 153 << "endif(TFEL_INSTALL_PATH)\n" 154 << "\n"; 155 #ifdef TFEL_APPEND_SUFFIX 156 m << "find_program(TFEL_CONFIG tfel-config-" TFEL_SUFFIX 157 " \"${TFELHOME}/bin\")\n"; 158 #else /* TFEL_APPEND_SUFFIX */ 159 m << "find_program(TFEL_CONFIG tfel-config \"${TFELHOME}/bin\")\n"; 160 #endif /* TFEL_APPEND_SUFFIX */ 161 m << "message(STATUS \"tfel-config : ${TFEL_CONFIG}\")\n" 162 << "\n"; 163 // << "spawn(TFEL_INCLUDE_PATH ${TFEL_CONFIG} \"--include-path\")\n" 164 // << "spawn(TFEL_LIBRARY_PATH ${TFEL_CONFIG} \"--library-path\")\n" 165 // << "spawn(TFEL_COMPILER_FLAGS ${TFEL_CONFIG}\n" 166 // << " \"--cppflags\" \"--compiler-flags\")\n" 167 switch (o.olevel) { 168 case GeneratorOptions::LEVEL2: 169 m << "spawn(TFEL_OFLAGS ${TFEL_CONFIG} \"--oflags\" \"--oflags2\")\n"; 170 break; 171 case GeneratorOptions::LEVEL1: 172 m << "spawn(TFEL_OFLAGS ${TFEL_CONFIG} \"--oflags\")\n"; 173 break; 174 case GeneratorOptions::LEVEL0: 175 m << "spawn(TFEL_OFLAGS ${TFEL_CONFIG} \"--oflags0\")\n"; 176 break; 177 } 178 m << "\n" 179 << "message(STATUS \"tfel oflags : ${TFEL_OFLAGS}\")\n" 180 << "\n"; 181 // debug flags 182 if (o.debugFlags) { 183 m << "spawn(TFEL_DEBUG_FLAGS ${TFEL_CONFIG} \"--debug-flags\")\n" 184 << "\n" 185 << "message(STATUS \"tfel debug flags : ${TFEL_DEBUG_FLAGS}\")\n" 186 << "\n"; 187 } 188 // include_directories 189 const auto include_directories = [&t] { 190 auto r = std::vector<std::string>{}; 191 r.push_back("../include"); 192 for (const auto& l : t.libraries) { 193 for (const auto& ld : l.include_directories) { 194 if (std::find(r.begin(), r.end(), ld) == r.end()) { 195 r.push_back(ld); 196 } 197 } 198 } 199 // adding the mfront search path to the include files 200 if (!SearchPathsHandler::getSearchPaths().empty()) { 201 const auto& paths = SearchPathsHandler::getSearchPaths(); 202 for (const auto& p : paths) { 203 if (std::find(r.begin(), r.end(), p) == r.end()) { 204 r.push_back(p); 205 } 206 } 207 } 208 return r; 209 }(); 210 if (include_directories.size() == 1) { 211 m << "include_directories(" << include_directories[0] << ")\n"; 212 } else { 213 m << "set(INCLUDE_DIRECTORIES)\n"; 214 for (const auto& d : include_directories) { 215 append2("INCLUDE_DIRECTORIES", d); 216 } 217 m << "include_directories(${INCLUDE_DIRECTORIES})\n"; 218 } 219 // link_directories 220 const auto link_directories = [&t] { 221 auto r = std::vector<std::string>{}; 222 for (const auto& l : t.libraries) { 223 for (const auto& ld : l.link_directories) { 224 if (std::find(r.begin(), r.end(), ld) == r.end()) { 225 r.push_back(ld); 226 } 227 } 228 } 229 return r; 230 }(); 231 if (!link_directories.empty()) { 232 m << "set(LINK_DIRECTORIES)\n"; 233 for (const auto& ld : link_directories) { 234 append2("LINK_DIRECTORIES", ld); 235 } 236 m << "link_directories(${LINK_DIRECTORIES})\n"; 237 } 238 m << "\n"; 239 for (const auto& l : t.libraries) { 240 if (l.name == "MFrontMaterialLaw") { 241 continue; 242 } 243 // cppflags 244 m << "# Setting compile flags for " << l.name << '\n' 245 << "set(" << l.name << "_COMPILE_FLAGS)\n" 246 << "list(APPEND " << l.name << "_COMPILE_FLAGS ${TFEL_OFLAGS})\n"; 247 if (o.debugFlags) { 248 m << "list(APPEND " << l.name 249 << "_COMPILE_FLAGS ${TFEL_DEBUG_FLAGS})\n"; 250 } 251 if ((o.sys == "win32") || (o.sys == "cygwin")) { 252 m << "list(APPEND " << l.name 253 << "_COMPILE_FLAGS \"-DMFRONT_COMPILING\")\n"; 254 } 255 for (const auto& f : l.cppflags) { 256 append(l.name + "_COMPILE_FLAGS", f); 257 } 258 m << "string (REPLACE \";\" \" \" " << l.name << "_COMPILE_FLAGS\n" 259 << " \"${" << l.name << "_COMPILE_FLAGS}\")\n"; 260 // link_libraries 261 auto has_link_libraries = [&o, &l] { 262 if (!l.link_libraries.empty()) { 263 return true; 264 } 265 return (!o.melt) ? !l.deps.empty() : false; 266 }(); 267 if (has_link_libraries) { 268 m << "# Setting link libraries for " << l.name << '\n' 269 << "set(" << l.name << "_LINK_LIBRARIES)\n"; 270 for (const auto& lf : l.link_libraries) { 271 append(l.name + "_LINK_LIBRARIES", lf); 272 } 273 if (!o.melt) { 274 for (const auto& ld : l.deps) { 275 append(l.name + "_LINK_LIBRARIES", ld); 276 } 277 } 278 } 279 // ldflags 280 m << "# Setting linker flags for " << l.name << '\n'; 281 if (!l.ldflags.empty()) { 282 m << "set(" << l.name << "_LINK_FLAGS )\n"; 283 for (const auto& lf : l.ldflags) { 284 append(l.name + "_LINK_FLAGS", lf); 285 } 286 m << "string(REPLACE \";\" \" \" " << l.name << "_LINK_FLAGS\n" 287 << " \"${" << l.name << "_LINK_FLAGS}\")\n"; 288 } 289 m << "add_library(" << l.name << " SHARED\n"; 290 for (const auto& s : l.sources) { 291 m << s << " "; 292 } 293 if (o.melt) { 294 for (const auto& ld : l.deps) { 295 for (const auto& s : t.getLibrary(ld).sources) { 296 m << s << " "; 297 } 298 } 299 } 300 m << ")\n"; 301 if (!l.link_libraries.empty()) { 302 m << "target_link_libraries(" << l.name << '\n' 303 << "${" << l.name << "_LINK_LIBRARIES})\n"; 304 } 305 m << "set_target_properties(" << l.name << '\n' 306 << "PROPERTIES COMPILE_FLAGS \"${" << l.name << "_COMPILE_FLAGS}\")\n"; 307 if (l.suffix != LibraryDescription::getDefaultLibrarySuffix( 308 t.system, t.libraryType)) { 309 m << "set_target_properties(" << l.name << '\n' 310 << "PROPERTIES SUFFIX " << l.suffix << ")\n"; 311 } 312 if (!l.ldflags.empty()) { 313 m << "set_target_properties(" << l.name << '\n' 314 << "PROPERTIES LINK_FLAGS \"${" << l.name << "_LINK_FLAGS}\")\n"; 315 } 316 const auto ipath = 317 l.install_path.empty() ? getInstallPath() : l.install_path; 318 if (!ipath.empty()) { 319 m << "# Setting install directory " << l.name << '\n'; 320 if ((tfel::utilities::starts_with(ipath, "$(env ")) || 321 (tfel::utilities::ends_with(ipath, ")"))) { 322 m << "install(TARGETS " << l.name << " DESTINATION $ENV{" 323 << ipath.substr(6, ipath.size() - 7) << "})\n"; 324 } else { 325 m << "install(TARGETS " << l.name << " DESTINATION " << ipath 326 << ")\n"; 327 } 328 } 329 m << '\n'; 330 } 331 } 332 callCMake(const std::string & t,const std::string & d)333 void callCMake(const std::string& t, const std::string& d) { 334 using namespace tfel::system; 335 using tfel::utilities::starts_with; 336 const char* cmake = getCMakeCommand(); 337 // const char * silent = getDebugMode() ? nullptr : "-s"; 338 const char* silent = nullptr; 339 const auto g = [] { 340 const auto cg = []() -> std::string { 341 const auto* e = ::getenv("CMAKE_GENERATOR"); 342 if (e != nullptr) { 343 return e; 344 } 345 return getCMakeDefaultGenerator(); 346 }(); 347 #if (defined _WIN32 || defined _WIN64) && (!defined __CYGWIN__) 348 // spawn function do not like spaces in arguments 349 if (cg.find(' ') != std::string::npos) { 350 return '"' + cg + '"'; 351 } 352 #endif 353 return cg; 354 }(); 355 // check for multi-configuration generator 356 const char* cfg1 = nullptr; 357 const char* cfg2 = nullptr; 358 if ((starts_with(g, "\"Visual Studio")) || (starts_with(g, "XCode"))) { 359 cfg1 = "--config"; 360 cfg2 = "Release"; 361 } 362 const char* tg1 = nullptr; 363 const char* tg2 = nullptr; 364 if (t != "all") { 365 tg1 = "--target"; 366 tg2 = t.c_str(); 367 } 368 const char* argv[] = {cmake, "-G", g.c_str(), ".", silent, nullptr}; 369 const char* argv2[] = {cmake, "--build", ".", tg1, tg2, 370 cfg1, cfg2, silent, nullptr}; 371 std::remove_if(argv2, argv2 + 9, 372 [](const char* ptr) { return ptr == nullptr; }); 373 auto error = [&t](const std::string& e, const char* const* args) { 374 auto msg = "callCmake: can't build target '" + t + "'\n"; 375 if (!e.empty()) { 376 msg += e + '\n'; 377 } 378 msg += "Command was: "; 379 for (const char* const* a = args; *a != nullptr; ++a) { 380 msg += *a; 381 msg += ' '; 382 } 383 tfel::raise(msg); 384 }; 385 tfel::raise_if(::strlen(cmake) == 0u, "callCmake: empty cmake command"); 386 const auto pwd = systemCall::getCurrentWorkingDirectory(); 387 systemCall::changeCurrentWorkingDirectory(d); 388 #if (defined _WIN32 || defined _WIN64) && (!defined __CYGWIN__) 389 if (_spawnvp(_P_WAIT, cmake, argv) != 0) { 390 error("", argv); 391 } 392 if (_spawnvp(_P_WAIT, cmake, argv2) != 0) { 393 error("", argv2); 394 } 395 #else 396 auto call_cmake = [&cmake, &error](const char* const* args) { 397 const auto child_pid = fork(); 398 int status = 0; 399 if (child_pid != 0) { 400 if (wait(&status) == -1) { 401 error( 402 "something went wrong while " 403 "waiting end of cmake process", 404 args); 405 } 406 } else { 407 execvp(cmake, const_cast<char* const*>(args)); 408 ::exit(EXIT_FAILURE); 409 } 410 return status; 411 }; 412 if (call_cmake(argv) != 0) { 413 error("cmake configuration went wrong", argv); 414 } 415 if (call_cmake(argv2) != 0) { 416 error("libraries building went wrong", argv2); 417 } 418 #endif 419 systemCall::changeCurrentWorkingDirectory(pwd); 420 } 421 422 } // end of namespace mfront 423