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 recipe 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, 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 // re-throw 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, 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 /** Deregisters 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 app_args = Arguments to provide to the custom initialization routine. 1180 */ 1181 void createEmptyPackage(NativePath path, string[] deps, string type, 1182 PackageFormat format = PackageFormat.sdl, 1183 scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null, 1184 string[] app_args = []) 1185 { 1186 if (!path.absolute) path = m_rootPath ~ path; 1187 path.normalize(); 1188 1189 VersionRange[string] depVers; 1190 string[] notFound; // keep track of any failed packages in here 1191 foreach (dep; deps) { 1192 try { 1193 Version ver = getLatestVersion(dep); 1194 if (ver.isBranch()) 1195 depVers[dep] = VersionRange(ver); 1196 else 1197 depVers[dep] = VersionRange.fromString("~>" ~ ver.toString()); 1198 } catch (Exception e) { 1199 notFound ~= dep; 1200 } 1201 } 1202 1203 if(notFound.length > 1){ 1204 throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound)); 1205 } 1206 else if(notFound.length == 1){ 1207 throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound)); 1208 } 1209 1210 if (m_dryRun) return; 1211 1212 initPackage(path, depVers, type, format, recipe_callback); 1213 1214 if (!["vibe.d", "deimos", "minimal"].canFind(type)) { 1215 runCustomInitialization(path, type, app_args); 1216 } 1217 1218 //Act smug to the user. 1219 logInfo("Success", Color.green, "created empty project in %s", path.toNativeString().color(Mode.bold)); 1220 } 1221 1222 private void runCustomInitialization(NativePath path, string type, string[] runArgs) 1223 { 1224 string packageName = type; 1225 auto template_pack = m_packageManager.getBestPackage(packageName); 1226 if (!template_pack) { 1227 logInfo("%s is not present, getting and storing it user wide", packageName); 1228 template_pack = fetch(packageName, VersionRange.Any, defaultPlacementLocation, FetchOptions.none); 1229 } 1230 1231 Package initSubPackage = m_packageManager.getSubPackage(template_pack, "init-exec", false); 1232 auto template_dub = new Dub(null, m_packageSuppliers); 1233 template_dub.loadPackage(initSubPackage); 1234 1235 GeneratorSettings settings = this.makeAppSettings(); 1236 settings.runArgs = runArgs; 1237 1238 initSubPackage.recipe.buildSettings.workingDirectory = path.toNativeString(); 1239 template_dub.generateProject("build", settings); 1240 } 1241 1242 /** Converts the package recipe of the loaded root package to the given format. 1243 1244 Params: 1245 destination_file_ext = The file extension matching the desired 1246 format. Possible values are "json" or "sdl". 1247 print_only = Print the converted recipe instead of writing to disk 1248 */ 1249 void convertRecipe(string destination_file_ext, bool print_only = false) 1250 { 1251 import std.path : extension; 1252 import std.stdio : stdout; 1253 import dub.recipe.io : serializePackageRecipe, writePackageRecipe; 1254 1255 if (print_only) { 1256 auto dst = stdout.lockingTextWriter; 1257 serializePackageRecipe(dst, m_project.rootPackage.rawRecipe, "dub."~destination_file_ext); 1258 return; 1259 } 1260 1261 auto srcfile = m_project.rootPackage.recipePath; 1262 auto srcext = srcfile.head.name.extension; 1263 if (srcext == "."~destination_file_ext) { 1264 // no logging before this point 1265 tagWidth.push(5); 1266 logError("Package format is already %s.", destination_file_ext); 1267 return; 1268 } 1269 1270 writePackageRecipe(srcfile.parentPath ~ ("dub."~destination_file_ext), m_project.rootPackage.rawRecipe); 1271 removeFile(srcfile); 1272 } 1273 1274 /** Runs DDOX to generate or serve documentation. 1275 1276 Params: 1277 run = If set to true, serves documentation on a local web server. 1278 Otherwise generates actual HTML files. 1279 generate_args = Additional command line arguments to pass to 1280 "ddox generate-html" or "ddox serve-html". 1281 */ 1282 void runDdox(bool run, string[] generate_args = null) 1283 { 1284 import std.process : browse; 1285 1286 if (m_dryRun) return; 1287 1288 // allow to choose a custom ddox tool 1289 auto tool = m_project.rootPackage.recipe.ddoxTool; 1290 if (tool.empty) tool = "ddox"; 1291 1292 auto tool_pack = m_packageManager.getBestPackage(tool); 1293 if (!tool_pack) { 1294 logInfo("%s is not present, getting and storing it user wide", tool); 1295 tool_pack = fetch(tool, VersionRange.Any, defaultPlacementLocation, FetchOptions.none); 1296 } 1297 1298 auto ddox_dub = new Dub(null, m_packageSuppliers); 1299 ddox_dub.loadPackage(tool_pack); 1300 ddox_dub.upgrade(UpgradeOptions.select); 1301 1302 GeneratorSettings settings = this.makeAppSettings(); 1303 1304 auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup; 1305 if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"]; 1306 1307 settings.runArgs = "filter" ~ filterargs ~ "docs.json"; 1308 ddox_dub.generateProject("build", settings); 1309 1310 auto p = tool_pack.path; 1311 p.endsWithSlash = true; 1312 auto tool_path = p.toNativeString(); 1313 1314 if (run) { 1315 settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args; 1316 browse("http://127.0.0.1:8080/"); 1317 } else { 1318 settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args; 1319 } 1320 ddox_dub.generateProject("build", settings); 1321 1322 if (!run) { 1323 // TODO: ddox should copy those files itself 1324 version(Windows) runCommand(`xcopy /S /D "`~tool_path~`public\*" docs\`, null, m_rootPath.toNativeString()); 1325 else runCommand("rsync -ru '"~tool_path~"public/' docs/", null, m_rootPath.toNativeString()); 1326 } 1327 } 1328 1329 /** 1330 * Compute and returns the path were artifacts are stored 1331 * 1332 * Expose `dub.generator.generator : packageCache` with this instance's 1333 * configured cache. 1334 */ 1335 protected NativePath packageCache (Package pkg) const 1336 { 1337 return .packageCache(this.m_dirs.cache, pkg); 1338 } 1339 1340 /// Exposed because `commandLine` replicates `generateProject` for `dub describe` 1341 /// instead of treating it like a regular generator... Remove this once the 1342 /// flaw is fixed, and don't add more calls to this function! 1343 package(dub) NativePath cachePathDontUse () const @safe pure nothrow @nogc 1344 { 1345 return this.m_dirs.cache; 1346 } 1347 1348 /// Make a `GeneratorSettings` suitable to generate tools (DDOC, DScanner, etc...) 1349 private GeneratorSettings makeAppSettings () const 1350 { 1351 GeneratorSettings settings; 1352 auto compiler_binary = this.defaultCompiler; 1353 1354 settings.config = "application"; 1355 settings.buildType = "debug"; 1356 settings.compiler = getCompiler(compiler_binary); 1357 settings.platform = settings.compiler.determinePlatform( 1358 settings.buildSettings, compiler_binary, this.defaultArchitecture); 1359 if (this.defaultLowMemory) 1360 settings.buildSettings.options |= BuildOption.lowmem; 1361 if (this.defaultEnvironments) 1362 settings.buildSettings.addEnvironments(this.defaultEnvironments); 1363 if (this.defaultBuildEnvironments) 1364 settings.buildSettings.addBuildEnvironments(this.defaultBuildEnvironments); 1365 if (this.defaultRunEnvironments) 1366 settings.buildSettings.addRunEnvironments(this.defaultRunEnvironments); 1367 if (this.defaultPreGenerateEnvironments) 1368 settings.buildSettings.addPreGenerateEnvironments(this.defaultPreGenerateEnvironments); 1369 if (this.defaultPostGenerateEnvironments) 1370 settings.buildSettings.addPostGenerateEnvironments(this.defaultPostGenerateEnvironments); 1371 if (this.defaultPreBuildEnvironments) 1372 settings.buildSettings.addPreBuildEnvironments(this.defaultPreBuildEnvironments); 1373 if (this.defaultPostBuildEnvironments) 1374 settings.buildSettings.addPostBuildEnvironments(this.defaultPostBuildEnvironments); 1375 if (this.defaultPreRunEnvironments) 1376 settings.buildSettings.addPreRunEnvironments(this.defaultPreRunEnvironments); 1377 if (this.defaultPostRunEnvironments) 1378 settings.buildSettings.addPostRunEnvironments(this.defaultPostRunEnvironments); 1379 settings.run = true; 1380 settings.overrideToolWorkingDirectory = m_rootPath; 1381 1382 return settings; 1383 } 1384 1385 private void determineDefaultCompiler() 1386 { 1387 import std.file : thisExePath; 1388 import std.path : buildPath, dirName, expandTilde, isAbsolute, isDirSeparator; 1389 import std.range : front; 1390 1391 // Env takes precedence 1392 if (auto envCompiler = environment.get("DC")) 1393 m_defaultCompiler = envCompiler; 1394 else 1395 m_defaultCompiler = m_config.defaultCompiler.expandTilde; 1396 if (m_defaultCompiler.length && m_defaultCompiler.isAbsolute) 1397 return; 1398 1399 static immutable BinaryPrefix = `$DUB_BINARY_PATH`; 1400 if(m_defaultCompiler.startsWith(BinaryPrefix)) 1401 { 1402 m_defaultCompiler = thisExePath().dirName() ~ m_defaultCompiler[BinaryPrefix.length .. $]; 1403 return; 1404 } 1405 1406 if (!find!isDirSeparator(m_defaultCompiler).empty) 1407 throw new Exception("defaultCompiler specified in a DUB config file cannot use an unqualified relative path:\n\n" ~ m_defaultCompiler ~ 1408 "\n\nUse \"$DUB_BINARY_PATH/../path/you/want\" instead."); 1409 1410 version (Windows) enum sep = ";", exe = ".exe"; 1411 version (Posix) enum sep = ":", exe = ""; 1412 1413 auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"]; 1414 // If a compiler name is specified, look for it next to dub. 1415 // Otherwise, look for any of the common compilers adjacent to dub. 1416 if (m_defaultCompiler.length) 1417 { 1418 string compilerPath = buildPath(thisExePath().dirName(), m_defaultCompiler ~ exe); 1419 if (existsFile(compilerPath)) 1420 { 1421 m_defaultCompiler = compilerPath; 1422 return; 1423 } 1424 } 1425 else 1426 { 1427 auto nextFound = compilers.find!(bin => existsFile(buildPath(thisExePath().dirName(), bin ~ exe))); 1428 if (!nextFound.empty) 1429 { 1430 m_defaultCompiler = buildPath(thisExePath().dirName(), nextFound.front ~ exe); 1431 return; 1432 } 1433 } 1434 1435 // If nothing found next to dub, search the user's PATH, starting 1436 // with the compiler name from their DUB config file, if specified. 1437 auto paths = environment.get("PATH", "").splitter(sep).map!NativePath; 1438 if (m_defaultCompiler.length && paths.canFind!(p => existsFile(p ~ (m_defaultCompiler~exe)))) 1439 return; 1440 foreach (p; paths) { 1441 auto res = compilers.find!(bin => existsFile(p ~ (bin~exe))); 1442 if (!res.empty) { 1443 m_defaultCompiler = res.front; 1444 return; 1445 } 1446 } 1447 m_defaultCompiler = compilers[0]; 1448 } 1449 1450 unittest 1451 { 1452 import std.path: buildPath, absolutePath; 1453 auto dub = new TestDub(".", null, SkipPackageSuppliers.configured); 1454 immutable olddc = environment.get("DC", null); 1455 immutable oldpath = environment.get("PATH", null); 1456 immutable testdir = "test-determineDefaultCompiler"; 1457 void repairenv(string name, string var) 1458 { 1459 if (var !is null) 1460 environment[name] = var; 1461 else if (name in environment) 1462 environment.remove(name); 1463 } 1464 scope (exit) repairenv("DC", olddc); 1465 scope (exit) repairenv("PATH", oldpath); 1466 scope (exit) rmdirRecurse(testdir); 1467 1468 version (Windows) enum sep = ";", exe = ".exe"; 1469 version (Posix) enum sep = ":", exe = ""; 1470 1471 immutable dmdpath = testdir.buildPath("dmd", "bin"); 1472 immutable ldcpath = testdir.buildPath("ldc", "bin"); 1473 mkdirRecurse(dmdpath); 1474 mkdirRecurse(ldcpath); 1475 immutable dmdbin = dmdpath.buildPath("dmd"~exe); 1476 immutable ldcbin = ldcpath.buildPath("ldc2"~exe); 1477 std.file.write(dmdbin, null); 1478 std.file.write(ldcbin, null); 1479 1480 environment["DC"] = dmdbin.absolutePath(); 1481 dub.determineDefaultCompiler(); 1482 assert(dub.m_defaultCompiler == dmdbin.absolutePath()); 1483 1484 environment["DC"] = "dmd"; 1485 environment["PATH"] = dmdpath ~ sep ~ ldcpath; 1486 dub.determineDefaultCompiler(); 1487 assert(dub.m_defaultCompiler == "dmd"); 1488 1489 environment["DC"] = "ldc2"; 1490 environment["PATH"] = dmdpath ~ sep ~ ldcpath; 1491 dub.determineDefaultCompiler(); 1492 assert(dub.m_defaultCompiler == "ldc2"); 1493 1494 environment.remove("DC"); 1495 environment["PATH"] = ldcpath ~ sep ~ dmdpath; 1496 dub.determineDefaultCompiler(); 1497 assert(dub.m_defaultCompiler == "ldc2"); 1498 } 1499 1500 private NativePath makeAbsolute(NativePath p) const { return p.absolute ? p : m_rootPath ~ p; } 1501 private NativePath makeAbsolute(string p) const { return makeAbsolute(NativePath(p)); } 1502 } 1503 1504 1505 /// Option flags for `Dub.fetch` 1506 enum FetchOptions 1507 { 1508 none = 0, 1509 forceBranchUpgrade = 1<<0, 1510 usePrerelease = 1<<1, 1511 forceRemove = 1<<2, /// Deprecated, does nothing. 1512 printOnly = 1<<3, 1513 } 1514 1515 /// Option flags for `Dub.upgrade` 1516 enum UpgradeOptions 1517 { 1518 none = 0, 1519 upgrade = 1<<1, /// Upgrade existing packages 1520 preRelease = 1<<2, /// include pre-release versions in upgrade 1521 forceRemove = 1<<3, /// Deprecated, does nothing. 1522 select = 1<<4, /// Update the dub.selections.json file with the upgraded versions 1523 dryRun = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence 1524 /*deprecated*/ printUpgradesOnly = dryRun, /// deprecated, use dryRun instead 1525 /*deprecated*/ useCachedResult = 1<<6, /// deprecated, has no effect 1526 noSaveSelections = 1<<7, /// Don't store updated selections on disk 1527 } 1528 1529 /// Determines which of the default package suppliers are queried for packages. 1530 public alias SkipPackageSuppliers = SPS; 1531 1532 private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) { 1533 protected { 1534 Dub m_dub; 1535 UpgradeOptions m_options; 1536 Dependency[][string] m_packageVersions; 1537 Package[string] m_remotePackages; 1538 SelectedVersions m_selectedVersions; 1539 Package m_rootPackage; 1540 bool[string] m_packagesToUpgrade; 1541 Package[PackageDependency] m_packages; 1542 TreeNodes[][TreeNode] m_children; 1543 } 1544 1545 1546 this(Dub dub, UpgradeOptions options, Package root, SelectedVersions selected_versions) 1547 { 1548 assert(dub !is null); 1549 assert(root !is null); 1550 assert(selected_versions !is null); 1551 1552 if (environment.get("DUB_NO_RESOLVE_LIMIT") !is null) 1553 super(ulong.max); 1554 else 1555 super(1_000_000); 1556 1557 m_dub = dub; 1558 m_options = options; 1559 m_rootPackage = root; 1560 m_selectedVersions = selected_versions; 1561 } 1562 1563 Dependency[string] resolve(string[] filter) 1564 { 1565 foreach (name; filter) 1566 m_packagesToUpgrade[name] = true; 1567 return super.resolve(TreeNode(m_rootPackage.name, Dependency(m_rootPackage.version_)), 1568 (m_options & UpgradeOptions.dryRun) == 0); 1569 } 1570 1571 protected bool isFixedPackage(string pack) 1572 { 1573 return m_packagesToUpgrade !is null && pack !in m_packagesToUpgrade; 1574 } 1575 1576 protected override Dependency[] getAllConfigs(string pack) 1577 { 1578 if (auto pvers = pack in m_packageVersions) 1579 return *pvers; 1580 1581 if ((!(m_options & UpgradeOptions.upgrade) || isFixedPackage(pack)) && m_selectedVersions.hasSelectedVersion(pack)) { 1582 auto ret = [m_selectedVersions.getSelectedVersion(pack)]; 1583 logDiagnostic("Using fixed selection %s %s", pack, ret[0]); 1584 m_packageVersions[pack] = ret; 1585 return ret; 1586 } 1587 1588 logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length); 1589 Version[] versions; 1590 foreach (p; m_dub.packageManager.getPackageIterator(pack)) 1591 versions ~= p.version_; 1592 1593 foreach (ps; m_dub.m_packageSuppliers) { 1594 try { 1595 auto vers = ps.getVersions(pack); 1596 vers.reverse(); 1597 if (!vers.length) { 1598 logDiagnostic("No versions for %s for %s", pack, ps.description); 1599 continue; 1600 } 1601 1602 versions ~= vers; 1603 break; 1604 } catch (Exception e) { 1605 logWarn("Package %s not found in %s: %s", pack, ps.description, e.msg); 1606 logDebug("Full error: %s", e.toString().sanitize); 1607 } 1608 } 1609 1610 // sort by version, descending, and remove duplicates 1611 versions = versions.sort!"a>b".uniq.array; 1612 1613 // move pre-release versions to the back of the list if no preRelease flag is given 1614 if (!(m_options & UpgradeOptions.preRelease)) 1615 versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array; 1616 1617 // filter out invalid/unreachable dependency specs 1618 versions = versions.filter!((v) { 1619 bool valid = getPackage(pack, Dependency(v)) !is null; 1620 if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v); 1621 return valid; 1622 }).array; 1623 1624 if (!versions.length) logDiagnostic("Nothing found for %s", pack); 1625 else logDiagnostic("Return for %s: %s", pack, versions); 1626 1627 auto ret = versions.map!(v => Dependency(v)).array; 1628 m_packageVersions[pack] = ret; 1629 return ret; 1630 } 1631 1632 protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes) 1633 { 1634 if (!nodes.configs.path.empty || !nodes.configs.repository.empty) { 1635 if (getPackage(nodes.pack, nodes.configs)) return [nodes.configs]; 1636 else return null; 1637 } 1638 else return null; 1639 } 1640 1641 1642 protected override TreeNodes[] getChildren(TreeNode node) 1643 { 1644 if (auto pc = node in m_children) 1645 return *pc; 1646 auto ret = getChildrenRaw(node); 1647 m_children[node] = ret; 1648 return ret; 1649 } 1650 1651 private final TreeNodes[] getChildrenRaw(TreeNode node) 1652 { 1653 import std.array : appender; 1654 auto ret = appender!(TreeNodes[]); 1655 auto pack = getPackage(node.pack, node.config); 1656 if (!pack) { 1657 // this can happen when the package description contains syntax errors 1658 logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config); 1659 return null; 1660 } 1661 auto basepack = pack.basePackage; 1662 1663 foreach (d; pack.getAllDependenciesRange()) { 1664 auto dbasename = getBasePackageName(d.name); 1665 1666 // detect dependencies to the root package (or sub packages thereof) 1667 if (dbasename == basepack.name) { 1668 auto absdeppath = d.spec.mapToPath(pack.path).path; 1669 absdeppath.endsWithSlash = true; 1670 auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(d.name), true); 1671 if (subpack) { 1672 auto desireddeppath = basepack.path; 1673 desireddeppath.endsWithSlash = true; 1674 1675 auto altdeppath = d.name == dbasename ? basepack.path : subpack.path; 1676 altdeppath.endsWithSlash = true; 1677 1678 if (!d.spec.path.empty && absdeppath != desireddeppath) 1679 logWarn("Sub package %s, referenced by %s %s must be referenced using the path to its base package", 1680 subpack.name, pack.name, pack); 1681 1682 enforce(d.spec.path.empty || absdeppath == desireddeppath || absdeppath == altdeppath, 1683 format("Dependency from %s to %s uses wrong path: %s vs. %s", 1684 node.pack, subpack.name, absdeppath.toNativeString(), desireddeppath.toNativeString())); 1685 } 1686 ret ~= TreeNodes(d.name, node.config); 1687 continue; 1688 } 1689 1690 DependencyType dt; 1691 if (d.spec.optional) { 1692 if (d.spec.default_) dt = DependencyType.optionalDefault; 1693 else dt = DependencyType.optional; 1694 } else dt = DependencyType.required; 1695 1696 Dependency dspec = d.spec.mapToPath(pack.path); 1697 1698 // if not upgrading, use the selected version 1699 if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(dbasename)) 1700 dspec = m_selectedVersions.getSelectedVersion(dbasename); 1701 1702 // keep selected optional dependencies and avoid non-selected optional-default dependencies by default 1703 if (!m_selectedVersions.bare) { 1704 if (dt == DependencyType.optionalDefault && !m_selectedVersions.hasSelectedVersion(dbasename)) 1705 dt = DependencyType.optional; 1706 else if (dt == DependencyType.optional && m_selectedVersions.hasSelectedVersion(dbasename)) 1707 dt = DependencyType.optionalDefault; 1708 } 1709 1710 ret ~= TreeNodes(d.name, dspec, dt); 1711 } 1712 return ret.data; 1713 } 1714 1715 protected override bool matches(Dependency configs, Dependency config) 1716 { 1717 if (!configs.path.empty) return configs.path == config.path; 1718 return configs.merge(config).valid; 1719 } 1720 1721 private Package getPackage(string name, Dependency dep) 1722 { 1723 auto key = PackageDependency(name, dep); 1724 if (auto pp = key in m_packages) 1725 return *pp; 1726 auto p = getPackageRaw(name, dep); 1727 m_packages[key] = p; 1728 return p; 1729 } 1730 1731 private Package getPackageRaw(string name, Dependency dep) 1732 { 1733 auto basename = getBasePackageName(name); 1734 1735 // for sub packages, first try to get them from the base package 1736 if (basename != name) { 1737 auto subname = getSubPackageName(name); 1738 auto basepack = getPackage(basename, dep); 1739 if (!basepack) return null; 1740 if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) { 1741 return sp; 1742 } else if (!basepack.subPackages.canFind!(p => p.path.length)) { 1743 // note: external sub packages are handled further below 1744 auto spr = basepack.getInternalSubPackage(subname); 1745 if (!spr.isNull) { 1746 auto sp = new Package(spr.get, basepack.path, basepack); 1747 m_remotePackages[sp.name] = sp; 1748 return sp; 1749 } else { 1750 logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep); 1751 return null; 1752 } 1753 } else { 1754 logDiagnostic("External sub package %s %s not found.", name, dep); 1755 return null; 1756 } 1757 } 1758 1759 // shortcut if the referenced package is the root package 1760 if (basename == m_rootPackage.basePackage.name) 1761 return m_rootPackage.basePackage; 1762 1763 if (!dep.repository.empty) { 1764 auto ret = m_dub.packageManager.loadSCMPackage(name, dep.repository); 1765 return ret !is null && dep.matches(ret.version_) ? ret : null; 1766 } else if (!dep.path.empty) { 1767 try { 1768 return m_dub.packageManager.getOrLoadPackage(dep.path); 1769 } catch (Exception e) { 1770 logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg); 1771 logDebug("Full error: %s", e.toString().sanitize); 1772 return null; 1773 } 1774 } 1775 const vers = dep.version_; 1776 1777 if (auto ret = m_dub.m_packageManager.getBestPackage(name, vers)) 1778 return ret; 1779 1780 auto key = name ~ ":" ~ vers.toString(); 1781 if (auto ret = key in m_remotePackages) 1782 return *ret; 1783 1784 auto prerelease = (m_options & UpgradeOptions.preRelease) != 0; 1785 1786 auto rootpack = name.split(":")[0]; 1787 1788 foreach (ps; m_dub.m_packageSuppliers) { 1789 if (rootpack == name) { 1790 try { 1791 auto desc = ps.fetchPackageRecipe(name, dep, prerelease); 1792 if (desc.type == Json.Type.null_) 1793 continue; 1794 auto ret = new Package(desc); 1795 m_remotePackages[key] = ret; 1796 return ret; 1797 } catch (Exception e) { 1798 logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, vers, ps.description, e.msg); 1799 logDebug("Full error: %s", e.toString().sanitize); 1800 } 1801 } else { 1802 logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, vers.toString()); 1803 try { 1804 FetchOptions fetchOpts; 1805 fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none; 1806 m_dub.fetch(rootpack, vers, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description"); 1807 auto ret = m_dub.m_packageManager.getBestPackage(name, vers); 1808 if (!ret) { 1809 logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep, name); 1810 return null; 1811 } 1812 m_remotePackages[key] = ret; 1813 return ret; 1814 } catch (Exception e) { 1815 logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg); 1816 logDebug("Full error: %s", e.toString().sanitize); 1817 } 1818 } 1819 } 1820 1821 m_remotePackages[key] = null; 1822 1823 logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep); 1824 return null; 1825 } 1826 } 1827 1828 /** 1829 * An instance of Dub that does not rely on the environment 1830 * 1831 * This instance of dub should not read any environment variables, 1832 * nor should it do any file IO, to make it usable and reliable in unittests. 1833 * Currently it reads environment variables but does not read the configuration. 1834 */ 1835 package final class TestDub : Dub 1836 { 1837 /// Forward to base constructor 1838 public this (string root = ".", PackageSupplier[] extras = null, 1839 SkipPackageSuppliers skip = SkipPackageSuppliers.none) 1840 { 1841 super(root, extras, skip); 1842 } 1843 1844 /// Avoid loading user configuration 1845 protected override void loadConfig() { /* No-op */ } 1846 } 1847 1848 private struct SpecialDirs { 1849 /// The path where to store temporary files and directory 1850 NativePath temp; 1851 /// The system-wide dub-specific folder 1852 NativePath systemSettings; 1853 /// The dub-specific folder in the user home directory 1854 NativePath userSettings; 1855 /** 1856 * User location where to install packages 1857 * 1858 * On Windows, this folder, unlike `userSettings`, does not roam, 1859 * so an account on a company network will not save the content of this data, 1860 * unlike `userSettings`. 1861 * 1862 * On Posix, this is currently equivalent to `userSettings`. 1863 * 1864 * See_Also: https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid 1865 */ 1866 NativePath userPackages; 1867 1868 /** 1869 * Location at which build/generation artifact will be written 1870 * 1871 * All build artifacts are stored under a single build cache, 1872 * which is usually located under `$HOME/.dub/cache/` on POSIX, 1873 * and `%LOCALAPPDATA%/dub/cache` on Windows. 1874 * 1875 * Versions of dub prior to v1.31.0 used to store artifact under the 1876 * project directory, but this led to issues with packages stored on 1877 * read-only file system / location, and lingering artifacts scattered 1878 * through the file system. 1879 */ 1880 NativePath cache; 1881 1882 /// Returns: An instance of `SpecialDirs` initialized from the environment 1883 public static SpecialDirs make () { 1884 import std.file : tempDir; 1885 1886 SpecialDirs result; 1887 result.temp = NativePath(tempDir); 1888 1889 version(Windows) { 1890 result.systemSettings = NativePath(environment.get("ProgramData")) ~ "dub/"; 1891 immutable appDataDir = environment.get("APPDATA"); 1892 result.userSettings = NativePath(appDataDir) ~ "dub/"; 1893 // LOCALAPPDATA is not defined before Windows Vista 1894 result.userPackages = NativePath(environment.get("LOCALAPPDATA", appDataDir)) ~ "dub"; 1895 } else version(Posix) { 1896 result.systemSettings = NativePath("/var/lib/dub/"); 1897 result.userSettings = NativePath(environment.get("HOME")) ~ ".dub/"; 1898 if (!result.userSettings.absolute) 1899 result.userSettings = getWorkingDirectory() ~ result.userSettings; 1900 result.userPackages = result.userSettings; 1901 } 1902 result.cache = result.userPackages ~ "cache"; 1903 return result; 1904 } 1905 }