Newer
Older
dub_jkp / source / dub / dub.d
  1. /**
  2. A package manager.
  3.  
  4. Copyright: © 2012-2013 Matthias Dondorff
  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.  
  40. /// The default supplier for packages, which is the registry
  41. /// hosted by code.dlang.org.
  42. PackageSupplier[] defaultPackageSuppliers()
  43. {
  44. URL url = URL.parse("http://code.dlang.org/");
  45. logDiagnostic("Using dub registry url '%s'", url);
  46. return [new RegistryPackageSupplier(url)];
  47. }
  48.  
  49. /// Option flags for fetch
  50. enum FetchOptions
  51. {
  52. none = 0,
  53. forceBranchUpgrade = 1<<0,
  54. usePrerelease = 1<<1,
  55. forceRemove = 1<<2,
  56. printOnly = 1<<3,
  57. }
  58.  
  59. /// The Dub class helps in getting the applications
  60. /// dependencies up and running. An instance manages one application.
  61. class Dub {
  62. private {
  63. bool m_dryRun = false;
  64. PackageManager m_packageManager;
  65. PackageSupplier[] m_packageSuppliers;
  66. Path m_rootPath;
  67. Path m_tempPath;
  68. Path m_userDubPath, m_systemDubPath;
  69. Json m_systemConfig, m_userConfig;
  70. Path m_projectPath;
  71. Project m_project;
  72. Path m_overrideSearchPath;
  73. }
  74.  
  75. /// Initiales the package manager for the vibe application
  76. /// under root.
  77. this(PackageSupplier[] additional_package_suppliers = null, string root_path = ".")
  78. {
  79. m_rootPath = Path(root_path);
  80. if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath;
  81.  
  82. version(Windows){
  83. m_systemDubPath = Path(environment.get("ProgramData")) ~ "dub/";
  84. m_userDubPath = Path(environment.get("APPDATA")) ~ "dub/";
  85. m_tempPath = Path(environment.get("TEMP"));
  86. } else version(Posix){
  87. m_systemDubPath = Path("/var/lib/dub/");
  88. m_userDubPath = Path(environment.get("HOME")) ~ ".dub/";
  89. if(!m_userDubPath.absolute)
  90. m_userDubPath = Path(getcwd()) ~ m_userDubPath;
  91. m_tempPath = Path("/tmp");
  92. }
  93. m_userConfig = jsonFromFile(m_userDubPath ~ "settings.json", true);
  94. m_systemConfig = jsonFromFile(m_systemDubPath ~ "settings.json", true);
  95.  
  96. PackageSupplier[] ps = additional_package_suppliers;
  97. if (auto pp = "registryUrls" in m_userConfig)
  98. ps ~= deserializeJson!(string[])(*pp)
  99. .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url)))
  100. .array;
  101. if (auto pp = "registryUrls" in m_systemConfig)
  102. ps ~= deserializeJson!(string[])(*pp)
  103. .map!(url => cast(PackageSupplier)new RegistryPackageSupplier(URL(url)))
  104. .array;
  105. ps ~= defaultPackageSuppliers();
  106.  
  107. m_packageSuppliers = ps;
  108. m_packageManager = new PackageManager(m_userDubPath, m_systemDubPath, false);
  109. updatePackageSearchPath();
  110. }
  111.  
  112. /// Initializes DUB with only a single search path
  113. this(Path override_path)
  114. {
  115. m_overrideSearchPath = override_path;
  116. m_packageManager = new PackageManager(Path(), Path(), false);
  117. updatePackageSearchPath();
  118. }
  119.  
  120. @property void dryRun(bool v) { m_dryRun = v; }
  121.  
  122. /** Returns the root path (usually the current working directory).
  123. */
  124. @property Path rootPath() const { return m_rootPath; }
  125. /// ditto
  126. @property void rootPath(Path root_path)
  127. {
  128. m_rootPath = root_path;
  129. if (!m_rootPath.absolute) m_rootPath = Path(getcwd()) ~ m_rootPath;
  130. }
  131.  
  132. /// Returns the name listed in the package.json of the current
  133. /// application.
  134. @property string projectName() const { return m_project.name; }
  135.  
  136. @property Path projectPath() const { return m_projectPath; }
  137.  
  138. @property string[] configurations() const { return m_project.configurations; }
  139.  
  140. @property inout(PackageManager) packageManager() inout { return m_packageManager; }
  141.  
  142. @property inout(Project) project() inout { return m_project; }
  143.  
  144. /// Loads the package from the current working directory as the main
  145. /// project package.
  146. void loadPackageFromCwd()
  147. {
  148. loadPackage(m_rootPath);
  149. }
  150.  
  151. /// Loads the package from the specified path as the main project package.
  152. void loadPackage(Path path)
  153. {
  154. m_projectPath = path;
  155. updatePackageSearchPath();
  156. m_project = new Project(m_packageManager, m_projectPath);
  157. }
  158.  
  159. /// Loads a specific package as the main project package (can be a sub package)
  160. void loadPackage(Package pack)
  161. {
  162. m_projectPath = pack.path;
  163. updatePackageSearchPath();
  164. m_project = new Project(m_packageManager, pack);
  165. }
  166.  
  167. void overrideSearchPath(Path path)
  168. {
  169. if (!path.absolute) path = Path(getcwd()) ~ path;
  170. m_overrideSearchPath = path;
  171. updatePackageSearchPath();
  172. }
  173.  
  174. string getDefaultConfiguration(BuildPlatform platform, bool allow_non_library_configs = true) const { return m_project.getDefaultConfiguration(platform, allow_non_library_configs); }
  175.  
  176. void upgrade(UpgradeOptions options)
  177. {
  178. auto resolver = new DependencyVersionResolver(this, options);
  179. auto versions = resolver.resolve(m_project.rootPackage, m_project.selections);
  180.  
  181. foreach (p, ver; versions) {
  182. assert(!p.canFind(":"), "Resolved packages contain a sub package!?: "~p);
  183. Package pack;
  184. if (!ver.path.empty) pack = m_packageManager.getTemporaryPackage(ver.path);
  185. else pack = m_packageManager.getBestPackage(p, ver);
  186. FetchOptions fetchOpts;
  187. fetchOpts |= (options & UpgradeOptions.preRelease) != 0 ? FetchOptions.usePrerelease : FetchOptions.none;
  188. fetchOpts |= (options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none;
  189. if (!pack) fetch(p, ver, PlacementLocation.userWide, fetchOpts);
  190. if ((options & UpgradeOptions.select) && ver.path.empty)
  191. m_project.selections.selectVersion(p, ver.version_);
  192. }
  193.  
  194. if (options & UpgradeOptions.select)
  195. m_project.saveSelections();
  196.  
  197. m_project.reinit();
  198. }
  199.  
  200. /// Generate project files for a specified IDE.
  201. /// Any existing project files will be overridden.
  202. void generateProject(string ide, GeneratorSettings settings) {
  203. auto generator = createProjectGenerator(ide, m_project, m_packageManager);
  204. if (m_dryRun) return; // TODO: pass m_dryRun to the generator
  205. generator.generate(settings);
  206. }
  207.  
  208. /// Executes tests on the current project. Throws an exception, if
  209. /// unittests failed.
  210. void testProject(GeneratorSettings settings, string config, Path custom_main_file)
  211. {
  212. if (custom_main_file.length && !custom_main_file.absolute) custom_main_file = getWorkingDirectory() ~ custom_main_file;
  213.  
  214. if (config.length == 0) {
  215. // if a custom main file was given, favor the first library configuration, so that it can be applied
  216. if (custom_main_file.length) config = m_project.getDefaultConfiguration(settings.platform, false);
  217. // else look for a "unittest" configuration
  218. if (!config.length && m_project.rootPackage.configurations.canFind("unittest")) config = "unittest";
  219. // if not found, fall back to the first "library" configuration
  220. if (!config.length) config = m_project.getDefaultConfiguration(settings.platform, false);
  221. // if still nothing found, use the first executable configuration
  222. if (!config.length) config = m_project.getDefaultConfiguration(settings.platform, true);
  223. }
  224.  
  225. auto generator = createProjectGenerator("build", m_project, m_packageManager);
  226.  
  227. auto test_config = format("__test__%s__", config);
  228.  
  229. BuildSettings lbuildsettings = settings.buildSettings;
  230. m_project.addBuildSettings(lbuildsettings, settings.platform, config, null, true);
  231. if (lbuildsettings.targetType == TargetType.none) {
  232. logInfo(`Configuration '%s' has target type "none". Skipping test.`, config);
  233. return;
  234. }
  235. if (lbuildsettings.targetType == TargetType.executable) {
  236. if (config == "unittest") logInfo("Running custom 'unittest' configuration.", config);
  237. else logInfo(`Configuration '%s' does not output a library. Falling back to "dub -b unittest -c %s".`, config, config);
  238. if (!custom_main_file.empty) logWarn("Ignoring custom main file.");
  239. settings.config = config;
  240. } else if (lbuildsettings.sourceFiles.empty) {
  241. logInfo(`No source files found in configuration '%s'. Falling back to "dub -b unittest".`, config);
  242. if (!custom_main_file.empty) logWarn("Ignoring custom main file.");
  243. settings.config = m_project.getDefaultConfiguration(settings.platform);
  244. } else {
  245. logInfo(`Generating test runner configuration '%s' for '%s' (%s).`, test_config, config, lbuildsettings.targetType);
  246.  
  247. BuildSettingsTemplate tcinfo = m_project.rootPackage.info.getConfiguration(config).buildSettings;
  248. tcinfo.targetType = TargetType.executable;
  249. tcinfo.targetName = test_config;
  250. tcinfo.versions[""] ~= "VibeCustomMain"; // HACK for vibe.d's legacy main() behavior
  251. string custommodname;
  252. if (custom_main_file.length) {
  253. import std.path;
  254. tcinfo.sourceFiles[""] ~= custom_main_file.relativeTo(m_project.rootPackage.path).toNativeString();
  255. tcinfo.importPaths[""] ~= custom_main_file.parentPath.toNativeString();
  256. custommodname = custom_main_file.head.toString().baseName(".d");
  257. }
  258.  
  259. string[] import_modules;
  260. foreach (file; lbuildsettings.sourceFiles) {
  261. if (file.endsWith(".d") && Path(file).head.toString() != "package.d")
  262. import_modules ~= lbuildsettings.determineModuleName(Path(file), m_project.rootPackage.path);
  263. }
  264.  
  265. // generate main file
  266. Path mainfile = getTempDir() ~ "dub_test_root.d";
  267. tcinfo.sourceFiles[""] ~= mainfile.toNativeString();
  268. tcinfo.mainSourceFile = mainfile.toNativeString();
  269. if (!m_dryRun) {
  270. auto fil = openFile(mainfile, FileMode.CreateTrunc);
  271. scope(exit) fil.close();
  272. fil.write("module dub_test_root;\n");
  273. fil.write("import std.typetuple;\n");
  274. foreach (mod; import_modules) fil.write(format("static import %s;\n", mod));
  275. fil.write("alias allModules = TypeTuple!(");
  276. foreach (i, mod; import_modules) {
  277. if (i > 0) fil.write(", ");
  278. fil.write(mod);
  279. }
  280. fil.write(");\n");
  281. if (custommodname.length) {
  282. fil.write(format("import %s;\n", custommodname));
  283. } else {
  284. fil.write(q{
  285. import std.stdio;
  286. import core.runtime;
  287.  
  288. void main() { writeln("All unit tests have been run successfully."); }
  289. shared static this() {
  290. version (Have_tested) {
  291. import tested;
  292. import core.runtime;
  293. import std.exception;
  294. Runtime.moduleUnitTester = () => true;
  295. //runUnitTests!app(new JsonTestResultWriter("results.json"));
  296. enforce(runUnitTests!allModules(new ConsoleTestResultWriter), "Unit tests failed.");
  297. }
  298. }
  299. });
  300. }
  301. }
  302. m_project.rootPackage.info.configurations ~= ConfigurationInfo(test_config, tcinfo);
  303. m_project = new Project(m_packageManager, m_project.rootPackage);
  304.  
  305. settings.config = test_config;
  306. }
  307.  
  308. generator.generate(settings);
  309. }
  310.  
  311. /// Outputs a JSON description of the project, including its dependencies.
  312. void describeProject(BuildPlatform platform, string config)
  313. {
  314. auto dst = Json.emptyObject;
  315. dst.configuration = config;
  316. dst.compiler = platform.compiler;
  317. dst.architecture = platform.architecture.serializeToJson();
  318. dst.platform = platform.platform.serializeToJson();
  319.  
  320. m_project.describe(dst, platform, config);
  321.  
  322. import std.stdio;
  323. write(dst.toPrettyString());
  324. }
  325.  
  326.  
  327. /// Returns all cached packages as a "packageId" = "version" associative array
  328. string[string] cachedPackages() const { return m_project.cachedPackagesIDs(); }
  329.  
  330. /// Fetches the package matching the dependency and places it in the specified location.
  331. Package fetch(string packageId, const Dependency dep, PlacementLocation location, FetchOptions options)
  332. {
  333. Json pinfo;
  334. PackageSupplier supplier;
  335. foreach(ps; m_packageSuppliers){
  336. try {
  337. pinfo = ps.getPackageDescription(packageId, dep, (options & FetchOptions.usePrerelease) != 0);
  338. supplier = ps;
  339. break;
  340. } catch(Exception e) {
  341. logDiagnostic("Package %s not found for %s: %s", packageId, ps.description(), e.msg);
  342. logDebug("Full error: %s", e.toString().sanitize());
  343. }
  344. }
  345. enforce(pinfo.type != Json.Type.undefined, "No package "~packageId~" was found matching the dependency "~dep.toString());
  346. string ver = pinfo["version"].get!string;
  347.  
  348. Path placement;
  349. final switch (location) {
  350. case PlacementLocation.local: placement = m_rootPath; break;
  351. case PlacementLocation.userWide: placement = m_userDubPath ~ "packages/"; break;
  352. case PlacementLocation.systemWide: placement = m_systemDubPath ~ "packages/"; break;
  353. }
  354.  
  355. // always upgrade branch based versions - TODO: actually check if there is a new commit available
  356. auto existing = m_packageManager.getPackage(packageId, ver, placement);
  357. if (options & FetchOptions.printOnly) {
  358. if (existing && existing.vers != ver)
  359. logInfo("A new version for %s is available (%s -> %s). Run \"dub upgrade %s\" to switch.",
  360. packageId, existing.vers, ver, packageId);
  361. return null;
  362. }
  363. if (existing) {
  364. if (!ver.startsWith("~") || !(options & FetchOptions.forceBranchUpgrade) || location == PlacementLocation.local) {
  365. // TODO: support git working trees by performing a "git pull" instead of this
  366. logDiagnostic("Package %s %s (%s) is already present with the latest version, skipping upgrade.",
  367. packageId, ver, placement);
  368. return existing;
  369. } else {
  370. logInfo("Removing %s %s to prepare replacement with a new version.", packageId, ver);
  371. if (!m_dryRun) m_packageManager.remove(existing, (options & FetchOptions.forceRemove) != 0);
  372. }
  373. }
  374.  
  375. logInfo("Fetching %s %s...", packageId, ver);
  376. if (m_dryRun) return null;
  377.  
  378. logDiagnostic("Acquiring package zip file");
  379. auto dload = m_projectPath ~ ".dub/temp/downloads";
  380. auto tempfname = packageId ~ "-" ~ (ver.startsWith('~') ? ver[1 .. $] : ver) ~ ".zip";
  381. auto tempFile = m_tempPath ~ tempfname;
  382. string sTempFile = tempFile.toNativeString();
  383. if (exists(sTempFile)) std.file.remove(sTempFile);
  384. supplier.retrievePackage(tempFile, packageId, dep, (options & FetchOptions.usePrerelease) != 0); // Q: continue on fail?
  385. scope(exit) std.file.remove(sTempFile);
  386.  
  387. logInfo("Placing %s %s to %s...", packageId, ver, placement.toNativeString());
  388. auto clean_package_version = ver[ver.startsWith("~") ? 1 : 0 .. $];
  389. Path dstpath = placement ~ (packageId ~ "-" ~ clean_package_version);
  390.  
  391. return m_packageManager.storeFetchedPackage(tempFile, pinfo, dstpath);
  392. }
  393.  
  394. /// Removes a given package from the list of present/cached modules.
  395. /// @removeFromApplication: if true, this will also remove an entry in the
  396. /// list of dependencies in the application's package.json
  397. void remove(in Package pack, bool force_remove)
  398. {
  399. logInfo("Removing %s in %s", pack.name, pack.path.toNativeString());
  400. if (!m_dryRun) m_packageManager.remove(pack, force_remove);
  401. }
  402.  
  403. /// @see remove(string, string, RemoveLocation)
  404. enum RemoveVersionWildcard = "*";
  405.  
  406. /// This will remove a given package with a specified version from the
  407. /// location.
  408. /// It will remove at most one package, unless @param version_ is
  409. /// specified as wildcard "*".
  410. /// @param package_id Package to be removed
  411. /// @param version_ Identifying a version or a wild card. An empty string
  412. /// may be passed into. In this case the package will be removed from the
  413. /// location, if there is only one version retrieved. This will throw an
  414. /// exception, if there are multiple versions retrieved.
  415. /// Note: as wildcard string only RemoveVersionWildcard ("*") is supported.
  416. /// @param location_
  417. void remove(string package_id, string version_, PlacementLocation location_, bool force_remove)
  418. {
  419. enforce(!package_id.empty);
  420. if (location_ == PlacementLocation.local) {
  421. logInfo("To remove a locally placed package, make sure you don't have any data"
  422. ~ "\nleft in it's directory and then simply remove the whole directory.");
  423. throw new Exception("dub cannot remove locally installed packages.");
  424. }
  425.  
  426. Package[] packages;
  427. const bool wildcardOrEmpty = version_ == RemoveVersionWildcard || version_.empty;
  428.  
  429. // Retrieve packages to be removed.
  430. foreach(pack; m_packageManager.getPackageIterator(package_id))
  431. if( wildcardOrEmpty || pack.vers == version_ )
  432. packages ~= pack;
  433.  
  434. // Check validity of packages to be removed.
  435. if(packages.empty) {
  436. throw new Exception("Cannot find package to remove. ("
  437. ~ "id: '" ~ package_id ~ "', version: '" ~ version_ ~ "', location: '" ~ to!string(location_) ~ "'"
  438. ~ ")");
  439. }
  440. if(version_.empty && packages.length > 1) {
  441. logError("Cannot remove package '" ~ package_id ~ "', there are multiple possibilities at location\n"
  442. ~ "'" ~ to!string(location_) ~ "'.");
  443. logError("Retrieved versions:");
  444. foreach(pack; packages)
  445. logError("%s", pack.vers);
  446. throw new Exception("Please specify a individual version or use the wildcard identifier '"
  447. ~ RemoveVersionWildcard ~ "' (without quotes).");
  448. }
  449.  
  450. logDebug("Removing %s packages.", packages.length);
  451. foreach(pack; packages) {
  452. try {
  453. remove(pack, force_remove);
  454. logInfo("Removed %s, version %s.", package_id, pack.vers);
  455. } catch (Exception e) {
  456. logError("Failed to remove %s %s: %s", package_id, pack.vers, e.msg);
  457. logInfo("Continuing with other packages (if any).");
  458. }
  459. }
  460. }
  461.  
  462. void addLocalPackage(string path, string ver, bool system)
  463. {
  464. if (m_dryRun) return;
  465. m_packageManager.addLocalPackage(makeAbsolute(path), ver, system ? LocalPackageType.system : LocalPackageType.user);
  466. }
  467.  
  468. void removeLocalPackage(string path, bool system)
  469. {
  470. if (m_dryRun) return;
  471. m_packageManager.removeLocalPackage(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
  472. }
  473.  
  474. void addSearchPath(string path, bool system)
  475. {
  476. if (m_dryRun) return;
  477. m_packageManager.addSearchPath(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
  478. }
  479.  
  480. void removeSearchPath(string path, bool system)
  481. {
  482. if (m_dryRun) return;
  483. m_packageManager.removeSearchPath(makeAbsolute(path), system ? LocalPackageType.system : LocalPackageType.user);
  484. }
  485.  
  486. void createEmptyPackage(Path path, string type)
  487. {
  488. if( !path.absolute() ) path = m_rootPath ~ path;
  489. path.normalize();
  490.  
  491. if (m_dryRun) return;
  492.  
  493. initPackage(path, type);
  494.  
  495. //Act smug to the user.
  496. logInfo("Successfully created an empty project in '%s'.", path.toNativeString());
  497. }
  498.  
  499. void runDdox(bool run)
  500. {
  501. if (m_dryRun) return;
  502.  
  503. auto ddox_pack = m_packageManager.getBestPackage("ddox", ">=0.0.0");
  504. if (!ddox_pack) ddox_pack = m_packageManager.getBestPackage("ddox", "~master");
  505. if (!ddox_pack) {
  506. logInfo("DDOX is not present, getting it and storing user wide");
  507. ddox_pack = fetch("ddox", Dependency(">=0.0.0"), PlacementLocation.userWide, FetchOptions.none);
  508. }
  509.  
  510. version(Windows) auto ddox_exe = "ddox.exe";
  511. else auto ddox_exe = "ddox";
  512.  
  513. if( !existsFile(ddox_pack.path~ddox_exe) ){
  514. logInfo("DDOX in %s is not built, performing build now.", ddox_pack.path.toNativeString());
  515.  
  516. auto ddox_dub = new Dub(m_packageSuppliers);
  517. ddox_dub.loadPackage(ddox_pack.path);
  518.  
  519. auto compiler_binary = "dmd";
  520.  
  521. GeneratorSettings settings;
  522. settings.config = "application";
  523. settings.compiler = getCompiler(compiler_binary);
  524. settings.platform = settings.compiler.determinePlatform(settings.buildSettings, compiler_binary);
  525. settings.buildType = "debug";
  526. ddox_dub.generateProject("build", settings);
  527.  
  528. //runCommands(["cd "~ddox_pack.path.toNativeString()~" && dub build -v"]);
  529. }
  530.  
  531. auto p = ddox_pack.path;
  532. p.endsWithSlash = true;
  533. auto dub_path = p.toNativeString();
  534.  
  535. string[] commands;
  536. string[] filterargs = m_project.rootPackage.info.ddoxFilterArgs.dup;
  537. if (filterargs.empty) filterargs = ["--min-protection=Protected", "--only-documented"];
  538. commands ~= dub_path~"ddox filter "~filterargs.join(" ")~" docs.json";
  539. if (!run) {
  540. commands ~= dub_path~"ddox generate-html --navigation-type=ModuleTree docs.json docs";
  541. version(Windows) commands ~= "xcopy /S /D "~dub_path~"public\\* docs\\";
  542. else commands ~= "cp -ru \""~dub_path~"public\"/* docs/";
  543. }
  544. runCommands(commands);
  545.  
  546. if (run) {
  547. auto proc = spawnProcess([dub_path~"ddox", "serve-html", "--navigation-type=ModuleTree", "docs.json", "--web-file-dir="~dub_path~"public"]);
  548. browse("http://127.0.0.1:8080/");
  549. wait(proc);
  550. }
  551. }
  552.  
  553. private void updatePackageSearchPath()
  554. {
  555. if (m_overrideSearchPath.length) {
  556. m_packageManager.disableDefaultSearchPaths = true;
  557. m_packageManager.searchPath = [m_overrideSearchPath];
  558. } else {
  559. auto p = environment.get("DUBPATH");
  560. Path[] paths;
  561.  
  562. version(Windows) enum pathsep = ";";
  563. else enum pathsep = ":";
  564. if (p.length) paths ~= p.split(pathsep).map!(p => Path(p))().array();
  565. m_packageManager.disableDefaultSearchPaths = false;
  566. m_packageManager.searchPath = paths;
  567. }
  568. }
  569.  
  570. private Path makeAbsolute(Path p) const { return p.absolute ? p : m_rootPath ~ p; }
  571. private Path makeAbsolute(string p) const { return makeAbsolute(Path(p)); }
  572. }
  573.  
  574. string determineModuleName(BuildSettings settings, Path file, Path base_path)
  575. {
  576. assert(base_path.absolute);
  577. if (!file.absolute) file = base_path ~ file;
  578.  
  579. size_t path_skip = 0;
  580. foreach (ipath; settings.importPaths.map!(p => Path(p))) {
  581. if (!ipath.absolute) ipath = base_path ~ ipath;
  582. assert(!ipath.empty);
  583. if (file.startsWith(ipath) && ipath.length > path_skip)
  584. path_skip = ipath.length;
  585. }
  586.  
  587. enforce(path_skip > 0,
  588. format("Source file '%s' not found in any import path.", file.toNativeString()));
  589.  
  590. auto mpath = file[path_skip .. file.length];
  591. auto ret = appender!string;
  592. foreach (i; 0 .. mpath.length) {
  593. import std.path;
  594. auto p = mpath[i].toString();
  595. if (p == "package.d") break;
  596. if (i > 0) ret ~= ".";
  597. if (i+1 < mpath.length) ret ~= p;
  598. else ret ~= p.baseName(".d");
  599. }
  600. return ret.data;
  601. }
  602.  
  603. enum UpgradeOptions
  604. {
  605. none = 0,
  606. upgrade = 1<<1, /// Upgrade existing packages
  607. preRelease = 1<<2, /// inclde pre-release versions in upgrade
  608. forceRemove = 1<<3, /// Force removing package folders, which contain unknown files
  609. select = 1<<4, /// Update the dub.selections.json file with the upgraded versions
  610. printUpgradesOnly = 1<<5, /// Instead of downloading new packages, just print a message to notify the user of their existence
  611. }
  612.  
  613. class DependencyVersionResolver : DependencyResolver!(Dependency, Dependency) {
  614. protected {
  615. Dub m_dub;
  616. UpgradeOptions m_options;
  617. Dependency[][string] m_packageVersions;
  618. Package[string] m_remotePackages;
  619. SelectedVersions m_selectedVersions;
  620. Package m_rootPackage;
  621. }
  622.  
  623.  
  624. this(Dub dub, UpgradeOptions options)
  625. {
  626. m_dub = dub;
  627. m_options = options;
  628. }
  629.  
  630. Dependency[string] resolve(Package root, SelectedVersions selected_versions)
  631. {
  632. m_rootPackage = root;
  633. m_selectedVersions = selected_versions;
  634. return super.resolve(TreeNode(root.name, Dependency(root.ver)), (m_options & UpgradeOptions.printUpgradesOnly) == 0);
  635. }
  636.  
  637. protected override Dependency[] getAllConfigs(string pack)
  638. {
  639. if (auto pvers = pack in m_packageVersions)
  640. return *pvers;
  641.  
  642. if (!(m_options & UpgradeOptions.upgrade) && m_selectedVersions.hasSelectedVersion(pack)) {
  643. auto ret = [m_selectedVersions.selectedVersion(pack)];
  644. logDiagnostic("Using fixed selection %s %s", pack, ret[0]);
  645. m_packageVersions[pack] = ret;
  646. return ret;
  647. }
  648.  
  649. logDiagnostic("Search for versions of %s (%s package suppliers)", pack, m_dub.m_packageSuppliers.length);
  650. Version[] versions;
  651. foreach (p; m_dub.packageManager.getPackageIterator(pack))
  652. versions ~= p.ver;
  653.  
  654. foreach (ps; m_dub.m_packageSuppliers) {
  655. try {
  656. auto vers = ps.getVersions(pack).reverse;
  657. if (!vers.length) {
  658. logDiagnostic("No versions for %s for %s", pack, ps.description);
  659. continue;
  660. }
  661.  
  662. versions ~= vers;
  663. break;
  664. } catch (Exception e) {
  665. logDebug("Package %s not found in %s: %s", pack, ps.description, e.msg);
  666. logDebug("Full error: %s", e.toString().sanitize);
  667. }
  668. }
  669.  
  670. // sort by version, descending, and remove duplicates
  671. versions = versions.sort!"a>b".uniq.array;
  672.  
  673. // move pre-release versions to the back of the list if no preRelease flag is given
  674. if (!(m_options & UpgradeOptions.preRelease))
  675. versions = versions.filter!(v => !v.isPreRelease).array ~ versions.filter!(v => v.isPreRelease).array;
  676.  
  677. if (!versions.length) logDiagnostic("Nothing found for %s", pack);
  678.  
  679. auto ret = versions.map!(v => Dependency(v)).array;
  680. m_packageVersions[pack] = ret;
  681. return ret;
  682. }
  683.  
  684. protected override Dependency[] getSpecificConfigs(TreeNodes nodes)
  685. {
  686. if (!nodes.configs.path.empty) return [nodes.configs];
  687. else return null;
  688. }
  689.  
  690.  
  691. protected override TreeNodes[] getChildren(TreeNode node)
  692. {
  693. auto ret = appender!(TreeNodes[]);
  694. auto pack = getPackage(node.pack, node.config);
  695. if (!pack) {
  696. // this can hapen when the package description contains syntax errors
  697. logDebug("Invalid package in dependency tree: %s %s", node.pack, node.config);
  698. return null;
  699. }
  700. foreach (dname, dspec; pack.dependencies) {
  701. auto dbasename = getBasePackageName(dname);
  702. if (dspec.optional && !m_dub.packageManager.getFirstPackage(dname))
  703. continue;
  704. if (m_options & UpgradeOptions.upgrade || !m_selectedVersions || !m_selectedVersions.hasSelectedVersion(dbasename))
  705. ret ~= TreeNodes(dname, dspec.mapToPath(pack.path));
  706. else ret ~= TreeNodes(dname, m_selectedVersions.selectedVersion(dbasename));
  707. }
  708. return ret.data;
  709. }
  710.  
  711. protected override bool matches(Dependency configs, Dependency config)
  712. {
  713. if (!configs.path.empty) return configs.path == config.path;
  714. return configs.merge(config).valid;
  715. }
  716.  
  717. private Package getPackage(string name, Dependency dep)
  718. {
  719. if (!dep.path.empty) {
  720. auto ret = m_dub.packageManager.getOrLoadPackage(dep.path);
  721. if (dep.matches(ret.ver)) return ret;
  722. }
  723.  
  724. if (auto ret = m_dub.m_packageManager.getBestPackage(name, dep))
  725. return ret;
  726.  
  727. auto key = name ~ ":" ~ dep.version_.toString();
  728.  
  729. if (auto ret = key in m_remotePackages)
  730. return *ret;
  731.  
  732. auto prerelease = (m_options & UpgradeOptions.preRelease) != 0;
  733.  
  734. auto rootpack = name.split(":")[0];
  735.  
  736. foreach (ps; m_dub.m_packageSuppliers) {
  737. if (rootpack == name) {
  738. try {
  739. auto desc = ps.getPackageDescription(name, dep, prerelease);
  740. auto ret = new Package(desc);
  741. m_remotePackages[key] = ret;
  742. return ret;
  743. } catch (Exception e) {
  744. logDiagnostic("Metadata for %s could not be downloaded from %s: %s", name, ps.description, e.msg);
  745. logDebug("Full error: %s", e.toString().sanitize);
  746. }
  747. } else {
  748. try {
  749. FetchOptions fetchOpts;
  750. fetchOpts |= prerelease ? FetchOptions.usePrerelease : FetchOptions.none;
  751. fetchOpts |= (m_options & UpgradeOptions.forceRemove) != 0 ? FetchOptions.forceRemove : FetchOptions.none;
  752. m_dub.fetch(rootpack, dep, PlacementLocation.userWide, fetchOpts);
  753. auto ret = m_dub.m_packageManager.getBestPackage(name, dep);
  754. if (!ret) {
  755. logWarn("Package %s %s doesn't have a sub package %s", rootpack, dep.version_, name);
  756. return null;
  757. }
  758. m_remotePackages[key] = ret;
  759. return ret;
  760. } catch (Exception e) {
  761. logDiagnostic("Package %s could not be downloaded from %s: %s", rootpack, ps.description, e.msg);
  762. logDebug("Full error: %s", e.toString().sanitize);
  763. }
  764. }
  765. }
  766.  
  767. m_remotePackages[key] = null;
  768.  
  769. logWarn("Package %s %s was found neither locally, nor in the configured package registries.", name, dep);
  770. return null;
  771. }
  772. }