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 : config, subDir; 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 DManager.SubmoduleState parseSpec(string spec) 83 { 84 auto parts = spec.split("+"); 85 parts = parts.map!strip().array(); 86 if (parts.empty) 87 parts = [null]; 88 auto rev = parseRev(parts.shift()); 89 90 auto state = d.begin(rev); 91 92 foreach (part; parts) 93 { 94 bool revert = part.skipOver("-"); 95 96 void apply(string component, string[2] branch, DManager.MergeMode mode) 97 { 98 if (revert) 99 d.revert(state, component, branch, mode); 100 else 101 d.merge(state, component, branch, mode); 102 } 103 104 if (part.matchCaptures(re!`^(\w[\w\-\.]*)#(\d+)$`, 105 (string component, int pull) 106 { 107 apply(component, d.getPull(component, pull), DManager.MergeMode.cherryPick); 108 })) 109 continue; 110 111 if (part.matchCaptures(re!`^(?:([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])/)?(\w[\w\-\.]*)/(?:(\w[\w\-]*)\.\.)?(\w[\w\-]*)$`, 112 (string user, string component, string base, string tip) 113 { 114 // Some "do what I mean" logic here: if the user 115 // specified a range, or a single commit, cherry-pick; 116 // otherwise (just a branch name), do a git merge 117 auto branch = d.getBranch(component, user, base, tip); 118 auto mode = branch[0] ? DManager.MergeMode.cherryPick : DManager.MergeMode.merge; 119 apply(component, branch, mode); 120 })) 121 continue; 122 123 throw new Exception("Don't know how to apply customization: " ~ spec); 124 } 125 126 return state; 127 } 128 129 /// Build D according to the given spec string 130 /// (e.g. master+dmd#123). 131 void buildCustom(string spec, bool asNeeded = false) 132 { 133 log("Building spec: " ~ spec); 134 auto submoduleState = parseSpec(spec); 135 runBuild(spec, submoduleState, asNeeded); 136 } 137 138 void checkout(string spec) 139 { 140 log("Checking out: " ~ spec); 141 auto submoduleState = parseSpec(spec); 142 d.checkout(submoduleState); 143 log("Done."); 144 } 145 146 /// Build all D versions (for the purpose of caching them). 147 /// Build order is in steps of decreasing powers of two. 148 void buildAll(string spec) 149 { 150 d.needUpdate(); 151 auto commits = d.getLog("refs/remotes/origin/" ~ spec); 152 commits.reverse(); // oldest first 153 154 for (int step = 1 << 30; step; step >>= 1) 155 { 156 if (step >= commits.length) 157 continue; 158 159 log("Building all revisions with step %d (%d/%d revisions)".format(step, commits.length/step, commits.length)); 160 161 for (int n = step; n < commits.length; n += step) 162 try 163 { 164 auto state = d.begin(commits[n].hash); 165 if (!d.isCached(state)) 166 { 167 log("Building revision %d/%d".format(n/step, commits.length/step)); 168 d.build(state); 169 } 170 } 171 catch (Exception e) 172 log(e.toString()); 173 } 174 }