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