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