1 module dbuild.buildsystem;
2 
3 import dbuild.build;
4 import std.digest.md : MD5;
5 
6 /// context of a build
7 struct BuildContext
8 {
9     BuildDirs dirs;
10     BuildType type;
11     bool quiet;
12 }
13 
14 /// simple interface to a build system (such as CMake, or Autotools)
15 interface BuildSystem
16 {
17     /// Feeds the digest in a way that makes a unique build identifier.
18     void feedBuildId(ref MD5 digest);
19     /// Issue the commands to build the source code to the requested binaries.
20     void issueCmds(BuildContext ctx);
21 }
22 
23 /// Helper to create a CMAKE build system, with generator and options.
24 /// Option should not deal with install dir or build type, which
25 /// are set from the build context.
26 /// The build system will issue 3 commands: configure, build and install.
27 struct CMake
28 {
29     static CMake create(string generator=null, string[] options=null)
30     {
31         import dbuild.util : searchExecutable;
32         import std.exception : enforce;
33 
34         enforce(searchExecutable("cmake"), "Could not find CMake!");
35         CMake cmake;
36         cmake._gen = generator;
37         cmake._options = options;
38         return cmake;
39     }
40 
41     version(Windows)
42     CMake withMsvcSetup(int minVer=0, string[] vcvarsOptions=null)
43     {
44         import std.exception : enforce;
45 
46         enforce(tryMsvcSetup(minVer, vcvarsOptions), "Could not find suitable MSVC install");
47         return this;
48     }
49 
50     version(Windows)
51     bool tryMsvcSetup(int minVer=0, string[] vcvarsOptions=null)
52     {
53         import dbuild.msvc : detectMsvcInstalls, msvcEnvironment;
54         import dbuild.util : searchExecutable;
55 
56         if (searchExecutable("cl")) return true; // TODO: what version
57 
58         auto installs = detectMsvcInstalls();
59         if (!installs.length || installs[0].ver[0] < minVer) {
60             return false;
61         }
62         _env = msvcEnvironment(installs[0].vcvarsBat, vcvarsOptions);
63         _additionalHashFeed = installs[0].vcvarsBat ~ vcvarsOptions;
64         _msvc = true;
65 
66         return true;
67     }
68 
69     private string findDefaultGenerator()
70     {
71         import dbuild.util : searchExecutable;
72 
73         version(Windows)
74         {
75             import dbuild.msvc : dubArchOptions;
76 
77             if (tryMsvcSetup(0, dubArchOptions())) {
78                 if (hasNinja) {
79                     return "Ninja";
80                 }
81                 else {
82                     return "NMake Makefiles";
83                 }
84             }
85         }
86         if (hasNinja) {
87             return "Ninja";
88         }
89         version(Windows) {
90             if (hasMingw32Make) {
91                 return "MinGW Makefiles";
92             }
93             else if (hasMake) { // under cygwin?
94                 return "MSYS Makefiles";
95             }
96         }
97         else {
98             if (hasMake) {
99                 return "Unix Makefiles";
100             }
101         }
102         return null;
103     }
104 
105     /// get the result BuildSystem
106     BuildSystem buildSystem()
107     {
108         import std.exception : enforce;
109 
110         if (!_gen) {
111             _gen = enforce(findDefaultGenerator(), "Could not find suitable CMake generator");
112         }
113 
114         version(Windows) {
115             if (_gen == "Ninja" && _msvc) {
116                 _env["CC"] = "cl";
117                 _env["CXX"] = "cl";
118             }
119         }
120 
121         return new CMakeBuildSystem(_gen, _options, _env, _additionalHashFeed);
122     }
123 
124     private string _gen;
125     private string[] _options;
126     private string[string] _env;
127     private string[] _additionalHashFeed;
128     version(Windows) private bool _msvc;
129 }
130 
131 
132 private @property bool hasNinja()
133 {
134     import dbuild.util : searchExecutable;
135     import std.concurrency : initOnce;
136 
137     static __gshared bool has;
138     return initOnce!has(searchExecutable("ninja") !is null);
139 }
140 
141 private @property bool hasGcc()
142 {
143     import dbuild.util : searchExecutable;
144     import std.concurrency : initOnce;
145 
146     static __gshared bool has;
147     return initOnce!has(searchExecutable("gcc") !is null);
148 }
149 
150 private @property bool hasClang()
151 {
152     import dbuild.util : searchExecutable;
153     import std.concurrency : initOnce;
154 
155     static __gshared bool has;
156     return initOnce!has(searchExecutable("clang") !is null);
157 }
158 
159 private @property bool hasMake()
160 {
161     import dbuild.util : searchExecutable;
162     import std.concurrency : initOnce;
163 
164     static __gshared bool has;
165     return initOnce!has(searchExecutable("make") !is null);
166 }
167 
168 private @property bool hasMingw32Make()
169 {
170     import dbuild.util : searchExecutable;
171     import std.concurrency : initOnce;
172 
173     static __gshared bool has;
174     return initOnce!has(searchExecutable("mingw32-make") !is null);
175 }
176 
177 
178 private class CMakeBuildSystem : BuildSystem
179 {
180     const(string) generator;
181     const(string[]) options;
182     string[string] env;
183     const(string[]) additionalHashFeed;
184 
185     this(in string generator, in string[] options, string[string] env, string[] additionalHashFeed)
186     {
187         this.generator = generator;
188         this.options = options;
189         this.env = env;
190         this.additionalHashFeed = additionalHashFeed;
191     }
192 
193     override void feedBuildId(ref MD5 digest)
194     {
195         import dbuild.util : feedDigestData;
196 
197         feedDigestData(digest, generator);
198         feedDigestData(digest, options);
199         if (additionalHashFeed.length) feedDigestData(digest, additionalHashFeed);
200     }
201 
202     override void issueCmds(BuildContext ctx)
203     {
204         import dbuild.util : runCommands;
205 
206         const string buildType = ctx.type == BuildType.deb ? "Debug" : "Release";
207 
208         const string[] configCmd = [
209             "cmake",
210             "-G", generator,
211             "-DCMAKE_BUILD_TYPE="~buildType,
212             "-DCMAKE_INSTALL_PREFIX="~ctx.dirs.installDir
213         ] ~ options ~ [
214             ctx.dirs.srcDir
215         ];
216         const string[] buildCmd = [
217             "cmake", "--build", "."
218         ];
219         const string[] installCmd = [
220             "cmake", "--build", ".", "--target", "install"
221         ];
222 
223         runCommands(
224             [ configCmd, buildCmd, installCmd ], ctx.dirs.buildDir, ctx.quiet, env
225         );
226     }
227 }