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