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 }