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