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