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