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