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 }
68 
69 bool isStaticLibrary(in string fileName) @safe pure nothrow {
70     import std.path: extension;
71     return fileName.extension == ".a";
72 }
73 
74 bool isObjectFile(in string fileName) @safe pure nothrow {
75     import reggae.rules.common: objExt;
76     import std.path: extension;
77     return fileName.extension == objExt;
78 }
79 
80 string inDubPackagePath(in string packagePath, in string filePath) @safe pure nothrow {
81     import std.path: buildPath;
82     import std.algorithm: startsWith;
83     return filePath.startsWith("$project")
84         ? filePath
85         : buildPath(packagePath, filePath);
86 }
87 
88 struct DubInfo {
89 
90     DubPackage[] packages;
91 
92     Target[] toTargets(Flag!"main" includeMain = Yes.main,
93                        in string compilerFlags = "",
94                        Flag!"allTogether" allTogether = No.allTogether) @safe const {
95 
96         import reggae.config: options;
97         import std.functional: not;
98 
99         Target[] targets;
100 
101         // -unittest should only apply to the main package
102         string deUnitTest(T)(in T index, in string flags) {
103             import std.string: replace;
104             return index == 0
105                 ? flags
106                 : flags.replace("-unittest", "").replace("-main", "");
107         }
108 
109         const(string)[] getVersions(T)(in T index) {
110             import std.algorithm: map;
111             import std.array: array;
112 
113             const(string)[] ret = index == 0
114                 ? packages[index].allOf!(a => a.versions)(packages)
115                 : packages[0].versions ~ packages[index].versions;
116 
117             return ret.map!(a => "-version=" ~ a).array;
118         }
119 
120         foreach(const i, const dubPackage; packages) {
121             const importPaths = allImportPaths();
122             const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages);
123             auto versions = getVersions(i);
124 
125             //the path must be explicit for the other packages, implicit for the "main"
126             //package
127             const projDir = i == 0 ? "" : dubPackage.path;
128 
129             const sharedFlag = targetType == TargetType.dynamicLibrary ? ["-fPIC"] : [];
130             immutable flags = chain(dubPackage.dflags,
131                                     versions,
132                                     [options.dflags],
133                                     sharedFlag,
134                                     [deUnitTest(i, compilerFlags)])
135                 .join(" ");
136 
137             const files = dubPackage.files.
138                 filter!(a => includeMain || a != dubPackage.mainSourceFile).
139                 filter!(not!isStaticLibrary).
140                 filter!(not!isObjectFile).
141                 map!(a => buildPath(dubPackage.path, a))
142                 .array;
143 
144             auto func = allTogether ? &dlangPackageObjectFilesTogether : &dlangPackageObjectFiles;
145             targets ~= func(files, flags, importPaths, stringImportPaths, projDir);
146             // add any object files that are meant to be linked
147             targets ~= dubPackage
148                 .files
149                 .filter!isObjectFile
150                 .map!(a => Target(inDubPackagePath(dubPackage.path, a)))
151                 .array;
152         }
153 
154         return targets ~ allStaticLibrarySources;
155     }
156 
157     TargetName targetName() @safe const pure nothrow {
158         const fileName = packages[0].targetFileName;
159         switch(targetType) with(TargetType) {
160         default:
161             return TargetName(fileName);
162         case library:
163             return TargetName("lib" ~ fileName ~ ".a");
164         case dynamicLibrary:
165             return TargetName("lib" ~ fileName ~ ".so");
166         }
167     }
168 
169     TargetType targetType() @safe const pure nothrow {
170         return packages[0].targetType;
171     }
172 
173     string[] mainLinkerFlags() @safe pure nothrow const {
174         import std.array: join;
175 
176         const pack = packages[0];
177         return (pack.targetType == TargetType.library || pack.targetType == TargetType.staticLibrary)
178             ? ["-shared"]
179             : [];
180     }
181 
182     string[] linkerFlags() @safe const pure nothrow {
183         const allLibs = packages.map!(a => a.libs).join;
184         return
185             allLibs.map!(a => "-L-l" ~ a).array ~
186             packages.map!(a => a.lflags.map!(b => "-L" ~ b)).join;
187     }
188 
189     string[] allImportPaths() @safe nothrow const {
190         import reggae.config: options;
191 
192         string[] paths;
193         auto rng = packages.map!(a => a.packagePaths(a.importPaths));
194         foreach(p; rng) paths ~= p;
195         return paths ~ options.projectPath;
196     }
197 
198     // must be at the very end
199     private Target[] allStaticLibrarySources() @trusted nothrow const pure {
200         import std.algorithm: filter, map;
201         import std.array: array, join;
202         return packages.
203             map!(a => cast(string[])a.files.filter!isStaticLibrary.array).
204             join.
205             map!(a => Target(a)).
206             array;
207     }
208 
209     // all postBuildCommands in one shell command. Empty if there are none
210     string postBuildCommands() @safe pure nothrow const {
211         import std.string: join;
212         return packages[0].postBuildCommands.join(" && ");
213     }
214 }
215 
216 
217 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow {
218     return paths.map!(a => buildPath(dubPackage.path, a)).array;
219 }
220 
221 //@trusted because of map.array
222 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow {
223 
224     import std.range: chain, only;
225     import std.array: array, front, empty;
226 
227     string[] result;
228 
229     foreach(dependency; chain(only(pack.name), pack.dependencies)) {
230         auto depPack = packages.find!(a => a.name == dependency);
231         if(!depPack.empty) {
232             result ~= F(depPack.front).array;
233         }
234     }
235     return result;
236 }