1 /** 2 Contains high-level functionality for working with packages. 3 4 Copyright: © 2012-2013 Matthias Dondorff, © 2012-2016 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Matthias Dondorff, Sönke Ludwig, Martin Nowak, Nick Sabalausky 7 */ 8 module dub.package_; 9 10 public import dub.recipe.packagerecipe; 11 12 import dub.compilers.compiler; 13 import dub.dependency; 14 import dub.description; 15 import dub.recipe.json; 16 import dub.recipe.sdl; 17 18 import dub.internal.logging; 19 import dub.internal.utils; 20 import dub.internal.vibecompat.core.file; 21 import dub.internal.vibecompat.data.json; 22 import dub.internal.vibecompat.inet.path; 23 24 import configy.Read : StrictMode; 25 26 import std.algorithm; 27 import std.array; 28 import std.conv; 29 import std.exception; 30 import std.file; 31 import std.range; 32 import std.string; 33 import std.typecons : Nullable; 34 35 36 /// Lists the supported package recipe formats. 37 enum PackageFormat { 38 json, /// JSON based, using the ".json" file extension 39 sdl /// SDLang based, using the ".sdl" file extension 40 } 41 42 struct FilenameAndFormat { 43 string filename; 44 PackageFormat format; 45 } 46 47 /// Supported package descriptions in decreasing order of preference. 48 static immutable FilenameAndFormat[] packageInfoFiles = [ 49 {"dub.json", PackageFormat.json}, 50 {"dub.sdl", PackageFormat.sdl}, 51 {"package.json", PackageFormat.json} 52 ]; 53 54 /// Returns a list of all recognized package recipe file names in descending order of precedence. 55 @property string[] packageInfoFilenames() { return packageInfoFiles.map!(f => cast(string)f.filename).array; } 56 57 /// Returns the default package recile file name. 58 @property string defaultPackageFilename() { return packageInfoFiles[0].filename; } 59 60 /// All built-in build type names except for the special `$DFLAGS` build type. 61 /// Has the default build type (`debug`) as first index. 62 static immutable string[] builtinBuildTypes = [ 63 "debug", 64 "plain", 65 "release", 66 "release-debug", 67 "release-nobounds", 68 "unittest", 69 "profile", 70 "profile-gc", 71 "docs", 72 "ddox", 73 "cov", 74 "cov-ctfe", 75 "unittest-cov", 76 "unittest-cov-ctfe", 77 "syntax" 78 ]; 79 80 /** Represents a package, including its sub packages. 81 */ 82 class Package { 83 private { 84 NativePath m_path; 85 NativePath m_infoFile; 86 PackageRecipe m_info; 87 PackageRecipe m_rawRecipe; 88 Package m_parentPackage; 89 } 90 91 /** Constructs a `Package` using an in-memory package recipe. 92 93 Params: 94 json_recipe = The package recipe in JSON format 95 recipe = The package recipe in generic format 96 root = The directory in which the package resides (if any). 97 parent = Reference to the parent package, if the new package is a 98 sub package. 99 version_override = Optional version to associate to the package 100 instead of the one declared in the package recipe, or the one 101 determined by invoking the VCS (GIT currently). 102 */ 103 this(Json json_recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") 104 { 105 import dub.recipe.json; 106 107 PackageRecipe recipe; 108 parseJson(recipe, json_recipe, parent ? parent.name : null); 109 this(recipe, root, parent, version_override); 110 } 111 /// ditto 112 this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") 113 { 114 // save the original recipe 115 m_rawRecipe = recipe.clone; 116 117 if (!version_override.empty) 118 recipe.version_ = version_override; 119 120 // try to run git to determine the version of the package if no explicit version was given 121 if (recipe.version_.length == 0 && !parent) { 122 try recipe.version_ = determineVersionFromSCM(root); 123 catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); 124 125 if (recipe.version_.length == 0) { 126 logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString()); 127 // TODO: Assume unknown version here? 128 // recipe.version_ = Version.unknown.toString(); 129 recipe.version_ = Version.masterBranch.toString(); 130 } else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_); 131 } 132 133 m_parentPackage = parent; 134 m_path = root; 135 m_path.endsWithSlash = true; 136 137 // use the given recipe as the basis 138 m_info = recipe; 139 140 checkDubRequirements(); 141 fillWithDefaults(); 142 mutuallyExcludeMainFiles(); 143 } 144 145 /** Searches the given directory for package recipe files. 146 147 Params: 148 directory = The directory to search 149 150 Returns: 151 Returns the full path to the package file, if any was found. 152 Otherwise returns an empty path. 153 */ 154 static NativePath findPackageFile(NativePath directory) 155 { 156 foreach (file; packageInfoFiles) { 157 auto filename = directory ~ file.filename; 158 if (existsFile(filename)) return filename; 159 } 160 return NativePath.init; 161 } 162 163 /** Constructs a `Package` using a package that is physically present on the local file system. 164 165 Params: 166 root = The directory in which the package resides. 167 recipe_file = Optional path to the package recipe file. If left 168 empty, the `root` directory will be searched for a recipe file. 169 parent = Reference to the parent package, if the new package is a 170 sub package. 171 version_override = Optional version to associate to the package 172 instead of the one declared in the package recipe, or the one 173 determined by invoking the VCS (GIT currently). 174 mode = Whether to issue errors, warning, or ignore unknown keys in dub.json 175 */ 176 static Package load(NativePath root, NativePath recipe_file = NativePath.init, 177 Package parent = null, string version_override = "", 178 StrictMode mode = StrictMode.Ignore) 179 { 180 import dub.recipe.io; 181 182 if (recipe_file.empty) recipe_file = findPackageFile(root); 183 184 enforce(!recipe_file.empty, 185 "No package file found in %s, expected one of %s" 186 .format(root.toNativeString(), 187 packageInfoFiles.map!(f => cast(string)f.filename).join("/"))); 188 189 auto recipe = readPackageRecipe(recipe_file, parent ? parent.name : null, mode); 190 191 auto ret = new Package(recipe, root, parent, version_override); 192 ret.m_infoFile = recipe_file; 193 return ret; 194 } 195 196 /** Returns the qualified name of the package. 197 198 The qualified name includes any possible parent package if this package 199 is a sub package. 200 */ 201 @property string name() 202 const { 203 if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name; 204 else return m_info.name; 205 } 206 207 /** Returns the directory in which the package resides. 208 209 Note that this can be empty for packages that are not stored in the 210 local file system. 211 */ 212 @property NativePath path() const { return m_path; } 213 214 215 /** Accesses the version associated with this package. 216 217 Note that this is a shortcut to `this.recipe.version_`. 218 */ 219 @property Version version_() const { return m_parentPackage ? m_parentPackage.version_ : Version(m_info.version_); } 220 /// ditto 221 @property void version_(Version value) { assert(m_parentPackage is null); m_info.version_ = value.toString(); } 222 223 /** Accesses the recipe contents of this package. 224 225 The recipe contains any default values and configurations added by DUB. 226 To access the raw user recipe, use the `rawRecipe` property. 227 228 See_Also: `rawRecipe` 229 */ 230 @property ref inout(PackageRecipe) recipe() inout { return m_info; } 231 232 /** Accesses the original package recipe. 233 234 The returned recipe matches exactly the contents of the original package 235 recipe. For the effective package recipe, augmented with DUB generated 236 default settings and configurations, use the `recipe` property. 237 238 See_Also: `recipe` 239 */ 240 @property ref const(PackageRecipe) rawRecipe() const { return m_rawRecipe; } 241 242 /** Returns the path to the package recipe file. 243 244 Note that this can be empty for packages that are not stored in the 245 local file system. 246 */ 247 @property NativePath recipePath() const { return m_infoFile; } 248 249 250 /** Returns the base package of this package. 251 252 The base package is the root of the sub package hierarchy (i.e. the 253 topmost parent). This will be `null` for packages that are not sub 254 packages. 255 */ 256 @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; } 257 258 /** Returns the parent of this package. 259 260 The parent package is the package that contains a sub package. This will 261 be `null` for packages that are not sub packages. 262 */ 263 @property inout(Package) parentPackage() inout { return m_parentPackage; } 264 265 /** Returns the list of all sub packages. 266 267 Note that this is a shortcut for `this.recipe.subPackages`. 268 */ 269 @property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; } 270 271 /** Returns the list of all build configuration names. 272 273 Configuration contents can be accessed using `this.recipe.configurations`. 274 */ 275 @property string[] configurations() 276 const { 277 auto ret = appender!(string[])(); 278 foreach (ref config; m_info.configurations) 279 ret.put(config.name); 280 return ret.data; 281 } 282 283 /** Returns the list of all custom build type names. 284 285 Build type contents can be accessed using `this.recipe.buildTypes`. 286 */ 287 @property string[] customBuildTypes() 288 const { 289 auto ret = appender!(string[])(); 290 foreach (name; m_info.buildTypes.byKey) 291 ret.put(name); 292 return ret.data; 293 } 294 295 /** Writes the current recipe contents to a recipe file. 296 297 The parameter-less overload writes to `this.path`, which must not be 298 empty. The default recipe file name will be used in this case. 299 */ 300 void storeInfo() 301 { 302 storeInfo(m_path); 303 m_infoFile = m_path ~ defaultPackageFilename; 304 } 305 /// ditto 306 void storeInfo(NativePath path) 307 const { 308 auto filename = path ~ defaultPackageFilename; 309 auto dstFile = openFile(filename.toNativeString(), FileMode.createTrunc); 310 scope(exit) dstFile.close(); 311 dstFile.writePrettyJsonString(m_info.toJson()); 312 } 313 314 /// Get the metadata cache for this package 315 @property Json metadataCache() 316 { 317 enum silent_fail = true; 318 return jsonFromFile(m_path ~ ".dub/metadata_cache.json", silent_fail); 319 } 320 321 /// Write metadata cache for this package 322 @property void metadataCache(Json json) 323 { 324 enum create_if_missing = true; 325 if (isWritableDir(m_path ~ ".dub", create_if_missing)) 326 writeJsonFile(m_path ~ ".dub/metadata_cache.json", json); 327 // TODO: store elsewhere 328 } 329 330 /** Returns the package recipe of a non-path-based sub package. 331 332 For sub packages that are declared within the package recipe of the 333 parent package, this function will return the corresponding recipe. Sub 334 packages declared using a path must be loaded manually (or using the 335 `PackageManager`). 336 */ 337 Nullable!PackageRecipe getInternalSubPackage(string name) 338 { 339 foreach (ref p; m_info.subPackages) 340 if (p.path.empty && p.recipe.name == name) 341 return Nullable!PackageRecipe(p.recipe); 342 return Nullable!PackageRecipe(); 343 } 344 345 /** Searches for use of compiler-specific flags that have generic 346 alternatives. 347 348 This will output a warning message for each such flag to the console. 349 */ 350 void warnOnSpecialCompilerFlags() 351 { 352 // warn about use of special flags 353 m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null); 354 foreach (ref config; m_info.configurations) 355 config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name); 356 } 357 358 /** Retrieves a build settings template. 359 360 If no `config` is given, this returns the build settings declared at the 361 root level of the package recipe. Otherwise returns the settings 362 declared within the given configuration (excluding those at the root 363 level). 364 365 Note that this is a shortcut to accessing `this.recipe.buildSettings` or 366 `this.recipe.configurations[].buildSettings`. 367 */ 368 const(BuildSettingsTemplate) getBuildSettings(string config = null) 369 const { 370 if (config.length) { 371 foreach (ref conf; m_info.configurations) 372 if (conf.name == config) 373 return conf.buildSettings; 374 assert(false, "Unknown configuration: "~config); 375 } else { 376 return m_info.buildSettings; 377 } 378 } 379 380 /** Returns all BuildSettings for the given platform and configuration. 381 382 This will gather the effective build settings declared in tha package 383 recipe for when building on a particular platform and configuration. 384 Root build settings and configuration specific settings will be 385 merged. 386 */ 387 BuildSettings getBuildSettings(in BuildPlatform platform, string config) 388 const { 389 BuildSettings ret; 390 m_info.buildSettings.getPlatformSettings(ret, platform, this.path); 391 bool found = false; 392 foreach(ref conf; m_info.configurations){ 393 if( conf.name != config ) continue; 394 conf.buildSettings.getPlatformSettings(ret, platform, this.path); 395 found = true; 396 break; 397 } 398 assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config); 399 400 // construct default target name based on package name 401 if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_"); 402 403 // special support for DMD style flags 404 getCompiler("dmd").extractBuildOptions(ret); 405 406 return ret; 407 } 408 409 /** Returns the combination of all build settings for all configurations 410 and platforms. 411 412 This can be useful for IDEs to gather a list of all potentially used 413 files or settings. 414 */ 415 BuildSettings getCombinedBuildSettings() 416 const { 417 BuildSettings ret; 418 m_info.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path); 419 foreach(ref conf; m_info.configurations) 420 conf.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path); 421 422 // construct default target name based on package name 423 if (ret.targetName.empty) ret.targetName = this.name.replace(":", "_"); 424 425 // special support for DMD style flags 426 getCompiler("dmd").extractBuildOptions(ret); 427 428 return ret; 429 } 430 431 /** Adds build type specific settings to an existing set of build settings. 432 433 This function searches the package recipe for overridden build types. If 434 none is found, the default build settings will be applied, if 435 `build_type` matches a default build type name. An exception is thrown 436 otherwise. 437 */ 438 void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type) 439 const { 440 import std.process; 441 string dflags = environment.get("DFLAGS", ""); 442 settings.addDFlags(dflags.split()); 443 444 if (auto pbt = build_type in m_info.buildTypes) { 445 logDiagnostic("Using custom build type '%s'.", build_type); 446 pbt.getPlatformSettings(settings, platform, this.path); 447 } else { 448 with(BuildOption) switch (build_type) { 449 default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type)); 450 case "$DFLAGS": break; 451 case "plain": break; 452 case "debug": settings.addOptions(debugMode, debugInfo); break; 453 case "release": settings.addOptions(releaseMode, optimize, inline); break; 454 case "release-debug": settings.addOptions(releaseMode, optimize, inline, debugInfo); break; 455 case "release-nobounds": settings.addOptions(releaseMode, optimize, inline, noBoundsCheck); break; 456 case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break; 457 case "docs": settings.addOptions(syntaxOnly, _docs); break; 458 case "ddox": settings.addOptions(syntaxOnly, _ddox); break; 459 case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break; 460 case "profile-gc": settings.addOptions(profileGC, debugInfo); break; 461 case "cov": settings.addOptions(coverage, debugInfo); break; 462 case "cov-ctfe": settings.addOptions(coverageCTFE, debugInfo); break; 463 case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break; 464 case "unittest-cov-ctfe": settings.addOptions(unittests, coverageCTFE, debugMode, debugInfo); break; 465 case "syntax": settings.addOptions(syntaxOnly); break; 466 } 467 } 468 } 469 470 /** Returns the selected configuration for a certain dependency. 471 472 If no configuration is specified in the package recipe, null will be 473 returned instead. 474 475 FIXME: The `platform` parameter is currently ignored, as the 476 `"subConfigurations"` field doesn't support platform suffixes. 477 */ 478 string getSubConfiguration(string config, in Package dependency, in BuildPlatform platform) 479 const { 480 bool found = false; 481 foreach(ref c; m_info.configurations){ 482 if( c.name == config ){ 483 if( auto pv = dependency.name in c.buildSettings.subConfigurations ) return *pv; 484 found = true; 485 break; 486 } 487 } 488 assert(found || config is null, "Invalid configuration \""~config~"\" for "~this.name); 489 if( auto pv = dependency.name in m_info.buildSettings.subConfigurations ) return *pv; 490 return null; 491 } 492 493 /** Returns the default configuration to build for the given platform. 494 495 This will return the first configuration that is applicable to the given 496 platform, or `null` if none is applicable. By default, only library 497 configurations will be returned. Setting `allow_non_library` to `true` 498 will also return executable configurations. 499 500 See_Also: `getPlatformConfigurations` 501 */ 502 string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library = false) 503 const { 504 foreach (ref conf; m_info.configurations) { 505 if (!conf.matchesPlatform(platform)) continue; 506 if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue; 507 return conf.name; 508 } 509 return null; 510 } 511 512 /** Returns a list of configurations suitable for the given platform. 513 514 Params: 515 platform = The platform against which to match configurations 516 allow_non_library = If set to true, executable configurations will 517 also be included. 518 519 See_Also: `getDefaultConfiguration` 520 */ 521 string[] getPlatformConfigurations(in BuildPlatform platform, bool allow_non_library = false) 522 const { 523 auto ret = appender!(string[]); 524 foreach(ref conf; m_info.configurations){ 525 if (!conf.matchesPlatform(platform)) continue; 526 if (!allow_non_library && conf.buildSettings.targetType == TargetType.executable) continue; 527 ret ~= conf.name; 528 } 529 if (ret.data.length == 0) ret.put(null); 530 return ret.data; 531 } 532 533 /** Determines if the package has a dependency to a certain package. 534 535 Params: 536 dependency_name = The name of the package to search for 537 config = Name of the configuration to use when searching 538 for dependencies 539 540 See_Also: `getDependencies` 541 */ 542 bool hasDependency(string dependency_name, string config) 543 const { 544 if (dependency_name in m_info.buildSettings.dependencies) return true; 545 foreach (ref c; m_info.configurations) 546 if ((config.empty || c.name == config) && dependency_name in c.buildSettings.dependencies) 547 return true; 548 return false; 549 } 550 551 /** Retrieves all dependencies for a particular configuration. 552 553 This includes dependencies that are declared at the root level of the 554 package recipe, as well as those declared within the specified 555 configuration. If no configuration with the given name exists, only 556 dependencies declared at the root level will be returned. 557 558 See_Also: `hasDependency` 559 */ 560 const(Dependency[string]) getDependencies(string config) 561 const { 562 Dependency[string] ret; 563 foreach (k, v; m_info.buildSettings.dependencies) { 564 // DMD bug: Not giving `Dependency` here leads to RangeError 565 Dependency dep = v; 566 ret[k] = dep; 567 } 568 foreach (ref conf; m_info.configurations) 569 if (conf.name == config) { 570 foreach (k, v; conf.buildSettings.dependencies) { 571 Dependency dep = v; 572 ret[k] = dep; 573 } 574 break; 575 } 576 return ret; 577 } 578 579 /** Returns a list of all possible dependencies of the package. 580 581 This list includes all dependencies of all configurations. The same 582 package may occur multiple times with possibly different `Dependency` 583 values. 584 */ 585 PackageDependency[] getAllDependencies() 586 const { 587 auto ret = appender!(PackageDependency[]); 588 getAllDependenciesRange().copy(ret); 589 return ret.data; 590 } 591 592 // Left as package until the final API for this has been found 593 package auto getAllDependenciesRange() 594 const { 595 return 596 chain( 597 only(this.recipe.buildSettings.dependencies.byKeyValue), 598 this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue) 599 ) 600 .joiner() 601 .map!(d => PackageDependency(d.key, d.value)); 602 } 603 604 605 /** Returns a description of the package for use in IDEs or build tools. 606 */ 607 PackageDescription describe(BuildPlatform platform, string config) 608 const { 609 return describe(platform, getCompiler(platform.compilerBinary), config); 610 } 611 /// ditto 612 PackageDescription describe(BuildPlatform platform, Compiler compiler, string config) 613 const { 614 PackageDescription ret; 615 ret.configuration = config; 616 ret.path = m_path.toNativeString(); 617 ret.name = this.name; 618 ret.version_ = this.version_; 619 ret.description = m_info.description; 620 ret.homepage = m_info.homepage; 621 ret.authors = m_info.authors.dup; 622 ret.copyright = m_info.copyright; 623 ret.license = m_info.license; 624 ret.dependencies = getDependencies(config).keys; 625 626 // save build settings 627 BuildSettings bs = getBuildSettings(platform, config); 628 BuildSettings allbs = getCombinedBuildSettings(); 629 630 ret.targetType = bs.targetType; 631 ret.targetPath = bs.targetPath; 632 ret.targetName = bs.targetName; 633 if (ret.targetType != TargetType.none && compiler) 634 ret.targetFileName = compiler.getTargetFileName(bs, platform); 635 ret.workingDirectory = bs.workingDirectory; 636 ret.mainSourceFile = bs.mainSourceFile; 637 ret.dflags = bs.dflags; 638 ret.lflags = bs.lflags; 639 ret.libs = bs.libs; 640 ret.injectSourceFiles = bs.injectSourceFiles; 641 ret.copyFiles = bs.copyFiles; 642 ret.versions = bs.versions; 643 ret.debugVersions = bs.debugVersions; 644 ret.importPaths = bs.importPaths; 645 ret.stringImportPaths = bs.stringImportPaths; 646 ret.preGenerateCommands = bs.preGenerateCommands; 647 ret.postGenerateCommands = bs.postGenerateCommands; 648 ret.preBuildCommands = bs.preBuildCommands; 649 ret.postBuildCommands = bs.postBuildCommands; 650 ret.environments = bs.environments; 651 ret.buildEnvironments = bs.buildEnvironments; 652 ret.runEnvironments = bs.runEnvironments; 653 ret.preGenerateEnvironments = bs.preGenerateEnvironments; 654 ret.postGenerateEnvironments = bs.postGenerateEnvironments; 655 ret.preBuildEnvironments = bs.preBuildEnvironments; 656 ret.postBuildEnvironments = bs.postBuildEnvironments; 657 ret.preRunEnvironments = bs.preRunEnvironments; 658 ret.postRunEnvironments = bs.postRunEnvironments; 659 660 // prettify build requirements output 661 for (int i = 1; i <= BuildRequirement.max; i <<= 1) 662 if (bs.requirements & cast(BuildRequirement)i) 663 ret.buildRequirements ~= cast(BuildRequirement)i; 664 665 // prettify options output 666 for (int i = 1; i <= BuildOption.max; i <<= 1) 667 if (bs.options & cast(BuildOption)i) 668 ret.options ~= cast(BuildOption)i; 669 670 // collect all possible source files and determine their types 671 SourceFileRole[string] sourceFileTypes; 672 foreach (f; allbs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.unusedStringImport; 673 foreach (f; allbs.importFiles) sourceFileTypes[f] = SourceFileRole.unusedImport; 674 foreach (f; allbs.sourceFiles) sourceFileTypes[f] = SourceFileRole.unusedSource; 675 foreach (f; bs.stringImportFiles) sourceFileTypes[f] = SourceFileRole.stringImport; 676 foreach (f; bs.importFiles) sourceFileTypes[f] = SourceFileRole.import_; 677 foreach (f; bs.sourceFiles) sourceFileTypes[f] = SourceFileRole.source; 678 foreach (f; sourceFileTypes.byKey.array.sort()) { 679 SourceFileDescription sf; 680 sf.path = f; 681 sf.role = sourceFileTypes[f]; 682 ret.files ~= sf; 683 } 684 685 return ret; 686 } 687 688 private void checkDubRequirements() 689 { 690 import dub.dependency : Dependency; 691 import dub.semver : isValidVersion; 692 import dub.version_ : dubVersion; 693 import std.exception : enforce; 694 695 const dep = m_info.toolchainRequirements.dub; 696 697 static assert(dubVersion.length); 698 static if (dubVersion[0] == 'v') { 699 enum dv = dubVersion[1 .. $]; 700 } 701 else { 702 enum dv = dubVersion; 703 } 704 static assert(isValidVersion(dv)); 705 706 enforce(dep.matches(dv), 707 "dub-" ~ dv ~ " does not comply with toolchainRequirements.dub " 708 ~ "specification: " ~ m_info.toolchainRequirements.dub.toString() 709 ~ "\nPlease consider upgrading your DUB installation"); 710 } 711 712 private void fillWithDefaults() 713 { 714 auto bs = &m_info.buildSettings; 715 716 // check for default string import folders 717 if ("" !in bs.stringImportPaths) { 718 foreach(defvf; ["views"]){ 719 if( existsFile(m_path ~ defvf) ) 720 bs.stringImportPaths[""] ~= defvf; 721 } 722 } 723 724 // check for default source folders 725 immutable hasSP = ("" in bs.sourcePaths) !is null; 726 immutable hasIP = ("" in bs.importPaths) !is null; 727 if (!hasSP || !hasIP) { 728 foreach (defsf; ["source/", "src/"]) { 729 if (existsFile(m_path ~ defsf)) { 730 if (!hasSP) bs.sourcePaths[""] ~= defsf; 731 if (!hasIP) bs.importPaths[""] ~= defsf; 732 } 733 } 734 } 735 736 // generate default configurations if none are defined 737 if (m_info.configurations.length == 0) { 738 // check for default app_main 739 string app_main_file; 740 auto pkg_name = m_info.name.length ? m_info.name : "unknown"; 741 MainFileSearch: foreach_reverse(sf; bs.sourcePaths.get("", null)){ 742 auto p = m_path ~ sf; 743 if( !existsFile(p) ) continue; 744 foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){ 745 if( existsFile(p ~ fil) ) { 746 app_main_file = (NativePath(sf) ~ fil).toNativeString(); 747 break MainFileSearch; 748 } 749 } 750 } 751 752 if (bs.targetType == TargetType.executable) { 753 BuildSettingsTemplate app_settings; 754 app_settings.targetType = TargetType.executable; 755 if (bs.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file; 756 m_info.configurations ~= ConfigurationInfo("application", app_settings); 757 } else if (bs.targetType != TargetType.none) { 758 BuildSettingsTemplate lib_settings; 759 lib_settings.targetType = bs.targetType == TargetType.autodetect ? TargetType.library : bs.targetType; 760 761 if (bs.targetType == TargetType.autodetect) { 762 if (app_main_file.length) { 763 lib_settings.excludedSourceFiles[""] ~= app_main_file; 764 765 BuildSettingsTemplate app_settings; 766 app_settings.targetType = TargetType.executable; 767 app_settings.mainSourceFile = app_main_file; 768 m_info.configurations ~= ConfigurationInfo("application", app_settings); 769 } 770 } 771 772 m_info.configurations ~= ConfigurationInfo("library", lib_settings); 773 } 774 } 775 } 776 777 package void simpleLint() 778 const { 779 if (m_parentPackage) { 780 if (m_parentPackage.path != path) { 781 if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license) 782 logWarn("Warning: License in subpackage %s is different than it's parent package, this is discouraged.", name); 783 } 784 } 785 if (name.empty) logWarn("Warning: The package in %s has no name.", path); 786 bool[string] cnames; 787 foreach (ref c; this.recipe.configurations) { 788 if (c.name in cnames) 789 logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.", 790 c.name, this.name); 791 cnames[c.name] = true; 792 } 793 } 794 795 /// Exclude files listed in mainSourceFile for other configurations unless they are listed in sourceFiles 796 private void mutuallyExcludeMainFiles() 797 { 798 string[] allMainFiles; 799 foreach (ref config; m_info.configurations) 800 if (!config.buildSettings.mainSourceFile.empty()) 801 allMainFiles ~= config.buildSettings.mainSourceFile; 802 803 if (allMainFiles.length == 0) 804 return; 805 806 foreach (ref config; m_info.configurations) { 807 import std.algorithm.searching : canFind; 808 auto bs = &config.buildSettings; 809 auto otherMainFiles = allMainFiles.filter!(elem => (elem != bs.mainSourceFile)).array; 810 811 if (bs.sourceFiles.length == 0) 812 bs.excludedSourceFiles[""] ~= otherMainFiles; 813 else 814 foreach (suffix, arr; bs.sourceFiles) 815 bs.excludedSourceFiles[suffix] ~= otherMainFiles.filter!(elem => !canFind(arr, elem)).array; 816 } 817 } 818 } 819 820 private string determineVersionFromSCM(NativePath path) 821 { 822 if (existsFile(path ~ ".git")) 823 { 824 import dub.internal.git : determineVersionWithGit; 825 826 return determineVersionWithGit(path); 827 } 828 return null; 829 }