1 /**
2     Generator for CMake build scripts
3 
4     Copyright: © 2015 Steven Dwy
5     License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6     Authors: Steven Dwy
7 */
8 module dub.generators.cmake;
9 
10 import dub.compilers.buildsettings;
11 import dub.generators.generator;
12 import dub.internal.vibecompat.core.file;
13 import dub.internal.vibecompat.inet.path;
14 import dub.internal.logging;
15 import dub.project;
16 
17 import std.algorithm: map, uniq;
18 import std.algorithm : stdsort = sort; // to avoid clashing with built-in sort
19 import std.array: appender, join, replace;
20 import std.stdio: File, write;
21 import std.string: format;
22 
23 class CMakeGenerator: ProjectGenerator
24 {
25     this(Project project)
26     {
27         super(project);
28     }
29 
30     override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets)
31     {
32         auto script = appender!(char[]);
33         auto scripts = appender!(string[]);
34         bool[string] visited;
35         NativePath projectRoot = m_project.rootPackage.path;
36         NativePath cmakeListsPath = projectRoot ~ "CMakeLists.txt";
37 
38         foreach(name, info; targets)
39         {
40             if(visited.get(name, false))
41                 continue;
42 
43             visited[name] = true;
44             name = name.sanitize;
45             string targetType;
46             string libType;
47             bool addTarget = true;
48 
49             switch(info.buildSettings.targetType) with(TargetType)
50             {
51                 case autodetect:
52                     throw new Exception("Don't know what to do about autodetect target type");
53                 case executable:
54                     targetType = "executable";
55 
56                     break;
57                 case dynamicLibrary:
58                     libType = "SHARED";
59 
60                     goto case;
61                 case library:
62                 case staticLibrary:
63                     targetType = "library";
64 
65                     break;
66                 case sourceLibrary:
67                     addTarget = false;
68 
69                     break;
70                 case none:
71                     continue;
72                 default:
73                     assert(false);
74             }
75 
76             script.put("include(UseD)\n");
77             script.put(
78                 "add_d_conditions(VERSION %s DEBUG %s)\n".format(
79                     info.buildSettings.versions.join(" "),
80                     info.buildSettings.debugVersions.join(" "),
81                 )
82             );
83 
84             foreach(directory; info.buildSettings.importPaths)
85                 script.put("include_directories(%s)\n".format(directory.sanitizeSlashes));
86 
87             foreach(directory; info.buildSettings.cImportPaths)
88                 script.put("c_include_directories(%s)\n".format(directory.sanitizeSlashes));
89 
90             if(addTarget)
91             {
92                 script.put("add_%s(%s %s\n".format(targetType, name, libType));
93 
94                 foreach(file; info.buildSettings.sourceFiles)
95                     script.put("    %s\n".format(file.sanitizeSlashes));
96 
97                 script.put(")\n");
98                 script.put(
99                     "target_link_libraries(%s %s %s)\n".format(
100                         name,
101                         (info.dependencies ~ info.linkDependencies).dup.stdsort.uniq.map!(s => sanitize(s)).join(" "),
102                         info.buildSettings.libs.join(" ")
103                     )
104                 );
105                 script.put(
106                     `set_target_properties(%s PROPERTIES TEXT_INCLUDE_DIRECTORIES "%s")`.format(
107                         name,
108                         info.buildSettings.stringImportPaths.map!(s => sanitizeSlashes(s)).join(";")
109                     ) ~ "\n"
110                 );
111             }
112 
113             string filename = (projectRoot ~ "%s.cmake".format(name)).toNativeString;
114             File file = File(filename, "w");
115 
116             file.write(script.data);
117             file.close;
118             script.shrinkTo(0);
119             scripts.put(filename);
120 
121             logInfo("Generated", Color.green, "%s.cmake", name);
122         }
123 
124         if(!cmakeListsPath.existsFile)
125         {
126             logWarn("You must use a fork of CMake which has D support for these scripts to function properly.");
127             logWarn("It is available at https://github.com/trentforkert/cmake");
128             logDiagnostic("Generating default CMakeLists.txt");
129 
130             script.put("cmake_minimum_required(VERSION 3.0)\n");
131             script.put("project(%s D)\n".format(m_project.rootPackage.name));
132 
133             foreach(path; scripts.data)
134                 script.put("include(%s)\n".format(path));
135 
136             File file = File(cmakeListsPath.toNativeString, "w");
137 
138             file.write(script.data);
139             file.close;
140 
141             logInfo("Generated", Color.green, "CMakeLists.txt (default)");
142         }
143     }
144 }
145 
146 ///Transform a package name into a valid CMake target name.
147 private string sanitize(string name)
148 {
149     return name.replace(":", "_");
150 }
151 
152 private string sanitizeSlashes(string path)
153 {
154     version(Windows)
155         return path.replace("\\", "/");
156     else
157         return path;
158 }