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