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