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