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.defaultTargetsString(options.projectPath), "\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 47 const deps = 48 target.dependenciesInProjectPath(options.projectPath) 49 ~ target.implicitsInProjectPath(options.projectPath) 50 ; 51 ret ~= deps.join(" "); 52 53 ret ~= " " ~ fileName() ~ "\n"; 54 ret ~= "\t@echo [make] Building " ~ output ~ "\n"; 55 ret ~= "\t" ~ command(target) ~ "\n"; 56 } 57 58 return ret; 59 } 60 61 private static string replaceEnvVars(in string str) @safe { 62 import std.regex: regex, matchAll; 63 import std.algorithm: _sort = sort, uniq, map; 64 import std.array: array, replace; 65 import std.process: environment; 66 67 auto re = regex(`\$(\w+)`); 68 auto envVars = str.matchAll(re).map!(a => a.hit).array._sort.uniq; 69 string ret = str; 70 71 foreach(var; envVars) { 72 ret = ret.replace(var, environment.get(var[1..$], "")); 73 } 74 75 return ret; 76 } 77 78 //includes rerunning reggae 79 string output() @safe { 80 81 import std.array: join; 82 83 auto ret = simpleOutput; 84 85 if(options.export_) { 86 ret = options.eraseProjectPath(ret); 87 } else { 88 // add a dependency on the Makefile to reggae itself and the build description, 89 // but only if not exporting a build 90 ret ~= fileName() ~ ": " ~ options.reggaeFileDependencies.join(" ") ~ "\n"; 91 ret ~= "\t" ~ options.rerunArgs.join(" ") ~ "\n"; 92 } 93 94 return replaceEnvVars(ret); 95 } 96 97 void writeBuild() @safe { 98 import std.stdio: File; 99 import std.path: buildPath; 100 101 auto output = output(); 102 auto file = File(buildPath(options.workingDir, fileName), "w"); 103 file.write(output); 104 } 105 106 //the only reason this is needed is to add auto dependency 107 //tracking 108 string command(Target target) @safe const { 109 import reggae.build: CommandType, replaceConcreteCompilersWithVars; 110 111 immutable cmdType = target.getCommandType; 112 if(cmdType == CommandType.code) 113 throw new Exception("Command type 'code' not supported for make backend"); 114 115 immutable cmd = target.shellCommand(options).replaceConcreteCompilersWithVars(options); 116 immutable depfile = target.expandOutputs(options.projectPath)[0] ~ ".dep"; 117 if(target.hasDefaultCommand) { 118 return cmdType == CommandType.link ? cmd : cmd ~ makeAutoDeps(depfile); 119 } else { 120 return cmd; 121 } 122 } 123 124 private void mkDir(Target target) @trusted const { 125 import std.path: dirName; 126 import std.file: exists, mkdirRecurse; 127 128 foreach(output; target.expandOutputs(options.projectPath)) { 129 import std.file; 130 if(!output.dirName.exists) mkdirRecurse(output.dirName); 131 } 132 } 133 } 134 135 136 //For explanation of the crazy Makefile commands, see: 137 //http://stackoverflow.com/questions/8025766/makefile-auto-dependency-generation 138 //http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ 139 private string makeAutoDeps(in string depfile) @safe pure nothrow { 140 immutable pFile = depfile ~ ".P"; 141 return "\n\t@cp " ~ depfile ~ " " ~ pFile ~ "; \\\n" ~ 142 " sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \\\n" ~ 143 " -e '/^$$/ d' -e 's/$$/ :/' < " ~ depfile ~ " >> " ~ pFile ~"; \\\n" ~ 144 " rm -f " ~ depfile ~ "\n\n" ~ 145 "-include " ~ pFile ~ "\n\n"; 146 }