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