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