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