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.rules.defaults; 10 import std.string: replace; 11 import std.algorithm: map; 12 import std.path: buildPath; 13 import std.typetuple: allSatisfy; 14 import std.traits: Unqual, isSomeFunction, ReturnType, arity; 15 import std.array: array, join; 16 17 18 Target createTargetFromTarget(in Target target) { 19 return Target(target.outputs, 20 target._command.removeBuilddir, 21 target.dependencies.map!(a => a.enclose(target)).array, 22 target.implicits.map!(a => a.enclose(target)).array); 23 } 24 25 /** 26 Contains the top-level targets. 27 */ 28 struct Build { 29 const(Target)[] targets; 30 31 this(in Target[] targets) { 32 this.targets = targets.map!createTargetFromTarget.array; 33 } 34 35 this(T...)(in T targets) { 36 foreach(t; targets) { 37 static if(isSomeFunction!(typeof(t))) { 38 const target = t(); 39 } else { 40 const target = t; 41 } 42 43 this.targets ~= createTargetFromTarget(target); 44 } 45 } 46 } 47 48 //a directory for each top-level target no avoid name clashes 49 //@trusted because of map -> buildPath -> array 50 Target enclose(in Target target, in Target topLevel) @trusted { 51 if(target.isLeaf) return Target(target.outputs.map!(a => a.removeBuilddir).array, 52 target._command.removeBuilddir, 53 target.dependencies, 54 target.implicits); 55 56 immutable dirName = buildPath("objs", topLevel.outputs[0] ~ ".objs"); 57 return Target(target.outputs.map!(a => realTargetPath(dirName, a)).array, 58 target._command.removeBuilddir, 59 target.dependencies.map!(a => a.enclose(topLevel)).array, 60 target.implicits.map!(a => a.enclose(topLevel)).array); 61 } 62 63 immutable gBuilddir = "$builddir"; 64 65 66 private string realTargetPath(in string dirName, in string output) @trusted pure { 67 import std.algorithm: canFind; 68 69 return output.canFind(gBuilddir) 70 ? output.removeBuilddir 71 : buildPath(dirName, output); 72 } 73 74 private string removeBuilddir(in string output) @trusted pure { 75 import std.path: buildNormalizedPath; 76 import std.algorithm; 77 return output. 78 splitter. 79 map!(a => a.canFind(gBuilddir) ? a.replace(gBuilddir, ".").buildNormalizedPath : a). 80 join(" "); 81 } 82 83 enum isTarget(alias T) = is(Unqual!(typeof(T)) == Target) || 84 isSomeFunction!T && is(ReturnType!T == Target); 85 86 unittest { 87 auto t1 = Target(); 88 const t2 = Target(); 89 static assert(isTarget!t1); 90 static assert(isTarget!t2); 91 } 92 93 mixin template buildImpl(targets...) if(allSatisfy!(isTarget, targets)) { 94 Build buildFunc() { 95 return Build(targets); 96 } 97 } 98 99 /** 100 Two variations on a template mixin. When reggae is used as a library, 101 this will essentially build reggae itself as part of the build description. 102 103 When reggae is used as a command-line tool to generate builds, it simply 104 declares the build function that will be called at run-time. The tool 105 will then compile the user's reggaefile.d with the reggae libraries, 106 resulting in a buildgen executable. 107 108 In either case, the compile-time parameters of $(D build) are the 109 build's top-level targets. 110 */ 111 version(reggaelib) { 112 mixin template build(targets...) if(allSatisfy!(isTarget, targets)) { 113 mixin reggaeGen!(targets); 114 } 115 } else { 116 alias build = buildImpl; 117 } 118 119 package template isBuildFunction(alias T) { 120 static if(!isSomeFunction!T) { 121 enum isBuildFunction = false; 122 } else { 123 enum isBuildFunction = is(ReturnType!T == Build) && arity!T == 0; 124 } 125 } 126 127 unittest { 128 Build myBuildFunction() { return Build(); } 129 static assert(isBuildFunction!myBuildFunction); 130 float foo; 131 static assert(!isBuildFunction!foo); 132 } 133 134 135 /** 136 The core of reggae's D-based DSL for describing build systems. 137 Targets contain outputs, a command to generate those outputs, 138 explicit dependencies and implicit dependencies. All dependencies 139 are themselves $(D Target) structs. 140 141 The command is given as a string. In this string, certain words 142 have special meaning: $(D $in), $(D $out), $(D $project) and $(D builddir). 143 144 $(D $in) gets expanded to all explicit dependencies. 145 $(D $out) gets expanded to all outputs. 146 $(D $project) gets expanded to the project directory (i.e. the directory including 147 the source files to build that was given as a command-line argument). This can be 148 useful when build outputs are to be placed in the source directory, such as 149 automatically generated source files. 150 $(D $builddir) expands to the build directory (i.e. where reggae was run from). 151 */ 152 struct Target { 153 const(string)[] outputs; 154 const(Target)[] dependencies; 155 const(Target)[] implicits; 156 157 this(in string output) @safe pure nothrow { 158 this(output, null, null); 159 } 160 161 this(in string output, string command, in Target dependency, 162 in Target[] implicits = []) @safe pure nothrow { 163 this([output], command, [dependency], implicits); 164 } 165 166 this(in string output, string command, 167 in Target[] dependencies, in Target[] implicits = []) @safe pure nothrow { 168 this([output], command, dependencies, implicits); 169 } 170 171 this(in string[] outputs, string command, 172 in Target[] dependencies, in Target[] implicits = []) @safe pure nothrow { 173 this.outputs = outputs; 174 this.dependencies = dependencies; 175 this.implicits = implicits; 176 this._command = command; 177 } 178 179 @property string dependencyFilesString(in string projectPath = "") @safe pure const nothrow { 180 return depFilesStringImpl(dependencies, projectPath); 181 } 182 183 @property string implicitFilesString(in string projectPath = "") @safe pure const nothrow { 184 return depFilesStringImpl(implicits, projectPath); 185 } 186 187 @property string command(in string projectPath = "") @trusted pure const nothrow { 188 //functional didn't work here, I don't know why so sticking with loops for now 189 string[] depOutputs; 190 foreach(dep; dependencies) { 191 foreach(output; dep.outputs) { 192 //leaf objects are references to source files in the project path 193 //those need their path built. Any other dependencies are in the 194 //build path, so they don't need the same treatment 195 depOutputs ~= dep.isLeaf ? buildPath(projectPath, output) : output; 196 } 197 } 198 auto replaceIn = _command.replace("$in", depOutputs.join(" ")); 199 auto replaceOut = replaceIn.replace("$out", outputs.join(" ")); 200 return replaceOut.replace("$project", projectPath); 201 } 202 203 bool isLeaf() @safe pure const nothrow { 204 return dependencies is null && implicits is null; 205 } 206 207 //@trusted because of replace 208 string rawCmdString(in string projectPath) @trusted pure nothrow const { 209 return _command.replace("$project", projectPath); 210 } 211 212 213 string shellCommand(in string projectPath = "") @safe pure const { 214 immutable rawCmdLine = rawCmdString(projectPath); 215 if(rawCmdLine.isDefaultCommand) { 216 return defaultCommand(projectPath, rawCmdLine); 217 } else { 218 return command(projectPath); 219 } 220 } 221 222 string[] outputsInProjectPath(in string projectPath) @safe pure nothrow const { 223 return outputs.map!(a => isLeaf ? buildPath(projectPath, a) : a).array; 224 } 225 226 private: 227 228 string _command; 229 230 //@trusted because of join 231 string depFilesStringImpl(in Target[] deps, in string projectPath) @trusted pure const nothrow { 232 import std.conv; 233 string files; 234 //join doesn't do const, resort to loops 235 foreach(i, dep; deps) { 236 files ~= text(dep.outputsInProjectPath(projectPath).join(" ")); 237 if(i != deps.length - 1) files ~= " "; 238 } 239 return files; 240 } 241 242 //this function returns a string to be run by the shell with `std.process.execute` 243 //it does 'normal' commands, not built-in rules 244 string defaultCommand(in string projectPath, in string rawCmdLine) @safe pure const { 245 import reggae.config: dCompiler, cppCompiler, cCompiler; 246 247 immutable flags = rawCmdLine.getDefaultRuleParams("flags", []).join(" "); 248 immutable includes = rawCmdLine.getDefaultRuleParams("includes", []).join(" "); 249 immutable depfile = outputs[0] ~ ".dep"; 250 251 string ccCommand(in string compiler) { 252 import std.stdio; 253 debug writeln("ccCommand with compiler ", compiler); 254 return [compiler, flags, includes, "-MMD", "-MT", outputs[0], 255 "-MF", depfile, "-o", outputs[0], "-c", 256 dependencyFilesString(projectPath)].join(" "); 257 } 258 259 260 immutable rule = rawCmdLine.getDefaultRule; 261 import std.stdio; 262 debug writeln("rule: ", rule); 263 264 switch(rule) { 265 266 case "_dcompile": 267 immutable stringImports = rawCmdLine.getDefaultRuleParams("stringImports", []).join(" "); 268 immutable command = [".reggae/dcompile", 269 "--objFile=" ~ outputs[0], 270 "--depFile=" ~ depfile, dCompiler, 271 flags, includes, stringImports, 272 dependencyFilesString(projectPath), 273 ].join(" "); 274 275 return command; 276 277 case "_cppcompile": return ccCommand(cppCompiler); 278 case "_ccompile": return ccCommand(cCompiler); 279 case "_link": 280 return [dCompiler, "-of" ~ outputs[0], 281 flags, 282 dependencyFilesString(projectPath)].join(" "); 283 default: 284 assert(0, "Unknown default rule " ~ rule); 285 } 286 } 287 }