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