1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * Handles running Button recursively. 8 * 9 * Instead of running another child process, we can use the same process to run 10 * Button recursively. 11 * 12 * There are a several advantages to doing it this way: 13 * - The same thread pool can be reused. Thus, the correct number of worker 14 * threads is always used. 15 * - The same verbosity settings as the parent can be used. 16 * - The same output coloring mode can be used as the parent process. 17 * - Logging of output is more immediate. Output is normally accumulated and 18 * then printed all at once so it isn't interleaved with everything else. 19 * - Avoids the overhead of running another process. However, in general, this 20 * is a non-issue. 21 * 22 * The only disadvantage to doing it this way is that it is more difficult to 23 * implement. 24 */ 25 module button.handlers.recursive; 26 27 import std.parallelism : TaskPool; 28 29 import button.log; 30 import button.resource; 31 import button.context; 32 import button.build; 33 import button.state; 34 import button.cli; 35 36 import darg; 37 38 void execute( 39 ref BuildContext ctx, 40 const(string)[] args, 41 string workDir, 42 ref Resources inputs, 43 ref Resources outputs, 44 TaskLogger logger 45 ) 46 { 47 import button.handlers.base : base = execute; 48 49 import std.path : dirName, absolutePath, buildPath; 50 51 auto globalOpts = parseArgs!GlobalOptions(args[1 .. $], Config.ignoreUnknown); 52 auto opts = parseArgs!BuildOptions(globalOpts.args); 53 54 // Not the build command, forward to the base handler. 55 if (globalOpts.command != "build") 56 { 57 base(ctx, args, workDir, inputs, outputs, logger); 58 return; 59 } 60 61 string path; 62 63 if (opts.path.length) 64 path = buildPath(workDir, opts.path); 65 else 66 path = buildDescriptionPath(workDir); 67 68 auto state = new BuildState(path.stateName); 69 70 // Reuse as much of the parent build context as possible. 71 auto newContext = BuildContext( 72 path.dirName.absolutePath, 73 ctx.pool, ctx.logger, state, 74 ctx.dryRun, ctx.verbose, ctx.color 75 ); 76 77 state.begin(); 78 79 scope (exit) 80 { 81 if (newContext.dryRun) 82 state.rollback(); 83 else 84 state.commit(); 85 } 86 87 syncBuildState(state, newContext.pool, path); 88 89 // TODO: Get changes from the parent build system because this is 90 // duplicating work that has already been done. 91 queueChanges(state, newContext.pool, newContext.color); 92 93 // Do the build. 94 update(newContext); 95 96 // Publish implicit resources to parent build 97 foreach (v; state.enumerate!(Index!Resource)) 98 { 99 immutable degreeIn = state.degreeIn(v); 100 immutable degreeOut = state.degreeOut(v); 101 102 if (degreeIn == 0 && degreeOut == 0) 103 continue; // Dangling resource 104 105 if (degreeIn == 0) 106 inputs.put(state[v]); 107 else 108 outputs.put(state[v]); 109 } 110 } 111 112 /** 113 * Updates the database with any changes to the build description. 114 */ 115 private void syncBuildState(BuildState state, TaskPool pool, string path) 116 { 117 auto r = state[BuildState.buildDescId]; 118 r.path = path; 119 if (r.update()) 120 { 121 path.syncState(state, pool); 122 123 // Update the build description resource 124 state[BuildState.buildDescId] = r; 125 } 126 } 127 128 /** 129 * Builds pending vertices. 130 */ 131 private void update(ref BuildContext ctx) 132 { 133 import std.array : array; 134 135 import button.task : Task; 136 137 auto resources = ctx.state.pending!Resource.array; 138 auto tasks = ctx.state.pending!Task.array; 139 140 if (resources.length == 0 && tasks.length == 0) 141 return; 142 143 ctx.state.buildGraph(resources, tasks).build(ctx); 144 }