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