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.resource; 30 import button.context; 31 import button.build; 32 import button.state; 33 import button.cli; 34 35 import darg; 36 37 void execute( 38 ref BuildContext ctx, 39 const(string)[] args, 40 string workDir, 41 ref Resources inputs, 42 ref Resources outputs 43 ) 44 { 45 import button.handlers.base : base = execute; 46 47 import std.path : dirName, absolutePath, buildPath; 48 49 auto globalOpts = parseArgs!GlobalOptions(args[1 .. $], Config.ignoreUnknown); 50 51 // Not the build command, forward to the base handler. 52 if (globalOpts.command != "build") 53 { 54 base(ctx, args, workDir, inputs, outputs); 55 return; 56 } 57 58 auto opts = parseArgs!BuildOptions(globalOpts.args); 59 60 string path; 61 62 if (opts.path.length) 63 path = buildPath(workDir, opts.path); 64 else 65 path = buildDescriptionPath(workDir); 66 67 auto state = new BuildState(path.stateName); 68 auto dir = path.dirName; 69 70 // Reuse as much of the parent build context as possible. 71 auto newContext = BuildContext( 72 dir.absolutePath, 73 ctx.pool, ctx.events, 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 auto r = state[v]; 106 107 // Make sure we are reporting inputs/outputs relative to the correct 108 // directory. 109 r.path = buildPath(dir, r.path); 110 111 if (degreeIn == 0) 112 inputs.put(r); 113 else 114 outputs.put(r); 115 } 116 } 117 118 /** 119 * Updates the database with any changes to the build description. 120 */ 121 private void syncBuildState(BuildState state, TaskPool pool, string path) 122 { 123 auto r = state[BuildState.buildDescId]; 124 r.path = path; 125 if (r.update()) 126 { 127 path.syncState(state, pool); 128 129 // Update the build description resource 130 state[BuildState.buildDescId] = r; 131 } 132 } 133 134 /** 135 * Builds pending vertices. 136 */ 137 private void update(ref BuildContext ctx) 138 { 139 import std.array : array; 140 141 import button.task : Task; 142 143 auto resources = ctx.state.pending!Resource.array; 144 auto tasks = ctx.state.pending!Task.array; 145 146 if (resources.length == 0 && tasks.length == 0) 147 return; 148 149 ctx.state.buildGraph(resources, tasks).build(ctx); 150 }