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 }