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 }