1 module digger; 2 3 import std.array; 4 import std.exception; 5 import std.file : thisExePath, exists; 6 import std.path; 7 import std.process; 8 import std.stdio; 9 import std.typetuple; 10 11 static if(!is(typeof({import ae.utils.text;}))) static assert(false, "ae library not found, did you clone with --recursive?"); else: 12 13 version (Windows) 14 static import ae.sys.net.wininet; 15 else 16 static import ae.sys.net.curl; 17 18 import ae.sys.d.manager : DManager; 19 import ae.utils.funopt; 20 import ae.utils.main; 21 import ae.utils.meta : structFun; 22 import ae.utils.text : eatLine; 23 24 import bisect; 25 import common; 26 import config; 27 import custom; 28 import install; 29 import repo; 30 31 // http://d.puremagic.com/issues/show_bug.cgi?id=7016 32 version(Windows) static import ae.sys.windows; 33 34 alias BuildOptions(string action, string pastAction, bool showBuildActions = true) = TypeTuple!( 35 Switch!(hiddenOption, 0, "64"), 36 Option!(string, showBuildActions ? "Select models (32, 64, or, on Windows, 32mscoff). You can specify multiple models by comma-separating them.\nOn this system, the default is " ~ DManager.Config.Build.components.common.defaultModel ~ " [build.components.common.models]" : hiddenOption, null, 0, "model"), 37 Option!(string[], "Do not " ~ action ~ " a component (that would otherwise be " ~ pastAction ~ " by default). List of default components: " ~ DManager.defaultComponents.join(", ") ~ " [build.components.enable.COMPONENT=false]", "COMPONENT", 0, "without"), 38 Option!(string[], "Specify an additional D component to " ~ action ~ ". List of available additional components: " ~ DManager.additionalComponents.join(", ") ~ " [build.components.enable.COMPONENT=true]", "COMPONENT", 0, "with"), 39 Option!(string[], showBuildActions ? `Additional make parameters, e.g. "HOST_CC=g++48" [build.components.common.makeArgs]` : hiddenOption, "ARG", 0, "makeArgs"), 40 Switch!(showBuildActions ? "Bootstrap the compiler (build from C++ source code) instead of downloading a pre-built binary package [build.components.dmd.bootstrap.fromSource]" : hiddenOption, 0, "bootstrap"), 41 Switch!(hiddenOption, 0, "use-vc"), 42 Switch!(hiddenOption, 0, "clobber-local-changes"), 43 ); 44 45 enum specDescription = "D ref (branch / tag / point in time) to build, plus any additional forks or pull requests. Example:\n" ~ 46 "\"master @ 3 weeks ago + dmd#123 + You/dmd/awesome-feature\""; 47 48 void parseBuildOptions(T...)(T options) // T == BuildOptions!action 49 { 50 if (options[0]) 51 d.config.build.components.common.models = ["64"]; 52 if (options[1]) 53 d.config.build.components.common.models = options[1].value.split(","); 54 foreach (componentName; options[2]) 55 d.config.build.components.enable[componentName] = false; 56 foreach (componentName; options[3]) 57 d.config.build.components.enable[componentName] = true; 58 d.config.build.components.common.makeArgs ~= options[4]; 59 d.config.build.components.dmd.bootstrap.fromSource |= options[5]; 60 d.config.build.components.dmd.useVC |= options[6]; 61 d.verifyWorkTree = !options[7]; 62 static assert(options.length == 8); 63 } 64 65 struct Digger 66 { 67 static: 68 @(`Build D from source code`) 69 int build(BuildOptions!("build", "built") options, Parameter!(string, specDescription) spec = "master") 70 { 71 parseBuildOptions(options); 72 buildCustom(spec); 73 return 0; 74 } 75 76 @(`Incrementally rebuild the current D checkout`) 77 int rebuild(BuildOptions!("rebuild", "rebuilt") options) 78 { 79 parseBuildOptions(options); 80 incrementalBuild(); 81 return 0; 82 } 83 84 @(`Run tests for enabled components`) 85 int test(BuildOptions!("test", "tested") options) 86 { 87 parseBuildOptions(options); 88 runTests(); 89 return 0; 90 } 91 92 @(`Check out D source code from git`) 93 int checkout(BuildOptions!("check out", "checked out", false) options, Parameter!(string, specDescription) spec = "master") 94 { 95 parseBuildOptions(options); 96 .checkout(spec); 97 return 0; 98 } 99 100 @(`Run a command using a D version`) 101 int run( 102 BuildOptions!("build", "built") options, 103 Parameter!(string, specDescription ~ "\nSpecify \"-\" to use the previously-built version.") spec, 104 Parameter!(string[], "Command to run and its arguments (use -- to pass switches)") command) 105 { 106 if (spec == "-") 107 enforce(options == typeof(options).init, "Can't specify build options when using the last built version."); 108 else 109 { 110 parseBuildOptions(options); 111 buildCustom(spec, /*asNeeded*/true); 112 } 113 114 auto binPath = resultDir.buildPath("bin").absolutePath(); 115 environment["PATH"] = binPath ~ pathSeparator ~ environment["PATH"]; 116 117 version (Windows) 118 return spawnProcess(command).wait(); 119 else 120 { 121 execvp(command[0], command); 122 errnoEnforce(false, "execvp failed"); 123 assert(false); // unreachable 124 } 125 } 126 127 @(`Install Digger's build result on top of an existing stable DMD installation`) 128 int install( 129 Switch!("Do not prompt", 'y') yes, 130 Switch!("Only print what would be done", 'n') dryRun, 131 Parameter!(string, "Directory to install to. Default is to find one in PATH.") installLocation = null, 132 ) 133 { 134 enforce(!yes || !dryRun, "--yes and --dry-run are mutually exclusive"); 135 .install.install(yes, dryRun, installLocation); 136 return 0; 137 } 138 139 @(`Undo the "install" action`) 140 int uninstall( 141 Switch!("Only print what would be done", 'n') dryRun, 142 Switch!("Do not verify files to be deleted; ignore errors") force, 143 Parameter!(string, "Directory to uninstall from. Default is to search PATH.") installLocation = null, 144 ) 145 { 146 .uninstall(dryRun, force, installLocation); 147 return 0; 148 } 149 150 @(`Bisect D history according to a bisect.ini file`) 151 int bisect( 152 Switch!("Skip sanity-check of the GOOD/BAD commits.") noVerify, 153 Option!(string[], "Additional bisect configuration. Equivalent to bisect.ini settings.", "NAME=VALUE", 'c', "config") configLines, 154 Parameter!(string, "Location of the bisect.ini file containing the bisection description.") bisectConfigFile = null, 155 ) 156 { 157 return doBisect(noVerify, bisectConfigFile, configLines); 158 } 159 160 @(`Cache maintenance actions (run with no arguments for details)`) 161 int cache(string[] args) 162 { 163 static struct CacheActions 164 { 165 static: 166 @(`Compact the cache`) 167 int compact() 168 { 169 d.optimizeCache(); 170 return 0; 171 } 172 173 @(`Delete entries cached as unbuildable`) 174 int purgeUnbuildable() 175 { 176 d.purgeUnbuildable(); 177 return 0; 178 } 179 180 @(`Migrate cached entries from one cache engine to another`) 181 int migrate(string source, string target) 182 { 183 d.migrateCache(source, target); 184 return 0; 185 } 186 } 187 188 return funoptDispatch!CacheActions(["digger cache"] ~ args); 189 } 190 191 // hidden actions 192 193 int buildAll(BuildOptions!("build", "built") options, string spec = "master") 194 { 195 parseBuildOptions(options); 196 .buildAll(spec); 197 return 0; 198 } 199 200 int delve(bool inBisect) 201 { 202 return doDelve(inBisect); 203 } 204 205 int parseRev(string rev) 206 { 207 stdout.writeln(.parseRev(rev)); 208 return 0; 209 } 210 211 int show(string revision) 212 { 213 d.getMetaRepo().git.run("log", "-n1", revision); 214 d.getMetaRepo().git.run("log", "-n1", "--pretty=format:t=%ct", revision); 215 return 0; 216 } 217 218 int getLatest() 219 { 220 writeln((cast(DManager.Website)d.getComponent("website")).getLatest()); 221 return 0; 222 } 223 224 int help() 225 { 226 throw new Exception("For help, run digger without any arguments."); 227 } 228 229 version (Windows) 230 int getAllMSIs() 231 { 232 d.getVSInstaller().getAllMSIs(); 233 return 0; 234 } 235 } 236 237 int digger() 238 { 239 version (D_Coverage) 240 { 241 import core.runtime; 242 dmd_coverSetMerge(true); 243 } 244 245 static void usageFun(string usage) 246 { 247 import std.algorithm, std.array, std.stdio, std.string; 248 auto lines = usage.splitLines(); 249 250 stderr.writeln("Digger v" ~ diggerVersion ~ " - a D source code building and archaeology tool"); 251 stderr.writeln("Created by Vladimir Panteleev <vladimir@thecybershadow.net>"); 252 stderr.writeln("https://github.com/CyberShadow/Digger"); 253 stderr.writeln(); 254 stderr.writeln("Configuration file: ", opts.configFile.value.exists ? opts.configFile.value : "(not present)"); 255 stderr.writeln("Working directory: ", config.config.local.workDir); 256 stderr.writeln(); 257 258 if (lines[0].canFind("ACTION [ACTION-ARGUMENTS]")) 259 { 260 lines = 261 [lines[0].replace(" ACTION ", " [OPTION]... ACTION ")] ~ 262 getUsageFormatString!(structFun!Opts).splitLines()[1..$] ~ 263 lines[1..$]; 264 265 stderr.writefln("%-(%s\n%)", lines); 266 stderr.writeln(); 267 stderr.writeln("For help on a specific action, run: digger ACTION --help"); 268 stderr.writeln("For more information, see README.md."); 269 stderr.writeln(); 270 } 271 else 272 stderr.writefln("%-(%s\n%)", lines); 273 } 274 275 return funoptDispatch!(Digger, FunOptConfig.init, usageFun)([thisExePath] ~ (opts.action ? [opts.action.value] ~ opts.actionArguments : [])); 276 } 277 278 mixin main!digger;