diff --git a/src/dotnet/CommonLocalizableStrings.resx b/src/dotnet/CommonLocalizableStrings.resx index 77847a864..236b2fd26 100644 --- a/src/dotnet/CommonLocalizableStrings.resx +++ b/src/dotnet/CommonLocalizableStrings.resx @@ -619,6 +619,9 @@ setx PATH "%PATH%;{0}" Tool '{0}' (version '{1}') is already installed. + + Failed to find staged tool package '{0}'. + Column maximum width must be greater than zero. diff --git a/src/dotnet/ToolPackage/IToolPackage.cs b/src/dotnet/ToolPackage/IToolPackage.cs index 3ee968cb9..c769b002d 100644 --- a/src/dotnet/ToolPackage/IToolPackage.cs +++ b/src/dotnet/ToolPackage/IToolPackage.cs @@ -4,14 +4,15 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.ToolPackage { internal interface IToolPackage { - string PackageId { get; } + PackageId Id { get; } - string PackageVersion { get; } + NuGetVersion Version { get; } DirectoryPath PackageDirectory { get; } diff --git a/src/dotnet/ToolPackage/IToolPackageInstaller.cs b/src/dotnet/ToolPackage/IToolPackageInstaller.cs index d8501d22d..c6ef964e1 100644 --- a/src/dotnet/ToolPackage/IToolPackageInstaller.cs +++ b/src/dotnet/ToolPackage/IToolPackageInstaller.cs @@ -4,14 +4,15 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.ToolPackage { internal interface IToolPackageInstaller { IToolPackage InstallPackage( - string packageId, - string packageVersion = null, + PackageId packageId, + VersionRange versionRange = null, string targetFramework = null, FilePath? nugetConfig = null, string source = null, diff --git a/src/dotnet/ToolPackage/IToolPackageStore.cs b/src/dotnet/ToolPackage/IToolPackageStore.cs index 72105d24d..4e3e88c65 100644 --- a/src/dotnet/ToolPackage/IToolPackageStore.cs +++ b/src/dotnet/ToolPackage/IToolPackageStore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.ToolPackage { @@ -11,6 +12,18 @@ namespace Microsoft.DotNet.ToolPackage { DirectoryPath Root { get; } - IEnumerable GetInstalledPackages(string packageId = null); + DirectoryPath GetRandomStagingDirectory(); + + NuGetVersion GetStagedPackageVersion(DirectoryPath stagingDirectory, PackageId packageId); + + DirectoryPath GetRootPackageDirectory(PackageId packageId); + + DirectoryPath GetPackageDirectory(PackageId packageId, NuGetVersion version); + + IEnumerable EnumeratePackages(); + + IEnumerable EnumeratePackageVersions(PackageId packageId); + + IToolPackage GetPackage(PackageId packageId, NuGetVersion version); } } diff --git a/src/dotnet/ToolPackage/PackageId.cs b/src/dotnet/ToolPackage/PackageId.cs new file mode 100644 index 000000000..07846bf7a --- /dev/null +++ b/src/dotnet/ToolPackage/PackageId.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.DotNet.InternalAbstractions; + +namespace Microsoft.DotNet.ToolPackage +{ + internal struct PackageId : IEquatable, IComparable + { + private string _id; + + public PackageId(string id) + { + _id = id?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(id)); + } + + public bool Equals(PackageId other) + { + return ToString() == other.ToString(); + } + + public int CompareTo(PackageId other) + { + return string.Compare(ToString(), other.ToString(), StringComparison.Ordinal); + } + + public override bool Equals(object obj) + { + return obj is PackageId id && Equals(id); + } + + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + + public override string ToString() + { + return _id ?? ""; + } + } +} diff --git a/src/dotnet/ToolPackage/ToolPackageInstaller.cs b/src/dotnet/ToolPackage/ToolPackageInstaller.cs index e30a4cc7e..cf655a301 100644 --- a/src/dotnet/ToolPackage/ToolPackageInstaller.cs +++ b/src/dotnet/ToolPackage/ToolPackageInstaller.cs @@ -7,13 +7,12 @@ using Microsoft.DotNet.Cli; using Microsoft.DotNet.Configurer; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.ToolPackage { internal class ToolPackageInstaller : IToolPackageInstaller { - public const string StagingDirectory = ".stage"; - private readonly IToolPackageStore _store; private readonly IProjectRestorer _projectRestorer; private readonly FilePath? _tempProject; @@ -32,33 +31,27 @@ namespace Microsoft.DotNet.ToolPackage } public IToolPackage InstallPackage( - string packageId, - string packageVersion = null, + PackageId packageId, + VersionRange versionRange = null, string targetFramework = null, FilePath? nugetConfig = null, string source = null, string verbosity = null) { - if (packageId == null) - { - throw new ArgumentNullException(nameof(packageId)); - } - - var packageRootDirectory = _store.Root.WithSubDirectories(packageId); + var packageRootDirectory = _store.GetRootPackageDirectory(packageId); string rollbackDirectory = null; return TransactionalAction.Run( action: () => { try { - - var stageDirectory = _store.Root.WithSubDirectories(StagingDirectory, Path.GetRandomFileName()); + var stageDirectory = _store.GetRandomStagingDirectory(); Directory.CreateDirectory(stageDirectory.Value); rollbackDirectory = stageDirectory.Value; var tempProject = CreateTempProject( packageId: packageId, - packageVersion: packageVersion, + versionRange: versionRange, targetFramework: targetFramework ?? BundledTargetFramework.GetTargetFrameworkMoniker(), restoreDirectory: stageDirectory); @@ -76,29 +69,22 @@ namespace Microsoft.DotNet.ToolPackage File.Delete(tempProject.Value); } - packageVersion = Path.GetFileName( - Directory.EnumerateDirectories( - stageDirectory.WithSubDirectories(packageId).Value).Single()); - - var packageDirectory = packageRootDirectory.WithSubDirectories(packageVersion); + var version = _store.GetStagedPackageVersion(stageDirectory, packageId); + var packageDirectory = _store.GetPackageDirectory(packageId, version); if (Directory.Exists(packageDirectory.Value)) { throw new ToolPackageException( string.Format( CommonLocalizableStrings.ToolPackageConflictPackageId, packageId, - packageVersion)); + version.ToNormalizedString())); } Directory.CreateDirectory(packageRootDirectory.Value); Directory.Move(stageDirectory.Value, packageDirectory.Value); rollbackDirectory = packageDirectory.Value; - return new ToolPackageInstance( - _store, - packageId, - packageVersion, - packageDirectory); + return new ToolPackageInstance(_store, packageId, version, packageDirectory); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { @@ -126,8 +112,8 @@ namespace Microsoft.DotNet.ToolPackage } private FilePath CreateTempProject( - string packageId, - string packageVersion, + PackageId packageId, + VersionRange versionRange, string targetFramework, DirectoryPath restoreDirectory) { @@ -159,8 +145,9 @@ namespace Microsoft.DotNet.ToolPackage new XElement("DisableImplicitNuGetFallbackFolder", "true")), // disable SDK side implicit NuGetFallbackFolder new XElement("ItemGroup", new XElement("PackageReference", - new XAttribute("Include", packageId), - new XAttribute("Version", packageVersion ?? "*") // nuget will restore * for latest + new XAttribute("Include", packageId.ToString()), + new XAttribute("Version", + versionRange?.ToString("S", new VersionRangeFormatter()) ?? "*") // nuget will restore latest stable for * )) )); diff --git a/src/dotnet/ToolPackage/ToolPackageInstance.cs b/src/dotnet/ToolPackage/ToolPackageInstance.cs index 5841c2ef6..74b092109 100644 --- a/src/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/dotnet/ToolPackage/ToolPackageInstance.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.Cli; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.ProjectModel; +using NuGet.Versioning; namespace Microsoft.DotNet.ToolPackage { @@ -17,20 +18,21 @@ namespace Microsoft.DotNet.ToolPackage public ToolPackageInstance( IToolPackageStore store, - string packageId, - string packageVersion, + PackageId id, + NuGetVersion version, DirectoryPath packageDirectory) { _store = store ?? throw new ArgumentNullException(nameof(store)); - PackageId = packageId ?? throw new ArgumentNullException(nameof(packageId)); - PackageVersion = packageVersion ?? throw new ArgumentNullException(nameof(packageVersion)); - PackageDirectory = packageDirectory; _commands = new Lazy>(GetCommands); + + Id = id; + Version = version ?? throw new ArgumentNullException(nameof(version)); + PackageDirectory = packageDirectory; } - public string PackageId { get; private set; } + public PackageId Id { get; private set; } - public string PackageVersion { get; private set; } + public NuGetVersion Version { get; private set; } public DirectoryPath PackageDirectory { get; private set; } @@ -53,13 +55,9 @@ namespace Microsoft.DotNet.ToolPackage { if (Directory.Exists(PackageDirectory.Value)) { - // Use the same staging directory for uninstall instead of temp + // Use the staging directory for uninstall // This prevents cross-device moves when temp is mounted to a different device - var tempPath = _store - .Root - .WithSubDirectories(ToolPackageInstaller.StagingDirectory) - .WithFile(Path.GetRandomFileName()) - .Value; + var tempPath = _store.GetRandomStagingDirectory().Value; Directory.Move(PackageDirectory.Value, tempPath); tempPackageDirectory = tempPath; } @@ -75,7 +73,7 @@ namespace Microsoft.DotNet.ToolPackage throw new ToolPackageException( string.Format( CommonLocalizableStrings.FailedToUninstallToolPackage, - PackageId, + Id, ex.Message), ex); } @@ -116,7 +114,7 @@ namespace Microsoft.DotNet.ToolPackage var toolConfigurationPath = PackageDirectory .WithSubDirectories( - PackageId, + Id.ToString(), library.Version.ToNormalizedString()) .WithFile(dotnetToolSettings.Path); @@ -138,7 +136,7 @@ namespace Microsoft.DotNet.ToolPackage "dotnet", PackageDirectory .WithSubDirectories( - PackageId, + Id.ToString(), library.Version.ToNormalizedString()) .WithFile(entryPointFromLockFile.Path))); @@ -158,7 +156,8 @@ namespace Microsoft.DotNet.ToolPackage { return lockFile ?.Targets?.SingleOrDefault(t => t.RuntimeIdentifier != null) - ?.Libraries?.SingleOrDefault(l => l.Name == PackageId); + ?.Libraries?.SingleOrDefault(l => + string.Compare(l.Name, Id.ToString(), StringComparison.CurrentCultureIgnoreCase) == 0); } private static LockFileItem FindItemInTargetLibrary(LockFileTargetLibrary library, string targetRelativeFilePath) diff --git a/src/dotnet/ToolPackage/ToolPackageStore.cs b/src/dotnet/ToolPackage/ToolPackageStore.cs index f9575a9cc..f424b6062 100644 --- a/src/dotnet/ToolPackage/ToolPackageStore.cs +++ b/src/dotnet/ToolPackage/ToolPackageStore.cs @@ -2,12 +2,16 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.ToolPackage { internal class ToolPackageStore : IToolPackageStore { + public const string StagingDirectory = ".stage"; + public ToolPackageStore(DirectoryPath root) { Root = root; @@ -15,17 +19,45 @@ namespace Microsoft.DotNet.ToolPackage public DirectoryPath Root { get; private set; } - public IEnumerable GetInstalledPackages(string packageId = null) + public DirectoryPath GetRandomStagingDirectory() { - if (packageId != null) - { - return EnumerateVersions(packageId); - } - - return EnumerateAllPackages().SelectMany(p => p); + return Root.WithSubDirectories(StagingDirectory, Path.GetRandomFileName()); } - private IEnumerable> EnumerateAllPackages() + public NuGetVersion GetStagedPackageVersion(DirectoryPath stagingDirectory, PackageId packageId) + { + if (NuGetVersion.TryParse( + Path.GetFileName( + Directory.EnumerateDirectories( + stagingDirectory.WithSubDirectories(packageId.ToString()).Value).FirstOrDefault()), + out var version)) + { + return version; + } + + throw new ToolPackageException( + string.Format( + CommonLocalizableStrings.FailedToFindStagedToolPackage, + packageId)); + } + + public DirectoryPath GetRootPackageDirectory(PackageId packageId) + { + return Root.WithSubDirectories(packageId.ToString()); + } + + public DirectoryPath GetPackageDirectory(PackageId packageId, NuGetVersion version) + { + if (version == null) + { + throw new ArgumentNullException(nameof(version)); + } + + return GetRootPackageDirectory(packageId) + .WithSubDirectories(version.ToNormalizedString().ToLowerInvariant()); + } + + public IEnumerable EnumeratePackages() { if (!Directory.Exists(Root.Value)) { @@ -34,19 +66,25 @@ namespace Microsoft.DotNet.ToolPackage foreach (var subdirectory in Directory.EnumerateDirectories(Root.Value)) { - var packageId = Path.GetFileName(subdirectory); - if (packageId == ToolPackageInstaller.StagingDirectory) + var name = Path.GetFileName(subdirectory); + var packageId = new PackageId(name); + + // Ignore the staging directory and any directory that isn't the same as the package id + if (name == StagingDirectory || name != packageId.ToString()) { continue; } - yield return EnumerateVersions(packageId); + foreach (var package in EnumeratePackageVersions(packageId)) + { + yield return package; + } } } - private IEnumerable EnumerateVersions(string packageId) + public IEnumerable EnumeratePackageVersions(PackageId packageId) { - var packageRootDirectory = Root.WithSubDirectories(packageId); + var packageRootDirectory = Root.WithSubDirectories(packageId.ToString()); if (!Directory.Exists(packageRootDirectory.Value)) { yield break; @@ -54,13 +92,27 @@ namespace Microsoft.DotNet.ToolPackage foreach (var subdirectory in Directory.EnumerateDirectories(packageRootDirectory.Value)) { - var version = Path.GetFileName(subdirectory); yield return new ToolPackageInstance( this, packageId, - version, - packageRootDirectory.WithSubDirectories(version)); + NuGetVersion.Parse(Path.GetFileName(subdirectory)), + new DirectoryPath(subdirectory)); } } + + public IToolPackage GetPackage(PackageId packageId, NuGetVersion version) + { + if (version == null) + { + throw new ArgumentNullException(nameof(version)); + } + + var directory = GetPackageDirectory(packageId, version); + if (!Directory.Exists(directory.Value)) + { + return null; + } + return new ToolPackageInstance(this, packageId, version, directory); + } } } diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/InstallToolCommand.cs b/src/dotnet/commands/dotnet-install/dotnet-install-tool/InstallToolCommand.cs index 881f81491..8459c2b55 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/InstallToolCommand.cs +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/InstallToolCommand.cs @@ -13,6 +13,7 @@ using Microsoft.DotNet.Configurer; using Microsoft.DotNet.ShellShim; using Microsoft.DotNet.ToolPackage; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.Tools.Install.Tool { @@ -25,7 +26,7 @@ namespace Microsoft.DotNet.Tools.Install.Tool private readonly IReporter _reporter; private readonly IReporter _errorReporter; - private readonly string _packageId; + private readonly PackageId _packageId; private readonly string _packageVersion; private readonly string _configFilePath; private readonly string _framework; @@ -48,7 +49,7 @@ namespace Microsoft.DotNet.Tools.Install.Tool throw new ArgumentNullException(nameof(appliedCommand)); } - _packageId = appliedCommand.Arguments.Single(); + _packageId = new PackageId(appliedCommand.Arguments.Single()); _packageVersion = appliedCommand.ValueOrDefault("version"); _configFilePath = appliedCommand.ValueOrDefault("configfile"); _framework = appliedCommand.ValueOrDefault("framework"); @@ -91,8 +92,16 @@ namespace Microsoft.DotNet.Tools.Install.Tool Path.GetFullPath(_configFilePath))); } - // Prevent installation if any version of the package is installed - if (_toolPackageStore.GetInstalledPackages(_packageId).FirstOrDefault() != null) + VersionRange versionRange = null; + if (!string.IsNullOrEmpty(_packageVersion) && !VersionRange.TryParse(_packageVersion, out versionRange)) + { + throw new GracefulException( + string.Format( + LocalizableStrings.InvalidNuGetVersionRange, + _packageVersion)); + } + + if (_toolPackageStore.EnumeratePackageVersions(_packageId).FirstOrDefault() != null) { _errorReporter.WriteLine(string.Format(LocalizableStrings.ToolAlreadyInstalled, _packageId).Red()); return 1; @@ -113,7 +122,7 @@ namespace Microsoft.DotNet.Tools.Install.Tool { package = _toolPackageInstaller.InstallPackage( packageId: _packageId, - packageVersion: _packageVersion, + versionRange: versionRange, targetFramework: _framework, nugetConfig: configFile, source: _source, @@ -133,8 +142,8 @@ namespace Microsoft.DotNet.Tools.Install.Tool string.Format( LocalizableStrings.InstallationSucceeded, string.Join(", ", package.Commands.Select(c => c.Name)), - package.PackageId, - package.PackageVersion).Green()); + package.Id, + package.Version.ToNormalizedString()).Green()); return 0; } catch (ToolPackageException ex) diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/LocalizableStrings.resx b/src/dotnet/commands/dotnet-install/dotnet-install-tool/LocalizableStrings.resx index 8d366537c..2b463ae96 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/LocalizableStrings.resx +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/LocalizableStrings.resx @@ -178,4 +178,7 @@ Tool '{1}' (version '{2}') was successfully installed. Failed to create shell shim for tool '{0}': {1} + + Specified version '{0}' is not a valid NuGet version range. + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.cs.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.cs.xlf index eb8dae6d6..894f345b3 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.cs.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.cs.xlf @@ -104,6 +104,11 @@ Instalace byla úspěšná. Pokud nejsou další pokyny, můžete přímo do já Balíček nástroje nebylo možné obnovit. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.de.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.de.xlf index 941d48da4..2d551c8f0 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.de.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.de.xlf @@ -104,6 +104,11 @@ Die Installation war erfolgreich. Sofern keine weiteren Anweisungen vorliegen, k Das Toolpaket konnte nicht wiederhergestellt werden. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.es.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.es.xlf index 13656cbc9..1907042c2 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.es.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.es.xlf @@ -104,6 +104,11 @@ La instalación se completó correctamente. Si no hay más instrucciones, puede No se puede restaurar el paquete de la herramienta. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.fr.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.fr.xlf index 65a0f7bcd..217f901ca 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.fr.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.fr.xlf @@ -104,6 +104,11 @@ L'installation a réussi. En l'absence d'instructions supplémentaires, vous pou Impossible de restaurer le package de l'outil. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.it.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.it.xlf index 9ffb72dfb..69df9674e 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.it.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.it.xlf @@ -104,6 +104,11 @@ L'installazione è riuscita. Se non ci sono altre istruzioni, è possibile digit Non è stato possibile ripristinare il pacchetto dello strumento. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ja.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ja.xlf index dd81fbb48..eb86d3b58 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ja.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ja.xlf @@ -104,6 +104,11 @@ Tool '{1}' (version '{2}') was successfully installed. ツール パッケージを復元できませんでした。 + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ko.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ko.xlf index 79acf23a0..41b63aff0 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ko.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ko.xlf @@ -104,6 +104,11 @@ Tool '{1}' (version '{2}') was successfully installed. 도구 패키지를 복원할 수 없습니다. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pl.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pl.xlf index 95e8b774f..c77c195ab 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pl.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pl.xlf @@ -104,6 +104,11 @@ Instalacja powiodła się. Jeśli nie ma dodatkowych instrukcji, możesz wpisać Nie można przywrócić pakietu narzędzia. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pt-BR.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pt-BR.xlf index d70b28da7..ab6c7b086 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.pt-BR.xlf @@ -104,6 +104,11 @@ A instalação foi bem-sucedida. Se não houver outras instruções, digite o se O pacote da ferramenta não pôde ser restaurado. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ru.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ru.xlf index a3705f15d..d4b0de225 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ru.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.ru.xlf @@ -104,6 +104,11 @@ Tool '{1}' (version '{2}') was successfully installed. Не удалось восстановить пакет инструмента. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.tr.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.tr.xlf index a9dd17bb6..13ed942cf 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.tr.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.tr.xlf @@ -104,6 +104,11 @@ Yükleme başarılı oldu. Daha fazla yönerge yoksa, çağırmak için şu komu Araç paketi geri yüklenemedi. + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hans.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hans.xlf index 840ebd25a..29f352687 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hans.xlf @@ -104,6 +104,11 @@ Tool '{1}' (version '{2}') was successfully installed. 无法还原工具包。 + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hant.xlf b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hant.xlf index 1a75879a6..911527d54 100644 --- a/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/dotnet/commands/dotnet-install/dotnet-install-tool/xlf/LocalizableStrings.zh-Hant.xlf @@ -104,6 +104,11 @@ Tool '{1}' (version '{2}') was successfully installed. 此工具套件無法還原。 + + Specified version '{0}' is not a valid NuGet version range. + Specified version '{0}' is not a valid NuGet version range. + + \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-list/dotnet-list-tool/ListToolCommand.cs b/src/dotnet/commands/dotnet-list/dotnet-list-tool/ListToolCommand.cs index b732ba3a8..b567ac74d 100644 --- a/src/dotnet/commands/dotnet-list/dotnet-list-tool/ListToolCommand.cs +++ b/src/dotnet/commands/dotnet-list/dotnet-list-tool/ListToolCommand.cs @@ -46,10 +46,10 @@ namespace Microsoft.DotNet.Tools.List.Tool table.AddColumn( LocalizableStrings.PackageIdColumn, - p => p.PackageId); + p => p.Id.ToString()); table.AddColumn( LocalizableStrings.VersionColumn, - p => p.PackageVersion); + p => p.Version.ToNormalizedString()); table.AddColumn( LocalizableStrings.CommandsColumn, p => string.Join(CommandDelimiter, p.Commands.Select(c => c.Name))); @@ -60,9 +60,9 @@ namespace Microsoft.DotNet.Tools.List.Tool private IEnumerable GetPackages() { - return _toolPackageStore.GetInstalledPackages() + return _toolPackageStore.EnumeratePackages() .Where(PackageHasCommands) - .OrderBy(p => p.PackageId) + .OrderBy(p => p.Id) .ToArray(); } @@ -79,7 +79,7 @@ namespace Microsoft.DotNet.Tools.List.Tool _errorReporter.WriteLine( string.Format( LocalizableStrings.InvalidPackageWarning, - p.PackageId, + p.Id, ex.Message).Yellow()); return false; } diff --git a/src/dotnet/commands/dotnet-uninstall/tool/UninstallToolCommand.cs b/src/dotnet/commands/dotnet-uninstall/tool/UninstallToolCommand.cs index d362e3838..ca470f4a6 100644 --- a/src/dotnet/commands/dotnet-uninstall/tool/UninstallToolCommand.cs +++ b/src/dotnet/commands/dotnet-uninstall/tool/UninstallToolCommand.cs @@ -49,12 +49,11 @@ namespace Microsoft.DotNet.Tools.Uninstall.Tool throw new GracefulException(LocalizableStrings.UninstallToolCommandOnlySupportsGlobal); } - var packageId = _options.Arguments.Single(); + var packageId = new PackageId(_options.Arguments.Single()); IToolPackage package = null; - try { - package = _toolPackageStore.GetInstalledPackages(packageId).SingleOrDefault(); + package = _toolPackageStore.EnumeratePackageVersions(packageId).SingleOrDefault(); if (package == null) { _errorReporter.WriteLine( @@ -92,8 +91,8 @@ namespace Microsoft.DotNet.Tools.Uninstall.Tool _reporter.WriteLine( string.Format( LocalizableStrings.UninstallSucceeded, - package.PackageId, - package.PackageVersion).Green()); + package.Id, + package.Version.ToNormalizedString()).Green()); return 0; } catch (ToolPackageException ex) diff --git a/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf b/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf index 8f0e7c10c..70be5e60b 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.cs.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.de.xlf b/src/dotnet/xlf/CommonLocalizableStrings.de.xlf index 3a93064c3..6ef10330e 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.de.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.de.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.es.xlf b/src/dotnet/xlf/CommonLocalizableStrings.es.xlf index f283122ee..c9d3df366 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.es.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.es.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf b/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf index 3a668e77f..ab0d6ae1f 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.fr.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.it.xlf b/src/dotnet/xlf/CommonLocalizableStrings.it.xlf index 89d2e6f1e..6210b184f 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.it.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.it.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf b/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf index 5d42dbf93..4541550f0 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.ja.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf b/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf index e136f3d5c..c9d93cce8 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.ko.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf b/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf index 0a2d70abc..2b22b4c8c 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.pl.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf b/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf index 81b666a10..60b4b7863 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.pt-BR.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf b/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf index 0926eee52..50ee839d2 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.ru.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf b/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf index 3d316c24f..a20c55ce0 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.tr.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf index b18d951b8..d26ed195d 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hans.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf index 8c61b8d28..fc901551e 100644 --- a/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf +++ b/src/dotnet/xlf/CommonLocalizableStrings.zh-Hant.xlf @@ -853,6 +853,11 @@ setx PATH "%PATH%;{0}" Settings file 'DotnetToolSettings.xml' was not found in the package. + + Failed to find staged tool package '{0}'. + Failed to find staged tool package '{0}'. + + \ No newline at end of file diff --git a/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstallerTests.cs b/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstallerTests.cs index 9c4bf61f5..46268c106 100644 --- a/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstallerTests.cs +++ b/test/Microsoft.DotNet.ToolPackage.Tests/ToolPackageInstallerTests.cs @@ -16,6 +16,7 @@ using Microsoft.DotNet.Tools.Install.Tool; using Microsoft.DotNet.Tools.Tests.ComponentMocks; using Microsoft.Extensions.DependencyModel.Tests; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; using Xunit; namespace Microsoft.DotNet.ToolPackage.Tests @@ -33,13 +34,13 @@ namespace Microsoft.DotNet.ToolPackage.Tests Action a = () => installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework); a.ShouldThrow().WithMessage(LocalizableStrings.ToolInstallationRestoreFailed); reporter.Lines.Count.Should().Be(1); - reporter.Lines[0].Should().Contain(TestPackageId); + reporter.Lines[0].Should().Contain(TestPackageId.ToString()); } [Theory] @@ -54,7 +55,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework); AssertPackageInstall(reporter, fileSystem, package, store); @@ -75,7 +76,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, nugetConfig: nugetConfigPath); @@ -102,7 +103,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests { package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, nugetConfig: nugetConfigPath); @@ -127,7 +128,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, nugetConfig: nugetConfigPath); @@ -166,7 +167,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework); AssertPackageInstall(reporter, fileSystem, package, store); @@ -208,7 +209,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), nugetConfig: nugetConfigPath); AssertPackageInstall(reporter, fileSystem, package, store); @@ -229,7 +230,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -251,7 +252,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests TransactionScopeOption.Required, TimeSpan.Zero)) { - installer.InstallPackage("non.existent.package.id"); + installer.InstallPackage(new PackageId("non.existent.package.id")); t.Complete(); } @@ -282,7 +283,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests { installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -314,7 +315,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests { Action first = () => installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -322,7 +323,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -353,7 +354,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -361,7 +362,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests Action secondCall = () => installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -376,7 +377,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests fileSystem .Directory - .Exists(store.Root.WithSubDirectories(TestPackageId).Value) + .Exists(store.Root.WithSubDirectories(TestPackageId.ToString()).Value) .Should() .BeTrue(); @@ -384,7 +385,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests fileSystem .Directory - .EnumerateFileSystemEntries(store.Root.WithSubDirectories(".stage").Value) + .EnumerateFileSystemEntries(store.Root.WithSubDirectories(ToolPackageStore.StagingDirectory).Value) .Should() .BeEmpty(); } @@ -402,7 +403,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -410,7 +411,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests package.Uninstall(); - store.GetInstalledPackages(TestPackageId).Should().BeEmpty(); + store.EnumeratePackages().Should().BeEmpty(); } [Theory] @@ -426,7 +427,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -438,10 +439,10 @@ namespace Microsoft.DotNet.ToolPackage.Tests { package.Uninstall(); - store.GetInstalledPackages(TestPackageId).Should().BeEmpty(); + store.EnumeratePackages().Should().BeEmpty(); } - package = store.GetInstalledPackages(TestPackageId).First(); + package = store.EnumeratePackageVersions(TestPackageId).First(); AssertPackageInstall(reporter, fileSystem, package, store); } @@ -459,7 +460,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests var package = installer.InstallPackage( packageId: TestPackageId, - packageVersion: TestPackageVersion, + versionRange: VersionRange.Parse(TestPackageVersion), targetFramework: _testTargetframework, source: source); @@ -473,7 +474,28 @@ namespace Microsoft.DotNet.ToolPackage.Tests scope.Complete(); } - store.GetInstalledPackages(TestPackageId).Should().BeEmpty(); + store.EnumeratePackages().Should().BeEmpty(); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GivenAPackageNameWithDifferentCaseItCanInstallThePackage(bool testMockBehaviorIsInSync) + { + var nugetConfigPath = WriteNugetConfigFileToPointToTheFeed(); + + var (store, installer, reporter, fileSystem) = Setup( + useMock: testMockBehaviorIsInSync, + feeds: GetMockFeedsForConfigFile(nugetConfigPath)); + + var package = installer.InstallPackage( + packageId: new PackageId("GlObAl.TooL.coNsoLe.DemO"), + targetFramework: _testTargetframework, + nugetConfig: nugetConfigPath); + + AssertPackageInstall(reporter, fileSystem, package, store); + + package.Uninstall(); } private static void AssertPackageInstall( @@ -484,11 +506,14 @@ namespace Microsoft.DotNet.ToolPackage.Tests { reporter.Lines.Should().BeEmpty(); - package.PackageId.Should().Be(TestPackageId); - package.PackageVersion.Should().Be(TestPackageVersion); + package.Id.Should().Be(TestPackageId); + package.Version.ToNormalizedString().Should().Be(TestPackageVersion); package.PackageDirectory.Value.Should().Contain(store.Root.Value); - store.GetInstalledPackages(TestPackageId).Select(p => p.PackageVersion).Should().Equal(TestPackageVersion); + store.EnumeratePackageVersions(TestPackageId) + .Select(p => p.Version.ToNormalizedString()) + .Should() + .Equal(TestPackageVersion); package.Commands.Count.Should().Be(1); fileSystem.File.Exists(package.Commands[0].Executable.Value).Should().BeTrue($"{package.Commands[0].Executable.Value} should exist"); @@ -506,11 +531,11 @@ namespace Microsoft.DotNet.ToolPackage.Tests .Directory .EnumerateFileSystemEntries(store.Root.Value) .Should() - .NotContain(e => Path.GetFileName(e) != ".stage"); + .NotContain(e => Path.GetFileName(e) != ToolPackageStore.StagingDirectory); fileSystem .Directory - .EnumerateFileSystemEntries(store.Root.WithSubDirectories(".stage").Value) + .EnumerateFileSystemEntries(store.Root.WithSubDirectories(ToolPackageStore.StagingDirectory).Value) .Should() .BeEmpty(); } @@ -536,7 +561,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests { new MockFeedPackage { - PackageId = TestPackageId, + PackageId = TestPackageId.ToString(), Version = TestPackageVersion } } @@ -556,7 +581,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests { new MockFeedPackage { - PackageId = TestPackageId, + PackageId = TestPackageId.ToString(), Version = TestPackageVersion } } @@ -576,7 +601,7 @@ namespace Microsoft.DotNet.ToolPackage.Tests { new MockFeedPackage { - PackageId = TestPackageId, + PackageId = TestPackageId.ToString(), Version = TestPackageVersion } } @@ -642,6 +667,6 @@ namespace Microsoft.DotNet.ToolPackage.Tests private static string GetTestLocalFeedPath() => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestAssetLocalNugetFeed"); private readonly string _testTargetframework = BundledTargetFramework.GetTargetFrameworkMoniker(); private const string TestPackageVersion = "1.0.4"; - private const string TestPackageId = "global.tool.console.demo"; + private static readonly PackageId TestPackageId = new PackageId("global.tool.console.demo"); } } diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ProjectRestorerMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ProjectRestorerMock.cs index 2bdb35820..f4f280df5 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ProjectRestorerMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ProjectRestorerMock.cs @@ -11,6 +11,7 @@ using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Tools; using Microsoft.DotNet.Tools.Install.Tool; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.Tools.Tests.ComponentMocks { @@ -63,7 +64,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks string verbosity = null) { string packageId; - string packageVersion; + VersionRange versionRange; string targetFramework; try @@ -77,7 +78,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks } packageId = tokens[0]; - packageVersion = tokens[1]; + versionRange = VersionRange.Parse(tokens[1]); targetFramework = tokens[2]; } catch (IOException) @@ -92,16 +93,16 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks var feedPackage = GetPackage( packageId, - packageVersion, + versionRange, nugetConfig, source); - packageVersion = feedPackage.Version; + var packageVersion = feedPackage.Version; targetFramework = string.IsNullOrEmpty(targetFramework) ? "targetFramework" : targetFramework; var fakeExecutableSubDirectory = Path.Combine( - packageId, - packageVersion, + packageId.ToLowerInvariant(), + packageVersion.ToLowerInvariant(), "tools", targetFramework, "any"); @@ -116,7 +117,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks private MockFeedPackage GetPackage( string packageId, - string packageVersion = null, + VersionRange versionRange = null, FilePath? nugetConfig = null, string source = null) { @@ -133,7 +134,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks return true; }) .SelectMany(f => f.Packages) - .Where(p => MatchPackageVersion(p, packageId, packageVersion)).OrderByDescending(p => p.Version) + .Where(p => MatchPackage(p, packageId, versionRange)).OrderByDescending(p => p.Version) .FirstOrDefault(); if (package == null) @@ -148,13 +149,15 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks return package; } - private static bool MatchPackageVersion(MockFeedPackage p, string packageId, string packageVersion) + private static bool MatchPackage(MockFeedPackage p, string packageId, VersionRange versionRange) { - if (string.IsNullOrEmpty(packageVersion)) + if (string.Compare(p.PackageId, packageId, StringComparison.CurrentCultureIgnoreCase) != 0) { - return p.PackageId == packageId; + return false; } - return p.PackageId == packageId && p.Version == packageVersion; + + return versionRange == null || + versionRange.FindBestMatch(new[] { NuGetVersion.Parse(p.Version) }) != null; } } } diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs index cce1000f5..9966ddb6a 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageInstallerMock.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Cli; using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.Tools.Tests.ComponentMocks { @@ -35,19 +36,19 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks } public IToolPackage InstallPackage( - string packageId, - string packageVersion = null, + PackageId packageId, + VersionRange versionRange = null, string targetFramework = null, FilePath? nugetConfig = null, string source = null, string verbosity = null) { - var packageRootDirectory = _store.Root.WithSubDirectories(packageId); + var packageRootDirectory = _store.GetRootPackageDirectory(packageId); string rollbackDirectory = null; return TransactionalAction.Run( action: () => { - var stageDirectory = _store.Root.WithSubDirectories(".stage", Path.GetRandomFileName()); + var stageDirectory = _store.GetRandomStagingDirectory(); _fileSystem.Directory.CreateDirectory(stageDirectory.Value); rollbackDirectory = stageDirectory.Value; @@ -56,7 +57,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks // Write a fake project with the requested package id, version, and framework _fileSystem.File.WriteAllText( tempProject.Value, - $"{packageId}:{packageVersion}:{targetFramework}"); + $"{packageId}:{versionRange?.ToString("S", new VersionRangeFormatter()) ?? "*"}:{targetFramework}"); // Perform a restore on the fake project _projectRestorer.Restore( @@ -71,29 +72,22 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks _installCallback(); } - packageVersion = Path.GetFileName( - _fileSystem.Directory.EnumerateFileSystemEntries( - stageDirectory.WithSubDirectories(packageId).Value).Single()); - - var packageDirectory = packageRootDirectory.WithSubDirectories(packageVersion); + var version = _store.GetStagedPackageVersion(stageDirectory, packageId); + var packageDirectory = _store.GetPackageDirectory(packageId, version); if (_fileSystem.Directory.Exists(packageDirectory.Value)) { throw new ToolPackageException( string.Format( CommonLocalizableStrings.ToolPackageConflictPackageId, packageId, - packageVersion)); + version.ToNormalizedString())); } _fileSystem.Directory.CreateDirectory(packageRootDirectory.Value); _fileSystem.Directory.Move(stageDirectory.Value, packageDirectory.Value); rollbackDirectory = packageDirectory.Value; - return new ToolPackageMock( - _fileSystem, - packageId, - packageVersion, - packageDirectory); + return new ToolPackageMock(_fileSystem, packageId, version, packageDirectory); }, rollback: () => { if (rollbackDirectory != null && _fileSystem.Directory.Exists(rollbackDirectory)) diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs index 5e968a288..6ad826441 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.DotNet.Cli; using Microsoft.DotNet.ToolPackage; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.Tools.Tests.ComponentMocks { @@ -19,22 +20,22 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks public ToolPackageMock( IFileSystem fileSystem, - string packageId, - string packageVersion, + PackageId id, + NuGetVersion version, DirectoryPath packageDirectory, Action uninstallCallback = null) { _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - PackageId = packageId ?? throw new ArgumentNullException(nameof(packageId)); - PackageVersion = packageVersion ?? throw new ArgumentNullException(nameof(packageVersion)); + Id = id; + Version = version ?? throw new ArgumentNullException(nameof(version)); PackageDirectory = packageDirectory; _commands = new Lazy>(GetCommands); _uninstallCallback = uninstallCallback; } - public string PackageId { get; private set; } + public PackageId Id { get; private set; } - public string PackageVersion { get; private set; } + public NuGetVersion Version { get; private set; } public DirectoryPath PackageDirectory { get; private set; } @@ -78,7 +79,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks throw new ToolPackageException( string.Format( CommonLocalizableStrings.FailedToUninstallToolPackage, - PackageId, + Id, ex.Message), ex); } @@ -118,7 +119,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks throw new ToolPackageException( string.Format( CommonLocalizableStrings.FailedToRetrieveToolConfiguration, - PackageId, + Id, ex.Message), ex); } diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageStoreMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageStoreMock.cs index 4681d3123..e8219560c 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageStoreMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageStoreMock.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.DotNet.Cli; using Microsoft.DotNet.ToolPackage; using Microsoft.Extensions.EnvironmentAbstractions; +using NuGet.Versioning; namespace Microsoft.DotNet.Tools.Tests.ComponentMocks { @@ -28,9 +29,66 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks public DirectoryPath Root { get; private set; } - public IEnumerable GetInstalledPackages(string packageId) + public DirectoryPath GetRandomStagingDirectory() { - var packageRootDirectory = Root.WithSubDirectories(packageId); + return Root.WithSubDirectories(ToolPackageStore.StagingDirectory, Path.GetRandomFileName()); + } + + public NuGetVersion GetStagedPackageVersion(DirectoryPath stagingDirectory, PackageId packageId) + { + if (NuGetVersion.TryParse( + Path.GetFileName( + _fileSystem.Directory.EnumerateFileSystemEntries( + stagingDirectory.WithSubDirectories(packageId.ToString()).Value).FirstOrDefault()), + out var version)) + { + return version; + } + + throw new ToolPackageException( + string.Format( + CommonLocalizableStrings.FailedToFindStagedToolPackage, + packageId)); + } + + public DirectoryPath GetRootPackageDirectory(PackageId packageId) + { + return Root.WithSubDirectories(packageId.ToString()); + } + + public DirectoryPath GetPackageDirectory(PackageId packageId, NuGetVersion version) + { + return GetRootPackageDirectory(packageId) + .WithSubDirectories(version.ToNormalizedString().ToLowerInvariant()); + } + + public IEnumerable EnumeratePackages() + { + if (!_fileSystem.Directory.Exists(Root.Value)) + { + yield break; + } + + foreach (var subdirectory in _fileSystem.Directory.EnumerateFileSystemEntries(Root.Value)) + { + var name = Path.GetFileName(subdirectory); + var packageId = new PackageId(name); + + if (name == ToolPackageStore.StagingDirectory || name != packageId.ToString()) + { + continue; + } + + foreach (var package in EnumeratePackageVersions(packageId)) + { + yield return package; + } + } + } + + public IEnumerable EnumeratePackageVersions(PackageId packageId) + { + var packageRootDirectory = Root.WithSubDirectories(packageId.ToString()); if (!_fileSystem.Directory.Exists(packageRootDirectory.Value)) { yield break; @@ -38,14 +96,23 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks foreach (var subdirectory in _fileSystem.Directory.EnumerateFileSystemEntries(packageRootDirectory.Value)) { - var version = Path.GetFileName(subdirectory); yield return new ToolPackageMock( _fileSystem, packageId, - version, - packageRootDirectory.WithSubDirectories(version), + NuGetVersion.Parse(Path.GetFileName(subdirectory)), + new DirectoryPath(subdirectory), _uninstallCallback); } } + + public IToolPackage GetPackage(PackageId packageId, NuGetVersion version) + { + var directory = GetPackageDirectory(packageId, version); + if (!_fileSystem.Directory.Exists(directory.Value)) + { + return null; + } + return new ToolPackageMock(_fileSystem, packageId, version, directory, _uninstallCallback); + } } } diff --git a/test/dotnet.Tests/CommandTests/InstallToolCommandTests.cs b/test/dotnet.Tests/CommandTests/InstallToolCommandTests.cs index 1a1f140b0..15b626428 100644 --- a/test/dotnet.Tests/CommandTests/InstallToolCommandTests.cs +++ b/test/dotnet.Tests/CommandTests/InstallToolCommandTests.cs @@ -148,17 +148,12 @@ namespace Microsoft.DotNet.Tests.Commands installToolCommand.Execute().Should().Be(1); - _reporter.Lines.Count.Should().Be(2); - _reporter - .Lines[0] + .Lines .Should() - .Contain("Simulated error"); - - _reporter - .Lines[1] - .Should() - .Contain(string.Format(LocalizableStrings.ToolInstallationFailed, PackageId)); + .Equal( + "Simulated error".Red(), + string.Format(LocalizableStrings.ToolInstallationFailed, PackageId).Red()); _fileSystem.Directory.Exists(Path.Combine(PathToPlacePackages, PackageId)).Should().BeFalse(); } @@ -205,20 +200,14 @@ namespace Microsoft.DotNet.Tests.Commands installToolCommand.Execute().Should().Be(1); - _reporter.Lines.Count.Should().Be(2); - _reporter - .Lines[0] + .Lines .Should() - .Contain( + .Equal( string.Format( LocalizableStrings.InvalidToolConfiguration, - "Simulated error")); - - _reporter - .Lines[1] - .Should() - .Contain(string.Format(LocalizableStrings.ToolInstallationFailedContactAuthor, PackageId)); + "Simulated error").Red(), + string.Format(LocalizableStrings.ToolInstallationFailedContactAuthor, PackageId).Red()); } [Fact] @@ -237,13 +226,144 @@ namespace Microsoft.DotNet.Tests.Commands _reporter .Lines - .Single() .Should() - .Contain(string.Format( + .Equal(string.Format( LocalizableStrings.InstallationSucceeded, ProjectRestorerMock.FakeCommandName, PackageId, - PackageVersion)); + PackageVersion).Green()); + } + + [Fact] + public void WhenRunWithInvalidVersionItShouldThrow() + { + const string invalidVersion = "!NotValidVersion!"; + ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId} --version {invalidVersion}"); + AppliedOption appliedCommand = result["dotnet"]["install"]["tool"]; + + var installToolCommand = new InstallToolCommand( + appliedCommand, + result, + _toolPackageStore, + CreateToolPackageInstaller(), + _shellShimRepositoryMock, + new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim, true), + _reporter); + + Action action = () => installToolCommand.Execute(); + + action + .ShouldThrow() + .WithMessage(string.Format( + LocalizableStrings.InvalidNuGetVersionRange, + invalidVersion)); + } + + [Fact] + public void WhenRunWithExactVersionItShouldSucceed() + { + ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId} --version {PackageVersion}"); + AppliedOption appliedCommand = result["dotnet"]["install"]["tool"]; + + var installToolCommand = new InstallToolCommand( + appliedCommand, + result, + _toolPackageStore, + CreateToolPackageInstaller(), + _shellShimRepositoryMock, + new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim, true), + _reporter); + + installToolCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ProjectRestorerMock.FakeCommandName, + PackageId, + PackageVersion).Green()); + } + + [Fact] + public void WhenRunWithValidVersionRangeItShouldSucceed() + { + ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId} --version [1.0,2.0]"); + AppliedOption appliedCommand = result["dotnet"]["install"]["tool"]; + + var installToolCommand = new InstallToolCommand( + appliedCommand, + result, + _toolPackageStore, + CreateToolPackageInstaller(), + _shellShimRepositoryMock, + new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim, true), + _reporter); + + installToolCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ProjectRestorerMock.FakeCommandName, + PackageId, + PackageVersion).Green()); + } + + [Fact] + public void WhenRunWithoutAMatchingRangeItShouldFail() + { + ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId} --version [5.0,10.0]"); + AppliedOption appliedCommand = result["dotnet"]["install"]["tool"]; + + var installToolCommand = new InstallToolCommand( + appliedCommand, + result, + _toolPackageStore, + CreateToolPackageInstaller(), + _shellShimRepositoryMock, + new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim, true), + _reporter); + + installToolCommand.Execute().Should().Be(1); + + _reporter + .Lines + .Should() + .Equal( + $"Error: failed to restore package {PackageId}.", // From mock implementation, not localized + LocalizableStrings.ToolInstallationRestoreFailed.Red(), + string.Format(LocalizableStrings.ToolInstallationFailed, PackageId).Red()); + } + + [Fact] + public void WhenRunWithValidVersionWildcardItShouldSucceed() + { + ParseResult result = Parser.Instance.Parse($"dotnet install tool -g {PackageId} --version 1.0.*"); + AppliedOption appliedCommand = result["dotnet"]["install"]["tool"]; + + var installToolCommand = new InstallToolCommand( + appliedCommand, + result, + _toolPackageStore, + CreateToolPackageInstaller(), + _shellShimRepositoryMock, + new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim, true), + _reporter); + + installToolCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ProjectRestorerMock.FakeCommandName, + PackageId, + PackageVersion).Green()); } private IToolPackageInstaller CreateToolPackageInstaller(