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