1 module reggae.dub.info;
2 
3 import reggae.build;
4 import reggae.rules;
5 import reggae.types;
6 import reggae.sorting;
7 
8 public import std.typecons: Yes, No;
9 import std.typecons: Flag;
10 import std.algorithm: map, filter, find, splitter;
11 import std.array: array, join;
12 import std.path: buildPath;
13 import std.traits: isCallable;
14 import std.range: chain;
15 
16 enum TargetType {
17     autodetect,
18     none,
19     executable,
20     library,
21     sourceLibrary,
22     dynamicLibrary,
23     staticLibrary,
24     object,
25 }
26 
27 
28 struct DubPackage {
29     string name;
30     string path;
31     string mainSourceFile;
32     string targetFileName;
33     string[] dflags;
34     string[] lflags;
35     string[] importPaths;
36     string[] stringImportPaths;
37     string[] files;
38     TargetType targetType;
39     string[] versions;
40     string[] dependencies;
41     string[] libs;
42     bool active;
43     string[] preBuildCommands;
44     string[] postBuildCommands;
45 
46     string toString() @safe pure const {
47         import std.string: join;
48         import std.conv: to;
49         import std.traits: Unqual;
50 
51         auto ret = `DubPackage(`;
52         string[] lines;
53 
54         foreach(ref elt; this.tupleof) {
55             static if(is(Unqual!(typeof(elt)) == TargetType))
56                 lines ~= `TargetType.` ~ elt.to!string;
57             else static if(is(Unqual!(typeof(elt)) == string))
58                 lines ~= `"` ~ elt.to!string ~ `"`;
59             else
60                 lines ~= elt.to!string;
61         }
62         ret ~= lines.join(`, `);
63         ret ~= `)`;
64         return ret;
65     }
66 
67     DubPackage dup() @safe pure nothrow const {
68         DubPackage ret;
69         foreach(i, member; this.tupleof) {
70             static if(__traits(compiles, member.dup))
71                 ret.tupleof[i] = member.dup;
72             else
73                 ret.tupleof[i] = member;
74         }
75         return ret;
76     }
77 }
78 
79 bool isStaticLibrary(in string fileName) @safe pure nothrow {
80     import std.path: extension;
81     return fileName.extension == ".a";
82 }
83 
84 bool isObjectFile(in string fileName) @safe pure nothrow {
85     import reggae.rules.common: objExt;
86     import std.path: extension;
87     return fileName.extension == objExt;
88 }
89 
90 string inDubPackagePath(in string packagePath, in string filePath) @safe pure nothrow {
91     import std.path: buildPath;
92     import std.algorithm: startsWith;
93     return filePath.startsWith("$project")
94         ? filePath
95         : buildPath(packagePath, filePath);
96 }
97 
98 struct DubObjsDir {
99     string globalDir;
100     string targetDir;
101 }
102 
103 struct DubInfo {
104 
105     DubPackage[] packages;
106 
107     DubInfo dup() @safe pure nothrow const {
108         import std.algorithm: map;
109         import std.array: array;
110         return DubInfo(packages.map!(a => a.dup).array);
111     }
112 
113     Target[] toTargets(in Flag!"main" includeMain = Yes.main,
114                        in string compilerFlags = "",
115                        in Flag!"allTogether" allTogether = No.allTogether,
116                        in DubObjsDir dubObjsDir = DubObjsDir())
117         @safe const
118     {
119 
120         import reggae.config: options;
121         import reggae.build: targetObjsDir;
122         import std.functional: not;
123         import std.path: buildPath, absolutePath, baseName, dirName;
124 
125         Target[] targets;
126 
127         // -unittest should only apply to the main package
128         string deUnitTest(T)(in T index, in string flags) {
129             import std.string: replace;
130             return index == 0
131                 ? flags
132                 : flags.replace("-unittest", "").replace("-main", "");
133         }
134 
135         foreach(const i, const dubPackage; packages) {
136             const importPaths = allImportPaths();
137             const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages);
138             const isMainPackage = i == 0;
139             //the path must be explicit for the other packages, implicit for the "main"
140             //package
141             const projDir = isMainPackage ? "" : dubPackage.path;
142 
143             const sharedFlag = targetType == TargetType.dynamicLibrary ? ["-fPIC"] : [];
144             immutable flags = chain(dubPackage.dflags,
145                                     dubPackage.versions.map!(a => "-version=" ~ a).array,
146                                     [options.dflags],
147                                     sharedFlag,
148                                     [deUnitTest(i, compilerFlags)])
149                 .join(" ");
150 
151             const files = dubPackage.files.
152                 filter!(a => includeMain || a != dubPackage.mainSourceFile).
153                 filter!(not!isStaticLibrary).
154                 filter!(not!isObjectFile).
155                 map!(a => buildPath(dubPackage.path, a))
156                 .array;
157 
158             auto func = allTogether ? &dlangPackageObjectFilesTogether : &dlangPackageObjectFiles;
159             auto packageTargets = func(files, flags, importPaths, stringImportPaths, projDir);
160 
161             // add any object files that are meant to be linked
162             packageTargets ~= dubPackage
163                 .files
164                 .filter!isObjectFile
165                 .map!(a => Target(inDubPackagePath(dubPackage.path, a)))
166                 .array;
167 
168 
169             // go through dub dependencies and optionally put the object files in dubObjsDir
170             if(!isMainPackage && dubObjsDir.globalDir != "") {
171                 foreach(ref target; packageTargets) {
172                     target.rawOutputs[0] = buildPath(dubObjsDir.globalDir,
173                                                      target.rawOutputs[0].dirName,
174                                                      dubObjsDir.targetDir,
175                                                      target.rawOutputs[0].baseName);
176                 }
177             }
178 
179             targets ~= packageTargets;
180         }
181 
182         return targets ~ allStaticLibrarySources;
183     }
184 
185     TargetName targetName() @safe const pure nothrow {
186         const fileName = packages[0].targetFileName;
187         switch(targetType) with(TargetType) {
188         default:
189             return TargetName(fileName);
190         case library:
191             return TargetName("lib" ~ fileName ~ ".a");
192         case dynamicLibrary:
193             return TargetName("lib" ~ fileName ~ ".so");
194         }
195     }
196 
197     TargetType targetType() @safe const pure nothrow {
198         return packages[0].targetType;
199     }
200 
201     string[] mainLinkerFlags() @safe pure nothrow const {
202         import std.array: join;
203 
204         const pack = packages[0];
205         return (pack.targetType == TargetType.library || pack.targetType == TargetType.staticLibrary)
206             ? ["-shared"]
207             : [];
208     }
209 
210     string[] linkerFlags() @safe const pure nothrow {
211         const allLibs = packages.map!(a => a.libs).join;
212         return
213             allLibs.map!(a => "-L-l" ~ a).array ~
214             packages.map!(a => a.lflags.map!(b => "-L" ~ b)).join;
215     }
216 
217     string[] allImportPaths() @safe nothrow const {
218         import reggae.config: options;
219 
220         string[] paths;
221         auto rng = packages.map!(a => a.packagePaths(a.importPaths));
222         foreach(p; rng) paths ~= p;
223         return paths ~ options.projectPath;
224     }
225 
226     // must be at the very end
227     private Target[] allStaticLibrarySources() @trusted nothrow const pure {
228         import std.algorithm: filter, map;
229         import std.array: array, join;
230         return packages.
231             map!(a => cast(string[])a.files.filter!isStaticLibrary.array).
232             join.
233             map!(a => Target(a)).
234             array;
235     }
236 
237     // all postBuildCommands in one shell command. Empty if there are none
238     string postBuildCommands() @safe pure nothrow const {
239         import std.string: join;
240         return packages[0].postBuildCommands.join(" && ");
241     }
242 }
243 
244 
245 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow {
246     return paths.map!(a => buildPath(dubPackage.path, a)).array;
247 }
248 
249 //@trusted because of map.array
250 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow {
251 
252     import std.range: chain, only;
253     import std.array: array, front, empty;
254 
255     string[] result;
256 
257     foreach(dependency; chain(only(pack.name), pack.dependencies)) {
258         auto depPack = packages.find!(a => a.name == dependency);
259         if(!depPack.empty) {
260             result ~= F(depPack.front).array;
261         }
262     }
263     return result;
264 }