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 deprecated("Open an issue if this is needed") 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 // `package` visibility as it is set from the PackageManager 84 package NativePath m_infoFile; 85 private { 86 NativePath m_path; 87 PackageRecipe m_info; 88 PackageRecipe m_rawRecipe; 89 Package m_parentPackage; 90 } 91 92 /** Constructs a `Package` using an in-memory package recipe. 93 94 Params: 95 json_recipe = The package recipe in JSON format 96 recipe = The package recipe in generic format 97 root = The directory in which the package resides (if any). 98 parent = Reference to the parent package, if the new package is a 99 sub package. 100 version_override = Optional version to associate to the package 101 instead of the one declared in the package recipe, or the one 102 determined by invoking the VCS (GIT currently). 103 */ 104 deprecated("Provide an already parsed PackageRecipe instead of a JSON object") 105 this(Json json_recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") 106 { 107 import dub.recipe.json; 108 109 PackageRecipe recipe; 110 parseJson(recipe, json_recipe, parent ? parent.name : null); 111 this(recipe, root, parent, version_override); 112 } 113 /// ditto 114 this(PackageRecipe recipe, NativePath root = NativePath(), Package parent = null, string version_override = "") 115 { 116 // save the original recipe 117 m_rawRecipe = recipe.clone; 118 119 if (!version_override.empty) 120 recipe.version_ = version_override; 121 122 // try to run git to determine the version of the package if no explicit version was given 123 if (recipe.version_.length == 0 && !parent) { 124 try recipe.version_ = determineVersionFromSCM(root); 125 catch (Exception e) logDebug("Failed to determine version by SCM: %s", e.msg); 126 127 if (recipe.version_.length == 0) { 128 logDiagnostic("Note: Failed to determine version of package %s at %s. Assuming ~master.", recipe.name, this.path.toNativeString()); 129 // TODO: Assume unknown version here? 130 // recipe.version_ = Version.unknown.toString(); 131 recipe.version_ = Version.masterBranch.toString(); 132 } else logDiagnostic("Determined package version using GIT: %s %s", recipe.name, recipe.version_); 133 } 134 135 m_parentPackage = parent; 136 m_path = root; 137 m_path.endsWithSlash = true; 138 139 // use the given recipe as the basis 140 m_info = recipe; 141 142 checkDubRequirements(); 143 fillWithDefaults(); 144 mutuallyExcludeMainFiles(); 145 } 146 147 /** Searches the given directory for package recipe files. 148 149 Params: 150 directory = The directory to search 151 152 Returns: 153 Returns the full path to the package file, if any was found. 154 Otherwise returns an empty path. 155 */ 156 deprecated("Use `PackageManager.findPackageFile`") 157 static NativePath findPackageFile(NativePath directory) 158 { 159 foreach (file; packageInfoFiles) { 160 auto filename = directory ~ file.filename; 161 if (existsFile(filename)) return filename; 162 } 163 return NativePath.init; 164 } 165 166 /** Constructs a `Package` using a package that is physically present on the local file system. 167 168 Params: 169 root = The directory in which the package resides. 170 recipe_file = Optional path to the package recipe file. If left 171 empty, the `root` directory will be searched for a recipe file. 172 parent = Reference to the parent package, if the new package is a 173 sub package. 174 version_override = Optional version to associate to the package 175 instead of the one declared in the package recipe, or the one 176 determined by invoking the VCS (GIT currently). 177 mode = Whether to issue errors, warning, or ignore unknown keys in dub.json 178 */ 179 deprecated("Use `PackageManager.getOrLoadPackage` instead of loading packages directly") 180 static Package load(NativePath root, NativePath recipe_file = NativePath.init, 181 Package parent = null, string version_override = "", 182 StrictMode mode = StrictMode.Ignore) 183 { 184 import dub.recipe.io; 185 186 if (recipe_file.empty) recipe_file = findPackageFile(root); 187 188 enforce(!recipe_file.empty, 189 "No package file found in %s, expected one of %s" 190 .format(root.toNativeString(), 191 packageInfoFiles.map!(f => cast(string)f.filename).join("/"))); 192 193 auto recipe = readPackageRecipe(recipe_file, parent ? parent.name : null, mode); 194 195 auto ret = new Package(recipe, root, parent, version_override); 196 ret.m_infoFile = recipe_file; 197 return ret; 198 } 199 200 /** Returns the qualified name of the package. 201 202 The qualified name includes any possible parent package if this package 203 is a sub package. 204 */ 205 @property string name() 206 const { 207 if (m_parentPackage) return m_parentPackage.name ~ ":" ~ m_info.name; 208 else return m_info.name; 209 } 210 211 /** Returns the directory in which the package resides. 212 213 Note that this can be empty for packages that are not stored in the 214 local file system. 215 */ 216 @property NativePath path() const { return m_path; } 217 218 219 /** Accesses the version associated with this package. 220 221 Note that this is a shortcut to `this.recipe.version_`. 222 */ 223 @property Version version_() const { return m_parentPackage ? m_parentPackage.version_ : Version(m_info.version_); } 224 /// ditto 225 @property void version_(Version value) { assert(m_parentPackage is null); m_info.version_ = value.toString(); } 226 227 /** Accesses the recipe contents of this package. 228 229 The recipe contains any default values and configurations added by DUB. 230 To access the raw user recipe, use the `rawRecipe` property. 231 232 See_Also: `rawRecipe` 233 */ 234 @property ref inout(PackageRecipe) recipe() inout { return m_info; } 235 236 /** Accesses the original package recipe. 237 238 The returned recipe matches exactly the contents of the original package 239 recipe. For the effective package recipe, augmented with DUB generated 240 default settings and configurations, use the `recipe` property. 241 242 See_Also: `recipe` 243 */ 244 @property ref const(PackageRecipe) rawRecipe() const { return m_rawRecipe; } 245 246 /** Returns the path to the package recipe file. 247 248 Note that this can be empty for packages that are not stored in the 249 local file system. 250 */ 251 @property NativePath recipePath() const { return m_infoFile; } 252 253 254 /** Returns the base package of this package. 255 256 The base package is the root of the sub package hierarchy (i.e. the 257 topmost parent). This will be `null` for packages that are not sub 258 packages. 259 */ 260 @property inout(Package) basePackage() inout { return m_parentPackage ? m_parentPackage.basePackage : this; } 261 262 /** Returns the parent of this package. 263 264 The parent package is the package that contains a sub package. This will 265 be `null` for packages that are not sub packages. 266 */ 267 @property inout(Package) parentPackage() inout { return m_parentPackage; } 268 269 /** Returns the list of all sub packages. 270 271 Note that this is a shortcut for `this.recipe.subPackages`. 272 */ 273 @property inout(SubPackage)[] subPackages() inout { return m_info.subPackages; } 274 275 /** Returns the list of all build configuration names. 276 277 Configuration contents can be accessed using `this.recipe.configurations`. 278 */ 279 @property string[] configurations() 280 const { 281 auto ret = appender!(string[])(); 282 foreach (ref config; m_info.configurations) 283 ret.put(config.name); 284 return ret.data; 285 } 286 287 /** Returns the list of all custom build type names. 288 289 Build type contents can be accessed using `this.recipe.buildTypes`. 290 */ 291 @property string[] customBuildTypes() 292 const { 293 auto ret = appender!(string[])(); 294 foreach (name; m_info.buildTypes.byKey) 295 ret.put(name); 296 return ret.data; 297 } 298 299 /** Writes the current recipe contents to a recipe file. 300 301 The parameter-less overload writes to `this.path`, which must not be 302 empty. The default recipe file name will be used in this case. 303 */ 304 void storeInfo() 305 { 306 storeInfo(m_path); 307 m_infoFile = m_path ~ defaultPackageFilename; 308 } 309 /// ditto 310 void storeInfo(NativePath path) 311 const { 312 auto filename = path ~ defaultPackageFilename; 313 writeJsonFile(filename, m_info.toJson()); 314 } 315 316 deprecated("Use `PackageManager.getSubPackage` instead") 317 Nullable!PackageRecipe getInternalSubPackage(string name) 318 { 319 foreach (ref p; m_info.subPackages) 320 if (p.path.empty && p.recipe.name == name) 321 return Nullable!PackageRecipe(p.recipe); 322 return Nullable!PackageRecipe(); 323 } 324 325 /** Searches for use of compiler-specific flags that have generic 326 alternatives. 327 328 This will output a warning message for each such flag to the console. 329 */ 330 void warnOnSpecialCompilerFlags() 331 { 332 // warn about use of special flags 333 m_info.buildSettings.warnOnSpecialCompilerFlags(m_info.name, null); 334 foreach (ref config; m_info.configurations) 335 config.buildSettings.warnOnSpecialCompilerFlags(m_info.name, config.name); 336 } 337 338 /** Retrieves a build settings template. 339 340 If no `config` is given, this returns the build settings declared at the 341 root level of the package recipe. Otherwise returns the settings 342 declared within the given configuration (excluding those at the root 343 level). 344 345 Note that this is a shortcut to accessing `this.recipe.buildSettings` or 346 `this.recipe.configurations[].buildSettings`. 347 */ 348 const(BuildSettingsTemplate) getBuildSettings(string config = null) 349 const { 350 if (config.length) { 351 foreach (ref conf; m_info.configurations) 352 if (conf.name == config) 353 return conf.buildSettings; 354 assert(false, "Unknown configuration: "~config); 355 } else { 356 return m_info.buildSettings; 357 } 358 } 359 360 /** Returns all BuildSettings for the given platform and configuration. 361 362 This will gather the effective build settings declared in the package 363 recipe for when building on a particular platform and configuration. 364 Root build settings and configuration specific settings will be 365 merged. 366 */ 367 BuildSettings getBuildSettings(in BuildPlatform platform, string config) 368 const { 369 BuildSettings ret; 370 m_info.buildSettings.getPlatformSettings(ret, platform, this.path); 371 bool found = false; 372 foreach(ref conf; m_info.configurations){ 373 if( conf.name != config ) continue; 374 conf.buildSettings.getPlatformSettings(ret, platform, this.path); 375 found = true; 376 break; 377 } 378 assert(found || config is null, "Unknown configuration for "~m_info.name~": "~config); 379 380 // construct default target name based on package name 381 if( ret.targetName.empty ) ret.targetName = this.name.replace(":", "_"); 382 383 // special support for DMD style flags 384 getCompiler("dmd").extractBuildOptions(ret); 385 386 return ret; 387 } 388 389 /** Returns the combination of all build settings for all configurations 390 and platforms. 391 392 This can be useful for IDEs to gather a list of all potentially used 393 files or settings. 394 */ 395 BuildSettings getCombinedBuildSettings() 396 const { 397 BuildSettings ret; 398 m_info.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path); 399 foreach(ref conf; m_info.configurations) 400 conf.buildSettings.getPlatformSettings(ret, BuildPlatform.any, this.path); 401 402 // construct default target name based on package name 403 if (ret.targetName.empty) ret.targetName = this.name.replace(":", "_"); 404 405 // special support for DMD style flags 406 getCompiler("dmd").extractBuildOptions(ret); 407 408 return ret; 409 } 410 411 /** Adds build type specific settings to an existing set of build settings. 412 413 This function searches the package recipe for overridden build types. If 414 none is found, the default build settings will be applied, if 415 `build_type` matches a default build type name. An exception is thrown 416 otherwise. 417 */ 418 void addBuildTypeSettings(ref BuildSettings settings, in BuildPlatform platform, string build_type) 419 const { 420 if (auto pbt = build_type in m_info.buildTypes) { 421 logDiagnostic("Using custom build type '%s'.", build_type); 422 pbt.getPlatformSettings(settings, platform, this.path); 423 } else { 424 with(BuildOption) switch (build_type) { 425 default: throw new Exception(format("Unknown build type for %s: '%s'", this.name, build_type)); 426 case "$DFLAGS": break; 427 case "plain": break; 428 case "debug": settings.addOptions(debugMode, debugInfo); break; 429 case "release": settings.addOptions(releaseMode, optimize, inline); break; 430 case "release-debug": settings.addOptions(releaseMode, optimize, inline, debugInfo); break; 431 case "release-nobounds": settings.addOptions(releaseMode, optimize, inline, noBoundsCheck); break; 432 case "unittest": settings.addOptions(unittests, debugMode, debugInfo); break; 433 case "docs": settings.addOptions(syntaxOnly, _docs); break; 434 case "ddox": settings.addOptions(syntaxOnly, _ddox); break; 435 case "profile": settings.addOptions(profile, optimize, inline, debugInfo); break; 436 case "profile-gc": settings.addOptions(profileGC, debugInfo); break; 437 case "cov": settings.addOptions(coverage, debugInfo); break; 438 case "cov-ctfe": settings.addOptions(coverageCTFE, debugInfo); break; 439 case "unittest-cov": settings.addOptions(unittests, coverage, debugMode, debugInfo); break; 440 case "unittest-cov-ctfe": settings.addOptions(unittests, coverageCTFE, debugMode, debugInfo); break; 441 case "syntax": settings.addOptions(syntaxOnly); break; 442 } 443 } 444 445 // Add environment DFLAGS last so that user specified values are not overriden by us. 446 import std.process : environment; 447 string dflags = environment.get("DFLAGS", ""); 448 settings.addDFlags(dflags.split()); 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 import std.algorithm: sort, uniq; 577 import std.array: array; 578 return 579 chain( 580 only(this.recipe.buildSettings.dependencies.byKeyValue), 581 this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue) 582 ) 583 .joiner() 584 .map!(d => PackageDependency(PackageName(d.key), d.value)) 585 .array 586 .sort 587 .uniq; 588 } 589 590 591 /** Returns a description of the package for use in IDEs or build tools. 592 */ 593 PackageDescription describe(BuildPlatform platform, string config) 594 const { 595 return describe(platform, getCompiler(platform.compilerBinary), config); 596 } 597 /// ditto 598 PackageDescription describe(BuildPlatform platform, Compiler compiler, string config) 599 const { 600 PackageDescription ret; 601 ret.configuration = config; 602 ret.path = m_path.toNativeString(); 603 ret.name = this.name; 604 ret.version_ = this.version_; 605 ret.description = m_info.description; 606 ret.homepage = m_info.homepage; 607 ret.authors = m_info.authors.dup; 608 ret.copyright = m_info.copyright; 609 ret.license = m_info.license; 610 ret.dependencies = getDependencies(config).keys; 611 612 // save build settings 613 BuildSettings bs = getBuildSettings(platform, config); 614 BuildSettings allbs = getCombinedBuildSettings(); 615 616 ret.targetType = bs.targetType; 617 ret.targetPath = bs.targetPath; 618 ret.targetName = bs.targetName; 619 if (ret.targetType != TargetType.none && compiler) 620 ret.targetFileName = compiler.getTargetFileName(bs, platform); 621 ret.workingDirectory = bs.workingDirectory; 622 ret.mainSourceFile = bs.mainSourceFile; 623 ret.dflags = bs.dflags; 624 ret.lflags = bs.lflags; 625 ret.libs = bs.libs; 626 ret.injectSourceFiles = bs.injectSourceFiles; 627 ret.copyFiles = bs.copyFiles; 628 ret.versions = bs.versions; 629 ret.debugVersions = bs.debugVersions; 630 ret.importPaths = bs.importPaths; 631 ret.cImportPaths = bs.cImportPaths; 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.semver : isValidVersion; 678 import dub.version_ : dubVersion; 679 import std.exception : enforce; 680 681 const dep = m_info.toolchainRequirements.dub; 682 683 static assert(dubVersion.length); 684 immutable dv = Version(dubVersion[(dubVersion[0] == 'v') .. $]); 685 686 enforce(dep.matches(dv), 687 "dub-" ~ dv.toString() ~ " does not comply with toolchainRequirements.dub " 688 ~ "specification: " ~ m_info.toolchainRequirements.dub.toString() 689 ~ "\nPlease consider upgrading your DUB installation"); 690 } 691 692 private void fillWithDefaults() 693 { 694 auto bs = &m_info.buildSettings; 695 696 // check for default string import folders 697 if ("" !in bs.stringImportPaths) { 698 foreach(defvf; ["views"]){ 699 if( existsFile(m_path ~ defvf) ) 700 bs.stringImportPaths[""] ~= defvf; 701 } 702 } 703 704 // check for default source folders 705 immutable hasSP = ("" in bs.sourcePaths) !is null; 706 immutable hasIP = ("" in bs.importPaths) !is null; 707 if (!hasSP || !hasIP) { 708 foreach (defsf; ["source/", "src/"]) { 709 if (existsFile(m_path ~ defsf)) { 710 if (!hasSP) bs.sourcePaths[""] ~= defsf; 711 if (!hasIP) bs.importPaths[""] ~= defsf; 712 } 713 } 714 } 715 716 // generate default configurations if none are defined 717 if (m_info.configurations.length == 0) { 718 // check for default app_main 719 string app_main_file; 720 auto pkg_name = m_info.name.length ? m_info.name : "unknown"; 721 MainFileSearch: foreach_reverse(sf; bs.sourcePaths.get("", null)){ 722 auto p = m_path ~ sf; 723 if( !existsFile(p) ) continue; 724 foreach(fil; ["app.d", "main.d", pkg_name ~ "/main.d", pkg_name ~ "/" ~ "app.d"]){ 725 if( existsFile(p ~ fil) ) { 726 app_main_file = (NativePath(sf) ~ fil).toNativeString(); 727 break MainFileSearch; 728 } 729 } 730 } 731 732 if (bs.targetType == TargetType.executable) { 733 BuildSettingsTemplate app_settings; 734 app_settings.targetType = TargetType.executable; 735 if (bs.mainSourceFile.empty) app_settings.mainSourceFile = app_main_file; 736 m_info.configurations ~= ConfigurationInfo("application", app_settings); 737 } else if (bs.targetType != TargetType.none) { 738 BuildSettingsTemplate lib_settings; 739 lib_settings.targetType = bs.targetType == TargetType.autodetect ? TargetType.library : bs.targetType; 740 741 if (bs.targetType == TargetType.autodetect) { 742 if (app_main_file.length) { 743 lib_settings.excludedSourceFiles[""] ~= app_main_file; 744 745 BuildSettingsTemplate app_settings; 746 app_settings.targetType = TargetType.executable; 747 app_settings.mainSourceFile = app_main_file; 748 m_info.configurations ~= ConfigurationInfo("application", app_settings); 749 } 750 } 751 752 m_info.configurations ~= ConfigurationInfo("library", lib_settings); 753 } 754 } 755 } 756 757 package void simpleLint() 758 const { 759 if (m_parentPackage) { 760 if (m_parentPackage.path != path) { 761 if (this.recipe.license.length && this.recipe.license != m_parentPackage.recipe.license) 762 logWarn("Warning: License in sub-package %s is different than its parent package, this is discouraged.", name); 763 } 764 } 765 if (name.empty) logWarn("Warning: The package in %s has no name.", path); 766 bool[string] cnames; 767 foreach (ref c; this.recipe.configurations) { 768 if (c.name in cnames) 769 logWarn("Warning: Multiple configurations with the name \"%s\" are defined in package \"%s\". This will most likely cause configuration resolution issues.", 770 c.name, this.name); 771 cnames[c.name] = true; 772 } 773 } 774 775 /// Exclude files listed in mainSourceFile for other configurations unless they are listed in sourceFiles 776 private void mutuallyExcludeMainFiles() 777 { 778 string[] allMainFiles; 779 foreach (ref config; m_info.configurations) 780 if (!config.buildSettings.mainSourceFile.empty()) 781 allMainFiles ~= config.buildSettings.mainSourceFile; 782 783 if (allMainFiles.length == 0) 784 return; 785 786 foreach (ref config; m_info.configurations) { 787 import std.algorithm.searching : canFind; 788 auto bs = &config.buildSettings; 789 auto otherMainFiles = allMainFiles.filter!(elem => (elem != bs.mainSourceFile)).array; 790 791 if (bs.sourceFiles.length == 0) 792 bs.excludedSourceFiles[""] ~= otherMainFiles; 793 else 794 foreach (suffix, arr; bs.sourceFiles) 795 bs.excludedSourceFiles[suffix] ~= otherMainFiles.filter!(elem => !canFind(arr, elem)).array; 796 } 797 } 798 } 799 800 private string determineVersionFromSCM(NativePath path) 801 { 802 if (existsFile(path ~ ".git")) 803 { 804 import dub.internal.git : determineVersionWithGit; 805 806 return determineVersionWithGit(path); 807 } 808 return null; 809 }