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 }