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