diff --git a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs index 3282033a4c7f..1a16911945cf 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs @@ -112,7 +112,13 @@ public async Task DownloadPackageAsync(PackageId packageId, string.Format(CliStrings.IsNotFoundInNuGetFeeds, packageId, source.Source)); } - var pathResolver = new VersionFolderPathResolver(downloadFolder == null || !downloadFolder.HasValue ? _packageInstallDir.Value : downloadFolder.Value.Value); + var resolvedDownloadFolder = downloadFolder == null || !downloadFolder.HasValue ? _packageInstallDir.Value : downloadFolder.Value.Value; + if (string.IsNullOrEmpty(resolvedDownloadFolder)) + { + throw new ArgumentException($"Package download folder must be specified either via {nameof(NuGetPackageDownloader)} constructor or via {nameof(downloadFolder)} method argument."); + } + var pathResolver = new VersionFolderPathResolver(resolvedDownloadFolder); + string nupkgPath = pathResolver.GetPackageFilePath(packageId.ToString(), resolvedPackageVersion); Directory.CreateDirectory(Path.GetDirectoryName(nupkgPath)); diff --git a/src/Cli/dotnet/ToolPackage/IToolPackage.cs b/src/Cli/dotnet/ToolPackage/IToolPackage.cs index f02fc4a18322..847abf88f1bb 100644 --- a/src/Cli/dotnet/ToolPackage/IToolPackage.cs +++ b/src/Cli/dotnet/ToolPackage/IToolPackage.cs @@ -13,6 +13,10 @@ internal interface IToolPackage NuGetVersion Version { get; } + public PackageId ResolvedPackageId { get; } + + public NuGetVersion ResolvedPackageVersion { get; } + DirectoryPath PackageDirectory { get; } RestoredCommand Command { get; } diff --git a/src/Cli/dotnet/ToolPackage/LocalToolsResolverCache.cs b/src/Cli/dotnet/ToolPackage/LocalToolsResolverCache.cs index 6cd9e8623324..c99f1efb4c72 100644 --- a/src/Cli/dotnet/ToolPackage/LocalToolsResolverCache.cs +++ b/src/Cli/dotnet/ToolPackage/LocalToolsResolverCache.cs @@ -31,6 +31,8 @@ public void Save( { EnsureFileStorageExists(); + // TODO: Save resolved package info here? + foreach (var distinctPackageIdAndRestoredCommandMap in restoredCommandMap.GroupBy(x => x.Key.PackageId)) { PackageId distinctPackageId = distinctPackageIdAndRestoredCommandMap.Key; @@ -213,5 +215,6 @@ private class CacheRow public string Name { get; set; } public string Runner { get; set; } public string PathToExecutable { get; set; } + // TODO: Need resolved package info here } } diff --git a/src/Cli/dotnet/ToolPackage/RestoredCommandIdentifier.cs b/src/Cli/dotnet/ToolPackage/RestoredCommandIdentifier.cs index 7005942cd7a4..ab4a48761ad3 100644 --- a/src/Cli/dotnet/ToolPackage/RestoredCommandIdentifier.cs +++ b/src/Cli/dotnet/ToolPackage/RestoredCommandIdentifier.cs @@ -17,6 +17,9 @@ internal class RestoredCommandIdentifier( string runtimeIdentifier, ToolCommandName commandName) : IEquatable { + + // TODO: What is this class and how is it different from CacheRow? Does it need to have the resolved package information? + public PackageId PackageId { get; } = packageId; public NuGetVersion Version { get; } = version ?? throw new ArgumentException(nameof(version)); public NuGetFramework TargetFramework { get; } = targetFramework ?? throw new ArgumentException(nameof(targetFramework)); diff --git a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs index b23c173b105e..f55b3c33dffc 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using NuGet.Packaging.Core; + namespace Microsoft.DotNet.Cli.ToolPackage; internal class ToolConfiguration @@ -8,6 +10,8 @@ internal class ToolConfiguration public ToolConfiguration( string commandName, string toolAssemblyEntryPoint, + string runner, + IDictionary ridSpecificPackages = null, IEnumerable warnings = null) { if (string.IsNullOrWhiteSpace(commandName)) @@ -15,7 +19,7 @@ public ToolConfiguration( throw new ToolConfigurationException(CliStrings.ToolSettingsMissingCommandName); } - if (string.IsNullOrWhiteSpace(toolAssemblyEntryPoint)) + if (string.IsNullOrWhiteSpace(toolAssemblyEntryPoint) && ridSpecificPackages?.Any() != true) { throw new ToolConfigurationException( string.Format( @@ -28,6 +32,8 @@ public ToolConfiguration( CommandName = commandName; ToolAssemblyEntryPoint = toolAssemblyEntryPoint; + Runner = runner; + RidSpecificPackages = ridSpecificPackages; Warnings = warnings ?? []; } @@ -55,7 +61,13 @@ private static void EnsureNoLeadingDot(string commandName) } } + + public string CommandName { get; } public string ToolAssemblyEntryPoint { get; } + public string Runner { get; } + + public IDictionary RidSpecificPackages { get; } + public IEnumerable Warnings { get; } } diff --git a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliTool.cs b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliTool.cs index f87d0fc13106..7c60af12a480 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliTool.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliTool.cs @@ -13,6 +13,9 @@ public class DotNetCliTool [XmlArrayItem("Command", IsNullable = false)] public DotNetCliToolCommand[] Commands { get; set; } + [XmlArrayItem("RuntimeIdentifierPackage", IsNullable = false)] + public DotNetCliToolRuntimeIdentifierPackage[] RuntimeIdentifierPackages { get; set; } + [XmlAttribute(AttributeName = "Version")] public string Version { get; set; } } diff --git a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliToolRuntimeIdentifierPackage.cs b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliToolRuntimeIdentifierPackage.cs new file mode 100644 index 000000000000..df01ed5dca7e --- /dev/null +++ b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliToolRuntimeIdentifierPackage.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Xml.Serialization; + +namespace Microsoft.DotNet.Cli.ToolPackage.ToolConfigurationDeserialization; + +[Serializable] +[DebuggerStepThrough] +[XmlType(AnonymousType = true)] +public class DotNetCliToolRuntimeIdentifierPackage +{ + [XmlAttribute] + public string RuntimeIdentifier { get; set; } + + [XmlAttribute] + public string Id { get; set; } + + [XmlAttribute] + public string Version { get; set; } +} diff --git a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs index 8750465a1ce4..9d65cd23de1f 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs @@ -4,6 +4,7 @@ using System.Xml; using System.Xml.Serialization; using Microsoft.DotNet.Cli.ToolPackage.ToolConfigurationDeserialization; +using NuGet.Packaging.Core; namespace Microsoft.DotNet.Cli.ToolPackage; @@ -11,7 +12,7 @@ internal static class ToolConfigurationDeserializer { // The supported tool configuration schema version. // This should match the schema version in the GenerateToolsSettingsFile task from the SDK. - private const int SupportedVersion = 1; + private const int SupportedVersion = 2; public static ToolConfiguration Deserialize(string pathToXml) { @@ -51,7 +52,9 @@ public static ToolConfiguration Deserialize(string pathToXml) throw new ToolConfigurationException(CliStrings.ToolSettingsMoreThanOneCommand); } - if (dotNetCliTool.Commands[0].Runner != "dotnet") + // TODO: Should be an error if runner is empty and there aren't any RID-specific packages + var runner = dotNetCliTool.Commands[0].Runner; + if (!string.IsNullOrEmpty(runner) && runner != "dotnet" && runner != "executable") { throw new ToolConfigurationException( string.Format( @@ -60,10 +63,15 @@ public static ToolConfiguration Deserialize(string pathToXml) dotNetCliTool.Commands[0].Runner)); } + var ridSpecificPackages = dotNetCliTool.RuntimeIdentifierPackages?.ToDictionary(p => p.RuntimeIdentifier, p => new PackageIdentity(p.Id, new NuGet.Versioning.NuGetVersion(p.Version))) + .AsReadOnly(); + return new ToolConfiguration( dotNetCliTool.Commands[0].Name, dotNetCliTool.Commands[0].EntryPoint, - warnings); + dotNetCliTool.Commands[0].Runner, + ridSpecificPackages: ridSpecificPackages, + warnings: warnings); } private static List GenerateWarningAccordingToVersionAttribute(DotNetCliTool dotNetCliTool) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 9b1b244486c8..1e7ad6966ad1 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -9,6 +9,7 @@ using Microsoft.TemplateEngine.Utils; using Newtonsoft.Json.Linq; using NuGet.Client; +using NuGet.Commands; using NuGet.Common; using NuGet.Configuration; using NuGet.ContentModel; @@ -72,104 +73,114 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa bool isGlobalTool = false, bool isGlobalToolRollForward = false, bool verifySignatures = true, - RestoreActionConfig restoreActionConfig = null - ) + RestoreActionConfig restoreActionConfig = null) { - var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - string rollbackDirectory = null; - return TransactionalAction.Run( - action: () => - { - ILogger nugetLogger = new NullLogger(); - if (verbosity.IsDetailedOrDiagnostic()) - { - nugetLogger = new NuGetConsoleLogger(); - } + ILogger nugetLogger = new NullLogger(); + if (verbosity.IsDetailedOrDiagnostic()) + { + nugetLogger = new NuGetConsoleLogger(); + } - if (versionRange == null) - { - var versionString = "*"; - versionRange = VersionRange.Parse(versionString); - } + if (versionRange == null) + { + var versionString = "*"; + versionRange = VersionRange.Parse(versionString); + } - var toolDownloadDir = isGlobalTool ? _globalToolStageDir : _localToolDownloadDir; - var assetFileDirectory = isGlobalTool ? _globalToolStageDir : _localToolAssetDir; + var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( + new DirectoryPath(), + verboseLogger: nugetLogger, + verifySignatures: verifySignatures, + shouldUsePackageSourceMapping: true, + restoreActionConfig: restoreActionConfig, + verbosityOptions: verbosity, + currentWorkingDirectory: _currentWorkingDirectory); - var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( - toolDownloadDir, - verboseLogger: nugetLogger, - verifySignatures: verifySignatures, - shouldUsePackageSourceMapping: true, - restoreActionConfig: restoreActionConfig, - verbosityOptions: verbosity, - currentWorkingDirectory: _currentWorkingDirectory); + var packageSourceLocation = new PackageSourceLocation(packageLocation.NugetConfig, packageLocation.RootConfigDirectory, packageLocation.SourceFeedOverrides, packageLocation.AdditionalFeeds); - var packageSourceLocation = new PackageSourceLocation(packageLocation.NugetConfig, packageLocation.RootConfigDirectory, packageLocation.SourceFeedOverrides, packageLocation.AdditionalFeeds); + NuGetVersion packageVersion = nugetPackageDownloader.GetBestPackageVersionAsync(packageId, versionRange, packageSourceLocation).GetAwaiter().GetResult(); - bool givenSpecificVersion = false; - if (versionRange.MinVersion != null && versionRange.MaxVersion != null && versionRange.MinVersion == versionRange.MaxVersion) - { - givenSpecificVersion = true; - } - NuGetVersion packageVersion = nugetPackageDownloader.GetBestPackageVersionAsync(packageId, versionRange, packageSourceLocation).GetAwaiter().GetResult(); + bool givenSpecificVersion = false; + if (versionRange.MinVersion != null && versionRange.MaxVersion != null && versionRange.MinVersion == versionRange.MaxVersion) + { + givenSpecificVersion = true; + } - rollbackDirectory = isGlobalTool ? toolDownloadDir.Value: new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); + if (isGlobalTool) + { + return InstallGlobalToolPackageInternal( + packageSourceLocation, + nugetPackageDownloader, + packageId, + packageVersion, + givenSpecificVersion, + targetFramework, + isGlobalToolRollForward); + } + else + { + return InstallLocalToolPackageInternal( + packageSourceLocation, + nugetPackageDownloader, + packageId, + packageVersion, + givenSpecificVersion, + targetFramework); + } + } - if (isGlobalTool) - { - NuGetv3LocalRepository nugetPackageRootDirectory = new(new VersionFolderPathResolver(_toolPackageStore.Root.Value).GetInstallPath(packageId.ToString(), packageVersion)); - var globalPackage = nugetPackageRootDirectory.FindPackage(packageId.ToString(), packageVersion); + private IToolPackage InstallGlobalToolPackageInternal( + PackageSourceLocation packageSourceLocation, + NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, + PackageId packageId, + NuGetVersion packageVersion, + bool givenSpecificVersion, + string targetFramework, + bool isGlobalToolRollForward) + { + // Check if package already exists in global tools location + NuGetv3LocalRepository nugetPackageRootDirectory = new(new VersionFolderPathResolver(_toolPackageStore.Root.Value).GetInstallPath(packageId.ToString(), packageVersion)); + var globalPackage = nugetPackageRootDirectory.FindPackage(packageId.ToString(), packageVersion); - if (globalPackage != null) - { - throw new ToolPackageException( - string.Format( - CliStrings.ToolPackageConflictPackageId, - packageId, - packageVersion.ToNormalizedString())); - } - } - NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); - var package = localRepository.FindPackage(packageId.ToString(), packageVersion); + if (globalPackage != null) + { + throw new ToolPackageException( + string.Format( + CliStrings.ToolPackageConflictPackageId, + packageId, + packageVersion.ToNormalizedString())); + } - if (package == null) - { - DownloadAndExtractPackage(packageId, nugetPackageDownloader, toolDownloadDir.Value, packageVersion, packageSourceLocation, includeUnlisted: givenSpecificVersion).GetAwaiter().GetResult(); - } - else if(isGlobalTool) - { - throw new ToolPackageException( - string.Format( - CliStrings.ToolPackageConflictPackageId, - packageId, - packageVersion.ToNormalizedString())); - } - - CreateAssetFile(packageId, packageVersion, toolDownloadDir, assetFileDirectory, _runtimeJsonPath, targetFramework); - - DirectoryPath toolReturnPackageDirectory; - DirectoryPath toolReturnJsonParentDirectory; + string rollbackDirectory = _globalToolStageDir.Value; - if (isGlobalTool) - { - toolReturnPackageDirectory = _toolPackageStore.GetPackageDirectory(packageId, packageVersion); - toolReturnJsonParentDirectory = _toolPackageStore.GetPackageDirectory(packageId, packageVersion); - var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - Directory.CreateDirectory(packageRootDirectory.Value); - FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(_globalToolStageDir.Value, toolReturnPackageDirectory.Value)); - rollbackDirectory = toolReturnPackageDirectory.Value; - } - else - { - toolReturnPackageDirectory = toolDownloadDir; - toolReturnJsonParentDirectory = _localToolAssetDir; - } + return TransactionalAction.Run( + action: () => + { + DownloadTool( + packageDownloadDir: _globalToolStageDir, + packageId, + packageVersion, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory: _globalToolStageDir, + targetFramework); + + var toolStoreTargetDirectory = _toolPackageStore.GetPackageDirectory(packageId, packageVersion); + + // Create parent directory in global tool store, for example dotnet\tools\.store\powershell + Directory.CreateDirectory(toolStoreTargetDirectory.GetParentPath().Value); + + // Move tool files from stage to final location + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(_globalToolStageDir.Value, toolStoreTargetDirectory.Value)); + + rollbackDirectory = toolStoreTargetDirectory.Value; var toolPackageInstance = new ToolPackageInstance(id: packageId, - version: packageVersion, - packageDirectory: toolReturnPackageDirectory, - assetsJsonParentDirectory: toolReturnJsonParentDirectory); + version: packageVersion, + packageDirectory: toolStoreTargetDirectory, + assetsJsonParentDirectory: toolStoreTargetDirectory); if (isGlobalToolRollForward) { @@ -184,7 +195,9 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa { Directory.Delete(rollbackDirectory, true); } - // Delete the root if it is empty + + // Delete global tool store package ID directory if it's empty (ie no other versions are installed) + DirectoryPath packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); if (Directory.Exists(packageRootDirectory.Value) && !Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any()) { @@ -193,6 +206,81 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa }); } + private IToolPackage InstallLocalToolPackageInternal( + PackageSourceLocation packageSourceLocation, + NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, + PackageId packageId, + NuGetVersion packageVersion, + bool givenSpecificVersion, + string targetFramework) + { + return TransactionalAction.Run( + action: () => + { + DownloadTool( + packageDownloadDir: _localToolDownloadDir, + packageId, + packageVersion, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory: _localToolAssetDir, + targetFramework); + + var toolPackageInstance = new ToolPackageInstance(id: packageId, + version: packageVersion, + packageDirectory: _localToolDownloadDir, + assetsJsonParentDirectory: _localToolAssetDir); + + return toolPackageInstance; + }); + } + + private void DownloadTool( + DirectoryPath packageDownloadDir, + PackageId packageId, + NuGetVersion packageVersion, + INuGetPackageDownloader nugetPackageDownloader, + PackageSourceLocation packageSourceLocation, + bool givenSpecificVersion, + DirectoryPath assetFileDirectory, + string targetFramework) + { + NuGetv3LocalRepository nugetLocalRepository = new(packageDownloadDir.Value); + + var package = nugetLocalRepository.FindPackage(packageId.ToString(), packageVersion); + + if (package == null) + { + DownloadAndExtractPackage(packageId, nugetPackageDownloader, packageDownloadDir.Value, packageVersion, packageSourceLocation, includeUnlisted: givenSpecificVersion).GetAwaiter().GetResult(); + } + + CreateAssetFile(packageId, packageVersion, packageDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), _runtimeJsonPath, targetFramework); + + // Also download RID-specific package if needed + var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, packageDownloadDir, assetFileDirectory); + if (toolConfiguration.RidSpecificPackages?.Any() == true) + { + var runtimeGraph = JsonRuntimeFormat.ReadRuntimeGraph(_runtimeJsonPath); + var bestRuntimeIdentifier = Microsoft.NET.Build.Tasks.NuGetUtils.GetBestMatchingRid(runtimeGraph, RuntimeInformation.RuntimeIdentifier, toolConfiguration.RidSpecificPackages.Keys, out bool wasInGraph); + if (bestRuntimeIdentifier == null) + { + // TODO: Localize + throw new ToolPackageException($"The tool does not support the current architecture or operating system (Runtime Identifier {RuntimeInformation.RuntimeIdentifier}"); + } + + var resolvedPackage = toolConfiguration.RidSpecificPackages[bestRuntimeIdentifier]; + + var resolvedLocalPackage = nugetLocalRepository.FindPackage(resolvedPackage.Id, resolvedPackage.Version); + if (resolvedLocalPackage == null) + { + DownloadAndExtractPackage(new PackageId(resolvedPackage.Id), nugetPackageDownloader, packageDownloadDir.Value, resolvedPackage.Version, packageSourceLocation, includeUnlisted: true).GetAwaiter().GetResult(); + } + + CreateAssetFile(new PackageId(resolvedPackage.Id), resolvedPackage.Version, packageDownloadDir, Path.Combine(assetFileDirectory.Value, ToolPackageInstance.RidSpecificPackageAssetsFileName), _runtimeJsonPath, targetFramework); + } + } + // The following methods are copied from the LockFileUtils class in Nuget.Client private static void AddToolsAssets( ManagedCodeConventions managedCodeConventions, @@ -284,35 +372,55 @@ private static async Task DownloadAndExtractPackage( bool includeUnlisted = false ) { - var packagePath = await nugetPackageDownloader.DownloadPackageAsync(packageId, packageVersion, packageSourceLocation, includeUnlisted: includeUnlisted).ConfigureAwait(false); + var versionFolderPathResolver = new VersionFolderPathResolver(packagesRootPath); - // look for package on disk and read the version - NuGetVersion version; + string? folderToDeleteOnFailure = null; - using (FileStream packageStream = File.OpenRead(packagePath)) + try { - PackageArchiveReader reader = new(packageStream); - version = new NuspecReader(reader.GetNuspec()).GetVersion(); + var packagePath = await nugetPackageDownloader.DownloadPackageAsync(packageId, packageVersion, packageSourceLocation, + includeUnlisted: includeUnlisted, downloadFolder: new DirectoryPath(packagesRootPath)).ConfigureAwait(false); - var packageHash = Convert.ToBase64String(new CryptoHashProvider("SHA512").CalculateHash(reader.GetNuspec())); - var hashPath = new VersionFolderPathResolver(packagesRootPath).GetHashPath(packageId.ToString(), version); + folderToDeleteOnFailure = Path.GetDirectoryName(packagePath); - Directory.CreateDirectory(Path.GetDirectoryName(hashPath)); - File.WriteAllText(hashPath, packageHash); - } + // look for package on disk and read the version + NuGetVersion version; + + using (FileStream packageStream = File.OpenRead(packagePath)) + { + PackageArchiveReader reader = new(packageStream); + version = new NuspecReader(reader.GetNuspec()).GetVersion(); - // Extract the package - var nupkgDir = new VersionFolderPathResolver(packagesRootPath).GetInstallPath(packageId.ToString(), version); - await nugetPackageDownloader.ExtractPackageAsync(packagePath, new DirectoryPath(nupkgDir)); + var packageHash = Convert.ToBase64String(new CryptoHashProvider("SHA512").CalculateHash(reader.GetNuspec())); + var hashPath = versionFolderPathResolver.GetHashPath(packageId.ToString(), version); - return version; + Directory.CreateDirectory(Path.GetDirectoryName(hashPath)); + File.WriteAllText(hashPath, packageHash); + } + + // Extract the package + var nupkgDir = versionFolderPathResolver.GetInstallPath(packageId.ToString(), version); + await nugetPackageDownloader.ExtractPackageAsync(packagePath, new DirectoryPath(nupkgDir)); + + return version; + } + catch + { + // If something fails, don't leave a folder with partial contents (such as a .nupkg but no hash or extracted contents) + if (folderToDeleteOnFailure != null && Directory.Exists(folderToDeleteOnFailure)) + { + Directory.Delete(folderToDeleteOnFailure, true); + } + + throw; + } } private static void CreateAssetFile( PackageId packageId, NuGetVersion version, DirectoryPath packagesRootPath, - DirectoryPath assetFileDirectory, + string assetFilePath, string runtimeJsonGraph, string targetFramework = null ) @@ -329,6 +437,7 @@ private static void CreateAssetFile( Name = packageId.ToString(), Version = version, Type = LibraryType.Package, + // TODO: What about DotnetToolRidPackage type? PackageType = [PackageType.DotnetTool] }; @@ -358,6 +467,7 @@ private static void CreateAssetFile( managedCriteria.Add(standardCriteria); // Create asset file + // TODO: What about DotnetToolRidPackage type? if (lockFileLib.PackageType.Contains(PackageType.DotnetTool)) { AddToolsAssets(conventions, lockFileLib, collection, managedCriteria); @@ -371,7 +481,7 @@ private static void CreateAssetFile( }; lockFileTarget.Libraries.Add(lockFileLib); lockFile.Targets.Add(lockFileTarget); - new LockFileFormat().Write(Path.Combine(assetFileDirectory.Value, "project.assets.json"), lockFile); + new LockFileFormat().Write(assetFilePath, lockFile); } public NuGetVersion GetNuGetVersion( diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs index a294e4993cf0..62dbc64502cd 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Configuration; using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Frameworks; using NuGet.Packaging; @@ -13,205 +15,198 @@ namespace Microsoft.DotNet.Cli.ToolPackage; // This is named "ToolPackageInstance" because "ToolPackage" would conflict with the namespace internal class ToolPackageInstance : IToolPackage { - public static ToolPackageInstance CreateFromAssetFile(PackageId id, DirectoryPath assetsJsonParentDirectory) - { - var lockFile = new LockFileFormat().Read(assetsJsonParentDirectory.WithFile(AssetsFileName).Value); - var packageDirectory = new DirectoryPath(lockFile.PackageFolders[0].Path); - var library = FindLibraryInLockFile(lockFile, id); - var version = library.Version; - - return new ToolPackageInstance(id, version, packageDirectory, assetsJsonParentDirectory); - } private const string PackagedShimsDirectoryConvention = "shims"; - public IEnumerable Warnings => _toolConfiguration.Value.Warnings; + public IEnumerable Warnings { get; private set; } public PackageId Id { get; private set; } public NuGetVersion Version { get; private set; } + public PackageId ResolvedPackageId { get; private set; } + + public NuGetVersion ResolvedPackageVersion { get; private set; } + public DirectoryPath PackageDirectory { get; private set; } - public RestoredCommand Command - { - get - { - return _command.Value; - } - } + public RestoredCommand Command { get; private set; } - public IReadOnlyList PackagedShims - { - get - { - return _packagedShims.Value; - } - } + public IReadOnlyList PackagedShims { get; private set; } public IEnumerable Frameworks { get; private set; } private const string AssetsFileName = "project.assets.json"; + public const string RidSpecificPackageAssetsFileName = "project.assets.ridpackage.json"; private const string ToolSettingsFileName = "DotnetToolSettings.xml"; - private readonly Lazy _command; - private readonly Lazy _toolConfiguration; - private readonly Lazy _lockFile; - private readonly Lazy> _packagedShims; - public ToolPackageInstance(PackageId id, NuGetVersion version, DirectoryPath packageDirectory, DirectoryPath assetsJsonParentDirectory) { - _command = new Lazy(GetCommand); - _packagedShims = new Lazy>(GetPackagedShims); - Id = id; Version = version ?? throw new ArgumentNullException(nameof(version)); PackageDirectory = packageDirectory; - _toolConfiguration = new Lazy(GetToolConfiguration); - _lockFile = - new Lazy( - () => new LockFileFormat().Read(assetsJsonParentDirectory.WithFile(AssetsFileName).Value)); - var installPath = new VersionFolderPathResolver(PackageDirectory.Value).GetInstallPath(Id.ToString(), Version); - var toolsPackagePath = Path.Combine(installPath, "tools"); - Frameworks = Directory.GetDirectories(toolsPackagePath) - .Select(path => NuGetFramework.ParseFolder(Path.GetFileName(path))); - } - private RestoredCommand GetCommand() - { - try - { - LockFileTargetLibrary library = FindLibraryInLockFile(_lockFile.Value); - ToolConfiguration configuration = _toolConfiguration.Value; - LockFileItem entryPointFromLockFile = FindItemInTargetLibrary(library, configuration.ToolAssemblyEntryPoint); - if (entryPointFromLockFile == null) - { - throw new ToolConfigurationException( - string.Format( - CliStrings.MissingToolEntryPointFile, - configuration.ToolAssemblyEntryPoint, - configuration.CommandName)); - } + bool usingRidSpecificPackage = File.Exists(assetsJsonParentDirectory.WithFile(RidSpecificPackageAssetsFileName).Value); - // Currently only "dotnet" commands are supported - return new RestoredCommand( - new ToolCommandName(configuration.CommandName), - "dotnet", - LockFileRelativePathToFullFilePath(entryPointFromLockFile.Path, library)); + string resolvedAssetsFileNameFullPath; + if (usingRidSpecificPackage) + { + resolvedAssetsFileNameFullPath = assetsJsonParentDirectory.WithFile(RidSpecificPackageAssetsFileName).Value; } - catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) + else { - throw new ToolConfigurationException( - string.Format( - CliStrings.FailedToRetrieveToolConfiguration, - ex.Message), - ex); + resolvedAssetsFileNameFullPath = assetsJsonParentDirectory.WithFile(AssetsFileName).Value; } - } - private FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, LockFileTargetLibrary library) - { - return PackageDirectory - .WithSubDirectories( - Id.ToString(), - library.Version.ToNormalizedString().ToLowerInvariant()) - .WithFile(lockFileRelativePath); - } + LockFile lockFile; - private ToolConfiguration GetToolConfiguration() - { try { - var library = FindLibraryInLockFile(_lockFile.Value); - return DeserializeToolConfiguration(library); + lockFile = new LockFileFormat().Read(resolvedAssetsFileNameFullPath); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { - throw new ToolConfigurationException( + throw new ToolPackageException( string.Format( - CliStrings.FailedToRetrieveToolConfiguration, + CliStrings.FailedToReadNuGetLockFile, + Id, ex.Message), ex); } - } - private IReadOnlyList GetPackagedShims() - { - LockFileTargetLibrary library; - try - { - library = FindLibraryInLockFile(_lockFile.Value); - } - catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) + var library = FindLibraryInLockFile(lockFile); + if (library == null) { throw new ToolPackageException( string.Format( + // TODO: Change exception message CliStrings.FailedToReadNuGetLockFile, Id, - ex.Message), - ex); + resolvedAssetsFileNameFullPath)); } - IEnumerable filesUnderShimsDirectory = library - ?.ToolsAssemblies - ?.Where(t => LockFileMatcher.MatchesDirectoryPath(t, PackagedShimsDirectoryConvention)); - if (filesUnderShimsDirectory == null) + if (usingRidSpecificPackage) + { + ResolvedPackageId = new PackageId(library.Name); + ResolvedPackageVersion = library.Version; + } + else { - return []; + ResolvedPackageId = Id; + ResolvedPackageVersion = Version; } - IEnumerable allAvailableShimRuntimeIdentifiers = filesUnderShimsDirectory - .Select(f => f.Path.Split('\\', '/')?[4]) // ex: "tools/netcoreapp2.1/any/shims/osx-x64/demo" osx-x64 is at [4] - .Where(f => !string.IsNullOrEmpty(f)); + var toolConfiguration = DeserializeToolConfiguration(library, packageDirectory); + Warnings = toolConfiguration.Warnings; + + var installPath = new VersionFolderPathResolver(PackageDirectory.Value).GetInstallPath(ResolvedPackageId.ToString(), ResolvedPackageVersion); + var toolsPackagePath = Path.Combine(installPath, "tools"); + Frameworks = Directory.GetDirectories(toolsPackagePath) + .Select(path => NuGetFramework.ParseFolder(Path.GetFileName(path))); + + LockFileItem entryPointFromLockFile = FindItemInTargetLibrary(library, toolConfiguration.ToolAssemblyEntryPoint); + if (entryPointFromLockFile == null) + { + throw new ToolConfigurationException( + string.Format( + CliStrings.MissingToolEntryPointFile, + toolConfiguration.ToolAssemblyEntryPoint, + toolConfiguration.CommandName)); + } + + Command = new RestoredCommand( + new ToolCommandName(toolConfiguration.CommandName), + toolConfiguration.Runner, + LockFileRelativePathToFullFilePath(entryPointFromLockFile.Path, library)); + + IEnumerable filesUnderShimsDirectory = library + ?.ToolsAssemblies + ?.Where(t => LockFileMatcher.MatchesDirectoryPath(t, PackagedShimsDirectoryConvention)); - if (new FrameworkDependencyFile().TryGetMostFitRuntimeIdentifier( - DotnetFiles.VersionFileObject.BuildRid, - [.. allAvailableShimRuntimeIdentifiers], - out var mostFitRuntimeIdentifier)) + if (filesUnderShimsDirectory == null) { - return library?.ToolsAssemblies?.Where(l => LockFileMatcher.MatchesDirectoryPath(l, $"{PackagedShimsDirectoryConvention}/{mostFitRuntimeIdentifier}")) - .Select(l => LockFileRelativePathToFullFilePath(l.Path, library)).ToArray() ?? []; + PackagedShims = []; } else { - return []; + IEnumerable allAvailableShimRuntimeIdentifiers = filesUnderShimsDirectory + .Select(f => f.Path.Split('\\', '/')?[4]) // ex: "tools/netcoreapp2.1/any/shims/osx-x64/demo" osx-x64 is at [4] + .Where(f => !string.IsNullOrEmpty(f)); + + if (new FrameworkDependencyFile().TryGetMostFitRuntimeIdentifier( + DotnetFiles.VersionFileObject.BuildRid, + [.. allAvailableShimRuntimeIdentifiers], + out var mostFitRuntimeIdentifier)) + { + PackagedShims = library?.ToolsAssemblies?.Where(l => LockFileMatcher.MatchesDirectoryPath(l, $"{PackagedShimsDirectoryConvention}/{mostFitRuntimeIdentifier}")) + .Select(l => LockFileRelativePathToFullFilePath(l.Path, library)).ToArray() ?? []; + } + else + { + PackagedShims = []; + } } } - private ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library) + private FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, LockFileTargetLibrary library) { - var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName); - if (dotnetToolSettings == null) - { - throw new ToolConfigurationException( - CliStrings.MissingToolSettingsFile); - } + return PackageDirectory + .WithSubDirectories( + library.Name, + library.Version.ToNormalizedString().ToLowerInvariant()) + .WithFile(lockFileRelativePath); + } + - var toolConfigurationPath = - PackageDirectory - .WithSubDirectories( - Id.ToString(), - library.Version.ToNormalizedString().ToLowerInvariant()) - .WithFile(dotnetToolSettings.Path); + public static ToolConfiguration GetToolConfiguration(PackageId id, + DirectoryPath packageDirectory, + DirectoryPath assetsJsonParentDirectory) + { + var lockFile = new LockFileFormat().Read(assetsJsonParentDirectory.WithFile(AssetsFileName).Value); + var lockFileTargetLibrary = FindLibraryInLockFile(lockFile); + return DeserializeToolConfiguration(lockFileTargetLibrary, packageDirectory); - var configuration = ToolConfigurationDeserializer.Deserialize(toolConfigurationPath.Value); - return configuration; } - private static LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile, PackageId id) + private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory) { - return lockFile - ?.Targets?.SingleOrDefault(t => t.RuntimeIdentifier != null) - ?.Libraries?.SingleOrDefault(l => - string.Compare(l.Name, id.ToString(), StringComparison.OrdinalIgnoreCase) == 0); + try + { + var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName); + if (dotnetToolSettings == null) + { + throw new ToolConfigurationException( + CliStrings.MissingToolSettingsFile); + } + + var toolConfigurationPath = + packageDirectory + .WithSubDirectories( + new PackageId(library.Name).ToString(), + library.Version.ToNormalizedString().ToLowerInvariant()) + .WithFile(dotnetToolSettings.Path); + + var configuration = ToolConfigurationDeserializer.Deserialize(toolConfigurationPath.Value); + return configuration; + } + catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) + { + throw new ToolConfigurationException( + string.Format( + CliStrings.FailedToRetrieveToolConfiguration, + ex.Message), + ex); + } } - private LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile) + private static LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile) { - return FindLibraryInLockFile(lockFile, Id); + return lockFile + ?.Targets?.SingleOrDefault(t => t.RuntimeIdentifier != null) + ?.Libraries?.SingleOrDefault(); } private static LockFileItem FindItemInTargetLibrary(LockFileTargetLibrary library, string targetRelativeFilePath) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageStoreAndQuery.cs b/src/Cli/dotnet/ToolPackage/ToolPackageStoreAndQuery.cs index a66b6f3294ed..4dbdacb83120 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageStoreAndQuery.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageStoreAndQuery.cs @@ -85,6 +85,10 @@ public IEnumerable EnumeratePackageVersions(PackageId packageId) foreach (var subdirectory in Directory.EnumerateDirectories(packageRootDirectory.Value)) { + // TODO: How to handle redirected RID-specific packages? + // Probably we can restore both priamry and RID-specific packages under the same versioned folder, + // for example .dotnet\tools\.store\microsoft.dotnet-interactive\1.0.415202 + // Probably the RID-specific package assets file can be stored under a different name, such as project.assets..json yield return new ToolPackageInstance(id: packageId, version: NuGetVersion.Parse(Path.GetFileName(subdirectory)), packageDirectory: new DirectoryPath(subdirectory), diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 7962031b83bc..5f4a10a7c384 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/NuGetUtils.NuGet.cs b/src/Common/NuGetUtils.NuGet.cs similarity index 100% rename from src/Tasks/Microsoft.NET.Build.Tasks/NuGetUtils.NuGet.cs rename to src/Common/NuGetUtils.NuGet.cs diff --git a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateToolsSettingsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateToolsSettingsFile.cs index 8ae9fbf8f517..63ec4f9d2f59 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateToolsSettingsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks.UnitTests/GivenAGenerateToolsSettingsFile.cs @@ -4,6 +4,7 @@ #nullable disable using FluentAssertions; +using Microsoft.Build.Framework; using Xunit; namespace Microsoft.NET.Build.Tasks.UnitTests @@ -13,7 +14,8 @@ public class GivenAGenerateToolsSettingsFile private XDocument _generatedDocument = null; public GivenAGenerateToolsSettingsFile() { - _generatedDocument = GenerateToolsSettingsFile.GenerateDocument("tool.dll", "mytool"); + _generatedDocument = GenerateToolsSettingsFile.GenerateDocument("tool.dll", "mytool", + commandRunner: null, runtimeIdentifier: null, toolPackageId: "mytool", toolPackageVersion: "1.0.0", Array.Empty()); } [Fact] diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs index 984d372c512f..7a0935db3ba8 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs @@ -8,7 +8,8 @@ namespace Microsoft.NET.Build.Tasks public class GenerateToolsSettingsFile : TaskBase { // bump whenever the format changes such that it will break old consumers - private static readonly int _formatVersion = 1; + // TODO: Make this version 2 when tool has RID-specific packages? + //private static readonly int _formatVersion = 1; [Required] public string EntryPointRelativePath { get; set; } @@ -16,25 +17,89 @@ public class GenerateToolsSettingsFile : TaskBase [Required] public string CommandName { get; set; } + public string CommandRunner { get; set; } + + public string RuntimeIdentifier { get; set; } + + public string ToolPackageId { get; set; } + + public string ToolPackageVersion { get; set; } + + public ITaskItem[] ToolPackageRuntimeIdentifiers { get; set; } + [Required] public string ToolsSettingsFilePath { get; set; } protected override void ExecuteCore() { - GenerateDocument(EntryPointRelativePath, CommandName).Save(ToolsSettingsFilePath); + GenerateDocument(EntryPointRelativePath, CommandName, CommandRunner, RuntimeIdentifier, ToolPackageId, ToolPackageVersion, ToolPackageRuntimeIdentifiers) + .Save(ToolsSettingsFilePath); } - internal static XDocument GenerateDocument(string entryPointRelativePath, string commandName) + internal static XDocument GenerateDocument(string entryPointRelativePath, string commandName, string commandRunner, string runtimeIdentifier, + string toolPackageId, string toolPackageVersion, ITaskItem[] toolPackageRuntimeIdentifiers) { + int formatVersion = 1; + + if (string.IsNullOrEmpty(commandRunner)) + { + commandRunner = "dotnet"; + } + + if (commandRunner != "dotnet") + { + formatVersion = 2; + } + + XElement runtimeIdentifierPackagesNode = null; + XElement commandNode = new XElement("Command", + new XAttribute("Name", commandName)); + + // Only generate RuntimeIdentifierPackages node for the primary package, when RuntimeIdentifier isn't set + if (string.IsNullOrEmpty(runtimeIdentifier) && (toolPackageRuntimeIdentifiers?.Any() ?? false)) + { + formatVersion = 2; + runtimeIdentifierPackagesNode = new XElement("RuntimeIdentifierPackages"); + foreach (var runtimeIdentifierPackage in toolPackageRuntimeIdentifiers) + { + string toolPackageRuntimeIdentifier = runtimeIdentifierPackage.ItemSpec; + + var packageNode = new XElement("RuntimeIdentifierPackage"); + packageNode.Add(new XAttribute("RuntimeIdentifier", toolPackageRuntimeIdentifier)); + + string ridPackageId = toolPackageId + "." + toolPackageRuntimeIdentifier; + packageNode.Add(new XAttribute("Id", ridPackageId)); + + string ridPackageVersion = runtimeIdentifierPackage.GetMetadata("Version"); + if (string.IsNullOrEmpty(ridPackageVersion)) + { + ridPackageVersion = toolPackageVersion; + } + packageNode.Add(new XAttribute("Version", ridPackageVersion)); + + runtimeIdentifierPackagesNode.Add(packageNode); + } + } + else + { + // EntryPoint and Runner are only set in packages with tool implementation, not in primary packages + // when there are RID-specific tool packages + commandNode.Add(new XAttribute("EntryPoint", entryPointRelativePath), + new XAttribute("Runner", commandRunner)); + } + + var dotnetCliToolNode = new XElement("DotNetCliTool", + new XAttribute("Version", formatVersion), + new XElement("Commands", commandNode)); + + if (runtimeIdentifierPackagesNode != null) + { + dotnetCliToolNode.Add(runtimeIdentifierPackagesNode); + } + return new XDocument( new XDeclaration(version: null, encoding: null, standalone: null), - new XElement("DotNetCliTool", - new XAttribute("Version", _formatVersion), - new XElement("Commands", - new XElement("Command", - new XAttribute("Name", commandName), - new XAttribute("EntryPoint", entryPointRelativePath), - new XAttribute("Runner", "dotnet"))))); + dotnetCliToolNode); } } } diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj index 92392482a554..7e139a6529fd 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.NET.Build.Tasks/Microsoft.NET.Build.Tasks.csproj @@ -101,6 +101,7 @@ + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets index dbc4c9ee1bf0..0ab48c31ea49 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets @@ -22,6 +22,15 @@ Copyright (c) .NET Foundation. All rights reserved. + + + + + <_ToolPackageShouldIncludeImplementation Condition=" '$(PackAsTool)' == 'true' And + ('@(ToolPackageRuntimeIdentifier)' == '' Or '$(RuntimeIdentifier)' != '')">true + <_ToolPackageShouldIncludeImplementation Condition="'$(_ToolPackageShouldIncludeImplementation)' == ''">false + + + + <_PackToolPublishDependency Condition=" ('$(GeneratePackageOnBuild)' != 'true' and '$(NoBuild)' != 'true') and $(IsPublishable) == 'true' ">_PublishBuildAlternative + <_PackToolPublishDependency Condition=" ('$(GeneratePackageOnBuild)' == 'true' or '$(NoBuild)' == 'true') and $(IsPublishable) == 'true' ">$(_PublishNoBuildAlternativeDependsOn) + + + + + $(TargetFileName) + + $([System.IO.Path]::GetFileName($(NativeBinary))) + + + + + + + + $(PackageId).$(RuntimeIdentifier) + + + + + + + + <_GeneratedFiles Include="$(PublishDepsFilePath)" Condition="'$(GenerateDependencyFile)' != 'true' or '$(_UseBuildDependencyFile)' == 'true'" /> <_GeneratedFiles Include="$(PublishRuntimeConfigFilePath)"/> + + + + + + + + + <_GeneratedFiles Include="$(_ToolsSettingsFilePath)"/> @@ -63,15 +124,25 @@ Copyright (c) .NET Foundation. All rights reserved. $(TargetName) - $(TargetFileName) + $(RuntimeIdentifier) <_GenerateToolsSettingsFileCacheFile Condition="'$(_GenerateToolsSettingsFileCacheFile)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).toolssettingsinput.cache <_GenerateToolsSettingsFileCacheFile>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), $(_GenerateToolsSettingsFileCacheFile))) + + dotnet + executable + + <_GenerateToolsSettingsFileInputCacheToHash Include="$(ToolEntryPoint)" /> <_GenerateToolsSettingsFileInputCacheToHash Include="$(ToolCommandName)" /> + <_GenerateToolsSettingsFileInputCacheToHash Include="$(ToolCommandRunner)" /> + <_GenerateToolsSettingsFileInputCacheToHash Include="$(ToolRuntimeIdentifier)" /> + <_GenerateToolsSettingsFileInputCacheToHash Include="$(PackageId)" /> + <_GenerateToolsSettingsFileInputCacheToHash Include="$(Version)" /> + <_GenerateToolsSettingsFileInputCacheToHash Include="@(ToolPackageRuntimeIdentifier->'%(Identity)%(Id)%(Version)')" /> @@ -98,6 +169,11 @@ Copyright (c) .NET Foundation. All rights reserved. @@ -111,9 +187,6 @@ Copyright (c) .NET Foundation. All rights reserved. - - diff --git a/test/Microsoft.DotNet.PackageInstall.Tests/ToolConfigurationDeserializerTests.cs b/test/Microsoft.DotNet.PackageInstall.Tests/ToolConfigurationDeserializerTests.cs index a484f838081e..acff382cd166 100644 --- a/test/Microsoft.DotNet.PackageInstall.Tests/ToolConfigurationDeserializerTests.cs +++ b/test/Microsoft.DotNet.PackageInstall.Tests/ToolConfigurationDeserializerTests.cs @@ -63,7 +63,7 @@ public void GivenMinorHigherVersionItHasNoWarning() public void GivenInvalidCharAsFileNameItThrows() { var invalidCommandName = "na\0me"; - Action a = () => new ToolConfiguration(invalidCommandName, "my.dll"); + Action a = () => new ToolConfiguration(invalidCommandName, "my.dll", "dotnet"); a.Should().Throw() .And.Message.Should() .Contain( @@ -77,7 +77,7 @@ public void GivenInvalidCharAsFileNameItThrows() public void GivenALeadingDotAsFileNameItThrows() { var invalidCommandName = ".mytool"; - Action a = () => new ToolConfiguration(invalidCommandName, "my.dll"); + Action a = () => new ToolConfiguration(invalidCommandName, "my.dll", "dotnet"); a.Should().Throw() .And.Message.Should() .Contain(string.Format( diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageDownloaderMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageDownloaderMock.cs index 4aa8504a6d32..59a984fb9d49 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageDownloaderMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageDownloaderMock.cs @@ -343,6 +343,10 @@ private class TestToolPackage : IToolPackage public IReadOnlyList PackagedShims { get; set; } public IEnumerable Frameworks => throw new NotImplementedException(); + + public PackageId ResolvedPackageId { get; set; } + + public NuGetVersion ResolvedPackageVersion { get; set; } } } } diff --git a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs index 78c9c25900f1..1044d4c378bd 100644 --- a/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs +++ b/test/Microsoft.DotNet.Tools.Tests.ComponentMocks/ToolPackageMock.cs @@ -64,6 +64,10 @@ public IReadOnlyList PackagedShims public IEnumerable Frameworks { get; private set; } + public PackageId ResolvedPackageId { get; private set; } + + public NuGetVersion ResolvedPackageVersion { get; private set; } + private RestoredCommand GetCommand() { try