diff --git a/src/ncc/CLI/Management/PackageManagerMenu.php b/src/ncc/CLI/Management/PackageManagerMenu.php index 0c69f7b..552193c 100644 --- a/src/ncc/CLI/Management/PackageManagerMenu.php +++ b/src/ncc/CLI/Management/PackageManagerMenu.php @@ -111,7 +111,7 @@ } catch(Exception $e) { - Console::outException(sprintf('Unable to fix missing packages: %s', $e->getMessage()), $e, 1); + Console::outException(sprintf('Unable to fix broken packages: %s', $e->getMessage()), $e, 1); return 1; } } @@ -401,6 +401,7 @@ /** * Uninstall all packages from the system * + * @param array $args * @return int * @throws IOException * @throws OperationException @@ -450,48 +451,76 @@ } $package_manager = new PackageManager(); - $results = $package_manager->getMissingPackages(); + $missing_dependencies = $package_manager->getMissingPackages(); + $broken_packages = $package_manager->getBrokenPackages(); $auto_yes = isset($args['y']); - if(count($results) === 0) + if(count($missing_dependencies) === 0 && count($broken_packages) === 0) { - Console::out('No missing packages found'); + Console::out('No broken packages found'); return 0; } - Console::out('The following packages that are required by other packages are missing:'); - $unfixable_count = 0; - foreach($results as $package => $source) + if(count($missing_dependencies) > 0) { - if($source === null) + Console::out('The following packages that are required by other packages are missing:'); + $unfixable_count = 0; + foreach($missing_dependencies as $package => $source) { - ++$unfixable_count; - continue; - } - - Console::out(sprintf(' %s', $package)); - } - - if($unfixable_count > 0) - { - Console::out('The following packages packages cannot be fixed because they are missing and no source was specified:'); - foreach($results as $package => $source) - { - if($source !== null) + if($source === null) { + ++$unfixable_count; continue; } Console::out(sprintf(' %s', $package)); } + + if($unfixable_count > 0) + { + Console::out('The following packages packages cannot be fixed because they are missing and no source was specified:'); + foreach($missing_dependencies as $package => $source) + { + if($source !== null) + { + continue; + } + + Console::out(sprintf(' %s', $package)); + } + } } - if(!$auto_yes && !Console::getBooleanInput('Do you want attempt to fix these missing packages?')) + if(count($broken_packages) > 0) + { + Console::out('The following packages are broken and should be removed:'); + foreach($broken_packages as $package) + { + Console::out(sprintf(' %s', $package)); + } + } + + if(!$auto_yes && !Console::getBooleanInput('Do you want attempt to fix these packages?')) { return 0; } - foreach($results as $package => $source) + foreach($broken_packages as $package) + { + Console::out(sprintf('Removing broken package %s', $package)); + $parsed = explode('=', $package, 2); + + if(count($parsed) === 1) + { + Console::out(sprintf('Uninstalling all versions of %s, removed %s packages', $package, count($package_manager->uninstall($parsed[0])))); + } + else + { + Console::out(sprintf('Uninstalling %s, removed %s packages', $package, count($package_manager->uninstall($parsed[0], $parsed[1])))); + } + } + + foreach($missing_dependencies as $package => $source) { if($source === null) { diff --git a/src/ncc/Managers/PackageManager.php b/src/ncc/Managers/PackageManager.php index b5a50dc..e5c8997 100644 --- a/src/ncc/Managers/PackageManager.php +++ b/src/ncc/Managers/PackageManager.php @@ -42,6 +42,7 @@ use ncc\Enums\RegexPatterns; use ncc\Enums\Scopes; use ncc\Enums\Types\ProjectType; + use ncc\Enums\Versions; use ncc\Exceptions\ConfigurationException; use ncc\Exceptions\IOException; use ncc\Exceptions\NetworkException; @@ -200,6 +201,30 @@ if($version === null) { + if($this->package_lock->getEntry($package_name)->isSymlinkRegistered()) + { + Console::outVerbose(sprintf( + 'Removing symlink for %s=%s at %s', + $package_name, $this->package_lock->getEntry($package_name)->getVersion(Versions::LATEST)->getVersion(), + PathFinder::findBinPath() . DIRECTORY_SEPARATOR . strtolower($package_name) + )); + + try + { + $symlink_path = PathFinder::findBinPath() . DIRECTORY_SEPARATOR . strtolower($this->package_lock->getEntry($package_name)->getAssembly()->getName()); + } + catch(Exception $e) + { + throw new IOException(sprintf('Failed to resolve symlink for %s=%s: %s', $package_name, $this->package_lock->getEntry($package_name)->getVersion(Versions::LATEST)->getVersion(), $e->getMessage()), $e); + } + + if(is_file($symlink_path)) + { + (new Filesystem())->remove($symlink_path); + $this->package_lock->getEntry($package_name)->setSymlinkRegistered(false); + } + } + foreach($this->package_lock->getEntry($package_name)->getVersions() as $iter_version) { Console::out(sprintf('Uninstalling package %s=%s', $package_name, $iter_version)); @@ -209,11 +234,37 @@ $removed_packages[] = sprintf('%s=%s', $package_name, $iter_version); } + + $this->package_lock->removeEntry($package_name); } else { Console::out(sprintf('Uninstalling package %s=%s', $package_name, $version)); $package_path = $this->package_lock->getPath($package_name, $version); + + if($this->package_lock->getEntry($package_name)->isSymlinkRegistered()) + { + Console::outVerbose(sprintf( + 'Removing symlink for %s=%s at %s', + $package_name, $version, PathFinder::findBinPath() . DIRECTORY_SEPARATOR . strtolower($package_name) + )); + + try + { + $symlink_path = PathFinder::findBinPath() . DIRECTORY_SEPARATOR . strtolower($this->package_lock->getEntry($package_name)->getAssembly($version)->getName()); + } + catch(Exception $e) + { + Console::outWarning(sprintf('Failed to resolve symlink for %s=%s: %s', $package_name, $version, $e->getMessage())); + } + + if(isset($symlink_path) && is_file($symlink_path)) + { + (new Filesystem())->remove($symlink_path); + $this->package_lock->getEntry($package_name)->setSymlinkRegistered(false); + } + } + $this->package_lock->getEntry($package_name)->removeVersion($version); (new Filesystem())->remove($package_path); @@ -309,6 +360,31 @@ return $results; } + /** + * Returns an array of broken packages detected on the system + * + * @return array + */ + public function getBrokenPackages(): array + { + $results = []; + + foreach($this->package_lock->getEntries() as $entry) + { + foreach($this->package_lock->getEntry($entry)->getBrokenVersions() as $version) + { + if(in_array(sprintf('%s=%s', $entry, $version), $results, true)) + { + continue; + } + + $results[] = sprintf('%s=%s', $entry, $version); + } + } + + return $results; + } + /** * Installs a package onto the system from a local ncc package file * diff --git a/src/ncc/Objects/PackageLock/PackageEntry.php b/src/ncc/Objects/PackageLock/PackageEntry.php index 4dd321a..a541fc7 100644 --- a/src/ncc/Objects/PackageLock/PackageEntry.php +++ b/src/ncc/Objects/PackageLock/PackageEntry.php @@ -459,6 +459,26 @@ return $execution_binary_path; } + /** + * Returns an array of all broken versions + * + * @return array + */ + public function getBrokenVersions(): array + { + $broken_versions = []; + + foreach($this->versions as $version) + { + if($version->isBroken($this->name)) + { + $broken_versions[] = $version->getVersion(); + } + } + + return $broken_versions; + } + /** * Returns an array representation of the object * diff --git a/src/ncc/Objects/PackageLock/VersionEntry.php b/src/ncc/Objects/PackageLock/VersionEntry.php index 0e4cbc4..d678dbb 100644 --- a/src/ncc/Objects/PackageLock/VersionEntry.php +++ b/src/ncc/Objects/PackageLock/VersionEntry.php @@ -234,6 +234,31 @@ return $this->getPath($package_name) . DIRECTORY_SEPARATOR . FileDescriptor::SHADOW_PACKAGE; } + /** + * Returns True if the package is broken, false otherwise + * + * @return bool + */ + public function isBroken(string $package_name): bool + { + if(!is_file($this->getPath($package_name) . DIRECTORY_SEPARATOR . FileDescriptor::SHADOW_PACKAGE)) + { + return true; + } + + if(!is_file($this->getPath($package_name) . DIRECTORY_SEPARATOR . FileDescriptor::ASSEMBLY)) + { + return true; + } + + if(!is_file($this->getPath($package_name) . DIRECTORY_SEPARATOR . FileDescriptor::METADATA)) + { + return true; + } + + return false; + } + /** * Returns an array representation of the object *