1 /** 2 Abstract representation of a package description file. 3 4 Copyright: © 2012-2014 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, Matthias Dondorff 7 */ 8 module dub.recipe.packagerecipe; 9 10 import dub.compilers.compiler; 11 import dub.compilers.utils : warnOnSpecialCompilerFlags; 12 import dub.dependency; 13 import dub.internal.logging; 14 15 import dub.internal.vibecompat.core.file; 16 import dub.internal.vibecompat.inet.path; 17 18 import dub.internal.configy.Attributes; 19 20 import std.algorithm : findSplit, sort; 21 import std.array : join, split; 22 import std.exception : enforce; 23 import std.file; 24 import std.range; 25 import std.process : environment; 26 27 28 /** 29 Returns the individual parts of a qualified package name. 30 31 Sub qualified package names are lists of package names separated by ":". For 32 example, "packa:packb:packc" references a package named "packc" that is a 33 sub package of "packb", which in turn is a sub package of "packa". 34 */ 35 string[] getSubPackagePath(string package_name) @safe pure 36 { 37 return package_name.split(":"); 38 } 39 40 /** 41 Returns the name of the top level package for a given (sub) package name. 42 43 In case of a top level package, the qualified name is returned unmodified. 44 */ 45 string getBasePackageName(string package_name) @safe pure 46 { 47 return package_name.findSplit(":")[0]; 48 } 49 50 /** 51 Returns the qualified sub package part of the given package name. 52 53 This is the part of the package name excluding the base package 54 name. See also $(D getBasePackageName). 55 */ 56 string getSubPackageName(string package_name) @safe pure 57 { 58 return package_name.findSplit(":")[2]; 59 } 60 61 @safe unittest 62 { 63 assert(getSubPackagePath("packa:packb:packc") == ["packa", "packb", "packc"]); 64 assert(getSubPackagePath("pack") == ["pack"]); 65 assert(getBasePackageName("packa:packb:packc") == "packa"); 66 assert(getBasePackageName("pack") == "pack"); 67 assert(getSubPackageName("packa:packb:packc") == "packb:packc"); 68 assert(getSubPackageName("pack") == ""); 69 } 70 71 /** 72 Represents the contents of a package recipe file (dub.json/dub.sdl) in an abstract way. 73 74 This structure is used to reason about package descriptions in isolation. 75 For higher level package handling, see the $(D Package) class. 76 */ 77 struct PackageRecipe { 78 /** 79 * Name of the package, used to uniquely identify the package. 80 * 81 * This field is the only mandatory one. 82 * Must be comprised of only lower case ASCII alpha-numeric characters, 83 * "-" or "_". 84 */ 85 string name; 86 87 /// Brief description of the package. 88 @Optional string description; 89 90 /// URL of the project website 91 @Optional string homepage; 92 93 /** 94 * List of project authors 95 * 96 * the suggested format is either: 97 * "Peter Parker" 98 * or 99 * "Peter Parker <pparker@example.com>" 100 */ 101 @Optional string[] authors; 102 103 /// Copyright declaration string 104 @Optional string copyright; 105 106 /// License(s) under which the project can be used 107 @Optional string license; 108 109 /// Set of version requirements for DUB, compilers and/or language frontend. 110 @Optional ToolchainRequirements toolchainRequirements; 111 112 /** 113 * Speficies an optional list of build configurations 114 * 115 * By default, the first configuration present in the package recipe 116 * will be used, except for special configurations (e.g. "unittest"). 117 * A specific configuration can be chosen from the command line using 118 * `--config=name` or `-c name`. A package can select a specific 119 * configuration in one of its dependency by using the `subConfigurations` 120 * build setting. 121 * Build settings defined at the top level affect all configurations. 122 */ 123 @Optional @Key("name") ConfigurationInfo[] configurations; 124 125 /** 126 * Defines additional custom build types or overrides the default ones 127 * 128 * Build types can be selected from the command line using `--build=name` 129 * or `-b name`. The default build type is `debug`. 130 */ 131 @Optional BuildSettingsTemplate[string] buildTypes; 132 133 /** 134 * Build settings influence the command line arguments and options passed 135 * to the compiler and linker. 136 * 137 * All build settings can be present at the top level, and are optional. 138 * Build settings can also be found in `configurations`. 139 */ 140 @Optional BuildSettingsTemplate buildSettings; 141 alias buildSettings this; 142 143 /** 144 * Specifies a list of command line flags usable for controlling 145 * filter behavior for `--build=ddox` [experimental] 146 */ 147 @Optional @Name("-ddoxFilterArgs") string[] ddoxFilterArgs; 148 149 /// Specify which tool to use with `--build=ddox` (experimental) 150 @Optional @Name("-ddoxTool") string ddoxTool; 151 152 /** 153 * Sub-packages path or definitions 154 * 155 * Sub-packages allow to break component of a large framework into smaller 156 * packages. In the recipe file, subpackages entry can take one of two forms: 157 * either the path to a sub-folder where a recipe file exists, 158 * or an object of the same format as a recipe file (or `PackageRecipe`). 159 */ 160 @Optional SubPackage[] subPackages; 161 162 /// Usually unused by users, this is set by dub automatically 163 @Optional @Name("version") string version_; 164 165 inout(ConfigurationInfo) getConfiguration(string name) 166 inout { 167 foreach (c; configurations) 168 if (c.name == name) 169 return c; 170 throw new Exception("Unknown configuration: "~name); 171 } 172 173 /** Clones the package recipe recursively. 174 */ 175 PackageRecipe clone() const { return .clone(this); } 176 } 177 178 struct SubPackage 179 { 180 string path; 181 PackageRecipe recipe; 182 183 /** 184 * Given a YAML parser, recurses into `recipe` or use `path` 185 * depending on the node type. 186 * 187 * Two formats are supported for `subpackages`: a string format, 188 * which is just the path to the subpackage, and embedding the 189 * full subpackage recipe into the parent package recipe. 190 * 191 * To support such a dual syntax, Configy requires the use 192 * of a `fromYAML` method, as it exposes the underlying format. 193 */ 194 static SubPackage fromYAML (scope ConfigParser!SubPackage p) 195 { 196 import dub.internal.dyaml.node; 197 198 if (p.node.nodeID == NodeID.mapping) 199 return SubPackage(null, p.parseAs!PackageRecipe); 200 else 201 return SubPackage(p.parseAs!string); 202 } 203 } 204 205 /// Describes minimal toolchain requirements 206 struct ToolchainRequirements 207 { 208 import std.typecons : Tuple, tuple; 209 210 // TODO: We can remove `@Optional` once bosagora/configy#30 is resolved, 211 // currently it fails because `Dependency.opCmp` is not CTFE-able. 212 213 /// DUB version requirement 214 @Optional @converter((scope ConfigParser!Dependency p) => p.node.as!string.parseDependency) 215 Dependency dub = Dependency.any; 216 /// D front-end version requirement 217 @Optional @converter((scope ConfigParser!Dependency p) => p.node.as!string.parseDMDDependency) 218 Dependency frontend = Dependency.any; 219 /// DMD version requirement 220 @Optional @converter((scope ConfigParser!Dependency p) => p.node.as!string.parseDMDDependency) 221 Dependency dmd = Dependency.any; 222 /// LDC version requirement 223 @Optional @converter((scope ConfigParser!Dependency p) => p.node.as!string.parseDependency) 224 Dependency ldc = Dependency.any; 225 /// GDC version requirement 226 @Optional @converter((scope ConfigParser!Dependency p) => p.node.as!string.parseDependency) 227 Dependency gdc = Dependency.any; 228 229 /** Get the list of supported compilers. 230 231 Returns: 232 An array of couples of compiler name and compiler requirement 233 */ 234 @property Tuple!(string, Dependency)[] supportedCompilers() const 235 { 236 Tuple!(string, Dependency)[] res; 237 if (dmd != Dependency.invalid) res ~= Tuple!(string, Dependency)("dmd", dmd); 238 if (ldc != Dependency.invalid) res ~= Tuple!(string, Dependency)("ldc", ldc); 239 if (gdc != Dependency.invalid) res ~= Tuple!(string, Dependency)("gdc", gdc); 240 return res; 241 } 242 243 bool empty() 244 const { 245 import std.algorithm.searching : all; 246 return only(dub, frontend, dmd, ldc, gdc) 247 .all!(r => r == Dependency.any); 248 } 249 } 250 251 252 /// Bundles information about a build configuration. 253 struct ConfigurationInfo { 254 string name; 255 @Optional string[] platforms; 256 @Optional BuildSettingsTemplate buildSettings; 257 alias buildSettings this; 258 259 /** 260 * Equivalent to the default constructor, used by Configy 261 */ 262 this(string name, string[] p, BuildSettingsTemplate build_settings) 263 @safe pure nothrow @nogc 264 { 265 this.name = name; 266 this.platforms = p; 267 this.buildSettings = build_settings; 268 } 269 270 this(string name, BuildSettingsTemplate build_settings) 271 { 272 enforce(!name.empty, "Configuration name is empty."); 273 this.name = name; 274 this.buildSettings = build_settings; 275 } 276 277 bool matchesPlatform(in BuildPlatform platform) 278 const { 279 if( platforms.empty ) return true; 280 foreach(p; platforms) 281 if (platform.matchesSpecification(p)) 282 return true; 283 return false; 284 } 285 } 286 287 /** 288 * A dependency with possible `BuildSettingsTemplate` 289 * 290 * Currently only `dflags` is taken into account, but the parser accepts any 291 * value that is in `BuildSettingsTemplate`. 292 * This feature was originally introduced to support `-preview`, as setting 293 * a `-preview` in `dflags` does not propagate down to dependencies. 294 */ 295 public struct RecipeDependency 296 { 297 /// The dependency itself 298 public Dependency dependency; 299 300 /// Additional dflags, if any 301 public BuildSettingsTemplate settings; 302 303 /// Convenience alias as most uses just want to deal with the `Dependency` 304 public alias dependency this; 305 306 /** 307 * Read a `Dependency` and `BuildSettingsTemplate` from the config file 308 * 309 * Required to support both short and long form 310 */ 311 static RecipeDependency fromYAML (scope ConfigParser!RecipeDependency p) 312 { 313 import dub.internal.dyaml.node; 314 315 if (p.node.nodeID == NodeID.scalar) { 316 auto d = YAMLFormat(p.node.as!string); 317 return RecipeDependency(d.toDependency()); 318 } 319 auto d = p.parseAs!YAMLFormat; 320 return RecipeDependency(d.toDependency(), d.settings); 321 } 322 323 /// In-file representation of a dependency as specified by the user 324 private struct YAMLFormat 325 { 326 @Name("version") @Optional string version_; 327 @Optional string path; 328 @Optional string repository; 329 bool optional; 330 @Name("default") bool default_; 331 332 @Optional BuildSettingsTemplate settings; 333 alias settings this; 334 335 /** 336 * Used by Configy to provide rich error message when parsing. 337 * 338 * Exceptions thrown from `validate` methods will be wrapped with field/file 339 * informations and rethrown from Configy, providing the user 340 * with the location of the configuration that triggered the error. 341 */ 342 public void validate () const 343 { 344 enforce(this.optional || !this.default_, 345 "Setting default to 'true' has no effect if 'optional' is not set"); 346 enforce(this.version_.length || this.path.length || this.repository.length, 347 "Need to provide one of the following fields: 'version', 'path', or 'repository'"); 348 349 enforce(!this.path.length || !this.repository.length, 350 "Cannot provide a 'path' dependency if a repository dependency is used"); 351 enforce(!this.repository.length || this.version_.length, 352 "Need to provide a commit hash in 'version' field with 'repository' dependency"); 353 354 // Need to deprecate this as it's fairly common 355 version (none) { 356 enforce(!this.path.length || !this.version_.length, 357 "Cannot provide a 'path' dependency if a 'version' dependency is used"); 358 } 359 } 360 361 /// Turns this struct into a `Dependency` 362 public Dependency toDependency () const 363 { 364 auto result = () { 365 if (this.path.length) 366 return Dependency(NativePath(this.path)); 367 if (this.repository.length) 368 return Dependency(Repository(this.repository, this.version_)); 369 return Dependency(VersionRange.fromString(this.version_)); 370 }(); 371 result.optional = this.optional; 372 result.default_ = this.default_; 373 return result; 374 } 375 } 376 } 377 378 /// Type used to avoid a breaking change when `Dependency[string]` 379 /// was changed to `RecipeDependency[string]` 380 package struct RecipeDependencyAA 381 { 382 /// The underlying data, `public` as `alias this` to `private` field doesn't 383 /// always work. 384 public RecipeDependency[string] data; 385 386 /// Expose base function, e.g. `clear` 387 alias data this; 388 389 /// Supports assignment from a `RecipeDependency` (used in the parser) 390 public void opIndexAssign(RecipeDependency dep, string key) 391 pure nothrow 392 { 393 this.data[key] = dep; 394 } 395 396 /// Supports assignment from a `Dependency`, used in user code mostly 397 public void opIndexAssign(Dependency dep, string key) 398 pure nothrow 399 { 400 this.data[key] = RecipeDependency(dep); 401 } 402 403 /// Configy doesn't like `alias this` to an AA 404 static RecipeDependencyAA fromYAML (scope ConfigParser!RecipeDependencyAA p) 405 { 406 return RecipeDependencyAA(p.parseAs!(typeof(this.data))); 407 } 408 } 409 410 /// This keeps general information about how to build a package. 411 /// It contains functions to create a specific BuildSetting, targeted at 412 /// a certain BuildPlatform. 413 struct BuildSettingsTemplate { 414 @Optional RecipeDependencyAA dependencies; 415 @Optional string systemDependencies; 416 @Optional TargetType targetType = TargetType.autodetect; 417 @Optional string targetPath; 418 @Optional string targetName; 419 @Optional string workingDirectory; 420 @Optional string mainSourceFile; 421 @Optional string[string] subConfigurations; 422 @StartsWith("dflags") string[][string] dflags; 423 @StartsWith("lflags") string[][string] lflags; 424 @StartsWith("libs") string[][string] libs; 425 @StartsWith("sourceFiles") string[][string] sourceFiles; 426 @StartsWith("sourcePaths") string[][string] sourcePaths; 427 @StartsWith("excludedSourceFiles") string[][string] excludedSourceFiles; 428 @StartsWith("injectSourceFiles") string[][string] injectSourceFiles; 429 @StartsWith("copyFiles") string[][string] copyFiles; 430 @StartsWith("extraDependencyFiles") string[][string] extraDependencyFiles; 431 @StartsWith("versions") string[][string] versions; 432 @StartsWith("debugVersions") string[][string] debugVersions; 433 @StartsWith("versionFilters") string[][string] versionFilters; 434 @StartsWith("debugVersionFilters") string[][string] debugVersionFilters; 435 @StartsWith("importPaths") string[][string] importPaths; 436 @StartsWith("stringImportPaths") string[][string] stringImportPaths; 437 @StartsWith("preGenerateCommands") string[][string] preGenerateCommands; 438 @StartsWith("postGenerateCommands") string[][string] postGenerateCommands; 439 @StartsWith("preBuildCommands") string[][string] preBuildCommands; 440 @StartsWith("postBuildCommands") string[][string] postBuildCommands; 441 @StartsWith("preRunCommands") string[][string] preRunCommands; 442 @StartsWith("postRunCommands") string[][string] postRunCommands; 443 @StartsWith("environments") string[string][string] environments; 444 @StartsWith("buildEnvironments")string[string][string] buildEnvironments; 445 @StartsWith("runEnvironments") string[string][string] runEnvironments; 446 @StartsWith("preGenerateEnvironments") string[string][string] preGenerateEnvironments; 447 @StartsWith("postGenerateEnvironments") string[string][string] postGenerateEnvironments; 448 @StartsWith("preBuildEnvironments") string[string][string] preBuildEnvironments; 449 @StartsWith("postBuildEnvironments") string[string][string] postBuildEnvironments; 450 @StartsWith("preRunEnvironments") string[string][string] preRunEnvironments; 451 @StartsWith("postRunEnvironments") string[string][string] postRunEnvironments; 452 453 @StartsWith("buildRequirements") @Optional 454 Flags!BuildRequirement[string] buildRequirements; 455 @StartsWith("buildOptions") @Optional 456 Flags!BuildOption[string] buildOptions; 457 458 459 BuildSettingsTemplate dup() const { 460 return clone(this); 461 } 462 463 /// Constructs a BuildSettings object from this template. 464 void getPlatformSettings(ref BuildSettings dst, in BuildPlatform platform, NativePath base_path) 465 const { 466 dst.targetType = this.targetType; 467 if (!this.targetPath.empty) dst.targetPath = this.targetPath; 468 if (!this.targetName.empty) dst.targetName = this.targetName; 469 if (!this.workingDirectory.empty) dst.workingDirectory = this.workingDirectory; 470 if (!this.mainSourceFile.empty) { 471 auto p = NativePath(this.mainSourceFile); 472 p.normalize(); 473 dst.mainSourceFile = p.toNativeString(); 474 dst.addSourceFiles(dst.mainSourceFile); 475 } 476 477 string[] collectFiles(in string[][string] paths_map, string pattern) 478 { 479 auto files = appender!(string[]); 480 481 import dub.project : buildSettingsVars; 482 import std.typecons : Nullable; 483 484 static Nullable!(string[string]) envVarCache; 485 486 if (envVarCache.isNull) envVarCache = environment.toAA(); 487 488 foreach (suffix, paths; paths_map) { 489 if (!platform.matchesSpecification(suffix)) 490 continue; 491 492 foreach (spath; paths) { 493 enforce(!spath.empty, "Paths must not be empty strings."); 494 auto path = NativePath(spath); 495 if (!path.absolute) path = base_path ~ path; 496 if (!existsDirectory(path)) { 497 import std.algorithm : any, find; 498 const hasVar = chain(buildSettingsVars, envVarCache.get.byKey).any!((string var) { 499 return spath.find("$"~var).length > 0 || spath.find("${"~var~"}").length > 0; 500 }); 501 if (!hasVar) 502 logWarn("Invalid source/import path: %s", path.toNativeString()); 503 continue; 504 } 505 506 auto pstr = path.toNativeString(); 507 foreach (d; dirEntries(pstr, pattern, SpanMode.depth)) { 508 import std.path : baseName, pathSplitter; 509 import std.algorithm.searching : canFind; 510 // eliminate any hidden files, or files in hidden directories. But always include 511 // files that are listed inside hidden directories that are specifically added to 512 // the project. 513 if (d.isDir || pathSplitter(d.name[pstr.length .. $]) 514 .canFind!(name => name.length && name[0] == '.')) 515 continue; 516 auto src = NativePath(d.name).relativeTo(base_path); 517 files ~= src.toNativeString(); 518 } 519 } 520 } 521 522 return files.data; 523 } 524 525 // collect source files 526 dst.addSourceFiles(collectFiles(sourcePaths, "*.d")); 527 auto sourceFiles = dst.sourceFiles.sort(); 528 529 // collect import files and remove sources 530 import std.algorithm : copy, setDifference; 531 532 auto importFiles = collectFiles(importPaths, "*.{d,di}").sort(); 533 immutable nremoved = importFiles.setDifference(sourceFiles).copy(importFiles.release).length; 534 importFiles = importFiles[0 .. $ - nremoved]; 535 dst.addImportFiles(importFiles.release); 536 537 dst.addStringImportFiles(collectFiles(stringImportPaths, "*")); 538 539 getPlatformSetting!("dflags", "addDFlags")(dst, platform); 540 getPlatformSetting!("lflags", "addLFlags")(dst, platform); 541 getPlatformSetting!("libs", "addLibs")(dst, platform); 542 getPlatformSetting!("sourceFiles", "addSourceFiles")(dst, platform); 543 getPlatformSetting!("excludedSourceFiles", "removeSourceFiles")(dst, platform); 544 getPlatformSetting!("injectSourceFiles", "addInjectSourceFiles")(dst, platform); 545 getPlatformSetting!("copyFiles", "addCopyFiles")(dst, platform); 546 getPlatformSetting!("extraDependencyFiles", "addExtraDependencyFiles")(dst, platform); 547 getPlatformSetting!("versions", "addVersions")(dst, platform); 548 getPlatformSetting!("debugVersions", "addDebugVersions")(dst, platform); 549 getPlatformSetting!("versionFilters", "addVersionFilters")(dst, platform); 550 getPlatformSetting!("debugVersionFilters", "addDebugVersionFilters")(dst, platform); 551 getPlatformSetting!("importPaths", "addImportPaths")(dst, platform); 552 getPlatformSetting!("stringImportPaths", "addStringImportPaths")(dst, platform); 553 getPlatformSetting!("preGenerateCommands", "addPreGenerateCommands")(dst, platform); 554 getPlatformSetting!("postGenerateCommands", "addPostGenerateCommands")(dst, platform); 555 getPlatformSetting!("preBuildCommands", "addPreBuildCommands")(dst, platform); 556 getPlatformSetting!("postBuildCommands", "addPostBuildCommands")(dst, platform); 557 getPlatformSetting!("preRunCommands", "addPreRunCommands")(dst, platform); 558 getPlatformSetting!("postRunCommands", "addPostRunCommands")(dst, platform); 559 getPlatformSetting!("environments", "addEnvironments")(dst, platform); 560 getPlatformSetting!("buildEnvironments", "addBuildEnvironments")(dst, platform); 561 getPlatformSetting!("runEnvironments", "addRunEnvironments")(dst, platform); 562 getPlatformSetting!("preGenerateEnvironments", "addPreGenerateEnvironments")(dst, platform); 563 getPlatformSetting!("postGenerateEnvironments", "addPostGenerateEnvironments")(dst, platform); 564 getPlatformSetting!("preBuildEnvironments", "addPreBuildEnvironments")(dst, platform); 565 getPlatformSetting!("postBuildEnvironments", "addPostBuildEnvironments")(dst, platform); 566 getPlatformSetting!("preRunEnvironments", "addPreRunEnvironments")(dst, platform); 567 getPlatformSetting!("postRunEnvironments", "addPostRunEnvironments")(dst, platform); 568 getPlatformSetting!("buildRequirements", "addRequirements")(dst, platform); 569 getPlatformSetting!("buildOptions", "addOptions")(dst, platform); 570 } 571 572 void getPlatformSetting(string name, string addname)(ref BuildSettings dst, in BuildPlatform platform) 573 const { 574 foreach(suffix, values; __traits(getMember, this, name)){ 575 if( platform.matchesSpecification(suffix) ) 576 __traits(getMember, dst, addname)(values); 577 } 578 } 579 580 void warnOnSpecialCompilerFlags(string package_name, string config_name) 581 { 582 auto nodef = false; 583 auto noprop = false; 584 foreach (req; this.buildRequirements) { 585 if (req & BuildRequirement.noDefaultFlags) nodef = true; 586 if (req & BuildRequirement.relaxProperties) noprop = true; 587 } 588 589 if (noprop) { 590 logWarn(`Warning: "buildRequirements": ["relaxProperties"] is deprecated and is now the default behavior. Note that the -property switch will probably be removed in future versions of DMD.`); 591 logWarn(""); 592 } 593 594 if (nodef) { 595 logWarn("Warning: This package uses the \"noDefaultFlags\" build requirement. Please use only for development purposes and not for released packages."); 596 logWarn(""); 597 } else { 598 string[] all_dflags; 599 Flags!BuildOption all_options; 600 foreach (flags; this.dflags) all_dflags ~= flags; 601 foreach (options; this.buildOptions) all_options |= options; 602 .warnOnSpecialCompilerFlags(all_dflags, all_options, package_name, config_name); 603 } 604 } 605 } 606 607 package(dub) void checkPlatform(const scope ref ToolchainRequirements tr, BuildPlatform platform, string package_name) 608 { 609 import dub.compilers.utils : dmdLikeVersionToSemverLike; 610 import std.algorithm.iteration : map; 611 import std.format : format; 612 613 string compilerver; 614 Dependency compilerspec; 615 616 switch (platform.compiler) { 617 default: 618 compilerspec = Dependency.any; 619 compilerver = "0.0.0"; 620 break; 621 case "dmd": 622 compilerspec = tr.dmd; 623 compilerver = platform.compilerVersion.length 624 ? dmdLikeVersionToSemverLike(platform.compilerVersion) 625 : "0.0.0"; 626 break; 627 case "ldc": 628 compilerspec = tr.ldc; 629 compilerver = platform.compilerVersion; 630 if (!compilerver.length) compilerver = "0.0.0"; 631 break; 632 case "gdc": 633 compilerspec = tr.gdc; 634 compilerver = platform.compilerVersion; 635 if (!compilerver.length) compilerver = "0.0.0"; 636 break; 637 } 638 639 enforce(compilerspec != Dependency.invalid, 640 format( 641 "Installed %s %s is not supported by %s. Supported compiler(s):\n%s", 642 platform.compiler, platform.compilerVersion, package_name, 643 tr.supportedCompilers.map!((cs) { 644 auto str = " - " ~ cs[0]; 645 if (cs[1] != Dependency.any) str ~= ": " ~ cs[1].toString(); 646 return str; 647 }).join("\n") 648 ) 649 ); 650 651 enforce(compilerspec.matches(compilerver), 652 format( 653 "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~ 654 "Please consider upgrading your installation.", 655 platform.compiler, platform.compilerVersion, 656 package_name, platform.compiler, compilerspec 657 ) 658 ); 659 660 enforce(tr.frontend.matches(dmdLikeVersionToSemverLike(platform.frontendVersionString)), 661 format( 662 "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~ 663 "Please consider upgrading your installation.", 664 platform.compiler, platform.compilerVersion, 665 platform.frontendVersionString, package_name, tr.frontend 666 ) 667 ); 668 } 669 670 package bool addRequirement(ref ToolchainRequirements req, string name, string value) 671 { 672 switch (name) { 673 default: return false; 674 case "dub": req.dub = parseDependency(value); break; 675 case "frontend": req.frontend = parseDMDDependency(value); break; 676 case "ldc": req.ldc = parseDependency(value); break; 677 case "gdc": req.gdc = parseDependency(value); break; 678 case "dmd": req.dmd = parseDMDDependency(value); break; 679 } 680 return true; 681 } 682 683 private static Dependency parseDependency(string dep) 684 { 685 if (dep == "no") return Dependency.invalid; 686 return Dependency(dep); 687 } 688 689 private static Dependency parseDMDDependency(string dep) 690 { 691 import dub.compilers.utils : dmdLikeVersionToSemverLike; 692 import dub.dependency : Dependency; 693 import std.algorithm : map, splitter; 694 import std.array : join; 695 696 if (dep == "no") return Dependency.invalid; 697 return dep 698 .splitter(' ') 699 .map!(r => dmdLikeVersionToSemverLike(r)) 700 .join(' ') 701 .Dependency; 702 } 703 704 private T clone(T)(ref const(T) val) 705 { 706 import dub.internal.dyaml.stdsumtype; 707 import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType; 708 709 static if (is(T == immutable)) return val; 710 else static if (isBasicType!T) return val; 711 else static if (isDynamicArray!T) { 712 alias V = typeof(T.init[0]); 713 static if (is(V == immutable)) return val; 714 else { 715 T ret = new V[val.length]; 716 foreach (i, ref f; val) 717 ret[i] = clone!V(f); 718 return ret; 719 } 720 } else static if (isAssociativeArray!T) { 721 alias V = ValueType!T; 722 T ret; 723 foreach (k, ref f; val) 724 ret[k] = clone!V(f); 725 return ret; 726 } else static if (is(T == SumType!A, A...)) { 727 return val.match!((any) => T(clone(any))); 728 } else static if (is(T == struct)) { 729 T ret; 730 foreach (i, M; typeof(T.tupleof)) 731 ret.tupleof[i] = clone!M(val.tupleof[i]); 732 return ret; 733 } else static assert(false, "Unsupported type: "~T.stringof); 734 } 735 736 unittest { // issue #1407 - duplicate main source file 737 { 738 BuildSettingsTemplate t; 739 t.mainSourceFile = "./foo.d"; 740 t.sourceFiles[""] = ["foo.d"]; 741 BuildSettings bs; 742 t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/")); 743 assert(bs.sourceFiles == ["foo.d"]); 744 } 745 746 version (Windows) {{ 747 BuildSettingsTemplate t; 748 t.mainSourceFile = "src/foo.d"; 749 t.sourceFiles[""] = ["src\\foo.d"]; 750 BuildSettings bs; 751 t.getPlatformSettings(bs, BuildPlatform.init, NativePath("/")); 752 assert(bs.sourceFiles == ["src\\foo.d"]); 753 }} 754 } 755 756 /** 757 * Edit all dependency names from `:foo` to `name:foo`. 758 * 759 * TODO: Remove the special case in the parser and remove this hack. 760 */ 761 package void fixDependenciesNames (T) (string root, ref T aggr) nothrow 762 { 763 static foreach (idx, FieldRef; T.tupleof) { 764 static if (is(immutable typeof(FieldRef) == immutable RecipeDependencyAA)) { 765 string[] toReplace; 766 foreach (key; aggr.tupleof[idx].byKey) 767 if (key.length && key[0] == ':') 768 toReplace ~= key; 769 foreach (k; toReplace) { 770 aggr.tupleof[idx][root ~ k] = aggr.tupleof[idx][k]; 771 aggr.tupleof[idx].data.remove(k); 772 } 773 } 774 else static if (is(typeof(FieldRef) == struct)) 775 fixDependenciesNames(root, aggr.tupleof[idx]); 776 } 777 }