Newer
Older
dub_jkp / source / dub / packagemanager.d
  1. /**
  2. Management of packages on the local computer.
  3.  
  4. Copyright: © 2012-2013 rejectedsoftware e.K.
  5. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
  6. Authors: Sönke Ludwig, Matthias Dondorff
  7. */
  8. module dub.packagemanager;
  9.  
  10. import dub.dependency;
  11. import dub.internal.utils;
  12. import dub.internal.vibecompat.core.file;
  13. import dub.internal.vibecompat.core.log;
  14. import dub.internal.vibecompat.data.json;
  15. import dub.internal.vibecompat.inet.path;
  16. import dub.package_;
  17.  
  18. import std.algorithm : countUntil, filter, sort, canFind, remove;
  19. import std.array;
  20. import std.conv;
  21. import std.digest.sha;
  22. import std.encoding : sanitize;
  23. import std.exception;
  24. import std.file;
  25. import std.string;
  26. import std.zip;
  27.  
  28.  
  29. /// The PackageManager can retrieve present packages and get / remove
  30. /// packages.
  31. class PackageManager {
  32. private {
  33. Repository[LocalPackageType] m_repositories;
  34. Path[] m_searchPath;
  35. Package[] m_packages;
  36. Package[] m_temporaryPackages;
  37. bool m_disableDefaultSearchPaths = false;
  38. }
  39.  
  40. this(Path user_path, Path system_path, bool refresh_packages = true)
  41. {
  42. m_repositories[LocalPackageType.user] = Repository(user_path);
  43. m_repositories[LocalPackageType.system] = Repository(system_path);
  44. if (refresh_packages) refresh(true);
  45. }
  46.  
  47. @property void searchPath(Path[] paths)
  48. {
  49. if (paths == m_searchPath) return;
  50. m_searchPath = paths.dup;
  51. refresh(false);
  52. }
  53. @property const(Path)[] searchPath() const { return m_searchPath; }
  54.  
  55. @property void disableDefaultSearchPaths(bool val)
  56. {
  57. if (val == m_disableDefaultSearchPaths) return;
  58. m_disableDefaultSearchPaths = val;
  59. refresh(true);
  60. }
  61.  
  62. @property const(Path)[] completeSearchPath()
  63. const {
  64. auto ret = appender!(Path[])();
  65. ret.put(m_searchPath);
  66. if (!m_disableDefaultSearchPaths) {
  67. ret.put(m_repositories[LocalPackageType.user].searchPath);
  68. ret.put(m_repositories[LocalPackageType.user].packagePath);
  69. ret.put(m_repositories[LocalPackageType.system].searchPath);
  70. ret.put(m_repositories[LocalPackageType.system].packagePath);
  71. }
  72. return ret.data;
  73. }
  74.  
  75.  
  76. /** Looks up a specific package.
  77.  
  78. Looks up a package matching the given version/path in the set of
  79. registered packages. The lookup order is done according the the
  80. usual rules (see getPackageIterator).
  81.  
  82. Params:
  83. name = The name of the package
  84. ver = The exact version of the package to query
  85. path = An exact path that the package must reside in. Note that
  86. the package must still be registered in the package manager.
  87. enable_overrides = Apply the local package override list before
  88. returning a package (enabled by default)
  89.  
  90. Returns:
  91. The matching package or null if no match was found.
  92. */
  93. Package getPackage(string name, Version ver, bool enable_overrides = true)
  94. {
  95. if (enable_overrides) {
  96. foreach (tp; [LocalPackageType.user, LocalPackageType.system])
  97. foreach (ovr; m_repositories[tp].overrides)
  98. if (ovr.package_ == name && ovr.version_.matches(ver)) {
  99. Package pack;
  100. if (!ovr.targetPath.empty) pack = getPackage(name, ovr.targetPath);
  101. else pack = getPackage(name, ovr.targetVersion, false);
  102. if (pack) return pack;
  103.  
  104. logWarn("Package override %s %s -> %s %s doesn't reference an existing package.",
  105. ovr.package_, ovr.version_, ovr.targetVersion, ovr.targetPath);
  106. }
  107. }
  108.  
  109. foreach (p; getPackageIterator(name))
  110. if (p.ver == ver)
  111. return p;
  112.  
  113. return null;
  114. }
  115.  
  116. /// ditto
  117. Package getPackage(string name, string ver, bool enable_overrides = true)
  118. {
  119. return getPackage(name, Version(ver), enable_overrides);
  120. }
  121.  
  122. /// ditto
  123. Package getPackage(string name, Version ver, Path path)
  124. {
  125. auto ret = getPackage(name, path);
  126. if (!ret || ret.ver != ver) return null;
  127. return ret;
  128. }
  129.  
  130. /// ditto
  131. Package getPackage(string name, string ver, Path path)
  132. {
  133. return getPackage(name, Version(ver), path);
  134. }
  135.  
  136. /// ditto
  137. Package getPackage(string name, Path path)
  138. {
  139. foreach( p; getPackageIterator(name) )
  140. if (p.path.startsWith(path))
  141. return p;
  142. return null;
  143. }
  144.  
  145.  
  146. /** Looks up the first package matching the given name.
  147. */
  148. Package getFirstPackage(string name)
  149. {
  150. foreach (ep; getPackageIterator(name))
  151. return ep;
  152. return null;
  153. }
  154.  
  155. Package getOrLoadPackage(Path path, PathAndFormat infoFile = PathAndFormat(), bool allow_sub_packages = false)
  156. {
  157. path.endsWithSlash = true;
  158. foreach (p; getPackageIterator())
  159. if (p.path == path && (!p.parentPackage || (allow_sub_packages && p.parentPackage.path != p.path)))
  160. return p;
  161. auto pack = new Package(path, infoFile);
  162. addPackages(m_temporaryPackages, pack);
  163. return pack;
  164. }
  165.  
  166.  
  167. /** Searches for the latest version of a package matching the given dependency.
  168. */
  169. Package getBestPackage(string name, Dependency version_spec, bool enable_overrides = true)
  170. {
  171. Package ret;
  172. foreach (p; getPackageIterator(name))
  173. if (version_spec.matches(p.ver) && (!ret || p.ver > ret.ver))
  174. ret = p;
  175.  
  176. if (enable_overrides && ret) {
  177. if (auto ovr = getPackage(name, ret.ver))
  178. return ovr;
  179. }
  180. return ret;
  181. }
  182.  
  183. /// ditto
  184. Package getBestPackage(string name, string version_spec)
  185. {
  186. return getBestPackage(name, Dependency(version_spec));
  187. }
  188.  
  189. Package getSubPackage(Package base_package, string sub_name, bool silent_fail)
  190. {
  191. foreach (p; getPackageIterator(base_package.name~":"~sub_name))
  192. if (p.parentPackage is base_package)
  193. return p;
  194. enforce(silent_fail, "Sub package "~base_package.name~":"~sub_name~" doesn't exist.");
  195. return null;
  196. }
  197.  
  198.  
  199. /** Determines if a package is managed by DUB.
  200.  
  201. Managed packages can be upgraded and removed.
  202. */
  203. bool isManagedPackage(Package pack)
  204. const {
  205. auto ppath = pack.basePackage.path;
  206. return isManagedPath(ppath);
  207. }
  208.  
  209. /** Determines if a specifc path is within a DUB managed package folder.
  210.  
  211. By default, managed folders are "~/.dub/packages" and
  212. "/var/lib/dub/packages".
  213. */
  214. bool isManagedPath(Path path)
  215. const {
  216. foreach (rep; m_repositories) {
  217. auto rpath = rep.packagePath;
  218. if (path.startsWith(rpath))
  219. return true;
  220. }
  221. return false;
  222. }
  223.  
  224. int delegate(int delegate(ref Package)) getPackageIterator()
  225. {
  226. int iterator(int delegate(ref Package) del)
  227. {
  228. foreach (tp; m_temporaryPackages)
  229. if (auto ret = del(tp)) return ret;
  230.  
  231. // first search local packages
  232. foreach (tp; LocalPackageType.min .. LocalPackageType.max+1)
  233. foreach (p; m_repositories[cast(LocalPackageType)tp].localPackages)
  234. if (auto ret = del(p)) return ret;
  235.  
  236. // and then all packages gathered from the search path
  237. foreach( p; m_packages )
  238. if( auto ret = del(p) )
  239. return ret;
  240. return 0;
  241. }
  242.  
  243. return &iterator;
  244. }
  245.  
  246. int delegate(int delegate(ref Package)) getPackageIterator(string name)
  247. {
  248. int iterator(int delegate(ref Package) del)
  249. {
  250. foreach (p; getPackageIterator())
  251. if (p.name == name)
  252. if (auto ret = del(p)) return ret;
  253. return 0;
  254. }
  255.  
  256. return &iterator;
  257. }
  258.  
  259.  
  260. /** Returns a list of all package overrides for the given scope.
  261. */
  262. const(PackageOverride)[] getOverrides(LocalPackageType scope_)
  263. const {
  264. return m_repositories[scope_].overrides;
  265. }
  266.  
  267. /** Adds a new override for the given package.
  268. */
  269. void addOverride(LocalPackageType scope_, string package_, Dependency version_spec, Version target)
  270. {
  271. m_repositories[scope_].overrides ~= PackageOverride(package_, version_spec, target);
  272. writeLocalPackageOverridesFile(scope_);
  273. }
  274. /// ditto
  275. void addOverride(LocalPackageType scope_, string package_, Dependency version_spec, Path target)
  276. {
  277. m_repositories[scope_].overrides ~= PackageOverride(package_, version_spec, target);
  278. writeLocalPackageOverridesFile(scope_);
  279. }
  280.  
  281. /** Removes an existing package override.
  282. */
  283. void removeOverride(LocalPackageType scope_, string package_, Dependency version_spec)
  284. {
  285. Repository* rep = &m_repositories[scope_];
  286. foreach (i, ovr; rep.overrides) {
  287. if (ovr.package_ != package_ || ovr.version_ != version_spec)
  288. continue;
  289. rep.overrides = rep.overrides[0 .. i] ~ rep.overrides[i+1 .. $];
  290. writeLocalPackageOverridesFile(scope_);
  291. return;
  292. }
  293. throw new Exception(format("No override exists for %s %s", package_, version_spec));
  294. }
  295.  
  296. /// Extracts the package supplied as a path to it's zip file to the
  297. /// destination and sets a version field in the package description.
  298. Package storeFetchedPackage(Path zip_file_path, Json package_info, Path destination)
  299. {
  300. auto package_name = package_info.name.get!string;
  301. auto package_version = package_info["version"].get!string;
  302. auto clean_package_version = package_version[package_version.startsWith("~") ? 1 : 0 .. $];
  303.  
  304. logDiagnostic("Placing package '%s' version '%s' to location '%s' from file '%s'",
  305. package_name, package_version, destination.toNativeString(), zip_file_path.toNativeString());
  306.  
  307. if( existsFile(destination) ){
  308. throw new Exception(format("%s (%s) needs to be removed from '%s' prior placement.", package_name, package_version, destination));
  309. }
  310.  
  311. // open zip file
  312. ZipArchive archive;
  313. {
  314. logDebug("Opening file %s", zip_file_path);
  315. auto f = openFile(zip_file_path, FileMode.read);
  316. scope(exit) f.close();
  317. archive = new ZipArchive(f.readAll());
  318. }
  319.  
  320. logDebug("Extracting from zip.");
  321.  
  322. // In a github zip, the actual contents are in a subfolder
  323. Path zip_prefix;
  324. outer: foreach(ArchiveMember am; archive.directory) {
  325. auto path = Path(am.name);
  326. foreach (fil; packageInfoFiles)
  327. if (path.length == 2 && path.head.toString == fil.filename) {
  328. zip_prefix = path[0 .. $-1];
  329. break outer;
  330. }
  331. }
  332.  
  333. logDebug("zip root folder: %s", zip_prefix);
  334.  
  335. Path getCleanedPath(string fileName) {
  336. auto path = Path(fileName);
  337. if(zip_prefix != Path() && !path.startsWith(zip_prefix)) return Path();
  338. return path[zip_prefix.length..path.length];
  339. }
  340.  
  341. // extract & place
  342. mkdirRecurse(destination.toNativeString());
  343. logDiagnostic("Copying all files...");
  344. int countFiles = 0;
  345. foreach(ArchiveMember a; archive.directory) {
  346. auto cleanedPath = getCleanedPath(a.name);
  347. if(cleanedPath.empty) continue;
  348. auto dst_path = destination~cleanedPath;
  349.  
  350. logDebug("Creating %s", cleanedPath);
  351. if( dst_path.endsWithSlash ){
  352. if( !existsDirectory(dst_path) )
  353. mkdirRecurse(dst_path.toNativeString());
  354. } else {
  355. if( !existsDirectory(dst_path.parentPath) )
  356. mkdirRecurse(dst_path.parentPath.toNativeString());
  357. auto dstFile = openFile(dst_path, FileMode.createTrunc);
  358. scope(exit) dstFile.close();
  359. dstFile.put(archive.expand(a));
  360. ++countFiles;
  361. }
  362. }
  363. logDiagnostic("%s file(s) copied.", to!string(countFiles));
  364.  
  365. // overwrite dub.json (this one includes a version field)
  366. auto pack = new Package(destination, PathAndFormat(), null, package_info["version"].get!string);
  367.  
  368. if (pack.packageInfoFilename.head != defaultPackageFilename)
  369. // Storeinfo saved a default file, this could be different to the file from the zip.
  370. removeFile(pack.packageInfoFilename);
  371. pack.storeInfo();
  372. addPackages(m_packages, pack);
  373. return pack;
  374. }
  375.  
  376. /// Removes the given the package.
  377. void remove(in Package pack, bool force_remove)
  378. {
  379. logDebug("Remove %s, version %s, path '%s'", pack.name, pack.vers, pack.path);
  380. enforce(!pack.path.empty, "Cannot remove package "~pack.name~" without a path.");
  381.  
  382. // remove package from repositories' list
  383. bool found = false;
  384. bool removeFrom(Package[] packs, in Package pack) {
  385. auto packPos = countUntil!("a.path == b.path")(packs, pack);
  386. if(packPos != -1) {
  387. packs = .remove(packs, packPos);
  388. return true;
  389. }
  390. return false;
  391. }
  392. foreach(repo; m_repositories) {
  393. if(removeFrom(repo.localPackages, pack)) {
  394. found = true;
  395. break;
  396. }
  397. }
  398. if(!found)
  399. found = removeFrom(m_packages, pack);
  400. enforce(found, "Cannot remove, package not found: '"~ pack.name ~"', path: " ~ to!string(pack.path));
  401.  
  402. logDebug("About to delete root folder for package '%s'.", pack.path);
  403. rmdirRecurse(pack.path.toNativeString());
  404. logInfo("Removed package: '"~pack.name~"'");
  405. }
  406.  
  407. Package addLocalPackage(Path path, string verName, LocalPackageType type)
  408. {
  409. path.endsWithSlash = true;
  410. auto pack = new Package(path);
  411. enforce(pack.name.length, "The package has no name, defined in: " ~ path.toString());
  412. if (verName.length)
  413. pack.ver = Version(verName);
  414.  
  415. // don't double-add packages
  416. Package[]* packs = &m_repositories[type].localPackages;
  417. foreach (p; *packs) {
  418. if (p.path == path) {
  419. enforce(p.ver == pack.ver, "Adding the same local package twice with differing versions is not allowed.");
  420. logInfo("Package is already registered: %s (version: %s)", p.name, p.ver);
  421. return p;
  422. }
  423. }
  424.  
  425. addPackages(*packs, pack);
  426.  
  427. writeLocalPackageList(type);
  428.  
  429. logInfo("Registered package: %s (version: %s)", pack.name, pack.ver);
  430. return pack;
  431. }
  432.  
  433. void removeLocalPackage(Path path, LocalPackageType type)
  434. {
  435. path.endsWithSlash = true;
  436.  
  437. Package[]* packs = &m_repositories[type].localPackages;
  438. size_t[] to_remove;
  439. foreach( i, entry; *packs )
  440. if( entry.path == path )
  441. to_remove ~= i;
  442. enforce(to_remove.length > 0, "No "~type.to!string()~" package found at "~path.toNativeString());
  443.  
  444. string[Version] removed;
  445. foreach_reverse( i; to_remove ) {
  446. removed[(*packs)[i].ver] = (*packs)[i].name;
  447. *packs = (*packs)[0 .. i] ~ (*packs)[i+1 .. $];
  448. }
  449.  
  450. writeLocalPackageList(type);
  451.  
  452. foreach(ver, name; removed)
  453. logInfo("Deregistered package: %s (version: %s)", name, ver);
  454. }
  455.  
  456. /// For the given type add another path where packages will be looked up.
  457. void addSearchPath(Path path, LocalPackageType type)
  458. {
  459. m_repositories[type].searchPath ~= path;
  460. writeLocalPackageList(type);
  461. }
  462.  
  463. /// Removes a search path from the given type.
  464. void removeSearchPath(Path path, LocalPackageType type)
  465. {
  466. m_repositories[type].searchPath = m_repositories[type].searchPath.filter!(p => p != path)().array();
  467. writeLocalPackageList(type);
  468. }
  469.  
  470. void refresh(bool refresh_existing_packages)
  471. {
  472. logDiagnostic("Refreshing local packages (refresh existing: %s)...", refresh_existing_packages);
  473.  
  474. // load locally defined packages
  475. void scanLocalPackages(LocalPackageType type)
  476. {
  477. Path list_path = m_repositories[type].packagePath;
  478. Package[] packs;
  479. Path[] paths;
  480. if (!m_disableDefaultSearchPaths) try {
  481. auto local_package_file = list_path ~ LocalPackagesFilename;
  482. logDiagnostic("Looking for local package map at %s", local_package_file.toNativeString());
  483. if( !existsFile(local_package_file) ) return;
  484. logDiagnostic("Try to load local package map at %s", local_package_file.toNativeString());
  485. auto packlist = jsonFromFile(list_path ~ LocalPackagesFilename);
  486. enforce(packlist.type == Json.Type.array, LocalPackagesFilename~" must contain an array.");
  487. foreach( pentry; packlist ){
  488. try {
  489. auto name = pentry.name.get!string;
  490. auto path = Path(pentry.path.get!string);
  491. if (name == "*") {
  492. paths ~= path;
  493. } else {
  494. auto ver = Version(pentry["version"].get!string);
  495.  
  496. Package pp;
  497. if (!refresh_existing_packages) {
  498. foreach (p; m_repositories[type].localPackages)
  499. if (p.path == path) {
  500. pp = p;
  501. break;
  502. }
  503. }
  504.  
  505. if (!pp) {
  506. auto infoFile = Package.findPackageFile(path);
  507. if (!infoFile.empty) pp = new Package(path, infoFile);
  508. else {
  509. logWarn("Locally registered package %s %s was not found. Please run \"dub remove-local %s\".",
  510. name, ver, path.toNativeString());
  511. auto info = Json.emptyObject;
  512. info.name = name;
  513. pp = new Package(info, path);
  514. }
  515. }
  516.  
  517. if (pp.name != name)
  518. logWarn("Local package at %s has different name than %s (%s)", path.toNativeString(), name, pp.name);
  519. pp.ver = ver;
  520.  
  521. addPackages(packs, pp);
  522. }
  523. } catch( Exception e ){
  524. logWarn("Error adding local package: %s", e.msg);
  525. }
  526. }
  527. } catch( Exception e ){
  528. logDiagnostic("Loading of local package list at %s failed: %s", list_path.toNativeString(), e.msg);
  529. }
  530. m_repositories[type].localPackages = packs;
  531. m_repositories[type].searchPath = paths;
  532. }
  533. scanLocalPackages(LocalPackageType.system);
  534. scanLocalPackages(LocalPackageType.user);
  535.  
  536. auto old_packages = m_packages;
  537.  
  538. // rescan the system and user package folder
  539. void scanPackageFolder(Path path)
  540. {
  541. if( path.existsDirectory() ){
  542. logDebug("iterating dir %s", path.toNativeString());
  543. try foreach( pdir; iterateDirectory(path) ){
  544. logDebug("iterating dir %s entry %s", path.toNativeString(), pdir.name);
  545. if (!pdir.isDirectory) continue;
  546.  
  547. auto pack_path = path ~ (pdir.name ~ "/");
  548.  
  549. if (isManagedPath(path)) {
  550. // Search for a single directory within this directory which happen to be a prefix of pdir
  551. // This is to support new folder structure installed over the ancient one.
  552. foreach (subdir; iterateDirectory(path ~ (pdir.name ~ "/")))
  553. if (subdir.isDirectory && pdir.name.startsWith(subdir.name)) {// eg: package vibe-d will be in "vibe-d-x.y.z/vibe-d"
  554. pack_path ~= subdir.name ~ "/";
  555. break;
  556. }
  557. }
  558.  
  559. auto packageFile = Package.findPackageFile(pack_path);
  560. if (packageFile.empty) continue;
  561. Package p;
  562. try {
  563. if (!refresh_existing_packages)
  564. foreach (pp; old_packages)
  565. if (pp.path == pack_path) {
  566. p = pp;
  567. break;
  568. }
  569. if (!p) p = new Package(pack_path, packageFile);
  570. addPackages(m_packages, p);
  571. } catch( Exception e ){
  572. logError("Failed to load package in %s: %s", pack_path, e.msg);
  573. logDiagnostic("Full error: %s", e.toString().sanitize());
  574. }
  575. }
  576. catch(Exception e) logDiagnostic("Failed to enumerate %s packages: %s", path.toNativeString(), e.toString());
  577. }
  578. }
  579.  
  580. m_packages = null;
  581. foreach (p; this.completeSearchPath)
  582. scanPackageFolder(p);
  583.  
  584. void loadOverrides(LocalPackageType type)
  585. {
  586. m_repositories[type].overrides = null;
  587. auto ovrfilepath = m_repositories[type].packagePath ~ LocalOverridesFilename;
  588. if (existsFile(ovrfilepath)) {
  589. foreach (entry; jsonFromFile(ovrfilepath)) {
  590. PackageOverride ovr;
  591. ovr.package_ = entry.name.get!string;
  592. ovr.version_ = Dependency(entry["version"].get!string);
  593. if (auto pv = "targetVersion" in entry) ovr.targetVersion = Version(pv.get!string);
  594. if (auto pv = "targetPath" in entry) ovr.targetPath = Path(pv.get!string);
  595. m_repositories[type].overrides ~= ovr;
  596. }
  597. }
  598. }
  599. loadOverrides(LocalPackageType.user);
  600. loadOverrides(LocalPackageType.system);
  601. }
  602.  
  603. alias Hash = ubyte[];
  604. /// Generates a hash value for a given package.
  605. /// Some files or folders are ignored during the generation (like .dub and
  606. /// .svn folders)
  607. Hash hashPackage(Package pack)
  608. {
  609. string[] ignored_directories = [".git", ".dub", ".svn"];
  610. // something from .dub_ignore or what?
  611. string[] ignored_files = [];
  612. SHA1 sha1;
  613. foreach(file; dirEntries(pack.path.toNativeString(), SpanMode.depth)) {
  614. if(file.isDir && ignored_directories.canFind(Path(file.name).head.toString()))
  615. continue;
  616. else if(ignored_files.canFind(Path(file.name).head.toString()))
  617. continue;
  618.  
  619. sha1.put(cast(ubyte[])Path(file.name).head.toString());
  620. if(file.isDir) {
  621. logDebug("Hashed directory name %s", Path(file.name).head);
  622. }
  623. else {
  624. sha1.put(openFile(Path(file.name)).readAll());
  625. logDebug("Hashed file contents from %s", Path(file.name).head);
  626. }
  627. }
  628. auto hash = sha1.finish();
  629. logDebug("Project hash: %s", hash);
  630. return hash[].dup;
  631. }
  632.  
  633. private void writeLocalPackageList(LocalPackageType type)
  634. {
  635. Json[] newlist;
  636. foreach (p; m_repositories[type].searchPath) {
  637. auto entry = Json.emptyObject;
  638. entry.name = "*";
  639. entry.path = p.toNativeString();
  640. newlist ~= entry;
  641. }
  642.  
  643. foreach (p; m_repositories[type].localPackages) {
  644. if (p.parentPackage) continue; // do not store sub packages
  645. auto entry = Json.emptyObject;
  646. entry["name"] = p.name;
  647. entry["version"] = p.ver.toString();
  648. entry["path"] = p.path.toNativeString();
  649. newlist ~= entry;
  650. }
  651.  
  652. Path path = m_repositories[type].packagePath;
  653. if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString());
  654. writeJsonFile(path ~ LocalPackagesFilename, Json(newlist));
  655. }
  656.  
  657. private void writeLocalPackageOverridesFile(LocalPackageType type)
  658. {
  659. Json[] newlist;
  660. foreach (ovr; m_repositories[type].overrides) {
  661. auto jovr = Json.emptyObject;
  662. jovr.name = ovr.package_;
  663. jovr["version"] = ovr.version_.versionString;
  664. if (!ovr.targetPath.empty) jovr.targetPath = ovr.targetPath.toNativeString();
  665. else jovr.targetVersion = ovr.targetVersion.toString();
  666. newlist ~= jovr;
  667. }
  668. auto path = m_repositories[type].packagePath;
  669. if (!existsDirectory(path)) mkdirRecurse(path.toNativeString());
  670. writeJsonFile(path ~ LocalOverridesFilename, Json(newlist));
  671. }
  672.  
  673. /// Adds the package and scans for subpackages.
  674. private void addPackages(ref Package[] dst_repos, Package pack)
  675. const {
  676. // Add the main package.
  677. dst_repos ~= pack;
  678.  
  679. // Additionally to the internally defined subpackages, whose metadata
  680. // is loaded with the main dub.json, load all externally defined
  681. // packages after the package is available with all the data.
  682. foreach (spr; pack.subPackages) {
  683. Package sp;
  684.  
  685. if (spr.path.length) {
  686. auto p = Path(spr.path);
  687. p.normalize();
  688. enforce(!p.absolute, "Sub package paths must be sub paths of the parent package.");
  689. auto path = pack.path ~ p;
  690. if (!existsFile(path)) {
  691. logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString());
  692. continue;
  693. }
  694. sp = new Package(path, PathAndFormat(), pack);
  695. } else sp = new Package(spr.recipe, pack.path, pack);
  696.  
  697. // Add the subpackage.
  698. try {
  699. dst_repos ~= sp;
  700. } catch (Exception e) {
  701. logError("Package '%s': Failed to load sub-package %s: %s", pack.name,
  702. spr.path.length ? spr.path : spr.recipe.name, e.msg);
  703. logDiagnostic("Full error: %s", e.toString().sanitize());
  704. }
  705. }
  706. }
  707. }
  708.  
  709. struct PackageOverride {
  710. string package_;
  711. Dependency version_;
  712. Version targetVersion;
  713. Path targetPath;
  714.  
  715. this(string package_, Dependency version_, Version target_version)
  716. {
  717. this.package_ = package_;
  718. this.version_ = version_;
  719. this.targetVersion = target_version;
  720. }
  721.  
  722. this(string package_, Dependency version_, Path target_path)
  723. {
  724. this.package_ = package_;
  725. this.version_ = version_;
  726. this.targetPath = target_path;
  727. }
  728. }
  729.  
  730. enum LocalPackageType {
  731. user,
  732. system
  733. }
  734.  
  735. enum LocalPackagesFilename = "local-packages.json";
  736. enum LocalOverridesFilename = "local-overrides.json";
  737.  
  738.  
  739. private struct Repository {
  740. Path path;
  741. Path packagePath;
  742. Path[] searchPath;
  743. Package[] localPackages;
  744. PackageOverride[] overrides;
  745.  
  746. this(Path path)
  747. {
  748. this.path = path;
  749. this.packagePath = path ~"packages/";
  750. }
  751. }