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