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