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 }