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(const SourceFile(a),
49                                          const Flags(flags),
50                                          const ImportPaths(importPaths),
51                                          const StringImportPaths(stringImportPaths),
52                                          projDir)).array;
53 }
54 
55 
56 /**
57  Currently only works for D. This convenience rule builds a D executable, automatically
58  calculating which files must be compiled in a similar way to rdmd.
59  All paths are relative to projectPath.
60  This template function is provided as a wrapper around the regular runtime version
61  below so it can be aliased without trying to call it at runtime. Basically, it's a
62  way to use the runtime executable without having define a function in reggaefile.d,
63  i.e.:
64  $(D
65  alias myApp = executable!(...);
66  mixin build!(myApp);
67  )
68  vs.
69  $(D
70  Build myBuld() { return executable(..); }
71  )
72  */
73 Target executable(App app,
74                   Flags flags = Flags(),
75                   ImportPaths importPaths = ImportPaths(),
76                   StringImportPaths stringImportPaths = StringImportPaths(),
77                   alias linkWithFunction = () { return cast(Target[])[];})
78     () {
79     auto linkWith = linkWithFunction();
80     return executable(app, flags, importPaths, stringImportPaths, linkWith);
81 }
82 
83 
84 //regular runtime version of executable
85 //all paths relative to projectPath
86 //@trusted because of .array
87 Target executable(in App app, in Flags flags,
88                   in ImportPaths importPaths,
89                   in StringImportPaths stringImportPaths,
90                   in Target[] linkWith) @trusted {
91 
92     if(getLanguage(app.srcFileName) != Language.D)
93         throw new Exception("'executable' rule only works with D files");
94 
95     auto mainObj = objectFile(SourceFile(app.srcFileName), flags, importPaths, stringImportPaths);
96     const output = runDCompiler(buildPath(projectPath, app.srcFileName), flags.value,
97                                 importPaths.value, stringImportPaths.value);
98 
99     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
100     const dependencies = [mainObj] ~ objectFiles(files, flags.value,
101                                                  importPaths.value, stringImportPaths.value);
102 
103     return link(ExeName(app.exeFileName), dependencies ~ linkWith);
104 }
105 
106 
107 //@trusted because of splitter
108 private auto runDCompiler(in string srcFileName, in string flags,
109                           in string[] importPaths, in string[] stringImportPaths) @trusted {
110 
111     import std.process: execute;
112     import std.exception: enforce;
113     import std.conv:text;
114 
115     immutable compiler = "dmd";
116     const compArgs = [compiler] ~ flags.splitter.array ~
117         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
118         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
119         ["-o-", "-v", "-c", srcFileName];
120     const compRes = execute(compArgs);
121     enforce(compRes.status == 0, text("executable could not run ", compArgs.join(" "), ":\n", compRes.output));
122     return compRes.output;
123 }