1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * Handles running "dmd" processes. 8 * 9 * Inputs and outputs are detected by parsing and modifying the command line 10 * before execution. DMD has a "-deps" option for writing inputs to a file. This 11 * option is dynamically added to the command line and parsed after the process 12 * exits. 13 */ 14 module button.handlers.dmd; 15 16 import button.log; 17 import button.resource; 18 import button.context; 19 20 import std.path; 21 import io.file; 22 23 private struct Options 24 { 25 // Flags 26 bool compileFlag; // -c 27 bool coverageFlag; // -cov 28 bool libFlag; // -lib 29 bool sharedFlag; // -shared 30 bool docFlag; // -D 31 bool headerFlag; // -H 32 bool mapFlag; // -map 33 bool suppressObjectsFlag; // -o- 34 bool jsonFlag; // -X 35 bool opFlag; // -op 36 37 // Options with arguments 38 string outputDir; // -od 39 string outputFile; // -of 40 string depsFile; // -deps= 41 string docDir; // -Dd 42 string docFile; // -Df 43 string headerDir; // -Hd 44 string headerFile; // -Hf 45 string[] importDirs; // -I 46 string[] stringImportDirs; // -J 47 string[] linkerFlags; // -L 48 const(string)[] run; // -run 49 string jsonFile; // -Xf 50 string cmdFile; // @cmdfile 51 52 // Left over files on the command line 53 string[] files; 54 55 /** 56 * Returns the object file path for the given source file path. 57 */ 58 string objectPath(string sourceFile) const pure 59 { 60 if (!opFlag) 61 sourceFile = baseName(sourceFile); 62 return buildPath(outputDir, setExtension(sourceFile, ".o")); 63 } 64 65 /** 66 * Returns a list of object file paths. 67 */ 68 const(char[])[] objects() const pure 69 { 70 import std.algorithm.iteration : map, filter; 71 import std.algorithm.searching : endsWith; 72 import std.array : array; 73 74 if (suppressObjectsFlag) 75 return []; 76 77 // If -c is specified, all source files are compiled into separate 78 // object files. If -c is not specified, all sources files are compiled 79 // into a single object file which is named based on the first source 80 // file specified. 81 if (compileFlag) 82 { 83 if (outputFile) 84 return [outputFile]; 85 else 86 return files 87 .filter!(p => p.endsWith(".d")) 88 .map!(p => objectPath(p)) 89 .array(); 90 } 91 92 // Object name is based on -of 93 if (outputFile) 94 return [objectPath(outputFile)]; 95 96 auto dSources = files.filter!(p => p.endsWith(".d")); 97 if (dSources.empty) 98 return []; 99 100 return [objectPath(dSources.front)]; 101 } 102 103 /** 104 * Returns the static library file path. 105 */ 106 string staticLibraryPath() const pure 107 { 108 import std.algorithm.iteration : filter; 109 import std.algorithm.searching : endsWith; 110 111 // If the output file has no extension, ".a" is appended. 112 113 // Note that -op and -o- have no effect when building static libraries. 114 115 string path; 116 117 if (outputFile) 118 path = defaultExtension(outputFile, ".a"); 119 else 120 { 121 // If no output file is specified with -of, the output file is based on 122 // the name of the first source file. 123 auto dSources = files.filter!(p => p.endsWith(".d")); 124 if (dSources.empty) 125 return null; 126 127 path = setExtension(baseName(dSources.front), ".a"); 128 } 129 130 return buildPath(outputDir, path); 131 } 132 133 /** 134 * Returns the shared library file path. 135 */ 136 string sharedLibraryPath() const pure 137 { 138 import std.algorithm.iteration : filter; 139 import std.algorithm.searching : endsWith; 140 141 if (outputFile) 142 return outputFile; 143 144 // If no output file is specified with -of, the output file is based on 145 // the name of the first source file. 146 auto dSources = files.filter!(p => p.endsWith(".d")); 147 if (dSources.empty) 148 return null; 149 150 return setExtension(baseName(dSources.front), ".so"); 151 } 152 153 /** 154 * Returns the static library file path. 155 */ 156 string executablePath() const pure 157 { 158 import std.algorithm.iteration : filter; 159 import std.algorithm.searching : endsWith; 160 161 if (outputFile) 162 return outputFile; 163 164 // If no output file is specified with -of, the output file is based on 165 // the name of the first source file. 166 auto dSources = files.filter!(p => p.endsWith(".d")); 167 if (dSources.empty) 168 return null; 169 170 return stripExtension(baseName(dSources.front)); 171 } 172 } 173 174 /** 175 * Parses DMD arguments. 176 */ 177 private Options parseArgs(const(string)[] args) pure 178 { 179 import std.algorithm.searching : startsWith; 180 import std.exception : enforce; 181 import std.range : front, popFront, empty; 182 183 Options opts; 184 185 while (!args.empty) 186 { 187 string arg = args.front; 188 189 if (arg == "-c") 190 opts.compileFlag = true; 191 else if (arg == "-cov") 192 opts.coverageFlag = true; 193 else if (arg == "-lib") 194 opts.libFlag = true; 195 else if (arg == "-shared") 196 opts.sharedFlag = true; 197 else if (arg == "-lib") 198 opts.docFlag = true; 199 else if (arg == "-H") 200 opts.headerFlag = true; 201 else if (arg == "-map") 202 opts.mapFlag = true; 203 else if (arg == "-X") 204 opts.jsonFlag = true; 205 else if (arg == "-op") 206 opts.opFlag = true; 207 else if (arg == "-o-") 208 opts.suppressObjectsFlag = true; 209 else if (arg == "-run") 210 { 211 args.popFront(); 212 opts.run = args; 213 break; 214 } 215 else if (arg.startsWith("-deps=")) 216 opts.depsFile = arg["-deps=".length .. $]; 217 else if (arg.startsWith("-od")) 218 opts.outputDir = arg["-od".length .. $]; 219 else if (arg.startsWith("-of")) 220 opts.outputFile = arg["-of".length .. $]; 221 else if (arg.startsWith("-Xf")) 222 opts.jsonFile = arg["-Xf".length .. $]; 223 else if (arg.startsWith("-Dd")) 224 opts.docDir = arg["-Dd".length .. $]; 225 else if (arg.startsWith("-Df")) 226 opts.docFile = arg["-Df".length .. $]; 227 else if (arg.startsWith("-Hd")) 228 opts.headerDir = arg["-Hd".length .. $]; 229 else if (arg.startsWith("-Hf")) 230 opts.headerFile = arg["-Hf".length .. $]; 231 else if (arg.startsWith("-I")) 232 opts.importDirs ~= arg["-I".length .. $]; 233 else if (arg.startsWith("-J")) 234 opts.stringImportDirs ~= arg["-J".length .. $]; 235 else if (arg.startsWith("-L")) 236 opts.stringImportDirs ~= arg["-L".length .. $]; 237 else if (arg.startsWith("@")) 238 opts.cmdFile = arg["@".length .. $]; 239 else if (!arg.startsWith("-")) 240 opts.files ~= arg; 241 242 args.popFront(); 243 } 244 245 return opts; 246 } 247 248 void execute( 249 ref BuildContext ctx, 250 const(string)[] args, 251 string workDir, 252 ref Resources inputs, 253 ref Resources outputs, 254 TaskLogger logger 255 ) 256 { 257 import button.handlers.base : base = execute; 258 259 import std.algorithm.iteration : map, filter, uniq; 260 import std.algorithm.searching : endsWith; 261 import std.range : enumerate, empty, popFront, front; 262 import std.regex : regex, matchAll; 263 import std.file : remove; 264 import std.array : array; 265 266 import io.text, io.range; 267 268 Options opts = parseArgs(args[1 .. $]); 269 270 string depsPath; 271 272 if (opts.depsFile is null) 273 { 274 // Output -deps to a temporary file. 275 depsPath = tempFile(AutoDelete.no).path; 276 args ~= "-deps=" ~ depsPath; 277 } 278 else 279 { 280 // -deps= was specified already. Just use this path to get the 281 // dependencies. 282 depsPath = opts.depsFile; 283 } 284 285 // Delete the temporary -deps file when done. 286 scope (exit) if (opts.depsFile is null) remove(depsPath); 287 288 base(ctx, args, workDir, inputs, outputs, logger); 289 290 // Add the inputs from the dependency file. 291 static r = regex(`\((.*?)\)`); 292 foreach (line; File(depsPath).byLine) 293 foreach (c; line.matchAll(r)) 294 inputs.put(c[1]); 295 296 inputs.put(opts.files); 297 298 // Determine the output files based on command line options. If no output 299 // file name is specified with -of, the file name is based on the first 300 // source file specified on the command line. 301 if (opts.libFlag) 302 { 303 if (auto path = opts.staticLibraryPath()) 304 outputs.put(path); 305 } 306 else if (opts.compileFlag) 307 { 308 outputs.put(opts.objects); 309 } 310 else if (opts.sharedFlag) 311 { 312 if (auto path = opts.sharedLibraryPath()) 313 outputs.put(path); 314 315 outputs.put(opts.objects); 316 } 317 else 318 { 319 if (!opts.suppressObjectsFlag) 320 { 321 // Binary executable. 322 if (auto path = opts.executablePath()) 323 outputs.put(path); 324 325 // Objects 326 outputs.put(opts.objects); 327 } 328 } 329 }