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 struct DubInfo {
40 
41     DubPackage[] packages;
42 
43     Target[] toTargets(Flag!"main" includeMain = Yes.main,
44                        in string compilerFlags = "",
45                        Flag!"allTogether" allTogether = No.allTogether) @safe const {
46 
47         import reggae.config: options;
48         import std.functional: not;
49 
50         Target[] targets;
51 
52         // -unittest should only apply to the main package
53         string deUnitTest(T)(in T index, in string flags) {
54             import std.string: replace;
55             return index == 0
56                 ? flags
57                 : flags.replace("-unittest", "").replace("-main", "");
58         }
59 
60         const(string)[] getVersions(T)(in T index) {
61             import std.algorithm: map;
62             import std.array: array;
63 
64             const(string)[] ret = index == 0
65                 ? packages[index].allOf!(a => a.versions)(packages)
66                 : packages[0].versions ~ packages[index].versions;
67 
68             return ret.map!(a => "-version=" ~ a).array;
69         }
70 
71         foreach(const i, const dubPackage; packages) {
72             const importPaths = allImportPaths();
73             const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages);
74             auto versions = getVersions(i);
75 
76             //the path must be explicit for the other packages, implicit for the "main"
77             //package
78             const projDir = i == 0 ? "" : dubPackage.path;
79 
80             immutable flags = chain(dubPackage.dflags,
81                                     versions,
82                                     [options.dflags],
83                                     [deUnitTest(i, compilerFlags)])
84                 .join(" ");
85 
86             const files = dubPackage.files.
87                 filter!(a => includeMain || a != dubPackage.mainSourceFile).
88                 filter!(not!isStaticLibrary).
89                 map!(a => buildPath(dubPackage.path, a)).array;
90 
91             auto func = allTogether ? &dlangPackageObjectFilesTogether : &dlangPackageObjectFiles;
92             targets ~= func(files, flags, importPaths, stringImportPaths, projDir);
93         }
94 
95         return targets;
96     }
97 
98     ExeName exeName() @safe const pure nothrow {
99         return ExeName(packages[0].targetFileName);
100     }
101 
102     string[] mainLinkerFlags() @safe pure nothrow const {
103         import std.array: join;
104 
105         const pack = packages[0];
106         return (pack.targetType == "library" || pack.targetType == "staticLibrary") ? ["-lib"] : [];
107     }
108 
109     string[] linkerFlags() @safe const pure nothrow {
110         const allLibs = packages.map!(a => a.libs).join;
111         return
112             allLibs.map!(a => "-L-l" ~ a).array ~
113             packages.map!(a => a.lflags.map!(b => "-L" ~ b)).join;
114     }
115 
116     string[] allImportPaths() @safe nothrow const {
117         import reggae.config: options;
118 
119         string[] paths;
120         auto rng = packages.map!(a => a.packagePaths(a.importPaths));
121         foreach(p; rng) paths ~= p;
122         return paths ~ options.projectPath;
123     }
124 
125     Target[] staticLibrarySources() @trusted nothrow const pure {
126         import std.algorithm: filter, map;
127         import std.array: array, join;
128         return packages.
129             map!(a => cast(string[])a.files.filter!isStaticLibrary.array).
130             join.
131             map!(a => Target(a)).
132             array;
133     }
134 }
135 
136 
137 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow {
138     return paths.map!(a => buildPath(dubPackage.path, a)).array;
139 }
140 
141 //@trusted because of map.array
142 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow {
143 
144     import std.algorithm: find;
145     import std.array: array, empty, front;
146 
147     string[] result;
148     //foreach(d; [pack.name] ~ pack.dependencies) doesn't compile with CTFE
149     //it seems to have to do with constness, replace string[] with const(string)[]
150     //and it won't compile
151     const dependencies = [pack.name] ~ pack.dependencies;
152     foreach(dependency; dependencies) {
153 
154         auto depPack = packages.find!(a => a.name == dependency);
155         if(!depPack.empty) {
156             result ~= F(depPack.front).array;
157         }
158     }
159     return result;
160 }