Newer
Older
dub_jkp / source / dub / packagemanager.d
@Sönke Ludwig Sönke Ludwig on 4 Dec 2017 24 KB Rename Path to NativePath.
  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. NativePath[] m_searchPath;
  35. Package[] m_packages;
  36. Package[] m_temporaryPackages;
  37. bool m_disableDefaultSearchPaths = false;
  38. }
  39.  
  40. this(NativePath user_path, NativePath 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(NativePath[] paths)
  50. {
  51. if (paths == m_searchPath) return;
  52. m_searchPath = paths.dup;
  53. refresh(false);
  54. }
  55. /// ditto
  56. @property const(NativePath)[] 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(NativePath)[] completeSearchPath()
  70. const {
  71. auto ret = appender!(NativePath[])();
  72. ret.put(cast(NativePath[])m_searchPath); // work around Phobos 17251
  73. if (!m_disableDefaultSearchPaths) {
  74. ret.put(cast(NativePath[])m_repositories[LocalPackageType.user].searchPath);
  75. ret.put(cast(NativePath)m_repositories[LocalPackageType.user].packagePath);
  76. ret.put(cast(NativePath[])m_repositories[LocalPackageType.system].searchPath);
  77. ret.put(cast(NativePath)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 = getOrLoadPackage(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, NativePath 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, NativePath path)
  139. {
  140. return getPackage(name, Version(ver), path);
  141. }
  142.  
  143. /// ditto
  144. Package getPackage(string name, NativePath 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 = NativePath 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(NativePath path, NativePath recipe_path = NativePath.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 specific 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(NativePath path)
  248. const {
  249. foreach (rep; m_repositories) {
  250. NativePath 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, NativePath 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(NativePath zip_file_path, Json package_info, NativePath destination)
  340. {
  341. import std.range : walkLength;
  342.  
  343. auto package_name = package_info["name"].get!string;
  344. auto package_version = package_info["version"].get!string;
  345. auto clean_package_version = package_version[package_version.startsWith("~") ? 1 : 0 .. $];
  346.  
  347. logDebug("Placing package '%s' version '%s' to location '%s' from file '%s'",
  348. package_name, package_version, destination.toNativeString(), zip_file_path.toNativeString());
  349.  
  350. if( existsFile(destination) ){
  351. throw new Exception(format("%s (%s) needs to be removed from '%s' prior placement.", package_name, package_version, destination));
  352. }
  353.  
  354. // open zip file
  355. ZipArchive archive;
  356. {
  357. logDebug("Opening file %s", zip_file_path);
  358. auto f = openFile(zip_file_path, FileMode.read);
  359. scope(exit) f.close();
  360. archive = new ZipArchive(f.readAll());
  361. }
  362.  
  363. logDebug("Extracting from zip.");
  364.  
  365. // In a github zip, the actual contents are in a subfolder
  366. alias PSegment = typeof(NativePath.init.head);
  367. PSegment[] zip_prefix;
  368. outer: foreach(ArchiveMember am; archive.directory) {
  369. auto path = NativePath(am.name).bySegment.array;
  370. foreach (fil; packageInfoFiles)
  371. if (path.length == 2 && path[$-1].toString == fil.filename) {
  372. zip_prefix = path[0 .. $-1];
  373. break outer;
  374. }
  375. }
  376.  
  377. logDebug("zip root folder: %s", zip_prefix);
  378.  
  379. NativePath getCleanedPath(string fileName) {
  380. auto path = NativePath(fileName);
  381. if (zip_prefix.length && !path.bySegment.startsWith(zip_prefix)) return NativePath.init;
  382. static if (is(typeof(path[0 .. 1]))) return path[zip_prefix.length .. $];
  383. else return NativePath(path.bySegment.array[zip_prefix.length .. $]);
  384. }
  385.  
  386. static void setAttributes(string path, ArchiveMember am)
  387. {
  388. import std.datetime : DosFileTimeToSysTime;
  389.  
  390. auto mtime = DosFileTimeToSysTime(am.time);
  391. setTimes(path, mtime, mtime);
  392. if (auto attrs = am.fileAttributes)
  393. std.file.setAttributes(path, attrs);
  394. }
  395.  
  396. // extract & place
  397. mkdirRecurse(destination.toNativeString());
  398. logDebug("Copying all files...");
  399. int countFiles = 0;
  400. foreach(ArchiveMember a; archive.directory) {
  401. auto cleanedPath = getCleanedPath(a.name);
  402. if(cleanedPath.empty) continue;
  403. auto dst_path = destination ~ cleanedPath;
  404.  
  405. logDebug("Creating %s", cleanedPath);
  406. if( dst_path.endsWithSlash ){
  407. if( !existsDirectory(dst_path) )
  408. mkdirRecurse(dst_path.toNativeString());
  409. } else {
  410. if( !existsDirectory(dst_path.parentPath) )
  411. mkdirRecurse(dst_path.parentPath.toNativeString());
  412. {
  413. auto dstFile = openFile(dst_path, FileMode.createTrunc);
  414. scope(exit) dstFile.close();
  415. dstFile.put(archive.expand(a));
  416. }
  417. setAttributes(dst_path.toNativeString(), a);
  418. ++countFiles;
  419. }
  420. }
  421. logDebug("%s file(s) copied.", to!string(countFiles));
  422.  
  423. // overwrite dub.json (this one includes a version field)
  424. auto pack = Package.load(destination, NativePath.init, null, package_info["version"].get!string);
  425.  
  426. if (pack.recipePath.head != defaultPackageFilename)
  427. // Storeinfo saved a default file, this could be different to the file from the zip.
  428. removeFile(pack.recipePath);
  429. pack.storeInfo();
  430. addPackages(m_packages, pack);
  431. return pack;
  432. }
  433.  
  434. /// Removes the given the package.
  435. void remove(in Package pack)
  436. {
  437. logDebug("Remove %s, version %s, path '%s'", pack.name, pack.version_, pack.path);
  438. enforce(!pack.path.empty, "Cannot remove package "~pack.name~" without a path.");
  439.  
  440. // remove package from repositories' list
  441. bool found = false;
  442. bool removeFrom(Package[] packs, in Package pack) {
  443. auto packPos = countUntil!("a.path == b.path")(packs, pack);
  444. if(packPos != -1) {
  445. packs = .remove(packs, packPos);
  446. return true;
  447. }
  448. return false;
  449. }
  450. foreach(repo; m_repositories) {
  451. if(removeFrom(repo.localPackages, pack)) {
  452. found = true;
  453. break;
  454. }
  455. }
  456. if(!found)
  457. found = removeFrom(m_packages, pack);
  458. enforce(found, "Cannot remove, package not found: '"~ pack.name ~"', path: " ~ to!string(pack.path));
  459.  
  460. logDebug("About to delete root folder for package '%s'.", pack.path);
  461. rmdirRecurse(pack.path.toNativeString());
  462. logInfo("Removed package: '"~pack.name~"'");
  463. }
  464.  
  465. /// Compatibility overload. Use the version without a `force_remove` argument instead.
  466. void remove(in Package pack, bool force_remove)
  467. {
  468. remove(pack);
  469. }
  470.  
  471. Package addLocalPackage(NativePath path, string verName, LocalPackageType type)
  472. {
  473. path.endsWithSlash = true;
  474. auto pack = Package.load(path);
  475. enforce(pack.name.length, "The package has no name, defined in: " ~ path.toString());
  476. if (verName.length)
  477. pack.version_ = Version(verName);
  478.  
  479. // don't double-add packages
  480. Package[]* packs = &m_repositories[type].localPackages;
  481. foreach (p; *packs) {
  482. if (p.path == path) {
  483. enforce(p.version_ == pack.version_, "Adding the same local package twice with differing versions is not allowed.");
  484. logInfo("Package is already registered: %s (version: %s)", p.name, p.version_);
  485. return p;
  486. }
  487. }
  488.  
  489. addPackages(*packs, pack);
  490.  
  491. writeLocalPackageList(type);
  492.  
  493. logInfo("Registered package: %s (version: %s)", pack.name, pack.version_);
  494. return pack;
  495. }
  496.  
  497. void removeLocalPackage(NativePath path, LocalPackageType type)
  498. {
  499. path.endsWithSlash = true;
  500.  
  501. Package[]* packs = &m_repositories[type].localPackages;
  502. size_t[] to_remove;
  503. foreach( i, entry; *packs )
  504. if( entry.path == path )
  505. to_remove ~= i;
  506. enforce(to_remove.length > 0, "No "~type.to!string()~" package found at "~path.toNativeString());
  507.  
  508. string[Version] removed;
  509. foreach_reverse( i; to_remove ) {
  510. removed[(*packs)[i].version_] = (*packs)[i].name;
  511. *packs = (*packs)[0 .. i] ~ (*packs)[i+1 .. $];
  512. }
  513.  
  514. writeLocalPackageList(type);
  515.  
  516. foreach(ver, name; removed)
  517. logInfo("Deregistered package: %s (version: %s)", name, ver);
  518. }
  519.  
  520. /// For the given type add another path where packages will be looked up.
  521. void addSearchPath(NativePath path, LocalPackageType type)
  522. {
  523. m_repositories[type].searchPath ~= path;
  524. writeLocalPackageList(type);
  525. }
  526.  
  527. /// Removes a search path from the given type.
  528. void removeSearchPath(NativePath path, LocalPackageType type)
  529. {
  530. m_repositories[type].searchPath = m_repositories[type].searchPath.filter!(p => p != path)().array();
  531. writeLocalPackageList(type);
  532. }
  533.  
  534. void refresh(bool refresh_existing_packages)
  535. {
  536. logDiagnostic("Refreshing local packages (refresh existing: %s)...", refresh_existing_packages);
  537.  
  538. // load locally defined packages
  539. void scanLocalPackages(LocalPackageType type)
  540. {
  541. NativePath list_path = m_repositories[type].packagePath;
  542. Package[] packs;
  543. NativePath[] paths;
  544. if (!m_disableDefaultSearchPaths) try {
  545. auto local_package_file = list_path ~ LocalPackagesFilename;
  546. logDiagnostic("Looking for local package map at %s", local_package_file.toNativeString());
  547. if( !existsFile(local_package_file) ) return;
  548. logDiagnostic("Try to load local package map at %s", local_package_file.toNativeString());
  549. auto packlist = jsonFromFile(list_path ~ LocalPackagesFilename);
  550. enforce(packlist.type == Json.Type.array, LocalPackagesFilename~" must contain an array.");
  551. foreach( pentry; packlist ){
  552. try {
  553. auto name = pentry["name"].get!string;
  554. auto path = NativePath(pentry["path"].get!string);
  555. if (name == "*") {
  556. paths ~= path;
  557. } else {
  558. auto ver = Version(pentry["version"].get!string);
  559.  
  560. Package pp;
  561. if (!refresh_existing_packages) {
  562. foreach (p; m_repositories[type].localPackages)
  563. if (p.path == path) {
  564. pp = p;
  565. break;
  566. }
  567. }
  568.  
  569. if (!pp) {
  570. auto infoFile = Package.findPackageFile(path);
  571. if (!infoFile.empty) pp = Package.load(path, infoFile);
  572. else {
  573. logWarn("Locally registered package %s %s was not found. Please run 'dub remove-local \"%s\"'.",
  574. name, ver, path.toNativeString());
  575. auto info = Json.emptyObject;
  576. info["name"] = name;
  577. pp = new Package(info, path);
  578. }
  579. }
  580.  
  581. if (pp.name != name)
  582. logWarn("Local package at %s has different name than %s (%s)", path.toNativeString(), name, pp.name);
  583. pp.version_ = ver;
  584.  
  585. addPackages(packs, pp);
  586. }
  587. } catch( Exception e ){
  588. logWarn("Error adding local package: %s", e.msg);
  589. }
  590. }
  591. } catch( Exception e ){
  592. logDiagnostic("Loading of local package list at %s failed: %s", list_path.toNativeString(), e.msg);
  593. }
  594. m_repositories[type].localPackages = packs;
  595. m_repositories[type].searchPath = paths;
  596. }
  597. scanLocalPackages(LocalPackageType.system);
  598. scanLocalPackages(LocalPackageType.user);
  599.  
  600. auto old_packages = m_packages;
  601.  
  602. // rescan the system and user package folder
  603. void scanPackageFolder(NativePath path)
  604. {
  605. if( path.existsDirectory() ){
  606. logDebug("iterating dir %s", path.toNativeString());
  607. try foreach( pdir; iterateDirectory(path) ){
  608. logDebug("iterating dir %s entry %s", path.toNativeString(), pdir.name);
  609. if (!pdir.isDirectory) continue;
  610.  
  611. auto pack_path = path ~ (pdir.name ~ "/");
  612.  
  613. auto packageFile = Package.findPackageFile(pack_path);
  614.  
  615. if (isManagedPath(path) && packageFile.empty) {
  616. // Search for a single directory within this directory which happen to be a prefix of pdir
  617. // This is to support new folder structure installed over the ancient one.
  618. foreach (subdir; iterateDirectory(path ~ (pdir.name ~ "/")))
  619. if (subdir.isDirectory && pdir.name.startsWith(subdir.name)) {// eg: package vibe-d will be in "vibe-d-x.y.z/vibe-d"
  620. pack_path ~= subdir.name ~ "/";
  621. packageFile = Package.findPackageFile(pack_path);
  622. break;
  623. }
  624. }
  625.  
  626. if (packageFile.empty) continue;
  627. Package p;
  628. try {
  629. if (!refresh_existing_packages)
  630. foreach (pp; old_packages)
  631. if (pp.path == pack_path) {
  632. p = pp;
  633. break;
  634. }
  635. if (!p) p = Package.load(pack_path, packageFile);
  636. addPackages(m_packages, p);
  637. } catch( Exception e ){
  638. logError("Failed to load package in %s: %s", pack_path, e.msg);
  639. logDiagnostic("Full error: %s", e.toString().sanitize());
  640. }
  641. }
  642. catch(Exception e) logDiagnostic("Failed to enumerate %s packages: %s", path.toNativeString(), e.toString());
  643. }
  644. }
  645.  
  646. m_packages = null;
  647. foreach (p; this.completeSearchPath)
  648. scanPackageFolder(p);
  649.  
  650. void loadOverrides(LocalPackageType type)
  651. {
  652. m_repositories[type].overrides = null;
  653. auto ovrfilepath = m_repositories[type].packagePath ~ LocalOverridesFilename;
  654. if (existsFile(ovrfilepath)) {
  655. foreach (entry; jsonFromFile(ovrfilepath)) {
  656. PackageOverride ovr;
  657. ovr.package_ = entry["name"].get!string;
  658. ovr.version_ = Dependency(entry["version"].get!string);
  659. if (auto pv = "targetVersion" in entry) ovr.targetVersion = Version(pv.get!string);
  660. if (auto pv = "targetPath" in entry) ovr.targetPath = NativePath(pv.get!string);
  661. m_repositories[type].overrides ~= ovr;
  662. }
  663. }
  664. }
  665. loadOverrides(LocalPackageType.user);
  666. loadOverrides(LocalPackageType.system);
  667. }
  668.  
  669. alias Hash = ubyte[];
  670. /// Generates a hash value for a given package.
  671. /// Some files or folders are ignored during the generation (like .dub and
  672. /// .svn folders)
  673. Hash hashPackage(Package pack)
  674. {
  675. string[] ignored_directories = [".git", ".dub", ".svn"];
  676. // something from .dub_ignore or what?
  677. string[] ignored_files = [];
  678. SHA1 sha1;
  679. foreach(file; dirEntries(pack.path.toNativeString(), SpanMode.depth)) {
  680. if(file.isDir && ignored_directories.canFind(NativePath(file.name).head.toString()))
  681. continue;
  682. else if(ignored_files.canFind(NativePath(file.name).head.toString()))
  683. continue;
  684.  
  685. sha1.put(cast(ubyte[])NativePath(file.name).head.toString());
  686. if(file.isDir) {
  687. logDebug("Hashed directory name %s", NativePath(file.name).head);
  688. }
  689. else {
  690. sha1.put(openFile(NativePath(file.name)).readAll());
  691. logDebug("Hashed file contents from %s", NativePath(file.name).head);
  692. }
  693. }
  694. auto hash = sha1.finish();
  695. logDebug("Project hash: %s", hash);
  696. return hash[].dup;
  697. }
  698.  
  699. private void writeLocalPackageList(LocalPackageType type)
  700. {
  701. Json[] newlist;
  702. foreach (p; m_repositories[type].searchPath) {
  703. auto entry = Json.emptyObject;
  704. entry["name"] = "*";
  705. entry["path"] = p.toNativeString();
  706. newlist ~= entry;
  707. }
  708.  
  709. foreach (p; m_repositories[type].localPackages) {
  710. if (p.parentPackage) continue; // do not store sub packages
  711. auto entry = Json.emptyObject;
  712. entry["name"] = p.name;
  713. entry["version"] = p.version_.toString();
  714. entry["path"] = p.path.toNativeString();
  715. newlist ~= entry;
  716. }
  717.  
  718. NativePath path = m_repositories[type].packagePath;
  719. if( !existsDirectory(path) ) mkdirRecurse(path.toNativeString());
  720. writeJsonFile(path ~ LocalPackagesFilename, Json(newlist));
  721. }
  722.  
  723. private void writeLocalPackageOverridesFile(LocalPackageType type)
  724. {
  725. Json[] newlist;
  726. foreach (ovr; m_repositories[type].overrides) {
  727. auto jovr = Json.emptyObject;
  728. jovr["name"] = ovr.package_;
  729. jovr["version"] = ovr.version_.versionSpec;
  730. if (!ovr.targetPath.empty) jovr["targetPath"] = ovr.targetPath.toNativeString();
  731. else jovr["targetVersion"] = ovr.targetVersion.toString();
  732. newlist ~= jovr;
  733. }
  734. auto path = m_repositories[type].packagePath;
  735. if (!existsDirectory(path)) mkdirRecurse(path.toNativeString());
  736. writeJsonFile(path ~ LocalOverridesFilename, Json(newlist));
  737. }
  738.  
  739. /// Adds the package and scans for subpackages.
  740. private void addPackages(ref Package[] dst_repos, Package pack)
  741. const {
  742. // Add the main package.
  743. dst_repos ~= pack;
  744.  
  745. // Additionally to the internally defined subpackages, whose metadata
  746. // is loaded with the main dub.json, load all externally defined
  747. // packages after the package is available with all the data.
  748. foreach (spr; pack.subPackages) {
  749. Package sp;
  750.  
  751. if (spr.path.length) {
  752. auto p = NativePath(spr.path);
  753. p.normalize();
  754. enforce(!p.absolute, "Sub package paths must be sub paths of the parent package.");
  755. auto path = pack.path ~ p;
  756. if (!existsFile(path)) {
  757. logError("Package %s declared a sub-package, definition file is missing: %s", pack.name, path.toNativeString());
  758. continue;
  759. }
  760. sp = Package.load(path, NativePath.init, pack);
  761. } else sp = new Package(spr.recipe, pack.path, pack);
  762.  
  763. // Add the subpackage.
  764. try {
  765. dst_repos ~= sp;
  766. } catch (Exception e) {
  767. logError("Package '%s': Failed to load sub-package %s: %s", pack.name,
  768. spr.path.length ? spr.path : spr.recipe.name, e.msg);
  769. logDiagnostic("Full error: %s", e.toString().sanitize());
  770. }
  771. }
  772. }
  773. }
  774.  
  775. struct PackageOverride {
  776. string package_;
  777. Dependency version_;
  778. Version targetVersion;
  779. NativePath targetPath;
  780.  
  781. this(string package_, Dependency version_, Version target_version)
  782. {
  783. this.package_ = package_;
  784. this.version_ = version_;
  785. this.targetVersion = target_version;
  786. }
  787.  
  788. this(string package_, Dependency version_, NativePath target_path)
  789. {
  790. this.package_ = package_;
  791. this.version_ = version_;
  792. this.targetPath = target_path;
  793. }
  794. }
  795.  
  796. enum LocalPackageType {
  797. user,
  798. system
  799. }
  800.  
  801. private enum LocalPackagesFilename = "local-packages.json";
  802. private enum LocalOverridesFilename = "local-overrides.json";
  803.  
  804.  
  805. private struct Repository {
  806. NativePath path;
  807. NativePath packagePath;
  808. NativePath[] searchPath;
  809. Package[] localPackages;
  810. PackageOverride[] overrides;
  811.  
  812. this(NativePath path)
  813. {
  814. this.path = path;
  815. this.packagePath = path ~"packages/";
  816. }
  817. }