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