From 26ac2df953ea1c8af2c03c33525ac0600c8e46f6 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Thu, 6 Feb 2020 17:42:32 +0000 Subject: [PATCH 1/4] (GH-1670) Add noshims options for install and upgrade The --noshims and --noshimsglobal options allow the user to stop shims being created for either the package, or the package and its dependencies. In addition to this new behaviour, the implementation handles the creation and removal of shims more robustly, so that shims from other packages (or the same package) cannot be unintentionally overwritten or removed. The new ShimRegistry class keeps track of the currently installed shims and their corresponding packages. It does this by examining all the exe files in the shim directory (so old-style .bat and unixy shims are not supported) and extracting the target file from the binary. If this target path contains the lib folder, then the package name can be obtained. ShimRegistry is updated for each package, just before the Powershell scripts are run, using `ShimGenerationService::take_snapshot`. When ShimGenerationService installs any shims for the package, ShimRegistry provides its existing shim files (ie. those that were not modified or removed since the last update) so that they can be deleted prior to installing any new ones. To stop shims being added from Install-BinFile, a new environment variable `chocolateyNoShims` is used when running the Powershell scripts. If a package sets a shim this way, but then forgets to call Uninstall-BinFile when uninstalling, the shim will be removed anyway if its target is in the package folder. --- Scenarios.md | 66 +++- .../helpers/functions/Install-BinFile.ps1 | 7 + src/chocolatey.tests.integration/Scenario.cs | 2 + .../chocolatey.tests.integration.csproj | 99 +++++ .../1.0.0/shimbasepackage.nuspec | 17 + .../1.0.0/tools/chocolateyInstall.ps1 | 1 + .../1.0.0/tools/chocolateyUninstall.ps1 | 1 + .../1.0.0/tools/shimbasepackage1.exe | 1 + .../1.0.0/shimhasdependency.nuspec | 20 + .../1.0.0/tools/chocolateyInstall.ps1 | 1 + .../1.0.0/tools/chocolateyUninstall.ps1 | 1 + .../1.0.0/tools/shimhasdependency1.exe | 1 + .../shimoverwrite/1.0.0/shimoverwrite.nuspec | 17 + .../1.0.0/tools/chocolateyInstall.ps1 | 1 + .../1.0.0/tools/chocolateyUninstall.ps1 | 1 + .../1.0.0/tools/install/shimoverwrite1.exe | 1 + .../1.0.0/tools/shimoverwrite1.exe | 1 + .../1.0.0/shimoverwriteother.nuspec | 20 + .../1.0.0/tools/chocolateyInstall.ps1 | 1 + .../1.0.0/tools/chocolateyUninstall.ps1 | 1 + .../1.0.0/tools/shimbasepackage1.exe | 1 + .../shimupgrade/1.0.0/shimupgrade.nuspec | 17 + .../1.0.0/tools/chocolateyInstall.ps1 | 1 + .../1.0.0/tools/chocolateyUninstall.ps1 | 1 + .../shimupgrade/1.0.0/tools/shimupgrade1.exe | 1 + .../shimupgrade/2.0.0/shimupgrade.nuspec | 17 + .../2.0.0/tools/chocolateyInstall.ps1 | 7 + .../2.0.0/tools/chocolateyUninstall.ps1 | 1 + .../shimupgrade/2.0.0/tools/shimupgrade2.exe | 1 + .../shimupgrade/3.0.0/shimupgrade.nuspec | 17 + .../3.0.0/tools/chocolateyInstall.ps1 | 1 + .../3.0.0/tools/chocolateyUninstall.ps1 | 1 + .../shimupgrade/3.0.0/tools/shimupgrade3.exe | 1 + .../1.0.0/shimwithbinfile.nuspec | 17 + .../1.0.0/tools/chocolateyInstall.ps1 | 7 + .../1.0.0/tools/chocolateyUninstall.ps1 | 4 + .../1.0.0/tools/shimwithbinfile1.bat | 1 + .../scenarios/InstallScenarios.cs | 367 ++++++++++++++++++ .../scenarios/UninstallScenarios.cs | 38 ++ .../scenarios/UpgradeScenarios.cs | 116 ++++++ .../commands/ChocolateyInstallCommandSpecs.cs | 12 + .../commands/ChocolateyUpgradeCommandSpecs.cs | 12 + src/chocolatey/chocolatey.csproj | 3 + .../commands/ChocolateyInstallCommand.cs | 6 + .../commands/ChocolateyUpgradeCommand.cs | 6 + .../configuration/ChocolateyConfiguration.cs | 2 + .../infrastructure.app/domain/ShimRecord.cs | 60 +++ .../infrastructure.app/domain/ShimRegistry.cs | 308 +++++++++++++++ .../infrastructure.app/domain/ShimStore.cs | 260 +++++++++++++ .../services/ChocolateyPackageService.cs | 3 + .../services/IShimGenerationService.cs | 6 + .../services/PowershellService.cs | 14 +- .../services/ShimGenerationService.cs | 151 +++++-- 53 files changed, 1687 insertions(+), 33 deletions(-) create mode 100644 src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/shimbasepackage.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/shimbasepackage1.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/shimhasdependency.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/shimhasdependency1.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/shimoverwrite.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/install/shimoverwrite1.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/shimoverwrite1.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/shimoverwriteother.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/shimbasepackage1.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/shimupgrade.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/shimupgrade1.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/shimupgrade.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/shimupgrade2.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/shimupgrade.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/shimupgrade3.exe create mode 100644 src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/shimwithbinfile.nuspec create mode 100644 src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyInstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyUninstall.ps1 create mode 100644 src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/shimwithbinfile1.bat create mode 100644 src/chocolatey/infrastructure.app/domain/ShimRecord.cs create mode 100644 src/chocolatey/infrastructure.app/domain/ShimRegistry.cs create mode 100644 src/chocolatey/infrastructure.app/domain/ShimStore.cs diff --git a/Scenarios.md b/Scenarios.md index c9767724fb..5633c3cce7 100644 --- a/Scenarios.md +++ b/Scenarios.md @@ -1,6 +1,6 @@ ## Chocolatey Usage Scenarios -### ChocolateyInstallCommand [ 35 Scenario(s), 293 Observation(s) ] +### ChocolateyInstallCommand [ 42 Scenario(s), 312 Observation(s) ] #### when force installing a package that depends on an unavailable newer version of an installed dependency forcing dependencies @@ -247,6 +247,20 @@ * should not install a package in the lib directory * should put a package in the lib bad directory +#### when installing a package that tries to overwrite another shim + + * should have a shim + * should have a shim with target in other package tools folder + * should have an error message + * should have shim target + +#### when installing a package that tries to overwrite its own shim + + * should create a shim + * should have a shim with target in tools folder + * should have an error message + * should have shim targets + #### when installing a package with a dependent package that also depends on a less constrained but still valid dependency of the same package * [PENDING] should contain a message that everything installed successfully @@ -282,6 +296,18 @@ * should not install a package in the lib directory * should not install the dependency in the lib directory +#### when installing a package with dependencies and noshims + + * should create a shim for the dependency + * should have shim targets + * should not create a shim for the package + +#### when installing a package with dependencies and noshimsglobal + + * should have shim targets + * should not create a shim for the dependency + * should not create a shim for the package + #### when installing a package with dependencies happy * should contain a message that everything installed successfully @@ -326,11 +352,25 @@ * [PENDING] should not install the conflicting package in the lib directory * [PENDING] should not upgrade the exact version dependency +#### when installing a package with install bin file + + * should not see the shim as an existing shim and remove it + +#### when installing a package with install bin file and noshims + + * should have shim target + * should not create a shim + #### when installing a package with no sources enabled * should have no sources enabled result * should not install any packages +#### when installing a package with noshims + + * should have shim target + * should not create a shim for the package + #### when installing a side by side package * config should match package result name @@ -541,7 +581,7 @@ * should contain success message -### ChocolateyUninstallCommand [ 13 Scenario(s), 93 Observation(s) ] +### ChocolateyUninstallCommand [ 14 Scenario(s), 95 Observation(s) ] #### when force uninstalling a package @@ -617,6 +657,11 @@ * should not remove package from the lib directory * should still have the package file in the directory +#### when uninstalling a package that forgets to call uninstall bin file + + * should have had a shim + * should have removed the shim + #### when uninstalling a package with a read and delete share locked file * should contain a message that it uninstalled successfully @@ -675,7 +720,7 @@ * should throw an error that it is not allowed -### ChocolateyUpgradeCommand [ 36 Scenario(s), 295 Observation(s) ] +### ChocolateyUpgradeCommand [ 39 Scenario(s), 301 Observation(s) ] #### when force upgrading a package @@ -920,6 +965,11 @@ * should have no sources enabled result * should not have any packages upgraded +#### when upgrading a package with noshims + + * should have shim target + * should not create a shim + #### when upgrading a package with readonly files * should contain a warning message that it upgraded successfully @@ -933,6 +983,16 @@ * should upgrade the package * should upgrade where install location reports +#### when upgrading a package with shims + + * should create a shim + * should not have original shim + +#### when upgrading a package with shims that errors + + * should have original shim + * should not create a shim + #### when upgrading a package with unavailable dependencies * should contain a message that it was unable to upgrade anything diff --git a/src/chocolatey.resources/helpers/functions/Install-BinFile.ps1 b/src/chocolatey.resources/helpers/functions/Install-BinFile.ps1 index 972b432fca..eff970dfbf 100644 --- a/src/chocolatey.resources/helpers/functions/Install-BinFile.ps1 +++ b/src/chocolatey.resources/helpers/functions/Install-BinFile.ps1 @@ -86,6 +86,13 @@ param( Write-FunctionCallLogMessage -Invocation $MyInvocation -Parameters $PSBoundParameters + if ($env:ChocolateyNoShims) { + Write-Debug "File shimming disabled for `'$($env:ChocolateyPackageName)`'." + Write-Debug "Removing any existing shim for `'$name`'." + Uninstall-BinFile $name $path + return + } + $nugetPath = [System.IO.Path]::GetFullPath((Join-Path "$helpersPath" '..\')) $nugetExePath = Join-Path "$nugetPath" 'bin' $packageBatchFileName = Join-Path $nugetExePath "$name.bat" diff --git a/src/chocolatey.tests.integration/Scenario.cs b/src/chocolatey.tests.integration/Scenario.cs index 4077b192f9..d40dd87cae 100644 --- a/src/chocolatey.tests.integration/Scenario.cs +++ b/src/chocolatey.tests.integration/Scenario.cs @@ -177,6 +177,8 @@ private static ChocolateyConfiguration baseline_configuration() config.PinCommand.Name = string.Empty; config.PinCommand.Command = PinCommandType.unknown; config.ListCommand.IdOnly = false; + config.NoShims = false; + config.NoShimsGlobal = false; return config; } diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index aaf566b4d4..23f26ddfbf 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -346,6 +346,105 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + Always diff --git a/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/shimbasepackage.nuspec b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/shimbasepackage.nuspec new file mode 100644 index 0000000000..b620fe06cd --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/shimbasepackage.nuspec @@ -0,0 +1,17 @@ + + + + shimbasepackage + 1.0.0 + shimbasepackage + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + shimbasepackage + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..5cc9f79b7a --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/shimbasepackage1.exe b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/shimbasepackage1.exe new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimbasepackage/1.0.0/tools/shimbasepackage1.exe @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/shimhasdependency.nuspec b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/shimhasdependency.nuspec new file mode 100644 index 0000000000..342b618af3 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/shimhasdependency.nuspec @@ -0,0 +1,20 @@ + + + + shimhasdependency + 1.0.0 + shimhasdependency + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + shimhasdependency + + + + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..5cc9f79b7a --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/shimhasdependency1.exe b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/shimhasdependency1.exe new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimhasdependency/1.0.0/tools/shimhasdependency1.exe @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/shimoverwrite.nuspec b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/shimoverwrite.nuspec new file mode 100644 index 0000000000..3c52b00e54 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/shimoverwrite.nuspec @@ -0,0 +1,17 @@ + + + + shimoverwrite + 1.0.0 + shimoverwrite + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + overwrite-own + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..5cc9f79b7a --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/install/shimoverwrite1.exe b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/install/shimoverwrite1.exe new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/install/shimoverwrite1.exe @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/shimoverwrite1.exe b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/shimoverwrite1.exe new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwrite/1.0.0/tools/shimoverwrite1.exe @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/shimoverwriteother.nuspec b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/shimoverwriteother.nuspec new file mode 100644 index 0000000000..e8941cd7bf --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/shimoverwriteother.nuspec @@ -0,0 +1,20 @@ + + + + shimoverwriteother + 1.0.0 + shimoverwriteother + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + shimoverwriteother + + + + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..5cc9f79b7a --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/shimbasepackage1.exe b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/shimbasepackage1.exe new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimoverwriteother/1.0.0/tools/shimbasepackage1.exe @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/shimupgrade.nuspec b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/shimupgrade.nuspec new file mode 100644 index 0000000000..ef00377e59 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/shimupgrade.nuspec @@ -0,0 +1,17 @@ + + + + shimupgrade + 1.0.0 + shimupgrade + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + shimupgrade + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..5cc9f79b7a --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/shimupgrade1.exe b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/shimupgrade1.exe new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/1.0.0/tools/shimupgrade1.exe @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/shimupgrade.nuspec b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/shimupgrade.nuspec new file mode 100644 index 0000000000..c4213d5185 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/shimupgrade.nuspec @@ -0,0 +1,17 @@ + + + + shimupgrade + 2.0.0 + shimupgrade + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + This version throws an error when installing + __REPLACE__ + shimupgrade + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..9b17e662f9 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1,7 @@ +try { + Write-Output "This is $packageName v$packageVersion being installed to `n '$packageFolder'." + Write-Error "Oh no! An error" + throw "We had an error captain!" +} catch { + throw $_.Exception +} diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/shimupgrade2.exe b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/shimupgrade2.exe new file mode 100644 index 0000000000..359a5b952d --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/2.0.0/tools/shimupgrade2.exe @@ -0,0 +1 @@ +2.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/shimupgrade.nuspec b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/shimupgrade.nuspec new file mode 100644 index 0000000000..7559fd0990 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/shimupgrade.nuspec @@ -0,0 +1,17 @@ + + + + shimupgrade + 3.0.0 + shimupgrade + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + __REPLACE__ + __REPLACE__ + shimupgrade + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..5cc9f79b7a --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..6489cba588 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1 @@ +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/shimupgrade3.exe b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/shimupgrade3.exe new file mode 100644 index 0000000000..56fea8a08d --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimupgrade/3.0.0/tools/shimupgrade3.exe @@ -0,0 +1 @@ +3.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/shimwithbinfile.nuspec b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/shimwithbinfile.nuspec new file mode 100644 index 0000000000..ec1d5047f5 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/shimwithbinfile.nuspec @@ -0,0 +1,17 @@ + + + + shimwithbinfile + 1.0.0 + shimwithbinfile + __REPLACE_AUTHORS_OF_SOFTWARE__ + __REPLACE_YOUR_NAME__ + false + This version forgets to call Uninstall-BinFile when uninstalling + __REPLACE__ + shimwithbinfile + + + + + diff --git a/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyInstall.ps1 new file mode 100644 index 0000000000..6e9af8f601 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyInstall.ps1 @@ -0,0 +1,7 @@ +$ErrorActionPreference = 'Stop' + +$toolsDir = Split-Path $MyInvocation.MyCommand.Definition +$filePath = Join-Path $toolsDir "shimwithbinfile1.bat" + +Install-Binfile "shimwithbinfile1" "$filePath" +Write-Output "$env:PackageName $env:PackageVersion Installed" diff --git a/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyUninstall.ps1 b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyUninstall.ps1 new file mode 100644 index 0000000000..f329925b82 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/chocolateyUninstall.ps1 @@ -0,0 +1,4 @@ +$ErrorActionPreference = 'Stop' + +Write-Output "Oops, we've forgotten to call 'Uninstall-BinFile `"shimwithbinfile1`"'" +Write-Output "$env:PackageName $env:PackageVersion Uninstalled" diff --git a/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/shimwithbinfile1.bat b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/shimwithbinfile1.bat new file mode 100644 index 0000000000..afaf360d37 --- /dev/null +++ b/src/chocolatey.tests.integration/context/shims/shimwithbinfile/1.0.0/tools/shimwithbinfile1.bat @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs index f0856ff6ac..f20a54e192 100644 --- a/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/InstallScenarios.cs @@ -3391,5 +3391,372 @@ public void should_not_install_any_packages() Results.Count().ShouldEqual(0); } } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_with_noshims : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimbasepackage"; + Scenario.add_packages_to_source_location(Configuration, "shimbasepackage.1.0.0*" + Constants.PackageExtension); + Configuration.NoShims = true; + } + + public override void Because() + { + Service.install_run(Configuration); + } + + [Fact] + public void should_have_shim_target() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var target = Path.Combine(packageDir, "tools", "shimbasepackage1.exe"); + + File.Exists(target).ShouldBeTrue(); + } + + [Fact] + public void should_not_create_a_shim_for_the_package() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimbasepackage1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_with_dependencies_and_noshims : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimhasdependency"; + Scenario.add_packages_to_source_location(Configuration, "shimhasdependency.1.0.0*" + Constants.PackageExtension); + Scenario.add_packages_to_source_location(Configuration, "shimbasepackage.1.0.0*" + Constants.PackageExtension); + Configuration.NoShims = true; + } + + public override void Because() + { + Results = Service.install_run(Configuration); + } + + [Fact] + public void should_have_shim_targets() + { + var targetsExist = true; + var errorMessage = string.Empty; + + foreach (var packageResult in Results) + { + var target = Path.Combine(packageResult.Value.InstallLocation, "tools", packageResult.Value.Name + "1.exe"); + + if (!File.Exists(target)) + { + targetsExist = false; + errorMessage = "Target file missing: {0}".format_with(target); + break; + } + } + + targetsExist.ShouldBeTrue(errorMessage); + } + + [Fact] + public void should_not_create_a_shim_for_the_package() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimhasdependency1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_create_a_shim_for_the_dependency() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimbasepackage1.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_with_dependencies_and_noshimsglobal : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimhasdependency"; + Scenario.add_packages_to_source_location(Configuration, "shimhasdependency.1.0.0*" + Constants.PackageExtension); + Scenario.add_packages_to_source_location(Configuration, "shimbasepackage.1.0.0*" + Constants.PackageExtension); + Configuration.NoShimsGlobal = true; + } + + public override void Because() + { + Results = Service.install_run(Configuration); + } + + [Fact] + public void should_have_shim_targets() + { + var targetsExist = true; + var errorMessage = string.Empty; + + foreach (var packageResult in Results) + { + var target = Path.Combine(packageResult.Value.InstallLocation, "tools", packageResult.Value.Name + "1.exe"); + + if (!File.Exists(target)) + { + targetsExist = false; + errorMessage = "Target file missing: {0}".format_with(target); + break; + } + } + + targetsExist.ShouldBeTrue(errorMessage); + } + + [Fact] + public void should_not_create_a_shim_for_the_package() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimhasdependency1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_a_shim_for_the_dependency() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimbasepackage1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_with_install_bin_file : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimwithbinfile"; + Scenario.add_packages_to_source_location(Configuration, "shimwithbinfile.1.0.0*" + Constants.PackageExtension); + } + + public override void Because() + { + Service.install_run(Configuration); + } + + [Fact] + public void should_not_see_the_shim_as_an_existing_shim_and_remove_it() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimwithbinfile1.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_with_install_bin_file_and_noshims : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimwithbinfile"; + Scenario.add_packages_to_source_location(Configuration, "shimwithbinfile.1.0.0*" + Constants.PackageExtension); + Configuration.NoShims = true; + } + + public override void Because() + { + Service.install_run(Configuration); + } + + [Fact] + public void should_have_shim_target() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var target = Path.Combine(packageDir, "tools", "shimwithbinfile1.bat"); + + File.Exists(target).ShouldBeTrue(); + } + + [Fact] + public void should_not_create_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimwithbinfile1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_that_tries_to_overwrite_its_own_shim : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimoverwrite"; + Scenario.add_packages_to_source_location(Configuration, "shimoverwrite.1.0.0*" + Constants.PackageExtension); + } + + public override void Because() + { + Service.install_run(Configuration); + } + + [Fact] + public void should_have_shim_targets() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var toolsPath = Path.Combine(packageDir, "tools"); + + string[] targets = { + Path.Combine(toolsPath, "shimoverwrite1.exe"), + Path.Combine(toolsPath, "install", "shimoverwrite1.exe") + }; + + var targetsExist = true; + var errorMessage = string.Empty; + + foreach (string target in targets) + { + if (!File.Exists(target)) + { + targetsExist = false; + errorMessage = "Target file missing: {0}".format_with(target); + break; + } + } + + targetsExist.ShouldBeTrue(errorMessage); + } + + [Fact] + public void should_have_an_error_message() + { + bool expectedMessage = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Error).or_empty_list_if_null()) + { + if (message.Contains("cannot overwrite")) expectedMessage = true; + } + + expectedMessage.ShouldBeTrue(); + } + + [Fact] + public void should_create_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimoverwrite1.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + public void should_have_a_shim_with_target_in_tools_folder() + { + var messages = new List(); + + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimoverwrite1.exe"); + CommandExecutor.execute( + shimfile, + "--shimgen-noop", + 10, + stdOutAction: (s, e) => messages.Add(e.Data), + stdErrAction: (s, e) => messages.Add(e.Data) + ); + + var messageFound = false; + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var path = Path.Combine(packageDir, "tools", "shimoverwrite1.exe"); + + foreach (var message in messages.or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(message)) continue; + if (message.Contains(path)) messageFound = true; + } + + messageFound.ShouldBeTrue("Target file message not found for: {0}".format_with(path)); + } + } + + [Concern(typeof(ChocolateyInstallCommand))] + public class when_installing_a_package_that_tries_to_overwrite_another_shim : ScenariosBase + { + public override void Context() + { + base.Context(); + Configuration.PackageNames = Configuration.Input = "shimoverwriteother"; + Scenario.add_packages_to_source_location(Configuration, "shimoverwriteother.1.0.0*" + Constants.PackageExtension); + Scenario.add_packages_to_source_location(Configuration, "shimbasepackage.1.0.0*" + Constants.PackageExtension); + } + + public override void Because() + { + Service.install_run(Configuration); + } + + [Fact] + public void should_have_shim_target() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + + // this matches the name of the existing shim from the shimbasepackage dependency + var target = Path.Combine(packageDir, "tools", "shimbasepackage1.exe"); + + File.Exists(target).ShouldBeTrue(); + } + + [Fact] + public void should_have_an_error_message() + { + bool expectedMessage = false; + foreach (var message in MockLogger.MessagesFor(LogLevel.Error).or_empty_list_if_null()) + { + if (message.Contains("cannot overwrite")) expectedMessage = true; + } + + expectedMessage.ShouldBeTrue(); + } + + [Fact] + public void should_have_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimbasepackage1.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + public void should_have_a_shim_with_target_in_other_package_tools_folder() + { + var messages = new List(); + + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimbasepackage1.exe"); + CommandExecutor.execute( + shimfile, + "--shimgen-noop", + 10, + stdOutAction: (s, e) => messages.Add(e.Data), + stdErrAction: (s, e) => messages.Add(e.Data) + ); + + var messageFound = false; + var dependencyDir = Path.Combine(Scenario.get_top_level(), "lib", "shimbasepackage"); + var path = Path.Combine(dependencyDir, "tools", "shimbasepackage1.exe"); + + foreach (var message in messages.or_empty_list_if_null()) + { + if (string.IsNullOrWhiteSpace(message)) continue; + if (message.Contains(path)) messageFound = true; + } + + messageFound.ShouldBeTrue("Target file message not found for: {0}".format_with(path)); + } + } } } diff --git a/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs index 2279bb3ff1..6471a8ce78 100644 --- a/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs @@ -1047,5 +1047,43 @@ public void should_have_expected_error_in_package_result() errorFound.ShouldBeTrue(); } } + + [Concern(typeof(ChocolateyUninstallCommand))] + public class when_uninstalling_a_package_that_forgets_to_call_uninstall_bin_file : ScenariosBase + { + private string shimFile; + private bool shimExisted; + + public override void Context() + { + Configuration = Scenario.upgrade(); + Scenario.reset(Configuration); + Service = NUnitSetup.Container.GetInstance(); + + Configuration.PackageNames = Configuration.Input = "shimwithbinfile"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "shimwithbinfile", "1.0.0"); + } + + public override void Because() + { + shimFile = Path.Combine(Scenario.get_top_level(), "bin", "shimwithbinfile1.exe"); + shimExisted = File.Exists(shimFile); + + Service.uninstall_run(Configuration); + } + + [Fact] + public void should_have_had_a_shim() + { + shimExisted.ShouldBeTrue(shimFile); + } + + [Fact] + public void should_have_removed_the_shim() + { + File.Exists(shimFile).ShouldBeFalse(); + } + } } } diff --git a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs index b9e152fcb0..a39d317f41 100644 --- a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs @@ -3182,5 +3182,121 @@ public void should_skip_packages_in_except_list() upgradePackageResult.Count.ShouldEqual(0, "upgradepackage should not be in the results list"); } } + + [Concern(typeof(ChocolateyUpgradeCommand))] + public class when_upgrading_a_package_with_noshims : ScenariosBase + { + public override void Context() + { + Configuration = Scenario.upgrade(); + Scenario.reset(Configuration); + Service = NUnitSetup.Container.GetInstance(); + + Configuration.PackageNames = Configuration.Input = "shimupgrade"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + Configuration.Version = "1.0.0"; + Configuration.NoShims = true; + } + + public override void Because() + { + Service.upgrade_run(Configuration); + } + + [Fact] + public void should_have_shim_target() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var target = Path.Combine(packageDir, "tools", "shimupgrade1.exe"); + + File.Exists(target).ShouldBeTrue(); + } + + [Fact] + public void should_not_create_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + } + + [Concern(typeof(ChocolateyUpgradeCommand))] + public class when_upgrading_a_package_with_shims_that_errors : ScenariosBase + { + public override void Context() + { + Configuration = Scenario.upgrade(); + Scenario.reset(Configuration); + Service = NUnitSetup.Container.GetInstance(); + + Configuration.PackageNames = Configuration.Input = "shimupgrade"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "shimupgrade", "1.0.0"); + + // version 2.0.0 errors on script install + Configuration.Version = "2.0.0"; + } + + public override void Because() + { + Service.upgrade_run(Configuration); + } + + [Fact] + public void should_have_original_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade1.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + + [Fact] + public void should_not_create_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade2.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + } + + [Concern(typeof(ChocolateyUpgradeCommand))] + public class when_upgrading_a_package_with_shims : ScenariosBase + { + public override void Context() + { + Configuration = Scenario.upgrade(); + Scenario.reset(Configuration); + Service = NUnitSetup.Container.GetInstance(); + + Configuration.PackageNames = Configuration.Input = "shimupgrade"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "shimupgrade", "1.0.0"); + + // version 3.0.0 is okay + Configuration.Version = "3.0.0"; + } + + public override void Because() + { + Service.upgrade_run(Configuration); + } + + [Fact] + public void should_not_have_original_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_create_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade3.exe"); + + File.Exists(shimfile).ShouldBeTrue(); + } + } } } diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs index f32acfd5e6..5ff6ddc5c9 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs @@ -235,6 +235,18 @@ public void should_add_short_version_of_password_to_the_option_set() { optionSet.Contains("p").ShouldBeTrue(); } + + [Fact] + public void should_add_noshims_to_the_option_set() + { + optionSet.Contains("noshims").ShouldBeTrue(); + } + + [Fact] + public void should_add_noshimsglobal_to_the_option_set() + { + optionSet.Contains("noshimsglobal").ShouldBeTrue(); + } } public class when_handling_additional_argument_parsing : ChocolateyInstallCommandSpecsBase diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs index 15f16f875f..41dc123fdb 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs @@ -217,6 +217,18 @@ public void should_add_short_version_of_password_to_the_option_set() { optionSet.Contains("p").ShouldBeTrue(); } + + [Fact] + public void should_add_noshims_to_the_option_set() + { + optionSet.Contains("noshims").ShouldBeTrue(); + } + + [Fact] + public void should_add_noshimsglobal_to_the_option_set() + { + optionSet.Contains("noshimsglobal").ShouldBeTrue(); + } } public class when_handling_additional_argument_parsing : ChocolateyUpgradeCommandSpecsBase diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index f1d4e6bc7e..6e88de73e8 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -134,6 +134,9 @@ + + + diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index ba9cf33556..1c2fcc5669 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -180,6 +180,12 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.UsePackageRepositoryOptimizations = false; } }) + .Add("noshims|no-shims", + "No Shims - Stop shims being created for the package. Defaults to false. Available in 0.10.16+.", + option => configuration.NoShims = option != null) + .Add("noshimsglobal|no-shims-global", + "No Shims Global - Stop shims being created for the package and its dependencies. Defaults to false. Available in 0.10.16+.", + option => configuration.NoShimsGlobal = option != null) ; //todo: package name can be a url / installertype diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index f8a4c31f6a..64e061ee1e 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -220,6 +220,12 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon configuration.Features.UsePackageRepositoryOptimizations = false; } }) + .Add("noshims|no-shims", + "No Shims - Stop shims being created for the package. Defaults to false. Available in 0.10.16+.", + option => configuration.NoShims = option != null) + .Add("noshimsglobal|no-shims-global", + "No Shims Global - Stop shims being created for the package and its dependencies. Defaults to false. Available in 0.10.16+.", + option => configuration.NoShimsGlobal = option != null) ; } diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index c5cc33f73f..13f842977e 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -221,6 +221,8 @@ private void append_output(StringBuilder propertyValues, string append) public string DownloadChecksum64 { get; set; } public string DownloadChecksumType { get; set; } public string DownloadChecksumType64 { get; set; } + public bool NoShims { get; set; } + public bool NoShimsGlobal { get; set; } /// /// Configuration values provided by choco. diff --git a/src/chocolatey/infrastructure.app/domain/ShimRecord.cs b/src/chocolatey/infrastructure.app/domain/ShimRecord.cs new file mode 100644 index 0000000000..370ade1e27 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/ShimRecord.cs @@ -0,0 +1,60 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.domain +{ + public class ShimRecord + { + /// + /// The exe file from the shim directory. + /// + public string ExeFile { get; set; } + + /// + /// The package name (could be empty). + /// + public string PackageName { get; set; } + + /// + /// The file being shimmed (could be empty). + /// + public string TargetFile { get; set; } + + /// + /// Creates a ShimRecord instance. + /// + /// The exe file from the shim directory. + public ShimRecord(string exeFile) + { + ExeFile = exeFile; + PackageName = string.Empty; + TargetFile = string.Empty; + } + + /// + /// Creates a ShimRecord instance. + /// + /// The exe file from the shim directory. + /// The package name. + /// The file being shimmed. + public ShimRecord(string exeFile, string packageName, string targetFile) + { + ExeFile = exeFile; + PackageName = packageName; + TargetFile = targetFile; + } + } +} diff --git a/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs b/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs new file mode 100644 index 0000000000..7f05f23795 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs @@ -0,0 +1,308 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.domain +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using filesystem; + + public class ShimRegistry + { + private readonly IFileSystem _fileSystem; + private readonly string _shimsLocation; + private readonly string _packagesLocation; + private readonly string _pathRoot; + private readonly Regex _packageRegex; + private ShimStore _store; + + /// + /// The level required when accessing the data. + /// + public enum DataLevel + { + Current, + Latest + } + + /// + /// Creates a ShimRegistry instance. + /// + /// The file system. + public ShimRegistry(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + _shimsLocation = ApplicationParameters.ShimsLocation; + _packagesLocation = ApplicationParameters.PackagesLocation; + _pathRoot = Path.GetPathRoot(_shimsLocation); + _packageRegex = new Regex(@"{0}\\(.[^\\]+)\\".format_with(_packagesLocation.Replace(@"\", @"\\")), + RegexOptions.Compiled | RegexOptions.IgnoreCase); + } + + /// + /// Adds a shim to the store. + /// + /// The shim exe file. + /// The package name. + /// The file being shimmed. + public void add(string exeFile, string packageName, string targetFile) + { + ensure_storage_exists(DataLevel.Current); + var record = new ShimRecord(exeFile, packageName, targetFile); + _store.add_record(record); + } + + /// + /// Gets a shim record from the store it if exists. + /// + /// The shim exe file. + /// The record or null. + public ShimRecord get(string exeFile) + { + ensure_storage_exists(DataLevel.Current); + return _store.get_record(exeFile); + } + + /// + /// Updates the store from the file system. + /// + public void create_snapshot() + { + ensure_storage_exists(DataLevel.Latest); + } + + /// + /// Returns all shim records for the package. + /// + /// The package name. + /// The shim records. + public IList get_all(string packageName) + { + ensure_storage_exists(DataLevel.Latest); + var exeFiles = get_shim_exe_files(); + + return _store.get_all_records(packageName); + } + + /// + /// Returns all shim records for the package that have not been modified. + /// + /// The package name. + /// The unmodified shim records. + public IList get_snapshot(string packageName) + { + ensure_storage_exists(DataLevel.Current); + var exeFiles = get_shim_exe_files(); + + return _store.get_snapshot_records(exeFiles, packageName); + } + + /// + /// Removes a shim from the store. + /// + /// The shim exe file. + public void remove(string exeFile) + { + ensure_storage_exists(DataLevel.Current); + _store.remove_record(exeFile); + } + + /// + /// Initializes or updates the store. + /// + /// The data level required. + private void ensure_storage_exists(DataLevel level) + { + if (_store == null) + { + init_store(); + } + else if (level == DataLevel.Latest) + { + update_store(); + } + } + + /// + /// Creates and initializes the store from the file system. + /// + private void init_store() + { + _store = new ShimStore(_fileSystem); + var exeFiles = get_shim_exe_files(); + + foreach (string file in exeFiles.or_empty_list_if_null()) + { + _store.add_record(get_shim_data(file)); + } + } + + /// + /// Updates the store from the file system. + /// + private void update_store() + { + var exeFiles = get_shim_exe_files(); + + foreach (string file in _store.get_files_to_update(exeFiles)) + { + _store.add_record(get_shim_data(file)); + } + } + + /// + /// Gets the exe files in the shim directory. + /// + /// The exe files. + private IEnumerable get_shim_exe_files() + { + return _fileSystem.get_files(_shimsLocation, pattern: "*.exe"); + } + + /// + /// Extracts any shim data from an exe file. + /// + /// The exe file + /// A record containing full or partial shim data. + private ShimRecord get_shim_data(string exeFile) + { + var item = new ShimRecord(exeFile); + + // check the exe is shimgen generated from the file info + FileVersionInfo info = FileVersionInfo.GetVersionInfo(exeFile); + + // note that this will not catch chocolatey specific shims + if (!info.ProductName.contains("shimgen")) return item; + + // extract the target file from the binary + string target = find_shim_target(exeFile); + + item.TargetFile = check_and_get_targetFile(target); + + // try and extract the package name + if (item.TargetFile.contains(_packagesLocation)) + { + var match = _packageRegex.Match(item.TargetFile).Groups[1]; + if (match != null) + { + item.PackageName = match.Value; + } + } + + return item; + } + + /// + /// Searches an exe file for the shim target file name. + /// + /// The exe file. + /// The target file name or null. + private string find_shim_target(string exeFile) + { + string target = null; + byte[] bytes = File.ReadAllBytes(exeFile); + + // search for the pattern: file at '' + int startIndex = 0; + string pattern = "file at '"; + int foundIndex = search_binary(bytes, pattern, startIndex); + + if (foundIndex == -1) + { + return target; + } + + // search for the closing single-quote + startIndex = foundIndex + (pattern.Length * 2); + pattern = "'"; + foundIndex = search_binary(bytes, pattern, startIndex); + + if (foundIndex != -1) + { + int count = foundIndex - startIndex; + var slice = new byte[count]; + Array.Copy(bytes, startIndex, slice, 0, count); + target = Encoding.Unicode.GetString(slice); + } + + return target; + } + + /// + /// Searches an array of bytes for a pattern. + /// + /// The array of bytes. + /// The pattern to search for. + /// The index to start searching from. + /// The index of the pattern or -1. + private int search_binary(byte[] bytes, string search, int startIndex) + { + int foundIndex = -1; + byte[] pattern = Encoding.Unicode.GetBytes(search); + int end = bytes.Length - pattern.Length + 1; + int i = startIndex; + int m = 0; + + while (i < end) + { + if (bytes[i] == pattern[m]) + { + ++m; + if (m == pattern.Length) { + foundIndex = i - pattern.Length + 1; + break; + } + } + else if (m > 0) + { + i -= m; + m = 0; + } + ++i; + } + + return foundIndex; + } + + /// + /// Checks that the extracted file name is valid. + /// + /// The target file name. + /// The fully qualified path or an empty string. + private string check_and_get_targetFile(string target) + { + if (string.IsNullOrEmpty(target)) return string.Empty; + + if (!target.Replace('/', '\\').StartsWith(_pathRoot, StringComparison.OrdinalIgnoreCase)) + { + target = _fileSystem.combine_paths(_packagesLocation, target); + } + + try + { + return _fileSystem.get_full_path(target); + } + catch + { + return string.Empty; + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/domain/ShimStore.cs b/src/chocolatey/infrastructure.app/domain/ShimStore.cs new file mode 100644 index 0000000000..ce1f3ae335 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/ShimStore.cs @@ -0,0 +1,260 @@ +// Copyright © 2017 - 2018 Chocolatey Software, Inc +// Copyright © 2011 - 2017 RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.domain +{ + using System; + using System.Collections.Generic; + using filesystem; + + public class ShimStore + { + private readonly IFileSystem _fileSystem; + private readonly IDictionary _items; + + public class BaseRecord + { + /// + /// The exe file from the shim directory. + /// + public string ExeFile { get; set; } + + /// + /// The package name (could be empty). + /// + public string PackageName { get; set; } + + /// + /// The file being shimmed (could be empty). + /// + public string TargetFile { get; set; } + + /// + /// ExeFile last modification time. + /// + public Int64 LastModified { get; set; } + + /// + /// Internal flag for synchronizing. + /// + public bool Current { get; set; } + + /// + /// Creates a BaseRecord instance. + /// + /// The data record. + /// Last modification time of the exe file. + public BaseRecord(ShimRecord record, Int64 lastModified) + { + ExeFile = record.ExeFile; + PackageName = record.PackageName; + TargetFile = record.TargetFile; + LastModified = lastModified; + Current = false; + } + } + + /// + /// Creates a ShimStore instance. + /// + /// The filesystem. + public ShimStore(IFileSystem filesystem) + { + _fileSystem = filesystem; + _items = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Adds a record to the store. + /// + /// The record. + public void add_record(ShimRecord record) + { + string key = get_key(record.ExeFile); + _items[key] = new BaseRecord(record, get_timestamp(record.ExeFile)); + } + + /// + /// Removes a record from the store. + /// + /// The exe file to remove. + public void remove_record(string exeFile) + { + string key = get_key(exeFile); + _items.Remove(key); + } + + /// + /// Gets a record from the store if it exsists. + /// + /// The exe file to get + /// The record or null. + public ShimRecord get_record(string exeFile) + { + BaseRecord record; + if (_items.TryGetValue(get_key(exeFile), out record)) + { + return new ShimRecord(record.ExeFile, record.PackageName, record.TargetFile); + } + + return null; + } + + /// + /// Synchronizes the store with the file system. + /// + /// The exe files in the shims directory. + /// A list of exe files that need adding or updating. + public IList get_files_to_update(IEnumerable exeFiles) + { + var updates = new List(); + BaseRecord record; + + // set found records as current and collect new or out-of-date ones + foreach (string file in exeFiles.or_empty_list_if_null()) + { + if (_items.TryGetValue(get_key(file), out record)) + { + if (get_timestamp(file) > record.LastModified) + { + updates.Add(file); + } + + record.Current = true; + } + else + { + updates.Add(file); + } + } + + remove_non_current(); + + return updates; + } + + /// + /// Returns all records for the package. + /// + /// The package name. + /// The list of records. + public IList get_all_records(string forPackage) + { + var records = new List(); + var values = _items.Values; + + foreach (BaseRecord record in values) + { + if (record.PackageName.Equals(forPackage, StringComparison.OrdinalIgnoreCase)) + { + records.Add(get_record_from_base(record)); + } + } + + return records; + } + + /// + /// Returns all records for the package that have not been modified. + /// + /// The exe files in the shims directory. + /// The package name. + /// The list of unmodified records. + public IList get_snapshot_records(IEnumerable exeFiles, string forPackage) + { + var records = new List(); + BaseRecord record; + + // set found records as current and collect non-changed items + foreach (string file in exeFiles.or_empty_list_if_null()) + { + if (_items.TryGetValue(get_key(file), out record)) + { + if (record.PackageName.Equals(forPackage, StringComparison.OrdinalIgnoreCase)) + { + if (get_timestamp(file) == record.LastModified) + { + records.Add(get_record_from_base(record)); + } + + } + + record.Current = true; + } + } + + remove_non_current(); + + return records; + } + + /// + /// Gets the storage key. + /// + /// The exe file. + /// The file name of the exe file. + private string get_key(string exeFile) + { + return _fileSystem.get_file_name(exeFile); + } + + /// + /// Create a ShimRecord from an internal base record. + /// + /// The internal base record. + /// The ShimRecord. + private ShimRecord get_record_from_base(BaseRecord record) + { + return new ShimRecord(record.ExeFile, record.PackageName, record.TargetFile); + } + + /// + /// Gets the timestamp of a file. + /// + /// The exe file. + /// The last modified UTC time. + private Int64 get_timestamp(string exeFile) + { + return _fileSystem.get_file_modified_date(exeFile).ToFileTimeUtc(); + } + + /// + /// Removes records marked as not current and resets the property. + /// + /// Only call this after setting the current records to true. + private void remove_non_current() + { + var removed = new List(); + + // collect the non-current records and reset property + foreach (KeyValuePair record in _items) + { + if (!record.Value.Current) + { + removed.Add(record.Key); + } + + record.Value.Current = false; + } + + // remove the non-current records + foreach (string key in removed) + { + _items.Remove(key); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 0b4295d93d..231f577237 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -344,6 +344,9 @@ public virtual void handle_package_result(PackageResult packageResult, Chocolate if (packageResult.Success && config.Information.PlatformType == PlatformType.Windows) { + // take the snapshot before running any install/uninstall scripts + _shimgenService.take_snapshot(); + if (!config.SkipPackageInstallProvider) { var installersBefore = _registryService.get_installer_keys(); diff --git a/src/chocolatey/infrastructure.app/services/IShimGenerationService.cs b/src/chocolatey/infrastructure.app/services/IShimGenerationService.cs index c6916ed5c6..2878021490 100644 --- a/src/chocolatey/infrastructure.app/services/IShimGenerationService.cs +++ b/src/chocolatey/infrastructure.app/services/IShimGenerationService.cs @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.services { + using System.Collections.Generic; using configuration; using results; @@ -34,5 +35,10 @@ public interface IShimGenerationService /// The configuration. /// The package result. void uninstall(ChocolateyConfiguration configuration, PackageResult packageResult); + + /// + /// Takes a snapshot of the existing shimgens + /// + void take_snapshot(); } } diff --git a/src/chocolatey/infrastructure.app/services/PowershellService.cs b/src/chocolatey/infrastructure.app/services/PowershellService.cs index 653c22d5b7..faeb4341f6 100644 --- a/src/chocolatey/infrastructure.app/services/PowershellService.cs +++ b/src/chocolatey/infrastructure.app/services/PowershellService.cs @@ -398,11 +398,14 @@ public void prepare_powershell_environment(IPackage package, ChocolateyConfigura Environment.SetEnvironmentVariable("chocolateyChecksumType64", null); Environment.SetEnvironmentVariable("chocolateyForceX86", null); Environment.SetEnvironmentVariable("DownloadCacheAvailable", null); + Environment.SetEnvironmentVariable("chocolateyNoShims", null); + + var isDependency = PackageUtility.package_is_a_dependency(configuration, package.Id); // we only want to pass the following args to packages that would apply. // like choco install git --params '' should pass those params to git.install, // but not another package unless the switch apply-install-arguments-to-dependencies is used - if (!PackageUtility.package_is_a_dependency(configuration, package.Id) || configuration.ApplyInstallArgumentsToDependencies) + if (!isDependency || configuration.ApplyInstallArgumentsToDependencies) { this.Log().Debug(ChocolateyLoggers.Verbose, "Setting installer args for {0}".format_with(package.Id)); Environment.SetEnvironmentVariable("installArguments", configuration.InstallArguments); @@ -417,13 +420,20 @@ public void prepare_powershell_environment(IPackage package, ChocolateyConfigura // we only want to pass package parameters to packages that would apply. // but not another package unless the switch apply-package-parameters-to-dependencies is used - if (!PackageUtility.package_is_a_dependency(configuration, package.Id) || configuration.ApplyPackageParametersToDependencies) + if (!isDependency || configuration.ApplyPackageParametersToDependencies) { this.Log().Debug(ChocolateyLoggers.Verbose, "Setting package parameters for {0}".format_with(package.Id)); Environment.SetEnvironmentVariable("packageParameters", configuration.PackageParameters); Environment.SetEnvironmentVariable("chocolateyPackageParameters", configuration.PackageParameters); } + // we only want to disable shimming for packages that would apply. + if (configuration.NoShimsGlobal || (!isDependency && configuration.NoShims)) + { + this.Log().Debug(ChocolateyLoggers.Verbose, "Setting noshims for {0}".format_with(package.Id)); + Environment.SetEnvironmentVariable("chocolateyNoShims", "true"); + } + if (!string.IsNullOrWhiteSpace(configuration.DownloadChecksum)) { Environment.SetEnvironmentVariable("chocolateyChecksum32", configuration.DownloadChecksum); diff --git a/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs b/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs index 01d6991b24..24faecfbf6 100644 --- a/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs +++ b/src/chocolatey/infrastructure.app/services/ShimGenerationService.cs @@ -1,13 +1,13 @@ // Copyright © 2017 - 2018 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. -// +// // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,6 +21,8 @@ namespace chocolatey.infrastructure.app.services using System.IO; using configuration; using filesystem; + using infrastructure.app.domain; + using infrastructure.app.utility; using infrastructure.commands; using results; @@ -33,13 +35,14 @@ public class ShimGenerationService : IShimGenerationService private const string OUTPUT_TOKEN = "{{output}}"; private readonly string _shimGenExePath = ApplicationParameters.Tools.ShimGenExe; private readonly IDictionary _shimGenArguments = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - + private readonly ShimRegistry _registry; public ShimGenerationService(IFileSystem fileSystem, ICommandExecutor commandExecutor) { _fileSystem = fileSystem; _commandExecutor = commandExecutor; set_shimgen_args_dictionary(); + _registry = new ShimRegistry(_fileSystem); } /// @@ -87,15 +90,54 @@ public void install(ChocolateyConfiguration configuration, PackageResult package return; } - //gather all .exes in the folder - var exeFiles = _fileSystem.get_files(packageResult.InstallLocation, pattern: "*.exe", option: SearchOption.AllDirectories); - foreach (string file in exeFiles.or_empty_list_if_null()) + var existingShims = _registry.get_snapshot(packageResult.Name); + var noShims = is_shimming_disabled(configuration, packageResult); + + if (noShims) + { + this.Log().Debug(() => "Automatic package shimming disabled for {0}".format_with(packageResult.Name)); + } + + if (existingShims.Count > 0) + { + this.Log().Debug(() => "Removing previous shims for {0}".format_with(packageResult.Name)); + uninstall_shims(existingShims); + } + + if (!noShims) + { + //gather all relevant .exes in the folder + var exeFiles = get_exe_files_to_shim(packageResult.InstallLocation); + install_shims(configuration, packageResult.Name, exeFiles); + } + } + + public void uninstall(ChocolateyConfiguration configuration, PackageResult packageResult) + { + var existingShims = _registry.get_all(packageResult.Name); + uninstall_shims(existingShims); + } + + public void take_snapshot() + { + _registry.create_snapshot(); + } + + /// + /// Installs shimgens for the package + /// + /// The configuration. + /// The package name. + /// The exe files to shim + private void install_shims(ChocolateyConfiguration configuration, string packageName, IList exeFiles) + { + var args = ExternalCommandArgsBuilder.build_arguments(configuration, _shimGenArguments); + + foreach (string file in exeFiles) { - if (_fileSystem.file_exists(file + ".ignore")) continue; bool isGui = _fileSystem.file_exists(file + ".gui"); //todo: v2 be able to determine gui automatically - var args = ExternalCommandArgsBuilder.build_arguments(configuration, _shimGenArguments); var shimLocation = _fileSystem.combine_paths(ApplicationParameters.ShimsLocation, _fileSystem.get_file_name(file)); var argsForPackage = args.Replace(PATH_TOKEN, file.Replace(ApplicationParameters.InstallLocation, "..\\")).Replace(OUTPUT_TOKEN, shimLocation).Replace(ICON_PATH_TOKEN, file); if (isGui) @@ -103,18 +145,28 @@ public void install(ChocolateyConfiguration configuration, PackageResult package argsForPackage += " --gui"; } + // check for an existing shim + ShimRecord record = _registry.get(shimLocation); + if (record != null) + { + this.Log().Error(() => " ShimGen cannot overwrite shim {0} in {1} package".format_with(_fileSystem.get_file_name(file), record.PackageName)); + this.Log().Debug(() => " Shim: {0}{1} Target: {2}{1} New target: {3}{1}".format_with(shimLocation, Environment.NewLine, record.TargetFile, file)); + Environment.ExitCode = 1; + continue; + } + var exitCode = _commandExecutor.execute( _shimGenExePath, argsForPackage, configuration.CommandExecutionTimeoutSeconds, (s, e) => - { - if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Debug(() => " [ShimGen] {0}".format_with(e.Data.escape_curly_braces())); - }, + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Debug(() => " [ShimGen] {0}".format_with(e.Data.escape_curly_braces())); + }, (s, e) => - { - if (string.IsNullOrWhiteSpace(e.Data)) return; - this.Log().Error(() => " [ShimGen] {0}".format_with(e.Data.escape_curly_braces())); - }, + { + if (string.IsNullOrWhiteSpace(e.Data)) return; + this.Log().Error(() => " [ShimGen] {0}".format_with(e.Data.escape_curly_braces())); + }, updateProcessPath: true ); @@ -124,24 +176,67 @@ public void install(ChocolateyConfiguration configuration, PackageResult package } else { + _registry.add(shimLocation, packageName, file); this.Log().Info(() => " ShimGen has successfully created a {0}shim for {1}".format_with(isGui ? "gui " : string.Empty, _fileSystem.get_file_name(file))); - this.Log().Debug(() => " Created: {0}{1} Targeting: {2}{1} IsGui:{3}{1}".format_with(shimLocation, Environment.NewLine, file, isGui)); + this.Log().Debug(() => " Created: {0}{1} Targeting: {2}{1} IsGui: {3}{1}".format_with(shimLocation, Environment.NewLine, file, isGui)); } } } - public void uninstall(ChocolateyConfiguration configuration, PackageResult packageResult) + /// + /// Uninstalls shimgens for the package + /// + /// The shim records for the package. + private void uninstall_shims(IList existingShims) { - //gather all .exes in the folder - var exeFiles = _fileSystem.get_files(packageResult.InstallLocation, pattern: "*.exe", option: SearchOption.AllDirectories); - foreach (string file in exeFiles.or_empty_list_if_null()) + foreach (ShimRecord record in existingShims) { - if (_fileSystem.file_exists(file + ".ignore")) continue; + this.Log().Debug(() => "Removing shim {0} targetting '{1}'".format_with(_fileSystem.get_file_name(record.ExeFile), record.TargetFile)); + _fileSystem.delete_file(record.ExeFile); - var shimLocation = _fileSystem.combine_paths(ApplicationParameters.ShimsLocation, _fileSystem.get_file_name(file)); - this.Log().Debug(() => "Removing shim for {0} at '{1}".format_with(_fileSystem.get_file_name(file), shimLocation)); - _fileSystem.delete_file(shimLocation); + if (!_fileSystem.file_exists(record.ExeFile)) + { + _registry.remove(record.ExeFile); + } } } + + /// + /// Returns the exe files to shim + /// + /// The package install location. + /// + private IList get_exe_files_to_shim(string installLocation) + { + var exeFiles = new List(); + + if (!_fileSystem.directory_exists(installLocation) || installLocation.is_equal_to(ApplicationParameters.InstallLocation) || installLocation.is_equal_to(ApplicationParameters.PackagesLocation)) + { + return exeFiles; + } + + var targetFiles = _fileSystem.get_files(installLocation, pattern: "*.exe", option: SearchOption.AllDirectories); + foreach (string file in targetFiles.or_empty_list_if_null()) + { + if (!_fileSystem.file_exists(file + ".ignore")) + { + exeFiles.Add(file); + } + } + + return exeFiles; + } + + /// + /// Checks if shimming is disabled for the package + /// + /// The configuration. + /// The package result. + /// true if shimming is disabled + private bool is_shimming_disabled(ChocolateyConfiguration configuration, PackageResult packageResult) + { + return configuration.NoShimsGlobal || (configuration.NoShims + && !PackageUtility.package_is_a_dependency(configuration, packageResult.Package.Id)); + } } -} \ No newline at end of file +} From 450f21f09cfe46e9a2397ce5eb74fa1aadb8ab0f Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 20 Jun 2020 14:21:05 +0100 Subject: [PATCH 2/4] Remove unused variable --- src/chocolatey/infrastructure.app/domain/ShimRegistry.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs b/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs index 7f05f23795..2df0f79600 100644 --- a/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs +++ b/src/chocolatey/infrastructure.app/domain/ShimRegistry.cs @@ -96,8 +96,6 @@ public void create_snapshot() public IList get_all(string packageName) { ensure_storage_exists(DataLevel.Latest); - var exeFiles = get_shim_exe_files(); - return _store.get_all_records(packageName); } From b4963903d91cfa5f756ca1cb2aa584074ea6d426 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 20 Jun 2020 19:16:05 +0100 Subject: [PATCH 3/4] Test upgrade with noshims on existing shim --- Scenarios.md | 8 ++- .../scenarios/UpgradeScenarios.cs | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/Scenarios.md b/Scenarios.md index 5633c3cce7..59c08a3322 100644 --- a/Scenarios.md +++ b/Scenarios.md @@ -720,7 +720,7 @@ * should throw an error that it is not allowed -### ChocolateyUpgradeCommand [ 39 Scenario(s), 301 Observation(s) ] +### ChocolateyUpgradeCommand [ 40 Scenario(s), 304 Observation(s) ] #### when force upgrading a package @@ -993,6 +993,12 @@ * should have original shim * should not create a shim +#### when upgrading a package with shims with noshims + + * should have shim target + * should not create a shim + * should not have original shim + #### when upgrading a package with unavailable dependencies * should contain a message that it was unable to upgrade anything diff --git a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs index a39d317f41..600afcac78 100644 --- a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs @@ -3298,5 +3298,54 @@ public void should_create_a_shim() File.Exists(shimfile).ShouldBeTrue(); } } + + [Concern(typeof(ChocolateyUpgradeCommand))] + public class when_upgrading_a_package_with_shims_with_noshims : ScenariosBase + { + public override void Context() + { + Configuration = Scenario.upgrade(); + Scenario.reset(Configuration); + Service = NUnitSetup.Container.GetInstance(); + + Configuration.PackageNames = Configuration.Input = "shimupgrade"; + Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); + Scenario.install_package(Configuration, "shimupgrade", "1.0.0"); + + // version 3.0.0 is okay + Configuration.Version = "3.0.0"; + Configuration.NoShims = true; + } + + public override void Because() + { + Service.upgrade_run(Configuration); + } + + [Fact] + public void should_have_shim_target() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var target = Path.Combine(packageDir, "tools", "shimupgrade3.exe"); + + File.Exists(target).ShouldBeTrue(); + } + + [Fact] + public void should_not_have_original_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade1.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + + [Fact] + public void should_not_create_a_shim() + { + var shimfile = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade3.exe"); + + File.Exists(shimfile).ShouldBeFalse(); + } + } } } From f8a2bcffaa0093ca69de7a1357654cf39909a3e9 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 20 Jun 2020 20:42:35 +0100 Subject: [PATCH 4/4] Make upgrade scenarios more robust --- Scenarios.md | 5 ++- .../scenarios/UpgradeScenarios.cs | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Scenarios.md b/Scenarios.md index 59c08a3322..011ebfa259 100644 --- a/Scenarios.md +++ b/Scenarios.md @@ -720,7 +720,7 @@ * should throw an error that it is not allowed -### ChocolateyUpgradeCommand [ 40 Scenario(s), 304 Observation(s) ] +### ChocolateyUpgradeCommand [ 40 Scenario(s), 307 Observation(s) ] #### when force upgrading a package @@ -986,6 +986,8 @@ #### when upgrading a package with shims * should create a shim + * should have had original shim + * should have shim target * should not have original shim #### when upgrading a package with shims that errors @@ -995,6 +997,7 @@ #### when upgrading a package with shims with noshims + * should have had original shim * should have shim target * should not create a shim * should not have original shim diff --git a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs index 600afcac78..b4ac0752a8 100644 --- a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs @@ -3263,6 +3263,9 @@ public void should_not_create_a_shim() [Concern(typeof(ChocolateyUpgradeCommand))] public class when_upgrading_a_package_with_shims : ScenariosBase { + private string _original_shim = string.Empty; + private bool _original_shim_existed = false; + public override void Context() { Configuration = Scenario.upgrade(); @@ -3273,6 +3276,9 @@ public override void Context() Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); Scenario.install_package(Configuration, "shimupgrade", "1.0.0"); + _original_shim = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade1.exe"); + _original_shim_existed = File.Exists(_original_shim); + // version 3.0.0 is okay Configuration.Version = "3.0.0"; } @@ -3282,6 +3288,23 @@ public override void Because() Service.upgrade_run(Configuration); } + [Fact] + public void should_have_had_original_shim() + { + var errorMessage = "Original shim file was not created: {0}".format_with(_original_shim); + + _original_shim_existed.ShouldBeTrue(errorMessage); + } + + [Fact] + public void should_have_shim_target() + { + var packageDir = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames); + var target = Path.Combine(packageDir, "tools", "shimupgrade3.exe"); + + File.Exists(target).ShouldBeTrue(); + } + [Fact] public void should_not_have_original_shim() { @@ -3302,6 +3325,9 @@ public void should_create_a_shim() [Concern(typeof(ChocolateyUpgradeCommand))] public class when_upgrading_a_package_with_shims_with_noshims : ScenariosBase { + private string _original_shim = string.Empty; + private bool _original_shim_existed = false; + public override void Context() { Configuration = Scenario.upgrade(); @@ -3312,6 +3338,9 @@ public override void Context() Scenario.add_packages_to_source_location(Configuration, Configuration.Input + "*" + Constants.PackageExtension); Scenario.install_package(Configuration, "shimupgrade", "1.0.0"); + _original_shim = Path.Combine(Scenario.get_top_level(), "bin", "shimupgrade1.exe"); + _original_shim_existed = File.Exists(_original_shim); + // version 3.0.0 is okay Configuration.Version = "3.0.0"; Configuration.NoShims = true; @@ -3322,6 +3351,14 @@ public override void Because() Service.upgrade_run(Configuration); } + [Fact] + public void should_have_had_original_shim() + { + var errorMessage = "Original shim file was not created: {0}".format_with(_original_shim); + + _original_shim_existed.ShouldBeTrue(errorMessage); + } + [Fact] public void should_have_shim_target() {