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 = getWorkingDirectory() ~ 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 = getWorkingDirectory() ~ 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 if (settings.overrideToolWorkingDirectory is NativePath.init) 690 settings.overrideToolWorkingDirectory = m_rootPath; 691 // With a requested `unittest` config, switch to the special test runner 692 // config (which doesn't require an existing `unittest` configuration). 693 if (settings.config == "unittest") { 694 const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun); 695 if (test_config) settings.config = test_config; 696 } 697 698 auto generator = createProjectGenerator(ide, m_project); 699 if (m_dryRun) return; // TODO: pass m_dryRun to the generator 700 generator.generate(settings); 701 } 702 703 /** Generate project files using the special test runner (`dub test`) configuration. 704 705 Any existing project files will be overridden. 706 */ 707 void testProject(GeneratorSettings settings, string config, NativePath custom_main_file) 708 { 709 settings.cache = this.m_dirs.cache; 710 if (settings.overrideToolWorkingDirectory is NativePath.init) 711 settings.overrideToolWorkingDirectory = m_rootPath; 712 if (!custom_main_file.empty && !custom_main_file.absolute) custom_main_file = m_rootPath ~ custom_main_file; 713 714 const test_config = m_project.addTestRunnerConfiguration(settings, !m_dryRun, config, custom_main_file); 715 if (!test_config) return; // target type "none" 716 717 settings.config = test_config; 718 719 auto generator = createProjectGenerator("build", m_project); 720 generator.generate(settings); 721 } 722 723 /** Executes D-Scanner tests on the current project. **/ 724 void lintProject(string[] args) 725 { 726 import std.path : buildPath, buildNormalizedPath; 727 728 if (m_dryRun) return; 729 730 auto tool = "dscanner"; 731 732 auto tool_pack = m_packageManager.getBestPackage(tool); 733 if (!tool_pack) { 734 logInfo("Hint", Color.light_blue, "%s is not present, getting and storing it user wide", tool); 735 tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none); 736 } 737 738 auto dscanner_dub = new Dub(null, m_packageSuppliers); 739 dscanner_dub.loadPackage(tool_pack); 740 dscanner_dub.upgrade(UpgradeOptions.select); 741 742 GeneratorSettings settings = this.makeAppSettings(); 743 foreach (dependencyPackage; m_project.dependencies) 744 { 745 auto cfgs = m_project.getPackageConfigs(settings.platform, null, true); 746 auto buildSettings = dependencyPackage.getBuildSettings(settings.platform, cfgs[dependencyPackage.name]); 747 foreach (importPath; buildSettings.importPaths) { 748 settings.runArgs ~= ["-I", buildNormalizedPath(dependencyPackage.path.toNativeString(), importPath.idup)]; 749 } 750 } 751 752 string configFilePath = buildPath(m_project.rootPackage.path.toNativeString(), "dscanner.ini"); 753 if (!args.canFind("--config") && exists(configFilePath)) { 754 settings.runArgs ~= ["--config", configFilePath]; 755 } 756 757 settings.runArgs ~= args ~ [m_project.rootPackage.path.toNativeString()]; 758 dscanner_dub.generateProject("build", settings); 759 } 760 761 /** Prints the specified build settings necessary for building the root package. 762 */ 763 void listProjectData(GeneratorSettings settings, string[] requestedData, ListBuildSettingsFormat list_type) 764 { 765 import std.stdio; 766 import std.ascii : newline; 767 768 if (settings.overrideToolWorkingDirectory is NativePath.init) 769 settings.overrideToolWorkingDirectory = m_rootPath; 770 771 // Split comma-separated lists 772 string[] requestedDataSplit = 773 requestedData 774 .map!(a => a.splitter(",").map!strip) 775 .joiner() 776 .array(); 777 778 auto data = m_project.listBuildSettings(settings, requestedDataSplit, list_type); 779 780 string delimiter; 781 final switch (list_type) with (ListBuildSettingsFormat) { 782 case list: delimiter = newline ~ newline; break; 783 case listNul: delimiter = "\0\0"; break; 784 case commandLine: delimiter = " "; break; 785 case commandLineNul: delimiter = "\0\0"; break; 786 } 787 788 write(data.joiner(delimiter)); 789 if (delimiter != "\0\0") writeln(); 790 } 791 792 /// Cleans intermediate/cache files of the given package (or all packages) 793 deprecated("Use `clean(Package)` instead") 794 void cleanPackage(NativePath path) 795 { 796 auto ppack = Package.findPackageFile(path); 797 enforce(!ppack.empty, "No package found.", path.toNativeString()); 798 this.clean(Package.load(path, ppack)); 799 } 800 801 /// Ditto 802 void clean() 803 { 804 const cache = this.m_dirs.cache; 805 logInfo("Cleaning", Color.green, "all artifacts at %s", 806 cache.toNativeString().color(Mode.bold)); 807 if (existsFile(cache)) 808 rmdirRecurse(cache.toNativeString()); 809 } 810 811 /// Ditto 812 void clean(Package pack) 813 { 814 const cache = this.packageCache(pack); 815 logInfo("Cleaning", Color.green, "artifacts for package %s at %s", 816 pack.name.color(Mode.bold), 817 cache.toNativeString().color(Mode.bold)); 818 819 // TODO: clear target files and copy files 820 if (existsFile(cache)) 821 rmdirRecurse(cache.toNativeString()); 822 } 823 824 /// Fetches the package matching the dependency and places it in the specified location. 825 deprecated("Use the overload that accepts either a `Version` or a `VersionRange` as second argument") 826 Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "") 827 { 828 const vrange = dep.visit!( 829 (VersionRange range) => range, 830 function VersionRange (any) { throw new Exception("Cannot call `dub.fetch` with a " ~ typeof(any).stringof ~ " dependency"); } 831 ); 832 return this.fetch(packageId, vrange, location, options, reason); 833 } 834 835 /// Ditto 836 Package fetch(string packageId, in Version vers, PlacementLocation location, FetchOptions options, string reason = "") 837 { 838 return this.fetch(packageId, VersionRange(vers, vers), location, options, reason); 839 } 840 841 /// Ditto 842 Package fetch(string packageId, in VersionRange range, PlacementLocation location, FetchOptions options, string reason = "") 843 { 844 auto basePackageName = getBasePackageName(packageId); 845 Json pinfo; 846 PackageSupplier supplier; 847 foreach(ps; m_packageSuppliers){ 848 try { 849 pinfo = ps.fetchPackageRecipe(basePackageName, Dependency(range), (options & FetchOptions.usePrerelease) != 0); 850 if (pinfo.type == Json.Type.null_) 851 continue; 852 supplier = ps; 853 break; 854 } catch(Exception e) { 855 logWarn("Package %s not found for %s: %s", packageId, ps.description, e.msg); 856 logDebug("Full error: %s", e.toString().sanitize()); 857 } 858 } 859 enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency " ~ range.toString()); 860 Version ver = Version(pinfo["version"].get!string); 861 862 // always upgrade branch based versions - TODO: actually check if there is a new commit available 863 Package existing = m_packageManager.getPackage(packageId, ver, location); 864 if (options & FetchOptions.printOnly) { 865 if (existing && existing.version_ != ver) 866 logInfo("A new version for %s is available (%s -> %s). Run \"%s\" to switch.", 867 packageId.color(Mode.bold), existing.version_, ver, 868 text("dub upgrade ", packageId).color(Mode.bold)); 869 return null; 870 } 871 872 if (existing) { 873 if (!ver.isBranch() || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) { 874 // TODO: support git working trees by performing a "git pull" instead of this 875 logDiagnostic("Package %s %s (in %s packages) is already present with the latest version, skipping upgrade.", 876 packageId, ver, location.toString); 877 return existing; 878 } else { 879 logInfo("Removing", Color.yellow, "%s %s to prepare replacement with a new version", packageId.color(Mode.bold), ver); 880 if (!m_dryRun) m_packageManager.remove(existing); 881 } 882 } 883 884 if (reason.length) logInfo("Fetching", Color.yellow, "%s %s (%s)", packageId.color(Mode.bold), ver, reason); 885 else logInfo("Fetching", Color.yellow, "%s %s", packageId.color(Mode.bold), ver); 886 if (m_dryRun) return null; 887 888 logDebug("Acquiring package zip file"); 889 890 // repeat download on corrupted zips, see #1336 891 foreach_reverse (i; 0..3) 892 { 893 import std.zip : ZipException; 894 895 auto path = getTempFile(basePackageName, ".zip"); 896 supplier.fetchPackage(path, basePackageName, Dependency(range), (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail? 897 scope(exit) removeFile(path); 898 logDiagnostic("Placing to %s...", location.toString()); 899 900 try { 901 return m_packageManager.store(path, location, basePackageName, ver); 902 } catch (ZipException e) { 903 logInfo("Failed to extract zip archive for %s %s...", packageId, ver); 904 // rethrow the exception at the end of the loop 905 if (i == 0) 906 throw e; 907 } 908 } 909 assert(0, "Should throw a ZipException instead."); 910 } 911 912 /** Removes a specific locally cached package. 913 914 This will delete the package files from disk and removes the 915 corresponding entry from the list of known packages. 916 917 Params: 918 pack = Package instance to remove 919 */ 920 void remove(in Package pack) 921 { 922 logInfo("Removing", Color.yellow, "%s (in %s)", pack.name.color(Mode.bold), pack.path.toNativeString()); 923 if (!m_dryRun) m_packageManager.remove(pack); 924 } 925 926 /// Compatibility overload. Use the version without a `force_remove` argument instead. 927 deprecated("Use `remove(pack)` directly instead, the boolean has no effect") 928 void remove(in Package pack, bool force_remove) 929 { 930 remove(pack); 931 } 932 933 /// @see remove(string, string, RemoveLocation) 934 enum RemoveVersionWildcard = "*"; 935 936 /** Removes one or more versions of a locally cached package. 937 938 This will remove a given package with a specified version from the 939 given location. It will remove at most one package, unless `version_` 940 is set to `RemoveVersionWildcard`. 941 942 Params: 943 package_id = Name of the package to be removed 944 location_ = Specifies the location to look for the given package 945 name/version. 946 resolve_version = Callback to select package version. 947 */ 948 void remove(string package_id, PlacementLocation location, 949 scope size_t delegate(in Package[] packages) resolve_version) 950 { 951 enforce(!package_id.empty); 952 if (location == PlacementLocation.local) { 953 logInfo("To remove a locally placed package, make sure you don't have any data" 954 ~ "\nleft in it's directory and then simply remove the whole directory."); 955 throw new Exception("dub cannot remove locally installed packages."); 956 } 957 958 Package[] packages; 959 960 // Retrieve packages to be removed. 961 foreach(pack; m_packageManager.getPackageIterator(package_id)) 962 if (m_packageManager.isManagedPackage(pack)) 963 packages ~= pack; 964 965 // Check validity of packages to be removed. 966 if(packages.empty) { 967 throw new Exception("Cannot find package to remove. (" 968 ~ "id: '" ~ package_id ~ "', location: '" ~ to!string(location) ~ "'" 969 ~ ")"); 970 } 971 972 // Sort package list in ascending version order 973 packages.sort!((a, b) => a.version_ < b.version_); 974 975 immutable idx = resolve_version(packages); 976 if (idx == size_t.max) 977 return; 978 else if (idx != packages.length) 979 packages = packages[idx .. idx + 1]; 980 981 logDebug("Removing %s packages.", packages.length); 982 foreach(pack; packages) { 983 try { 984 remove(pack); 985 } catch (Exception e) { 986 logError("Failed to remove %s %s: %s", package_id, pack.version_, e.msg); 987 logInfo("Continuing with other packages (if any)."); 988 } 989 } 990 } 991 992 /// Compatibility overload. Use the version without a `force_remove` argument instead. 993 deprecated("Use the overload without the 3rd argument (`force_remove`) instead") 994 void remove(string package_id, PlacementLocation location, bool force_remove, 995 scope size_t delegate(in Package[] packages) resolve_version) 996 { 997 remove(package_id, location, resolve_version); 998 } 999 1000 /** Removes a specific version of a package. 1001 1002 Params: 1003 package_id = Name of the package to be removed 1004 version_ = Identifying a version or a wild card. If an empty string 1005 is passed, the package will be removed from the location, if 1006 there is only one version retrieved. This will throw an 1007 exception, if there are multiple versions retrieved. 1008 location_ = Specifies the location to look for the given package 1009 name/version. 1010 */ 1011 void remove(string package_id, string version_, PlacementLocation location) 1012 { 1013 remove(package_id, location, (in packages) { 1014 if (version_ == RemoveVersionWildcard || version_.empty) 1015 return packages.length; 1016 1017 foreach (i, p; packages) { 1018 if (p.version_ == Version(version_)) 1019 return i; 1020 } 1021 throw new Exception("Cannot find package to remove. (" 1022 ~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location) ~ "'" 1023 ~ ")"); 1024 }); 1025 } 1026 1027 /// Compatibility overload. Use the version without a `force_remove` argument instead. 1028 deprecated("Use the overload without force_remove instead") 1029 void remove(string package_id, string version_, PlacementLocation location, bool force_remove) 1030 { 1031 remove(package_id, version_, location); 1032 } 1033 1034 /** Adds a directory to the list of locally known packages. 1035 1036 Forwards to `PackageManager.addLocalPackage`. 1037 1038 Params: 1039 path = Path to the package 1040 ver = Optional version to associate with the package (can be left 1041 empty) 1042 system = Make the package known system wide instead of user wide 1043 (requires administrator privileges). 1044 1045 See_Also: `removeLocalPackage` 1046 */ 1047 void addLocalPackage(string path, string ver, bool system) 1048 { 1049 if (m_dryRun) return; 1050 m_packageManager.addLocalPackage(makeAbsolute(path), ver, system ? PlacementLocation.system : PlacementLocation.user); 1051 } 1052 1053 /** Removes a directory from the list of locally known packages. 1054 1055 Forwards to `PackageManager.removeLocalPackage`. 1056 1057 Params: 1058 path = Path to the package 1059 system = Make the package known system wide instead of user wide 1060 (requires administrator privileges). 1061 1062 See_Also: `addLocalPackage` 1063 */ 1064 void removeLocalPackage(string path, bool system) 1065 { 1066 if (m_dryRun) return; 1067 m_packageManager.removeLocalPackage(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user); 1068 } 1069 1070 /** Registers a local directory to search for packages to use for satisfying 1071 dependencies. 1072 1073 Params: 1074 path = Path to a directory containing package directories 1075 system = Make the package known system wide instead of user wide 1076 (requires administrator privileges). 1077 1078 See_Also: `removeSearchPath` 1079 */ 1080 void addSearchPath(string path, bool system) 1081 { 1082 if (m_dryRun) return; 1083 m_packageManager.addSearchPath(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user); 1084 } 1085 1086 /** Unregisters a local directory search path. 1087 1088 Params: 1089 path = Path to a directory containing package directories 1090 system = Make the package known system wide instead of user wide 1091 (requires administrator privileges). 1092 1093 See_Also: `addSearchPath` 1094 */ 1095 void removeSearchPath(string path, bool system) 1096 { 1097 if (m_dryRun) return; 1098 m_packageManager.removeSearchPath(makeAbsolute(path), system ? PlacementLocation.system : PlacementLocation.user); 1099 } 1100 1101 /** Queries all package suppliers with the given query string. 1102 1103 Returns a list of tuples, where the first entry is the human readable 1104 name of the package supplier and the second entry is the list of 1105 matched packages. 1106 1107 Params: 1108 query = the search term to match packages on 1109 1110 See_Also: `PackageSupplier.searchPackages` 1111 */ 1112 auto searchPackages(string query) 1113 { 1114 import std.typecons : Tuple, tuple; 1115 Tuple!(string, PackageSupplier.SearchResult[])[] results; 1116 foreach (ps; this.m_packageSuppliers) { 1117 try 1118 results ~= tuple(ps.description, ps.searchPackages(query)); 1119 catch (Exception e) { 1120 logWarn("Searching %s for '%s' failed: %s", ps.description, query, e.msg); 1121 } 1122 } 1123 return results.filter!(tup => tup[1].length); 1124 } 1125 1126 /** Returns a list of all available versions (including branches) for a 1127 particular package. 1128 1129 The list returned is based on the registered package suppliers. Local 1130 packages are not queried in the search for versions. 1131 1132 See_also: `getLatestVersion` 1133 */ 1134 Version[] listPackageVersions(string name) 1135 { 1136 Version[] versions; 1137 auto basePackageName = getBasePackageName(name); 1138 foreach (ps; this.m_packageSuppliers) { 1139 try versions ~= ps.getVersions(basePackageName); 1140 catch (Exception e) { 1141 logWarn("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg); 1142 } 1143 } 1144 return versions.sort().uniq.array; 1145 } 1146 1147 /** Returns the latest available version for a particular package. 1148 1149 This function returns the latest numbered version of a package. If no 1150 numbered versions are available, it will return an available branch, 1151 preferring "~master". 1152 1153 Params: 1154 package_name: The name of the package in question. 1155 prefer_stable: If set to `true` (the default), returns the latest 1156 stable version, even if there are newer pre-release versions. 1157 1158 See_also: `listPackageVersions` 1159 */ 1160 Version getLatestVersion(string package_name, bool prefer_stable = true) 1161 { 1162 auto vers = listPackageVersions(package_name); 1163 enforce(!vers.empty, "Failed to find any valid versions for a package name of '"~package_name~"'."); 1164 auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array; 1165 if (prefer_stable && final_versions.length) return final_versions[$-1]; 1166 else return vers[$-1]; 1167 } 1168 1169 /** Initializes a directory with a package skeleton. 1170 1171 Params: 1172 path = Path of the directory to create the new package in. The 1173 directory will be created if it doesn't exist. 1174 deps = List of dependencies to add to the package recipe. 1175 type = Specifies the type of the application skeleton to use. 1176 format = Determines the package recipe format to use. 1177 recipe_callback = Optional callback that can be used to 1178 customize the recipe before it gets written. 1179 */ 1180 void createEmptyPackage(NativePath path, string[] deps, string type, 1181 PackageFormat format = PackageFormat.sdl, 1182 scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null, 1183 string[] app_args = []) 1184 { 1185 if (!path.absolute) path = m_rootPath ~ path; 1186 path.normalize(); 1187 1188 VersionRange[string] depVers; 1189 string[] notFound; // keep track of any failed packages in here 1190 foreach (dep; deps) { 1191 try { 1192 Version ver = getLatestVersion(dep); 1193 if (ver.isBranch()) 1194 depVers[dep] = VersionRange(ver); 1195 else 1196 depVers[dep] = VersionRange.fromString("~>" ~ ver.toString()); 1197 } catch (Exception e) { 1198 notFound ~= dep; 1199 } 1200 } 1201 1202 if(notFound.length > 1){ 1203 throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound)); 1204 } 1205 else if(notFound.length == 1){ 1206 throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound)); 1207 } 1208 1209 if (m_dryRun) return; 1210 1211 initPackage(path, depVers, type, format, recipe_callback); 1212 1213 if (!["vibe.d", "deimos", "minimal"].canFind(type)) { 1214 runCustomInitialization(path, type, app_args); 1215 } 1216 1217 //Act smug to the user. 1218 logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold)); 1219 } 1220 1221 private void runCustomInitialization(NativePath path, string type, string[] runArgs) 1222 { 1223 string packageName = type; 1224 auto template_pack = m_packageManager.getBestPackage(packageName); 1225 if (!template_pack) { 1226 logInfo("%s is not present, getting and storing it user wide", packageName); 1227 template_pack = fetch(packageName, VersionRange.Any, defaultPlacementLocation, FetchOptions.none); 1228 } 1229 1230 Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false); 1231 auto template_dub = new Dub(null, m_packageSuppliers); 1232 template_dub.loadPackage(initSubPackage); 1233 1234 GeneratorSettings settings = this.makeAppSettings(); 1235 settings.runArgs = runArgs; 1236 1237 initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString(); 1238 template_dub.generateProject("build", settings); 1239 } 1240 1241 /** Converts the package recipe of the loaded root package to the given format. 1242 1243 Params: 1244 destination_file_ext = The file extension matching the desired 1245 format. Possible values are "json" or "sdl". 1246 print_only = Print the converted recipe instead of writing to disk 1247 */ 1248 void convertRecipe(string destination_file_ext, bool print_only = false) 1249 { 1250 import std.path : extension; 1251 import std.stdio : stdout; 1252 import dub.recipe.io : serializePackageRecipe, writePackageRecipe; 1253 1254 if (print_only) { 1255 auto dst = stdout.lockingTextWriter; 1256 serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext); 1257 return; 1258 } 1259 1260 auto srcfile = m_project.rootPackage.recipePath; 1261 auto srcext = srcfile.head.name.extension; 1262 if (srcext == "."~destination_file_ext) { 1263 // no logging before this point 1264 tagWidth.push(5); 1265 logError("Package format is already %s.", destination_file_ext); 1266 return; 1267 } 1268 1269 writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe); 1270 removeFile(srcfile); 1271 } 1272 1273 /** Runs DDOX to generate or serve documentation. 1274 1275 Params: 1276 run = If set to true, serves documentation on a local web server. 1277 Otherwise generates actual HTML files. 1278 generate_args = Additional command line arguments to pass to 1279 "ddox generate-html" or "ddox serve-html". 1280 */ 1281 void runDdox(bool run, string[] generate_args = null) 1282 { 1283 import std.process : browse; 1284 1285 if (m_dryRun) return; 1286 1287 // allow to choose a custom ddox tool 1288 auto tool = m_project.rootPackage.recipe.ddoxTool; 1289 if (tool.empty) tool = "ddox"; 1290 1291 auto tool_pack = m_packageManager.getBestPackage(tool); 1292 if (!tool_pack) { 1293 logInfo("%s is not present, getting and storing it user wide", tool); 1294 tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none); 1295 } 1296 1297 auto ddox_dub = new Dub(null, m_packageSuppliers); 1298 ddox_dub.loadPackage(tool_pack); 1299 ddox_dub.upgrade(UpgradeOptions.select); 1300 1301 GeneratorSettings settings = this.makeAppSettings(); 1302 1303 auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup; 1304 if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"]; 1305 1306 settings.runArgs = "filter" ~ filterargs ~ "docs.json"; 1307 ddox_dub.generateProject("build", settings); 1308 1309 auto p = tool_pack.path; 1310 p.endsWithSlash = true; 1311 auto tool_path = p.toNativeString(); 1312 1313 if (run) { 1314 settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args; 1315 browse("http://127.0.0.1:8080/"); 1316 } else { 1317 settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args; 1318 } 1319 ddox_dub.generateProject("build", settings); 1320 1321 if (!run) { 1322 // TODO: ddox should copy those files itself 1323 version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`, null, m_rootPath.toNativeString()); 1324 else runCommand("rsync -ru '"~tool_path~"public/' docs/", null, m_rootPath.toNativeString()); 1325 } 1326 } 1327 1328 /** 1329 * Compute and returns the path were artifacts are stored 1330 * 1331 * Expose `dub.generator.generator : packageCache` with this instance's 1332 * configured cache. 1333 */ 1334 protected NativePath packageCache (Package pkg) const 1335 { 1336 return .packageCache(this.m_dirs.cache, pkg); 1337 } 1338 1339 /// Exposed because `commandLine` replicates `generateProject` for `dub describe` 1340 /// instead of treating it like a regular generator... Remove this once the 1341 /// flaw is fixed, and don't add more calls to this function! 1342 package(dub) NativePath cachePathDontUse () const @safe pure nothrow @nogc 1343 { 1344 return this.m_dirs.cache; 1345 } 1346 1347 /// Make a `GeneratorSettings` suitable to generate tools (DDOC, DScanner, etc...) 1348 private GeneratorSettings makeAppSettings () const 1349 { 1350 GeneratorSettings settings; 1351 auto compiler_binary = this.defaultCompiler; 1352 1353 settings.config = "application"; 1354 settings.buildType = "debug"; 1355 settings.compiler = getCompiler(compiler_binary); 1356 settings.platform = settings.compiler.determinePlatform( 1357 settings.buildSettings, compiler_binary, this.defaultArchitecture); 1358 if (this.defaultLowMemory) 1359 settings.buildSettings.options |= BuildOption.lowmem; 1360 if (this.defaultEnvironments) 1361 settings.buildSettings.addEnvironments(this.defaultEnvironments); 1362 if (this.defaultBuildEnvironments) 1363 settings.buildSettings.addBuildEnvironments(this.defaultBuildEnvironments); 1364 if (this.defaultRunEnvironments) 1365 settings.buildSettings.addRunEnvironments(this.defaultRunEnvironments); 1366 if (this.defaultPreGenerateEnvironments) 1367 settings.buildSettings.addPreGenerateEnvironments(this.defaultPreGenerateEnvironments); 1368 if (this.defaultPostGenerateEnvironments) 1369 settings.buildSettings.addPostGenerateEnvironments(this.defaultPostGenerateEnvironments); 1370 if (this.defaultPreBuildEnvironments) 1371 settings.buildSettings.addPreBuildEnvironments(this.defaultPreBuildEnvironments); 1372 if (this.defaultPostBuildEnvironments) 1373 settings.buildSettings.addPostBuildEnvironments(this.defaultPostBuildEnvironments); 1374 if (this.defaultPreRunEnvironments) 1375 settings.buildSettings.addPreRunEnvironments(this.defaultPreRunEnvironments); 1376 if (this.defaultPostRunEnvironments) 1377 settings.buildSettings.addPostRunEnvironments(this.defaultPostRunEnvironments); 1378 settings.run = true; 1379 settings.overrideToolWorkingDirectory = m_rootPath; 1380 1381 return settings; 1382 } 1383 1384 private void determineDefaultCompiler() 1385 { 1386 import std.file : thisExePath; 1387 import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator; 1388 import std.range : front; 1389 1390 // Env takes precedence 1391 if (auto envCompiler = environment.get("DC")) 1392 m_defaultCompiler = envCompiler; 1393 else 1394 m_defaultCompiler = m_config.defaultCompiler.expandTilde; 1395 if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute) 1396 return; 1397 1398 static immutable BinaryPrefix = `$DUB_BINARY_PATH`; 1399 if(m_defaultCompiler.startsWith(BinaryPrefix)) 1400 { 1401 m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $]; 1402 return; 1403 } 1404 1405 if (!find!isDirSeparator(m_defaultCompiler).empty) 1406 throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ m_defaultCompiler ~ 1407 "\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead."); 1408 1409 version (Windows) enum sep = ";", exe = ".exe"; 1410 version (Posix) enum sep = ":", exe = ""; 1411 1412 auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; 1413 // If a compiler name is specified, look for it next to dub. 1414 // Otherwise, look for any of the common compilers adjacent to dub. 1415 if (m_defaultCompiler.length) 1416 { 1417 string compilerPath = buildPath(thisExePath().dirName(), m_defaultCompiler ~ exe); 1418 if (existsFile(compilerPath)) 1419 { 1420 m_defaultCompiler = compilerPath; 1421 return; 1422 } 1423 } 1424 else 1425 { 1426 auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe))); 1427 if (!nextFound.empty) 1428 { 1429 m_defaultCompiler = buildPath(thisExePath().dirName(), nextFound.front ~ exe); 1430 return; 1431 } 1432 } 1433 1434 // If nothing found next to dub, search the user's PATH, starting 1435 // with the compiler name from their DUB config file, if specified. 1436 auto paths = environment.get("PATH", "").splitter(sep).map!NativePath; 1437 if (m_defaultCompiler.length && paths.canFind!(p => existsFile(p ~ (m_defaultCompiler~exe)))) 1438 return; 1439 foreach (p; paths) { 1440 auto res = compilers.find!(bin => existsFile(p ~ (bin~exe))); 1441 if (!res.empty) { 1442 m_defaultCompiler = res.front; 1443 return; 1444 } 1445 } 1446 m_defaultCompiler = compilers[0]; 1447 } 1448 1449 unittest 1450 { 1451 import std.path: buildPath, absolutePath; 1452 auto dub = new TestDub(".", null, SkipPackageSuppliers.configured); 1453 immutable olddc = environment.get("DC", null); 1454 immutable oldpath = environment.get("PATH", null); 1455 immutable testdir = "test-determineDefaultCompiler"; 1456 void repairenv(string name, string var) 1457 { 1458 if (var !is null) 1459 environment[name] = var; 1460 else if (name in environment) 1461 environment.remove(name); 1462 } 1463 scope (exit) repairenv("DC", olddc); 1464 scope (exit) repairenv("PATH", oldpath); 1465 scope (exit) rmdirRecurse(testdir); 1466 1467 version (Windows) enum sep = ";", exe = ".exe"; 1468 version (Posix) enum sep = ":", exe = ""; 1469 1470 immutable dmdpath = testdir.buildPath("dmd", "bin"); 1471 immutable ldcpath = testdir.buildPath("ldc", "bin"); 1472 mkdirRecurse(dmdpath); 1473 mkdirRecurse(ldcpath); 1474 immutable dmdbin = dmdpath.buildPath("dmd"~exe); 1475 immutable ldcbin = ldcpath.buildPath("ldc2"~exe); 1476 std.file.write(dmdbin, null); 1477 std.file.write(ldcbin, null); 1478 1479 environment["DC"] = dmdbin.absolutePath(); 1480 dub.determineDefaultCompiler(); 1481 assert(dub.m_defaultCompiler == dmdbin.absolutePath()); 1482 1483 environment["DC"] = "dmd"; 1484 environment["PATH"] = dmdpath ~ sep ~ ldcpath; 1485 dub.determineDefaultCompiler(); 1486 assert(dub.m_defaultCompiler == "dmd"); 1487 1488 environment["DC"] = "ldc2"; 1489 environment["PATH"] = dmdpath ~ sep ~ ldcpath; 1490 dub.determineDefaultCompiler(); 1491 assert(dub.m_defaultCompiler == "ldc2"); 1492 1493 environment.remove("DC"); 1494 environment["PATH"] = ldcpath ~ sep ~ dmdpath; 1495 dub.determineDefaultCompiler(); 1496 assert(dub.m_defaultCompiler == "ldc2"); 1497 } 1498 1499 private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; } 1500 private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); } 1501 } 1502 1503 1504 /// Option flags for `Dub.fetch` 1505 enum FetchOptions 1506 { 1507 none = 0, 1508 forceBranchUpgrade = 1<<0, 1509 usePrerelease = 1<<1, 1510 forceRemove = 1<<2, /// Deprecated, does nothing. 1511 printOnly = 1<<3, 1512 } 1513 1514 /// Option flags for `Dub.upgrade` 1515 enum UpgradeOptions 1516 { 1517 none = 0, 1518 upgrade = 1<<1, /// Upgrade existing packages 1519 preRelease = 1<<2, /// inclde pre-release versions in upgrade 1520 forceRemove = 1<<3, /// Deprecated, does nothing. 1521 select = 1<<4, /// Update the dub.selections.json file with the upgraded versions 1522 dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence 1523 /*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead 1524 /*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect 1525 noSaveSelections = 1<<7, /// Don't store updated selections on disk 1526 } 1527 1528 /// Determines which of the default package suppliers are queried for packages. 1529 public alias SkipPackageSuppliers = SPS; 1530 1531 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) { 1532 protected { 1533 Dub m_dub; 1534 UpgradeOptions m_options; 1535 Dependency[][string] m_packageVersions; 1536 Package[string] m_remotePackages; 1537 SelectedVersions m_selectedVersions; 1538 Package m_rootPackage; 1539 bool[string] m_packagesToUpgrade; 1540 Package[PackageDependency] m_packages; 1541 TreeNodes[][TreeNode] m_children; 1542 } 1543 1544 1545 this(Dub dub, UpgradeOptions options, Package root, SelectedVersions selected_versions) 1546 { 1547 assert(dub !is null); 1548 assert(root !is null); 1549 assert(selected_versions !is null); 1550 1551 if (environment.get("DUB_NO_RESOLVE_LIMIT") !is null) 1552 super(ulong.max); 1553 else 1554 super(1_000_000); 1555 1556 m_dub = dub; 1557 m_options = options; 1558 m_rootPackage = root; 1559 m_selectedVersions = selected_versions; 1560 } 1561 1562 Dependency[string] resolve(string[] filter) 1563 { 1564 foreach (name; filter) 1565 m_packagesToUpgrade[name] = true; 1566 return super.resolve(TreeNode(m_rootPackage.name, Dependency(m_rootPackage.version_)), 1567 (m_options & UpgradeOptions.dryRun) == 0); 1568 } 1569 1570 protected bool isFixedPackage(string pack) 1571 { 1572 return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade; 1573 } 1574 1575 protected override Dependency[] getAllConfigs(string pack) 1576 { 1577 if (auto pvers = pack in m_packageVersions) 1578 return *pvers; 1579 1580 if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) { 1581 auto ret = [m_selectedVersions.getSelectedVersion(pack)]; 1582 logDiagnostic("Using fixed selection %s %s", pack, ret[0]); 1583 m_packageVersions[pack] = ret; 1584 return ret; 1585 } 1586 1587 logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length); 1588 Version[] versions; 1589 foreach (p; m_dub.packageManager.getPackageIterator(pack)) 1590 versions ~= p.version_; 1591 1592 foreach (ps; m_dub.m_packageSuppliers) { 1593 try { 1594 auto vers = ps.getVersions(pack); 1595 vers.reverse(); 1596 if (!vers.length) { 1597 logDiagnostic("No versions for %s for %s", pack, ps.description); 1598 continue; 1599 } 1600 1601 versions ~= vers; 1602 break; 1603 } catch (Exception e) { 1604 logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg); 1605 logDebug("Full error: %s", e.toString().sanitize); 1606 } 1607 } 1608 1609 // sort by version, descending, and remove duplicates 1610 versions = versions.sort!"a>b".uniq.array; 1611 1612 // move pre-release versions to the back of the list if no preRelease flag is given 1613 if (!(m_options & UpgradeOptions.preRelease)) 1614 versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array; 1615 1616 // filter out invalid/unreachable dependency specs 1617 versions = versions.filter!((v) { 1618 bool valid = getPackage(pack, Dependency(v)) !is null; 1619 if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v); 1620 return valid; 1621 }).array; 1622 1623 if (!versions.length) logDiagnostic("Nothing found for %s", pack); 1624 else logDiagnostic("Return for %s: %s", pack, versions); 1625 1626 auto ret = versions.map!(v => Dependency(v)).array; 1627 m_packageVersions[pack] = ret; 1628 return ret; 1629 } 1630 1631 protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes) 1632 { 1633 if (!nodes.configs.path.empty || !nodes.configs.repository.empty) { 1634 if (getPackage(pack, nodes.configs)) return [nodes.configs]; 1635 else return null; 1636 } 1637 else return null; 1638 } 1639 1640 1641 protected override TreeNodes[] getChildren(TreeNode node) 1642 { 1643 if (auto pc = node in m_children) 1644 return *pc; 1645 auto ret = getChildrenRaw(node); 1646 m_children[node] = ret; 1647 return ret; 1648 } 1649 1650 private final TreeNodes[] getChildrenRaw(TreeNode node) 1651 { 1652 import std.array : appender; 1653 auto ret = appender!(TreeNodes[]); 1654 auto pack = getPackage(node.pack, node.config); 1655 if (!pack) { 1656 // this can hapen when the package description contains syntax errors 1657 logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config); 1658 return null; 1659 } 1660 auto basepack = pack.basePackage; 1661 1662 foreach (d; pack.getAllDependenciesRange()) { 1663 auto dbasename = getBasePackageName(d.name); 1664 1665 // detect dependencies to the root package (or sub packages thereof) 1666 if (dbasename == basepack.name) { 1667 auto absdeppath = d.spec.mapToPath(pack.path).path; 1668 absdeppath.endsWithSlash = true; 1669 auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(d.name), true); 1670 if (subpack) { 1671 auto desireddeppath = basepack.path; 1672 desireddeppath.endsWithSlash = true; 1673 1674 auto altdeppath = d.name == dbasename ? basepack.path : subpack.path; 1675 altdeppath.endsWithSlash = true; 1676 1677 if (!d.spec.path.empty && absdeppath != desireddeppath) 1678 logWarn("Sub package %s, referenced by %s %s must be referenced using the path to its base package", 1679 subpack.name, pack.name, pack.version_); 1680 1681 enforce(d.spec.path.empty || absdeppath == desireddeppath || absdeppath == altdeppath, 1682 format("Dependency from %s to %s uses wrong path: %s vs. %s", 1683 node.pack, subpack.name, absdeppath.toNativeString(), desireddeppath.toNativeString())); 1684 } 1685 ret ~= TreeNodes(d.name, node.config); 1686 continue; 1687 } 1688 1689 DependencyType dt; 1690 if (d.spec.optional) { 1691 if (d.spec.default_) dt = DependencyType.optionalDefault; 1692 else dt = DependencyType.optional; 1693 } else dt = DependencyType.required; 1694 1695 Dependency dspec = d.spec.mapToPath(pack.path); 1696 1697 // if not upgrading, use the selected version 1698 if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(dbasename)) 1699 dspec = m_selectedVersions.getSelectedVersion(dbasename); 1700 1701 // keep selected optional dependencies and avoid non-selected optional-default dependencies by default 1702 if (!m_selectedVersions.bare) { 1703 if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(dbasename)) 1704 dt = DependencyType.optional; 1705 else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(dbasename)) 1706 dt = DependencyType.optionalDefault; 1707 } 1708 1709 ret ~= TreeNodes(d.name, dspec, dt); 1710 } 1711 return ret.data; 1712 } 1713 1714 protected override bool matches(Dependency configs, Dependency config) 1715 { 1716 if (!configs.path.empty) return configs.path == config.path; 1717 return configs.merge(config).valid; 1718 } 1719 1720 private Package getPackage(string name, Dependency dep) 1721 { 1722 auto key = PackageDependency(name, dep); 1723 if (auto pp = key in m_packages) 1724 return *pp; 1725 auto p = getPackageRaw(name, dep); 1726 m_packages[key] = p; 1727 return p; 1728 } 1729 1730 private Package getPackageRaw(string name, Dependency dep) 1731 { 1732 auto basename = getBasePackageName(name); 1733 1734 // for sub packages, first try to get them from the base package 1735 if (basename != name) { 1736 auto subname = getSubPackageName(name); 1737 auto basepack = getPackage(basename, dep); 1738 if (!basepack) return null; 1739 if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) { 1740 return sp; 1741 } else if (!basepack.subPackages.canFind!(p => p.path.length)) { 1742 // note: external sub packages are handled further below 1743 auto spr = basepack.getInternalSubPackage(subname); 1744 if (!spr.isNull) { 1745 auto sp = new Package(spr.get, basepack.path, basepack); 1746 m_remotePackages[sp.name] = sp; 1747 return sp; 1748 } else { 1749 logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_); 1750 return null; 1751 } 1752 } else { 1753 logDiagnostic("External sub package %s %s not found.", name, dep.version_); 1754 return null; 1755 } 1756 } 1757 1758 // shortcut if the referenced package is the root package 1759 if (basename == m_rootPackage.basePackage.name) 1760 return m_rootPackage.basePackage; 1761 1762 if (!dep.repository.empty) { 1763 auto ret = m_dub.packageManager.loadSCMPackage(name, dep.repository); 1764 return ret !is null && dep.matches(ret.version_) ? ret : null; 1765 } else if (!dep.path.empty) { 1766 try { 1767 return m_dub.packageManager.getOrLoadPackage(dep.path); 1768 } catch (Exception e) { 1769 logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg); 1770 logDebug("Full error: %s", e.toString().sanitize); 1771 return null; 1772 } 1773 } 1774 const vers = dep.version_; 1775 1776 if (auto ret = m_dub.m_packageManager.getBestPackage(name, vers)) 1777 return ret; 1778 1779 auto key = name ~ ":" ~ vers.toString(); 1780 if (auto ret = key in m_remotePackages) 1781 return *ret; 1782 1783 auto prerelease = (m_options & UpgradeOptions.preRelease) != 0; 1784 1785 auto rootpack = name.split(":")[0]; 1786 1787 foreach (ps; m_dub.m_packageSuppliers) { 1788 if (rootpack == name) { 1789 try { 1790 auto desc = ps.fetchPackageRecipe(name, dep, prerelease); 1791 if (desc.type == Json.Type.null_) 1792 continue; 1793 auto ret = new Package(desc); 1794 m_remotePackages[key] = ret; 1795 return ret; 1796 } catch (Exception e) { 1797 logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, vers, ps.description, e.msg); 1798 logDebug("Full error: %s", e.toString().sanitize); 1799 } 1800 } else { 1801 logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, vers.toString()); 1802 try { 1803 FetchOptions fetchOpts; 1804 fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none; 1805 m_dub.fetch(rootpack, vers, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description"); 1806 auto ret = m_dub.m_packageManager.getBestPackage(name, vers); 1807 if (!ret) { 1808 logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name); 1809 return null; 1810 } 1811 m_remotePackages[key] = ret; 1812 return ret; 1813 } catch (Exception e) { 1814 logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg); 1815 logDebug("Full error: %s", e.toString().sanitize); 1816 } 1817 } 1818 } 1819 1820 m_remotePackages[key] = null; 1821 1822 logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep); 1823 return null; 1824 } 1825 } 1826 1827 /** 1828 * An instance of Dub that does not rely on the environment 1829 * 1830 * This instance of dub should not read any environment variables, 1831 * nor should it do any file IO, to make it usable and reliable in unittests. 1832 * Currently it reads environment variables but does not read the configuration. 1833 */ 1834 package final class TestDub : Dub 1835 { 1836 /// Forward to base constructor 1837 public this (string root = ".", PackageSupplier[] extras = null, 1838 SkipPackageSuppliers skip = SkipPackageSuppliers.none) 1839 { 1840 super(root, extras, skip); 1841 } 1842 1843 /// Avoid loading user configuration 1844 protected override void loadConfig() { /* No-op */ } 1845 } 1846 1847 private struct SpecialDirs { 1848 /// The path where to store temporary files and directory 1849 NativePath temp; 1850 /// The system-wide dub-specific folder 1851 NativePath systemSettings; 1852 /// The dub-specific folder in the user home directory 1853 NativePath userSettings; 1854 /** 1855 * User location where to install packages 1856 * 1857 * On Windows, this folder, unlike `userSettings`, does not roam, 1858 * so an account on a company network will not save the content of this data, 1859 * unlike `userSettings`. 1860 * 1861 * On Posix, this is currently equivalent to `userSettings`. 1862 * 1863 * See_Also: https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid 1864 */ 1865 NativePath userPackages; 1866 1867 /** 1868 * Location at which build/generation artifact will be written 1869 * 1870 * All build artifacts are stored under a single build cache, 1871 * which is usually located under `$HOME/.dub/cache/` on POSIX, 1872 * and `%LOCALAPPDATA%/dub/cache` on Windows. 1873 * 1874 * Versions of dub prior to v1.31.0 used to store artifact under the 1875 * project directory, but this led to issues with packages stored on 1876 * read-only filesystem / location, and lingering artifacts scattered 1877 * through the filesystem. 1878 */ 1879 NativePath cache; 1880 1881 /// Returns: An instance of `SpecialDirs` initialized from the environment 1882 public static SpecialDirs make () { 1883 import std.file : tempDir; 1884 1885 SpecialDirs result; 1886 result.temp = NativePath(tempDir); 1887 1888 version(Windows) { 1889 result.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/"; 1890 immutable appDataDir = environment.get("APPDATA"); 1891 result.userSettings = NativePath(appDataDir) ~ "dub/"; 1892 // LOCALAPPDATA is not defined before Windows Vista 1893 result.userPackages = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub"; 1894 } else version(Posix) { 1895 result.systemSettings = NativePath("/var/lib/dub/"); 1896 result.userSettings = NativePath(environment.get("HOME")) ~ ".dub/"; 1897 if (!result.userSettings.absolute) 1898 result.userSettings = getWorkingDirectory() ~ result.userSettings; 1899 result.userPackages = result.userSettings; 1900 } 1901 result.cache = result.userPackages ~ "cache"; 1902 return result; 1903 } 1904 }