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