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