1 /** 2 SDL format support for PackageRecipe 3 4 Copyright: © 2014-2015 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.recipe.sdl; 9 10 import dub.compilers.compiler; 11 import dub.dependency; 12 import dub.internal.sdlang; 13 import dub.internal.vibecompat.core.log; 14 import dub.internal.vibecompat.inet.path; 15 import dub.recipe.packagerecipe; 16 17 import std.algorithm : map; 18 import std.array : array; 19 import std.conv; 20 import std..string : startsWith; 21 22 23 void parseSDL(ref PackageRecipe recipe, string sdl, string parent_name, string filename) 24 { 25 parseSDL(recipe, parseSource(sdl, filename), parent_name); 26 } 27 28 void parseSDL(ref PackageRecipe recipe, Tag sdl, string parent_name) 29 { 30 Tag[] subpacks; 31 Tag[] configs; 32 33 // parse top-level fields 34 foreach (n; sdl.all.tags) { 35 enforceSDL(n.name.length > 0, "Anonymous tags are not allowed at the root level.", n); 36 switch (n.fullName) { 37 default: break; 38 case "name": recipe.name = n.stringTagValue; break; 39 case "version": recipe.version_ = n.stringTagValue; break; 40 case "description": recipe.description = n.stringTagValue; break; 41 case "homepage": recipe.homepage = n.stringTagValue; break; 42 case "authors": recipe.authors ~= n.stringArrayTagValue; break; 43 case "copyright": recipe.copyright = n.stringTagValue; break; 44 case "license": recipe.license = n.stringTagValue; break; 45 case "subPackage": subpacks ~= n; break; 46 case "configuration": configs ~= n; break; 47 case "buildType": 48 auto name = n.stringTagValue(true); 49 BuildSettingsTemplate bt; 50 parseBuildSettings(n, bt, parent_name); 51 recipe.buildTypes[name] = bt; 52 break; 53 case "toolchainRequirements": 54 parseToolchainRequirements(recipe.toolchainRequirements, n); 55 break; 56 case "x:ddoxFilterArgs": recipe.ddoxFilterArgs ~= n.stringArrayTagValue; break; 57 case "x:ddoxTool": recipe.ddoxTool = n.stringTagValue; break; 58 } 59 } 60 61 enforceSDL(recipe.name.length > 0, "The package \"name\" field is missing or empty.", sdl); 62 string full_name = parent_name.length ? parent_name ~ ":" ~ recipe.name : recipe.name; 63 64 // parse general build settings 65 parseBuildSettings(sdl, recipe.buildSettings, full_name); 66 67 // determine default target type for configurations 68 auto defttype = recipe.buildSettings.targetType; 69 if (defttype == TargetType.autodetect) 70 defttype = TargetType.library; 71 72 // parse configurations 73 recipe.configurations.length = configs.length; 74 foreach (i, n; configs) { 75 recipe.configurations[i].buildSettings.targetType = defttype; 76 parseConfiguration(n, recipe.configurations[i], full_name); 77 } 78 79 // finally parse all sub packages 80 recipe.subPackages.length = subpacks.length; 81 foreach (i, n; subpacks) { 82 if (n.values.length) { 83 recipe.subPackages[i].path = n.stringTagValue; 84 } else { 85 enforceSDL(n.attributes.length == 0, "No attributes allowed for inline sub package definitions.", n); 86 parseSDL(recipe.subPackages[i].recipe, n, full_name); 87 } 88 } 89 } 90 91 Tag toSDL(in ref PackageRecipe recipe) 92 { 93 Tag ret = new Tag; 94 void add(T)(string field, T value) { ret.add(new Tag(null, field, [Value(value)])); } 95 add("name", recipe.name); 96 if (recipe.version_.length) add("version", recipe.version_); 97 if (recipe.description.length) add("description", recipe.description); 98 if (recipe.homepage.length) add("homepage", recipe.homepage); 99 if (recipe.authors.length) ret.add(new Tag(null, "authors", recipe.authors.map!(a => Value(a)).array)); 100 if (recipe.copyright.length) add("copyright", recipe.copyright); 101 if (recipe.license.length) add("license", recipe.license); 102 foreach (name, settings; recipe.buildTypes) { 103 auto t = new Tag(null, "buildType", [Value(name)]); 104 t.add(settings.toSDL()); 105 ret.add(t); 106 } 107 if (!recipe.toolchainRequirements.empty) { 108 ret.add(toSDL(recipe.toolchainRequirements)); 109 } 110 if (recipe.ddoxFilterArgs.length) 111 ret.add(new Tag("x", "ddoxFilterArgs", recipe.ddoxFilterArgs.map!(a => Value(a)).array)); 112 if (recipe.ddoxTool.length) ret.add(new Tag("x", "ddoxTool", [Value(recipe.ddoxTool)])); 113 ret.add(recipe.buildSettings.toSDL()); 114 foreach(config; recipe.configurations) 115 ret.add(config.toSDL()); 116 foreach (i, subPackage; recipe.subPackages) { 117 if (subPackage.path !is null) { 118 add("subPackage", subPackage.path); 119 } else { 120 auto t = subPackage.recipe.toSDL(); 121 t.name = "subPackage"; 122 ret.add(t); 123 } 124 } 125 return ret; 126 } 127 128 private void parseBuildSettings(Tag settings, ref BuildSettingsTemplate bs, string package_name) 129 { 130 foreach (setting; settings.all.tags) 131 parseBuildSetting(setting, bs, package_name); 132 } 133 134 private void parseBuildSetting(Tag setting, ref BuildSettingsTemplate bs, string package_name) 135 { 136 switch (setting.fullName) { 137 default: break; 138 case "dependency": parseDependency(setting, bs, package_name); break; 139 case "systemDependencies": bs.systemDependencies = setting.stringTagValue; break; 140 case "targetType": bs.targetType = setting.stringTagValue.to!TargetType; break; 141 case "targetName": bs.targetName = setting.stringTagValue; break; 142 case "targetPath": bs.targetPath = setting.stringTagValue; break; 143 case "workingDirectory": bs.workingDirectory = setting.stringTagValue; break; 144 case "subConfiguration": 145 auto args = setting.stringArrayTagValue; 146 enforceSDL(args.length == 2, "Expecting package and configuration names as arguments.", setting); 147 bs.subConfigurations[expandPackageName(args[0], package_name, setting)] = args[1]; 148 break; 149 case "dflags": setting.parsePlatformStringArray(bs.dflags); break; 150 case "lflags": setting.parsePlatformStringArray(bs.lflags); break; 151 case "libs": setting.parsePlatformStringArray(bs.libs); break; 152 case "sourceFiles": setting.parsePlatformStringArray(bs.sourceFiles); break; 153 case "sourcePaths": setting.parsePlatformStringArray(bs.sourcePaths); break; 154 case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break; 155 case "mainSourceFile": bs.mainSourceFile = setting.stringTagValue; break; 156 case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break; 157 case "extraDependencyFiles": setting.parsePlatformStringArray(bs.extraDependencyFiles); break; 158 case "versions": setting.parsePlatformStringArray(bs.versions); break; 159 case "debugVersions": setting.parsePlatformStringArray(bs.debugVersions); break; 160 case "x:versionFilters": setting.parsePlatformStringArray(bs.versionFilters); break; 161 case "x:debugVersionFilters": setting.parsePlatformStringArray(bs.debugVersionFilters); break; 162 case "importPaths": setting.parsePlatformStringArray(bs.importPaths); break; 163 case "stringImportPaths": setting.parsePlatformStringArray(bs.stringImportPaths); break; 164 case "preGenerateCommands": setting.parsePlatformStringArray(bs.preGenerateCommands); break; 165 case "postGenerateCommands": setting.parsePlatformStringArray(bs.postGenerateCommands); break; 166 case "preBuildCommands": setting.parsePlatformStringArray(bs.preBuildCommands); break; 167 case "postBuildCommands": setting.parsePlatformStringArray(bs.postBuildCommands); break; 168 case "preRunCommands": setting.parsePlatformStringArray(bs.preRunCommands); break; 169 case "postRunCommands": setting.parsePlatformStringArray(bs.postRunCommands); break; 170 case "buildRequirements": setting.parsePlatformEnumArray!BuildRequirement(bs.buildRequirements); break; 171 case "buildOptions": setting.parsePlatformEnumArray!BuildOption(bs.buildOptions); break; 172 } 173 } 174 175 private void parseDependency(Tag t, ref BuildSettingsTemplate bs, string package_name) 176 { 177 enforceSDL(t.values.length != 0, "Missing dependency name.", t); 178 enforceSDL(t.values.length == 1, "Multiple dependency names.", t); 179 auto pkg = expandPackageName(t.values[0].get!string, package_name, t); 180 enforceSDL(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once.", t); 181 182 Dependency dep = Dependency.any; 183 auto attrs = t.attributes; 184 185 auto pv = "version" in attrs; 186 187 if ("path" in attrs) { 188 if ("version" in attrs) 189 logDiagnostic("Ignoring version specification (%s) for path based dependency %s", attrs["version"][0].value.get!string, attrs["path"][0].value.get!string); 190 dep.versionSpec = "*"; 191 dep.path = NativePath(attrs["path"][0].value.get!string); 192 } else { 193 enforceSDL("version" in attrs, "Missing version specification.", t); 194 dep.versionSpec = attrs["version"][0].value.get!string; 195 } 196 197 if ("optional" in attrs) 198 dep.optional = attrs["optional"][0].value.get!bool; 199 200 if ("default" in attrs) 201 dep.default_ = attrs["default"][0].value.get!bool; 202 203 bs.dependencies[pkg] = dep; 204 } 205 206 private void parseConfiguration(Tag t, ref ConfigurationInfo ret, string package_name) 207 { 208 ret.name = t.stringTagValue(true); 209 foreach (f; t.tags) { 210 switch (f.fullName) { 211 default: parseBuildSetting(f, ret.buildSettings, package_name); break; 212 case "platforms": ret.platforms ~= f.stringArrayTagValue; break; 213 } 214 } 215 } 216 217 private Tag toSDL(in ref ConfigurationInfo config) 218 { 219 auto ret = new Tag(null, "configuration", [Value(config.name)]); 220 if (config.platforms.length) ret.add(new Tag(null, "platforms", config.platforms[].map!(p => Value(p)).array)); 221 ret.add(config.buildSettings.toSDL()); 222 return ret; 223 } 224 225 private Tag[] toSDL(in ref BuildSettingsTemplate bs) 226 { 227 Tag[] ret; 228 void add(string name, string value, string namespace = null) { ret ~= new Tag(namespace, name, [Value(value)]); } 229 void adda(string name, string suffix, in string[] values, string namespace = null) { 230 ret ~= new Tag(namespace, name, values[].map!(v => Value(v)).array, 231 suffix.length ? [new Attribute(null, "platform", Value(suffix[1 .. $]))] : null); 232 } 233 234 string[] toNameArray(T, U)(U bits) if(is(T == enum)) { 235 string[] ret; 236 foreach (m; __traits(allMembers, T)) 237 if (bits & __traits(getMember, T, m)) 238 ret ~= m; 239 return ret; 240 } 241 242 foreach (pack, d; bs.dependencies) { 243 Attribute[] attribs; 244 if (!d.path.empty) attribs ~= new Attribute(null, "path", Value(d.path.toString())); 245 else attribs ~= new Attribute(null, "version", Value(d.versionSpec)); 246 if (d.optional) attribs ~= new Attribute(null, "optional", Value(true)); 247 ret ~= new Tag(null, "dependency", [Value(pack)], attribs); 248 } 249 if (bs.systemDependencies !is null) add("systemDependencies", bs.systemDependencies); 250 if (bs.targetType != TargetType.autodetect) add("targetType", bs.targetType.to!string()); 251 if (bs.targetPath.length) add("targetPath", bs.targetPath); 252 if (bs.targetName.length) add("targetName", bs.targetName); 253 if (bs.workingDirectory.length) add("workingDirectory", bs.workingDirectory); 254 if (bs.mainSourceFile.length) add("mainSourceFile", bs.mainSourceFile); 255 foreach (pack, conf; bs.subConfigurations) ret ~= new Tag(null, "subConfiguration", [Value(pack), Value(conf)]); 256 foreach (suffix, arr; bs.dflags) adda("dflags", suffix, arr); 257 foreach (suffix, arr; bs.lflags) adda("lflags", suffix, arr); 258 foreach (suffix, arr; bs.libs) adda("libs", suffix, arr); 259 foreach (suffix, arr; bs.sourceFiles) adda("sourceFiles", suffix, arr); 260 foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr); 261 foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr); 262 foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr); 263 foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr); 264 foreach (suffix, arr; bs.versions) adda("versions", suffix, arr); 265 foreach (suffix, arr; bs.debugVersions) adda("debugVersions", suffix, arr); 266 foreach (suffix, arr; bs.versionFilters) adda("versionFilters", suffix, arr, "x"); 267 foreach (suffix, arr; bs.debugVersionFilters) adda("debugVersionFilters", suffix, arr, "x"); 268 foreach (suffix, arr; bs.importPaths) adda("importPaths", suffix, arr); 269 foreach (suffix, arr; bs.stringImportPaths) adda("stringImportPaths", suffix, arr); 270 foreach (suffix, arr; bs.preGenerateCommands) adda("preGenerateCommands", suffix, arr); 271 foreach (suffix, arr; bs.postGenerateCommands) adda("postGenerateCommands", suffix, arr); 272 foreach (suffix, arr; bs.preBuildCommands) adda("preBuildCommands", suffix, arr); 273 foreach (suffix, arr; bs.postBuildCommands) adda("postBuildCommands", suffix, arr); 274 foreach (suffix, arr; bs.preRunCommands) adda("preRunCommands", suffix, arr); 275 foreach (suffix, arr; bs.postRunCommands) adda("postRunCommands", suffix, arr); 276 foreach (suffix, bits; bs.buildRequirements) adda("buildRequirements", suffix, toNameArray!BuildRequirement(bits)); 277 foreach (suffix, bits; bs.buildOptions) adda("buildOptions", suffix, toNameArray!BuildOption(bits)); 278 return ret; 279 } 280 281 private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag) 282 { 283 foreach (attr; tag.attributes) 284 tr.addRequirement(attr.name, attr.value.get!string); 285 } 286 287 private Tag toSDL(const ref ToolchainRequirements tr) 288 { 289 Attribute[] attrs; 290 if (tr.dub != Dependency.any) attrs ~= new Attribute("dub", Value(tr.dub.toString())); 291 if (tr.frontend != Dependency.any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString())); 292 if (tr.dmd != Dependency.any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString())); 293 if (tr.ldc != Dependency.any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString())); 294 if (tr.gdc != Dependency.any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString())); 295 return new Tag(null, "toolchainRequirements", null, attrs); 296 } 297 298 private string expandPackageName(string name, string parent_name, Tag tag) 299 { 300 import std.algorithm : canFind; 301 import std..string : format; 302 if (name.startsWith(":")) { 303 enforceSDL(!parent_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", parent_name, name), tag); 304 return parent_name ~ name; 305 } else return name; 306 } 307 308 private string stringTagValue(Tag t, bool allow_child_tags = false) 309 { 310 import std..string : format; 311 enforceSDL(t.values.length > 0, format("Missing string value for '%s'.", t.fullName), t); 312 enforceSDL(t.values.length == 1, format("Expected only one value for '%s'.", t.fullName), t); 313 enforceSDL(t.values[0].peek!string !is null, format("Expected value of type string for '%s'.", t.fullName), t); 314 enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t); 315 // Q: should attributes be disallowed, or just ignored for forward compatibility reasons? 316 //enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t); 317 return t.values[0].get!string; 318 } 319 320 private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false) 321 { 322 import std..string : format; 323 enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t); 324 // Q: should attributes be disallowed, or just ignored for forward compatibility reasons? 325 //enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t); 326 327 string[] ret; 328 foreach (v; t.values) { 329 enforceSDL(t.values[0].peek!string !is null, format("Values for '%s' must be strings.", t.fullName), t); 330 ret ~= v.get!string; 331 } 332 return ret; 333 } 334 335 private void parsePlatformStringArray(Tag t, ref string[][string] dst) 336 { 337 string platform; 338 if ("platform" in t.attributes) 339 platform = "-" ~ t.attributes["platform"][0].value.get!string; 340 dst[platform] ~= t.values.map!(v => v.get!string).array; 341 } 342 343 private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst) 344 { 345 string platform; 346 if ("platform" in t.attributes) 347 platform = "-" ~ t.attributes["platform"][0].value.get!string; 348 foreach (v; t.values) { 349 if (platform !in dst) dst[platform] = Es.init; 350 dst[platform] |= v.get!string.to!E; 351 } 352 } 353 354 private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__) 355 { 356 import std..string : format; 357 if (!condition) { 358 throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line, message), file, line); 359 } 360 } 361 362 363 unittest { // test all possible fields 364 auto sdl = 365 `name "projectname"; 366 description "project description"; 367 homepage "http://example.com" 368 authors "author 1" "author 2" 369 authors "author 3" 370 copyright "copyright string" 371 license "license string" 372 version "1.0.0" 373 subPackage { 374 name "subpackage1" 375 } 376 subPackage { 377 name "subpackage2" 378 dependency "projectname:subpackage1" version="*" 379 } 380 subPackage "pathsp3" 381 configuration "config1" { 382 platforms "windows" "linux" 383 targetType "library" 384 } 385 configuration "config2" { 386 platforms "windows-x86" 387 targetType "executable" 388 } 389 buildType "debug" { 390 dflags "-g" "-debug" 391 } 392 buildType "release" { 393 dflags "-release" "-O" 394 } 395 toolchainRequirements dub="~>1.11.0" dmd="~>2.082" 396 x:ddoxFilterArgs "-arg1" "-arg2" 397 x:ddoxFilterArgs "-arg3" 398 x:ddoxTool "ddoxtool" 399 400 dependency ":subpackage1" optional=false path="." 401 dependency "somedep" version="1.0.0" optional=true 402 systemDependencies "system dependencies" 403 targetType "executable" 404 targetName "target name" 405 targetPath "target path" 406 workingDirectory "working directory" 407 subConfiguration ":subpackage2" "library" 408 buildRequirements "allowWarnings" "silenceDeprecations" 409 buildOptions "verbose" "ignoreUnknownPragmas" 410 libs "lib1" "lib2" 411 libs "lib3" 412 sourceFiles "source1" "source2" 413 sourceFiles "source3" 414 sourcePaths "sourcepath1" "sourcepath2" 415 sourcePaths "sourcepath3" 416 excludedSourceFiles "excluded1" "excluded2" 417 excludedSourceFiles "excluded3" 418 mainSourceFile "main source" 419 copyFiles "copy1" "copy2" 420 copyFiles "copy3" 421 extraDependencyFiles "extradepfile1" "extradepfile2" 422 extraDependencyFiles "extradepfile3" 423 versions "version1" "version2" 424 versions "version3" 425 debugVersions "debug1" "debug2" 426 debugVersions "debug3" 427 x:versionFilters "version1" "version2" 428 x:versionFilters "version3" 429 x:versionFilters 430 x:debugVersionFilters "debug1" "debug2" 431 x:debugVersionFilters "debug3" 432 x:debugVersionFilters 433 importPaths "import1" "import2" 434 importPaths "import3" 435 stringImportPaths "string1" "string2" 436 stringImportPaths "string3" 437 preGenerateCommands "preg1" "preg2" 438 preGenerateCommands "preg3" 439 postGenerateCommands "postg1" "postg2" 440 postGenerateCommands "postg3" 441 preBuildCommands "preb1" "preb2" 442 preBuildCommands "preb3" 443 postBuildCommands "postb1" "postb2" 444 postBuildCommands "postb3" 445 preRunCommands "prer1" "prer2" 446 preRunCommands "prer3" 447 postRunCommands "postr1" "postr2" 448 postRunCommands "postr3" 449 dflags "df1" "df2" 450 dflags "df3" 451 lflags "lf1" "lf2" 452 lflags "lf3" 453 `; 454 PackageRecipe rec1; 455 parseSDL(rec1, sdl, null, "testfile"); 456 PackageRecipe rec; 457 parseSDL(rec, rec1.toSDL(), null); // verify that all fields are serialized properly 458 459 assert(rec.name == "projectname"); 460 assert(rec.description == "project description"); 461 assert(rec.homepage == "http://example.com"); 462 assert(rec.authors == ["author 1", "author 2", "author 3"]); 463 assert(rec.copyright == "copyright string"); 464 assert(rec.license == "license string"); 465 assert(rec.version_ == "1.0.0"); 466 assert(rec.subPackages.length == 3); 467 assert(rec.subPackages[0].path == ""); 468 assert(rec.subPackages[0].recipe.name == "subpackage1"); 469 assert(rec.subPackages[1].path == ""); 470 assert(rec.subPackages[1].recipe.name == "subpackage2"); 471 assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1); 472 assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies); 473 assert(rec.subPackages[2].path == "pathsp3"); 474 assert(rec.configurations.length == 2); 475 assert(rec.configurations[0].name == "config1"); 476 assert(rec.configurations[0].platforms == ["windows", "linux"]); 477 assert(rec.configurations[0].buildSettings.targetType == TargetType.library); 478 assert(rec.configurations[1].name == "config2"); 479 assert(rec.configurations[1].platforms == ["windows-x86"]); 480 assert(rec.configurations[1].buildSettings.targetType == TargetType.executable); 481 assert(rec.buildTypes.length == 2); 482 assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]); 483 assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]); 484 assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0")); 485 assert(rec.toolchainRequirements.frontend == Dependency.any); 486 assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0")); 487 assert(rec.toolchainRequirements.ldc == Dependency.any); 488 assert(rec.toolchainRequirements.gdc == Dependency.any); 489 assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string); 490 assert(rec.ddoxTool == "ddoxtool"); 491 assert(rec.buildSettings.dependencies.length == 2); 492 assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false); 493 assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath(".")); 494 assert(rec.buildSettings.dependencies["somedep"].versionSpec == "1.0.0"); 495 assert(rec.buildSettings.dependencies["somedep"].optional == true); 496 assert(rec.buildSettings.dependencies["somedep"].path.empty); 497 assert(rec.buildSettings.systemDependencies == "system dependencies"); 498 assert(rec.buildSettings.targetType == TargetType.executable); 499 assert(rec.buildSettings.targetName == "target name"); 500 assert(rec.buildSettings.targetPath == "target path"); 501 assert(rec.buildSettings.workingDirectory == "working directory"); 502 assert(rec.buildSettings.subConfigurations.length == 1); 503 assert(rec.buildSettings.subConfigurations["projectname:subpackage2"] == "library"); 504 assert(rec.buildSettings.buildRequirements == ["": cast(BuildRequirements)(BuildRequirement.allowWarnings | BuildRequirement.silenceDeprecations)]); 505 assert(rec.buildSettings.buildOptions == ["": cast(BuildOptions)(BuildOption.verbose | BuildOption.ignoreUnknownPragmas)]); 506 assert(rec.buildSettings.libs == ["": ["lib1", "lib2", "lib3"]]); 507 assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]); 508 assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]); 509 assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]); 510 assert(rec.buildSettings.mainSourceFile == "main source"); 511 assert(rec.buildSettings.copyFiles == ["": ["copy1", "copy2", "copy3"]]); 512 assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]); 513 assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]); 514 assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]); 515 assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]); 516 assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]); 517 assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]); 518 assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]); 519 assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]); 520 assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]); 521 assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]); 522 assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]); 523 assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]); 524 assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]); 525 assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]); 526 assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]); 527 } 528 529 unittest { // test platform identifiers 530 auto sdl = 531 `name "testproject" 532 dflags "-a" "-b" platform="windows-x86" 533 dflags "-c" platform="windows-x86" 534 dflags "-e" "-f" 535 dflags "-g" 536 dflags "-h" "-i" platform="linux" 537 dflags "-j" platform="linux" 538 `; 539 PackageRecipe rec; 540 parseSDL(rec, sdl, null, "testfile"); 541 assert(rec.buildSettings.dflags.length == 3); 542 assert(rec.buildSettings.dflags["-windows-x86"] == ["-a", "-b", "-c"]); 543 assert(rec.buildSettings.dflags[""] == ["-e", "-f", "-g"]); 544 assert(rec.buildSettings.dflags["-linux"] == ["-h", "-i", "-j"]); 545 } 546 547 unittest { // test for missing name field 548 import std.exception; 549 auto sdl = `description "missing name"`; 550 PackageRecipe rec; 551 assertThrown(parseSDL(rec, sdl, null, "testfile")); 552 } 553 554 unittest { // test single value fields 555 import std.exception; 556 PackageRecipe rec; 557 assertThrown!Exception(parseSDL(rec, `name "hello" "world"`, null, "testfile")); 558 assertThrown!Exception(parseSDL(rec, `name`, null, "testfile")); 559 assertThrown!Exception(parseSDL(rec, `name 10`, null, "testfile")); 560 assertThrown!Exception(parseSDL(rec, 561 `name "hello" { 562 world 563 }`, null, "testfile")); 564 assertThrown!Exception(parseSDL(rec, 565 `name "" 566 versions "hello" 10` 567 , null, "testfile")); 568 } 569 570 unittest { // test basic serialization 571 PackageRecipe p; 572 p.name = "test"; 573 p.authors = ["foo", "bar"]; 574 p.buildSettings.dflags["-windows"] = ["-a"]; 575 p.buildSettings.lflags[""] = ["-b", "-c"]; 576 auto sdl = toSDL(p).toSDLDocument(); 577 assert(sdl == 578 `name "test" 579 authors "foo" "bar" 580 dflags "-a" platform="windows" 581 lflags "-b" "-c" 582 `); 583 } 584 585 unittest { 586 auto sdl = "name \"test\"\nsourcePaths"; 587 PackageRecipe rec; 588 parseSDL(rec, sdl, null, "testfile"); 589 assert("" in rec.buildSettings.sourcePaths); 590 }