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