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 struct DubPackage {
17     string name;
18     string path;
19     string mainSourceFile;
20     string targetFileName;
21     string[] dflags;
22     string[] lflags;
23     string[] importPaths;
24     string[] stringImportPaths;
25     string[] files;
26     string targetType;
27     string[] versions;
28     string[] dependencies;
29     string[] libs;
30     bool active;
31     string[] preBuildCommands;
32 }
33 
34 bool isStaticLibrary(in string fileName) @safe pure nothrow {
35     import std.path: extension;
36     return fileName.extension == ".a";
37 }
38 
39 bool isObjectFile(in string fileName) @safe pure nothrow {
40     import reggae.rules.common: objExt;
41     import std.path: extension;
42     return fileName.extension == objExt;
43 }
44 
45 string inDubPackagePath(in string packagePath, in string filePath) @safe pure nothrow {
46     import std.path: buildPath;
47     import std.algorithm: startsWith;
48     return filePath.startsWith("$project")
49         ? filePath
50         : buildPath(packagePath, filePath);
51 }
52 
53 struct DubInfo {
54 
55     DubPackage[] packages;
56 
57     Target[] toTargets(Flag!"main" includeMain = Yes.main,
58                        in string compilerFlags = "",
59                        Flag!"allTogether" allTogether = No.allTogether) @safe const {
60 
61         import reggae.config: options;
62         import std.functional: not;
63 
64         Target[] targets;
65 
66         // -unittest should only apply to the main package
67         string deUnitTest(T)(in T index, in string flags) {
68             import std.string: replace;
69             return index == 0
70                 ? flags
71                 : flags.replace("-unittest", "").replace("-main", "");
72         }
73 
74         const(string)[] getVersions(T)(in T index) {
75             import std.algorithm: map;
76             import std.array: array;
77 
78             const(string)[] ret = index == 0
79                 ? packages[index].allOf!(a => a.versions)(packages)
80                 : packages[0].versions ~ packages[index].versions;
81 
82             return ret.map!(a => "-version=" ~ a).array;
83         }
84 
85         foreach(const i, const dubPackage; packages) {
86             const importPaths = allImportPaths();
87             const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages);
88             auto versions = getVersions(i);
89 
90             //the path must be explicit for the other packages, implicit for the "main"
91             //package
92             const projDir = i == 0 ? "" : dubPackage.path;
93 
94             immutable flags = chain(dubPackage.dflags,
95                                     versions,
96                                     [options.dflags],
97                                     [deUnitTest(i, compilerFlags)])
98                 .join(" ");
99 
100             const files = dubPackage.files.
101                 filter!(a => includeMain || a != dubPackage.mainSourceFile).
102                 filter!(not!isStaticLibrary).
103                 filter!(not!isObjectFile).
104                 map!(a => buildPath(dubPackage.path, a))
105                 .array;
106 
107             auto func = allTogether ? &dlangPackageObjectFilesTogether : &dlangPackageObjectFiles;
108             targets ~= func(files, flags, importPaths, stringImportPaths, projDir);
109             // add any object files that are meant to be linked
110             targets ~= dubPackage
111                 .files
112                 .filter!isObjectFile
113                 .map!(a => Target(inDubPackagePath(dubPackage.path, a)))
114                 .array;
115         }
116 
117         return targets ~ allStaticLibrarySources;
118     }
119 
120     TargetName targetName() @safe const pure nothrow {
121         return TargetName(packages[0].targetFileName);
122     }
123 
124     string targetType() @safe const pure nothrow {
125         return packages[0].targetType;
126     }
127 
128     string[] mainLinkerFlags() @safe pure nothrow const {
129         import std.array: join;
130 
131         const pack = packages[0];
132         return (pack.targetType == "library" || pack.targetType == "staticLibrary") ? ["-lib"] : [];
133     }
134 
135     string[] linkerFlags() @safe const pure nothrow {
136         const allLibs = packages.map!(a => a.libs).join;
137         return
138             allLibs.map!(a => "-L-l" ~ a).array ~
139             packages.map!(a => a.lflags.map!(b => "-L" ~ b)).join;
140     }
141 
142     string[] allImportPaths() @safe nothrow const {
143         import reggae.config: options;
144 
145         string[] paths;
146         auto rng = packages.map!(a => a.packagePaths(a.importPaths));
147         foreach(p; rng) paths ~= p;
148         return paths ~ options.projectPath;
149     }
150 
151     // must be at the very end
152     private Target[] allStaticLibrarySources() @trusted nothrow const pure {
153         import std.algorithm: filter, map;
154         import std.array: array, join;
155         return packages.
156             map!(a => cast(string[])a.files.filter!isStaticLibrary.array).
157             join.
158             map!(a => Target(a)).
159             array;
160     }
161 }
162 
163 
164 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow {
165     return paths.map!(a => buildPath(dubPackage.path, a)).array;
166 }
167 
168 //@trusted because of map.array
169 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow {
170 
171     import std.algorithm: find;
172     import std.array: array, empty, front;
173 
174     string[] result;
175     //foreach(d; [pack.name] ~ pack.dependencies) doesn't compile with CTFE
176     //it seems to have to do with constness, replace string[] with const(string)[]
177     //and it won't compile
178     const dependencies = [pack.name] ~ pack.dependencies;
179     foreach(dependency; dependencies) {
180 
181         auto depPack = packages.find!(a => a.name == dependency);
182         if(!depPack.empty) {
183             result ~= F(depPack.front).array;
184         }
185     }
186     return result;
187 }