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 }