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) @safe pure { 20 this(build, Options()); 21 } 22 23 this(Build build, in string projectPath) @safe pure { 24 import reggae.config: options; 25 auto modOptions = options.dup; 26 modOptions.projectPath = projectPath; 27 this(build, modOptions); 28 } 29 30 this(Build build, in Options options) @safe pure { 31 this.build = build; 32 this.options = options; 33 } 34 35 string fileName() @safe pure nothrow const { 36 return "Makefile"; 37 } 38 39 //only the main targets 40 string simpleOutput() @safe const { 41 42 auto ret = text("all: ", build.defaultTargetsString(options.projectPath), "\n"); 43 44 foreach(t; build.range) { 45 46 mkDir(t); 47 48 immutable output = t.outputsInProjectPath(options.projectPath).join(" "); 49 if(t.getCommandType == CommandType.phony) { 50 ret ~= ".PHONY: " ~ output ~ "\n"; 51 } 52 ret ~= output ~ ": "; 53 ret ~= t.dependencyFilesString(options.projectPath); 54 immutable implicitFiles = t.implicitFilesString(options.projectPath); 55 if(!implicitFiles.empty) ret ~= " " ~ t.implicitFilesString(options.projectPath); 56 ret ~= " Makefile\n"; 57 58 ret ~= "\t" ~ command(t) ~ "\n"; 59 } 60 61 return ret; 62 } 63 64 //includes rerunning reggae 65 string output() @safe const { 66 auto ret = simpleOutput; 67 ret ~= "Makefile: " ~ options.reggaeFilePath ~ " " ~ options.ranFromPath ~ "\n"; 68 ret ~= "\t" ~ options.rerunArgs.join(" ") ~ "\n"; 69 70 return ret; 71 } 72 73 private void mkDir(in Target target) @trusted const { 74 foreach(output; target.outputsInProjectPath(options.projectPath)) { 75 import std.file; 76 if(!output.dirName.exists) mkdirRecurse(output.dirName); 77 } 78 } 79 80 //the only reason this is needed is to add auto dependency 81 //tracking 82 string command(in Target target) @safe const { 83 immutable cmdType = target.getCommandType; 84 if(cmdType == CommandType.code) 85 throw new Exception("Command type 'code' not supported for make backend"); 86 87 immutable cmd = target.shellCommand(options.projectPath); 88 immutable depfile = target.outputsInProjectPath(options.projectPath)[0] ~ ".dep"; 89 if(target.hasDefaultCommand) { 90 return cmdType == CommandType.link ? cmd : cmd ~ makeAutoDeps(depfile); 91 } else { 92 return cmd; 93 } 94 } 95 } 96 97 98 //For explanation of the crazy Makefile commands, see: 99 //http://stackoverflow.com/questions/8025766/makefile-auto-dependency-generation 100 //http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ 101 private string makeAutoDeps(in string depfile) @safe pure nothrow { 102 immutable pFile = depfile ~ ".P"; 103 return "\n\t@cp " ~ depfile ~ " " ~ pFile ~ "; \\\n" ~ 104 " sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \\\n" ~ 105 " -e '/^$$/ d' -e 's/$$/ :/' < " ~ depfile ~ " >> " ~ pFile ~"; \\\n" ~ 106 " rm -f " ~ depfile ~ "\n\n" ~ 107 "-include " ~ pFile ~ "\n\n"; 108 }