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