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 if ("path" in attrs) { 186 if ("version" in attrs) 187 logDiagnostic("Ignoring version specification (%s) for path based dependency %s", attrs["version"][0].value.get!string, attrs["path"][0].value.get!string); 188 dep.versionSpec = "*"; 189 dep.path = NativePath(attrs["path"][0].value.get!string); 190 } else { 191 enforceSDL("version" in attrs, "Missing version specification.", t); 192 dep.versionSpec = attrs["version"][0].value.get!string; 193 } 194 195 if ("optional" in attrs) 196 dep.optional = attrs["optional"][0].value.get!bool; 197 198 if ("default" in attrs) 199 dep.default_ = attrs["default"][0].value.get!bool; 200 201 bs.dependencies[pkg] = dep; 202 } 203 204 private void parseConfiguration(Tag t, ref ConfigurationInfo ret, string package_name) 205 { 206 ret.name = t.stringTagValue(true); 207 foreach (f; t.tags) { 208 switch (f.fullName) { 209 default: parseBuildSetting(f, ret.buildSettings, package_name); break; 210 case "platforms": ret.platforms ~= f.stringArrayTagValue; break; 211 } 212 } 213 } 214 215 private Tag toSDL(in ref ConfigurationInfo config) 216 { 217 auto ret = new Tag(null, "configuration", [Value(config.name)]); 218 if (config.platforms.length) ret.add(new Tag(null, "platforms", config.platforms[].map!(p => Value(p)).array)); 219 ret.add(config.buildSettings.toSDL()); 220 return ret; 221 } 222 223 private Tag[] toSDL(in ref BuildSettingsTemplate bs) 224 { 225 Tag[] ret; 226 void add(string name, string value, string namespace = null) { ret ~= new Tag(namespace, name, [Value(value)]); } 227 void adda(string name, string suffix, in string[] values, string namespace = null) { 228 ret ~= new Tag(namespace, name, values[].map!(v => Value(v)).array, 229 suffix.length ? [new Attribute(null, "platform", Value(suffix[1 .. $]))] : null); 230 } 231 232 string[] toNameArray(T, U)(U bits) if(is(T == enum)) { 233 string[] ret; 234 foreach (m; __traits(allMembers, T)) 235 if (bits & __traits(getMember, T, m)) 236 ret ~= m; 237 return ret; 238 } 239 240 foreach (pack, d; bs.dependencies) { 241 Attribute[] attribs; 242 if (!d.path.empty) attribs ~= new Attribute(null, "path", Value(d.path.toString())); 243 else attribs ~= new Attribute(null, "version", Value(d.versionSpec)); 244 if (d.optional) attribs ~= new Attribute(null, "optional", Value(true)); 245 ret ~= new Tag(null, "dependency", [Value(pack)], attribs); 246 } 247 if (bs.systemDependencies !is null) add("systemDependencies", bs.systemDependencies); 248 if (bs.targetType != TargetType.autodetect) add("targetType", bs.targetType.to!string()); 249 if (bs.targetPath.length) add("targetPath", bs.targetPath); 250 if (bs.targetName.length) add("targetName", bs.targetName); 251 if (bs.workingDirectory.length) add("workingDirectory", bs.workingDirectory); 252 if (bs.mainSourceFile.length) add("mainSourceFile", bs.mainSourceFile); 253 foreach (pack, conf; bs.subConfigurations) ret ~= new Tag(null, "subConfiguration", [Value(pack), Value(conf)]); 254 foreach (suffix, arr; bs.dflags) adda("dflags", suffix, arr); 255 foreach (suffix, arr; bs.lflags) adda("lflags", suffix, arr); 256 foreach (suffix, arr; bs.libs) adda("libs", suffix, arr); 257 foreach (suffix, arr; bs.sourceFiles) adda("sourceFiles", suffix, arr); 258 foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr); 259 foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr); 260 foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr); 261 foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr); 262 foreach (suffix, arr; bs.versions) adda("versions", suffix, arr); 263 foreach (suffix, arr; bs.debugVersions) adda("debugVersions", suffix, arr); 264 foreach (suffix, arr; bs.versionFilters) adda("versionFilters", suffix, arr, "x"); 265 foreach (suffix, arr; bs.debugVersionFilters) adda("debugVersionFilters", suffix, arr, "x"); 266 foreach (suffix, arr; bs.importPaths) adda("importPaths", suffix, arr); 267 foreach (suffix, arr; bs.stringImportPaths) adda("stringImportPaths", suffix, arr); 268 foreach (suffix, arr; bs.preGenerateCommands) adda("preGenerateCommands", suffix, arr); 269 foreach (suffix, arr; bs.postGenerateCommands) adda("postGenerateCommands", suffix, arr); 270 foreach (suffix, arr; bs.preBuildCommands) adda("preBuildCommands", suffix, arr); 271 foreach (suffix, arr; bs.postBuildCommands) adda("postBuildCommands", suffix, arr); 272 foreach (suffix, arr; bs.preRunCommands) adda("preRunCommands", suffix, arr); 273 foreach (suffix, arr; bs.postRunCommands) adda("postRunCommands", suffix, arr); 274 foreach (suffix, bits; bs.buildRequirements) adda("buildRequirements", suffix, toNameArray!BuildRequirement(bits)); 275 foreach (suffix, bits; bs.buildOptions) adda("buildOptions", suffix, toNameArray!BuildOption(bits)); 276 return ret; 277 } 278 279 private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag) 280 { 281 foreach (attr; tag.attributes) 282 tr.addRequirement(attr.name, attr.value.get!string); 283 } 284 285 private Tag toSDL(const ref ToolchainRequirements tr) 286 { 287 Attribute[] attrs; 288 if (tr.dub != Dependency.any) attrs ~= new Attribute("dub", Value(tr.dub.toString())); 289 if (tr.frontend != Dependency.any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString())); 290 if (tr.dmd != Dependency.any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString())); 291 if (tr.ldc != Dependency.any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString())); 292 if (tr.gdc != Dependency.any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString())); 293 return new Tag(null, "toolchainRequirements", null, attrs); 294 } 295 296 private string expandPackageName(string name, string parent_name, Tag tag) 297 { 298 import std.algorithm : canFind; 299 import std.string : format; 300 if (name.startsWith(":")) { 301 enforceSDL(!parent_name.canFind(':'), format("Short-hand packages syntax not allowed within sub packages: %s -> %s", parent_name, name), tag); 302 return parent_name ~ name; 303 } else return name; 304 } 305 306 private string stringTagValue(Tag t, bool allow_child_tags = false) 307 { 308 import std.string : format; 309 enforceSDL(t.values.length > 0, format("Missing string value for '%s'.", t.fullName), t); 310 enforceSDL(t.values.length == 1, format("Expected only one value for '%s'.", t.fullName), t); 311 enforceSDL(t.values[0].peek!string !is null, format("Expected value of type string for '%s'.", t.fullName), t); 312 enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t); 313 // Q: should attributes be disallowed, or just ignored for forward compatibility reasons? 314 //enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t); 315 return t.values[0].get!string; 316 } 317 318 private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false) 319 { 320 import std.string : format; 321 enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t); 322 // Q: should attributes be disallowed, or just ignored for forward compatibility reasons? 323 //enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t); 324 325 string[] ret; 326 foreach (v; t.values) { 327 enforceSDL(t.values[0].peek!string !is null, format("Values for '%s' must be strings.", t.fullName), t); 328 ret ~= v.get!string; 329 } 330 return ret; 331 } 332 333 private void parsePlatformStringArray(Tag t, ref string[][string] dst) 334 { 335 string platform; 336 if ("platform" in t.attributes) 337 platform = "-" ~ t.attributes["platform"][0].value.get!string; 338 dst[platform] ~= t.values.map!(v => v.get!string).array; 339 } 340 341 private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst) 342 { 343 string platform; 344 if ("platform" in t.attributes) 345 platform = "-" ~ t.attributes["platform"][0].value.get!string; 346 foreach (v; t.values) { 347 if (platform !in dst) dst[platform] = Es.init; 348 dst[platform] |= v.get!string.to!E; 349 } 350 } 351 352 private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__) 353 { 354 import std.string : format; 355 if (!condition) { 356 throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line + 1, message), file, line); 357 } 358 } 359 360 361 unittest { // test all possible fields 362 auto sdl = 363 `name "projectname"; 364 description "project description"; 365 homepage "http://example.com" 366 authors "author 1" "author 2" 367 authors "author 3" 368 copyright "copyright string" 369 license "license string" 370 version "1.0.0" 371 subPackage { 372 name "subpackage1" 373 } 374 subPackage { 375 name "subpackage2" 376 dependency "projectname:subpackage1" version="*" 377 } 378 subPackage "pathsp3" 379 configuration "config1" { 380 platforms "windows" "linux" 381 targetType "library" 382 } 383 configuration "config2" { 384 platforms "windows-x86" 385 targetType "executable" 386 } 387 buildType "debug" { 388 dflags "-g" "-debug" 389 } 390 buildType "release" { 391 dflags "-release" "-O" 392 } 393 toolchainRequirements dub="~>1.11.0" dmd="~>2.082" 394 x:ddoxFilterArgs "-arg1" "-arg2" 395 x:ddoxFilterArgs "-arg3" 396 x:ddoxTool "ddoxtool" 397 398 dependency ":subpackage1" optional=false path="." 399 dependency "somedep" version="1.0.0" optional=true 400 systemDependencies "system dependencies" 401 targetType "executable" 402 targetName "target name" 403 targetPath "target path" 404 workingDirectory "working directory" 405 subConfiguration ":subpackage2" "library" 406 buildRequirements "allowWarnings" "silenceDeprecations" 407 buildOptions "verbose" "ignoreUnknownPragmas" 408 libs "lib1" "lib2" 409 libs "lib3" 410 sourceFiles "source1" "source2" 411 sourceFiles "source3" 412 sourcePaths "sourcepath1" "sourcepath2" 413 sourcePaths "sourcepath3" 414 excludedSourceFiles "excluded1" "excluded2" 415 excludedSourceFiles "excluded3" 416 mainSourceFile "main source" 417 copyFiles "copy1" "copy2" 418 copyFiles "copy3" 419 extraDependencyFiles "extradepfile1" "extradepfile2" 420 extraDependencyFiles "extradepfile3" 421 versions "version1" "version2" 422 versions "version3" 423 debugVersions "debug1" "debug2" 424 debugVersions "debug3" 425 x:versionFilters "version1" "version2" 426 x:versionFilters "version3" 427 x:versionFilters 428 x:debugVersionFilters "debug1" "debug2" 429 x:debugVersionFilters "debug3" 430 x:debugVersionFilters 431 importPaths "import1" "import2" 432 importPaths "import3" 433 stringImportPaths "string1" "string2" 434 stringImportPaths "string3" 435 preGenerateCommands "preg1" "preg2" 436 preGenerateCommands "preg3" 437 postGenerateCommands "postg1" "postg2" 438 postGenerateCommands "postg3" 439 preBuildCommands "preb1" "preb2" 440 preBuildCommands "preb3" 441 postBuildCommands "postb1" "postb2" 442 postBuildCommands "postb3" 443 preRunCommands "prer1" "prer2" 444 preRunCommands "prer3" 445 postRunCommands "postr1" "postr2" 446 postRunCommands "postr3" 447 dflags "df1" "df2" 448 dflags "df3" 449 lflags "lf1" "lf2" 450 lflags "lf3" 451 `; 452 PackageRecipe rec1; 453 parseSDL(rec1, sdl, null, "testfile"); 454 PackageRecipe rec; 455 parseSDL(rec, rec1.toSDL(), null); // verify that all fields are serialized properly 456 457 assert(rec.name == "projectname"); 458 assert(rec.description == "project description"); 459 assert(rec.homepage == "http://example.com"); 460 assert(rec.authors == ["author 1", "author 2", "author 3"]); 461 assert(rec.copyright == "copyright string"); 462 assert(rec.license == "license string"); 463 assert(rec.version_ == "1.0.0"); 464 assert(rec.subPackages.length == 3); 465 assert(rec.subPackages[0].path == ""); 466 assert(rec.subPackages[0].recipe.name == "subpackage1"); 467 assert(rec.subPackages[1].path == ""); 468 assert(rec.subPackages[1].recipe.name == "subpackage2"); 469 assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1); 470 assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies); 471 assert(rec.subPackages[2].path == "pathsp3"); 472 assert(rec.configurations.length == 2); 473 assert(rec.configurations[0].name == "config1"); 474 assert(rec.configurations[0].platforms == ["windows", "linux"]); 475 assert(rec.configurations[0].buildSettings.targetType == TargetType.library); 476 assert(rec.configurations[1].name == "config2"); 477 assert(rec.configurations[1].platforms == ["windows-x86"]); 478 assert(rec.configurations[1].buildSettings.targetType == TargetType.executable); 479 assert(rec.buildTypes.length == 2); 480 assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]); 481 assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]); 482 assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0")); 483 assert(rec.toolchainRequirements.frontend == Dependency.any); 484 assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0")); 485 assert(rec.toolchainRequirements.ldc == Dependency.any); 486 assert(rec.toolchainRequirements.gdc == Dependency.any); 487 assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string); 488 assert(rec.ddoxTool == "ddoxtool"); 489 assert(rec.buildSettings.dependencies.length == 2); 490 assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false); 491 assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath(".")); 492 assert(rec.buildSettings.dependencies["somedep"].versionSpec == "1.0.0"); 493 assert(rec.buildSettings.dependencies["somedep"].optional == true); 494 assert(rec.buildSettings.dependencies["somedep"].path.empty); 495 assert(rec.buildSettings.systemDependencies == "system dependencies"); 496 assert(rec.buildSettings.targetType == TargetType.executable); 497 assert(rec.buildSettings.targetName == "target name"); 498 assert(rec.buildSettings.targetPath == "target path"); 499 assert(rec.buildSettings.workingDirectory == "working directory"); 500 assert(rec.buildSettings.subConfigurations.length == 1); 501 assert(rec.buildSettings.subConfigurations["projectname:subpackage2"] == "library"); 502 assert(rec.buildSettings.buildRequirements == ["": cast(BuildRequirements)(BuildRequirement.allowWarnings | BuildRequirement.silenceDeprecations)]); 503 assert(rec.buildSettings.buildOptions == ["": cast(BuildOptions)(BuildOption.verbose | BuildOption.ignoreUnknownPragmas)]); 504 assert(rec.buildSettings.libs == ["": ["lib1", "lib2", "lib3"]]); 505 assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]); 506 assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]); 507 assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]); 508 assert(rec.buildSettings.mainSourceFile == "main source"); 509 assert(rec.buildSettings.copyFiles == ["": ["copy1", "copy2", "copy3"]]); 510 assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]); 511 assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]); 512 assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]); 513 assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]); 514 assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]); 515 assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]); 516 assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]); 517 assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]); 518 assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]); 519 assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]); 520 assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]); 521 assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]); 522 assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]); 523 assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]); 524 assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]); 525 } 526 527 unittest { // test platform identifiers 528 auto sdl = 529 `name "testproject" 530 dflags "-a" "-b" platform="windows-x86" 531 dflags "-c" platform="windows-x86" 532 dflags "-e" "-f" 533 dflags "-g" 534 dflags "-h" "-i" platform="linux" 535 dflags "-j" platform="linux" 536 `; 537 PackageRecipe rec; 538 parseSDL(rec, sdl, null, "testfile"); 539 assert(rec.buildSettings.dflags.length == 3); 540 assert(rec.buildSettings.dflags["-windows-x86"] == ["-a", "-b", "-c"]); 541 assert(rec.buildSettings.dflags[""] == ["-e", "-f", "-g"]); 542 assert(rec.buildSettings.dflags["-linux"] == ["-h", "-i", "-j"]); 543 } 544 545 unittest { // test for missing name field 546 import std.exception; 547 auto sdl = `description "missing name"`; 548 PackageRecipe rec; 549 assertThrown(parseSDL(rec, sdl, null, "testfile")); 550 } 551 552 unittest { // test single value fields 553 import std.exception; 554 PackageRecipe rec; 555 assertThrown!Exception(parseSDL(rec, `name "hello" "world"`, null, "testfile")); 556 assertThrown!Exception(parseSDL(rec, `name`, null, "testfile")); 557 assertThrown!Exception(parseSDL(rec, `name 10`, null, "testfile")); 558 assertThrown!Exception(parseSDL(rec, 559 `name "hello" { 560 world 561 }`, null, "testfile")); 562 assertThrown!Exception(parseSDL(rec, 563 `name "" 564 versions "hello" 10` 565 , null, "testfile")); 566 } 567 568 unittest { // test basic serialization 569 PackageRecipe p; 570 p.name = "test"; 571 p.authors = ["foo", "bar"]; 572 p.buildSettings.dflags["-windows"] = ["-a"]; 573 p.buildSettings.lflags[""] = ["-b", "-c"]; 574 auto sdl = toSDL(p).toSDLDocument(); 575 assert(sdl == 576 `name "test" 577 authors "foo" "bar" 578 dflags "-a" platform="windows" 579 lflags "-b" "-c" 580 `); 581 } 582 583 unittest { 584 auto sdl = "name \"test\"\nsourcePaths"; 585 PackageRecipe rec; 586 parseSDL(rec, sdl, null, "testfile"); 587 assert("" in rec.buildSettings.sourcePaths); 588 }