diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 09ae129488..642ddbcf2c 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -606,7 +606,8 @@ public virtual ConcurrentDictionary install_run(Chocolate action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); } - var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action)); + var beforeModifyAction = new Action(packageResult => before_package_modify(packageResult, config)); + var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action, beforeModifyAction)); foreach (var result in results) { diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 8c49c9b9eb..840d44cbb8 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -24,6 +24,7 @@ namespace chocolatey.infrastructure.app.services using System.Net; using adapters; using chocolatey.infrastructure.app.utility; + using chocolatey.infrastructure.configuration; using commandline; using configuration; using domain; @@ -451,6 +452,17 @@ public virtual ConcurrentDictionary install_run(Chocolate uninstallSuccessAction: null, addUninstallHandler: true); + EventHandler beforeModifyHandler = (sender, eventArgs) => + { + // Soundness: any code called here must avoid calling packageManager methods for + // InstallPackage() and UninstallPackage(), or in some other way handle the + // unbounded recursion doing so would create. + backup_and_before_modify(eventArgs.Package, config, beforeModifyAction); + }; + + packageManager.PackageUninstalling += beforeModifyHandler; + packageManager.PackageInstalling += beforeModifyHandler; + config.start_backup(); foreach (string packageName in packageNames.or_empty_list_if_null()) @@ -637,6 +649,17 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate uninstallSuccessAction: null, addUninstallHandler: false); + EventHandler beforeModifyHandler = (sender, eventArgs) => + { + // Soundness: any code called here must avoid calling packageManager methods for + // InstallPackage() and UninstallPackage(), or in some other way handle the + // unbounded recursion doing so would create. + backup_and_before_modify(eventArgs.Package, config, beforeUpgradeAction); + }; + + packageManager.PackageUninstalling += beforeModifyHandler; + packageManager.PackageInstalling += beforeModifyHandler; + var configIgnoreDependencies = config.IgnoreDependencies; set_package_names_if_all_is_specified(config, () => { config.IgnoreDependencies = true; }); config.IgnoreDependencies = configIgnoreDependencies; @@ -685,7 +708,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate } else { - var installResults = install_run(config, continueAction); + var installResults = install_run(config, continueAction, beforeUpgradeAction); foreach (var result in installResults) { packageInstalls.GetOrAdd(result.Key, result.Value); @@ -850,22 +873,12 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate packageName, version == null ? null : version.ToString())) { - if (beforeUpgradeAction != null) - { - var currentPackageResult = new PackageResult(installedPackage, get_install_directory(config, installedPackage)); - beforeUpgradeAction(currentPackageResult); - } - - remove_rollback_directory_if_exists(packageName); - ensure_package_files_have_compatible_attributes(config, installedPackage, pkgInfo); - rename_legacy_package_version(config, installedPackage, pkgInfo); - backup_existing_version(config, installedPackage, pkgInfo); - remove_shim_directors(config, installedPackage, pkgInfo); if (config.Force && (installedPackage.Version == availablePackage.Version)) { FaultTolerance.try_catch_with_logging_exception( () => { + backup_existing_package_files(config, installedPackage); _fileSystem.delete_directory_if_exists(_fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id), recursive: true); remove_cache_for_package(config, installedPackage); }, @@ -876,6 +889,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate { packageManager.UpdatePackage(availablePackage, updateDependencies: !config.IgnoreDependencies, allowPrereleaseVersions: config.Prerelease); } + remove_nuget_cache_for_package(availablePackage); } } @@ -1109,7 +1123,7 @@ public virtual void backup_existing_version(ChocolateyConfiguration config, IPac var errored = false; try { - _fileSystem.move_directory(pkgInstallPath, backupLocation); + _fileSystem.copy_directory(pkgInstallPath, backupLocation, overwriteExisting: true); } catch (Exception ex) { @@ -1120,8 +1134,6 @@ public virtual void backup_existing_version(ChocolateyConfiguration config, IPac { try { - _fileSystem.copy_directory(backupLocation, pkgInstallPath, overwriteExisting: true); - remove_packaging_files_prior_to_upgrade(pkgInstallPath, config.CommandName); } catch (Exception ex) @@ -1292,20 +1304,29 @@ public virtual ConcurrentDictionary uninstall_run(Chocola var packageUninstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; - var packageManager = NugetCommon.GetPackageManager(config, _nugetLogger, _packageDownloader, - installSuccessAction: null, - uninstallSuccessAction: (e) => - { - var pkg = e.Package; - "chocolatey".Log().Info(ChocolateyLoggers.Important, " {0} has been successfully uninstalled.".format_with(pkg.Id)); - }, - addUninstallHandler: true); + var packageManager = NugetCommon.GetPackageManager( + config, + _nugetLogger, + _packageDownloader, + installSuccessAction: null, + uninstallSuccessAction: (e) => + { + var pkg = e.Package; + "chocolatey".Log().Info(ChocolateyLoggers.Important, " {0} has been successfully uninstalled.".format_with(pkg.Id)); + }, + addUninstallHandler: true); var loopCount = 0; + packageManager.PackageUninstalling += (s, e) => { + // Soundness: any code called here must avoid calling packageManager methods for + // InstallPackage() and UninstallPackage(), or in some other way handle the + // unbounded recursion doing so would create. var pkg = e.Package; + backup_and_before_modify(pkg, config, beforeUninstallAction); + // TODO: Removal special handling for SxS packages once we hit v2.0.0 // this section fires twice sometimes, like for older packages in a sxs install... @@ -1331,8 +1352,8 @@ public virtual ConcurrentDictionary uninstall_run(Chocola } // is this the latest version, have you passed --sxs, or is this a side-by-side install? This is the only way you get through to the continue action. - var latestVersion = packageManager.LocalRepository.FindPackage(e.Package.Id); - var pkgInfo = _packageInfoService.get_package_information(e.Package); + var latestVersion = packageManager.LocalRepository.FindPackage(pkg.Id); + var pkgInfo = _packageInfoService.get_package_information(pkg); if (latestVersion.Version == pkg.Version || config.AllowMultipleVersions || (pkgInfo != null && pkgInfo.IsSideBySide)) { packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); @@ -1342,6 +1363,10 @@ public virtual ConcurrentDictionary uninstall_run(Chocola { //todo: #2578 allow cleaning of pkgstore files } + + ensure_nupkg_is_removed(pkg, pkgInfo); + remove_installation_files(pkg, pkgInfo); + remove_cache_for_package(config, pkg); }; // if we are uninstalling a package and not forcing dependencies, @@ -1500,21 +1525,7 @@ public virtual ConcurrentDictionary uninstall_run(Chocola packageVersion.Id, packageVersion.Version.to_string()) ) { - if (beforeUninstallAction != null) - { - // guessing this is not added so that it doesn't fail the action if an error is recorded? - //var currentPackageResult = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageVersion, get_install_directory(config, packageVersion))); - var currentPackageResult = new PackageResult(packageVersion, get_install_directory(config, packageVersion)); - beforeUninstallAction(currentPackageResult); - } - ensure_package_files_have_compatible_attributes(config, packageVersion, pkgInfo); - rename_legacy_package_version(config, packageVersion, pkgInfo); - remove_rollback_directory_if_exists(packageName); - backup_existing_version(config, packageVersion, pkgInfo); packageManager.UninstallPackage(packageVersion.Id.to_lower(), forceRemove: config.Force, removeDependencies: config.ForceDependencies, version: packageVersion.Version); - ensure_nupkg_is_removed(packageVersion, pkgInfo); - remove_installation_files(packageVersion, pkgInfo); - remove_cache_for_package(config, packageVersion); } } catch (Exception ex) @@ -1548,6 +1559,47 @@ public virtual ConcurrentDictionary uninstall_run(Chocola return packageUninstalls; } + /// + /// This method should be called before any modifications are made to a package. + /// Typically this should be injected into the IPackageManager's Installing and Uninstalling events + /// in order to handle both installation and uninstallation of dependencies as well as the primary + /// packages. + /// + /// The package currently being modified. For IPackageManager event handlers, this will be the eventArgs.Package property. + /// The current configuration. + /// Any action to run before performing backup operations. Typically this is an invocation of the chocolateyBeforeModify script. + private void backup_and_before_modify(IPackage package, ChocolateyConfiguration config, Action beforeModifyAction) + { + var installDirectory = get_install_directory(config, package); + + if (installDirectory != null) + { + // if this is an already installed package we're modifying, ensure we run its beforeModify script and back it up properly. + if (beforeModifyAction != null) + { + "chocolatey".Log().Debug("Running beforeModify step for '{0}'", package.Id); + + var packageToModify = new PackageResult(package, installDirectory); + beforeModifyAction(packageToModify); + } + + "chocolatey".Log().Debug("Backing up package files for '{0}'", package.Id); + + backup_existing_package_files(config, package); + } + } + + private void backup_existing_package_files(ChocolateyConfiguration config, IPackage package) + { + var packageInformation = _packageInfoService.get_package_information(package); + + ensure_package_files_have_compatible_attributes(config, package, packageInformation); + rename_legacy_package_version(config, package, packageInformation); + remove_rollback_directory_if_exists(package.Id); + backup_existing_version(config, package, packageInformation); + remove_shim_directors(config, package, packageInformation); + } + /// /// NuGet will happily report a package has been uninstalled, even if it doesn't always remove the nupkg. /// Ensure that the package is deleted or throw an error.