1 module reggae.backend.make; 2 3 4 struct Makefile { 5 6 import reggae.build: Build, Target; 7 import reggae.options: Options; 8 9 Build build; 10 const(Options) options; 11 string projectPath; 12 13 this(Build build, in Options options) @safe pure { 14 this.build = build; 15 this.options = options; 16 } 17 18 string fileName() @safe pure nothrow const { 19 return "Makefile"; 20 } 21 22 //only the main targets 23 string simpleOutput() @safe { 24 25 import reggae.options: banner; 26 import reggae.build: CommandType; 27 import std.conv: text; 28 import std.array: join; 29 30 auto ret = banner; 31 ret ~= text("all: ", build.defaultTargetsOutputs(options.projectPath).join(" "), "\n"); 32 ret ~= ".SUFFIXES:\n"; //disable default rules 33 ret ~= options.compilerVariables.join("\n") ~ "\n"; 34 35 ret ~= "$(VERBOSE).SILENT:\n"; // Do not display executed commands 36 37 foreach(target; build.range) { 38 39 mkDir(target); 40 41 immutable output = target.expandOutputs(options.projectPath).join(" "); 42 if(target.getCommandType == CommandType.phony) { 43 ret ~= ".PHONY: " ~ output ~ "\n"; 44 } 45 ret ~= output ~ ": "; 46 ret ~= (target.dependenciesInProjectPath(options.projectPath) ~ 47 target.implicitsInProjectPath(options.projectPath)).join(" "); 48 49 ret ~= " " ~ fileName() ~ "\n"; 50 ret ~= "\t@echo [make] Building " ~ output ~ "\n"; 51 ret ~= "\t" ~ command(target) ~ "\n"; 52 } 53 54 return ret; 55 } 56 57 private static string replaceEnvVars(in string str) @safe { 58 import std.regex: regex, matchAll; 59 import std.algorithm: _sort = sort, uniq, map; 60 import std.array: array, replace; 61 import std.process: environment; 62 63 auto re = regex(`\$(\w+)`); 64 auto envVars = str.matchAll(re).map!(a => a.hit).array._sort.uniq; 65 string ret = str; 66 67 foreach(var; envVars) { 68 ret = ret.replace(var, environment.get(var[1..$], "")); 69 } 70 71 return ret; 72 } 73 74 //includes rerunning reggae 75 string output() @safe { 76 77 import std.array: join; 78 79 auto ret = simpleOutput; 80 81 if(options.export_) { 82 ret = options.eraseProjectPath(ret); 83 } else { 84 // add a dependency on the Makefile to reggae itself and the build description, 85 // but only if not exporting a build 86 ret ~= fileName() ~ ": " ~ options.reggaeFileDependencies.join(" ") ~ "\n"; 87 ret ~= "\t" ~ options.rerunArgs.join(" ") ~ "\n"; 88 } 89 90 return replaceEnvVars(ret); 91 } 92 93 void writeBuild() @safe { 94 import std.stdio: File; 95 import reggae.path: buildPath; 96 97 auto output = output(); 98 auto file = File(buildPath(options.workingDir, fileName), "w"); 99 file.write(output); 100 } 101 102 //the only reason this is needed is to add auto dependency 103 //tracking 104 string command(Target target) @safe const { 105 import reggae.build: CommandType, replaceConcreteCompilersWithVars; 106 107 immutable cmdType = target.getCommandType; 108 if(cmdType == CommandType.code) 109 throw new Exception("Command type 'code' not supported for make backend"); 110 111 immutable cmd = target.shellCommand(options).replaceConcreteCompilersWithVars(options); 112 immutable depfile = target.expandOutputs(options.projectPath)[0] ~ ".dep"; 113 if(target.hasDefaultCommand) { 114 return cmdType == CommandType.link ? cmd : cmd ~ makeAutoDeps(depfile); 115 } else { 116 return cmd; 117 } 118 } 119 120 private void mkDir(Target target) @trusted const { 121 import std.path: dirName; 122 import std.file: exists, mkdirRecurse; 123 124 foreach(output; target.expandOutputs(options.projectPath)) { 125 import std.file; 126 if(!output.dirName.exists) mkdirRecurse(output.dirName); 127 } 128 } 129 } 130 131 132 //For explanation of the crazy Makefile commands, see: 133 //http://stackoverflow.com/questions/8025766/makefile-auto-dependency-generation 134 //http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ 135 private string makeAutoDeps(in string depfile) @safe pure nothrow { 136 immutable pFile = depfile ~ ".P"; 137 return "\n\t@cp " ~ depfile ~ " " ~ pFile ~ "; \\\n" ~ 138 " sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \\\n" ~ 139 " -e '/^$$/ d' -e 's/$$/ :/' < " ~ depfile ~ " >> " ~ pFile ~"; \\\n" ~ 140 " rm -f " ~ depfile ~ "\n\n" ~ 141 "-include " ~ pFile ~ "\n\n"; 142 }