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 }