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 }