1 /**
2  * Copyright: Copyright Jason White, 2016
3  * License:   MIT
4  * Authors:   Jason White
5  *
6  * Description:
7  * Handles the 'convert' command.
8  */
9 module button.cli.convert;
10 
11 import std.parallelism : TaskPool;
12 
13 import button.cli.options : ConvertOptions, ConvertFormat, GlobalOptions;
14 
15 import button.build;
16 import button.resource;
17 import button.task;
18 
19 import io.file, io.text;
20 
21 /**
22  * Converts the JSON build description to another format.
23  *
24  * This is useful for converting the build to a shell script, for example.
25  */
26 int convertCommand(ConvertOptions opts, GlobalOptions globalOpts)
27 {
28     string path;
29 
30     try
31     {
32         path = buildDescriptionPath(opts.path);
33     }
34     catch (BuildException e)
35     {
36         stderr.println(e.msg);
37         return 1;
38     }
39     catch (SysException e)
40     {
41         stderr.println(e.msg);
42         return 1;
43     }
44 
45     // TODO: Add Batch output with proper error handling
46 
47     final switch (opts.type)
48     {
49         case ConvertFormat.bash:
50             return convertToBash(path, opts.output);
51     }
52 }
53 
54 /**
55  * A header explaining what this file is.
56  */
57 private immutable bashHeader = q"EOS
58 # This file was automatically generated by Button. Do not modify it.
59 EOS";
60 
61 bool visitResource(File* f, Resource v,
62         size_t degreeIn, size_t degreeChanged)
63 {
64     // Nothing needs to happen here. Just unconditionally continue on to the
65     // next vertex in the graph.
66     return true;
67 }
68 
69 bool bashVisitTask(File* f, Task v,
70         size_t degreeIn, size_t degreeChanged)
71 {
72     import button.command : escapeShellArg;
73 
74     f.println();
75 
76     if (v.display.length)
77         f.println("# ", v.display);
78 
79     if (v.workingDirectory.length)
80         f.println("pushd -- ", v.workingDirectory.escapeShellArg);
81 
82     foreach (command; v.commands)
83         f.println(command.toPrettyString);
84 
85     if (v.workingDirectory.length)
86         f.println("popd");
87 
88     // Unconditionally continue on to the next vertex in the graph.
89     return true;
90 }
91 
92 /**
93  * Converts the build description to Bash.
94  */
95 private int convertToBash(string input, string output)
96 {
97     import std.parallelism : TaskPool;
98 
99     auto f = File(output, FileFlags.writeEmpty);
100 
101     version (Posix)
102     {
103         // Make the output executable. This is a workaround until the mode can
104         // be changed in the File() constructor.
105         import core.sys.posix.sys.stat : chmod;
106         import std.internal.cstring : tempCString;
107         sysEnforce(chmod(output.tempCString(), 0b111_101_101) == 0,
108                 "Failed to make script executable");
109     }
110 
111     f.println("#!/bin/bash");
112     f.print(bashHeader);
113 
114     // Stop the build when a command fails.
115     f.println("set -xe -o pipefail");
116 
117     // Traverse the graph single-threaded, writing out the commands
118     auto g = input.rules.graph();
119 
120     auto pool = new TaskPool(0);
121     scope (exit) pool.finish(true);
122 
123     g.traverse!(visitResource, bashVisitTask)(&f, pool);
124 
125     return 0;
126 }