1 module reggae.build; 2 3 import std.string: replace; 4 import std.algorithm: map; 5 import std.path: buildPath; 6 import std.typetuple: allSatisfy; 7 import std.traits: Unqual, isSomeFunction, ReturnType, arity; 8 import std.array: array, join; 9 10 11 Target createTargetFromTarget(in Target target) { 12 return Target(target.outputs, 13 target._command.removeBuilddir, 14 target.dependencies.map!(a => a.enclose(target)).array, 15 target.implicits.map!(a => a.enclose(target)).array); 16 } 17 18 19 struct Build { 20 const(Target)[] targets; 21 22 this(in Target[] targets) { 23 this.targets = targets.map!createTargetFromTarget.array; 24 } 25 26 this(T...)(in T targets) { 27 foreach(t; targets) { 28 static if(isSomeFunction!(typeof(t))) { 29 const target = t(); 30 } else { 31 const target = t; 32 } 33 34 this.targets ~= createTargetFromTarget(target); 35 } 36 } 37 } 38 39 //a directory for each top-level target no avoid name clashes 40 //@trusted because of map -> buildPath -> array 41 Target enclose(in Target target, in Target topLevel) @trusted { 42 if(target.isLeaf) return Target(target.outputs.map!(a => a.removeBuilddir).array, 43 target._command.removeBuilddir, 44 target.dependencies, 45 target.implicits); 46 47 immutable dirName = buildPath("objs", topLevel.outputs[0] ~ ".objs"); 48 return Target(target.outputs.map!(a => realTargetPath(dirName, a)).array, 49 target._command.removeBuilddir, 50 target.dependencies.map!(a => a.enclose(topLevel)).array, 51 target.implicits.map!(a => a.enclose(topLevel)).array); 52 } 53 54 immutable gBuilddir = "$builddir"; 55 56 57 private string realTargetPath(in string dirName, in string output) @trusted pure { 58 import std.algorithm: canFind; 59 60 return output.canFind(gBuilddir) 61 ? output.removeBuilddir 62 : buildPath(dirName, output); 63 } 64 65 private string removeBuilddir(in string output) @trusted pure { 66 import std.path: buildNormalizedPath; 67 import std.algorithm; 68 return output. 69 splitter. 70 map!(a => a.canFind(gBuilddir) ? a.replace(gBuilddir, ".").buildNormalizedPath : a). 71 join(" "); 72 } 73 74 enum isTarget(alias T) = is(Unqual!(typeof(T)) == Target) || 75 isSomeFunction!T && is(ReturnType!T == Target); 76 77 unittest { 78 auto t1 = Target(); 79 const t2 = Target(); 80 static assert(isTarget!t1); 81 static assert(isTarget!t2); 82 } 83 84 mixin template build(T...) if(allSatisfy!(isTarget, T)) { 85 Build buildFunc() { 86 return Build(T); 87 } 88 } 89 90 91 package template isBuildFunction(alias T) { 92 static if(!isSomeFunction!T) { 93 enum isBuildFunction = false; 94 } else { 95 enum isBuildFunction = is(ReturnType!T == Build) && arity!T == 0; 96 } 97 } 98 99 unittest { 100 Build myBuildFunction() { return Build(); } 101 static assert(isBuildFunction!myBuildFunction); 102 float foo; 103 static assert(!isBuildFunction!foo); 104 } 105 106 107 struct Target { 108 const(string)[] outputs; 109 const(Target)[] dependencies; 110 const(Target)[] implicits; 111 112 this(in string output) @safe pure nothrow { 113 this(output, null, null); 114 } 115 116 this(in string output, string command, in Target dependency, 117 in Target[] implicits = []) @safe pure nothrow { 118 this([output], command, [dependency], implicits); 119 } 120 121 this(in string output, string command, 122 in Target[] dependencies, in Target[] implicits = []) @safe pure nothrow { 123 this([output], command, dependencies, implicits); 124 } 125 126 this(in string[] outputs, string command, 127 in Target[] dependencies, in Target[] implicits = []) @safe pure nothrow { 128 this.outputs = outputs; 129 this.dependencies = dependencies; 130 this.implicits = implicits; 131 this._command = command; 132 } 133 134 @property string dependencyFiles(in string projectPath = "") @safe const nothrow { 135 return depFilesImpl(dependencies, projectPath); 136 } 137 138 @property string implicitFiles(in string projectPath = "") @safe const nothrow { 139 return depFilesImpl(implicits, projectPath); 140 } 141 142 @property string command(in string projectPath = "") @trusted pure const nothrow { 143 //functional didn't work here, I don't know why so sticking with loops for now 144 string[] depOutputs; 145 foreach(dep; dependencies) { 146 foreach(output; dep.outputs) { 147 depOutputs ~= dep.isLeaf ? buildPath(projectPath, output) : output; 148 } 149 } 150 auto replaceIn = _command.replace("$in", depOutputs.join(" ")); 151 auto replaceOut = replaceIn.replace("$out", outputs.join(" ")); 152 return replaceOut.replace("$project", projectPath); 153 } 154 155 bool isLeaf() @safe pure const nothrow { 156 return dependencies is null && implicits is null; 157 } 158 159 //@trusted because of replace 160 package string inOutCommand(in string projectPath = "") @trusted pure nothrow const { 161 return _command.replace("$project", projectPath); 162 } 163 164 private: 165 166 string _command; 167 168 //@trusted because of join 169 string depFilesImpl(in Target[] deps, in string projectPath) @trusted const nothrow { 170 import std.conv; 171 string files; 172 //join doesn't do const, resort to loops 173 foreach(i, dep; deps) { 174 files ~= text(dep.outputs.map!(a => dep.isLeaf ? buildPath(projectPath, a) : a).join(" ")); 175 if(i != deps.length - 1) files ~= " "; 176 } 177 return files; 178 } 179 }