1 module dbuild.build;
2 
3 import dbuild.buildsystem : BuildSystem;
4 import dbuild.src : Source;
5 import dbuild.target : Target;
6 
7 
8 enum BuildType
9 {
10     /// release build
11     rel,
12     /// debug build
13     deb,
14 }
15 
16 /// directories involved during a build
17 struct BuildDirs
18 {
19     /// the source directory
20     string srcDir;
21     /// the build directory
22     string buildDir;
23     /// the install directory
24     string installDir;
25 
26     /// build a path within the source directory
27     /// Examples:
28     /// -----------
29     /// BuildDirs dirs;
30     /// const mainFile = dirs.src("src", "main.c"); // get [src-dir]/src/main.c
31     /// -----------
32     string src(Comps...)(Comps comps) const
33     {
34         import std.path : buildPath;
35 
36         return buildPath(srcDir, comps);
37     }
38 
39     /// build a path within the build directory
40     string build(Comps...)(Comps comps) const
41     {
42         import std.path : buildPath;
43 
44         return buildPath(buildDir, comps);
45     }
46 
47     /// build a path within the install directory
48     string install(Comps...)(Comps comps) const
49     {
50         import std.path : buildPath;
51 
52         return buildPath(installDir, comps);
53     }
54 }
55 
56 struct BuildResult
57 {
58     BuildDirs dirs;
59     Target[string] targets;
60 
61     string artifact(in string name)
62     {
63         auto target = targets[name];
64         if (!target.artifact) {
65             target.resolveArtifact(dirs.installDir);
66         }
67         return target.artifact;
68     }
69 }
70 
71 struct Build
72 {
73     /// Start to construct Build from within the .dub/subDir.
74     /// This workDir will be where the source will be downloaded,
75     /// extracted and where the build will take place.
76     /// An exception will be thrown if dub environment cannot be detected.
77     static Build dubWorkDir(string subDir="dbuild")
78     {
79         Build bld;
80         bld._dubSubDir = subDir;
81         return bld;
82     }
83 
84     /// Start to construct Build from within the workDir.
85     /// This workDir will be where the source will be downloaded,
86     /// extracted and where the build will take place.
87     static Build workDir(string workDir)
88     {
89         Build bld;
90         bld._workDir = workDir;
91         return bld;
92     }
93 
94     /// Set the source package to be fetched
95     Build src(Source source)
96     {
97         _source = source;
98         return this;
99     }
100 
101     Build debug_()
102     {
103         _type = BuildType.deb;
104         return this;
105     }
106 
107     Build release()
108     {
109         _type = BuildType.rel;
110         return this;
111     }
112 
113     Build type(in BuildType type)
114     {
115         _type = type;
116         return this;
117     }
118 
119     /// Set the install directory.
120     /// If not called, it will be set automatically within the work dir
121     Build install (string prefix)
122     {
123         _installPrefix = prefix;
124         return this;
125     }
126 
127     /// Do not log and shut down external commands output.
128     Build quiet ()
129     {
130         _quiet = true;
131         return this;
132     }
133 
134     /// Add a target to be checked before attempting to start the build
135     /// and to help resolving to a result artifact
136     Build target(Target target)
137     {
138         _targets[target.name] = target;
139         return this;
140     }
141 
142     /// Perform the build
143     /// Throws if build fails
144     /// Returns: the directories involved in the build
145     BuildResult build(BuildSystem buildSystem)
146     {
147         import dbuild.buildsystem : BuildContext;
148         import dbuild.util : lockFile;
149         import std.exception : enforce;
150         import std.file : mkdirRecurse;
151         import std.stdio : writeln;
152 
153         checkPrerequisites();
154         ensureWorkDir();
155         const srcDir = _source.obtain(_workDir);
156         const buildId = computeBuildId(buildSystem);
157         const buildDir = bldPath(buildId);
158         mkdirRecurse(buildDir);
159         if (!_installPrefix.length) {
160             _installPrefix = installPath(buildId);
161         }
162 
163         BuildDirs dirs;
164         dirs.srcDir = srcDir;
165         dirs.buildDir = buildDir;
166         dirs.installDir = _installPrefix;
167 
168         if (!checkTargets(dirs)) {
169             auto lock = lockFile(bldLockPath(buildId));
170             buildSystem.issueCmds(BuildContext(dirs, _type, _quiet));
171         }
172         else {
173             writeln("targets are up-to-date");
174         }
175 
176         return BuildResult(dirs, _targets);
177     }
178 
179     private string _dubSubDir;
180     private string _workDir;
181     private Source _source;
182     private string _srcDir;
183     private BuildType _type;
184     private string _installPrefix;
185     private bool _quiet;
186     private Target[string] _targets;
187 
188     private void checkPrerequisites()
189     {
190         import dbuild.util : searchExecutable;
191         import std.exception : enforce;
192 
193         enforce(_source, "did not set source");
194     }
195 
196     private void ensureWorkDir()
197     {
198         import std.exception : enforce;
199         import std.file : mkdirRecurse;
200         import std.path : buildPath;
201         import std.process : environment;
202 
203         if (!_workDir.length) {
204             const dubPkgDir = environment.get("DUB_PACKAGE_DIR");
205             enforce(dubPkgDir, "Dub environment could not be found. workDir must be used");
206 
207             enforce (_dubSubDir.length, "either workDir or dubWorkDir must be used");
208 
209             _workDir = buildPath(dubPkgDir, ".dub", _dubSubDir);
210         }
211 
212         mkdirRecurse(_workDir);
213     }
214 
215     private string bldLockPath(in string buildId)
216     {
217         import std.path : buildPath;
218 
219         return buildPath(_workDir, ".bldLock-"~buildId);
220     }
221 
222     private string bldPath(in string buildId)
223     {
224         import std.path : buildPath;
225 
226         return buildPath(_workDir, "build-"~buildId);
227     }
228 
229     private string installPath(in string buildId)
230     {
231         import std.path : buildPath;
232 
233         return buildPath(_workDir, "install-"~buildId);
234     }
235 
236     private string computeBuildId(BuildSystem bs)
237     {
238         import dbuild.util : feedDigestData;
239         import std.digest : toHexString, LetterCase;
240         import std.digest.md : MD5;
241 
242         MD5 md5;
243         _source.feedBuildId(md5);
244         feedDigestData(md5, _type);
245         feedDigestData(md5, _installPrefix);
246         bs.feedBuildId(md5);
247 
248         const hash = md5.finish();
249         return toHexString!(LetterCase.lower)(hash)[0 .. 7].idup;
250     }
251 
252     private bool checkTargets(BuildDirs dirs)
253     {
254         if (!_targets.length) return false;
255         foreach (t; _targets) {
256             t.resolveArtifact(dirs.installDir);
257             if (!t.check(dirs.srcDir)) return false;
258         }
259         return true;
260     }
261 }