1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * Handles the 'build' command. 8 */ 9 module button.cli.build; 10 11 import std.parallelism : TaskPool; 12 13 import button.cli.options : BuildOptions, GlobalOptions; 14 15 import io.text, io.file; 16 17 import button.state; 18 import button.rule; 19 import button.graph; 20 import button.build; 21 import button.resource; 22 import button.task; 23 import button.textcolor; 24 import button.events; 25 import button.watcher; 26 import button.context; 27 import button.exceptions; 28 29 /** 30 * Updates the build. 31 * 32 * All outputs are brought up-to-date based on their inputs. If '--autopilot' is 33 * specified, once the build finishes, we watch for changes to inputs and run 34 * another build. 35 */ 36 int buildCommand(BuildOptions opts, GlobalOptions globalOpts) 37 { 38 import std.parallelism : totalCPUs; 39 import std.path : dirName, absolutePath; 40 import button.loggers.console; 41 42 if (opts.threads == 0) 43 opts.threads = totalCPUs; 44 45 auto pool = new TaskPool(opts.threads - 1); 46 scope (exit) pool.finish(true); 47 48 immutable color = TextColor(colorOutput(opts.color)); 49 50 auto events = new ConsoleLogger(stdout, stderr, opts.verbose, pool.size); 51 52 string path; 53 BuildState state; 54 55 try 56 { 57 path = buildDescriptionPath(opts.path); 58 state = new BuildState(path.stateName); 59 } 60 catch (BuildException e) 61 { 62 stderr.println(color.status, ":: ", color.error, 63 "Error", color.reset, ": ", e.msg); 64 return 1; 65 } 66 67 auto context = BuildContext(absolutePath(dirName(path)), pool, events, 68 state, opts.dryRun, opts.verbose, color); 69 70 if (!opts.autopilot) 71 { 72 return doBuild(context, path); 73 } 74 else 75 { 76 // Do the initial build, checking for changes the old-fashioned way. 77 doBuild(context, path); 78 79 return doAutoBuild(context, path, opts.watchDir, opts.delay); 80 } 81 } 82 83 int doBuild(ref BuildContext ctx, string path) 84 { 85 import std.datetime.stopwatch : StopWatch, AutoStart; 86 87 auto sw = StopWatch(AutoStart.yes); 88 89 scope (exit) 90 { 91 import std.conv : to; 92 import core.time : Duration; 93 sw.stop(); 94 95 if (ctx.verbose) 96 { 97 println(ctx.color.status, ":: Total time taken: ", ctx.color.reset, 98 cast(Duration)sw.peek()); 99 } 100 } 101 102 try 103 { 104 ctx.state.begin(); 105 scope (exit) 106 { 107 if (ctx.dryRun) 108 ctx.state.rollback(); 109 else 110 ctx.state.commit(); 111 } 112 113 syncBuildState(ctx, path); 114 115 if (ctx.verbose) 116 println(ctx.color.status, ":: Checking for changes...", ctx.color.reset); 117 118 queueChanges(ctx.state, ctx.pool, ctx.color); 119 120 update(ctx); 121 } 122 catch (BuildException e) 123 { 124 stderr.println(ctx.color.status, ":: ", ctx.color.error, 125 "Error", ctx.color.reset, ": ", e.msg); 126 return 1; 127 } 128 catch (Exception e) 129 { 130 stderr.println(ctx.color.status, ":: ", ctx.color.error, 131 "Build failed!", ctx.color.reset, 132 " See the output above for details."); 133 if (ctx.verbose) 134 println(ctx.color.status, ":: ", e.toString()); 135 136 return 1; 137 } 138 139 return 0; 140 } 141 142 int doAutoBuild(ref BuildContext ctx, string path, 143 string watchDir, size_t delay) 144 { 145 println(ctx.color.status, ":: Waiting for changes...", ctx.color.reset); 146 147 ctx.state.begin(); 148 scope (exit) 149 { 150 if (ctx.dryRun) 151 ctx.state.rollback(); 152 else 153 ctx.state.commit(); 154 } 155 156 foreach (changes; ChangeChunks(ctx.state, watchDir, delay)) 157 { 158 try 159 { 160 size_t changed = 0; 161 162 foreach (v; changes) 163 { 164 // Check if the resource contents actually changed 165 auto r = ctx.state[v]; 166 167 if (r.update()) 168 { 169 ctx.state.addPending(v); 170 ctx.state[v] = r; 171 ++changed; 172 } 173 } 174 175 if (changed > 0) 176 { 177 println(ctx.color.status, ":: Change detected. Building...", 178 ctx.color.reset); 179 syncBuildState(ctx, path); 180 update(ctx); 181 println(ctx.color.status, ":: Waiting for changes...", 182 ctx.color.reset); 183 } 184 } 185 catch (BuildException e) 186 { 187 stderr.println(ctx.color.status, ":: ", ctx.color.error, 188 "Error", ctx.color.reset, ": ", e.msg); 189 continue; 190 } 191 catch (Exception e) 192 { 193 stderr.println(ctx.color.status, ":: ", ctx.color.error, 194 "Build failed!", ctx.color.reset, 195 " See the output above for details."); 196 continue; 197 } 198 } 199 200 // Unreachable 201 } 202 203 /** 204 * Updates the database with any changes to the build description. 205 */ 206 void syncBuildState(ref BuildContext ctx, string path) 207 { 208 // TODO: Don't store the build description in the database. The parent build 209 // system should store the change state of the build description and tell 210 // the child which input resources have changed upon an update. 211 auto r = ctx.state[BuildState.buildDescId]; 212 r.path = path; 213 if (r.update()) 214 { 215 if (ctx.verbose) 216 println(ctx.color.status, 217 ":: Build description changed. Syncing with the database...", 218 ctx.color.reset); 219 220 path.syncState(ctx.state, ctx.pool); 221 222 // Update the build description resource 223 ctx.state[BuildState.buildDescId] = r; 224 } 225 } 226 227 /** 228 * Builds pending vertices. 229 */ 230 void update(ref BuildContext ctx) 231 { 232 import std.array : array; 233 import std.algorithm.iteration : filter; 234 235 auto resources = ctx.state.pending!Resource.array; 236 auto tasks = ctx.state.pending!Task.array; 237 238 if (resources.length == 0 && tasks.length == 0) 239 { 240 if (ctx.verbose) 241 { 242 println(ctx.color.status, ":: ", ctx.color.success, 243 "Nothing to do. Everything is up to date.", ctx.color.reset); 244 } 245 246 return; 247 } 248 249 // Print what we found. 250 if (ctx.verbose) 251 { 252 printfln(" - Found %s%d%s modified resource(s)", 253 ctx.color.boldBlue, resources.length, ctx.color.reset); 254 printfln(" - Found %s%d%s pending task(s)", 255 ctx.color.boldBlue, tasks.length, ctx.color.reset); 256 257 println(ctx.color.status, ":: Building...", ctx.color.reset); 258 } 259 260 auto subgraph = ctx.state.buildGraph(resources, tasks); 261 subgraph.build(ctx); 262 263 if (ctx.verbose) 264 println(ctx.color.status, ":: ", ctx.color.success, "Build succeeded", 265 ctx.color.reset); 266 }