1 module reggae.build; 2 3 import reggae.rules.defaults; 4 import std.string: replace; 5 import std.algorithm: map; 6 import std.path: buildPath; 7 import std.typetuple: allSatisfy; 8 import std.traits: Unqual, isSomeFunction, ReturnType, arity; 9 import std.array: array, join; 10 11 12 Target createTargetFromTarget(in Target target) { 13 return Target(target.outputs, 14 target._command.removeBuilddir, 15 target.dependencies.map!(a => a.enclose(target)).array, 16 target.implicits.map!(a => a.enclose(target)).array); 17 } 18 19 20 struct Build { 21 const(Target)[] targets; 22 23 this(in Target[] targets) { 24 this.targets = targets.map!createTargetFromTarget.array; 25 } 26 27 this(T...)(in T targets) { 28 foreach(t; targets) { 29 static if(isSomeFunction!(typeof(t))) { 30 const target = t(); 31 } else { 32 const target = t; 33 } 34 35 this.targets ~= createTargetFromTarget(target); 36 } 37 } 38 } 39 40 //a directory for each top-level target no avoid name clashes 41 //@trusted because of map -> buildPath -> array 42 Target enclose(in Target target, in Target topLevel) @trusted { 43 if(target.isLeaf) return Target(target.outputs.map!(a => a.removeBuilddir).array, 44 target._command.removeBuilddir, 45 target.dependencies, 46 target.implicits); 47 48 immutable dirName = buildPath("objs", topLevel.outputs[0] ~ ".objs"); 49 return Target(target.outputs.map!(a => realTargetPath(dirName, a)).array, 50 target._command.removeBuilddir, 51 target.dependencies.map!(a => a.enclose(topLevel)).array, 52 target.implicits.map!(a => a.enclose(topLevel)).array); 53 } 54 55 immutable gBuilddir = "$builddir"; 56 57 58 private string realTargetPath(in string dirName, in string output) @trusted pure { 59 import std.algorithm: canFind; 60 61 return output.canFind(gBuilddir) 62 ? output.removeBuilddir 63 : buildPath(dirName, output); 64 } 65 66 private string removeBuilddir(in string output) @trusted pure { 67 import std.path: buildNormalizedPath; 68 import std.algorithm; 69 return output. 70 splitter. 71 map!(a => a.canFind(gBuilddir) ? a.replace(gBuilddir, ".").buildNormalizedPath : a). 72 join(" "); 73 } 74 75 enum isTarget(alias T) = is(Unqual!(typeof(T)) == Target) || 76 isSomeFunction!T && is(ReturnType!T == Target); 77 78 unittest { 79 auto t1 = Target(); 80 const t2 = Target(); 81 static assert(isTarget!t1); 82 static assert(isTarget!t2); 83 } 84 85 mixin template build(T...) if(allSatisfy!(isTarget, T)) { 86 Build buildFunc() { 87 return Build(T); 88 } 89 } 90 91 92 package template isBuildFunction(alias T) { 93 static if(!isSomeFunction!T) { 94 enum isBuildFunction = false; 95 } else { 96 enum isBuildFunction = is(ReturnType!T == Build) && arity!T == 0; 97 } 98 } 99 100 unittest { 101 Build myBuildFunction() { return Build(); } 102 static assert(isBuildFunction!myBuildFunction); 103 float foo; 104 static assert(!isBuildFunction!foo); 105 } 106 107 108 struct Target { 109 const(string)[] outputs; 110 const(Target)[] dependencies; 111 const(Target)[] implicits; 112 113 this(in string output) @safe pure nothrow { 114 this(output, null, null); 115 } 116 117 this(in string output, string command, in Target dependency, 118 in Target[] implicits = []) @safe pure nothrow { 119 this([output], command, [dependency], implicits); 120 } 121 122 this(in string output, string command, 123 in Target[] dependencies, in Target[] implicits = []) @safe pure nothrow { 124 this([output], command, dependencies, implicits); 125 } 126 127 this(in string[] outputs, string command, 128 in Target[] dependencies, in Target[] implicits = []) @safe pure nothrow { 129 this.outputs = outputs; 130 this.dependencies = dependencies; 131 this.implicits = implicits; 132 this._command = command; 133 } 134 135 @property string dependencyFilesString(in string projectPath = "") @safe pure const nothrow { 136 return depFilesStringImpl(dependencies, projectPath); 137 } 138 139 @property string implicitFilesString(in string projectPath = "") @safe pure const nothrow { 140 return depFilesStringImpl(implicits, projectPath); 141 } 142 143 @property string command(in string projectPath = "") @trusted pure const nothrow { 144 //functional didn't work here, I don't know why so sticking with loops for now 145 string[] depOutputs; 146 foreach(dep; dependencies) { 147 foreach(output; dep.outputs) { 148 //leaf objects are references to source files in the project path 149 //those need their path built. Any other dependencies are in the 150 //build path, so they don't need the same treatment 151 depOutputs ~= dep.isLeaf ? buildPath(projectPath, output) : output; 152 } 153 } 154 auto replaceIn = _command.replace("$in", depOutputs.join(" ")); 155 auto replaceOut = replaceIn.replace("$out", outputs.join(" ")); 156 return replaceOut.replace("$project", projectPath); 157 } 158 159 bool isLeaf() @safe pure const nothrow { 160 return dependencies is null && implicits is null; 161 } 162 163 //@trusted because of replace 164 string rawCmdString(in string projectPath) @trusted pure nothrow const { 165 return _command.replace("$project", projectPath); 166 } 167 168 169 string shellCommand(in string projectPath = "") @safe pure const { 170 immutable rawCmdLine = rawCmdString(projectPath); 171 if(rawCmdLine.isDefaultCommand) { 172 return defaultCommand(projectPath, rawCmdLine); 173 } else { 174 return command(projectPath); 175 } 176 } 177 178 string[] outputsInProjectPath(in string projectPath) @safe pure nothrow const { 179 return outputs.map!(a => isLeaf ? buildPath(projectPath, a) : a).array; 180 } 181 182 private: 183 184 string _command; 185 186 //@trusted because of join 187 string depFilesStringImpl(in Target[] deps, in string projectPath) @trusted pure const nothrow { 188 import std.conv; 189 string files; 190 //join doesn't do const, resort to loops 191 foreach(i, dep; deps) { 192 files ~= text(dep.outputsInProjectPath(projectPath).join(" ")); 193 if(i != deps.length - 1) files ~= " "; 194 } 195 return files; 196 } 197 198 //this function returns a string to be run by the shell with `std.process.execute` 199 //it does 'normal' commands, not built-in rules 200 string defaultCommand(in string projectPath, in string rawCmdLine) @safe pure const { 201 import reggae.config: dCompiler, cppCompiler, cCompiler; 202 203 immutable flags = rawCmdLine.getDefaultRuleParams("flags", []).join(" "); 204 immutable includes = rawCmdLine.getDefaultRuleParams("includes", []).join(" "); 205 immutable depfile = outputs[0] ~ ".dep"; 206 207 string ccCommand(in string compiler) { 208 import std.stdio; 209 debug writeln("ccCommand with compiler ", compiler); 210 return [compiler, flags, includes, "-MMD", "-MT", outputs[0], 211 "-MF", depfile, "-o", outputs[0], "-c", 212 dependencyFilesString(projectPath)].join(" "); 213 } 214 215 216 immutable rule = rawCmdLine.getDefaultRule; 217 import std.stdio; 218 debug writeln("rule: ", rule); 219 220 switch(rule) { 221 222 case "_dcompile": 223 immutable stringImports = rawCmdLine.getDefaultRuleParams("stringImports", []).join(" "); 224 immutable command = [".reggae/reggaebin", 225 "--objFile=" ~ outputs[0], 226 "--depFile=" ~ depfile, dCompiler, 227 flags, includes, stringImports, 228 dependencyFilesString(projectPath), 229 ].join(" "); 230 231 return command; 232 233 case "_cppcompile": return ccCommand(cppCompiler); 234 case "_ccompile": return ccCommand(cCompiler); 235 case "_dlink": 236 return [dCompiler, "-of" ~ outputs[0], 237 flags, 238 dependencyFilesString(projectPath)].join(" "); 239 default: 240 assert(0, "Unknown default rule " ~ rule); 241 } 242 } 243 }