1 module tests.ut.ninja;
2 
3 
4 import unit_threaded;
5 import reggae;
6 import reggae.options;
7 import reggae.path: buildPath;
8 import reggae.backend.ninja;
9 
10 
11 version(Windows)
12     enum isWindows = true;
13 else
14     enum isWindows = false;
15 
16 @("Empty") unittest {
17     auto ninja = Ninja();
18     ninja.buildEntries.shouldBeEmpty;
19     ninja.ruleEntries.shouldBeEmpty;
20 }
21 
22 @("C++ linker") unittest {
23     auto ninja = Ninja(Build(Target("mybin",
24                                      "/usr/bin/c++ $in -o $out",
25                                      [Target("foo.o"), Target("bar.o")],
26                                   )));
27     ninja.buildEntries.shouldEqual([NinjaEntry("build mybin: cpp foo.o bar.o",
28                                                ["between = -o"])
29                                        ]);
30     ninja.ruleEntries.shouldEqual([NinjaEntry("rule cpp",
31                                               ["command = /usr/bin/c++ $in $between $out"])
32                                       ]);
33 }
34 
35 @("C++ linker project path") unittest {
36     auto ninja = Ninja(Build(Target("mybin",
37                                      "/usr/bin/c++ $in -o $out",
38                                      [Target("foo.o"), Target("bar.o")],
39                                   )),
40                         "/home/user/myproject");
41     enum obj1 = buildPath("/home/user/myproject/foo.o");
42     enum obj2 = buildPath("/home/user/myproject/bar.o");
43     ninja.buildEntries.shouldEqual([NinjaEntry("build mybin: cpp " ~ obj1 ~ " " ~ obj2,
44                                                ["between = -o"])
45                                        ]);
46     ninja.ruleEntries.shouldEqual([NinjaEntry("rule cpp",
47                                               ["command = /usr/bin/c++ $in $between $out"])
48                                       ]);
49 }
50 
51 
52 @("C++ linker project path and build") unittest {
53     auto ninja = Ninja(Build(Target("mybin",
54                                      "/usr/bin/c++ $in -o $out",
55                                      [Target("foo.o"), Target("bar.o")],
56                                   )),
57                         "/home/user/myproject");
58     enum obj1 = buildPath("/home/user/myproject/foo.o");
59     enum obj2 = buildPath("/home/user/myproject/bar.o");
60     ninja.buildEntries.shouldEqual([NinjaEntry("build mybin: cpp " ~ obj1 ~ " " ~ obj2,
61                                                ["between = -o"])
62                                        ]);
63     ninja.ruleEntries.shouldEqual([NinjaEntry("rule cpp",
64                                               ["command = /usr/bin/c++ $in $between $out"])
65                                       ]);
66 }
67 
68 
69 @("ICC build") unittest {
70     auto ninja = Ninja(Build(Target("/path/to/foo.o",
71                                      "icc.12.0.022b.i686-linux -pe-file-prefix=/usr/intel/12.0.022b/cc/12.0.022b/include/ @/usr/lib/icc-cc.cfg -I/path/to/headers -gcc-version=345 -fno-strict-aliasing -nostdinc -include /path/to/myheader.h -DTOOL_CHAIN_GCC=gcc-user -D__STUFF__ -imacros /path/to/preinclude_macros.h -I/path/to -Wall -c -MD -MF /path/to/foo.d -o $out $in",
72                                      [Target("/path/to/foo.c")])));
73     ninja.buildEntries.shouldEqual([NinjaEntry("build " ~ buildPath("/path/to/foo.o") ~ ": icc.12.0.022b.i686-linux " ~ buildPath("/path/to/foo.c"),
74                                                ["before = -pe-file-prefix=/usr/intel/12.0.022b/cc/12.0.022b/include/ @/usr/lib/icc-cc.cfg -I/path/to/headers -gcc-version=345 -fno-strict-aliasing -nostdinc -include /path/to/myheader.h -DTOOL_CHAIN_GCC=gcc-user -D__STUFF__ -imacros /path/to/preinclude_macros.h -I/path/to -Wall -c -MD -MF /path/to/foo.d -o"])]);
75     ninja.ruleEntries.shouldEqual([NinjaEntry("rule icc.12.0.022b.i686-linux",
76                                               ["command = icc.12.0.022b.i686-linux $before $out $in"])]);
77 }
78 
79 
80 @("Before and after") unittest {
81     auto ninja = Ninja(Build(Target("foo.temp",
82                                      "icc @/path/to/icc-ld.cfg -o $out $in -Wl,-rpath-link -Wl,/usr/lib",
83                                      [Target("main.o"), Target("extra.o"), Target("sub_foo.o"), Target("sub_bar.o"),
84                                       Target("sub_baz.a")])));
85     ninja.buildEntries.shouldEqual([NinjaEntry("build foo.temp: icc main.o extra.o sub_foo.o sub_bar.o sub_baz.a",
86                                                ["before = @/path/to/icc-ld.cfg -o",
87                                                 "after = -Wl,-rpath-link -Wl,/usr/lib"])]);
88     ninja.ruleEntries.shouldEqual([NinjaEntry("rule icc",
89                                               ["command = icc $before $out $in $after"])]);
90 }
91 
92 @("Simple D build") unittest {
93     auto mainObj  = Target(`main.o`,  `dmd -I$project/src -c $in -of$out`, Target(`src/main.d`));
94     auto mathsObj = Target(`maths.o`, `dmd -c $in -of$out`, Target(`src/maths.d`));
95     auto app = Target(`myapp`,
96                        `dmd -of$out $in`,
97                        [mainObj, mathsObj]
98         );
99     auto build = Build(app);
100     auto ninja = Ninja(build, "/path/to/project");
101 
102     ninja.buildEntries.shouldEqual(
103         [NinjaEntry(buildPath("build .reggae/objs/myapp.objs/main.o: dmd /path/to/project/src/main.d"),
104                     ["before = -I" ~ buildPath("/path/to/project") ~ "/src -c",
105                      "between = -of"]),
106          NinjaEntry(buildPath("build .reggae/objs/myapp.objs/maths.o: dmd /path/to/project/src/maths.d"),
107                     ["before = -c",
108                      "between = -of"]),
109          NinjaEntry(buildPath("build myapp: dmd_2 .reggae/objs/myapp.objs/main.o .reggae/objs/myapp.objs/maths.o"),
110                     ["before = -of"])
111             ]);
112 
113     ninja.ruleEntries.shouldEqual(
114         [NinjaEntry("rule dmd",
115                     ["command = dmd $before $in $between$out"]),
116          NinjaEntry("rule dmd_2",
117                     ["command = dmd $before$out $in"])
118             ]);
119 }
120 
121 
122 @("Implicit dependencies") unittest {
123     auto target = Target("foo.o", "gcc -o $out -c $in", [Target("foo.c")], [Target("foo.h")]);
124     auto ninja = Ninja(Build(target));
125     ninja.buildEntries.shouldEqual(
126         [NinjaEntry("build foo.o: gcc foo.c | foo.h",
127                     ["before = -o",
128                      "between = -c"])
129             ]);
130 
131     ninja.ruleEntries.shouldEqual(
132         [NinjaEntry("rule gcc",
133                     ["command = gcc $before $out $between $in"])]);
134 }
135 
136 @("Implicit dependencies more than one") unittest {
137     auto target = Target("foo.o", "gcc -o $out -c $in", [Target("foo.c")], [Target("foo.h"), Target("foo.idl")]);
138     auto ninja = Ninja(Build(target));
139     ninja.buildEntries.shouldEqual(
140         [NinjaEntry("build foo.o: gcc foo.c | foo.h foo.idl",
141                     ["before = -o",
142                      "between = -c"])
143             ]);
144 
145     ninja.ruleEntries.shouldEqual(
146         [NinjaEntry("rule gcc",
147                     ["command = gcc $before $out $between $in"])]);
148 }
149 
150 
151 version(DigitalMars) {
152     @("Default rules") unittest {
153         import reggae.config: gDefaultOptions;
154         version(Windows)
155             enum defaultDCModel = " -m32mscoff";
156         else
157             enum defaultDCModel = null;
158         defaultRules(gDefaultOptions).shouldEqual(
159             [
160                 NinjaEntry("rule _ccompile",
161                            isWindows
162                            ? ["command = cl.exe @$out.rsp",
163                               "rspfile = $out.rsp",
164                               "rspfile_content = /nologo $flags $includes /showIncludes /Fo$out -c $in",
165                               "deps = msvc",
166                               "description = Compiling $out"]
167                            : ["command = gcc $flags $includes -MMD -MT $out -MF $out.dep -o $out -c $in",
168                               "deps = gcc",
169                               "depfile = $out.dep",
170                               "description = Compiling $out"]),
171                 NinjaEntry("rule _cppcompile",
172                            isWindows
173                            ? ["command = cl.exe @$out.rsp",
174                               "rspfile = $out.rsp",
175                               "rspfile_content = /nologo $flags $includes /showIncludes /Fo$out -c $in",
176                               "deps = msvc",
177                               "description = Compiling $out"]
178                            : ["command = g++ $flags $includes -MMD -MT $out -MF $out.dep -o $out -c $in",
179                               "deps = gcc",
180                               "depfile = $out.dep",
181                               "description = Compiling $out"]),
182                 NinjaEntry("rule _dcompile",
183                            isWindows
184                            ? ["command = " ~ buildPath(".reggae/dcompile") ~ " @$out.rsp",
185                               "rspfile = $out.rsp",
186                               "rspfile_content = --objFile=$out --depFile=$out.dep dmd" ~
187                                   defaultDCModel ~ " $flags $includes $stringImports $in",
188                               "deps = gcc",
189                               "depfile = $out.dep",
190                               "description = Compiling $out"]
191                            : ["command = " ~ buildPath(".reggae/dcompile") ~ " --objFile=$out --depFile=$out.dep dmd" ~
192                                   defaultDCModel ~ " $flags $includes $stringImports $in",
193                               "deps = gcc",
194                               "depfile = $out.dep",
195                               "description = Compiling $out"]),
196                 NinjaEntry("rule _clink",
197                            isWindows
198                            ? ["command = cl.exe @$out.rsp",
199                               "rspfile = $out.rsp",
200                               "rspfile_content = /nologo /Fo$out $flags $in",
201                               "description = Linking $out"]
202                            : ["command = gcc -o $out $flags $in",
203                               "description = Linking $out"]),
204                 NinjaEntry("rule _cpplink",
205                            isWindows
206                            ? ["command = cl.exe @$out.rsp",
207                               "rspfile = $out.rsp",
208                               "rspfile_content = /nologo /Fo$out $flags $in",
209                               "description = Linking $out"]
210                            : ["command = g++ -o $out $flags $in",
211                               "description = Linking $out"]),
212                 NinjaEntry("rule _dlink",
213                            isWindows
214                            ? ["command = dmd @$out.rsp",
215                               "rspfile = $out.rsp",
216                               "rspfile_content =" ~ defaultDCModel ~ " -of$out $flags $in",
217                               "description = Linking $out"]
218                            : ["command = dmd" ~ defaultDCModel ~ " -of$out $flags $in",
219                               "description = Linking $out"]),
220                 NinjaEntry("rule _ulink",
221                            ["command = dmd" ~ defaultDCModel ~ " -of$out $flags $in",
222                             "description = Linking $out"]),
223                 NinjaEntry("rule _ccompileAndLink",
224                            isWindows
225                            ? ["command = cl.exe @$out.rsp",
226                               "rspfile = $out.rsp",
227                               "rspfile_content = /nologo $flags $includes /showIncludes /Fo$out $in",
228                               "deps = msvc",
229                               "description = Building $out"]
230                            : ["command = gcc $flags $includes -MMD -MT $out -MF $out.dep -o $out $in",
231                               "deps = gcc",
232                               "depfile = $out.dep",
233                               "description = Building $out"]),
234                 NinjaEntry("rule _cppcompileAndLink",
235                            isWindows
236                            ? ["command = cl.exe @$out.rsp",
237                               "rspfile = $out.rsp",
238                               "rspfile_content = /nologo $flags $includes /showIncludes /Fo$out $in",
239                               "deps = msvc",
240                               "description = Building $out"]
241                            : ["command = g++ $flags $includes -MMD -MT $out -MF $out.dep -o $out $in",
242                               "deps = gcc",
243                               "depfile = $out.dep",
244                               "description = Building $out"]),
245                 NinjaEntry("rule _dcompileAndLink",
246                            isWindows
247                            ? ["command = " ~ buildPath(".reggae/dcompile") ~ " @$out.rsp",
248                               "rspfile = $out.rsp",
249                               "rspfile_content = --objFile=$out --depFile=$out.dep dmd" ~
250                                   defaultDCModel ~ " $flags $includes $stringImports $in",
251                               "deps = gcc",
252                               "depfile = $out.dep",
253                               "description = Building $out"]
254                            : ["command = " ~ buildPath(".reggae/dcompile") ~ " --objFile=$out --depFile=$out.dep dmd" ~
255                                   defaultDCModel ~ " $flags $includes $stringImports $in",
256                               "deps = gcc",
257                               "depfile = $out.dep",
258                               "description = Building $out"]),
259 
260                 NinjaEntry("rule _phony",
261                            isWindows
262                            ? [`command = cmd.exe /c "$cmd"`,
263                               "description = $cmd"]
264                            : ["command = $cmd"]),
265                 ]);
266     }
267 }
268 
269 @("Default rules weird C compiler") unittest {
270     auto options = Options();
271     options.cCompiler = "weirdcc";
272     auto rules = defaultRules(options);
273     auto entry = NinjaEntry("rule _ccompile",
274                             isWindows
275                             ? ["command = weirdcc @$out.rsp",
276                                "rspfile = $out.rsp",
277                                "rspfile_content = /nologo $flags $includes /showIncludes /Fo$out -c $in",
278                                "deps = msvc",
279                                "description = Compiling $out"]
280                             : ["command = weirdcc $flags $includes -MMD -MT $out -MF $out.dep -o $out -c $in",
281                                "deps = gcc",
282                                "depfile = $out.dep",
283                                "description = Compiling $out"]);
284     entry.shouldBeIn(rules);
285 }
286 
287 @("Implicit output") unittest {
288     auto foo = Target(["foo.h", "foo.c"], "protocomp $in", [Target("foo.proto")]);
289     auto bar = Target(["bar.h", "bar.c"], "protocomp $in", [Target("bar.proto")]);
290     auto ninja = Ninja(Build(foo, bar));
291 
292     ninja.buildEntries.shouldEqual(
293         [NinjaEntry("build foo.h foo.c: protocomp foo.proto"),
294          NinjaEntry("build bar.h bar.c: protocomp bar.proto")]);
295 
296     ninja.ruleEntries.shouldEqual(
297         [NinjaEntry("rule protocomp",
298                     ["command = protocomp $in "])]);
299 }
300 
301 
302 @("Implicit input") unittest {
303     auto protoSrcs = Target([`$builddir/gen/protocol.c`, `$builddir/gen/protocol.h`],
304                              `./compiler $in`,
305                              [Target(`protocol.proto`)]);
306     auto protoObj = Target(`$builddir/bin/protocol.o`,
307                             `gcc -o $out -c $builddir/gen/protocol.c`,
308                             [], [protoSrcs]);
309     auto protoD = Target(`$builddir/gen/protocol.d`,
310                           `./translator $builddir/gen/protocol.h $out`,
311                           [], [protoSrcs]);
312     auto app = Target(`app`, `dmd -of$out $in`,
313                        [Target("src/main.d"), protoObj, protoD]);
314 
315     auto ninja = Ninja(Build(app));
316 
317     ninja.buildEntries.shouldEqual(
318         [NinjaEntry(buildPath("build gen/protocol.c gen/protocol.h: compiler protocol.proto")),
319          NinjaEntry(buildPath("build bin/protocol.o: gcc gen/protocol.c | gen/protocol.c gen/protocol.h"),
320                     ["before = -o",
321                      "between = -c"]),
322          NinjaEntry(buildPath("build gen/protocol.d: translator gen/protocol.h | gen/protocol.c gen/protocol.h")),
323          NinjaEntry(buildPath("build app: dmd src/main.d bin/protocol.o gen/protocol.d"),
324                     ["before = -of"])
325             ]);
326 
327     ninja.ruleEntries.shouldEqual(
328         [NinjaEntry("rule compiler",
329                     ["command = ./compiler $in "]),
330          NinjaEntry("rule gcc",
331                     ["command = gcc $before $out $between $in"]),
332          NinjaEntry("rule translator",
333                     ["command = ./translator $in $out"]),
334          NinjaEntry("rule dmd",
335                     ["command = dmd $before$out $in"])
336             ]);
337 }
338 
339 
340 @("Output in project path custom") unittest {
341     auto tgt = Target("$project/foo.o", "gcc -o $out -c $in", Target("foo.c"));
342     auto ninja = Ninja(Build(tgt), "/path/to/proj");
343     ninja.buildEntries.shouldEqual(
344         [NinjaEntry(buildPath("build /path/to/proj/foo.o: gcc /path/to/proj/foo.c"),
345                     ["before = -o",
346                      "between = -c"])]);
347 }
348 
349 
350 @("Output and dep output in project path") unittest {
351     auto fooLib = Target("$project/foo.so", "dmd -of$out $in", [Target("src1.d"), Target("src2.d")]);
352     auto symlink1 = Target("$project/weird/path/thingie1", "ln -sf $in $out", fooLib);
353     auto symlink2 = Target("$project/weird/path/thingie2", "ln -sf $in $out", fooLib);
354     auto build = Build(symlink1, symlink2); //defined by the mixin
355     auto ninja = Ninja(build, "/tmp/proj");
356 
357     ninja.buildEntries.shouldEqual(
358         [NinjaEntry(buildPath("build /tmp/proj/foo.so: dmd /tmp/proj/src1.d /tmp/proj/src2.d"),
359                     ["before = -of"]),
360          NinjaEntry(buildPath("build /tmp/proj/weird/path/thingie1: ln /tmp/proj/foo.so"),
361                     ["before = -sf"]),
362          NinjaEntry(buildPath("build /tmp/proj/weird/path/thingie2: ln /tmp/proj/foo.so"),
363                     ["before = -sf"]),
364             ]
365         );
366 }
367 
368 @("Output in project path default") unittest {
369     import reggae.ctaa;
370     auto tgt = Target("$project/foo.o",
371                        Command(CommandType.compile, assocListT("foo", ["bar"])),
372                        Target("foo.c"));
373     auto ninja = Ninja(Build(tgt), "/path/to/proj");
374     ninja.buildEntries.shouldEqual(
375         [NinjaEntry(buildPath("build /path/to/proj/foo.o: _ccompile /path/to/proj/foo.c"),
376                     ["foo = bar"])]);
377 }
378 
379 
380 @("Phony rule") unittest {
381     auto tgt = Target("lephony",
382                        Command.phony("whatever boo bop"),
383                        [Target("toto"), Target("tata")],
384                        [Target("implicit")]);
385     auto ninja = Ninja(Build(tgt), "/path/to/proj");
386     ninja.buildEntries.shouldEqual(
387         [NinjaEntry(buildPath("build lephony: _phony /path/to/proj/toto /path/to/proj/tata | /path/to/proj/implicit"),
388                     ["cmd = whatever boo bop",
389                      "pool = console"])]
390         );
391 }
392 
393 @("Implicits with no in") unittest {
394     Target[] emptyDependencies;
395     auto stuff = Target("foo.o", "dmd -of$out -c $in", Target("foo.d"));
396     auto foo = Target("$project/foodir", "mkdir -p $out", emptyDependencies, [stuff]);
397     auto ninja = Ninja(Build(foo), "/path/to/proj"); //to make sure we can
398     ninja.buildEntries.shouldEqual(
399         [NinjaEntry(buildPath("build .reggae/objs/__project__/foodir.objs/foo.o: dmd /path/to/proj/foo.d"),
400                     ["before = -of",
401                      "between = -c"]),
402          NinjaEntry(buildPath("build /path/to/proj/foodir: mkdir  | .reggae/objs/__project__/foodir.objs/foo.o"),
403                     ["before = -p"]),
404             ]);
405     ninja.ruleEntries.shouldEqual(
406         [NinjaEntry("rule dmd",
407                     ["command = dmd $before$out $between $in"]),
408          NinjaEntry("rule mkdir",
409                     ["command = mkdir $before $out$in"])
410             ]);
411 }
412 
413 
414 @("Custom rule involving project path") unittest {
415     auto foo = Target("foo.o", "$project/../dmd/src/dmd -of$out -c $in", Target("foo.d"));
416     auto app = Target("app", "$project/../dmd/src/dmd -of$out -c $in", Target("foo.d"));
417     auto ninja = Ninja(Build(app), "/path/to/proj");
418     ninja.ruleEntries.shouldEqual(
419         [NinjaEntry("rule dmd",
420                     ["command = " ~ buildPath("/path/to/proj") ~ "/../dmd/src/dmd $before$out $between $in"]),
421             ]);
422 }
423 
424 
425 @("Target with no dependencies") unittest {
426     auto obj = Target("utmain.o", "dmd -of$out -c $in",
427                        Target("utmain.d", "/home/atila/coding/d/dtest/bin/dtest -f $out --generate"));
428     //before the fix this throws because of no $in and $out in the target
429     auto ninja = Ninja(Build(obj), "/path/to/proj");
430 }