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 }