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 }