1 module reggae.makefile;
2 
3 import reggae.build;
4 import reggae.range;
5 import reggae.rules;
6 import std.conv;
7 import std.array;
8 import std.path;
9 import std.algorithm;
10 
11 
12 struct Makefile {
13     Build build;
14     string projectPath;
15 
16     this(Build build) @safe pure {
17         this.build = build;
18         this.projectPath = "";
19     }
20 
21     this(Build build, string projectPath) @safe pure {
22         this.build = build;
23         this.projectPath = projectPath.absolutePath;
24     }
25 
26     string fileName() @safe pure nothrow const {
27         return "Makefile";
28     }
29 
30     string simpleOutput() @safe const {
31 
32         const outputs = build.targets.map!(a => a.outputs[0]).join(" ");
33         auto ret = text("all: ", outputs, "\n");
34 
35         foreach(topTarget; build.targets) {
36             foreach(t; DepthFirst(topTarget)) {
37 
38                 mkDir(t);
39 
40                 ret ~= text(t.outputs.join(" "), ": ");
41                 ret ~= t.dependencyFiles(projectPath);
42                 immutable implicitFiles = t.implicitFiles(projectPath);
43                 if(!implicitFiles.empty) ret ~= " " ~ t.implicitFiles(projectPath);
44                 ret ~= " Makefile\n";
45                 ret ~= "\t";
46                 ret ~= command(t);
47                 ret ~= "\n";
48             }
49         }
50 
51         return ret;
52     }
53 
54     string output() @safe const {
55         import reggae.config;
56         auto ret = simpleOutput;
57         ret ~= "Makefile: " ~ buildFilePath ~ " " ~ reggaePath ~ "\n";
58         immutable _dflags = dflags == "" ? "" : " --dflags='" ~ dflags ~ "'";
59         ret ~= "\t" ~ reggaePath ~ " -b make" ~ _dflags ~ " " ~ projectPath ~ "\n";
60         return ret;
61     }
62 
63     private void mkDir(in Target target) @trusted const {
64         foreach(output; target.outputs) {
65             import std.file;
66             if(!output.dirName.exists) mkdirRecurse(output.dirName);
67         }
68     }
69 
70     string command(in Target target) @safe const {
71         immutable rawCmdLine = target.inOutCommand(projectPath);
72         if(rawCmdLine.isDefaultCommand) {
73             return command(target, rawCmdLine);
74         } else {
75             return target.command(projectPath);
76         }
77     }
78 
79     string command(in Target target, in string rawCmdLine) @safe const {
80         import reggae.config;
81 
82         immutable rule = rawCmdLine.getDefaultRule;
83         immutable flags = rawCmdLine.getDefaultRuleParams("flags", []).join(" ");
84         immutable includes = rawCmdLine.getDefaultRuleParams("includes", []).join(" ");
85         immutable depfile = target.outputs[0] ~ ".d";
86 
87         string ccCommand(in string compiler) {
88             immutable command = [compiler, flags, includes, "-MMD", "-MT", target.outputs[0],
89                                  "-MF", depfile, "-o", target.outputs[0], "-c",
90                                  target.dependencyFiles(projectPath)].join(" ");
91             return command ~ makeAutoDeps(depfile);
92         }
93 
94         if(rule == "_dcompile") {
95             immutable stringImports = rawCmdLine.getDefaultRuleParams("stringImports", []).join(" ");
96             immutable command = [".reggae/dcompile",
97                                  "--objFile=" ~ target.outputs[0],
98                                  "--depFile=" ~ depfile, dCompiler,
99                                  flags, includes, stringImports,
100                                  target.dependencyFiles(projectPath),
101                 ].join(" ");
102 
103             return command ~ makeAutoDeps(depfile);
104 
105         } else if(rule == "_cppcompile") {
106             return ccCommand(cppCompiler);
107         } else if(rule == "_ccompile") {
108             return ccCommand(cCompiler);
109         } else if(rule == "_dlink") {
110             return [dCompiler, "-of" ~ target.outputs[0], target.dependencyFiles(projectPath)].join(" ");
111         } else {
112             throw new Exception("Unknown Makefile default rule " ~ rule);
113         }
114     }
115 
116 private:
117 
118     void addRerunBuild(ref string ret) @safe pure nothrow const {
119         import reggae.config;
120         ret ~= "Makefile: " ~ buildFilePath ~ " " ~ reggaePath ~ "\n";
121         immutable _dflags = dflags == "" ? "" : " --dflags='" ~ dflags ~ "'";
122         ret ~= "\t" ~ reggaePath ~ " -b make" ~ _dflags ~ " " ~ projectPath ~ "\n";
123     }
124 }
125 
126 
127 //For explanation of the crazy Makefile commands, see:
128 //http://stackoverflow.com/questions/8025766/makefile-auto-dependency-generation
129 //http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
130 private string makeAutoDeps(in string depfile) @safe pure nothrow {
131     immutable pFile = depfile ~ ".P";
132     return "\n\t@cp " ~ depfile ~ " " ~ pFile ~ "; \\\n" ~
133         "    sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \\\n" ~
134         "        -e '/^$$/ d' -e 's/$$/ :/' < " ~ depfile ~ " >> " ~ pFile ~"; \\\n" ~
135         "    rm -f " ~ depfile ~ "\n\n" ~
136         "-include " ~ pFile ~ "\n\n";
137 }