1 /** 2 A package manager. 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 7 */ 8 module dub.dub; 9 10 import dub.compilers.compiler; 11 import dub.data.settings : SPS = SkipPackageSuppliers, Settings; 12 import dub.dependency; 13 import dub.dependencyresolver; 14 import dub.internal.utils; 15 import dub.internal.vibecompat.core.file; 16 import dub.internal.vibecompat.data.json; 17 import dub.internal.vibecompat.inet.url; 18 import dub.internal.logging; 19 import dub.package_; 20 import dub.packagemanager; 21 import dub.packagesuppliers; 22 import dub.project; 23 import dub.generators.generator; 24 import dub.init; 25 26 import std.algorithm; 27 import std.array : array, replace; 28 import std.conv : text, to; 29 import std.encoding : sanitize; 30 import std.exception : enforce; 31 import std.file; 32 import std.process : environment; 33 import std.range : assumeSorted, empty; 34 import std.string; 35 36 static this() 37 { 38 import dub.compilers.dmd : DMDCompiler; 39 import dub.compilers.gdc : GDCCompiler; 40 import dub.compilers.ldc : LDCCompiler; 41 registerCompiler(new DMDCompiler); 42 registerCompiler(new GDCCompiler); 43 registerCompiler(new LDCCompiler); 44 } 45 46 deprecated("use defaultRegistryURLs") enum defaultRegistryURL = defaultRegistryURLs[0]; 47 48 /// The URL to the official package registry and it's default fallback registries. 49 static immutable string[] defaultRegistryURLs = [ 50 "https://code.dlang.org/", 51 "https://codemirror.dlang.org/", 52 "https://dub.bytecraft.nl/", 53 "https://code-mirror.dlang.io/", 54 ]; 55 56 /** Returns a default list of package suppliers. 57 58 This will contain a single package supplier that points to the official 59 package registry. 60 61 See_Also: `defaultRegistryURLs` 62 */ 63 deprecated("This function wasn't intended for public use - open an issue with Dub if you need it") 64 PackageSupplier[] defaultPackageSuppliers() 65 { 66 logDiagnostic("Using dub registry url '%s'", defaultRegistryURLs[0]); 67 return [new FallbackPackageSupplier(defaultRegistryURLs.map!_getRegistryPackageSupplier.array)]; 68 } 69 70 /** Returns a registry package supplier according to protocol. 71 72 Allowed protocols are dub+http(s):// and maven+http(s)://. 73 */ 74 deprecated("This function wasn't intended for public use - open an issue with Dub if you need it") 75 PackageSupplier getRegistryPackageSupplier(string url) 76 { 77 return _getRegistryPackageSupplier(url); 78 } 79 80 // Private to avoid a bug in `defaultPackageSuppliers` with `map` triggering a deprecation 81 // even though the context is deprecated. 82 private PackageSupplier _getRegistryPackageSupplier(string url) 83 { 84 switch (url.startsWith("dub+", "mvn+", "file://")) 85 { 86 case 1: 87 return new RegistryPackageSupplier(URL(url[4..$])); 88 case 2: 89 return new MavenRegistryPackageSupplier(URL(url[4..$])); 90 case 3: 91 return new FileSystemPackageSupplier(NativePath(url[7..$])); 92 default: 93 return new RegistryPackageSupplier(URL(url)); 94 } 95 } 96 97 deprecated unittest 98 { 99 auto dubRegistryPackageSupplier = getRegistryPackageSupplier("dub+https://code.dlang.org"); 100 assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org")); 101 102 dubRegistryPackageSupplier = getRegistryPackageSupplier("https://code.dlang.org"); 103 assert(dubRegistryPackageSupplier.description.canFind(" https://code.dlang.org")); 104 105 auto mavenRegistryPackageSupplier = getRegistryPackageSupplier("mvn+http://localhost:8040/maven/libs-release/dubpackages"); 106 assert(mavenRegistryPackageSupplier.description.canFind(" http://localhost:8040/maven/libs-release/dubpackages")); 107 108 auto fileSystemPackageSupplier = getRegistryPackageSupplier("file:///etc/dubpackages"); 109 assert(fileSystemPackageSupplier.description.canFind(" " ~ NativePath("/etc/dubpackages").toNativeString)); 110 } 111 112 /** Provides a high-level entry point for DUB's functionality. 113 114 This class provides means to load a certain project (a root package with 115 all of its dependencies) and to perform high-level operations as found in 116 the command line interface. 117 */ 118 class Dub { 119 protected { 120 bool m_dryRun = false; 121 PackageManager m_packageManager; 122 PackageSupplier[] m_packageSuppliers; 123 NativePath m_rootPath; 124 string m_mainRecipePath; 125 SpecialDirs m_dirs; 126 Settings m_config; 127 Project m_project; 128 string m_defaultCompiler; 129 } 130 131 /** The default placement location of fetched packages. 132 133 This property can be altered, so that packages which are downloaded as part 134 of the normal upgrade process are stored in a certain location. This is 135 how the "--local" and "--system" command line switches operate. 136 */ 137 PlacementLocation defaultPlacementLocation = PlacementLocation.user; 138 139 140 /** Initializes the instance for use with a specific root package. 141 142 Note that a package still has to be loaded using one of the 143 `loadPackage` overloads. 144 145 Params: 146 root_path = Path to the root package 147 base = A list of package suppliers that are always present 148 (regardless of `skip`) and take precedence over the default 149 and configured `PackageSupplier`. This setting is currently 150 not used by the dub application but useful for libraries. 151 skip = Can be used to skip using the configured package suppliers, 152 as well as the default suppliers. 153 */ 154 this(string root_path = ".", PackageSupplier[] base = null, 155 SkipPackageSuppliers skip = SkipPackageSuppliers.none) 156 { 157 m_rootPath = NativePath(root_path); 158 if (!m_rootPath.absolute) m_rootPath = getWorkingDirectory() ~ m_rootPath; 159 160 init(); 161 162 const registry_var = environment.get("DUB_REGISTRY", null); 163 m_packageSuppliers = this.makePackageSuppliers(base, skip, registry_var); 164 m_packageManager = this.makePackageManager(); 165 166 auto ccps = m_config.customCachePaths; 167 if (ccps.length) 168 m_packageManager.customCachePaths = ccps; 169 170 // TODO: Move this environment read out of the ctor 171 if (auto p = environment.get("DUBPATH")) { 172 version(Windows) enum pathsep = ";"; 173 else enum pathsep = ":"; 174 NativePath[] paths = p.split(pathsep) 175 .map!(p => NativePath(p))().array(); 176 m_packageManager.searchPath = paths; 177 } 178 } 179 180 /** Initializes the instance with a single package search path, without 181 loading a package. 182 183 This constructor corresponds to the "--bare" option of the command line 184 interface. 185 186 Params: 187 root = The root path of the Dub instance itself. 188 pkg_root = The root of the location where packages are located 189 Only packages under this location will be accessible. 190 Note that packages at the top levels will be ignored. 191 */ 192 this(NativePath root, NativePath pkg_root) 193 { 194 // Note: We're doing `init()` before setting the `rootPath`, 195 // to prevent `init` from reading the project's settings. 196 init(); 197 this.m_rootPath = root; 198 m_packageManager = new PackageManager(pkg_root); 199 } 200 201 deprecated("Use the overload that takes `(NativePath pkg_root, NativePath root)`") 202 this(NativePath pkg_root) 203 { 204 this(pkg_root, pkg_root); 205 } 206 207 /** 208 * Get the `PackageManager` instance to use for this `Dub` instance 209 * 210 * The `PackageManager` is a central component of `Dub` as it allows to 211 * store and retrieve packages from the file system. In unittests, or more 212 * generally in a library setup, one may wish to provide a custom 213 * implementation, which can be done by overriding this method. 214 */ 215 protected PackageManager makePackageManager() 216 { 217 return new PackageManager(m_rootPath, m_dirs.userPackages, m_dirs.systemSettings, false); 218 } 219 220 protected void init() 221 { 222 this.m_dirs = SpecialDirs.make(); 223 this.m_config = this.loadConfig(this.m_dirs); 224 this.m_defaultCompiler = this.determineDefaultCompiler(); 225 } 226 227 /** 228 * Load user configuration for this instance 229 * 230 * This can be overloaded in child classes to prevent library / unittest 231 * dub from doing any kind of file IO. 232 * As this routine is used during initialization, the only assumption made 233 * in the base implementation is that `m_rootPath` has been populated. 234 * Child implementation should not rely on any other field in the base 235 * class having been populated. 236 * 237 * Params: 238 * dirs = An instance of `SpecialDirs` to read from and write to, 239 * as the configurations being read might set a `dubHome`. 240 * 241 * Returns: 242 * A populated `Settings` instance. 243 */ 244 protected Settings loadConfig(ref SpecialDirs dirs) const 245 { 246 import dub.internal.configy.Read; 247 248 static void readSettingsFile (NativePath path_, ref Settings current) 249 { 250 // TODO: Remove `StrictMode.Warn` after v1.40 release 251 // The default is to error, but as the previous parser wasn't 252 // complaining, we should first warn the user. 253 const path = path_.toNativeString(); 254 if (path.exists) { 255 auto newConf = parseConfigFileSimple!Settings(path, StrictMode.Warn); 256 if (!newConf.isNull()) 257 current = current.merge(newConf.get()); 258 } 259 } 260 261 Settings result; 262 const dubFolderPath = NativePath(thisExePath).parentPath; 263 264 // override default userSettings + userPackages if a $DPATH or 265 // $DUB_HOME environment variable is set. 266 bool overrideDubHomeFromEnv; 267 { 268 string dubHome = environment.get("DUB_HOME"); 269 if (!dubHome.length) { 270 auto dpath = environment.get("DPATH"); 271 if (dpath.length) 272 dubHome = (NativePath(dpath) ~ "dub/").toNativeString(); 273 274 } 275 if (dubHome.length) { 276 overrideDubHomeFromEnv = true; 277 278 dirs.userSettings = NativePath(dubHome); 279 dirs.userPackages = dirs.userSettings; 280 dirs.cache = dirs.userPackages ~ "cache"; 281 } 282 } 283 284 readSettingsFile(dirs.systemSettings ~ "settings.json", result); 285 readSettingsFile(dubFolderPath ~ "../etc/dub/settings.json", result); 286 version (Posix) { 287 if (dubFolderPath.absolute && dubFolderPath.startsWith(NativePath("usr"))) 288 readSettingsFile(NativePath("/etc/dub/settings.json"), result); 289 } 290 291 // Override user + local package path from system / binary settings 292 // Then continues loading local settings from these folders. (keeping 293 // global /etc/dub/settings.json settings intact) 294 // 295 // Don't use it if either $DPATH or $DUB_HOME are set, as environment 296 // variables usually take precedence over configuration. 297 if (!overrideDubHomeFromEnv && result.dubHome.set) { 298 dirs.userSettings = NativePath(result.dubHome.expandEnvironmentVariables); 299 } 300 301 // load user config: 302 readSettingsFile(dirs.userSettings ~ "settings.json", result); 303 304 // load per-package config: 305 if (!this.m_rootPath.empty) 306 readSettingsFile(this.m_rootPath ~ "dub.settings.json", result); 307 308 // same as userSettings above, but taking into account the 309 // config loaded from user settings and per-package config as well. 310 if (!overrideDubHomeFromEnv && result.dubHome.set) { 311 dirs.userPackages = NativePath(result.dubHome.expandEnvironmentVariables); 312 dirs.cache = dirs.userPackages ~ "cache"; 313 } 314 315 return result; 316 } 317 318 /** Get the list of package suppliers. 319 320 Params: 321 additional_package_suppliers = A list of package suppliers to try 322 before the suppliers found in the configurations files and the 323 `defaultPackageSuppliers`. 324 skip = Can be used to skip using the configured package suppliers, 325 as well as the default suppliers. 326 */ 327 deprecated("This is an implementation detail. " ~ 328 "Use `packageSuppliers` to get the computed list of package " ~ 329 "suppliers once a `Dub` instance has been constructed.") 330 public PackageSupplier[] getPackageSuppliers(PackageSupplier[] base, SkipPackageSuppliers skip) 331 { 332 return this.makePackageSuppliers(base, skip, environment.get("DUB_REGISTRY", null)); 333 } 334 335 /// Ditto 336 protected PackageSupplier[] makePackageSuppliers(PackageSupplier[] base, 337 SkipPackageSuppliers skip, string registry_var) 338 { 339 PackageSupplier[] ps = base; 340 341 if (skip < SkipPackageSuppliers.all) 342 { 343 ps ~= registry_var 344 .splitter(";") 345 .map!(url => this.makePackageSupplier(url)) 346 .array; 347 } 348 349 if (skip < SkipPackageSuppliers.configured) 350 { 351 ps ~= m_config.registryUrls 352 .map!(url => this.makePackageSupplier(url)) 353 .array; 354 } 355 356 if (skip < SkipPackageSuppliers.standard) 357 ps ~= new FallbackPackageSupplier( 358 defaultRegistryURLs.map!(url => this.makePackageSupplier(url)) 359 .array); 360 361 return ps; 362 } 363 364 // Note: This test rely on the environment, which is not how unittests should work. 365 // This should be removed / refactored to keep coverage without affecting the env. 366 unittest 367 { 368 import dub.test.base : TestDub; 369 370 scope (exit) environment.remove("DUB_REGISTRY"); 371 auto dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured); 372 assert(dub.packageSuppliers.length == 0); 373 environment["DUB_REGISTRY"] = "http://example.com/"; 374 dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured); 375 assert(dub.packageSuppliers.length == 1); 376 environment["DUB_REGISTRY"] = "http://example.com/;http://foo.com/"; 377 dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured); 378 assert(dub.packageSuppliers.length == 2); 379 dub = new TestDub(null, ".", [new RegistryPackageSupplier(URL("http://bar.com/"))], SkipPackageSuppliers.configured); 380 assert(dub.packageSuppliers.length == 3); 381 382 dub = new TestDub(); 383 assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.none, null).length == 1); 384 assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.configured, null).length == 0); 385 assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.standard, null).length == 0); 386 assert(dub.makePackageSuppliers(null, SkipPackageSuppliers.standard, "http://example.com/") 387 .length == 1); 388 } 389 390 /** 391 * Instantiate a `PackageSupplier` according to a given URL 392 * 393 * This is a factory function for `PackageSupplier`. Child classes may 394 * wish to override this to implement their own `PackageSupplier` logic, 395 * be it by extending this method's ability or replacing it. 396 * 397 * Params: 398 * url = The URL of the `PackageSupplier`. 399 * 400 * Returns: 401 * A new instance of a `PackageSupplier`. 402 */ 403 protected PackageSupplier makePackageSupplier(string url) const 404 { 405 switch (url.startsWith("dub+", "mvn+", "file://")) 406 { 407 case 1: 408 return new RegistryPackageSupplier(URL(url[4..$])); 409 case 2: 410 return new MavenRegistryPackageSupplier(URL(url[4..$])); 411 case 3: 412 return new FileSystemPackageSupplier(NativePath(url[7..$])); 413 default: 414 return new RegistryPackageSupplier(URL(url)); 415 } 416 } 417 418 /// ditto 419 deprecated("This is an implementation detail. " ~ 420 "Use `packageSuppliers` to get the computed list of package " ~ 421 "suppliers once a `Dub` instance has been constructed.") 422 public PackageSupplier[] getPackageSuppliers(PackageSupplier[] additional_package_suppliers) 423 { 424 return getPackageSuppliers(additional_package_suppliers, m_config.skipRegistry); 425 } 426 427 @property bool dryRun() const { return m_dryRun; } 428 @property void dryRun(bool v) { m_dryRun = v; } 429 430 /** Returns the root path (usually the current working directory). 431 */ 432 @property NativePath rootPath() const { return m_rootPath; } 433 /// ditto 434 deprecated("Changing the root path is deprecated as it has non-obvious pitfalls " ~ 435 "(e.g. settings aren't reloaded). Instantiate a new `Dub` instead") 436 @property void rootPath(NativePath root_path) 437 { 438 m_rootPath = root_path; 439 if (!m_rootPath.absolute) m_rootPath = getWorkingDirectory() ~ m_rootPath; 440 } 441 442 /// Returns the name listed in the dub.json of the current 443 /// application. 444 @property string projectName() const { return m_project.name; } 445 446 @property string mainRecipePath() const { return m_mainRecipePath; } 447 /// Whenever the switch --recipe= is supplied, this member will be populated. 448 @property string mainRecipePath(string recipePath) 449 { 450 return m_mainRecipePath = recipePath; 451 } 452 453 454 @property NativePath projectPath() const { return this.m_project.rootPackage.path; } 455 456 @property string[] configurations() const { return m_project.configurations; } 457 458 @property inout(PackageManager) packageManager() inout { return m_packageManager; } 459 460 @property inout(Project) project() inout { return m_project; } 461 462 @property inout(PackageSupplier)[] packageSuppliers() inout { return m_packageSuppliers; } 463 464 /** Returns the default compiler binary to use for building D code. 465 466 If set, the "defaultCompiler" field of the DUB user or system 467 configuration file will be used. Otherwise the PATH environment variable 468 will be searched for files named "dmd", "gdc", "gdmd", "ldc2", "ldmd2" 469 (in that order, taking into account operating system specific file 470 extensions) and the first match is returned. If no match is found, "dmd" 471 will be used. 472 */ 473 @property string defaultCompiler() const { return m_defaultCompiler; } 474 475 /** Returns the default architecture to use for building D code. 476 477 If set, the "defaultArchitecture" field of the DUB user or system 478 configuration file will be used. Otherwise null will be returned. 479 */ 480 @property string defaultArchitecture() const { return this.m_config.defaultArchitecture; } 481 482 /** Returns the default low memory option to use for building D code. 483 484 If set, the "defaultLowMemory" field of the DUB user or system 485 configuration file will be used. Otherwise false will be returned. 486 */ 487 @property bool defaultLowMemory() const { return this.m_config.defaultLowMemory; } 488 489 @property const(string[string]) defaultEnvironments() const { return this.m_config.defaultEnvironments; } 490 @property const(string[string]) defaultBuildEnvironments() const { return this.m_config.defaultBuildEnvironments; } 491 @property const(string[string]) defaultRunEnvironments() const { return this.m_config.defaultRunEnvironments; } 492 @property const(string[string]) defaultPreGenerateEnvironments() const { return this.m_config.defaultPreGenerateEnvironments; } 493 @property const(string[string]) defaultPostGenerateEnvironments() const { return this.m_config.defaultPostGenerateEnvironments; } 494 @property const(string[string]) defaultPreBuildEnvironments() const { return this.m_config.defaultPreBuildEnvironments; } 495 @property const(string[string]) defaultPostBuildEnvironments() const { return this.m_config.defaultPostBuildEnvironments; } 496 @property const(string[string]) defaultPreRunEnvironments() const { return this.m_config.defaultPreRunEnvironments; } 497 @property const(string[string]) defaultPostRunEnvironments() const { return this.m_config.defaultPostRunEnvironments; } 498 499 /** Loads the package that resides within the configured `rootPath`. 500 */ 501 void loadPackage() 502 { 503 loadPackage(m_rootPath); 504 } 505 506 /// Loads the package from the specified path as the main project package. 507 void loadPackage(NativePath path) 508 { 509 auto pack = this.m_packageManager.getOrLoadPackage( 510 path, NativePath.init, false, StrictMode.Warn); 511 this.loadPackage(pack); 512 } 513 514 /// Loads a specific package as the main project package (can be a sub package) 515 void loadPackage(Package pack) 516 { 517 auto selections = Project.loadSelections(pack); 518 m_project = new Project(m_packageManager, pack, selections); 519 } 520 521 /** Loads a single file package. 522 523 Single-file packages are D files that contain a package recipe comment 524 at their top. A recipe comment must be a nested `/+ ... +/` style 525 comment, containing the virtual recipe file name and a colon, followed by the 526 recipe contents (what would normally be in dub.sdl/dub.json). 527 528 Example: 529 --- 530 /+ dub.sdl: 531 name "test" 532 dependency "vibe-d" version="~>0.7.29" 533 +/ 534 import vibe.http.server; 535 536 void main() 537 { 538 auto settings = new HTTPServerSettings; 539 settings.port = 8080; 540 listenHTTP(settings, &hello); 541 } 542 543 void hello(HTTPServerRequest req, HTTPServerResponse res) 544 { 545 res.writeBody("Hello, World!"); 546 } 547 --- 548 549 The script above can be invoked with "dub --single test.d". 550 */ 551 void loadSingleFilePackage(NativePath path) 552 { 553 import dub.recipe.io : parsePackageRecipe; 554 import std.file : readText; 555 import std.path : baseName, stripExtension; 556 557 path = makeAbsolute(path); 558 559 string file_content = readText(path.toNativeString()); 560 561 if (file_content.startsWith("#!")) { 562 auto idx = file_content.indexOf('\n'); 563 enforce(idx > 0, "The source fine doesn't contain anything but a shebang line."); 564 file_content = file_content[idx+1 .. $]; 565 } 566 567 file_content = file_content.strip(); 568 569 string recipe_content; 570 571 if (file_content.startsWith("/+")) { 572 file_content = file_content[2 .. $]; 573 auto idx = file_content.indexOf("+/"); 574 enforce(idx >= 0, "Missing \"+/\" to close comment."); 575 recipe_content = file_content[0 .. idx].strip(); 576 } else throw new Exception("The source file must start with a recipe comment."); 577 578 auto nidx = recipe_content.indexOf('\n'); 579 580 auto idx = recipe_content.indexOf(':'); 581 enforce(idx > 0 && (nidx < 0 || nidx > idx), 582 "The first line of the recipe comment must list the recipe file name followed by a colon (e.g. \"/+ dub.sdl:\")."); 583 auto recipe_filename = recipe_content[0 .. idx]; 584 recipe_content = recipe_content[idx+1 .. $]; 585 auto recipe_default_package_name = path.toString.baseName.stripExtension.strip; 586 587 const PackageName empty; 588 auto recipe = parsePackageRecipe(recipe_content, recipe_filename, empty, recipe_default_package_name); 589 enforce(recipe.buildSettings.sourceFiles.length == 0, "Single-file packages are not allowed to specify source files."); 590 enforce(recipe.buildSettings.sourcePaths.length == 0, "Single-file packages are not allowed to specify source paths."); 591 enforce(recipe.buildSettings.cSourcePaths.length == 0, "Single-file packages are not allowed to specify C source paths."); 592 enforce(recipe.buildSettings.importPaths.length == 0, "Single-file packages are not allowed to specify import paths."); 593 enforce(recipe.buildSettings.cImportPaths.length == 0, "Single-file packages are not allowed to specify C import paths."); 594 recipe.buildSettings.sourceFiles[""] = [path.toNativeString()]; 595 recipe.buildSettings.sourcePaths[""] = []; 596 recipe.buildSettings.cSourcePaths[""] = []; 597 recipe.buildSettings.importPaths[""] = []; 598 recipe.buildSettings.cImportPaths[""] = []; 599 recipe.buildSettings.mainSourceFile = path.toNativeString(); 600 if (recipe.buildSettings.targetType == TargetType.autodetect) 601 recipe.buildSettings.targetType = TargetType.executable; 602 603 auto pack = new Package(recipe, path.parentPath, null, "~master"); 604 loadPackage(pack); 605 } 606 /// ditto 607 void loadSingleFilePackage(string path) 608 { 609 loadSingleFilePackage(NativePath(path)); 610 } 611 612 /** Gets the default configuration for a particular build platform. 613 614 This forwards to `Project.getDefaultConfiguration` and requires a 615 project to be loaded. 616 */ 617 string getDefaultConfiguration(in BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); } 618 619 /** Attempts to upgrade the dependency selection of the loaded project. 620 621 Params: 622 options = Flags that control how the upgrade is carried out 623 packages_to_upgrade = Optional list of packages. If this list 624 contains one or more packages, only those packages will 625 be upgraded. Otherwise, all packages will be upgraded at 626 once. 627 */ 628 void upgrade(UpgradeOptions options, string[] packages_to_upgrade = null) 629 { 630 // clear non-existent version selections 631 if (!(options & UpgradeOptions.upgrade)) { 632 next_pack: 633 foreach (p; m_project.selections.selectedPackages) { 634 const name = PackageName(p); // Always a main package name 635 auto dep = m_project.selections.getSelectedVersion(name); 636 if (!dep.path.empty) { 637 auto path = dep.path; 638 if (!path.absolute) path = this.rootPath ~ path; 639 try if (m_packageManager.getOrLoadPackage(path)) continue; 640 catch (Exception e) { logDebug("Failed to load path based selection: %s", e.toString().sanitize); } 641 } else if (!dep.repository.empty) { 642 if (m_packageManager.loadSCMPackage(name, dep.repository)) 643 continue; 644 } else { 645 if (m_packageManager.getPackage(name, dep.version_)) continue; 646 foreach (ps; m_packageSuppliers) { 647 try { 648 auto versions = ps.getVersions(name); 649 if (versions.canFind!(v => dep.matches(v, VersionMatchMode.strict))) 650 continue next_pack; 651 } catch (Exception e) { 652 logWarn("Error querying versions for %s, %s: %s", p, ps.description, e.msg); 653 logDebug("Full error: %s", e.toString().sanitize()); 654 } 655 } 656 } 657 658 logWarn("Selected package %s %s doesn't exist. Using latest matching version instead.", p, dep); 659 m_project.selections.deselectVersion(name); 660 } 661 } 662 663 auto resolver = new DependencyVersionResolver( 664 this, options, m_project.rootPackage, m_project.selections); 665 Dependency[PackageName] versions = resolver.resolve(packages_to_upgrade); 666 667 if (options & UpgradeOptions.dryRun) { 668 bool any = false; 669 string rootbasename = PackageName(m_project.rootPackage.name).main.toString(); 670 671 foreach (p, ver; versions) { 672 if (!ver.path.empty || !ver.repository.empty) continue; 673 674 auto basename = p.main; 675 if (basename.toString() == rootbasename) continue; 676 677 if (!m_project.selections.hasSelectedVersion(basename)) { 678 logInfo("Upgrade", Color.cyan, 679 "Package %s would be selected with version %s", basename, ver); 680 any = true; 681 continue; 682 } 683 auto sver = m_project.selections.getSelectedVersion(basename); 684 if (!sver.path.empty || !sver.repository.empty) continue; 685 if (ver.version_ <= sver.version_) continue; 686 logInfo("Upgrade", Color.cyan, 687 "%s would be upgraded from %s to %s.", 688 basename.toString().color(Mode.bold), sver, ver); 689 any = true; 690 } 691 if (any) logInfo("Use \"%s\" to perform those changes", "dub upgrade".color(Mode.bold)); 692 return; 693 } 694 695 foreach (name, ver; versions) { 696 assert(!name.sub, "Resolved packages contain a sub package!?: " ~ name.toString()); 697 Package pack; 698 if (!ver.path.empty) { 699 try pack = m_packageManager.getOrLoadPackage(ver.path); 700 catch (Exception e) { 701 logDebug("Failed to load path based selection: %s", e.toString().sanitize); 702 continue; 703 } 704 } else if (!ver.repository.empty) { 705 pack = m_packageManager.loadSCMPackage(name, ver.repository); 706 } else { 707 assert(ver.isExactVersion, "Resolved dependency is neither path, nor repository, nor exact version based!?"); 708 pack = m_packageManager.getPackage(name, ver.version_); 709 if (pack && m_packageManager.isManagedPackage(pack) 710 && ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0) 711 { 712 // TODO: only re-install if there is actually a new commit available 713 logInfo("Re-installing branch based dependency %s %s", name, ver.toString()); 714 m_packageManager.remove(pack); 715 pack = null; 716 } 717 } 718 719 FetchOptions fetchOpts; 720 fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none; 721 if (!pack) this.fetch(name, ver.version_, fetchOpts, defaultPlacementLocation, "getting selected version"); 722 if ((options & UpgradeOptions.select) && name.toString() != m_project.rootPackage.name) { 723 if (!ver.repository.empty) { 724 m_project.selections.selectVersion(name, ver.repository); 725 } else if (ver.path.empty) { 726 m_project.selections.selectVersion(name, ver.version_); 727 } else { 728 NativePath relpath = ver.path; 729 if (relpath.absolute) relpath = relpath.relativeTo(m_project.rootPackage.path); 730 m_project.selections.selectVersion(name, relpath); 731 } 732 } 733 } 734 735 string[] missingDependenciesBeforeReinit = m_project.missingDependencies; 736 m_project.reinit(); 737 738 if (!m_project.hasAllDependencies) { 739 auto resolvedDependencies = setDifference( 740 assumeSorted(missingDependenciesBeforeReinit), 741 assumeSorted(m_project.missingDependencies) 742 ); 743 if (!resolvedDependencies.empty) 744 upgrade(options, m_project.missingDependencies); 745 } 746 747 if ((options & UpgradeOptions.select) && !(options & (UpgradeOptions.noSaveSelections | UpgradeOptions.dryRun))) 748 m_project.saveSelections(); 749 } 750 751 /** Generate project files for a specified generator. 752 753 Any existing project files will be overridden. 754 */ 755 void generateProject(string ide, GeneratorSettings settings) 756 { 757 settings.cache = this.m_dirs.cache; 758 if (settings.overrideToolWorkingDirectory is NativePath.init) 759 settings.overrideToolWorkingDirectory = m_rootPath; 760 // With a requested `unittest` config, switch to the special test runner 761 // config (which doesn't require an existing `unittest` configuration). 762 if (settings.config == "unittest") { 763 const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun); 764 if (test_config) settings.config = test_config; 765 } 766 767 auto generator = createProjectGenerator(ide, m_project); 768 if (m_dryRun) return; // TODO: pass m_dryRun to the generator 769 generator.generate(settings); 770 } 771 772 /** Generate project files using the special test runner (`dub test`) configuration. 773 774 Any existing project files will be overridden. 775 */ 776 void testProject(GeneratorSettings settings, string config, NativePath custom_main_file) 777 { 778 settings.cache = this.m_dirs.cache; 779 if (settings.overrideToolWorkingDirectory is NativePath.init) 780 settings.overrideToolWorkingDirectory = m_rootPath; 781 if (!custom_main_file.empty && !custom_main_file.absolute) custom_main_file = m_rootPath ~ custom_main_file; 782 783 const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun, config, custom_main_file); 784 if (!test_config) return; // target type "none" 785 786 settings.config = test_config; 787 788 auto generator = createProjectGenerator("build", m_project); 789 generator.generate(settings); 790 } 791 792 /** Executes D-Scanner tests on the current project. **/ 793 void lintProject(string[] args) 794 { 795 if (m_dryRun) return; 796 797 auto tool = PackageName("dscanner"); 798 799 auto tool_pack = m_packageManager.getBestPackage(tool); 800 if (!tool_pack) { 801 logInfo("Hint", Color.light_blue, "%s is not present, getting and storing it locally", tool); 802 tool_pack = this.fetch(tool); 803 } 804 805 auto dscanner_dub = new Dub(null, m_packageSuppliers); 806 dscanner_dub.loadPackage(tool_pack); 807 dscanner_dub.upgrade(UpgradeOptions.select); 808 809 GeneratorSettings settings = this.makeAppSettings(); 810 foreach (dependencyPackage; m_project.dependencies) 811 { 812 auto cfgs = m_project.getPackageConfigs(settings.platform, null, true); 813 auto buildSettings = dependencyPackage.getBuildSettings(settings.platform, cfgs[dependencyPackage.name]); 814 foreach (importPath; buildSettings.importPaths) { 815 settings.runArgs ~= ["-I", (dependencyPackage.path ~ importPath).toNativeString()]; 816 } 817 foreach (cimportPath; buildSettings.cImportPaths) { 818 settings.runArgs ~= ["-I", (dependencyPackage.path ~ cimportPath).toNativeString()]; 819 } 820 } 821 822 string configFilePath = (m_project.rootPackage.path ~ "dscanner.ini").toNativeString(); 823 if (!args.canFind("--config") && exists(configFilePath)) { 824 settings.runArgs ~= ["--config", configFilePath]; 825 } 826 827 settings.runArgs ~= args ~ [m_project.rootPackage.path.toNativeString()]; 828 dscanner_dub.generateProject("build", settings); 829 } 830 831 /** Prints the specified build settings necessary for building the root package. 832 */ 833 void listProjectData(GeneratorSettings settings, string[] requestedData, ListBuildSettingsFormat list_type) 834 { 835 import std.stdio; 836 import std.ascii : newline; 837 838 if (settings.overrideToolWorkingDirectory is NativePath.init) 839 settings.overrideToolWorkingDirectory = m_rootPath; 840 841 // Split comma-separated lists 842 string[] requestedDataSplit = 843 requestedData 844 .map!(a => a.splitter(",").map!strip) 845 .joiner() 846 .array(); 847 848 auto data = m_project.listBuildSettings(settings, requestedDataSplit, list_type); 849 850 string delimiter; 851 final switch (list_type) with (ListBuildSettingsFormat) { 852 case list: delimiter = newline ~ newline; break; 853 case listNul: delimiter = "\0\0"; break; 854 case commandLine: delimiter = " "; break; 855 case commandLineNul: delimiter = "\0\0"; break; 856 } 857 858 write(data.joiner(delimiter)); 859 if (delimiter != "\0\0") writeln(); 860 } 861 862 /// Cleans intermediate/cache files of the given package (or all packages) 863 deprecated("Use `clean(Package)` instead") 864 void cleanPackage(NativePath path) 865 { 866 this.clean(Package.load(path)); 867 } 868 869 /// Ditto 870 void clean() 871 { 872 const cache = this.m_dirs.cache; 873 logInfo("Cleaning", Color.green, "all artifacts at %s", 874 cache.toNativeString().color(Mode.bold)); 875 if (existsFile(cache)) 876 rmdirRecurse(cache.toNativeString()); 877 } 878 879 /// Ditto 880 void clean(Package pack) 881 { 882 const cache = this.packageCache(pack); 883 logInfo("Cleaning", Color.green, "artifacts for package %s at %s", 884 pack.name.color(Mode.bold), 885 cache.toNativeString().color(Mode.bold)); 886 887 // TODO: clear target files and copy files 888 if (existsFile(cache)) 889 rmdirRecurse(cache.toNativeString()); 890 } 891 892 deprecated("Use the overload that accepts either a `Version` or a `VersionRange` as second argument") 893 Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "") 894 { 895 const vrange = dep.visit!( 896 (VersionRange range) => range, 897 function VersionRange (any) { throw new Exception("Cannot call `dub.fetch` with a " ~ typeof(any).stringof ~ " dependency"); } 898 ); 899 return this.fetch(packageId, vrange, location, options, reason); 900 } 901 902 deprecated("Use `fetch(PackageName, Version, [FetchOptions, PlacementLocation, string])`") 903 Package fetch(string name, in Version vers, PlacementLocation location, FetchOptions options, string reason = "") 904 { 905 const n = PackageName(name); 906 return this.fetch(n, VersionRange(vers, vers), options, location, reason); 907 } 908 909 deprecated("Use `fetch(PackageName, VersionRange, [FetchOptions, PlacementLocation, string])`") 910 Package fetch(string name, in VersionRange range, PlacementLocation location, FetchOptions options, string reason = "") 911 { 912 const n = PackageName(name); 913 return this.fetch(n, range, options, location, reason); 914 } 915 916 /** 917 * Fetches a missing package and stores it locally 918 * 919 * This will query the configured PackageSuppliers for a package 920 * matching the `range` specification, store it locally, and load 921 * it in the `PackageManager`. Note that unlike the command line 922 * version, this function is not idempotent and will remove an 923 * existing package and re-download it. 924 * 925 * Params: 926 * name = Name of the package to retrieve. Subpackages will lead 927 * to the main package being retrieved and the subpackage 928 * being returned (if it exists). 929 * vers = For `Version` overloads, the exact version to return. 930 * range = The `VersionRange` to match. Default to `Any` to fetch 931 * the latest version. 932 * options = A set of options used for fetching / matching versions. 933 * location = Where to store the retrieved package. Default to the 934 * configured `defaultPlacementLocation`. 935 * reason = Optionally, the reason for retriving this package. 936 * This is used only for logging. 937 * 938 * Returns: 939 * The fetched or loaded `Package`, or `null` in dry-run mode. 940 * 941 * Throws: 942 * If the package cannot be fetched or loaded. 943 */ 944 Package fetch(in PackageName name, in Version vers, 945 FetchOptions options = FetchOptions.none, string reason = "") 946 { 947 return this.fetch(name, VersionRange(vers, vers), options, 948 this.defaultPlacementLocation, reason); 949 } 950 951 /// Ditto 952 Package fetch(in PackageName name, in Version vers, FetchOptions options, 953 PlacementLocation location, string reason = "") 954 { 955 return this.fetch(name, VersionRange(vers, vers), options, 956 this.defaultPlacementLocation, reason); 957 } 958 959 /// Ditto 960 Package fetch(in PackageName name, in VersionRange range = VersionRange.Any, 961 FetchOptions options = FetchOptions.none, string reason = "") 962 { 963 return this.fetch(name, range, options, this.defaultPlacementLocation, reason); 964 } 965 966 /// Ditto 967 Package fetch(in PackageName name, in VersionRange range, FetchOptions options, 968 PlacementLocation location, string reason = "") 969 { 970 Json pinfo; 971 PackageSupplier supplier; 972 foreach(ps; m_packageSuppliers){ 973 try { 974 pinfo = ps.fetchPackageRecipe(name.main, range, (options & FetchOptions.usePrerelease) != 0); 975 if (pinfo.type == Json.Type.null_) 976 continue; 977 supplier = ps; 978 break; 979 } catch(Exception e) { 980 logWarn("Package %s not found for %s: %s", name, ps.description, e.msg); 981 logDebug("Full error: %s", e.toString().sanitize()); 982 } 983 } 984 enforce(!pinfo.type.among(Json.Type.undefined, Json.Type.null_), 985 "No package %s was found matching the dependency %s" 986 .format(name, range)); 987 Version ver = Version(pinfo["version"].get!string); 988 989 // always upgrade branch based versions - TODO: actually check if there is a new commit available 990 Package existing = m_packageManager.getPackage(name, ver, location); 991 if (options & FetchOptions.printOnly) { 992 if (existing && existing.version_ != ver) 993 logInfo("A new version for %s is available (%s -> %s). Run \"%s\" to switch.", 994 name.toString().color(Mode.bold), existing, ver, 995 text("dub upgrade ", name.main).color(Mode.bold)); 996 return null; 997 } 998 999 if (existing) { 1000 if (!ver.isBranch() || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) { 1001 // TODO: support git working trees by performing a "git pull" instead of this 1002 logDiagnostic("Package %s %s (in %s packages) is already present with the latest version, skipping upgrade.", 1003 name, ver, location.toString); 1004 return existing; 1005 } else { 1006 logInfo("Removing", Color.yellow, "%s %s to prepare " ~ 1007 "replacement with a new version", name.toString().color(Mode.bold), 1008 ver); 1009 if (!m_dryRun) m_packageManager.remove(existing); 1010 } 1011 } 1012 1013 if (reason.length) logInfo("Fetching", Color.yellow, "%s %s (%s)", name.toString().color(Mode.bold), ver, reason); 1014 else logInfo("Fetching", Color.yellow, "%s %s", name.toString().color(Mode.bold), ver); 1015 if (m_dryRun) return null; 1016 1017 logDebug("Acquiring package zip file"); 1018 1019 // repeat download on corrupted zips, see #1336 1020 foreach_reverse (i; 0..3) 1021 { 1022 import std.zip : ZipException; 1023 1024 auto data = supplier.fetchPackage(name.main, range, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail? 1025 logDiagnostic("Placing to %s...", location.toString()); 1026 1027 try { 1028 return m_packageManager.store(data, location, name.main, ver); 1029 } catch (ZipException e) { 1030 logInfo("Failed to extract zip archive for %s@%s...", name, ver); 1031 // re-throw the exception at the end of the loop 1032 if (i == 0) 1033 throw e; 1034 } 1035 } 1036 assert(0, "Should throw a ZipException instead."); 1037 } 1038 1039 /** Removes a specific locally cached package. 1040 1041 This will delete the package files from disk and removes the 1042 corresponding entry from the list of known packages. 1043 1044 Params: 1045 pack = Package instance to remove 1046 */ 1047 void remove(in Package pack) 1048 { 1049 logInfo("Removing", Color.yellow, "%s (in %s)", pack.name.color(Mode.bold), pack.path.toNativeString()); 1050 if (!m_dryRun) m_packageManager.remove(pack); 1051 } 1052 1053 /// Compatibility overload. Use the version without a `force_remove` argument instead. 1054 deprecated("Use `remove(pack)` directly instead, the boolean has no effect") 1055 void remove(in Package pack, bool force_remove) 1056 { 1057 remove(pack); 1058 } 1059 1060 /// @see remove(string, string, RemoveLocation) 1061 enum RemoveVersionWildcard = "*"; 1062 1063 /** Removes one or more versions of a locally cached package. 1064 1065 This will remove a given package with a specified version from the 1066 given location. It will remove at most one package, unless `version_` 1067 is set to `RemoveVersionWildcard`. 1068 1069 Params: 1070 name = Name of the package to be removed 1071 location = Specifies the location to look for the given package 1072 name/version. 1073 resolve_version = Callback to select package version. 1074 */ 1075 void remove(in PackageName name, PlacementLocation location, 1076 scope size_t delegate(in Package[] packages) resolve_version) 1077 { 1078 enforce(name.main.toString().length); 1079 enforce(!name.sub.length, "Cannot remove subpackage %s, remove %s instead" 1080 .format(name, name.main)); 1081 if (location == PlacementLocation.local) { 1082 logInfo("To remove a locally placed package, make sure you don't have any data" 1083 ~ "\nleft in it's directory and then simply remove the whole directory."); 1084 throw new Exception("dub cannot remove locally installed packages."); 1085 } 1086 1087 Package[] packages; 1088 1089 // Retrieve packages to be removed. 1090 foreach(pack; m_packageManager.getPackageIterator(name.toString())) 1091 if (m_packageManager.isManagedPackage(pack)) 1092 packages ~= pack; 1093 1094 // Check validity of packages to be removed. 1095 enforce(!packages.empty, "Cannot find package '%s' to remove at %s location" 1096 .format(name, location.toString())); 1097 1098 // Sort package list in ascending version order 1099 packages.sort!((a, b) => a.version_ < b.version_); 1100 1101 immutable idx = resolve_version(packages); 1102 if (idx == size_t.max) 1103 return; 1104 else if (idx != packages.length) 1105 packages = packages[idx .. idx + 1]; 1106 1107 logDebug("Removing %s packages.", packages.length); 1108 foreach(pack; packages) { 1109 try { 1110 remove(pack); 1111 } catch (Exception e) { 1112 logError("Failed to remove %s %s: %s", name, pack, e.msg); 1113 logInfo("Continuing with other packages (if any)."); 1114 } 1115 } 1116 } 1117 1118 deprecated("Use `remove(PackageName, PlacementLocation, delegate)`") 1119 void remove(string name, PlacementLocation location, 1120 scope size_t delegate(in Package[] packages) resolve_version) 1121 { 1122 this.remove(PackageName(name), location, resolve_version); 1123 } 1124 1125 /// Compatibility overload. Use the version without a `force_remove` argument instead. 1126 deprecated("Use the overload without the 3rd argument (`force_remove`) instead") 1127 void remove(string package_id, PlacementLocation location, bool force_remove, 1128 scope size_t delegate(in Package[] packages) resolve_version) 1129 { 1130 remove(package_id, location, resolve_version); 1131 } 1132 1133 /** Removes a specific version of a package. 1134 1135 Params: 1136 name = Name of the package to be removed 1137 version_ = Identifying a version or a wild card. If an empty string 1138 is passed, the package will be removed from the location, if 1139 there is only one version retrieved. This will throw an 1140 exception, if there are multiple versions retrieved. 1141 location = Specifies the location to look for the given package 1142 name/version. 1143 */ 1144 void remove(in PackageName name, string version_, PlacementLocation location) 1145 { 1146 remove(name, location, (in packages) { 1147 if (version_ == RemoveVersionWildcard || version_.empty) 1148 return packages.length; 1149 1150 foreach (i, p; packages) { 1151 if (p.version_ == Version(version_)) 1152 return i; 1153 } 1154 throw new Exception("Cannot find package '%s@%s' to remove at %s location" 1155 .format(name, version_, location.toString())); 1156 }); 1157 } 1158 1159 deprecated("Use `remove(PackageName, string, PlacementLocation)`") 1160 void remove(string name, string version_, PlacementLocation location) 1161 { 1162 this.remove(PackageName(name), version_, location); 1163 } 1164 1165 /// Compatibility overload. Use the version without a `force_remove` argument instead. 1166 deprecated("Use the overload without force_remove instead") 1167 void remove(string package_id, string version_, PlacementLocation location, bool force_remove) 1168 { 1169 remove(package_id, version_, location); 1170 } 1171 1172 /** Adds a directory to the list of locally known packages. 1173 1174 Forwards to `PackageManager.addLocalPackage`. 1175 1176 Params: 1177 path = Path to the package 1178 ver = Optional version to associate with the package (can be left 1179 empty) 1180 system = Make the package known system wide instead of user wide 1181 (requires administrator privileges). 1182 1183 See_Also: `removeLocalPackage` 1184 */ 1185 deprecated("Use `addLocalPackage(string, string, PlacementLocation)` instead") 1186 void addLocalPackage(string path, string ver, bool system) 1187 { 1188 this.addLocalPackage(path, ver, system ? PlacementLocation.system : PlacementLocation.user); 1189 } 1190 1191 /// Ditto 1192 void addLocalPackage(string path, string ver, PlacementLocation loc) 1193 { 1194 if (m_dryRun) return; 1195 this.m_packageManager.addLocalPackage(makeAbsolute(path), ver, loc); 1196 } 1197 1198 /** Removes a directory from the list of locally known packages. 1199 1200 Forwards to `PackageManager.removeLocalPackage`. 1201 1202 Params: 1203 path = Path to the package 1204 system = Make the package known system wide instead of user wide 1205 (requires administrator privileges). 1206 1207 See_Also: `addLocalPackage` 1208 */ 1209 deprecated("Use `removeLocalPackage(string, string, PlacementLocation)` instead") 1210 void removeLocalPackage(string path, bool system) 1211 { 1212 this.removeLocalPackage(path, system ? PlacementLocation.system : PlacementLocation.user); 1213 } 1214 1215 /// Ditto 1216 void removeLocalPackage(string path, PlacementLocation loc) 1217 { 1218 if (m_dryRun) return; 1219 this.m_packageManager.removeLocalPackage(makeAbsolute(path), loc); 1220 } 1221 1222 /** Registers a local directory to search for packages to use for satisfying 1223 dependencies. 1224 1225 Params: 1226 path = Path to a directory containing package directories 1227 system = Make the package known system wide instead of user wide 1228 (requires administrator privileges). 1229 1230 See_Also: `removeSearchPath` 1231 */ 1232 deprecated("Use `addSearchPath(string, PlacementLocation)` instead") 1233 void addSearchPath(string path, bool system) 1234 { 1235 this.addSearchPath(path, system ? PlacementLocation.system : PlacementLocation.user); 1236 } 1237 1238 /// Ditto 1239 void addSearchPath(string path, PlacementLocation loc) 1240 { 1241 if (m_dryRun) return; 1242 this.m_packageManager.addSearchPath(makeAbsolute(path), loc); 1243 } 1244 1245 /** Deregisters a local directory search path. 1246 1247 Params: 1248 path = Path to a directory containing package directories 1249 system = Make the package known system wide instead of user wide 1250 (requires administrator privileges). 1251 1252 See_Also: `addSearchPath` 1253 */ 1254 deprecated("Use `removeSearchPath(string, PlacementLocation)` instead") 1255 void removeSearchPath(string path, bool system) 1256 { 1257 this.removeSearchPath(path, system ? PlacementLocation.system : PlacementLocation.user); 1258 } 1259 1260 /// Ditto 1261 void removeSearchPath(string path, PlacementLocation loc) 1262 { 1263 if (m_dryRun) return; 1264 this.m_packageManager.removeSearchPath(makeAbsolute(path), loc); 1265 } 1266 1267 /** Queries all package suppliers with the given query string. 1268 1269 Returns a list of tuples, where the first entry is the human readable 1270 name of the package supplier and the second entry is the list of 1271 matched packages. 1272 1273 Params: 1274 query = the search term to match packages on 1275 1276 See_Also: `PackageSupplier.searchPackages` 1277 */ 1278 auto searchPackages(string query) 1279 { 1280 import std.typecons : Tuple, tuple; 1281 Tuple!(string, PackageSupplier.SearchResult[])[] results; 1282 foreach (ps; this.m_packageSuppliers) { 1283 try 1284 results ~= tuple(ps.description, ps.searchPackages(query)); 1285 catch (Exception e) { 1286 logWarn("Searching %s for '%s' failed: %s", ps.description, query, e.msg); 1287 } 1288 } 1289 return results.filter!(tup => tup[1].length); 1290 } 1291 1292 /** Returns a list of all available versions (including branches) for a 1293 particular package. 1294 1295 The list returned is based on the registered package suppliers. Local 1296 packages are not queried in the search for versions. 1297 1298 See_also: `getLatestVersion` 1299 */ 1300 Version[] listPackageVersions(in PackageName name) 1301 { 1302 Version[] versions; 1303 foreach (ps; this.m_packageSuppliers) { 1304 try versions ~= ps.getVersions(name); 1305 catch (Exception e) { 1306 logWarn("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg); 1307 } 1308 } 1309 return versions.sort().uniq.array; 1310 } 1311 1312 deprecated("Use `listPackageVersions(PackageName)`") 1313 Version[] listPackageVersions(string name) 1314 { 1315 const n = PackageName(name); 1316 return this.listPackageVersions(n); 1317 } 1318 1319 /** Returns the latest available version for a particular package. 1320 1321 This function returns the latest numbered version of a package. If no 1322 numbered versions are available, it will return an available branch, 1323 preferring "~master". 1324 1325 Params: 1326 name = The name of the package in question. 1327 prefer_stable = If set to `true` (the default), returns the latest 1328 stable version, even if there are newer pre-release versions. 1329 1330 See_also: `listPackageVersions` 1331 */ 1332 Version getLatestVersion(in PackageName name, bool prefer_stable = true) 1333 { 1334 auto vers = this.listPackageVersions(name); 1335 enforce(!vers.empty, 1336 "Failed to find any valid versions for a package name of '%s'." 1337 .format(name)); 1338 auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array; 1339 if (prefer_stable && final_versions.length) return final_versions[$-1]; 1340 else return vers[$-1]; 1341 } 1342 1343 deprecated("Use `getLatestVersion(PackageName, bool)`") 1344 Version getLatestVersion(string name, bool prefer_stable = true) 1345 { 1346 const n = PackageName(name); 1347 return this.getLatestVersion(n, prefer_stable); 1348 } 1349 1350 /** Initializes a directory with a package skeleton. 1351 1352 Params: 1353 path = Path of the directory to create the new package in. The 1354 directory will be created if it doesn't exist. 1355 deps = List of dependencies to add to the package recipe. 1356 type = Specifies the type of the application skeleton to use. 1357 format = Determines the package recipe format to use. 1358 recipe_callback = Optional callback that can be used to 1359 customize the recipe before it gets written. 1360 app_args = Arguments to provide to the custom initialization routine. 1361 */ 1362 void createEmptyPackage(NativePath path, string[] deps, string type, 1363 PackageFormat format = PackageFormat.sdl, 1364 scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null, 1365 string[] app_args = []) 1366 { 1367 if (!path.absolute) path = m_rootPath ~ path; 1368 path.normalize(); 1369 1370 VersionRange[string] depVers; 1371 string[] notFound; // keep track of any failed packages in here 1372 foreach (dep; deps) { 1373 const name = PackageName(dep); 1374 try { 1375 Version ver = this.getLatestVersion(name); 1376 if (ver.isBranch()) 1377 depVers[dep] = VersionRange(ver); 1378 else 1379 depVers[dep] = VersionRange.fromString("~>" ~ ver.toString()); 1380 } catch (Exception e) { 1381 notFound ~= dep; 1382 } 1383 } 1384 1385 if(notFound.length > 1){ 1386 throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound)); 1387 } 1388 else if(notFound.length == 1){ 1389 throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound)); 1390 } 1391 1392 if (m_dryRun) return; 1393 1394 initPackage(path, depVers, type, format, recipe_callback); 1395 1396 if (!["vibe.d", "deimos", "minimal"].canFind(type)) { 1397 runCustomInitialization(path, type, app_args); 1398 } 1399 1400 //Act smug to the user. 1401 logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold)); 1402 } 1403 1404 /** 1405 * Run initialization code from a template project 1406 * 1407 * Looks up a project, then get its `init-exec` subpackage, 1408 * and run this to initialize the repository with a default structure. 1409 */ 1410 private void runCustomInitialization(NativePath path, string name, string[] runArgs) 1411 { 1412 auto name_ = PackageName(name); 1413 auto template_pack = m_packageManager.getBestPackage(name_); 1414 if (!template_pack) { 1415 logInfo("%s is not present, getting and storing it locally", name); 1416 template_pack = fetch(name_); 1417 } 1418 1419 Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false); 1420 auto template_dub = new Dub(null, m_packageSuppliers); 1421 template_dub.loadPackage(initSubPackage); 1422 1423 GeneratorSettings settings = this.makeAppSettings(); 1424 settings.runArgs = runArgs; 1425 1426 initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString(); 1427 template_dub.generateProject("build", settings); 1428 } 1429 1430 /** Converts the package recipe of the loaded root package to the given format. 1431 1432 Params: 1433 destination_file_ext = The file extension matching the desired 1434 format. Possible values are "json" or "sdl". 1435 print_only = Print the converted recipe instead of writing to disk 1436 */ 1437 void convertRecipe(string destination_file_ext, bool print_only = false) 1438 { 1439 import std.path : extension; 1440 import std.stdio : stdout; 1441 import dub.recipe.io : serializePackageRecipe, writePackageRecipe; 1442 1443 if (print_only) { 1444 auto dst = stdout.lockingTextWriter; 1445 serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext); 1446 return; 1447 } 1448 1449 auto srcfile = m_project.rootPackage.recipePath; 1450 auto srcext = srcfile.head.name.extension; 1451 if (srcext == "."~destination_file_ext) { 1452 // no logging before this point 1453 tagWidth.push(5); 1454 logError("Package format is already %s.", destination_file_ext); 1455 return; 1456 } 1457 1458 writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe); 1459 removeFile(srcfile); 1460 } 1461 1462 /** Runs DDOX to generate or serve documentation. 1463 1464 Params: 1465 run = If set to true, serves documentation on a local web server. 1466 Otherwise generates actual HTML files. 1467 generate_args = Additional command line arguments to pass to 1468 "ddox generate-html" or "ddox serve-html". 1469 */ 1470 void runDdox(bool run, string[] generate_args = null) 1471 { 1472 import std.process : browse; 1473 1474 if (m_dryRun) return; 1475 1476 // allow to choose a custom ddox tool 1477 auto tool = m_project.rootPackage.recipe.ddoxTool.empty 1478 ? PackageName("ddox") 1479 : PackageName(m_project.rootPackage.recipe.ddoxTool); 1480 1481 auto tool_pack = m_packageManager.getBestPackage(tool); 1482 if (!tool_pack) { 1483 logInfo("%s is not present, getting and storing it user wide", tool); 1484 tool_pack = this.fetch(tool); 1485 } 1486 1487 auto ddox_dub = new Dub(null, m_packageSuppliers); 1488 ddox_dub.loadPackage(tool_pack); 1489 ddox_dub.upgrade(UpgradeOptions.select); 1490 1491 GeneratorSettings settings = this.makeAppSettings(); 1492 1493 auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup; 1494 if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"]; 1495 1496 settings.runArgs = "filter" ~ filterargs ~ "docs.json"; 1497 ddox_dub.generateProject("build", settings); 1498 1499 auto p = tool_pack.path; 1500 p.endsWithSlash = true; 1501 auto tool_path = p.toNativeString(); 1502 1503 if (run) { 1504 settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args; 1505 browse("http://127.0.0.1:8080/"); 1506 } else { 1507 settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args; 1508 } 1509 ddox_dub.generateProject("build", settings); 1510 1511 if (!run) { 1512 // TODO: ddox should copy those files itself 1513 version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`, null, m_rootPath.toNativeString()); 1514 else runCommand("rsync -ru '"~tool_path~"public/' docs/", null, m_rootPath.toNativeString()); 1515 } 1516 } 1517 1518 /** 1519 * Compute and returns the path were artifacts are stored 1520 * 1521 * Expose `dub.generator.generator : packageCache` with this instance's 1522 * configured cache. 1523 */ 1524 protected NativePath packageCache (Package pkg) const 1525 { 1526 return .packageCache(this.m_dirs.cache, pkg); 1527 } 1528 1529 /// Exposed because `commandLine` replicates `generateProject` for `dub describe` 1530 /// instead of treating it like a regular generator... Remove this once the 1531 /// flaw is fixed, and don't add more calls to this function! 1532 package(dub) NativePath cachePathDontUse () const @safe pure nothrow @nogc 1533 { 1534 return this.m_dirs.cache; 1535 } 1536 1537 /// Make a `GeneratorSettings` suitable to generate tools (DDOC, DScanner, etc...) 1538 private GeneratorSettings makeAppSettings () const 1539 { 1540 GeneratorSettings settings; 1541 auto compiler_binary = this.defaultCompiler; 1542 1543 settings.config = "application"; 1544 settings.buildType = "debug"; 1545 settings.compiler = getCompiler(compiler_binary); 1546 settings.platform = settings.compiler.determinePlatform( 1547 settings.buildSettings, compiler_binary, this.defaultArchitecture); 1548 if (this.defaultLowMemory) 1549 settings.buildSettings.options |= BuildOption.lowmem; 1550 if (this.defaultEnvironments) 1551 settings.buildSettings.addEnvironments(this.defaultEnvironments); 1552 if (this.defaultBuildEnvironments) 1553 settings.buildSettings.addBuildEnvironments(this.defaultBuildEnvironments); 1554 if (this.defaultRunEnvironments) 1555 settings.buildSettings.addRunEnvironments(this.defaultRunEnvironments); 1556 if (this.defaultPreGenerateEnvironments) 1557 settings.buildSettings.addPreGenerateEnvironments(this.defaultPreGenerateEnvironments); 1558 if (this.defaultPostGenerateEnvironments) 1559 settings.buildSettings.addPostGenerateEnvironments(this.defaultPostGenerateEnvironments); 1560 if (this.defaultPreBuildEnvironments) 1561 settings.buildSettings.addPreBuildEnvironments(this.defaultPreBuildEnvironments); 1562 if (this.defaultPostBuildEnvironments) 1563 settings.buildSettings.addPostBuildEnvironments(this.defaultPostBuildEnvironments); 1564 if (this.defaultPreRunEnvironments) 1565 settings.buildSettings.addPreRunEnvironments(this.defaultPreRunEnvironments); 1566 if (this.defaultPostRunEnvironments) 1567 settings.buildSettings.addPostRunEnvironments(this.defaultPostRunEnvironments); 1568 settings.run = true; 1569 settings.overrideToolWorkingDirectory = m_rootPath; 1570 1571 return settings; 1572 } 1573 1574 /** 1575 * Determine the default compiler to use for this instance 1576 * 1577 * The default compiler will be used unless --compiler is specified. 1578 * The environment variable `DC` will take precedence over anything, 1579 * followed by the configuration. If nothing is found, the folder in 1580 * which `dub` is installed will be searched, and if nothing is found, 1581 * the $PATH will be searched. 1582 * In the majority of cases, as we distribute `dub` alongside the compiler, 1583 * it will be found once the directory in which dub reside is searched. 1584 * 1585 * Returns: The value to use for the default compiler. 1586 */ 1587 protected string determineDefaultCompiler() const 1588 { 1589 import std.file : thisExePath; 1590 import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator; 1591 import std.range : front; 1592 1593 // Env takes precedence 1594 string result; 1595 if (auto envCompiler = environment.get("DC")) 1596 result = envCompiler; 1597 else 1598 result = this.m_config.defaultCompiler.expandTilde; 1599 if (result.length && result.isAbsolute) 1600 return result; 1601 1602 static immutable BinaryPrefix = `$DUB_BINARY_PATH`; 1603 if (result.startsWith(BinaryPrefix)) 1604 return thisExePath().dirName() ~ result[BinaryPrefix.length .. $]; 1605 1606 if (!find!isDirSeparator(result).empty) 1607 throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ result ~ 1608 "\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead."); 1609 1610 version (Windows) enum sep = ";", exe = ".exe"; 1611 version (Posix) enum sep = ":", exe = ""; 1612 1613 auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; 1614 // If a compiler name is specified, look for it next to dub. 1615 // Otherwise, look for any of the common compilers adjacent to dub. 1616 if (result.length) 1617 { 1618 string compilerPath = buildPath(thisExePath().dirName(), result ~ exe); 1619 if (existsFile(compilerPath)) 1620 return compilerPath; 1621 } 1622 else 1623 { 1624 auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe))); 1625 if (!nextFound.empty) 1626 return buildPath(thisExePath().dirName(), nextFound.front ~ exe); 1627 } 1628 1629 // If nothing found next to dub, search the user's PATH, starting 1630 // with the compiler name from their DUB config file, if specified. 1631 auto paths = environment.get("PATH", "").splitter(sep).map!NativePath; 1632 if (result.length && paths.canFind!(p => existsFile(p ~ (result ~ exe)))) 1633 return result; 1634 foreach (p; paths) { 1635 auto res = compilers.find!(bin => existsFile(p ~ (bin~exe))); 1636 if (!res.empty) 1637 return res.front; 1638 } 1639 return compilers[0]; 1640 } 1641 1642 // This test also relies on the environment and the filesystem, 1643 // as the `makePackageSuppliers` does, and should be refactored. 1644 unittest 1645 { 1646 import dub.test.base : TestDub; 1647 import std.path: buildPath, absolutePath; 1648 1649 auto dub = new TestDub(null, ".", null, SkipPackageSuppliers.configured); 1650 immutable olddc = environment.get("DC", null); 1651 immutable oldpath = environment.get("PATH", null); 1652 immutable testdir = "test-determineDefaultCompiler"; 1653 void repairenv(string name, string var) 1654 { 1655 if (var !is null) 1656 environment[name] = var; 1657 else if (name in environment) 1658 environment.remove(name); 1659 } 1660 scope (exit) repairenv("DC", olddc); 1661 scope (exit) repairenv("PATH", oldpath); 1662 scope (exit) rmdirRecurse(testdir); 1663 1664 version (Windows) enum sep = ";", exe = ".exe"; 1665 version (Posix) enum sep = ":", exe = ""; 1666 1667 immutable dmdpath = testdir.buildPath("dmd", "bin"); 1668 immutable ldcpath = testdir.buildPath("ldc", "bin"); 1669 mkdirRecurse(dmdpath); 1670 mkdirRecurse(ldcpath); 1671 immutable dmdbin = dmdpath.buildPath("dmd"~exe); 1672 immutable ldcbin = ldcpath.buildPath("ldc2"~exe); 1673 std.file.write(dmdbin, null); 1674 std.file.write(ldcbin, null); 1675 1676 environment["DC"] = dmdbin.absolutePath(); 1677 assert(dub.determineDefaultCompiler() == dmdbin.absolutePath()); 1678 1679 environment["DC"] = "dmd"; 1680 environment["PATH"] = dmdpath ~ sep ~ ldcpath; 1681 assert(dub.determineDefaultCompiler() == "dmd"); 1682 1683 environment["DC"] = "ldc2"; 1684 environment["PATH"] = dmdpath ~ sep ~ ldcpath; 1685 assert(dub.determineDefaultCompiler() == "ldc2"); 1686 1687 environment.remove("DC"); 1688 environment["PATH"] = ldcpath ~ sep ~ dmdpath; 1689 assert(dub.determineDefaultCompiler() == "ldc2"); 1690 } 1691 1692 private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; } 1693 private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); } 1694 } 1695 1696 1697 /// Option flags for `Dub.fetch` 1698 enum FetchOptions 1699 { 1700 none = 0, 1701 forceBranchUpgrade = 1<<0, 1702 usePrerelease = 1<<1, 1703 forceRemove = 1<<2, /// Deprecated, does nothing. 1704 printOnly = 1<<3, 1705 } 1706 1707 /// Option flags for `Dub.upgrade` 1708 enum UpgradeOptions 1709 { 1710 none = 0, 1711 upgrade = 1<<1, /// Upgrade existing packages 1712 preRelease = 1<<2, /// include pre-release versions in upgrade 1713 forceRemove = 1<<3, /// Deprecated, does nothing. 1714 select = 1<<4, /// Update the dub.selections.json file with the upgraded versions 1715 dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence 1716 /*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead 1717 /*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect 1718 noSaveSelections = 1<<7, /// Don't store updated selections on disk 1719 } 1720 1721 /// Determines which of the default package suppliers are queried for packages. 1722 public alias SkipPackageSuppliers = SPS; 1723 1724 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) { 1725 protected { 1726 Dub m_dub; 1727 UpgradeOptions m_options; 1728 Dependency[][PackageName] m_packageVersions; 1729 Package[string] m_remotePackages; 1730 SelectedVersions m_selectedVersions; 1731 Package m_rootPackage; 1732 bool[PackageName] m_packagesToUpgrade; 1733 Package[PackageDependency] m_packages; 1734 TreeNodes[][TreeNode] m_children; 1735 } 1736 1737 1738 this(Dub dub, UpgradeOptions options, Package root, SelectedVersions selected_versions) 1739 { 1740 assert(dub !is null); 1741 assert(root !is null); 1742 assert(selected_versions !is null); 1743 1744 if (environment.get("DUB_NO_RESOLVE_LIMIT") !is null) 1745 super(ulong.max); 1746 else 1747 super(1_000_000); 1748 1749 m_dub = dub; 1750 m_options = options; 1751 m_rootPackage = root; 1752 m_selectedVersions = selected_versions; 1753 } 1754 1755 Dependency[PackageName] resolve(string[] filter) 1756 { 1757 foreach (name; filter) 1758 m_packagesToUpgrade[PackageName(name)] = true; 1759 return super.resolve(TreeNode(PackageName(m_rootPackage.name), Dependency(m_rootPackage.version_)), 1760 (m_options & UpgradeOptions.dryRun) == 0); 1761 } 1762 1763 protected bool isFixedPackage(in PackageName pack) 1764 { 1765 return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade; 1766 } 1767 1768 protected override Dependency[] getAllConfigs(in PackageName pack) 1769 { 1770 if (auto pvers = pack in m_packageVersions) 1771 return *pvers; 1772 1773 if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) { 1774 auto ret = [m_selectedVersions.getSelectedVersion(pack)]; 1775 logDiagnostic("Using fixed selection %s %s", pack, ret[0]); 1776 m_packageVersions[pack] = ret; 1777 return ret; 1778 } 1779 1780 logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length); 1781 Version[] versions; 1782 foreach (p; m_dub.packageManager.getPackageIterator(pack.toString())) 1783 versions ~= p.version_; 1784 1785 foreach (ps; m_dub.m_packageSuppliers) { 1786 try { 1787 auto vers = ps.getVersions(pack); 1788 vers.reverse(); 1789 if (!vers.length) { 1790 logDiagnostic("No versions for %s for %s", pack, ps.description); 1791 continue; 1792 } 1793 1794 versions ~= vers; 1795 break; 1796 } catch (Exception e) { 1797 logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg); 1798 logDebug("Full error: %s", e.toString().sanitize); 1799 } 1800 } 1801 1802 // sort by version, descending, and remove duplicates 1803 versions = versions.sort!"a>b".uniq.array; 1804 1805 // move pre-release versions to the back of the list if no preRelease flag is given 1806 if (!(m_options & UpgradeOptions.preRelease)) 1807 versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array; 1808 1809 // filter out invalid/unreachable dependency specs 1810 versions = versions.filter!((v) { 1811 bool valid = getPackage(pack, Dependency(v)) !is null; 1812 if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v); 1813 return valid; 1814 }).array; 1815 1816 if (!versions.length) logDiagnostic("Nothing found for %s", pack); 1817 else logDiagnostic("Return for %s: %s", pack, versions); 1818 1819 auto ret = versions.map!(v => Dependency(v)).array; 1820 m_packageVersions[pack] = ret; 1821 return ret; 1822 } 1823 1824 protected override Dependency[] getSpecificConfigs(in PackageName pack, TreeNodes nodes) 1825 { 1826 if (!nodes.configs.path.empty || !nodes.configs.repository.empty) { 1827 if (getPackage(nodes.pack, nodes.configs)) return [nodes.configs]; 1828 else return null; 1829 } 1830 else return null; 1831 } 1832 1833 1834 protected override TreeNodes[] getChildren(TreeNode node) 1835 { 1836 if (auto pc = node in m_children) 1837 return *pc; 1838 auto ret = getChildrenRaw(node); 1839 m_children[node] = ret; 1840 return ret; 1841 } 1842 1843 private final TreeNodes[] getChildrenRaw(TreeNode node) 1844 { 1845 import std.array : appender; 1846 auto ret = appender!(TreeNodes[]); 1847 auto pack = getPackage(node.pack, node.config); 1848 if (!pack) { 1849 // this can happen when the package description contains syntax errors 1850 logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config); 1851 return null; 1852 } 1853 auto basepack = pack.basePackage; 1854 1855 foreach (d; pack.getAllDependenciesRange()) { 1856 auto dbasename = d.name.main.toString(); 1857 1858 // detect dependencies to the root package (or sub packages thereof) 1859 if (dbasename == basepack.name) { 1860 auto absdeppath = d.spec.mapToPath(pack.path).path; 1861 absdeppath.endsWithSlash = true; 1862 auto subpack = m_dub.m_packageManager.getSubPackage(basepack, d.name.sub, true); 1863 if (subpack) { 1864 auto desireddeppath = basepack.path; 1865 desireddeppath.endsWithSlash = true; 1866 1867 auto altdeppath = d.name == d.name.main ? basepack.path : subpack.path; 1868 altdeppath.endsWithSlash = true; 1869 1870 if (!d.spec.path.empty && absdeppath != desireddeppath) 1871 logWarn("Sub package %s, referenced by %s %s must be referenced using the path to its base package", 1872 subpack.name, pack.name, pack); 1873 1874 enforce(d.spec.path.empty || absdeppath == desireddeppath || absdeppath == altdeppath, 1875 format("Dependency from %s to %s uses wrong path: %s vs. %s", 1876 node.pack, subpack.name, absdeppath.toNativeString(), desireddeppath.toNativeString())); 1877 } 1878 ret ~= TreeNodes(d.name, node.config); 1879 continue; 1880 } 1881 1882 DependencyType dt; 1883 if (d.spec.optional) { 1884 if (d.spec.default_) dt = DependencyType.optionalDefault; 1885 else dt = DependencyType.optional; 1886 } else dt = DependencyType.required; 1887 1888 Dependency dspec = d.spec.mapToPath(pack.path); 1889 1890 // if not upgrading, use the selected version 1891 if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(d.name.main)) 1892 dspec = m_selectedVersions.getSelectedVersion(d.name.main); 1893 1894 // keep selected optional dependencies and avoid non-selected optional-default dependencies by default 1895 if (!m_selectedVersions.bare) { 1896 if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(d.name.main)) 1897 dt = DependencyType.optional; 1898 else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(d.name.main)) 1899 dt = DependencyType.optionalDefault; 1900 } 1901 1902 ret ~= TreeNodes(d.name, dspec, dt); 1903 } 1904 return ret.data; 1905 } 1906 1907 protected override bool matches(Dependency configs, Dependency config) 1908 { 1909 if (!configs.path.empty) return configs.path == config.path; 1910 return configs.merge(config).valid; 1911 } 1912 1913 private Package getPackage(PackageName name, Dependency dep) 1914 { 1915 auto key = PackageDependency(name, dep); 1916 if (auto pp = key in m_packages) 1917 return *pp; 1918 auto p = getPackageRaw(name, dep); 1919 m_packages[key] = p; 1920 return p; 1921 } 1922 1923 private Package getPackageRaw(in PackageName name, Dependency dep) 1924 { 1925 import dub.recipe.json; 1926 1927 // for sub packages, first try to get them from the base package 1928 if (name.main != name) { 1929 auto subname = name.sub; 1930 auto basepack = getPackage(name.main, dep); 1931 if (!basepack) return null; 1932 if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) 1933 return sp; 1934 logDiagnostic("Subpackage %s@%s not found.", name, dep); 1935 return null; 1936 } 1937 1938 // shortcut if the referenced package is the root package 1939 if (name.main.toString() == m_rootPackage.basePackage.name) 1940 return m_rootPackage.basePackage; 1941 1942 if (!dep.repository.empty) { 1943 auto ret = m_dub.packageManager.loadSCMPackage(name, dep.repository); 1944 return ret !is null && dep.matches(ret.version_) ? ret : null; 1945 } 1946 if (!dep.path.empty) { 1947 try { 1948 return m_dub.packageManager.getOrLoadPackage(dep.path); 1949 } catch (Exception e) { 1950 logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg); 1951 logDebug("Full error: %s", e.toString().sanitize); 1952 return null; 1953 } 1954 } 1955 const vers = dep.version_; 1956 1957 if (auto ret = m_dub.m_packageManager.getBestPackage(name, vers)) 1958 return ret; 1959 1960 auto key = name.toString() ~ ":" ~ vers.toString(); 1961 if (auto ret = key in m_remotePackages) 1962 return *ret; 1963 1964 auto prerelease = (m_options & UpgradeOptions.preRelease) != 0; 1965 1966 foreach (ps; m_dub.m_packageSuppliers) { 1967 if (name.main == name) { 1968 try { 1969 auto desc = ps.fetchPackageRecipe(name, VersionRange(vers, vers), prerelease); 1970 if (desc.type == Json.Type.null_) 1971 continue; 1972 PackageRecipe recipe; 1973 parseJson(recipe, desc); 1974 auto ret = new Package(recipe); 1975 m_remotePackages[key] = ret; 1976 return ret; 1977 } catch (Exception e) { 1978 logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, vers, ps.description, e.msg); 1979 logDebug("Full error: %s", e.toString().sanitize); 1980 } 1981 } else { 1982 logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, vers.toString()); 1983 try { 1984 FetchOptions fetchOpts; 1985 fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none; 1986 m_dub.fetch(name.main, vers, fetchOpts, m_dub.defaultPlacementLocation, "need sub package description"); 1987 auto ret = m_dub.m_packageManager.getBestPackage(name, vers); 1988 if (!ret) { 1989 logWarn("Package %s %s doesn't have a sub package %s", name.main, dep, name); 1990 return null; 1991 } 1992 m_remotePackages[key] = ret; 1993 return ret; 1994 } catch (Exception e) { 1995 logDiagnostic("Package %s could not be downloaded from %s: %s", name.main, ps.description, e.msg); 1996 logDebug("Full error: %s", e.toString().sanitize); 1997 } 1998 } 1999 } 2000 2001 m_remotePackages[key] = null; 2002 2003 logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep); 2004 return null; 2005 } 2006 } 2007 2008 package struct SpecialDirs { 2009 /// The path where to store temporary files and directory 2010 NativePath temp; 2011 /// The system-wide dub-specific folder 2012 NativePath systemSettings; 2013 /// The dub-specific folder in the user home directory 2014 NativePath userSettings; 2015 /** 2016 * User location where to install packages 2017 * 2018 * On Windows, this folder, unlike `userSettings`, does not roam, 2019 * so an account on a company network will not save the content of this data, 2020 * unlike `userSettings`. 2021 * 2022 * On Posix, this is currently equivalent to `userSettings`. 2023 * 2024 * See_Also: https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid 2025 */ 2026 NativePath userPackages; 2027 2028 /** 2029 * Location at which build/generation artifact will be written 2030 * 2031 * All build artifacts are stored under a single build cache, 2032 * which is usually located under `$HOME/.dub/cache/` on POSIX, 2033 * and `%LOCALAPPDATA%/dub/cache` on Windows. 2034 * 2035 * Versions of dub prior to v1.31.0 used to store artifact under the 2036 * project directory, but this led to issues with packages stored on 2037 * read-only file system / location, and lingering artifacts scattered 2038 * through the file system. 2039 * 2040 * Dub writes in the cache directory some Json description files 2041 * of the available artifacts. These files are intended to be read by 2042 * 3rd party software (e.g. Meson). The default cache location specified 2043 * in this function should therefore not change across future Dub versions. 2044 */ 2045 NativePath cache; 2046 2047 /// Returns: An instance of `SpecialDirs` initialized from the environment 2048 public static SpecialDirs make () { 2049 import std.file : tempDir; 2050 2051 SpecialDirs result; 2052 result.temp = NativePath(tempDir); 2053 2054 version(Windows) { 2055 result.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/"; 2056 immutable appDataDir = environment.get("APPDATA"); 2057 result.userSettings = NativePath(appDataDir) ~ "dub/"; 2058 // LOCALAPPDATA is not defined before Windows Vista 2059 result.userPackages = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub"; 2060 } else version(Posix) { 2061 result.systemSettings = NativePath("/var/lib/dub/"); 2062 result.userSettings = NativePath(environment.get("HOME")) ~ ".dub/"; 2063 if (!result.userSettings.absolute) 2064 result.userSettings = getWorkingDirectory() ~ result.userSettings; 2065 result.userPackages = result.userSettings; 2066 } 2067 result.cache = result.userPackages ~ "cache"; 2068 return result; 2069 } 2070 }