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