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