1 /** 2 This module contains the core data definitions that allow a build 3 to be expressed in. $(D Build) is a container struct for top-level 4 targets, $(D Target) is the heart of the system. 5 */ 6 7 module reggae.build; 8 9 import reggae.ctaa; 10 import reggae.rules.common: Language, getLanguage; 11 import reggae.options; 12 import reggae.path: buildPath, deabsolutePath; 13 14 import std..string: replace; 15 import std.algorithm; 16 import std.path: dirSeparator; 17 import std.typetuple: allSatisfy; 18 import std.traits: Unqual, isSomeFunction, ReturnType, arity; 19 import std.array: array, join; 20 import std.conv; 21 import std.exception; 22 import std.typecons; 23 import std.range; 24 import std.typecons; 25 26 27 /** 28 Contains the top-level targets. 29 */ 30 struct Build { 31 static struct TopLevelTarget { 32 Target target; 33 bool optional; 34 } 35 36 private TopLevelTarget[] _targets; 37 38 this(Target[] targets) { 39 _targets = targets.map!createTopLevelTarget.array; 40 } 41 42 this(R)(R targets) if(isInputRange!R && is(Unqual!(ElementType!R) == TopLevelTarget)) { 43 _targets = targets.array; 44 } 45 46 this(T...)(T targets) { 47 foreach(t; targets) { 48 //the constructor needs to go from Target to TopLevelTarget 49 //and accepts functions that return a parameter as well as parameters themselves 50 //if a function, call it, if not, take the value 51 //if the value is Target, call createTopLevelTarget, if not, take it as is 52 static if(isSomeFunction!(typeof(t)) && is(ReturnType!(typeof(t))) == Target) { 53 _targets ~= createTopLevelTarget(t()); 54 } else static if(is(Unqual!(typeof(t)) == TopLevelTarget)) { 55 _targets ~= t; 56 } else { 57 _targets ~= createTopLevelTarget(t); 58 } 59 } 60 } 61 62 auto targets() @trusted pure nothrow { 63 return _targets.map!(a => a.target); 64 } 65 66 auto defaultTargets() @trusted pure nothrow { 67 return _targets.filter!(a => !a.optional).map!(a => a.target); 68 } 69 70 string[] defaultTargetsOutputs(in string projectPath) @trusted pure { 71 return defaultTargets.map!(a => a.expandOutputs(projectPath)).join(); 72 } 73 74 auto range() @safe pure { 75 import reggae.range; 76 return UniqueDepthFirst(this); 77 } 78 79 ubyte[] toBytes(in Options options) @safe pure { 80 ubyte[] bytes; 81 bytes ~= setUshort(cast(ushort)targets.length); 82 foreach(t; targets) bytes ~= t.toBytes(options); 83 return bytes; 84 } 85 86 static Build fromBytes(ubyte[] bytes) @trusted { 87 immutable length = getUshort(bytes); 88 auto build = Build(); 89 foreach(_; 0 .. length) { 90 build._targets ~= TopLevelTarget(Target.fromBytes(bytes), false); 91 } 92 return build; 93 } 94 } 95 96 97 /** 98 Designate a target as optional so it won't be built by default. 99 "Compile-time" version that can be aliased 100 */ 101 Build.TopLevelTarget optional(alias targetFunc)() { 102 return optional(targetFunc()); 103 } 104 105 /** 106 Designate a target as optional so it won't be built by default. 107 */ 108 Build.TopLevelTarget optional(Target target) { 109 return createTopLevelTarget(target, true); 110 } 111 112 Build.TopLevelTarget createTopLevelTarget(Target target, bool optional = false) { 113 return Build.TopLevelTarget(target.inTopLevelObjDirOf(objDirOf(target), Yes.topLevel), optional); 114 } 115 116 117 immutable gBuilddir = "$builddir"; 118 immutable gProjdir = "$project"; 119 120 //a directory for each top-level target to avoid name clashes 121 //@trusted because of map -> buildPath -> array 122 Target inTopLevelObjDirOf(Target target, string objDir, Flag!"topLevel" isTopLevel = No.topLevel) @trusted { 123 if (target._outputs.any!(a => a.startsWith(gBuilddir) || a.startsWith(gProjdir))) 124 objDir = objDirOf(target); 125 126 // base dir for relative outputs: 127 const baseDir = isTopLevel ? null : // don't touch top-level target outputs 128 target.isLeaf ? gProjdir : // leaf targets are src files in the project dir 129 objDir; // obj directory specific to top-level target 130 131 auto outputs = target._outputs.map!(a => expandOutput(a, gProjdir, baseDir)).array; 132 133 return Target(outputs, 134 target._command.expandVariables, 135 target._dependencies.map!(a => a.inTopLevelObjDirOf(objDir)).array, 136 target._implicits.map!(a => a.inTopLevelObjDirOf(objDir)).array); 137 } 138 139 140 string objDirOf(in Target target) @safe pure nothrow { 141 // remove $builddir and shorten $project to __project__ 142 const output = expandOutput(target._outputs[0], "__project__"); 143 return buildPath(".reggae", "objs", output.deabsolutePath ~ ".objs"); 144 } 145 146 string expandOutput(string path, in string projectPath, in string basePath = null) @safe pure nothrow { 147 path = buildPath(path); // normalize to native slashes 148 // $builddir/foo => foo 149 if (path.startsWith(gBuilddir ~ dirSeparator)) return path[gBuilddir.length+1 .. $]; 150 // $project/foo => <projectPath>/foo 151 if (path.startsWith(gProjdir ~ dirSeparator)) return buildPath(projectPath, path[gProjdir.length+1 .. $]); 152 // don't touch paths starting with env variables 153 if (path.startsWith("$")) return path; 154 // /foo => /foo 155 // bar => <basePath>/bar 156 return buildPath(basePath, path); 157 } 158 159 160 enum isTarget(alias T) = 161 is(Unqual!(typeof(T)) == Target) || 162 is(Unqual!(typeof(T)) == Build.TopLevelTarget) || 163 isSomeFunction!T && is(ReturnType!T == Target) || 164 isSomeFunction!T && is(ReturnType!T == Build.TopLevelTarget); 165 166 unittest { 167 auto t1 = Target(); 168 const t2 = Target(); 169 static assert(isTarget!t1); 170 static assert(isTarget!t2); 171 const t3 = Build.TopLevelTarget(Target()); 172 static assert(isTarget!t3); 173 } 174 175 mixin template buildImpl(targets...) if(allSatisfy!(isTarget, targets)) { 176 Build buildFunc() { 177 return Build(targets); 178 } 179 } 180 181 /** 182 Two variations on a template mixin. When reggae is used as a library, 183 this will essentially build reggae itself as part of the build description. 184 185 When reggae is used as a command-line tool to generate builds, it simply 186 declares the build function that will be called at run-time. The tool 187 will then compile the user's reggaefile.d with the reggae libraries, 188 resulting in a buildgen executable. 189 190 In either case, the compile-time parameters of $(D build) are the 191 build's top-level targets. 192 */ 193 version(reggaelib) { 194 mixin template build(targets...) if(allSatisfy!(isTarget, targets)) { 195 mixin reggaeGen!(targets); 196 } 197 } else { 198 alias build = buildImpl; 199 } 200 201 package template isBuildFunction(alias T) { 202 static if(!isSomeFunction!T) { 203 enum isBuildFunction = false; 204 } else { 205 enum isBuildFunction = is(ReturnType!T == Build) && arity!T == 0; 206 } 207 } 208 209 unittest { 210 Build myBuildFunction() { return Build(); } 211 static assert(isBuildFunction!myBuildFunction); 212 float foo; 213 static assert(!isBuildFunction!foo); 214 } 215 216 217 private static auto arrayify(E, T)(T value) { 218 static if(isInputRange!T && is(Unqual!(ElementType!T) == E)) 219 return value.array; 220 else static if(is(Unqual!T == E)) 221 return [value]; 222 else static if(is(Unqual!T == void[])) { 223 E[] nothing; 224 return nothing; 225 } else static if(is(Unqual!T == string)) 226 return [E(value)]; 227 else { 228 import std.conv: text; 229 static assert(false, text("Can not arraify value of type ", T.stringof)); 230 } 231 } 232 233 234 /** 235 The core of reggae's D-based DSL for describing build systems. 236 Targets contain outputs, a command to generate those outputs, 237 explicit dependencies and implicit dependencies. All dependencies 238 are themselves $(D Target) structs. 239 240 The command is given as a string. In this string, certain words 241 have special meaning: $(D $in), $(D $out), $(D $project) and $(D builddir). 242 243 $(D $in) gets expanded to all explicit dependencies. 244 $(D $out) gets expanded to all outputs. 245 $(D $project) gets expanded to the project directory (i.e. the directory including 246 the source files to build that was given as a command-line argument). This can be 247 useful when build outputs are to be placed in the source directory, such as 248 automatically generated source files. 249 $(D $builddir) expands to the build directory (i.e. where reggae was run from). 250 */ 251 struct Target { 252 private string[] _outputs; 253 private Command _command; ///see $(D Command) struct 254 private Target[] _dependencies; 255 private Target[] _implicits; 256 257 enum Target[] noTargets = []; 258 259 this(string output) @safe pure nothrow { 260 this(output, "", noTargets, noTargets); 261 } 262 263 this(O, C)(O outputs, C command) { 264 this(outputs, command, noTargets, noTargets); 265 } 266 267 this(O, C, D)(O outputs, C command, D dependencies) { 268 this(outputs, command, dependencies, noTargets); 269 } 270 271 this(O, C, D, I)(O outputs, C command, D dependencies, I implicits) { 272 273 this._outputs = arrayify!string(outputs); 274 275 static if(is(C == Command)) 276 this._command = command; 277 else 278 this._command = Command(command); 279 280 this._dependencies = arrayify!Target(dependencies); 281 this._implicits = arrayify!Target(implicits); 282 } 283 284 /** 285 The outputs without expanding special variables 286 */ 287 @property inout(string)[] rawOutputs(in string projectPath = "") @safe pure inout { 288 return _outputs; 289 } 290 291 @property inout(Target)[] dependencyTargets(in string projectPath = "") @safe pure nothrow inout { 292 return _dependencies; 293 } 294 295 @property inout(Target)[] implicitTargets(in string projectPath = "") @safe pure nothrow inout { 296 return _implicits; 297 } 298 299 @property string[] dependenciesInProjectPath(in string projectPath) @safe pure const { 300 return depsInProjectPath(_dependencies, projectPath); 301 } 302 303 @property string[] implicitsInProjectPath(in string projectPath) @safe pure const { 304 return depsInProjectPath(_implicits, projectPath); 305 } 306 307 bool isLeaf() @safe pure const nothrow { 308 return _dependencies is null && _implicits is null && getCommandType == CommandType.shell && _command.command == ""; 309 } 310 311 Language getLanguage() @safe pure const nothrow { 312 import reggae.range: Leaves; 313 import reggae.rules.common: getLanguage; 314 import std.algorithm: any; 315 316 auto leaves = () @trusted { return Leaves(this).array; }(); 317 318 foreach(language; [Language.D, Language.Cplusplus, Language.C]) { 319 if(leaves.any!(a => a._outputs.length && .getLanguage(a._outputs[0]) == language)) 320 return language; 321 } 322 323 return Language.unknown; 324 } 325 326 ///Replace special variables and return a list of outputs thus modified 327 string[] expandOutputs(in string projectPath) @safe pure const { 328 return _outputs.map!(o => expandOutput(o, projectPath)).array; 329 } 330 331 //@trusted because of replace 332 string rawCmdString(in string projectPath = "") @safe pure const { 333 return _command.rawCmdString(projectPath); 334 } 335 336 ///returns a command string to be run by the shell 337 string shellCommand(in Options options, 338 Flag!"dependencies" deps = Yes.dependencies) @safe pure const { 339 return _command.shellCommand(options, getLanguage(), _outputs, inputs(options.projectPath), deps); 340 } 341 342 // not const because the code commands take inputs and outputs as non-const strings 343 const(string)[] execute(in Options options) @safe const { 344 return _command.execute(options, getLanguage(), _outputs, inputs(options.projectPath)); 345 } 346 347 bool hasDefaultCommand() @safe const pure { 348 return _command.isDefaultCommand; 349 } 350 351 CommandType getCommandType() @safe pure const nothrow { 352 return _command.getType; 353 } 354 355 string[] getCommandParams(in string projectPath, in string key, string[] ifNotFound) @safe pure const { 356 return _command.getParams(projectPath, key, ifNotFound); 357 } 358 359 const(string)[] commandParamNames() @safe pure nothrow const { 360 return _command.paramNames; 361 } 362 363 static Target phony(T...)(string name, string shellCommand, T args) { 364 return Target(name, Command.phony(shellCommand), args); 365 } 366 367 string toString(in Options options) nothrow const { 368 try { 369 if(isLeaf) return _outputs[0]; 370 immutable _outputs = _outputs.length == 1 ? `"` ~ _outputs[0] ~ `"` : text(_outputs); 371 immutable depsStr = _dependencies.length == 0 ? "" : text(_dependencies); 372 immutable impsStr = _implicits.length == 0 ? "" : text(_implicits); 373 auto parts = [text(_outputs), `"` ~ shellCommand(options) ~ `"`]; 374 if(depsStr != "") parts ~= depsStr; 375 if(impsStr != "") parts ~= impsStr; 376 return text("Target(", parts.join(",\n"), ")"); 377 } catch(Exception) { 378 assert(0); 379 } 380 } 381 382 ubyte[] toBytes(in Options options) @safe pure const { 383 ubyte[] bytes; 384 bytes ~= setUshort(cast(ushort)_outputs.length); 385 foreach(output; _outputs) { 386 bytes ~= arrayToBytes(expandOutput(output, options.projectPath, isLeaf ? options.projectPath : null)); 387 } 388 389 bytes ~= arrayToBytes(shellCommand(options)); 390 391 bytes ~= setUshort(cast(ushort)_dependencies.length); 392 foreach(dep; _dependencies) bytes ~= dep.toBytes(options); 393 394 bytes ~= setUshort(cast(ushort)_implicits.length); 395 foreach(imp; _implicits) bytes ~= imp.toBytes(options); 396 397 return bytes; 398 } 399 400 static Target fromBytes(ref ubyte[] bytes) @trusted pure nothrow { 401 string[] outputs; 402 immutable numOutputs = getUshort(bytes); 403 404 foreach(i; 0 .. numOutputs) { 405 outputs ~= cast(string)bytesToArray!char(bytes); 406 } 407 408 auto command = Command(cast(string)bytesToArray!char(bytes)); 409 410 Target[] dependencies; 411 immutable numDeps = getUshort(bytes); 412 foreach(i; 0..numDeps) dependencies ~= Target.fromBytes(bytes); 413 414 Target[] implicits; 415 immutable numImps = getUshort(bytes); 416 foreach(i; 0..numImps) implicits ~= Target.fromBytes(bytes); 417 418 return Target(outputs, command, dependencies, implicits); 419 } 420 421 bool opEquals()(auto ref const Target other) @safe pure const { 422 423 bool sameSet(T)(const(T)[] fst, const(T)[] snd) { 424 if(fst.length != snd.length) return false; 425 return fst.all!(a => snd.any!(b => a == b)); 426 } 427 428 return 429 sameSet(_outputs, other._outputs) && 430 _command == other._command && 431 sameSet(_dependencies, other._dependencies) && 432 sameSet(_implicits, other._implicits); 433 } 434 435 private: 436 437 string[] depsInProjectPath(in Target[] deps, in string projectPath) @safe pure const { 438 import reggae.range; 439 return deps.map!(a => a.expandOutputs(projectPath)).join; 440 } 441 442 string[] inputs(in string projectPath) @safe pure nothrow const { 443 //functional didn't work here, I don't know why so sticking with loops for now 444 string[] inputs; 445 foreach(dep; _dependencies) { 446 foreach(output; dep._outputs) { 447 //leaf objects are references to source files in the project path, 448 //those need their path built. Any other dependencies are in the 449 //build path, so they don't need the same treatment 450 inputs ~= expandOutput(output, projectPath, dep.isLeaf ? projectPath : null); 451 } 452 } 453 return inputs; 454 } 455 } 456 457 458 enum CommandType { 459 shell, 460 compile, 461 link, 462 compileAndLink, 463 code, 464 phony, 465 } 466 467 alias CommandFunction = void function(in string[], in string[]); 468 alias CommandDelegate = void delegate(in string[], in string[]); 469 470 /** 471 A command to be execute to produce a targets outputs from its inputs. 472 In general this will be a shell command, but the high-level rules 473 use commands with known semantics (compilation, linking, etc) 474 */ 475 struct Command { 476 alias Params = AssocList!(string, string[]); 477 478 private string command; 479 private CommandType type; 480 private Params params; 481 private CommandFunction function_; 482 private CommandDelegate delegate_; 483 484 ///If constructed with a string, it's a shell command 485 this(string shellCommand) @safe pure nothrow { 486 command = shellCommand; 487 type = CommandType.shell; 488 } 489 490 /**Explicitly request a command of this type with these parameters 491 In general to create one of the builtin high level rules*/ 492 this(CommandType type, Params params = Params()) @safe pure { 493 if(type == CommandType.shell || type == CommandType.code) 494 throw new Exception("Command rule cannot be shell or code"); 495 this.type = type; 496 this.params = params; 497 } 498 499 ///A D function call command 500 this(CommandDelegate dg) @safe pure nothrow { 501 type = CommandType.code; 502 this.delegate_ = dg; 503 } 504 505 ///A D function call command 506 this(CommandFunction func) @safe pure nothrow { 507 type = CommandType.code; 508 this.function_ = func; 509 } 510 511 static Command phony(in string shellCommand) @safe pure nothrow { 512 Command cmd; 513 cmd.type = CommandType.phony; 514 cmd.command = shellCommand; 515 return cmd; 516 } 517 518 const(string)[] paramNames() @safe pure nothrow const { 519 return params.keys; 520 } 521 522 CommandType getType() @safe pure const nothrow { 523 return type; 524 } 525 526 bool isDefaultCommand() @safe pure const { 527 return type == CommandType.compile || type == CommandType.link || type == CommandType.compileAndLink; 528 } 529 530 string[] getParams(in string projectPath, in string key, string[] ifNotFound) @safe pure const { 531 return getParams(projectPath, key, true, ifNotFound); 532 } 533 534 Command expandVariables() @safe pure { 535 switch(type) with(CommandType) { 536 case shell: 537 case phony: 538 string expCommand = command.replace(gBuilddir ~ dirSeparator, ""); 539 version(Windows) 540 expCommand = expCommand.replace(gBuilddir ~ "/", ""); 541 expCommand = expCommand.replace(gBuilddir, "."); 542 auto cmd = Command(expCommand); 543 cmd.type = this.type; 544 return cmd; 545 default: 546 return this; 547 } 548 } 549 550 ///Replace $in, $out, $project with values and remove $builddir 551 private static string expandCmd(in string cmd, in string projectPath, 552 in string[] outputs, in string[] inputs) @safe pure { 553 auto outs = outputs.map!buildPath; 554 auto ins = inputs.map!buildPath; 555 auto replaceIn = cmd.dup.replace("$in", ins.join(" ")); 556 auto replaceOut = replaceIn.replace("$out", outs.join(" ")); 557 auto r = replaceOut.replace(gProjdir, buildPath(projectPath)); 558 r = r.replace(gBuilddir ~ dirSeparator, ""); 559 version(Windows) 560 r = r.replace(gBuilddir ~ "/", ""); 561 r = r.replace(gBuilddir, "."); 562 return r; 563 } 564 565 string rawCmdString(in string projectPath) @safe pure const { 566 if(getType != CommandType.shell) 567 throw new Exception("Command type 'code' not supported for ninja backend"); 568 return command.replace(gProjdir, buildPath(projectPath)); 569 } 570 571 private string[] getParams(string projectPath, in string key, 572 bool useIfNotFound, string[] ifNotFound = []) @safe pure const { 573 projectPath = buildPath(projectPath); 574 return params.get(key, ifNotFound).map!(a => a.replace(gProjdir, projectPath)).array; 575 } 576 577 static private string getDefaultDCompilerModelArg(in Options options) @safe pure nothrow { 578 version(Windows) { 579 import std.path: baseName, stripExtension; 580 const isDMD = baseName(stripExtension(options.dCompiler)) == "dmd"; 581 return isDMD ? " -m32mscoff" : null; 582 } else { 583 return null; 584 } 585 } 586 587 static string builtinTemplate(in CommandType type, 588 in Language language, 589 in Options options, 590 in Flag!"dependencies" deps = Yes.dependencies) @safe pure { 591 592 final switch(type) with(CommandType) { 593 case phony: 594 assert(0, "builtinTemplate cannot be phony"); 595 596 case shell: 597 assert(0, "builtinTemplate cannot be shell"); 598 599 case link: { 600 version(Windows) 601 immutable cArgs = " /nologo /Fo$out $flags $in"; 602 else 603 immutable cArgs = " -o $out $flags $in"; 604 605 final switch(language) with(Language) { 606 case D: 607 case unknown: 608 return options.dCompiler ~ getDefaultDCompilerModelArg(options) ~ " -of$out $flags $in"; 609 case Cplusplus: 610 return options.cppCompiler ~ cArgs; 611 case C: 612 return options.cCompiler ~ cArgs; 613 } 614 } 615 616 case code: 617 throw new Exception("Command type 'code' has no built-in template"); 618 619 case compile: 620 return compileTemplate(type, language, options, deps).replace("$out $in", "$out -c $in"); 621 622 case compileAndLink: 623 return compileTemplate(type, language, options, deps); 624 } 625 } 626 627 private static string compileTemplate(in CommandType type, 628 in Language language, 629 in Options options, 630 in Flag!"dependencies" deps = Yes.dependencies) @safe pure { 631 version(Windows) 632 { 633 immutable ccParams = 634 " /nologo $flags $includes" ~ (deps ? " /showIncludes" : null) ~ " /Fo$out $in"; 635 } 636 else 637 { 638 immutable ccParams = deps 639 ? " $flags $includes -MMD -MT $out -MF $out.dep -o $out $in" 640 : " $flags $includes -o $out $in"; 641 } 642 643 final switch(language) with(Language) { 644 case D: { 645 const modelArg = getDefaultDCompilerModelArg(options); 646 return deps 647 ? buildPath(".reggae/dcompile") ~ " --objFile=$out --depFile=$out.dep " ~ 648 options.dCompiler ~ modelArg ~ " $flags $includes $stringImports $in" 649 : options.dCompiler ~ modelArg ~ " $flags $includes $stringImports -of$out $in"; 650 } 651 case Cplusplus: 652 return options.cppCompiler ~ ccParams; 653 case C: 654 return options.cCompiler ~ ccParams; 655 case unknown: 656 throw new Exception("Unsupported language for compiling"); 657 } 658 } 659 660 string defaultCommand(in Options options, 661 in Language language, 662 in string[] outputs, 663 in string[] inputs, 664 Flag!"dependencies" deps = Yes.dependencies) @safe pure const { 665 666 import std.conv: text; 667 668 assert(isDefaultCommand, text("This command is not a default command: ", this)); 669 string cmd; 670 try 671 cmd = builtinTemplate(type, language, options, deps); 672 catch(Exception ex) { 673 throw new Exception(text(ex.msg, "\noutputs: ", outputs, "\ninputs: ", inputs)); 674 } 675 676 foreach(key; params.keys) { 677 immutable var = "$" ~ key; 678 immutable value = getParams(options.projectPath, key, []).join(" "); 679 cmd = cmd.replace(var, value); 680 } 681 return expandCmd(cmd, options.projectPath, outputs, inputs); 682 } 683 684 ///returns a command string to be run by the shell 685 string shellCommand(in Options options, 686 in Language language, 687 in string[] outputs, 688 in string[] inputs, 689 Flag!"dependencies" deps = Yes.dependencies) @safe pure const { 690 return isDefaultCommand 691 ? defaultCommand(options, language, outputs, inputs, deps) 692 : expandCmd(command, options.projectPath, outputs, inputs); 693 } 694 695 const(string)[] execute(in Options options, in Language language, 696 in string[] outputs, in string[] inputs) const @trusted { 697 import std.process; 698 699 final switch(type) with(CommandType) { 700 case shell: 701 case compile: 702 case link: 703 case compileAndLink: 704 case phony: 705 immutable cmd = shellCommand(options, language, outputs, inputs); 706 if(cmd == "") return outputs; 707 708 const string[string] env = null; 709 Config config = Config.none; 710 size_t maxOutput = size_t.max; 711 712 immutable res = executeShell(cmd, env, config, maxOutput, options.workingDir); 713 enforce(res.status == 0, "Could not execute phony " ~ cmd ~ ":\n" ~ res.output); 714 return [res.output]; 715 case code: 716 assert(function_ !is null || delegate_ !is null, 717 "Command of type code with null function"); 718 function_ !is null ? function_(inputs, outputs) : delegate_(inputs, outputs); 719 return ["code"]; 720 } 721 } 722 723 ubyte[] toBytes() @safe pure nothrow const { 724 final switch(type) { 725 726 case CommandType.shell: 727 return [cast(ubyte)type] ~ cast(ubyte[])command.dup; 728 729 case CommandType.compile: 730 case CommandType.compileAndLink: 731 case CommandType.link: 732 case CommandType.phony: 733 ubyte[] bytes; 734 bytes ~= cast(ubyte)type; 735 bytes ~= cast(ubyte)(params.keys.length >> 8); 736 bytes ~= (params.keys.length & 0xff); 737 foreach(key; params.keys) { 738 bytes ~= arrayToBytes(key); 739 bytes ~= cast(ubyte)(params[key].length >> 8); 740 bytes ~= (params[key].length & 0xff); 741 foreach(value; params[key]) 742 bytes ~= arrayToBytes(value); 743 } 744 return bytes; 745 746 case CommandType.code: 747 assert(0); 748 } 749 } 750 751 static Command fromBytes(ubyte[] bytes) @trusted pure { 752 immutable type = cast(CommandType)bytes[0]; 753 bytes = bytes[1..$]; 754 755 final switch(type) { 756 757 case CommandType.shell: 758 char[] chars; 759 foreach(b; bytes) chars ~= cast(char)b; 760 return Command(cast(string)chars); 761 762 case CommandType.compile: 763 case CommandType.compileAndLink: 764 case CommandType.link: 765 case CommandType.phony: 766 Params params; 767 768 immutable numKeys = getUshort(bytes); 769 foreach(i; 0..numKeys) { 770 immutable key = cast(string)bytesToArray!char(bytes); 771 immutable numValues = getUshort(bytes); 772 773 string[] values; 774 foreach(j; 0..numValues) { 775 values ~= bytesToArray!char(bytes); 776 } 777 params[key] = values; 778 } 779 return Command(type, params); 780 781 case CommandType.code: 782 throw new Exception("Cannot serialise Command of type code"); 783 } 784 } 785 786 string toString() const pure @safe { 787 final switch(type) with(CommandType) { 788 case shell: 789 case phony: 790 return `Command("` ~ command ~ `")`; 791 case compile: 792 case link: 793 case compileAndLink: 794 case code: 795 return `Command(` ~ type.to!string ~ 796 (params.keys.length ? ", " ~ text(params) : "") ~ 797 `)`; 798 } 799 } 800 } 801 802 803 private ubyte[] arrayToBytes(T)(in T[] arr) { 804 auto bytes = new ubyte[arr.length + 2]; 805 immutable length = cast(ushort)arr.length; 806 bytes[0] = length >> 8; 807 bytes[1] = length & 0xff; 808 foreach(i, c; arr) bytes[i + 2] = cast(ubyte)c; 809 return bytes; 810 } 811 812 813 private T[] bytesToArray(T)(ref ubyte[] bytes) { 814 T[] arr; 815 arr.length = getUshort(bytes); 816 foreach(i, b; bytes[0 .. arr.length]) arr[i] = cast(T)b; 817 bytes = bytes[arr.length .. $]; 818 return arr; 819 } 820 821 822 private ushort getUshort(ref ubyte[] bytes) @safe pure nothrow { 823 immutable length = (bytes[0] << 8) + bytes[1]; 824 bytes = bytes[2..$]; 825 return length; 826 } 827 828 private ubyte[] setUshort(in ushort length) @safe pure nothrow { 829 auto bytes = new ubyte[2]; 830 bytes[0] = length >> 8; 831 bytes[1] = length & 0xff; 832 return bytes; 833 } 834 835 836 string replaceConcreteCompilersWithVars(in string cmd, in Options options) @safe pure nothrow { 837 return cmd. 838 replace(options.dCompiler, "$(DC)"). 839 replace(options.cppCompiler, "$(CXX)"). 840 replace(options.cCompiler, "$(CC)"); 841 }