1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * Handles running gcc processes. 8 * 9 * Inputs and outputs are detected by adding the -MMD option and parsing the 10 * deps file that gcc produces. 11 */ 12 module button.handlers.gcc; 13 14 import button.exceptions; 15 import button.resource; 16 import button.context; 17 18 import std.range.primitives : isInputRange, ElementEncodingType, 19 front, empty, popFront; 20 21 import std.traits : isSomeChar; 22 23 /** 24 * Helper function to escape a character. 25 */ 26 private C escapeChar(C)(C c) 27 if (isSomeChar!C) 28 { 29 switch (c) 30 { 31 case 't': return '\t'; 32 case 'v': return '\v'; 33 case 'r': return '\r'; 34 case 'n': return '\n'; 35 case 'b': return '\b'; 36 case 'f': return '\f'; 37 case '0': return '\0'; 38 default: return c; 39 } 40 } 41 42 /** 43 * A single Make rule. 44 */ 45 struct MakeRule 46 { 47 string target; 48 string[] deps; 49 } 50 51 /** 52 * An input range of Make rules. 53 * 54 * This parses a deps file that gcc produces. The file consists of simple Make 55 * rules. Rules are separated by lines (discounting line continuations). Each 56 * rule consists of a target file and its dependencies. 57 */ 58 struct MakeRules(Source) 59 if (isInputRange!Source && isSomeChar!(ElementEncodingType!Source)) 60 { 61 private 62 { 63 import std.array : Appender; 64 import std.traits : Unqual; 65 66 alias C = Unqual!(ElementEncodingType!Source); 67 68 Source source; 69 bool _empty; 70 MakeRule current; 71 72 Appender!(C[]) buf; 73 } 74 75 this(Source source) 76 { 77 this.source = source; 78 popFront(); 79 } 80 81 @property 82 bool empty() const pure nothrow 83 { 84 return _empty; 85 } 86 87 @property 88 const(MakeRule) front() const pure nothrow 89 { 90 return current; 91 } 92 93 /** 94 * Parses a single file name. 95 */ 96 private string parseFileName() 97 { 98 import std.uni : isWhite; 99 100 buf.clear(); 101 102 while (!source.empty) 103 { 104 immutable c = source.front; 105 106 if (c == ':' || c.isWhite) 107 { 108 // ':' delimits a target. 109 break; 110 } 111 else if (c == '\\') 112 { 113 // Skip past the '\\' 114 source.popFront(); 115 if (source.empty) 116 break; 117 118 immutable e = source.front; 119 if (e == '\n') 120 { 121 // Line continuation 122 source.popFront(); 123 } 124 else 125 { 126 buf.put(escapeChar(e)); 127 source.popFront(); 128 } 129 130 continue; 131 } 132 133 // Regular character 134 buf.put(c); 135 source.popFront(); 136 } 137 138 return buf.data.idup; 139 } 140 141 /** 142 * Skips spaces. 143 */ 144 private void skipSpace() 145 { 146 import std.uni : isSpace; 147 while (!source.empty && isSpace(source.front)) 148 source.popFront(); 149 } 150 151 /** 152 * Skips whitespace 153 */ 154 private void skipWhite() 155 { 156 import std.uni : isWhite; 157 while (!source.empty && isWhite(source.front)) 158 source.popFront(); 159 } 160 161 /** 162 * Parses the list of dependencies after the target. 163 * 164 * Returns: The list of dependencies. 165 */ 166 private string[] parseDeps() 167 { 168 Appender!(string[]) deps; 169 170 skipSpace(); 171 172 while (!source.empty) 173 { 174 // A new line delimits the dependency list 175 if (source.front == '\n') 176 { 177 source.popFront(); 178 break; 179 } 180 181 auto dep = parseFileName(); 182 if (dep.length) 183 deps.put(dep); 184 185 if (!source.empty && source.front == ':') 186 throw new MakeParserError("Unexpected ':'"); 187 188 skipSpace(); 189 } 190 191 return deps.data; 192 } 193 194 /** 195 * Parses a rule. 196 */ 197 private MakeRule parseRule() 198 { 199 string target = parseFileName(); 200 if (target.empty) 201 throw new MakeParserError("Empty target name"); 202 203 skipSpace(); 204 205 if (source.empty) 206 throw new MakeParserError("Unexpected end of file"); 207 208 if (source.front != ':') 209 throw new MakeParserError("Expected ':' after target name"); 210 211 source.popFront(); 212 213 // Parse dependency names 214 auto deps = parseDeps(); 215 216 skipWhite(); 217 218 return MakeRule(target, deps); 219 } 220 221 void popFront() 222 { 223 skipWhite(); 224 225 if (source.empty) 226 { 227 _empty = true; 228 return; 229 } 230 231 current = parseRule(); 232 } 233 } 234 235 /** 236 * Convenience function for constructing a MakeRules range. 237 */ 238 MakeRules!Source makeRules(Source)(Source source) 239 if (isInputRange!Source && isSomeChar!(ElementEncodingType!Source)) 240 { 241 return MakeRules!Source(source); 242 } 243 244 unittest 245 { 246 import std.array : array; 247 import std.exception : collectException; 248 import std.algorithm.comparison : equal; 249 250 static assert(isInputRange!(MakeRules!string)); 251 252 { 253 auto rules = makeRules( 254 "\n\nfoo.c : foo.h \\\n bar.h \\\n" 255 ); 256 257 assert(rules.equal([ 258 MakeRule("foo.c", ["foo.h", "bar.h"]), 259 ])); 260 } 261 262 { 263 auto rules = makeRules( 264 "foo.c : foo.h \\\n bar.h\n"~ 265 " \nbar.c : bar.h\n"~ 266 "\n \nbaz.c:\n"~ 267 "ba\\\nz.c: blah.h\n"~ 268 `foo\ bar: bing\ bang` 269 ); 270 271 assert(rules.equal([ 272 MakeRule("foo.c", ["foo.h", "bar.h"]), 273 MakeRule("bar.c", ["bar.h"]), 274 MakeRule("baz.c", []), 275 MakeRule("baz.c", ["blah.h"]), 276 MakeRule("foo bar", ["bing bang"]), 277 ])); 278 } 279 280 assert(collectException!MakeParserError(makeRules( 281 "foo.c: foo.h: bar.h" 282 ).array)); 283 } 284 285 void execute( 286 ref BuildContext ctx, 287 const(string)[] args, 288 string workDir, 289 ref Resources inputs, 290 ref Resources outputs 291 ) 292 { 293 import std.file : remove; 294 295 import io.file : File, tempFile, AutoDelete; 296 import io.range : byBlock; 297 298 import button.handlers.base : base = execute; 299 300 // Create the temporary file for the dependencies. 301 auto depsPath = tempFile(AutoDelete.no).path; 302 scope (exit) remove(depsPath); 303 304 // Tell gcc to write dependencies to our temporary file. 305 args ~= ["-MMD", "-MF", depsPath]; 306 307 base(ctx, args, workDir, inputs, outputs); 308 309 // TODO: Parse the command line arguments for -I and -o options. 310 311 // Parse the dependencies 312 auto deps = File(depsPath).byBlock!char; 313 foreach (rule; makeRules(&deps)) 314 { 315 outputs.put(rule.target); 316 inputs.put(rule.deps); 317 } 318 }