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