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