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 
152         checkPrerequisites();
153         ensureWorkDir();
154         const srcDir = _source.obtain(_workDir);
155         const buildId = computeBuildId(buildSystem);
156         const buildDir = bldPath(buildId);
157         mkdirRecurse(buildDir);
158         if (!_installPrefix.length) {
159             _installPrefix = installPath(buildId);
160         }
161 
162         BuildDirs dirs;
163         dirs.srcDir = srcDir;
164         dirs.buildDir = buildDir;
165         dirs.installDir = _installPrefix;
166 
167         if (!checkTargets(dirs)) {
168             auto lock = lockFile(bldLockPath(buildId));
169             buildSystem.issueCmds(BuildContext(dirs, _type, _quiet));
170         }
171 
172         return BuildResult(dirs, _targets);
173     }
174 
175     private string _dubSubDir;
176     private string _workDir;
177     private Source _source;
178     private string _srcDir;
179     private BuildType _type;
180     private string _installPrefix;
181     private bool _quiet;
182     private Target[string] _targets;
183 
184     private void checkPrerequisites()
185     {
186         import dbuild.util : searchExecutable;
187         import std.exception : enforce;
188 
189         enforce(_source, "did not set source");
190     }
191 
192     private void ensureWorkDir()
193     {
194         import std.exception : enforce;
195         import std.file : mkdirRecurse;
196         import std.path : buildPath;
197         import std.process : environment;
198 
199         if (!_workDir.length) {
200             const dubPkgDir = environment.get("DUB_PACKAGE_DIR");
201             enforce(dubPkgDir, "Dub environment could not be found. workDir must be used");
202 
203             enforce (_dubSubDir.length, "either workDir or dubWorkDir must be used");
204 
205             _workDir = buildPath(dubPkgDir, ".dub", _dubSubDir);
206         }
207 
208         mkdirRecurse(_workDir);
209     }
210 
211     private string bldLockPath(in string buildId)
212     {
213         import std.path : buildPath;
214 
215         return buildPath(_workDir, ".bldLock-"~buildId);
216     }
217 
218     private string bldPath(in string buildId)
219     {
220         import std.path : buildPath;
221 
222         return buildPath(_workDir, "build-"~buildId);
223     }
224 
225     private string installPath(in string buildId)
226     {
227         import std.path : buildPath;
228 
229         return buildPath(_workDir, "install-"~buildId);
230     }
231 
232     private string computeBuildId(BuildSystem bs)
233     {
234         import dbuild.util : feedDigestData;
235         import std.digest : toHexString, LetterCase;
236         import std.digest.md : MD5;
237 
238         MD5 md5;
239         _source.feedBuildId(md5);
240         feedDigestData(md5, _type);
241         feedDigestData(md5, _installPrefix);
242         bs.feedBuildId(md5);
243 
244         const hash = md5.finish();
245         return toHexString!(LetterCase.lower)(hash)[0 .. 7].idup;
246     }
247 
248     private bool checkTargets(BuildDirs dirs)
249     {
250         if (!_targets.length) return false;
251         foreach (t; _targets) {
252             t.resolveArtifact(dirs.installDir);
253             if (!t.check(dirs.srcDir)) return false;
254         }
255         return true;
256     }
257 }