diff --git a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs index 8ff44c3c2..697675b5a 100644 --- a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs +++ b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs @@ -37,5 +37,6 @@ public ChocolateyPackageInformation(IPackageMetadata package) public bool IsPinned { get; set; } public string ExtraInformation { get; set; } public string DeploymentLocation { get; set; } + public string SourceInstalledFrom { get; set; } } } diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs index 882c51eb1..368232522 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs @@ -45,6 +45,7 @@ public class ChocolateyPackageInformationService : IChocolateyPackageInformation private const string ExtraFile = ".extra"; private const string VersionOverrideFile = ".version"; private const string DeploymentLocationFile = ".deploymentLocation"; + private const string SourceInstalledFromFile = ".sourceInstalledFrom"; // We need to store the package identifiers we have warned about // to prevent duplicated outputs. @@ -194,6 +195,20 @@ has errored attempting to read it. This file will be renamed to ); } + var sourceInstalledFromFile = _fileSystem.CombinePaths(pkgStorePath, SourceInstalledFromFile); + if (_fileSystem.FileExists(sourceInstalledFromFile)) + { + FaultTolerance.TryCatchWithLoggingException( + () => + { + packageInformation.SourceInstalledFrom = _fileSystem.ReadFile(sourceInstalledFromFile); + }, + "Unable to read package installed from file", + throwError: false, + logWarningInsteadOfError: true + ); + } + return packageInformation; } @@ -310,6 +325,21 @@ public void Save(ChocolateyPackageInformation packageInformation) { _fileSystem.DeleteFile(_fileSystem.CombinePaths(pkgStorePath, DeploymentLocationFile)); } + + if (!string.IsNullOrWhiteSpace(packageInformation.SourceInstalledFrom)) + { + var sourceInstalledFromFile = _fileSystem.CombinePaths(pkgStorePath, SourceInstalledFromFile); + if (_fileSystem.FileExists(sourceInstalledFromFile)) + { + _fileSystem.DeleteFile(sourceInstalledFromFile); + } + + _fileSystem.WriteFile(sourceInstalledFromFile, packageInformation.SourceInstalledFrom); + } + else + { + _fileSystem.DeleteFile(_fileSystem.CombinePaths(pkgStorePath, SourceInstalledFromFile)); + } } public void Remove(IPackageMetadata package) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 317d27b1f..14742a25a 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -565,6 +565,7 @@ public virtual void HandlePackageResult(PackageResult packageResult, ChocolateyC } pkgInfo.DeploymentLocation = Environment.GetEnvironmentVariable(EnvironmentVariables.Package.ChocolateyPackageInstallLocation); + pkgInfo.SourceInstalledFrom = packageResult.SourceInstalledFrom; UpdatePackageInformation(pkgInfo); EnsureBadPackagesPathIsClean(packageResult); diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 5f5690519..9258522f4 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -217,6 +217,9 @@ it is possible that incomplete package lists are returned from a command ChocolateyPackageMetadata packageLocalMetadata; string packageInstallLocation = null; + string deploymentlocation = null; + string sourceInstalledFrom = null; + if (package.PackagePath != null && !string.IsNullOrWhiteSpace(package.PackagePath)) { packageLocalMetadata = new ChocolateyPackageMetadata(package.PackagePath, _fileSystem); @@ -235,6 +238,8 @@ it is possible that incomplete package lists are returned from a command if (config.ListCommand.LocalOnly && packageLocalMetadata != null) { packageInfo = _packageInfoService.Get(packageLocalMetadata); + deploymentlocation = packageInfo.DeploymentLocation; + sourceInstalledFrom = packageInfo.SourceInstalledFrom; if (config.ListCommand.IncludeVersionOverrides) { @@ -294,7 +299,7 @@ Package url{6} Tags: {9} Software Site: {10} Software License: {11}{12}{13}{14}{15}{16} - Description: {17}{18}{19} + Description: {17}{18}{19}{20}{21} ".FormatWith( package.Title.EscapeCurlyBraces(), package.Published.GetValueOrDefault().UtcDateTime.ToShortDateString(), @@ -329,6 +334,8 @@ Package url{6} package.Summary != null && !string.IsNullOrWhiteSpace(package.Summary.ToStringSafe()) ? "\r\n Summary: {0}".FormatWith(package.Summary.EscapeCurlyBraces().ToStringSafe()) : string.Empty, package.Description.EscapeCurlyBraces().Replace("\n ", "\n").Replace("\n", "\n "), !string.IsNullOrWhiteSpace(package.ReleaseNotes.ToStringSafe()) ? "{0} Release Notes: {1}".FormatWith(Environment.NewLine, package.ReleaseNotes.EscapeCurlyBraces().Replace("\n ", "\n").Replace("\n", "\n ")) : string.Empty, + !string.IsNullOrWhiteSpace(deploymentlocation) ? "{0} Deployed to: '{1}'".FormatWith(Environment.NewLine, deploymentlocation) :string.Empty, + !string.IsNullOrWhiteSpace(sourceInstalledFrom) ? "{0} Source package was installed from: '{1}'".FormatWith(Environment.NewLine, sourceInstalledFrom) : string.Empty, packageArgumentsUnencrypted != null ? packageArgumentsUnencrypted : string.Empty )); } @@ -354,7 +361,7 @@ Package url{6} } else { - yield return new PackageResult(packageLocalMetadata, package, config.ListCommand.LocalOnly ? packageInstallLocation : null, config.Sources); + yield return new PackageResult(packageLocalMetadata, package, config.ListCommand.LocalOnly ? packageInstallLocation : null, config.Sources, null); } } @@ -994,7 +1001,7 @@ Version was specified as '{0}'. It is possible that version packageRemoteMetadata.PackageTestResultStatus == "Failing" && packageRemoteMetadata.IsDownloadCacheAvailable ? " - Likely broken for FOSS users (due to download location changes)" : packageRemoteMetadata.PackageTestResultStatus == "Failing" ? " - Possibly broken" : string.Empty )); - var packageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath)); + var packageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath, null, packageDependencyInfo.Source.ToStringSafe())); if (shouldAddForcedResultMessage) { packageResult.Messages.Add(new ResultMessage(ResultType.Note, "Backing up and removing old version")); @@ -1819,7 +1826,7 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon packageRemoteMetadata.PackageTestResultStatus == "Failing" && packageRemoteMetadata.IsDownloadCacheAvailable ? " - Likely broken for FOSS users (due to download location changes)" : packageRemoteMetadata.PackageTestResultStatus == "Failing" ? " - Possibly broken" : string.Empty )); - var upgradePackageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath)); + var upgradePackageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath, null, packageDependencyInfo.Source.ToStringSafe())); upgradePackageResult.ResetMetadata(packageMetadata, packageRemoteMetadata); upgradePackageResult.InstallLocation = installedPath; @@ -2895,7 +2902,7 @@ protected virtual void BackupAndRunBeforeModify( { "chocolatey".Log().Debug("Running beforeModify step for '{0}'", packageResult.PackageMetadata.Id); - var packageResultCopy = new PackageResult(packageResult.PackageMetadata, packageResult.SearchMetadata, packageResult.InstallLocation, packageResult.Source); + var packageResultCopy = new PackageResult(packageResult.PackageMetadata, packageResult.SearchMetadata, packageResult.InstallLocation, packageResult.Source, null); beforeModifyAction(packageResultCopy, config); diff --git a/src/chocolatey/infrastructure/results/PackageResult.cs b/src/chocolatey/infrastructure/results/PackageResult.cs index 95327340f..a64d5f8d7 100644 --- a/src/chocolatey/infrastructure/results/PackageResult.cs +++ b/src/chocolatey/infrastructure/results/PackageResult.cs @@ -41,13 +41,36 @@ public bool Warning get { return Messages.Any(x => x.MessageType == ResultType.Warn); } } + /// + /// Name of the package installed. + /// public string Name { get; private set; } + /// + /// Version of the package installed. + /// public string Version { get; private set; } + /// + /// Instance of representing the nuspec file in memory. + /// public IPackageMetadata PackageMetadata { get; private set; } + /// + /// Instance of representing the package data returned from a repository. + /// public IPackageSearchMetadata SearchMetadata { get; private set; } + /// + /// Location on disk that the package has been installed to. + /// public string InstallLocation { get; set; } + /// + /// Sources available during package installation. + /// public string Source { get; set; } + [Obsolete("This property is deprecated and will be removed in v3.")] public string SourceUri { get; set; } + /// + /// The package source used to install the package. + /// + public string SourceInstalledFrom { get; set; } public int ExitCode { get; set; } public void ResetMetadata(IPackageMetadata metadata, IPackageSearchMetadata search) @@ -58,13 +81,27 @@ public void ResetMetadata(IPackageMetadata metadata, IPackageSearchMetadata sear Version = metadata.Version.ToNormalizedStringChecked(); } - public PackageResult(IPackageMetadata packageMetadata, string installLocation, string source = null) : this(packageMetadata.Id, packageMetadata.Version.ToNormalizedStringChecked(), installLocation) + /// + /// Initializes a new instance of the class. + /// + /// Instance of representing the nuspec file in memory. Assigned to + /// Assigned to + /// Sources available during package installation. Assigned to + public PackageResult(IPackageMetadata packageMetadata, string installLocation, string source = null) + : this(packageMetadata.Id, packageMetadata.Version.ToNormalizedStringChecked(), installLocation) { PackageMetadata = packageMetadata; Source = source; } - public PackageResult(IPackageSearchMetadata packageSearch, string installLocation, string source = null) : this(packageSearch.Identity.Id, packageSearch.Identity.Version.ToNormalizedStringChecked(), installLocation) + /// + /// Initializes a new instance of the class. + /// + /// Instance of representing the package data returned from a repository. Assigned to + /// Assigned to + /// Sources available during package installation. Assigned to + public PackageResult(IPackageSearchMetadata packageSearch, string installLocation, string source = null) + : this(packageSearch.Identity.Id, packageSearch.Identity.Version.ToNormalizedStringChecked(), installLocation) { SearchMetadata = packageSearch; Source = source; @@ -103,10 +140,24 @@ public PackageResult(IPackageSearchMetadata packageSearch, string installLocatio */ } - public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source = null) : this(packageMetadata.Id, packageMetadata.Version.ToNormalizedStringChecked(), installLocation) + [Obsolete("This overload is deprecated and will be removed in v3.")] + public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source = null) + : this(packageMetadata, packageSearch, installLocation, source, null) { } + + /// + /// Initializes a new instance of the class. + /// + /// Instance of representing the nuspec file in memory. Assigned to + /// Instance of representing the package data returned from a repository. Assigned to + /// Assigned to + /// Sources available during package installation. Assigned to + /// The package source used to install the package. Assigned to + public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source, string sourceInstalledFrom) + : this(packageMetadata.Id, packageMetadata.Version.ToNormalizedStringChecked(), installLocation) { SearchMetadata = packageSearch; PackageMetadata = packageMetadata; + SourceInstalledFrom = sourceInstalledFrom; var sources = new List(); if (!string.IsNullOrEmpty(source)) { @@ -118,6 +169,8 @@ public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata pa { this.Log().Debug("Unable to determine sources from '{0}'. Using value as is.{1} {2}".FormatWith(source, Environment.NewLine, ex.ToStringSafe())); // source is already set above + // Note: Where above? This seems to be copied/pasted from the overload above, but forgot to actually set the source. + // While this seems like it may be incorrect, we do not currently know if this is an issue at all. return; } } @@ -125,6 +178,13 @@ public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata pa Source = sources.FirstOrDefault(uri => uri.IsFile || uri.IsUnc).ToStringSafe(); } + /// + /// Initializes a new instance of the class. + /// + /// Name of the package installed. Assigned to + /// Version of the package installed. Assigned to + /// Location on disk the package was installed to. Assigned to + /// Sources available during package installation. Assigned to public PackageResult(string name, string version, string installLocation, string source = null) { Name = name; diff --git a/tests/helpers/common/Chocolatey/Initialize-ChocolateyTestInstall.ps1 b/tests/helpers/common/Chocolatey/Initialize-ChocolateyTestInstall.ps1 index 9019d35ae..e662af7fe 100644 --- a/tests/helpers/common/Chocolatey/Initialize-ChocolateyTestInstall.ps1 +++ b/tests/helpers/common/Chocolatey/Initialize-ChocolateyTestInstall.ps1 @@ -28,6 +28,9 @@ $null = robocopy $env:ChocolateyInstall/lib/chocolatey $Directory/lib/chocolatey /MIR $null = robocopy $env:ChocolateyInstall/lib/chocolatey.extension $Directory/lib/chocolatey.extension /MIR $null = robocopy $env:ChocolateyInstall/lib/chocolatey-agent $Directory/lib/chocolatey-agent /MIR + Get-ChildItem $env:ChocolateyInstall/lib -Filter chocolatey-license-* | ForEach-Object { + $null = robocopy "$($_.FullName)" "$Directory/lib/$($_.Name)" /MIR + } $env:ChocolateyInstall = $Directory Set-ChocolateyTestLocation -Directory $Directory diff --git a/tests/pester-tests/commands/choco-info.Tests.ps1 b/tests/pester-tests/commands/choco-info.Tests.ps1 index de12208bc..8a5e77cc0 100644 --- a/tests/pester-tests/commands/choco-info.Tests.ps1 +++ b/tests/pester-tests/commands/choco-info.Tests.ps1 @@ -34,6 +34,27 @@ } } + Context "Should display source and deployment locations when using --local-only" { + BeforeAll { + Initialize-ChocolateyTestInstall -Source $PSScriptRoot\testpackages + + $Setup = Invoke-Choco install installpackage + + $Setup.ExitCode | Should -Be 0 -Because $Setup.String + + $Output = Invoke-Choco info installpackage --local-only + } + + It "Exits with Success (0)" { + $Output.ExitCode | Should -Be 0 -Because $Output.String + } + + It "Should contain source and deployment location summary" { + $Output.Lines | Should -Contain "Source package was installed from: $PSScriptRoot\testpackages" -Because $Output.String + $Output.String | Should -Match "Deployed to:" + } + } + Context "Should include configured sources" { BeforeAll { Initialize-ChocolateyTestInstall -Source $PSScriptRoot\testpackages