1 /**
2 High-level rules for compiling D files. For a D-only application with
3 no dub dependencies, $(D scriptlike) 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 
18 
19 //generate object file(s) for a D package. By default generates one per package,
20 //if reggae.config.perModule is true, generates one per module
21 Target[] dlangPackageObjectFiles(in string[] srcFiles, in string flags = "",
22                      in string[] importPaths = [], in string[] stringImportPaths = [],
23                      in string projDir = "$project") @safe pure {
24     import reggae.config;
25     auto func = options.perModule ? &dlangPackageObjectFilesPerModule : &dlangPackageObjectFilesPerPackage;
26     return func(srcFiles, flags, importPaths, stringImportPaths, projDir);
27 }
28 
29 Target[] dlangPackageObjectFilesPerPackage(in string[] srcFiles, in string flags = "",
30                                in string[] importPaths = [], in string[] stringImportPaths = [],
31                                in string projDir = "$project") @trusted pure {
32 
33     if(srcFiles.empty) return [];
34     const command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir);
35     return srcFiles.byPackage.map!(a => Target(a[0].packagePath.objFileName,
36                                                command,
37                                                a.map!(a => Target(a)).array)).array;
38 }
39 
40 Target[] dlangPackageObjectFilesPerModule(in string[] srcFiles, in string flags = "",
41                               in string[] importPaths = [], in string[] stringImportPaths = [],
42                               in string projDir = "$project") @trusted pure {
43     return srcFiles.map!(a => objectFile(const SourceFile(a),
44                                          const Flags(flags),
45                                          const ImportPaths(importPaths),
46                                          const StringImportPaths(stringImportPaths),
47                                          projDir)).array;
48 }
49 
50 
51 /**
52  Currently only works for D. This convenience rule builds a D scriptlike, automatically
53  calculating which files must be compiled in a similar way to rdmd.
54  All paths are relative to projectPath.
55  This template function is provided as a wrapper around the regular runtime version
56  below so it can be aliased without trying to call it at runtime. Basically, it's a
57  way to use the runtime scriptlike without having define a function in reggaefile.d,
58  i.e.:
59  $(D
60  alias myApp = scriptlike!(...);
61  mixin build!(myApp);
62  )
63  vs.
64  $(D
65  Build myBuld() { return scriptlike(..); }
66  )
67  */
68 Target scriptlike(App app,
69                   Flags flags = Flags(),
70                   ImportPaths importPaths = ImportPaths(),
71                   StringImportPaths stringImportPaths = StringImportPaths(),
72                   alias linkWithFunction = () { return cast(Target[])[];})
73     () @trusted {
74     auto linkWith = linkWithFunction();
75     import reggae.config;
76     return scriptlike(options.projectPath, app, flags, importPaths, stringImportPaths, linkWith);
77 }
78 
79 
80 //regular runtime version of scriptlike
81 //all paths relative to projectPath
82 //@trusted because of .array
83 Target scriptlike(in string projectPath,
84                   in App app, in Flags flags,
85                   in ImportPaths importPaths,
86                   in StringImportPaths stringImportPaths,
87                   in Target[] linkWith) @trusted {
88 
89     if(getLanguage(app.srcFileName.value) != Language.D)
90         throw new Exception("'scriptlike' rule only works with D files");
91 
92     auto mainObj = objectFile(SourceFile(app.srcFileName.value), flags, importPaths, stringImportPaths);
93     const output = runDCompiler(projectPath, buildPath(projectPath, app.srcFileName.value), flags.value,
94                                 importPaths.value, stringImportPaths.value);
95 
96     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
97     const dependencies = [mainObj] ~ dlangPackageObjectFiles(files, flags.value,
98                                                              importPaths.value, stringImportPaths.value);
99 
100     return link(ExeName(app.exeFileName.value), dependencies ~ linkWith);
101 }
102 
103 
104 //@trusted because of splitter
105 private auto runDCompiler(in string projectPath,
106                           in string srcFileName,
107                           in string flags,
108                           in string[] importPaths,
109                           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("scriptlike could not run ", compArgs.join(" "), ":\n", compRes.output));
122     return compRes.output;
123 }