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