1 /**
2 High-level rules for compiling D files. For a D-only application with
3 no dub dependencies, $(D executable) should suffice. If the app depends
4 on dub packages, consult the reggae.rules.dub module instead.
5  */
6 
7 module reggae.rules.d;
8 
9 import reggae.types;
10 import reggae.build;
11 import reggae.sorting;
12 import reggae.dependencies: dMainDepSrcs;
13 import reggae.rules.common;
14 import std.algorithm;
15 import std.array;
16 
17 //objectFile, objectFiles and link are the only default rules
18 //They work by serialising the rule to use piggy-backing on Target's string
19 //command attribute. It's horrible, but it works with the original decision
20 //of using strings as commands. Should be changed to be a sum type where
21 //a string represents a shell command and other variants call D code.
22 
23 //generate object file(s) for a D package. By default generates one per package,
24 //if reggae.config.perModule is true, generates one per module
25 Target[] objectFiles(in string[] srcFiles, in string flags = "",
26                      in string[] importPaths = [], in string[] stringImportPaths = [],
27                      in string projDir = "$project") @safe pure {
28     import reggae.config;
29     auto func = perModule ? &objectFilesPerModule : &objectFilesPerPackage;
30     return func(srcFiles, flags, importPaths, stringImportPaths, projDir);
31 }
32 
33 Target[] objectFilesPerPackage(in string[] srcFiles, in string flags = "",
34                                in string[] importPaths = [], in string[] stringImportPaths = [],
35                                in string projDir = "$project") @trusted pure {
36 
37     if(srcFiles.empty) return [];
38     const command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir);
39     return srcFiles.byPackage.map!(a => Target(a[0].packagePath.objFileName,
40                                                command,
41                                                a.map!(a => Target(a)).array)).array;
42 }
43 
44 Target[] objectFilesPerModule(in string[] srcFiles, in string flags = "",
45                               in string[] importPaths = [], in string[] stringImportPaths = [],
46                               in string projDir = "$project") @trusted pure {
47 
48     return srcFiles.map!(a => objectFile(a, flags, importPaths, stringImportPaths, projDir)).array;
49 }
50 
51 
52 /**
53  Currently only works for D. This convenience rule builds a D executable, automatically
54  calculating which files must be compiled in a similar way to rdmd.
55  All paths are relative to projectPath.
56  This template function is provided as a wrapper around the regular runtime version
57  below so it can be aliased without trying to call it at runtime. Basically, it's a
58  way to use the runtime executable without having define a function in reggaefile.d,
59  i.e.:
60  $(D
61  alias myApp = executable!(...);
62  mixin build!(myApp);
63  )
64  vs.
65  $(D
66  Build myBuld() { return executable(..); }
67  )
68  */
69 Target executable(App app,
70             Flags flags = Flags(),
71             ImportPaths importPaths = ImportPaths(),
72             StringImportPaths stringImportPaths = StringImportPaths(),
73             alias linkWithFunction = () { return cast(Target[])[];})
74     () {
75     auto linkWith = linkWithFunction();
76     return executable(app, flags, importPaths, stringImportPaths, linkWith);
77 }
78 
79 
80 //regular runtime version of executable
81 //all paths relative to projectPath
82 //@trusted because of .array
83 Target executable(in App app, in Flags flags,
84             in ImportPaths importPaths,
85             in StringImportPaths stringImportPaths,
86             in Target[] linkWith) @trusted {
87 
88     auto mainObj = objectFile(app.srcFileName, flags.value, importPaths.value, stringImportPaths.value);
89     const output = runDCompiler(buildPath(projectPath, app.srcFileName), flags.value,
90                                 importPaths.value, stringImportPaths.value);
91 
92     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
93     const dependencies = [mainObj] ~ objectFiles(files, flags.value,
94                                                  importPaths.value, stringImportPaths.value);
95 
96     return link(app.exeFileName, dependencies ~ linkWith);
97 }
98 
99 
100 //@trusted because of splitter
101 private auto runDCompiler(in string srcFileName, in string flags,
102                           in string[] importPaths, in string[] stringImportPaths) @trusted {
103 
104     import std.process: execute;
105     import std.exception: enforce;
106     import std.conv:text;
107 
108     immutable compiler = "dmd";
109     const compArgs = [compiler] ~ flags.splitter.array ~
110         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
111         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
112         ["-o-", "-v", "-c", srcFileName];
113     const compRes = execute(compArgs);
114     enforce(compRes.status == 0, text("executable could not run ", compArgs.join(" "), ":\n", compRes.output));
115     return compRes.output;
116 }