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