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 }