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("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("objs/$project/foo.so.objs/obj1.o", "dmd -of$out -c $in", src1); 168 auto newObj2 = Target("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("objs/foo.objs/obj1.o", "dmd -of$out -c $in", Target("src1.d")); 187 auto fooObj2 = Target("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("objs/bar.objs/obj1.o", "dmd -of$out -c $in", Target("src1.d")); 191 auto barObj2 = Target("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("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 }