1 
2 /**
3 High-level rules for compiling D files. For a D-only application with
4 no dub dependencies, $(D scriptlike) should suffice. If the app depends
5 on dub packages, consult the reggae.rules.dub module instead.
6  */
7 
8 module reggae.rules.d;
9 
10 import reggae.types;
11 import reggae.build;
12 import reggae.sorting;
13 import reggae.dependencies: dMainDepSrcs;
14 import reggae.rules.common;
15 import std.algorithm;
16 import std.array;
17 
18 
19 
20 //generate object file(s) for a D package. By default generates one per package,
21 //if reggae.config.perModule is true, generates one per module
22 Target[] dlangPackageObjectFiles(in string[] srcFiles, in string flags = "",
23                                  in string[] importPaths = [], in string[] stringImportPaths = [],
24                                  in string projDir = "$project") @safe {
25 
26     import reggae.config: options;
27 
28     auto func = options.perModule
29         ? &dlangPackageObjectFilesPerModule
30         : options.allAtOnce
31             ? &dlangPackageObjectFilesTogether
32             : &dlangPackageObjectFilesPerPackage;
33 
34     return func(srcFiles, flags, importPaths, stringImportPaths, projDir);
35 }
36 
37 Target[] dlangPackageObjectFilesPerPackage(in string[] srcFiles,
38                                            in string flags = "",
39                                            in string[] importPaths = [],
40                                            in string[] stringImportPaths = [],
41                                            in string projDir = "$project")
42     @trusted pure {
43 
44     if(srcFiles.empty) return [];
45     auto command(in string[] files) {
46         return compileCommand(files[0].packagePath ~ ".d",
47                               flags,
48                               importPaths,
49                               stringImportPaths,
50                               projDir);
51     }
52     return srcFiles.byPackage.map!(a => Target(a[0].packagePath.objFileName,
53                                                command(a),
54                                                a.map!(a => Target(a)).array)).array;
55 }
56 
57 Target[] dlangPackageObjectFilesPerModule(in string[] srcFiles, in string flags = "",
58                                           in string[] importPaths = [], in string[] stringImportPaths = [],
59                                           in string projDir = "$project") @trusted pure {
60     return srcFiles.map!(a => objectFile(const SourceFile(a),
61                                          const Flags(flags),
62                                          const ImportPaths(importPaths),
63                                          const StringImportPaths(stringImportPaths),
64                                          projDir)).array;
65 }
66 
67 // compiles all source files in one go
68 Target[] dlangPackageObjectFilesTogether(in string[] srcFiles, in string flags = "",
69                                          in string[] importPaths = [], in string[] stringImportPaths = [],
70                                          in string projDir = "$project") @trusted pure {
71 
72     if(srcFiles.empty) return [];
73     auto command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir);
74     return [Target(srcFiles[0].packagePath.objFileName, command, srcFiles.map!(a => Target(a)).array)];
75 }
76 
77 
78 /**
79  Currently only works for D. This convenience rule builds a D scriptlike, automatically
80  calculating which files must be compiled in a similar way to rdmd.
81  All paths are relative to projectPath.
82  This template function is provided as a wrapper around the regular runtime version
83  below so it can be aliased without trying to call it at runtime. Basically, it's a
84  way to use the runtime scriptlike without having define a function in reggaefile.d,
85  i.e.:
86  $(D
87  alias myApp = scriptlike!(...);
88  mixin build!(myApp);
89  )
90  vs.
91  $(D
92  Build myBuld() { return scriptlike(..); }
93  )
94  */
95 Target scriptlike(App app,
96                   Flags flags = Flags(),
97                   ImportPaths importPaths = ImportPaths(),
98                   StringImportPaths stringImportPaths = StringImportPaths(),
99                   alias linkWithFunction = () { return cast(Target[])[];})
100     () @trusted {
101     auto linkWith = linkWithFunction();
102     import reggae.config: options;
103     return scriptlike(options.projectPath, app, flags, importPaths, stringImportPaths, linkWith);
104 }
105 
106 
107 //regular runtime version of scriptlike
108 //all paths relative to projectPath
109 //@trusted because of .array
110 Target scriptlike(in string projectPath,
111                   in App app, in Flags flags,
112                   in ImportPaths importPaths,
113                   in StringImportPaths stringImportPaths,
114                   Target[] linkWith) @trusted {
115 
116     import std.path;
117 
118     if(getLanguage(app.srcFileName.value) != Language.D)
119         throw new Exception("'scriptlike' rule only works with D files");
120 
121     auto mainObj = objectFile(SourceFile(app.srcFileName.value), flags, importPaths, stringImportPaths);
122     const output = runDCompiler(projectPath, buildPath(projectPath, app.srcFileName.value), flags.value,
123                                 importPaths.value, stringImportPaths.value);
124 
125     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
126     auto dependencies = [mainObj] ~ dlangPackageObjectFiles(files, flags.value,
127                                                              importPaths.value, stringImportPaths.value);
128 
129     return link(ExeName(app.exeFileName.value), dependencies ~ linkWith);
130 }
131 
132 
133 //@trusted because of splitter
134 private auto runDCompiler(in string projectPath,
135                           in string srcFileName,
136                           in string flags,
137                           in string[] importPaths,
138                           in string[] stringImportPaths) @trusted {
139 
140     import std.process: execute;
141     import std.exception: enforce;
142     import std.conv:text;
143 
144     immutable compiler = "dmd";
145     const compArgs = [compiler] ~ flags.splitter.array ~
146         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
147         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
148         ["-o-", "-v", "-c", srcFileName];
149     const compRes = execute(compArgs);
150     enforce(compRes.status == 0, text("scriptlike could not run ", compArgs.join(" "), ":\n", compRes.output));
151     return compRes.output;
152 }