1 module custom; 2 3 import std.algorithm; 4 import std.array; 5 import std.conv; 6 import std.exception; 7 import std.file; 8 import std.getopt; 9 import std.path; 10 import std.stdio; 11 import std..string; 12 13 import ae.sys.d.manager; 14 import ae.utils.array; 15 import ae.utils.json; 16 import ae.utils.regex; 17 18 import common; 19 import config; 20 import install; 21 import repo; 22 23 alias indexOf = std..string.indexOf; 24 25 // https://issues.dlang.org/show_bug.cgi?id=15777 26 alias strip = std..string.strip; 27 28 alias subDir!"result" resultDir; 29 30 /// We save a JSON file to the result directory with the build parameters. 31 struct BuildInfo 32 { 33 string diggerVersion; 34 string spec; 35 DiggerManager.Config.Build config; 36 DManager.SubmoduleState components; 37 } 38 39 enum buildInfoFileName = "build-info.json"; 40 41 void prepareResult() 42 { 43 log("Moving..."); 44 if (resultDir.exists) 45 resultDir.rmdirRecurse(); 46 rename(d.buildDir, resultDir); 47 48 log("Build successful.\n\nTo start using it, run `digger install`, or add %s to your PATH.".format( 49 resultDir.buildPath("bin").absolutePath() 50 )); 51 } 52 53 /// Build the customized D version. 54 /// The result will be in resultDir. 55 void runBuild(string spec, DManager.SubmoduleState submoduleState, bool asNeeded) 56 { 57 auto buildInfoPath = buildPath(resultDir, buildInfoFileName); 58 auto buildInfo = BuildInfo(diggerVersion, spec, d.config.build, submoduleState); 59 if (asNeeded && buildInfoPath.exists && buildInfoPath.readText.jsonParse!BuildInfo == buildInfo) 60 { 61 log("Reusing existing version in " ~ resultDir); 62 return; 63 } 64 d.build(submoduleState); 65 prepareResult(); 66 std.file.write(buildInfoPath, buildInfo.toJson()); 67 } 68 69 /// Perform an incremental build, i.e. don't clean or fetch anything from remote repos 70 void incrementalBuild() 71 { 72 d.rebuild(); 73 prepareResult(); 74 } 75 76 /// Run tests. 77 void runTests() 78 { 79 d.test(); 80 } 81 82 /// Implements transient persistence for the current customization state. 83 struct DCustomizer 84 { 85 struct CustomizationState 86 { 87 string spec; 88 DManager.SubmoduleState submoduleState; 89 string[string][string] pulls; 90 string[string][string][string] forks; 91 } 92 CustomizationState state; 93 94 enum fileName = "customization-state.json"; 95 96 void load() 97 { 98 state = fileName.readText().jsonParse!CustomizationState(); 99 } 100 101 void save() 102 { 103 std.file.write(fileName, state.toJson()); 104 } 105 106 void finish() 107 { 108 std.file.remove(fileName); 109 } 110 111 string getPull(string submoduleName, string pullNumber) 112 { 113 string rev = state.pulls.get(submoduleName, null).get(pullNumber, null); 114 if (!rev) 115 state.pulls[submoduleName][pullNumber] = rev = d.getPull(submoduleName, pullNumber.to!int); 116 return rev; 117 } 118 119 string getFork(string submoduleName, string user, string branch) 120 { 121 string rev = state.forks.get(submoduleName, null).get(user, null).get(branch, null); 122 if (!rev) 123 state.forks[submoduleName][user][branch] = rev = d.getFork(submoduleName, user, branch); 124 return rev; 125 } 126 } 127 128 DCustomizer customizer; 129 130 int handleWebTask(string[] args) 131 { 132 enforce(args.length, "No task specified"); 133 switch (args[0]) 134 { 135 case "initialize": 136 d.needUpdate(); 137 log("Ready."); 138 return 0; 139 case "begin": 140 d.haveUpdate = true; // already updated in "initialize" 141 customizer.state.spec = "digger-web @ " ~ (args.length == 1 ? "(master)" : args[1]); 142 customizer.state.submoduleState = d.begin(parseRev(args.length == 1 ? null : args[1])); 143 customizer.save(); 144 log("Ready."); 145 return 0; 146 case "merge": 147 enforce(args.length == 3); 148 customizer.load(); 149 d.merge(customizer.state.submoduleState, args[1], customizer.getPull(args[1], args[2])); 150 customizer.save(); 151 return 0; 152 case "unmerge": 153 enforce(args.length == 3); 154 customizer.load(); 155 d.unmerge(customizer.state.submoduleState, args[1], customizer.getPull(args[1], args[2])); 156 customizer.save(); 157 return 0; 158 case "merge-fork": 159 enforce(args.length == 4); 160 customizer.load(); 161 d.merge(customizer.state.submoduleState, args[2], customizer.getFork(args[2], args[1], args[3])); 162 customizer.save(); 163 return 0; 164 case "unmerge-fork": 165 enforce(args.length == 4); 166 customizer.load(); 167 d.unmerge(customizer.state.submoduleState, args[2], customizer.getFork(args[2], args[1], args[3])); 168 customizer.save(); 169 return 0; 170 case "callback": 171 d.callback(args[1..$]); 172 return 0; 173 case "build": 174 { 175 customizer.load(); 176 177 string model; 178 getopt(args, 179 "model", &model, 180 ); 181 enforce(args.length == 1, "Unrecognized build option"); 182 183 if (model.length) 184 d.config.build.components.common.models = [model]; 185 186 runBuild(customizer.state.spec, customizer.state.submoduleState, false); 187 customizer.finish(); 188 return 0; 189 } 190 case "branches": 191 foreach (line; d.getMetaRepo().git.query("branch", "--remotes").splitLines()) 192 if (line.startsWith(" origin/") && line[2..$].indexOf(" ") < 0) 193 writeln(line[9..$]); 194 return 0; 195 case "tags": 196 d.getMetaRepo().git.run("tag"); 197 return 0; 198 case "install-preview": 199 install.install(false, true); 200 return 0; 201 case "install": 202 install.install(true, false); 203 return 0; 204 default: 205 assert(false); 206 } 207 } 208 209 DManager.SubmoduleState parseSpec(string spec) 210 { 211 auto parts = spec.split("+"); 212 parts = parts.map!strip().array(); 213 if (parts.empty) 214 parts = [null]; 215 auto rev = parseRev(parts.shift()); 216 217 auto state = d.begin(rev); 218 219 foreach (part; parts) 220 { 221 bool revert = part.skipOver("-"); 222 223 void handleCommit(string component, string commit, int mainline) 224 { 225 if (revert) 226 d.revert(state, component, commit, mainline); 227 else 228 d.merge(state, component, commit); 229 } 230 231 void handleBranch(string component, string branch) 232 { 233 if (revert) 234 { 235 string commit; int mainline; 236 d.getChild(state, component, branch, /*out*/commit, /*out*/mainline); 237 handleCommit(component, commit, mainline); 238 } 239 else 240 handleCommit(component, branch, 0); 241 } 242 243 if (part.matchCaptures(re!`^(\w[\w\-\.]*)#(\d+)$`, 244 (string component, int pull) 245 { 246 handleBranch(component, d.getPull(component, pull)); 247 })) 248 continue; 249 250 if (part.matchCaptures(re!`^([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])/(\w[\w\-\.]*)/(\w[\w\-]*)$`, 251 (string user, string component, string branch) 252 { 253 handleBranch(component, d.getFork(component, user, branch)); 254 })) 255 continue; 256 257 if (part.matchCaptures(re!`^(\w+)/([0-9a-fA-F]{40})$`, 258 (string component, string commit) 259 { 260 handleCommit(component, commit, 0); 261 })) 262 continue; 263 264 throw new Exception("Don't know how to apply customization: " ~ spec); 265 } 266 267 return state; 268 } 269 270 /// Build D according to the given spec string 271 /// (e.g. master+dmd#123). 272 void buildCustom(string spec, bool asNeeded = false) 273 { 274 log("Building spec: " ~ spec); 275 auto submoduleState = parseSpec(spec); 276 runBuild(spec, submoduleState, asNeeded); 277 } 278 279 void checkout(string spec) 280 { 281 log("Checking out: " ~ spec); 282 auto submoduleState = parseSpec(spec); 283 d.checkout(submoduleState); 284 log("Done."); 285 } 286 287 /// Build all D versions (for the purpose of caching them). 288 /// Build order is in steps of decreasing powers of two. 289 void buildAll(string spec) 290 { 291 d.needUpdate(); 292 auto commits = d.getLog("refs/remotes/origin/" ~ spec); 293 commits.reverse(); // oldest first 294 295 for (int step = 1 << 30; step; step >>= 1) 296 { 297 if (step >= commits.length) 298 continue; 299 300 log("Building all revisions with step %d (%d/%d revisions)".format(step, commits.length/step, commits.length)); 301 302 for (int n = step; n < commits.length; n += step) 303 try 304 { 305 auto state = d.begin(commits[n].hash); 306 if (!d.isCached(state)) 307 { 308 log("Building revision %d/%d".format(n/step, commits.length/step)); 309 d.build(state); 310 } 311 } 312 catch (Exception e) 313 log(e.toString()); 314 } 315 }