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