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