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.dyaml.stdsumtype; 13 import dub.internal.logging; 14 import dub.internal.sdlang; 15 import dub.internal.vibecompat.inet.path; 16 import dub.recipe.packagerecipe; 17 18 import std.algorithm : map; 19 import std.array : array; 20 import std.conv; 21 import std.string : startsWith, format; 22 23 deprecated("Use `parseSDL(PackageRecipe, string, PackageName, string)` instead") 24 void parseSDL(ref PackageRecipe recipe, string sdl, string parent_name, string filename) 25 { 26 parseSDL(recipe, parseSource(sdl, filename), PackageName(parent_name)); 27 } 28 29 deprecated("Use `parseSDL(PackageRecipe, Tag, PackageName)` instead") 30 void parseSDL(ref PackageRecipe recipe, Tag sdl, string parent_name) 31 { 32 parseSDL(recipe, sdl, PackageName(parent_name)); 33 } 34 35 void parseSDL(ref PackageRecipe recipe, string sdl, in PackageName parent, 36 string filename) 37 { 38 parseSDL(recipe, parseSource(sdl, filename), parent); 39 } 40 41 void parseSDL(ref PackageRecipe recipe, Tag sdl, in PackageName parent = PackageName.init) 42 { 43 Tag[] subpacks; 44 Tag[] configs; 45 46 // parse top-level fields 47 foreach (n; sdl.all.tags) { 48 enforceSDL(n.name.length > 0, "Anonymous tags are not allowed at the root level.", n); 49 switch (n.fullName) { 50 default: break; 51 case "name": recipe.name = n.stringTagValue; break; 52 case "version": recipe.version_ = n.stringTagValue; break; 53 case "description": recipe.description = n.stringTagValue; break; 54 case "homepage": recipe.homepage = n.stringTagValue; break; 55 case "authors": recipe.authors ~= n.stringArrayTagValue; break; 56 case "copyright": recipe.copyright = n.stringTagValue; break; 57 case "license": recipe.license = n.stringTagValue; break; 58 case "subPackage": subpacks ~= n; break; 59 case "configuration": configs ~= n; break; 60 case "buildType": 61 auto name = n.stringTagValue(true); 62 BuildSettingsTemplate bt; 63 parseBuildSettings(n, bt, parent); 64 recipe.buildTypes[name] = bt; 65 break; 66 case "toolchainRequirements": 67 parseToolchainRequirements(recipe.toolchainRequirements, n); 68 break; 69 case "x:ddoxFilterArgs": recipe.ddoxFilterArgs ~= n.stringArrayTagValue; break; 70 case "x:ddoxTool": recipe.ddoxTool = n.stringTagValue; break; 71 } 72 } 73 74 enforceSDL(recipe.name.length > 0, "The package \"name\" field is missing or empty.", sdl); 75 const full_name = parent.toString().length 76 ? PackageName(parent.toString() ~ ":" ~ recipe.name) 77 : PackageName(recipe.name); 78 79 // parse general build settings 80 parseBuildSettings(sdl, recipe.buildSettings, full_name); 81 82 // parse configurations 83 recipe.configurations.length = configs.length; 84 foreach (i, n; configs) { 85 parseConfiguration(n, recipe.configurations[i], full_name); 86 } 87 88 // finally parse all sub packages 89 recipe.subPackages.length = subpacks.length; 90 foreach (i, n; subpacks) { 91 if (n.values.length) { 92 recipe.subPackages[i].path = n.stringTagValue; 93 } else { 94 enforceSDL(n.attributes.length == 0, "No attributes allowed for inline sub package definitions.", n); 95 parseSDL(recipe.subPackages[i].recipe, n, full_name); 96 } 97 } 98 } 99 100 Tag toSDL(const scope ref PackageRecipe recipe) 101 { 102 Tag ret = new Tag; 103 void add(T)(string field, T value) { ret.add(new Tag(null, field, [Value(value)])); } 104 add("name", recipe.name); 105 if (recipe.version_.length) add("version", recipe.version_); 106 if (recipe.description.length) add("description", recipe.description); 107 if (recipe.homepage.length) add("homepage", recipe.homepage); 108 if (recipe.authors.length) ret.add(new Tag(null, "authors", recipe.authors.map!(a => Value(a)).array)); 109 if (recipe.copyright.length) add("copyright", recipe.copyright); 110 if (recipe.license.length) add("license", recipe.license); 111 foreach (name, settings; recipe.buildTypes) { 112 auto t = new Tag(null, "buildType", [Value(name)]); 113 t.add(settings.toSDL()); 114 ret.add(t); 115 } 116 if (!recipe.toolchainRequirements.empty) { 117 ret.add(toSDL(recipe.toolchainRequirements)); 118 } 119 if (recipe.ddoxFilterArgs.length) 120 ret.add(new Tag("x", "ddoxFilterArgs", recipe.ddoxFilterArgs.map!(a => Value(a)).array)); 121 if (recipe.ddoxTool.length) ret.add(new Tag("x", "ddoxTool", [Value(recipe.ddoxTool)])); 122 ret.add(recipe.buildSettings.toSDL()); 123 foreach(config; recipe.configurations) 124 ret.add(config.toSDL()); 125 foreach (i, subPackage; recipe.subPackages) { 126 if (subPackage.path !is null) { 127 add("subPackage", subPackage.path); 128 } else { 129 auto t = subPackage.recipe.toSDL(); 130 t.name = "subPackage"; 131 ret.add(t); 132 } 133 } 134 return ret; 135 } 136 137 private void parseBuildSettings(Tag settings, ref BuildSettingsTemplate bs, 138 in PackageName name) 139 { 140 foreach (setting; settings.all.tags) 141 parseBuildSetting(setting, bs, name); 142 } 143 144 private void parseBuildSetting(Tag setting, ref BuildSettingsTemplate bs, 145 in PackageName name) 146 { 147 switch (setting.fullName) { 148 default: break; 149 case "dependency": parseDependency(setting, bs, name); break; 150 case "systemDependencies": bs.systemDependencies = setting.stringTagValue; break; 151 case "targetType": bs.targetType = setting.stringTagValue.to!TargetType; break; 152 case "targetName": bs.targetName = setting.stringTagValue; break; 153 case "targetPath": bs.targetPath = setting.stringTagValue; break; 154 case "workingDirectory": bs.workingDirectory = setting.stringTagValue; break; 155 case "subConfiguration": 156 auto args = setting.stringArrayTagValue; 157 enforceSDL(args.length == 2, "Expecting package and configuration names as arguments.", setting); 158 bs.subConfigurations[expandPackageName(args[0], name, setting)] = args[1]; 159 break; 160 case "dflags": setting.parsePlatformStringArray(bs.dflags); break; 161 case "lflags": setting.parsePlatformStringArray(bs.lflags); break; 162 case "libs": setting.parsePlatformStringArray(bs.libs); break; 163 case "sourceFiles": setting.parsePlatformStringArray(bs.sourceFiles); break; 164 case "sourcePaths": setting.parsePlatformStringArray(bs.sourcePaths); break; 165 case "cSourcePaths": setting.parsePlatformStringArray(bs.cSourcePaths); break; 166 case "excludedSourceFiles": setting.parsePlatformStringArray(bs.excludedSourceFiles); break; 167 case "mainSourceFile": bs.mainSourceFile = setting.stringTagValue; break; 168 case "injectSourceFiles": setting.parsePlatformStringArray(bs.injectSourceFiles); break; 169 case "copyFiles": setting.parsePlatformStringArray(bs.copyFiles); break; 170 case "extraDependencyFiles": setting.parsePlatformStringArray(bs.extraDependencyFiles); break; 171 case "versions": setting.parsePlatformStringArray(bs.versions); break; 172 case "debugVersions": setting.parsePlatformStringArray(bs.debugVersions); break; 173 case "x:versionFilters": setting.parsePlatformStringArray(bs.versionFilters); break; 174 case "x:debugVersionFilters": setting.parsePlatformStringArray(bs.debugVersionFilters); break; 175 case "importPaths": setting.parsePlatformStringArray(bs.importPaths); break; 176 case "cImportPaths": setting.parsePlatformStringArray(bs.cImportPaths); break; 177 case "stringImportPaths": setting.parsePlatformStringArray(bs.stringImportPaths); break; 178 case "preGenerateCommands": setting.parsePlatformStringArray(bs.preGenerateCommands); break; 179 case "postGenerateCommands": setting.parsePlatformStringArray(bs.postGenerateCommands); break; 180 case "preBuildCommands": setting.parsePlatformStringArray(bs.preBuildCommands); break; 181 case "postBuildCommands": setting.parsePlatformStringArray(bs.postBuildCommands); break; 182 case "preRunCommands": setting.parsePlatformStringArray(bs.preRunCommands); break; 183 case "postRunCommands": setting.parsePlatformStringArray(bs.postRunCommands); break; 184 case "environments": setting.parsePlatformStringAA(bs.environments); break; 185 case "buildEnvironments": setting.parsePlatformStringAA(bs.buildEnvironments); break; 186 case "runEnvironments": setting.parsePlatformStringAA(bs.runEnvironments); break; 187 case "preGenerateEnvironments": setting.parsePlatformStringAA(bs.preGenerateEnvironments); break; 188 case "postGenerateEnvironments": setting.parsePlatformStringAA(bs.postGenerateEnvironments); break; 189 case "preBuildEnvironments": setting.parsePlatformStringAA(bs.preBuildEnvironments); break; 190 case "postBuildEnvironments": setting.parsePlatformStringAA(bs.postBuildEnvironments); break; 191 case "preRunEnvironments": setting.parsePlatformStringAA(bs.preRunEnvironments); break; 192 case "postRunEnvironments": setting.parsePlatformStringAA(bs.postRunEnvironments); break; 193 case "buildRequirements": setting.parsePlatformEnumArray!BuildRequirement(bs.buildRequirements); break; 194 case "buildOptions": setting.parsePlatformEnumArray!BuildOption(bs.buildOptions); break; 195 } 196 } 197 198 private void parseDependency(Tag t, ref BuildSettingsTemplate bs, in PackageName name) 199 { 200 enforceSDL(t.values.length != 0, "Missing dependency name.", t); 201 enforceSDL(t.values.length == 1, "Multiple dependency names.", t); 202 auto pkg = expandPackageName(t.values[0].expect!string(t), name, t); 203 enforceSDL(pkg !in bs.dependencies, "The dependency '"~pkg~"' is specified more than once.", t); 204 205 Dependency dep = Dependency.Any; 206 auto attrs = t.attributes; 207 208 if ("path" in attrs) { 209 dep = Dependency(NativePath(attrs["path"][0].value.expect!string(t, t.fullName ~ " path"))); 210 } else if ("repository" in attrs) { 211 enforceSDL("version" in attrs, "Missing version specification.", t); 212 213 dep = Dependency(Repository(attrs["repository"][0].value.expect!string(t, t.fullName ~ " repository"), 214 attrs["version"][0].value.expect!string(t, t.fullName ~ " version"))); 215 } else { 216 enforceSDL("version" in attrs, "Missing version specification.", t); 217 dep = Dependency(attrs["version"][0].value.expect!string(t, t.fullName ~ " version")); 218 } 219 220 if ("optional" in attrs) 221 dep.optional = attrs["optional"][0].value.expect!bool(t, t.fullName ~ " optional"); 222 223 if ("default" in attrs) 224 dep.default_ = attrs["default"][0].value.expect!bool(t, t.fullName ~ " default"); 225 226 bs.dependencies[pkg] = dep; 227 228 BuildSettingsTemplate dbs; 229 parseBuildSettings(t, bs.dependencies[pkg].settings, name); 230 } 231 232 private void parseConfiguration(Tag t, ref ConfigurationInfo ret, in PackageName name) 233 { 234 ret.name = t.stringTagValue(true); 235 foreach (f; t.tags) { 236 switch (f.fullName) { 237 default: parseBuildSetting(f, ret.buildSettings, name); break; 238 case "platforms": ret.platforms ~= f.stringArrayTagValue; break; 239 } 240 } 241 } 242 243 private Tag toSDL(const scope ref ConfigurationInfo config) 244 { 245 auto ret = new Tag(null, "configuration", [Value(config.name)]); 246 if (config.platforms.length) ret.add(new Tag(null, "platforms", config.platforms[].map!(p => Value(p)).array)); 247 ret.add(config.buildSettings.toSDL()); 248 return ret; 249 } 250 251 private Tag[] toSDL(const scope ref BuildSettingsTemplate bs) 252 { 253 Tag[] ret; 254 void add(string name, string value, string namespace = null) { ret ~= new Tag(namespace, name, [Value(value)]); } 255 void adda(string name, string suffix, in string[] values, string namespace = null) { 256 ret ~= new Tag(namespace, name, values[].map!(v => Value(v)).array, 257 suffix.length ? [new Attribute(null, "platform", Value(suffix))] : null); 258 } 259 void addaa(string name, string suffix, in string[string] values, string namespace = null) { 260 foreach (k, v; values) { 261 ret ~= new Tag(namespace, name, [Value(k), Value(v)], 262 suffix.length ? [new Attribute(null, "platform", Value(suffix))] : null); 263 } 264 } 265 266 string[] toNameArray(T, U)(U bits) if(is(T == enum)) { 267 string[] ret; 268 foreach (m; __traits(allMembers, T)) 269 if (bits & __traits(getMember, T, m)) 270 ret ~= m; 271 return ret; 272 } 273 274 foreach (pack, d; bs.dependencies) { 275 Attribute[] attribs; 276 d.visit!( 277 (const Repository r) { 278 attribs ~= new Attribute(null, "repository", Value(r.toString())); 279 attribs ~= new Attribute(null, "version", Value(r.ref_)); 280 }, 281 (const NativePath p) { 282 attribs ~= new Attribute(null, "path", Value(p.toString())); 283 }, 284 (const VersionRange v) { 285 attribs ~= new Attribute(null, "version", Value(v.toString())); 286 }, 287 ); 288 if (d.optional) attribs ~= new Attribute(null, "optional", Value(true)); 289 auto t = new Tag(null, "dependency", [Value(pack)], attribs); 290 if (d.settings !is typeof(d.settings).init) 291 t.add(d.settings.toSDL()); 292 ret ~= t; 293 } 294 if (bs.systemDependencies !is null) add("systemDependencies", bs.systemDependencies); 295 if (bs.targetType != TargetType.autodetect) add("targetType", bs.targetType.to!string()); 296 if (bs.targetPath.length) add("targetPath", bs.targetPath); 297 if (bs.targetName.length) add("targetName", bs.targetName); 298 if (bs.workingDirectory.length) add("workingDirectory", bs.workingDirectory); 299 if (bs.mainSourceFile.length) add("mainSourceFile", bs.mainSourceFile); 300 foreach (pack, conf; bs.subConfigurations) ret ~= new Tag(null, "subConfiguration", [Value(pack), Value(conf)]); 301 foreach (suffix, arr; bs.dflags) adda("dflags", suffix, arr); 302 foreach (suffix, arr; bs.lflags) adda("lflags", suffix, arr); 303 foreach (suffix, arr; bs.libs) adda("libs", suffix, arr); 304 foreach (suffix, arr; bs.sourceFiles) adda("sourceFiles", suffix, arr); 305 foreach (suffix, arr; bs.sourcePaths) adda("sourcePaths", suffix, arr); 306 foreach (suffix, arr; bs.cSourcePaths) adda("cSourcePaths", suffix, arr); 307 foreach (suffix, arr; bs.excludedSourceFiles) adda("excludedSourceFiles", suffix, arr); 308 foreach (suffix, arr; bs.injectSourceFiles) adda("injectSourceFiles", suffix, arr); 309 foreach (suffix, arr; bs.copyFiles) adda("copyFiles", suffix, arr); 310 foreach (suffix, arr; bs.extraDependencyFiles) adda("extraDependencyFiles", suffix, arr); 311 foreach (suffix, arr; bs.versions) adda("versions", suffix, arr); 312 foreach (suffix, arr; bs.debugVersions) adda("debugVersions", suffix, arr); 313 foreach (suffix, arr; bs.versionFilters) adda("versionFilters", suffix, arr, "x"); 314 foreach (suffix, arr; bs.debugVersionFilters) adda("debugVersionFilters", suffix, arr, "x"); 315 foreach (suffix, arr; bs.importPaths) adda("importPaths", suffix, arr); 316 foreach (suffix, arr; bs.cImportPaths) adda("cImportPaths", suffix, arr); 317 foreach (suffix, arr; bs.stringImportPaths) adda("stringImportPaths", suffix, arr); 318 foreach (suffix, arr; bs.preGenerateCommands) adda("preGenerateCommands", suffix, arr); 319 foreach (suffix, arr; bs.postGenerateCommands) adda("postGenerateCommands", suffix, arr); 320 foreach (suffix, arr; bs.preBuildCommands) adda("preBuildCommands", suffix, arr); 321 foreach (suffix, arr; bs.postBuildCommands) adda("postBuildCommands", suffix, arr); 322 foreach (suffix, arr; bs.preRunCommands) adda("preRunCommands", suffix, arr); 323 foreach (suffix, arr; bs.postRunCommands) adda("postRunCommands", suffix, arr); 324 foreach (suffix, aa; bs.environments) addaa("environments", suffix, aa); 325 foreach (suffix, aa; bs.buildEnvironments) addaa("buildEnvironments", suffix, aa); 326 foreach (suffix, aa; bs.runEnvironments) addaa("runEnvironments", suffix, aa); 327 foreach (suffix, aa; bs.preGenerateEnvironments) addaa("preGenerateEnvironments", suffix, aa); 328 foreach (suffix, aa; bs.postGenerateEnvironments) addaa("postGenerateEnvironments", suffix, aa); 329 foreach (suffix, aa; bs.preBuildEnvironments) addaa("preBuildEnvironments", suffix, aa); 330 foreach (suffix, aa; bs.postBuildEnvironments) addaa("postBuildEnvironments", suffix, aa); 331 foreach (suffix, aa; bs.preRunEnvironments) addaa("preRunEnvironments", suffix, aa); 332 foreach (suffix, aa; bs.postRunEnvironments) addaa("postRunEnvironments", suffix, aa); 333 foreach (suffix, bits; bs.buildRequirements) adda("buildRequirements", suffix, toNameArray!BuildRequirement(bits)); 334 foreach (suffix, bits; bs.buildOptions) adda("buildOptions", suffix, toNameArray!BuildOption(bits)); 335 return ret; 336 } 337 338 private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag) 339 { 340 foreach (attr; tag.attributes) 341 tr.addRequirement(attr.name, attr.value.expect!string(tag)); 342 } 343 344 private Tag toSDL(const ref ToolchainRequirements tr) 345 { 346 Attribute[] attrs; 347 if (tr.dub != VersionRange.Any) attrs ~= new Attribute("dub", Value(tr.dub.toString())); 348 if (tr.frontend != VersionRange.Any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString())); 349 if (tr.dmd != VersionRange.Any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString())); 350 if (tr.ldc != VersionRange.Any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString())); 351 if (tr.gdc != VersionRange.Any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString())); 352 return new Tag(null, "toolchainRequirements", null, attrs); 353 } 354 355 private string expandPackageName(string name, in PackageName parent, Tag tag) 356 { 357 import std.algorithm : canFind; 358 if (!name.startsWith(":")) 359 return name; 360 enforceSDL(!parent.sub.length, "Short-hand packages syntax not " ~ 361 "allowed within sub packages: %s -> %s".format(parent, name), tag); 362 return parent.toString() ~ name; 363 } 364 365 private string stringTagValue(Tag t, bool allow_child_tags = false) 366 { 367 enforceSDL(t.values.length > 0, format("Missing string value for '%s'.", t.fullName), t); 368 enforceSDL(t.values.length == 1, format("Expected only one value for '%s'.", t.fullName), t); 369 enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t); 370 // Q: should attributes be disallowed, or just ignored for forward compatibility reasons? 371 //enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t); 372 return t.values[0].expect!string(t); 373 } 374 375 private T expect(T)( 376 Value value, 377 Tag errorInfo, 378 string customFieldName = null, 379 string file = __FILE__, 380 int line = __LINE__ 381 ) 382 { 383 return value.match!( 384 (T v) => v, 385 (fallback) 386 { 387 enforceSDL(false, format("Expected value of type " ~ T.stringof ~ " for '%s', but got %s.", 388 customFieldName.length ? customFieldName : errorInfo.fullName, 389 typeof(fallback).stringof), 390 errorInfo, file, line); 391 return T.init; 392 } 393 ); 394 } 395 396 private string[] stringArrayTagValue(Tag t, bool allow_child_tags = false) 397 { 398 enforceSDL(allow_child_tags || t.tags.length == 0, format("No child tags allowed for '%s'.", t.fullName), t); 399 // Q: should attributes be disallowed, or just ignored for forward compatibility reasons? 400 //enforceSDL(t.attributes.length == 0, format("No attributes allowed for '%s'.", t.fullName), t); 401 402 string[] ret; 403 foreach (i, v; t.values) { 404 ret ~= v.expect!string(t, text(t.fullName, "[", i, "]")); 405 } 406 return ret; 407 } 408 409 private string getPlatformSuffix(Tag t, string file = __FILE__, int line = __LINE__) 410 { 411 string platform; 412 if ("platform" in t.attributes) 413 platform = t.attributes["platform"][0].value.expect!string(t, t.fullName ~ " platform", file, line); 414 return platform; 415 } 416 417 private void parsePlatformStringArray(Tag t, ref string[][string] dst) 418 { 419 string platform = t.getPlatformSuffix; 420 dst[platform] ~= t.values.map!(v => v.expect!string(t)).array; 421 } 422 private void parsePlatformStringAA(Tag t, ref string[string][string] dst) 423 { 424 string platform = t.getPlatformSuffix; 425 enforceSDL(t.values.length == 2, format("Values for '%s' must be 2 required.", t.fullName), t); 426 dst[platform][t.values[0].expect!string(t)] = t.values[1].expect!string(t); 427 } 428 429 private void parsePlatformEnumArray(E, Es)(Tag t, ref Es[string] dst) 430 { 431 string platform = t.getPlatformSuffix; 432 foreach (v; t.values) { 433 if (platform !in dst) dst[platform] = Es.init; 434 dst[platform] |= v.expect!string(t).to!E; 435 } 436 } 437 438 private void enforceSDL(bool condition, lazy string message, Tag tag, string file = __FILE__, int line = __LINE__) 439 { 440 if (!condition) { 441 throw new Exception(format("%s(%s): Error: %s", tag.location.file, tag.location.line + 1, message), file, line); 442 } 443 } 444 445 // Just a wrapper around `parseSDL` for easier testing 446 version (unittest) private void parseSDLTest(ref PackageRecipe recipe, string sdl) 447 { 448 parseSDL(recipe, parseSource(sdl, "testfile"), PackageName.init); 449 } 450 451 unittest { // test all possible fields 452 auto sdl = 453 `name "projectname"; 454 description "project description"; 455 homepage "http://example.com" 456 authors "author 1" "author 2" 457 authors "author 3" 458 copyright "copyright string" 459 license "license string" 460 version "1.0.0" 461 subPackage { 462 name "subpackage1" 463 } 464 subPackage { 465 name "subpackage2" 466 dependency "projectname:subpackage1" version="*" 467 } 468 subPackage "pathsp3" 469 configuration "config1" { 470 platforms "windows" "linux" 471 targetType "library" 472 } 473 configuration "config2" { 474 platforms "windows-x86" 475 targetType "executable" 476 } 477 buildType "debug" { 478 dflags "-g" "-debug" 479 } 480 buildType "release" { 481 dflags "-release" "-O" 482 } 483 toolchainRequirements dub="~>1.11.0" dmd="~>2.082" 484 x:ddoxFilterArgs "-arg1" "-arg2" 485 x:ddoxFilterArgs "-arg3" 486 x:ddoxTool "ddoxtool" 487 488 dependency ":subpackage1" optional=false path="." { 489 dflags "-g" "-debug" 490 } 491 dependency "somedep" version="1.0.0" optional=true 492 systemDependencies "system dependencies" 493 targetType "executable" 494 targetName "target name" 495 targetPath "target path" 496 workingDirectory "working directory" 497 subConfiguration ":subpackage2" "library" 498 buildRequirements "allowWarnings" "silenceDeprecations" 499 buildOptions "verbose" "ignoreUnknownPragmas" 500 libs "lib1" "lib2" 501 libs "lib3" 502 sourceFiles "source1" "source2" 503 sourceFiles "source3" 504 sourcePaths "sourcepath1" "sourcepath2" 505 sourcePaths "sourcepath3" 506 cSourcePaths "csourcepath1" "csourcepath2" 507 cSourcePaths "csourcepath3" 508 excludedSourceFiles "excluded1" "excluded2" 509 excludedSourceFiles "excluded3" 510 mainSourceFile "main source" 511 injectSourceFiles "finalbinarysourcefile.d" "extrafile" 512 copyFiles "copy1" "copy2" 513 copyFiles "copy3" 514 extraDependencyFiles "extradepfile1" "extradepfile2" 515 extraDependencyFiles "extradepfile3" 516 versions "version1" "version2" 517 versions "version3" 518 debugVersions "debug1" "debug2" 519 debugVersions "debug3" 520 x:versionFilters "version1" "version2" 521 x:versionFilters "version3" 522 x:versionFilters 523 x:debugVersionFilters "debug1" "debug2" 524 x:debugVersionFilters "debug3" 525 x:debugVersionFilters 526 importPaths "import1" "import2" 527 importPaths "import3" 528 cImportPaths "cimport1" "cimport2" 529 cImportPaths "cimport3" 530 stringImportPaths "string1" "string2" 531 stringImportPaths "string3" 532 preGenerateCommands "preg1" "preg2" 533 preGenerateCommands "preg3" 534 postGenerateCommands "postg1" "postg2" 535 postGenerateCommands "postg3" 536 preBuildCommands "preb1" "preb2" 537 preBuildCommands "preb3" 538 postBuildCommands "postb1" "postb2" 539 postBuildCommands "postb3" 540 preRunCommands "prer1" "prer2" 541 preRunCommands "prer3" 542 postRunCommands "postr1" "postr2" 543 postRunCommands "postr3" 544 environments "Var1" "env" 545 buildEnvironments "Var2" "buildEnv" 546 runEnvironments "Var3" "runEnv" 547 preGenerateEnvironments "Var4" "preGenEnv" 548 postGenerateEnvironments "Var5" "postGenEnv" 549 preBuildEnvironments "Var6" "preBuildEnv" 550 postBuildEnvironments "Var7" "postBuildEnv" 551 preRunEnvironments "Var8" "preRunEnv" 552 postRunEnvironments "Var9" "postRunEnv" 553 dflags "df1" "df2" 554 dflags "df3" 555 lflags "lf1" "lf2" 556 lflags "lf3" 557 `; 558 PackageRecipe rec1; 559 parseSDLTest(rec1, sdl); 560 PackageRecipe rec; 561 parseSDL(rec, rec1.toSDL()); // verify that all fields are serialized properly 562 563 assert(rec.name == "projectname"); 564 assert(rec.description == "project description"); 565 assert(rec.homepage == "http://example.com"); 566 assert(rec.authors == ["author 1", "author 2", "author 3"]); 567 assert(rec.copyright == "copyright string"); 568 assert(rec.license == "license string"); 569 assert(rec.version_ == "1.0.0"); 570 assert(rec.subPackages.length == 3); 571 assert(rec.subPackages[0].path == ""); 572 assert(rec.subPackages[0].recipe.name == "subpackage1"); 573 assert(rec.subPackages[1].path == ""); 574 assert(rec.subPackages[1].recipe.name == "subpackage2"); 575 assert(rec.subPackages[1].recipe.buildSettings.dependencies.length == 1); 576 assert("projectname:subpackage1" in rec.subPackages[1].recipe.buildSettings.dependencies); 577 assert(rec.subPackages[2].path == "pathsp3"); 578 assert(rec.configurations.length == 2); 579 assert(rec.configurations[0].name == "config1"); 580 assert(rec.configurations[0].platforms == ["windows", "linux"]); 581 assert(rec.configurations[0].buildSettings.targetType == TargetType.library); 582 assert(rec.configurations[1].name == "config2"); 583 assert(rec.configurations[1].platforms == ["windows-x86"]); 584 assert(rec.configurations[1].buildSettings.targetType == TargetType.executable); 585 assert(rec.buildTypes.length == 2); 586 assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]); 587 assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]); 588 assert(rec.toolchainRequirements.dub == VersionRange.fromString("~>1.11.0")); 589 assert(rec.toolchainRequirements.frontend == VersionRange.Any); 590 assert(rec.toolchainRequirements.dmd == VersionRange.fromString("~>2.82.0")); 591 assert(rec.toolchainRequirements.ldc == VersionRange.Any); 592 assert(rec.toolchainRequirements.gdc == VersionRange.Any); 593 assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string); 594 assert(rec.ddoxTool == "ddoxtool"); 595 assert(rec.buildSettings.dependencies.length == 2); 596 assert(rec.buildSettings.dependencies["projectname:subpackage1"].optional == false); 597 assert(rec.buildSettings.dependencies["projectname:subpackage1"].path == NativePath(".")); 598 assert(rec.buildSettings.dependencies["projectname:subpackage1"].settings.dflags == ["":["-g", "-debug"]]); 599 assert(rec.buildSettings.dependencies["somedep"].version_.toString() == "1.0.0"); 600 assert(rec.buildSettings.dependencies["somedep"].optional == true); 601 assert(rec.buildSettings.systemDependencies == "system dependencies"); 602 assert(rec.buildSettings.targetType == TargetType.executable); 603 assert(rec.buildSettings.targetName == "target name"); 604 assert(rec.buildSettings.targetPath == "target path"); 605 assert(rec.buildSettings.workingDirectory == "working directory"); 606 assert(rec.buildSettings.subConfigurations.length == 1); 607 assert(rec.buildSettings.subConfigurations["projectname:subpackage2"] == "library"); 608 assert(rec.buildSettings.buildRequirements == ["": cast(Flags!BuildRequirement)(BuildRequirement.allowWarnings | BuildRequirement.silenceDeprecations)]); 609 assert(rec.buildSettings.buildOptions == ["": cast(Flags!BuildOption)(BuildOption.verbose | BuildOption.ignoreUnknownPragmas)]); 610 assert(rec.buildSettings.libs == ["": ["lib1", "lib2", "lib3"]]); 611 assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]); 612 assert(rec.buildSettings.sourcePaths == ["": ["sourcepath1", "sourcepath2", "sourcepath3"]]); 613 assert(rec.buildSettings.cSourcePaths == ["": ["csourcepath1", "csourcepath2", "csourcepath3"]]); 614 assert(rec.buildSettings.excludedSourceFiles == ["": ["excluded1", "excluded2", "excluded3"]]); 615 assert(rec.buildSettings.mainSourceFile == "main source"); 616 assert(rec.buildSettings.sourceFiles == ["": ["source1", "source2", "source3"]]); 617 assert(rec.buildSettings.injectSourceFiles == ["": ["finalbinarysourcefile.d", "extrafile"]]); 618 assert(rec.buildSettings.extraDependencyFiles == ["": ["extradepfile1", "extradepfile2", "extradepfile3"]]); 619 assert(rec.buildSettings.versions == ["": ["version1", "version2", "version3"]]); 620 assert(rec.buildSettings.debugVersions == ["": ["debug1", "debug2", "debug3"]]); 621 assert(rec.buildSettings.versionFilters == ["": ["version1", "version2", "version3"]]); 622 assert(rec.buildSettings.debugVersionFilters == ["": ["debug1", "debug2", "debug3"]]); 623 assert(rec.buildSettings.importPaths == ["": ["import1", "import2", "import3"]]); 624 assert(rec.buildSettings.cImportPaths == ["": ["cimport1", "cimport2", "cimport3"]]); 625 assert(rec.buildSettings.stringImportPaths == ["": ["string1", "string2", "string3"]]); 626 assert(rec.buildSettings.preGenerateCommands == ["": ["preg1", "preg2", "preg3"]]); 627 assert(rec.buildSettings.postGenerateCommands == ["": ["postg1", "postg2", "postg3"]]); 628 assert(rec.buildSettings.preBuildCommands == ["": ["preb1", "preb2", "preb3"]]); 629 assert(rec.buildSettings.postBuildCommands == ["": ["postb1", "postb2", "postb3"]]); 630 assert(rec.buildSettings.preRunCommands == ["": ["prer1", "prer2", "prer3"]]); 631 assert(rec.buildSettings.postRunCommands == ["": ["postr1", "postr2", "postr3"]]); 632 assert(rec.buildSettings.environments == ["": ["Var1": "env"]]); 633 assert(rec.buildSettings.buildEnvironments == ["": ["Var2": "buildEnv"]]); 634 assert(rec.buildSettings.runEnvironments == ["": ["Var3": "runEnv"]]); 635 assert(rec.buildSettings.preGenerateEnvironments == ["": ["Var4": "preGenEnv"]]); 636 assert(rec.buildSettings.postGenerateEnvironments == ["": ["Var5": "postGenEnv"]]); 637 assert(rec.buildSettings.preBuildEnvironments == ["": ["Var6": "preBuildEnv"]]); 638 assert(rec.buildSettings.postBuildEnvironments == ["": ["Var7": "postBuildEnv"]]); 639 assert(rec.buildSettings.preRunEnvironments == ["": ["Var8": "preRunEnv"]]); 640 assert(rec.buildSettings.postRunEnvironments == ["": ["Var9": "postRunEnv"]]); 641 assert(rec.buildSettings.dflags == ["": ["df1", "df2", "df3"]]); 642 assert(rec.buildSettings.lflags == ["": ["lf1", "lf2", "lf3"]]); 643 } 644 645 unittest { // test platform identifiers 646 auto sdl = 647 `name "testproject" 648 dflags "-a" "-b" platform="windows-x86" 649 dflags "-c" platform="windows-x86" 650 dflags "-e" "-f" 651 dflags "-g" 652 dflags "-h" "-i" platform="linux" 653 dflags "-j" platform="linux" 654 `; 655 PackageRecipe rec; 656 parseSDLTest(rec, sdl); 657 assert(rec.buildSettings.dflags.length == 3); 658 assert(rec.buildSettings.dflags["windows-x86"] == ["-a", "-b", "-c"]); 659 assert(rec.buildSettings.dflags[""] == ["-e", "-f", "-g"]); 660 assert(rec.buildSettings.dflags["linux"] == ["-h", "-i", "-j"]); 661 } 662 663 unittest { // test for missing name field 664 import std.exception; 665 auto sdl = `description "missing name"`; 666 PackageRecipe rec; 667 assertThrown(parseSDLTest(rec, sdl)); 668 } 669 670 unittest { // test single value fields 671 import std.exception; 672 PackageRecipe rec; 673 assertThrown!Exception(parseSDLTest(rec, `name "hello" "world"`)); 674 assertThrown!Exception(parseSDLTest(rec, `name`)); 675 assertThrown!Exception(parseSDLTest(rec, `name 10`)); 676 assertThrown!Exception(parseSDLTest(rec, 677 `name "hello" { 678 world 679 }`)); 680 assertThrown!Exception(parseSDLTest(rec, 681 `name "" 682 versions "hello" 10`)); 683 } 684 685 unittest { // test basic serialization 686 PackageRecipe p; 687 p.name = "test"; 688 p.authors = ["foo", "bar"]; 689 p.buildSettings.dflags["windows"] = ["-a"]; 690 p.buildSettings.lflags[""] = ["-b", "-c"]; 691 auto sdl = toSDL(p).toSDLDocument(); 692 assert(sdl == 693 `name "test" 694 authors "foo" "bar" 695 dflags "-a" platform="windows" 696 lflags "-b" "-c" 697 `); 698 } 699 700 unittest { 701 auto sdl = "name \"test\"\nsourcePaths"; 702 PackageRecipe rec; 703 parseSDLTest(rec, sdl); 704 assert("" in rec.buildSettings.sourcePaths); 705 } 706 707 unittest { 708 auto sdl = "name \"test\"\ncSourcePaths"; 709 PackageRecipe rec; 710 parseSDLTest(rec, sdl); 711 assert("" in rec.buildSettings.cSourcePaths); 712 } 713 714 unittest { 715 auto sdl = 716 `name "test" 717 dependency "package" repository="git+https://some.url" version="12345678" 718 `; 719 PackageRecipe rec; 720 parseSDLTest(rec, sdl); 721 auto dependency = rec.buildSettings.dependencies["package"]; 722 assert(!dependency.repository.empty); 723 assert(dependency.repository.ref_ == "12345678"); 724 } 725 726 unittest { 727 PackageRecipe p; 728 p.name = "test"; 729 730 auto repository = Repository("git+https://some.url", "12345678"); 731 p.buildSettings.dependencies["package"] = Dependency(repository); 732 auto sdl = toSDL(p).toSDLDocument(); 733 assert(sdl == 734 `name "test" 735 dependency "package" repository="git+https://some.url" version="12345678" 736 `); 737 }