1 module tests.ut.build;
2 
3 import unit_threaded;
4 import reggae;
5 import reggae.options;
6 import reggae.path: buildPath;
7 import std.array;
8 import std.format;
9 
10 
11 @("isLeaf") unittest {
12     Target("tgt").isLeaf.shouldBeTrue;
13     Target("other", "", [Target("foo"), Target("bar")]).isLeaf.shouldBeFalse;
14     Target("implicits", "", [], [Target("foo")]).isLeaf.shouldBeFalse;
15 }
16 
17 
18 @("$in and $out") unittest {
19     import reggae.config: gDefaultOptions;
20     //Tests that specifying $in and $out in the command string gets substituted correctly
21     {
22         auto target = Target("foo",
23                               "createfoo -o $out $in",
24                               [Target("bar.txt"), Target("baz.txt")]);
25         target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual(
26             "createfoo -o foo " ~ buildPath("/path/to/bar.txt") ~ " " ~ buildPath("/path/to/baz.txt"));
27     }
28     {
29         auto target = Target("tgt",
30                               "gcc -o $out $in",
31                               [
32                                   Target("src1.o", "gcc -c -o $out $in", [Target("src1.c")]),
33                                   Target("src2.o", "gcc -c -o $out $in", [Target("src2.c")])
34                                   ],
35             );
36         target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual("gcc -o tgt src1.o src2.o");
37     }
38 
39     {
40         auto target = Target(["proto.h", "proto.c"],
41                               "protocompile $out -i $in",
42                               [Target("proto.idl")]);
43         target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual(
44             "protocompile proto.h proto.c -i " ~ buildPath("/path/to/proto.idl"));
45     }
46 
47     {
48         auto target = Target("lib1.a",
49                               "ar -o$out $in",
50                               [Target(["foo1.o", "foo2.o"], "cmd", [Target("tmp")]),
51                                Target("bar.o"),
52                                Target("baz.o")]);
53         target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual(
54             "ar -olib1.a foo1.o foo2.o " ~ buildPath("/path/to/bar.o") ~ " " ~ buildPath("/path/to/baz.o"));
55     }
56 }
57 
58 
59 @("Project") unittest {
60     import reggae.config: gDefaultOptions;
61     auto target = Target("foo",
62                           "makefoo -i $in -o $out -p $project",
63                           [Target("bar"), Target("baz")]);
64     target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual(
65         "makefoo -i " ~ buildPath("/path/to/bar") ~ " " ~ buildPath("/path/to/baz") ~ " -o foo -p " ~ buildPath("/path/to"));
66 }
67 
68 
69 @("Multiple outputs") unittest {
70     import reggae.config: gDefaultOptions;
71     auto target = Target(["foo.hpp", "foo.cpp"], "protocomp $in", [Target("foo.proto")]);
72     target.rawOutputs.shouldEqual(["foo.hpp", "foo.cpp"]);
73     target.shellCommand(gDefaultOptions.withProjectPath("myproj")).shouldEqual("protocomp " ~ buildPath("myproj/foo.proto"));
74 
75     auto bld = Build(target);
76     bld.targets.array[0].rawOutputs.shouldEqual(["foo.hpp", "foo.cpp"]);
77 }
78 
79 
80 @("InTopLevelObjDir") unittest {
81 
82     auto theApp = Target("theapp");
83     auto dirName = objDirOf(theApp);
84     auto fooObj = Target("foo.o", "", [Target("foo.c")]);
85     fooObj.inTopLevelObjDirOf(dirName).shouldEqual(
86         Target(buildPath(".reggae/objs/theapp.objs/foo.o"), "", [Target(buildPath("$project/foo.c"))]));
87 
88     auto barObjInBuildDir = Target("$builddir/bar.o", "", [Target(buildPath("$project/bar.c"))]);
89     barObjInBuildDir.inTopLevelObjDirOf(dirName).shouldEqual(
90         Target("bar.o", "", [Target(buildPath("$project/bar.c"))]));
91 
92     auto leafTarget = Target("foo.c");
93     leafTarget.inTopLevelObjDirOf(dirName).shouldEqual(Target(buildPath("$project/foo.c")));
94 }
95 
96 
97 @("Multiple outputs, implicits") unittest {
98     auto protoSrcs = Target([`$builddir/gen/protocol.c`, `$builddir/gen/protocol.h`],
99                              `./compiler $in`,
100                              [Target(`protocol.proto`)]);
101     auto protoObj = Target(`$builddir/bin/protocol.o`,
102                             `gcc -o $out -c $builddir/gen/protocol.c`,
103                             [], [protoSrcs]);
104     auto protoD = Target(`$builddir/gen/protocol.d`,
105                           `echo "extern(C) " > $out; cat $builddir/gen/protocol.h >> $out`,
106                           [], [protoSrcs]);
107     auto app = Target(`app`,
108                        `dmd -of$out $in`,
109                        [Target(`src/main.d`), protoObj, protoD]);
110     auto build = Build(app);
111 
112     auto newProtoSrcs = Target([buildPath("gen/protocol.c"), buildPath("gen/protocol.h")],
113                                 `./compiler $in`,
114                                 [Target(buildPath("$project/protocol.proto"))]);
115     auto newProtoD = Target(buildPath("gen/protocol.d"),
116                              `echo "extern(C) " > $out; cat gen/protocol.h >> $out`,
117                              [], [newProtoSrcs]);
118 
119     build.targets.array.shouldEqual(
120         [Target("app", "dmd -of$out $in",
121                 [Target(buildPath("$project/src/main.d")),
122                  Target(buildPath("bin/protocol.o"), "gcc -o $out -c gen/protocol.c",
123                         [], [newProtoSrcs]),
124                  newProtoD])]
125         );
126 }
127 
128 
129 @("optional") unittest {
130     enum foo = Target("foo", "dmd -of$out $in", Target("foo.d"));
131     enum bar = Target("bar", "dmd -of$out $in", Target("bar.d"));
132     enum newBar = Target("bar", "dmd -of$out $in", Target(buildPath("$project/bar.d")));
133 
134     optional(bar).target.shouldEqual(newBar);
135     mixin build!(foo, optional(bar));
136     auto build = buildFunc();
137     build.targets.array[1].shouldEqual(newBar);
138 }
139 
140 
141 @("Diamond deps") unittest {
142     auto src1 = Target("src1.d");
143     auto src2 = Target("src2.d");
144     auto obj1 = Target("obj1.o", "dmd -of$out -c $in", src1);
145     auto obj2 = Target("obj2.o", "dmd -of$out -c $in", src2);
146     auto fooLib = Target("$project/foo.so", "dmd -of$out $in", [obj1, obj2]);
147     auto symlink1 = Target("$project/weird/path/thingie1", "ln -sf $in $out", fooLib);
148     auto symlink2 = Target("$project/weird/path/thingie2", "ln -sf $in $out", fooLib);
149     auto build = Build(symlink1, symlink2);
150 
151     auto newSrc1 = Target(buildPath("$project/src1.d"));
152     auto newSrc2 = Target(buildPath("$project/src2.d"));
153     auto newObj1 = Target(buildPath(".reggae/objs/__project__/foo.so.objs/obj1.o"), "dmd -of$out -c $in", newSrc1);
154     auto newObj2 = Target(buildPath(".reggae/objs/__project__/foo.so.objs/obj2.o"), "dmd -of$out -c $in", newSrc2);
155     auto newFooLib = Target(buildPath("$project/foo.so"), "dmd -of$out $in", [newObj1, newObj2]);
156     auto newSymlink1 = Target(buildPath("$project/weird/path/thingie1"), "ln -sf $in $out", newFooLib);
157     auto newSymlink2 = Target(buildPath("$project/weird/path/thingie2"), "ln -sf $in $out", newFooLib);
158 
159     build.range.array.shouldEqual([newObj1, newObj2, newFooLib, newSymlink1, newSymlink2]);
160 }
161 
162 @("Phobos optional bug") unittest {
163     enum obj1 = Target("obj1.o", "dmd -of$out -c $in", Target("src1.d"));
164     enum obj2 = Target("obj2.o", "dmd -of$out -c $in", Target("src2.d"));
165     enum foo = Target("foo", "dmd -of$out $in", [obj1, obj2]);
166     Target bar() {
167         return Target("bar", "dmd -of$out $in", [obj1, obj2]);
168     }
169     mixin build!(foo, optional!(bar));
170     auto build = buildFunc();
171 
172     auto fooObj1 = Target(buildPath(".reggae/objs/foo.objs/obj1.o"), "dmd -of$out -c $in", Target(buildPath("$project/src1.d")));
173     auto fooObj2 = Target(buildPath(".reggae/objs/foo.objs/obj2.o"), "dmd -of$out -c $in", Target(buildPath("$project/src2.d")));
174     auto newFoo = Target("foo", "dmd -of$out $in", [fooObj1, fooObj2]);
175 
176     auto barObj1 = Target(buildPath(".reggae/objs/bar.objs/obj1.o"), "dmd -of$out -c $in", Target(buildPath("$project/src1.d")));
177     auto barObj2 = Target(buildPath(".reggae/objs/bar.objs/obj2.o"), "dmd -of$out -c $in", Target(buildPath("$project/src2.d")));
178     auto newBar = Target("bar", "dmd -of$out $in", [barObj1, barObj2]);
179 
180     build.range.array.shouldEqual([fooObj1, fooObj2, newFoo, barObj1, barObj2, newBar]);
181 }
182 
183 
184 @("Outputs in project path") unittest {
185     auto mkDir = Target("$project/foodir", "mkdir -p $out", [], []);
186     mkDir.expandOutputs("/path/to/proj").shouldEqual([buildPath("/path/to/proj/foodir")]);
187 }
188 
189 
190 @("expandOutputs") unittest {
191     auto foo = Target("$project/foodir", "mkdir -p $out", [], []);
192     foo.expandOutputs("/path/to/proj").array.shouldEqual([buildPath("/path/to/proj/foodir")]);
193 
194     auto bar = Target("$builddir/foodir", "mkdir -p $out", [], []);
195     bar.expandOutputs("/path/to/proj").array.shouldEqual(["foodir"]);
196 }
197 
198 
199 @("Command $builddir") unittest {
200     import reggae.config: gDefaultOptions;
201     auto cmd = Command("dmd -of$builddir/ut_debug $in");
202     cmd.shellCommand(gDefaultOptions.withProjectPath("/path/to/proj"), Language.unknown, ["$builddir/ut_debug"], ["foo.d"]).
203         shouldEqual("dmd -ofut_debug foo.d");
204 }
205 
206 
207 @("$builddir in top-level target") unittest {
208     auto ao = objectFile(SourceFile("a.c"));
209     auto liba = Target("$builddir/liba.a", "ar rcs liba.a a.o", [ao]);
210     mixin build!(liba);
211     auto build = buildFunc();
212     build.targets[0].rawOutputs.shouldEqual(["liba.a"]);
213 }
214 
215 
216 @("Output in $builddir") unittest {
217     auto target = Target("$builddir/foo/bar", "cmd", [Target("foo.d"), Target("bar.d")]);
218     target.expandOutputs("/path/to").shouldEqual([buildPath("foo/bar")]);
219 }
220 
221 @("Output in $project") unittest {
222     auto target = Target("$project/foo/bar", "cmd", [Target("foo.d"), Target("bar.d")]);
223     target.expandOutputs("/path/to").shouldEqual([buildPath("/path/to/foo/bar")]);
224 }
225 
226 @("Cmd with $builddir") unittest {
227     auto target = Target("output", "cmd -I$builddir/include $in $out", [Target("foo.d"), Target("bar.d")]);
228     target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual(
229         "cmd -Iinclude " ~ buildPath("/path/to/foo.d") ~ " " ~ buildPath("/path/to/bar.d") ~ " output");
230 }
231 
232 @("Cmd with $project") unittest {
233     auto target = Target("output", "cmd -I$project/include $in $out", [Target("foo.d"), Target("bar.d")]);
234     target.shellCommand(gDefaultOptions.withProjectPath("/path/to")).shouldEqual(
235         "cmd -I" ~ buildPath("/path/to") ~ "/include" ~ " " ~ buildPath("/path/to/foo.d") ~ " " ~ buildPath("/path/to/bar.d") ~ " output");
236 }
237 
238 @("Deps in $builddir") unittest {
239     auto target = Target("output", "cmd", [Target("$builddir/foo.d"), Target("$builddir/bar.d")]);
240     target.dependenciesInProjectPath("/path/to").shouldEqual(["foo.d", "bar.d"]);
241 }
242 
243 @("Deps in $project") unittest {
244     auto target = Target("output", "cmd", [Target("$project/foo.d"), Target("$project/bar.d")]);
245     target.dependenciesInProjectPath("/path/to").shouldEqual(
246         [buildPath("/path/to/foo.d"), buildPath("/path/to/bar.d")]);
247 }
248 
249 
250 @("Build with one dep in $builddir") unittest {
251     auto target = Target("output", "cmd -o $out -c $in", Target("$builddir/input.d"));
252     alias top = link!(ExeName("ut"), targetConcat!(target));
253     auto build = Build(top);
254     build.targets[0].dependencyTargets[0].dependenciesInProjectPath("/path/to").shouldEqual(["input.d"]);
255 }
256 
257 
258 @("Replace concrete compiler with variables")
259 unittest {
260     immutable str = "\n" ~
261         "clang -o foo -c foo.c\n" ~
262         "clang++ -o foo -c foo.cpp\n" ~
263         "ldmd -offoo -c foo.d\n";
264     auto opts = Options();
265     opts.cCompiler = "clang";
266     opts.cppCompiler = "clang++";
267     opts.dCompiler = "ldmd";
268     str.replaceConcreteCompilersWithVars(opts).shouldEqual(
269         "\n" ~
270         "$(CC) -o foo -c foo.c\n" ~
271         "$(CXX) -o foo -c foo.cpp\n" ~
272         "$(DC) -offoo -c foo.d\n"
273         );
274 }
275 
276 
277 @("optional targets should also sandbox their dependencies") unittest {
278     auto med = Target("med", "medcmd -o $out $in", Target("input"));
279     auto tgt = Target("output", "cmd -o $out $in", med);
280     auto build = Build(optional(tgt));
281     build.targets.shouldEqual(
282         [Target("output",
283                 "cmd -o $out $in",
284                 Target(buildPath(".reggae/objs/output.objs/med"),
285                        "medcmd -o $out $in",
286                        buildPath("$project/input")))]);
287 }
288 
289 @("input path with environment variable")
290 unittest {
291     auto build = Build(Target("app", "dmd -of$out $in", [Target("foo.d"), Target("$LIB/liblua.a")]));
292     Options options;
293     options.projectPath = "/proj";
294 
295     const srcFile = buildPath("/proj/foo.d");
296     const libFile = buildPath("$LIB/liblua.a");
297     build.targets[0].shellCommand(options).shouldEqual(
298         "dmd -ofapp %s %s".format(srcFile, libFile));
299     build.targets[0].dependenciesInProjectPath(options.projectPath)
300         .shouldEqual([srcFile, libFile]);
301 }