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