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