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.core.log;
  16. import dub.internal.vibecompat.data.json;
  17. import dub.internal.vibecompat.inet.url;
  18. import dub.package_;
  19. import dub.packagemanager;
  20. import dub.packagesupplier;
  21. import dub.project;
  22. import dub.generators.generator;
  23. import dub.init;
  24.  
  25.  
  26. // todo: cleanup imports.
  27. import std.algorithm;
  28. import std.array;
  29. import std.conv;
  30. import std.datetime;
  31. import std.exception;
  32. import std.file;
  33. import std.process;
  34. import std.string;
  35. import std.typecons;
  36. import std.zip;
  37. import std.encoding : sanitize;
  38.  
  39. // Workaround for libcurl liker errors when building with LDC
  40. version (LDC) pragma(lib, "curl");
  41.  
  42. // Set output path and options for coverage reports
  43. version (DigitalMars) version (D_Coverage) static if (__VERSION__ >= 2068)
  44. {
  45. shared static this()
  46. {
  47. import core.runtime, std.file, std.path, std.stdio;
  48. dmd_coverSetMerge(true);
  49. auto path = buildPath(dirName(thisExePath()), "../cov");
  50. if (!path.exists)
  51. mkdir(path);
  52. dmd_coverDestPath(path);
  53. }
  54. }
  55.  
  56. static this()
  57. {
  58. import dub.compilers.dmd : DMDCompiler;
  59. import dub.compilers.gdc : GDCCompiler;
  60. import dub.compilers.ldc : LDCCompiler;
  61. registerCompiler(new DMDCompiler);
  62. registerCompiler(new GDCCompiler);
  63. registerCompiler(new LDCCompiler);
  64. }
  65.  
  66. /// The URL to the official package registry.
  67. enum defaultRegistryURL = "http://code.dlang.org/";
  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: `defaultRegistryURL`
  75. */
  76. PackageSupplier[] defaultPackageSuppliers()
  77. {
  78. logDiagnostic("Using dub registry url '%s'", defaultRegistryURL);
  79. return [new RegistryPackageSupplier(URL(defaultRegistryURL))];
  80. }
  81.  
  82.  
  83. /** Provides a high-level entry point for DUB's functionality.
  84.  
  85. This class provides means to load a certain project (a root package with
  86. all of its dependencies) and to perform high-level operations as found in
  87. the command line interface.
  88. */
  89. class Dub {
  90. private {
  91. bool m_dryRun = false;
  92. PackageManager m_packageManager;
  93. PackageSupplier[] m_packageSuppliers;
  94. Path m_rootPath;
  95. Path m_tempPath;
  96. Path m_userDubPath, m_systemDubPath;
  97. Json m_systemConfig, m_userConfig;
  98. Path m_projectPath;
  99. Project m_project;
  100. Path m_overrideSearchPath;
  101. string m_defaultCompiler;
  102. }
  103.  
  104. /** The default placement location of fetched packages.
  105.  
  106. This property can be altered, so that packages which are downloaded as part
  107. of the normal upgrade process are stored in a certain location. This is
  108. how the "--local" and "--system" command line switches operate.
  109. */
  110. PlacementLocation defaultPlacementLocation = PlacementLocation.user;
  111.  
  112.  
  113. /** Initializes the instance for use with a specific root package.
  114.  
  115. Note that a package still has to be loaded using one of the
  116. `loadPackage` overloads.
  117.  
  118. Params:
  119. root_path = Path to the root package
  120. additional_package_suppliers = A list of package suppliers to try
  121. before the suppliers found in the configurations files and the
  122. `defaultPackageSuppliers`.
  123. skip_registry = Can be used to skip using the configured package
  124. suppliers, as well as the default suppliers.
  125. */
  126. this(string root_path = ".", PackageSupplier[] additional_package_suppliers = null,
  127. SkipPackageSuppliers skip_registry = SkipPackageSuppliers.none)
  128. {
  129. m_rootPath = Path(root_path);
  130. if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath;
  131.  
  132. init();
  133.  
  134. PackageSupplier[] ps = additional_package_suppliers;
  135.  
  136. if (skip_registry < SkipPackageSuppliers.all) {
  137. if (auto pp = "registryUrls" in m_userConfig)
  138. ps ~= deserializeJson!(string[])(*pp)
  139. .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url)))
  140. .array;
  141. }
  142.  
  143. if (skip_registry < SkipPackageSuppliers.all) {
  144. if (auto pp = "registryUrls" in m_systemConfig)
  145. ps ~= deserializeJson!(string[])(*pp)
  146. .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url)))
  147. .array;
  148. }
  149.  
  150. if (skip_registry < SkipPackageSuppliers.standard)
  151. ps ~= defaultPackageSuppliers();
  152.  
  153. m_packageSuppliers = ps;
  154. m_packageManager = new PackageManager(m_userDubPath, m_systemDubPath);
  155. updatePackageSearchPath();
  156. }
  157.  
  158. /** Initializes the instance with a single package search path, without
  159. loading a package.
  160.  
  161. This constructor corresponds to the "--bare" option of the command line
  162. interface. Use
  163. */
  164. this(Path override_path)
  165. {
  166. init();
  167. m_overrideSearchPath = override_path;
  168. m_packageManager = new PackageManager(Path(), Path(), false);
  169. updatePackageSearchPath();
  170. }
  171.  
  172. private void init()
  173. {
  174. import std.file : tempDir;
  175. version(Windows){
  176. m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/";
  177. m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/";
  178. } else version(Posix){
  179. m_systemDubPath = Path("/var/lib/dub/");
  180. m_userDubPath = Path(environment.get("HOME")) ~ ".dub/";
  181. if(!m_userDubPath.absolute)
  182. m_userDubPath = Path(getcwd()) ~ m_userDubPath;
  183. }
  184.  
  185. m_tempPath = Path(tempDir);
  186. m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true);
  187. m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true);
  188.  
  189. determineDefaultCompiler();
  190. }
  191.  
  192. @property void dryRun(bool v) { m_dryRun = v; }
  193.  
  194. /** Returns the root path (usually the current working directory).
  195. */
  196. @property Path rootPath() const { return m_rootPath; }
  197. /// ditto
  198. @property void rootPath(Path root_path)
  199. {
  200. m_rootPath = root_path;
  201. if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath;
  202. }
  203.  
  204. /// Returns the name listed in the dub.json of the current
  205. /// application.
  206. @property string projectName() const { return m_project.name; }
  207.  
  208. @property Path projectPath() const { return m_projectPath; }
  209.  
  210. @property string[] configurations() const { return m_project.configurations; }
  211.  
  212. @property inout(PackageManager) packageManager() inout { return m_packageManager; }
  213.  
  214. @property inout(Project) project() inout { return m_project; }
  215.  
  216. /** Returns the default compiler binary to use for building D code.
  217.  
  218. If set, the "defaultCompiler" field of the DUB user or system
  219. configuration file will be used. Otherwise the PATH environment variable
  220. will be searched for files named "dmd", "gdc", "gdmd", "ldc2", "ldmd2"
  221. (in that order, taking into account operating system specific file
  222. extensions) and the first match is returned. If no match is found, "dmd"
  223. will be used.
  224. */
  225. @property string defaultCompiler() const { return m_defaultCompiler; }
  226.  
  227. /** Loads the package that resides within the configured `rootPath`.
  228. */
  229. void loadPackage()
  230. {
  231. loadPackage(m_rootPath);
  232. }
  233.  
  234. /// Loads the package from the specified path as the main project package.
  235. void loadPackage(Path path)
  236. {
  237. m_projectPath = path;
  238. updatePackageSearchPath();
  239. m_project = new Project(m_packageManager, m_projectPath);
  240. }
  241.  
  242. /// Loads a specific package as the main project package (can be a sub package)
  243. void loadPackage(Package pack)
  244. {
  245. m_projectPath = pack.path;
  246. updatePackageSearchPath();
  247. m_project = new Project(m_packageManager, pack);
  248. }
  249.  
  250. /** Loads a single file package.
  251.  
  252. Single-file packages are D files that contain a package receipe comment
  253. at their top. A recipe comment must be a nested `/+ ... +/` style
  254. comment, containing the virtual recipe file name and a colon, followed by the
  255. recipe contents (what would normally be in dub.sdl/dub.json).
  256.  
  257. Example:
  258. ---
  259. /+ dub.sdl:
  260. name "test"
  261. dependency "vibe-d" version="~>0.7.29"
  262. +/
  263. import vibe.http.server;
  264.  
  265. void main()
  266. {
  267. auto settings = new HTTPServerSettings;
  268. settings.port = 8080;
  269. listenHTTP(settings, &hello);
  270. }
  271.  
  272. void hello(HTTPServerRequest req, HTTPServerResponse res)
  273. {
  274. res.writeBody("Hello, World!");
  275. }
  276. ---
  277.  
  278. The script above can be invoked with "dub --single test.d".
  279. */
  280. void loadSingleFilePackage(Path path)
  281. {
  282. import dub.recipe.io : parsePackageRecipe;
  283. import std.file : mkdirRecurse, readText;
  284.  
  285. path = makeAbsolute(path);
  286.  
  287. string file_content = readText(path.toNativeString());
  288. auto idx = file_content.indexOf("/+");
  289. enforce(idx >= 0, "Missing /+ ... +/ recipe comment.");
  290. file_content = file_content[idx+2 .. $];
  291. idx = file_content.indexOf("+/");
  292. enforce(idx >= 0, "Missing closing \"+/\" for recipe comment.");
  293. string recipe_content = file_content[0 .. idx];
  294.  
  295. idx = recipe_content.indexOf(':');
  296. enforce(idx > 0, "Missing recipe file name (e.g. \"dub.sdl:\") in recipe comment");
  297. auto recipe_filename = recipe_content[0 .. idx].strip();
  298. recipe_content = recipe_content[idx+1 .. $];
  299.  
  300. auto recipe = parsePackageRecipe(recipe_content, recipe_filename);
  301. recipe.buildSettings.sourceFiles[""] = [path.toNativeString()];
  302. recipe.buildSettings.mainSourceFile = path.toNativeString();
  303. if (recipe.buildSettings.targetType == TargetType.autodetect)
  304. recipe.buildSettings.targetType = TargetType.executable;
  305.  
  306. auto pack = new Package(recipe, path.parentPath, null, "~master");
  307. loadPackage(pack);
  308. }
  309. /// ditto
  310. void loadSingleFilePackage(string path)
  311. {
  312. loadSingleFilePackage(Path(path));
  313. }
  314.  
  315. /** Disables the default search paths and only searches a specific directory
  316. for packages.
  317. */
  318. void overrideSearchPath(Path path)
  319. {
  320. if (!path.absolute) path = Path(getcwd()) ~ path;
  321. m_overrideSearchPath = path;
  322. updatePackageSearchPath();
  323. }
  324.  
  325. /** Gets the default configuration for a particular build platform.
  326.  
  327. This forwards to `Project.getDefaultConfiguration` and requires a
  328. project to be loaded.
  329. */
  330. string getDefaultConfiguration(BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); }
  331.  
  332. /** Attempts to upgrade the dependency selection of the loaded project.
  333. */
  334. void upgrade(UpgradeOptions options)
  335. {
  336. // clear non-existent version selections
  337. if (!(options & UpgradeOptions.upgrade)) {
  338. next_pack:
  339. foreach (p; m_project.selections.selectedPackages) {
  340. auto dep = m_project.selections.getSelectedVersion(p);
  341. if (!dep.path.empty) {
  342. auto path = dep.path;
  343. if (!path.absolute) path = this.rootPath ~ path;
  344. try if (m_packageManager.getOrLoadPackage(path)) continue;
  345. catch (Exception e) { logDebug("Failed to load path based selection: %s", e.toString().sanitize); }
  346. } else {
  347. if (m_packageManager.getPackage(p, dep.version_)) continue;
  348. foreach (ps; m_packageSuppliers) {
  349. try {
  350. auto versions = ps.getVersions(p);
  351. if (versions.canFind!(v => dep.matches(v)))
  352. continue next_pack;
  353. } catch (Exception e) {
  354. logDiagnostic("Error querying versions for %s, %s: %s", p, ps.description, e.msg);
  355. logDebug("Full error: %s", e.toString().sanitize());
  356. }
  357. }
  358. }
  359.  
  360. logWarn("Selected package %s %s doesn't exist. Using latest matching version instead.", p, dep);
  361. m_project.selections.deselectVersion(p);
  362. }
  363. }
  364.  
  365. Dependency[string] versions;
  366. if ((options & UpgradeOptions.useCachedResult) && m_project.isUpgradeCacheUpToDate()) {
  367. logDiagnostic("Using cached upgrade results...");
  368. versions = m_project.getUpgradeCache();
  369. } else {
  370. auto resolver = new DependencyVersionResolver(this, options);
  371. versions = resolver.resolve(m_project.rootPackage, m_project.selections);
  372. if (options & UpgradeOptions.useCachedResult) {
  373. logDiagnostic("Caching upgrade results...");
  374. m_project.setUpgradeCache(versions);
  375. }
  376. }
  377.  
  378. if (options & UpgradeOptions.printUpgradesOnly) {
  379. bool any = false;
  380. string rootbasename = getBasePackageName(m_project.rootPackage.name);
  381.  
  382. foreach (p, ver; versions) {
  383. if (!ver.path.empty) continue;
  384.  
  385. auto basename = getBasePackageName(p);
  386. if (basename == rootbasename) continue;
  387.  
  388. if (!m_project.selections.hasSelectedVersion(basename)) {
  389. logInfo("Package %s can be installed with version %s.",
  390. basename, ver);
  391. any = true;
  392. continue;
  393. }
  394. auto sver = m_project.selections.getSelectedVersion(basename);
  395. if (!sver.path.empty) continue;
  396. if (ver.version_ <= sver.version_) continue;
  397. logInfo("Package %s can be upgraded from %s to %s.",
  398. basename, sver, ver);
  399. any = true;
  400. }
  401. if (any) logInfo("Use \"dub upgrade\" to perform those changes.");
  402. return;
  403. }
  404.  
  405. foreach (p; versions.byKey) {
  406. auto ver = versions[p]; // Workaround for DMD 2.070.0 AA issue (crashes in aaApply2 if iterating by key+value)
  407. assert(!p.canFind(":"), "Resolved packages contain a sub package!?: "~p);
  408. Package pack;
  409. if (!ver.path.empty) {
  410. try pack = m_packageManager.getOrLoadPackage(ver.path);
  411. catch (Exception e) {
  412. logDebug("Failed to load path based selection: %s", e.toString().sanitize);
  413. continue;
  414. }
  415. } else {
  416. pack = m_packageManager.getBestPackage(p, ver);
  417. if (pack && m_packageManager.isManagedPackage(pack)
  418. && ver.version_.isBranch && (options & UpgradeOptions.upgrade) != 0)
  419. {
  420. // TODO: only re-install if there is actually a new commit available
  421. logInfo("Re-installing branch based dependency %s %s", p, ver.toString());
  422. m_packageManager.remove(pack, (options & UpgradeOptions.forceRemove) != 0);
  423. pack = null;
  424. }
  425. }
  426.  
  427. FetchOptions fetchOpts;
  428. fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none;
  429. fetchOpts |= (options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none;
  430. if (!pack) fetch(p, ver, defaultPlacementLocation, fetchOpts, "getting selected version");
  431. if ((options & UpgradeOptions.select) && p != m_project.rootPackage.name) {
  432. if (ver.path.empty) m_project.selections.selectVersion(p, ver.version_);
  433. else {
  434. Path relpath = ver.path;
  435. if (relpath.absolute) relpath = relpath.relativeTo(m_project.rootPackage.path);
  436. m_project.selections.selectVersion(p, relpath);
  437. }
  438. }
  439. }
  440.  
  441. m_project.reinit();
  442.  
  443. if ((options & UpgradeOptions.select) && !(options & UpgradeOptions.noSaveSelections))
  444. m_project.saveSelections();
  445. }
  446.  
  447. /** Generate project files for a specified generator.
  448.  
  449. Any existing project files will be overridden.
  450. */
  451. void generateProject(string ide, GeneratorSettings settings)
  452. {
  453. auto generator = createProjectGenerator(ide, m_project);
  454. if (m_dryRun) return; // TODO: pass m_dryRun to the generator
  455. generator.generate(settings);
  456. }
  457.  
  458. /** Executes tests on the current project.
  459.  
  460. Throws an exception, if unittests failed.
  461. */
  462. void testProject(GeneratorSettings settings, string config, Path custom_main_file)
  463. {
  464. if (custom_main_file.length && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file;
  465.  
  466. if (config.length == 0) {
  467. // if a custom main file was given, favor the first library configuration, so that it can be applied
  468. if (custom_main_file.length) config = m_project.getDefaultConfiguration(settings.platform, false);
  469. // else look for a "unittest" configuration
  470. if (!config.length && m_project.rootPackage.configurations.canFind("unittest")) config = "unittest";
  471. // if not found, fall back to the first "library" configuration
  472. if (!config.length) config = m_project.getDefaultConfiguration(settings.platform, false);
  473. // if still nothing found, use the first executable configuration
  474. if (!config.length) config = m_project.getDefaultConfiguration(settings.platform, true);
  475. }
  476.  
  477. auto generator = createProjectGenerator("build", m_project);
  478.  
  479. auto test_config = format("__test__%s__", config);
  480.  
  481. BuildSettings lbuildsettings = settings.buildSettings;
  482. m_project.addBuildSettings(lbuildsettings, settings.platform, config, null, true);
  483. if (lbuildsettings.targetType == TargetType.none) {
  484. logInfo(`Configuration '%s' has target type "none". Skipping test.`, config);
  485. return;
  486. }
  487.  
  488. if (lbuildsettings.targetType == TargetType.executable) {
  489. if (config == "unittest") logInfo("Running custom 'unittest' configuration.", config);
  490. else logInfo(`Configuration '%s' does not output a library. Falling back to "dub -b unittest -c %s".`, config, config);
  491. if (!custom_main_file.empty) logWarn("Ignoring custom main file.");
  492. settings.config = config;
  493. } else if (lbuildsettings.sourceFiles.empty) {
  494. logInfo(`No source files found in configuration '%s'. Falling back to "dub -b unittest".`, config);
  495. if (!custom_main_file.empty) logWarn("Ignoring custom main file.");
  496. settings.config = m_project.getDefaultConfiguration(settings.platform);
  497. } else {
  498. logInfo(`Generating test runner configuration '%s' for '%s' (%s).`, test_config, config, lbuildsettings.targetType);
  499.  
  500. BuildSettingsTemplate tcinfo = m_project.rootPackage.recipe.getConfiguration(config).buildSettings;
  501. tcinfo.targetType = TargetType.executable;
  502. tcinfo.targetName = test_config;
  503. tcinfo.versions[""] ~= "VibeCustomMain"; // HACK for vibe.d's legacy main() behavior
  504. string custommodname;
  505. if (custom_main_file.length) {
  506. import std.path;
  507. tcinfo.sourceFiles[""] ~= custom_main_file.relativeTo(m_project.rootPackage.path).toNativeString();
  508. tcinfo.importPaths[""] ~= custom_main_file.parentPath.toNativeString();
  509. custommodname = custom_main_file.head.toString().baseName(".d");
  510. }
  511.  
  512. string[] import_modules;
  513. foreach (file; lbuildsettings.sourceFiles) {
  514. if (file.endsWith(".d") && Path(file).head.toString() != "package.d")
  515. import_modules ~= dub.internal.utils.determineModuleName(lbuildsettings, Path(file), m_project.rootPackage.path);
  516. }
  517.  
  518. // generate main file
  519. Path mainfile = getTempFile("dub_test_root", ".d");
  520. tcinfo.sourceFiles[""] ~= mainfile.toNativeString();
  521. tcinfo.mainSourceFile = mainfile.toNativeString();
  522. if (!m_dryRun) {
  523. auto fil = openFile(mainfile, FileMode.createTrunc);
  524. scope(exit) fil.close();
  525. fil.write("module dub_test_root;\n");
  526. fil.write("import std.typetuple;\n");
  527. foreach (mod; import_modules) fil.write(format("static import %s;\n", mod));
  528. fil.write("alias allModules = TypeTuple!(");
  529. foreach (i, mod; import_modules) {
  530. if (i > 0) fil.write(", ");
  531. fil.write(mod);
  532. }
  533. fil.write(");\n");
  534. if (custommodname.length) {
  535. fil.write(format("import %s;\n", custommodname));
  536. } else {
  537. fil.write(q{
  538. import std.stdio;
  539. import core.runtime;
  540.  
  541. void main() { writeln("All unit tests have been run successfully."); }
  542. shared static this() {
  543. version (Have_tested) {
  544. import tested;
  545. import core.runtime;
  546. import std.exception;
  547. Runtime.moduleUnitTester = () => true;
  548. //runUnitTests!app(new JsonTestResultWriter("results.json"));
  549. enforce(runUnitTests!allModules(new ConsoleTestResultWriter), "Unit tests failed.");
  550. }
  551. }
  552. });
  553. }
  554. }
  555. m_project.rootPackage.recipe.configurations ~= ConfigurationInfo(test_config, tcinfo);
  556. m_project = new Project(m_packageManager, m_project.rootPackage);
  557.  
  558. settings.config = test_config;
  559. }
  560.  
  561. generator.generate(settings);
  562. }
  563.  
  564. /** Prints the specified build settings necessary for building the root package.
  565. */
  566. void listProjectData(GeneratorSettings settings, string[] requestedData, ListBuildSettingsFormat list_type)
  567. {
  568. import std.stdio;
  569. import std.ascii : newline;
  570.  
  571. // Split comma-separated lists
  572. string[] requestedDataSplit =
  573. requestedData
  574. .map!(a => a.splitter(",").map!strip)
  575. .joiner()
  576. .array();
  577.  
  578. auto data = m_project.listBuildSettings(settings, requestedDataSplit, list_type);
  579.  
  580. string delimiter;
  581. final switch (list_type) with (ListBuildSettingsFormat) {
  582. case list: delimiter = newline ~ newline; break;
  583. case listNul: delimiter = "\0\0"; break;
  584. case commandLine: delimiter = " "; break;
  585. case commandLineNul: delimiter = "\0\0"; break;
  586. }
  587.  
  588. write(data.joiner(delimiter));
  589. if (delimiter != "\0\0") writeln();
  590. }
  591.  
  592. /// Cleans intermediate/cache files of the given package
  593. void cleanPackage(Path path)
  594. {
  595. logInfo("Cleaning package at %s...", path.toNativeString());
  596. enforce(!Package.findPackageFile(path).empty, "No package found.", path.toNativeString());
  597.  
  598. // TODO: clear target files and copy files
  599.  
  600. if (existsFile(path ~ ".dub/build")) rmdirRecurse((path ~ ".dub/build").toNativeString());
  601. if (existsFile(path ~ ".dub/obj")) rmdirRecurse((path ~ ".dub/obj").toNativeString());
  602. }
  603.  
  604. /// Fetches the package matching the dependency and places it in the specified location.
  605. Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options, string reason = "")
  606. {
  607. Json pinfo;
  608. PackageSupplier supplier;
  609. foreach(ps; m_packageSuppliers){
  610. try {
  611. pinfo = ps.fetchPackageRecipe(packageId, dep, (options & FetchOptions.usePrerelease) != 0);
  612. supplier = ps;
  613. break;
  614. } catch(Exception e) {
  615. logDiagnostic("Package %s not found for %s: %s", packageId, ps.description, e.msg);
  616. logDebug("Full error: %s", e.toString().sanitize());
  617. }
  618. }
  619. enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency "~dep.toString());
  620. string ver = pinfo["version"].get!string;
  621.  
  622. Path placement;
  623. final switch (location) {
  624. case PlacementLocation.local: placement = m_rootPath; break;
  625. case PlacementLocation.user: placement = m_userDubPath ~ "packages/"; break;
  626. case PlacementLocation.system: placement = m_systemDubPath ~ "packages/"; break;
  627. }
  628.  
  629. // always upgrade branch based versions - TODO: actually check if there is a new commit available
  630. Package existing;
  631. try existing = m_packageManager.getPackage(packageId, ver, placement);
  632. catch (Exception e) {
  633. logWarn("Failed to load existing package %s: %s", ver, e.msg);
  634. logDiagnostic("Full error: %s", e.toString().sanitize);
  635. }
  636.  
  637. if (options & FetchOptions.printOnly) {
  638. if (existing && existing.version_ != Version(ver))
  639. logInfo("A new version for %s is available (%s -> %s). Run \"dub upgrade %s\" to switch.",
  640. packageId, existing.version_, ver, packageId);
  641. return null;
  642. }
  643.  
  644. if (existing) {
  645. if (!ver.startsWith("~") || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) {
  646. // TODO: support git working trees by performing a "git pull" instead of this
  647. logDiagnostic("Package %s %s (%s) is already present with the latest version, skipping upgrade.",
  648. packageId, ver, placement);
  649. return existing;
  650. } else {
  651. logInfo("Removing %s %s to prepare replacement with a new version.", packageId, ver);
  652. if (!m_dryRun) m_packageManager.remove(existing, (options & FetchOptions.forceRemove) != 0);
  653. }
  654. }
  655.  
  656. if (reason.length) logInfo("Fetching %s %s (%s)...", packageId, ver, reason);
  657. else logInfo("Fetching %s %s...", packageId, ver);
  658. if (m_dryRun) return null;
  659.  
  660. logDiagnostic("Acquiring package zip file");
  661.  
  662. auto clean_package_version = ver[ver.startsWith("~") ? 1 : 0 .. $];
  663. clean_package_version = clean_package_version.replace("+", "_"); // + has special meaning for Optlink
  664. if (!placement.existsFile())
  665. mkdirRecurse(placement.toNativeString());
  666. Path dstpath = placement ~ (packageId ~ "-" ~ clean_package_version);
  667. if (!dstpath.existsFile())
  668. mkdirRecurse(dstpath.toNativeString());
  669.  
  670. // Support libraries typically used with git submodules like ae.
  671. // Such libraries need to have ".." as import path but this can create
  672. // import path leakage.
  673. dstpath = dstpath ~ packageId;
  674.  
  675. auto lock = lockFile(dstpath.toNativeString() ~ ".lock", 30.seconds); // possibly wait for other dub instance
  676. if (dstpath.existsFile())
  677. {
  678. m_packageManager.refresh(false);
  679. return m_packageManager.getPackage(packageId, ver, dstpath);
  680. }
  681.  
  682. auto path = getTempFile(packageId, ".zip");
  683. supplier.fetchPackage(path, packageId, dep, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail?
  684. scope(exit) std.file.remove(path.toNativeString());
  685.  
  686. logInfo("Placing %s %s to %s...", packageId, ver, placement.toNativeString());
  687. return m_packageManager.storeFetchedPackage(path, pinfo, dstpath);
  688. }
  689.  
  690. /** Removes a specific locally cached package.
  691.  
  692. This will delete the package files from disk and removes the
  693. corresponding entry from the list of known packages.
  694.  
  695. Params:
  696. pack = Package instance to remove
  697. force_remove = Forces removal of the package, even if untracked
  698. files are found in its folder.
  699. */
  700. void remove(in Package pack, bool force_remove)
  701. {
  702. logInfo("Removing %s in %s", pack.name, pack.path.toNativeString());
  703. if (!m_dryRun) m_packageManager.remove(pack, force_remove);
  704. }
  705.  
  706. /// @see remove(string, string, RemoveLocation)
  707. enum RemoveVersionWildcard = "*";
  708.  
  709. /** Removes one or more versions of a locally cached package.
  710.  
  711. This will remove a given package with a specified version from the
  712. given location. It will remove at most one package, unless `version_`
  713. is set to `RemoveVersionWildcard`.
  714.  
  715. Params:
  716. package_id = Name of the package to be removed
  717. version_ = Identifying a version or a wild card. If an empty string
  718. is passed, the package will be removed from the location, if
  719. there is only one version retrieved. This will throw an
  720. exception, if there are multiple versions retrieved.
  721. location_ = Specifies the location to look for the given package
  722. name/version.
  723. force_remove = Forces removal of the package, even if untracked
  724. files are found in its folder.
  725. */
  726. void remove(string package_id, string version_, PlacementLocation location, bool force_remove)
  727. {
  728. enforce(!package_id.empty);
  729. if (location == PlacementLocation.local) {
  730. logInfo("To remove a locally placed package, make sure you don't have any data"
  731. ~ "\nleft in it's directory and then simply remove the whole directory.");
  732. throw new Exception("dub cannot remove locally installed packages.");
  733. }
  734.  
  735. Package[] packages;
  736. const bool wildcardOrEmpty = version_ == RemoveVersionWildcard || version_.empty;
  737.  
  738. // Retrieve packages to be removed.
  739. foreach(pack; m_packageManager.getPackageIterator(package_id))
  740. if ((wildcardOrEmpty || pack.version_ == Version(version_)) && m_packageManager.isManagedPackage(pack))
  741. packages ~= pack;
  742.  
  743. // Check validity of packages to be removed.
  744. if(packages.empty) {
  745. throw new Exception("Cannot find package to remove. ("
  746. ~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location) ~ "'"
  747. ~ ")");
  748. }
  749. if(version_.empty && packages.length > 1) {
  750. logError("Cannot remove package '" ~ package_id ~ "', there are multiple possibilities at location\n"
  751. ~ "'" ~ to!string(location) ~ "'.");
  752. logError("Available versions:");
  753. foreach(pack; packages)
  754. logError(" %s", pack.version_);
  755. throw new Exception("Please specify a individual version using --version=... or use the"
  756. ~ " wildcard --version=" ~ RemoveVersionWildcard ~ " to remove all versions.");
  757. }
  758.  
  759. logDebug("Removing %s packages.", packages.length);
  760. foreach(pack; packages) {
  761. try {
  762. remove(pack, force_remove);
  763. logInfo("Removed %s, version %s.", package_id, pack.version_);
  764. } catch (Exception e) {
  765. logError("Failed to remove %s %s: %s", package_id, pack.version_, e.msg);
  766. logInfo("Continuing with other packages (if any).");
  767. }
  768. }
  769. }
  770.  
  771. /** Adds a directory to the list of locally known packages.
  772.  
  773. Forwards to `PackageManager.addLocalPackage`.
  774.  
  775. Params:
  776. path = Path to the package
  777. ver = Optional version to associate with the package (can be left
  778. empty)
  779. system = Make the package known system wide instead of user wide
  780. (requires administrator privileges).
  781.  
  782. See_Also: `removeLocalPackage`
  783. */
  784. void addLocalPackage(string path, string ver, bool system)
  785. {
  786. if (m_dryRun) return;
  787. m_packageManager.addLocalPackage(makeAbsolute(path), ver, system ? LocalPackageType.system : LocalPackageType.user);
  788. }
  789.  
  790. /** Removes a directory from the list of locally known packages.
  791.  
  792. Forwards to `PackageManager.removeLocalPackage`.
  793.  
  794. Params:
  795. path = Path to the package
  796. system = Make the package known system wide instead of user wide
  797. (requires administrator privileges).
  798.  
  799. See_Also: `addLocalPackage`
  800. */
  801. void removeLocalPackage(string path, bool system)
  802. {
  803. if (m_dryRun) return;
  804. m_packageManager.removeLocalPackage(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
  805. }
  806.  
  807. /** Registers a local directory to search for packages to use for satisfying
  808. dependencies.
  809.  
  810. Params:
  811. path = Path to a directory containing package directories
  812. system = Make the package known system wide instead of user wide
  813. (requires administrator privileges).
  814.  
  815. See_Also: `removeSearchPath`
  816. */
  817. void addSearchPath(string path, bool system)
  818. {
  819. if (m_dryRun) return;
  820. m_packageManager.addSearchPath(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
  821. }
  822.  
  823. /** Unregisters a local directory search path.
  824.  
  825. Params:
  826. path = Path to a directory containing package directories
  827. system = Make the package known system wide instead of user wide
  828. (requires administrator privileges).
  829.  
  830. See_Also: `addSearchPath`
  831. */
  832. void removeSearchPath(string path, bool system)
  833. {
  834. if (m_dryRun) return;
  835. m_packageManager.removeSearchPath(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
  836. }
  837.  
  838. /** Queries all package suppliers with the given query string.
  839.  
  840. Returns a list of tuples, where the first entry is the human readable
  841. name of the package supplier and the second entry is the list of
  842. matched packages.
  843.  
  844. See_Also: `PackageSupplier.searchPackages`
  845. */
  846. auto searchPackages(string query)
  847. {
  848. return m_packageSuppliers.map!(ps => tuple(ps.description, ps.searchPackages(query))).array
  849. .filter!(t => t[1].length);
  850. }
  851.  
  852. /** Returns a list of all available versions (including branches) for a
  853. particular package.
  854.  
  855. The list returned is based on the registered package suppliers. Local
  856. packages are not queried in the search for versions.
  857.  
  858. See_also: `getLatestVersion`
  859. */
  860. Version[] listPackageVersions(string name)
  861. {
  862. Version[] versions;
  863. foreach (ps; this.m_packageSuppliers) {
  864. try versions ~= ps.getVersions(name);
  865. catch (Exception e) {
  866. logDebug("Failed to get versions for package %s on provider %s: %s", name, ps.description, e.msg);
  867. }
  868. }
  869. return versions.sort().uniq.array;
  870. }
  871.  
  872. /** Returns the latest available version for a particular package.
  873.  
  874. This function returns the latest numbered version of a package. If no
  875. numbered versions are available, it will return an available branch,
  876. preferring "~master".
  877.  
  878. Params:
  879. package_name: The name of the package in question.
  880. prefer_stable: If set to `true` (the default), returns the latest
  881. stable version, even if there are newer pre-release versions.
  882.  
  883. See_also: `listPackageVersions`
  884. */
  885. Version getLatestVersion(string package_name, bool prefer_stable = true)
  886. {
  887. auto vers = listPackageVersions(package_name);
  888. enforce(!vers.empty, "Failed to find any valid versions for a package name of '"~package_name~"'.");
  889. auto final_versions = vers.filter!(v => !v.isBranch && !v.isPreRelease).array;
  890. if (prefer_stable && final_versions.length) return final_versions[$-1];
  891. else if (vers[$-1].isBranch) return vers[$-1];
  892. else return vers[$-1];
  893. }
  894.  
  895. /** Initializes a directory with a package skeleton.
  896.  
  897. Params:
  898. path = Path of the directory to create the new package in. The
  899. directory will be created if it doesn't exist.
  900. deps = List of dependencies to add to the package recipe.
  901. type = Specifies the type of the application skeleton to use.
  902. format = Determines the package recipe format to use.
  903. recipe_callback = Optional callback that can be used to
  904. customize the recipe before it gets written.
  905. */
  906. void createEmptyPackage(Path path, string[] deps, string type,
  907. PackageFormat format = PackageFormat.sdl,
  908. scope void delegate(ref PackageRecipe, ref PackageFormat) recipe_callback = null)
  909. {
  910. if (!path.absolute) path = m_rootPath ~ path;
  911. path.normalize();
  912.  
  913. string[string] depVers;
  914. string[] notFound; // keep track of any failed packages in here
  915. foreach (dep; deps) {
  916. Version ver;
  917. try {
  918. ver = getLatestVersion(dep);
  919. depVers[dep] = ver.isBranch ? ver.toString() : "~>" ~ ver.toString();
  920. } catch (Exception e) {
  921. notFound ~= dep;
  922. }
  923. }
  924.  
  925. if(notFound.length > 1){
  926. throw new Exception(.format("Couldn't find packages: %-(%s, %).", notFound));
  927. }
  928. else if(notFound.length == 1){
  929. throw new Exception(.format("Couldn't find package: %-(%s, %).", notFound));
  930. }
  931.  
  932. if (m_dryRun) return;
  933.  
  934. initPackage(path, depVers, type, format, recipe_callback);
  935.  
  936. //Act smug to the user.
  937. logInfo("Successfully created an empty project in '%s'.", path.toNativeString());
  938. }
  939.  
  940. /** Converts the package recipe of the loaded root package to the given format.
  941.  
  942. Params:
  943. destination_file_ext = The file extension matching the desired
  944. format. Possible values are "json" or "sdl".
  945. */
  946. void convertRecipe(string destination_file_ext)
  947. {
  948. import std.path : extension;
  949. import dub.recipe.io : writePackageRecipe;
  950.  
  951. auto srcfile = m_project.rootPackage.recipePath;
  952. auto srcext = srcfile[$-1].toString().extension;
  953. if (srcext == "."~destination_file_ext) {
  954. logInfo("Package format is already %s.", destination_file_ext);
  955. return;
  956. }
  957.  
  958. writePackageRecipe(srcfile[0 .. $-1] ~ ("dub."~destination_file_ext), m_project.rootPackage.recipe);
  959. removeFile(srcfile);
  960. }
  961.  
  962. /** Runs DDOX to generate or serve documentation.
  963.  
  964. Params:
  965. run = If set to true, serves documentation on a local web server.
  966. Otherwise generates actual HTML files.
  967. generate_args = Additional command line arguments to pass to
  968. "ddox generate-html" or "ddox serve-html".
  969. */
  970. void runDdox(bool run, string[] generate_args = null)
  971. {
  972. if (m_dryRun) return;
  973.  
  974. // allow to choose a custom ddox tool
  975. auto tool = m_project.rootPackage.recipe.ddoxTool;
  976. if (tool.empty) tool = "ddox";
  977.  
  978. auto tool_pack = m_packageManager.getBestPackage(tool, ">=0.0.0");
  979. if (!tool_pack) tool_pack = m_packageManager.getBestPackage(tool, "~master");
  980. if (!tool_pack) {
  981. logInfo("% is not present, getting and storing it user wide", tool);
  982. tool_pack = fetch(tool, Dependency(">=0.0.0"), defaultPlacementLocation, FetchOptions.none);
  983. }
  984.  
  985. auto ddox_dub = new Dub(null, m_packageSuppliers);
  986. ddox_dub.loadPackage(tool_pack.path);
  987. ddox_dub.upgrade(UpgradeOptions.select);
  988.  
  989. auto compiler_binary = this.defaultCompiler;
  990.  
  991. GeneratorSettings settings;
  992. settings.config = "application";
  993. settings.compiler = getCompiler(compiler_binary); // TODO: not using --compiler ???
  994. settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary);
  995. settings.buildType = "debug";
  996. settings.run = true;
  997.  
  998. auto filterargs = m_project.rootPackage.recipe.ddoxFilterArgs.dup;
  999. if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
  1000.  
  1001. settings.runArgs = "filter" ~ filterargs ~ "docs.json";
  1002. ddox_dub.generateProject("build", settings);
  1003.  
  1004. auto p = tool_pack.path;
  1005. p.endsWithSlash = true;
  1006. auto tool_path = p.toNativeString();
  1007.  
  1008. if (run) {
  1009. settings.runArgs = ["serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~tool_path~"public"] ~ generate_args;
  1010. browse("http://127.0.0.1:8080/");
  1011. } else {
  1012. settings.runArgs = ["generate-html", "--navigation-type=ModuleTree", "docs.json", "docs"] ~ generate_args;
  1013. }
  1014. ddox_dub.generateProject("build", settings);
  1015.  
  1016. if (!run) {
  1017. // TODO: ddox should copy those files itself
  1018. version(Windows) runCommand("xcopy /S /D "~tool_path~"public\\* docs\\");
  1019. else runCommand("rsync -ru '"~tool_path~"public/' docs/");
  1020. }
  1021. }
  1022.  
  1023. private void updatePackageSearchPath()
  1024. {
  1025. if (m_overrideSearchPath.length) {
  1026. m_packageManager.disableDefaultSearchPaths = true;
  1027. m_packageManager.searchPath = [m_overrideSearchPath];
  1028. } else {
  1029. auto p = environment.get("DUBPATH");
  1030. Path[] paths;
  1031.  
  1032. version(Windows) enum pathsep = ";";
  1033. else enum pathsep = ":";
  1034. if (p.length) paths ~= p.split(pathsep).map!(p => Path(p))().array();
  1035. m_packageManager.disableDefaultSearchPaths = false;
  1036. m_packageManager.searchPath = paths;
  1037. }
  1038. }
  1039.  
  1040. private void determineDefaultCompiler()
  1041. {
  1042. import std.process : environment;
  1043.  
  1044. m_defaultCompiler = m_userConfig["defaultCompiler"].opt!string();
  1045. if (m_defaultCompiler.length) return;
  1046.  
  1047. m_defaultCompiler = m_systemConfig["defaultCompiler"].opt!string();
  1048. if (m_defaultCompiler.length) return;
  1049.  
  1050. version (Windows) enum sep = ";", exe = ".exe";
  1051. version (Posix) enum sep = ":", exe = "";
  1052.  
  1053. auto compilers = ["dmd", "gdc", "gdmd", "ldc2", "ldmd2"];
  1054.  
  1055. auto paths = environment.get("PATH", "").splitter(sep).map!Path;
  1056. auto res = compilers.find!(bin => paths.canFind!(p => existsFile(p ~ (bin~exe))));
  1057. m_defaultCompiler = res.empty ? compilers[0] : res.front;
  1058. }
  1059.  
  1060. private Path makeAbsolute(Path p) const { return p.absolute ? p : m_rootPath ~ p; }
  1061. private Path makeAbsolute(string p) const { return makeAbsolute(Path(p)); }
  1062. }
  1063.  
  1064.  
  1065. /// Option flags for `Dub.fetch`
  1066. enum FetchOptions
  1067. {
  1068. none = 0,
  1069. forceBranchUpgrade = 1<<0,
  1070. usePrerelease = 1<<1,
  1071. forceRemove = 1<<2,
  1072. printOnly = 1<<3,
  1073. }
  1074.  
  1075. /// Option flags for `Dub.upgrade`
  1076. enum UpgradeOptions
  1077. {
  1078. none = 0,
  1079. upgrade = 1<<1, /// Upgrade existing packages
  1080. preRelease = 1<<2, /// inclde pre-release versions in upgrade
  1081. forceRemove = 1<<3, /// Force removing package folders, which contain unknown files
  1082. select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
  1083. printUpgradesOnly = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
  1084. useCachedResult = 1<<6, /// Use cached information stored with the package to determine upgrades
  1085. noSaveSelections = 1<<7, /// Don't store updated selections on disk
  1086. }
  1087.  
  1088. /// Determines which of the default package suppliers are queried for packages.
  1089. enum SkipPackageSuppliers {
  1090. none, /// Uses all configured package suppliers.
  1091. standard, /// Does not use the default package suppliers (`defaultPackageSuppliers`).
  1092. all /// Uses only manually specified package suppliers.
  1093. }
  1094.  
  1095. private class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
  1096. protected {
  1097. Dub m_dub;
  1098. UpgradeOptions m_options;
  1099. Dependency[][string] m_packageVersions;
  1100. Package[string] m_remotePackages;
  1101. SelectedVersions m_selectedVersions;
  1102. Package m_rootPackage;
  1103. }
  1104.  
  1105.  
  1106. this(Dub dub, UpgradeOptions options)
  1107. {
  1108. m_dub = dub;
  1109. m_options = options;
  1110. }
  1111.  
  1112. Dependency[string] resolve(Package root, SelectedVersions selected_versions)
  1113. {
  1114. m_rootPackage = root;
  1115. m_selectedVersions = selected_versions;
  1116. return super.resolve(TreeNode(root.name, Dependency(root.version_)), (m_options & UpgradeOptions.printUpgradesOnly) == 0);
  1117. }
  1118.  
  1119. protected override Dependency[] getAllConfigs(string pack)
  1120. {
  1121. if (auto pvers = pack in m_packageVersions)
  1122. return *pvers;
  1123.  
  1124. if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(pack)) {
  1125. auto ret = [m_selectedVersions.getSelectedVersion(pack)];
  1126. logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
  1127. m_packageVersions[pack] = ret;
  1128. return ret;
  1129. }
  1130.  
  1131. logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
  1132. Version[] versions;
  1133. foreach (p; m_dub.packageManager.getPackageIterator(pack))
  1134. versions ~= p.version_;
  1135.  
  1136. foreach (ps; m_dub.m_packageSuppliers) {
  1137. try {
  1138. auto vers = ps.getVersions(pack);
  1139. vers.reverse();
  1140. if (!vers.length) {
  1141. logDiagnostic("No versions for %s for %s", pack, ps.description);
  1142. continue;
  1143. }
  1144.  
  1145. versions ~= vers;
  1146. break;
  1147. } catch (Exception e) {
  1148. logDebug("Package %s not found in %s: %s", pack, ps.description, e.msg);
  1149. logDebug("Full error: %s", e.toString().sanitize);
  1150. }
  1151. }
  1152.  
  1153. // sort by version, descending, and remove duplicates
  1154. versions = versions.sort!"a>b".uniq.array;
  1155.  
  1156. // move pre-release versions to the back of the list if no preRelease flag is given
  1157. if (!(m_options & UpgradeOptions.preRelease))
  1158. versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
  1159.  
  1160. // filter out invalid/unreachable dependency specs
  1161. versions = versions.filter!((v) {
  1162. bool valid = getPackage(pack, Dependency(v)) !is null;
  1163. if (!valid) logDiagnostic("Excluding invalid dependency specification %s %s from dependency resolution process.", pack, v);
  1164. return valid;
  1165. }).array;
  1166.  
  1167. if (!versions.length) logDiagnostic("Nothing found for %s", pack);
  1168. else logDiagnostic("Return for %s: %s", pack, versions);
  1169.  
  1170. auto ret = versions.map!(v => Dependency(v)).array;
  1171. m_packageVersions[pack] = ret;
  1172. return ret;
  1173. }
  1174.  
  1175. protected override Dependency[] getSpecificConfigs(string pack, TreeNodes nodes)
  1176. {
  1177. if (!nodes.configs.path.empty && getPackage(pack, nodes.configs)) return [nodes.configs];
  1178. else return null;
  1179. }
  1180.  
  1181.  
  1182. protected override TreeNodes[] getChildren(TreeNode node)
  1183. {
  1184. auto ret = appender!(TreeNodes[]);
  1185. auto pack = getPackage(node.pack, node.config);
  1186. if (!pack) {
  1187. // this can hapen when the package description contains syntax errors
  1188. logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
  1189. return null;
  1190. }
  1191. auto basepack = pack.basePackage;
  1192.  
  1193. foreach (d; pack.getAllDependencies()) {
  1194. auto dbasename = getBasePackageName(d.name);
  1195.  
  1196. // detect dependencies to the root package (or sub packages thereof)
  1197. if (dbasename == basepack.name) {
  1198. auto absdeppath = d.spec.mapToPath(pack.path).path;
  1199. absdeppath.endsWithSlash = true;
  1200. auto subpack = m_dub.m_packageManager.getSubPackage(basepack, getSubPackageName(d.name), true);
  1201. if (subpack) {
  1202. auto desireddeppath = d.name == dbasename ? basepack.path : subpack.path;
  1203. desireddeppath.endsWithSlash = true;
  1204. enforce(d.spec.path.empty || absdeppath == desireddeppath,
  1205. format("Dependency from %s to root package references wrong path: %s vs. %s",
  1206. node.pack, absdeppath.toNativeString(), desireddeppath.toNativeString()));
  1207. }
  1208. ret ~= TreeNodes(d.name, node.config);
  1209. continue;
  1210. }
  1211.  
  1212. DependencyType dt;
  1213. if (d.spec.optional) {
  1214. if (d.spec.default_) dt = DependencyType.optionalDefault;
  1215. else dt = DependencyType.optional;
  1216. } else dt = DependencyType.required;
  1217.  
  1218. if (m_options & UpgradeOptions.upgrade || !m_selectedVersions || !m_selectedVersions.hasSelectedVersion(dbasename)) {
  1219. // keep deselected dependencies deselected by default
  1220. if (m_selectedVersions && !m_selectedVersions.bare && dt == DependencyType.optionalDefault)
  1221. dt = DependencyType.optional;
  1222. ret ~= TreeNodes(d.name, d.spec.mapToPath(pack.path), dt);
  1223. } else {
  1224. // keep already selected optional dependencies if possible
  1225. if (dt == DependencyType.optional) dt = DependencyType.optionalDefault;
  1226. ret ~= TreeNodes(d.name, m_selectedVersions.getSelectedVersion(dbasename), dt);
  1227. }
  1228. }
  1229. return ret.data;
  1230. }
  1231.  
  1232. protected override bool matches(Dependency configs, Dependency config)
  1233. {
  1234. if (!configs.path.empty) return configs.path == config.path;
  1235. return configs.merge(config).valid;
  1236. }
  1237.  
  1238. private Package getPackage(string name, Dependency dep)
  1239. {
  1240. auto basename = getBasePackageName(name);
  1241.  
  1242. // for sub packages, first try to get them from the base package
  1243. if (basename != name) {
  1244. auto subname = getSubPackageName(name);
  1245. auto basepack = getPackage(basename, dep);
  1246. if (!basepack) return null;
  1247. if (auto sp = m_dub.m_packageManager.getSubPackage(basepack, subname, true)) {
  1248. return sp;
  1249. } else if (!basepack.subPackages.canFind!(p => p.path.length)) {
  1250. // note: external sub packages are handled further below
  1251. auto spr = basepack.getInternalSubPackage(subname);
  1252. if (!spr.isNull) {
  1253. auto sp = new Package(spr, basepack.path, basepack);
  1254. m_remotePackages[sp.name] = sp;
  1255. return sp;
  1256. } else {
  1257. logDiagnostic("Sub package %s doesn't exist in %s %s.", name, basename, dep.version_);
  1258. return null;
  1259. }
  1260. } else if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep)) {
  1261. return ret;
  1262. } else {
  1263. logDiagnostic("External sub package %s %s not found.", name, dep.version_);
  1264. return null;
  1265. }
  1266. }
  1267.  
  1268. // shortcut if the referenced package is the root package
  1269. if (basename == m_rootPackage.basePackage.name)
  1270. return m_rootPackage.basePackage;
  1271.  
  1272. if (!dep.path.empty) {
  1273. try {
  1274. auto ret = m_dub.packageManager.getOrLoadPackage(dep.path);
  1275. if (dep.matches(ret.version_)) return ret;
  1276. } catch (Exception e) {
  1277. logDiagnostic("Failed to load path based dependency %s: %s", name, e.msg);
  1278. logDebug("Full error: %s", e.toString().sanitize);
  1279. return null;
  1280. }
  1281. }
  1282.  
  1283. if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep))
  1284. return ret;
  1285.  
  1286. auto key = name ~ ":" ~ dep.version_.toString();
  1287. if (auto ret = key in m_remotePackages)
  1288. return *ret;
  1289.  
  1290. auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
  1291.  
  1292. auto rootpack = name.split(":")[0];
  1293.  
  1294. foreach (ps; m_dub.m_packageSuppliers) {
  1295. if (rootpack == name) {
  1296. try {
  1297. auto desc = ps.fetchPackageRecipe(name, dep, prerelease);
  1298. auto ret = new Package(desc);
  1299. m_remotePackages[key] = ret;
  1300. return ret;
  1301. } catch (Exception e) {
  1302. logDiagnostic("Metadata for %s %s could not be downloaded from %s: %s", name, dep, ps.description, e.msg);
  1303. logDebug("Full error: %s", e.toString().sanitize);
  1304. }
  1305. } else {
  1306. logDiagnostic("Package %s not found in base package description (%s). Downloading whole package.", name, dep.version_.toString());
  1307. try {
  1308. FetchOptions fetchOpts;
  1309. fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
  1310. fetchOpts |= (m_options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none;
  1311. m_dub.fetch(rootpack, dep, m_dub.defaultPlacementLocation, fetchOpts, "need sub package description");
  1312. auto ret = m_dub.m_packageManager.getBestPackage(name, dep);
  1313. if (!ret) {
  1314. logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name);
  1315. return null;
  1316. }
  1317. m_remotePackages[key] = ret;
  1318. return ret;
  1319. } catch (Exception e) {
  1320. logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg);
  1321. logDebug("Full error: %s", e.toString().sanitize);
  1322. }
  1323. }
  1324. }
  1325.  
  1326. m_remotePackages[key] = null;
  1327.  
  1328. logWarn("Package %s %s could not be loaded either locally, or from the configured package registries.", name, dep);
  1329. return null;
  1330. }
  1331. }