From 3eb29e49270324f58d428aab8c8d3a2e38bbc834 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Sun, 6 Apr 2025 23:57:57 -0400 Subject: [PATCH 01/11] Start of support for packing tools with RID-specific packages --- .../ToolConfigurationDeserializer.cs | 2 +- .../ToolPackage/ToolPackageDownloader.cs | 2 + .../GivenAGenerateToolsSettingsFile.cs | 4 +- .../GenerateToolsSettingsFile.cs | 89 ++++++++++++++++--- .../targets/Microsoft.NET.PackTool.targets | 45 +++++++++- 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs index 8750465a1ce4..935da6d34929 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs @@ -11,7 +11,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) { diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 9b1b244486c8..9b6802762943 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -329,6 +329,7 @@ private static void CreateAssetFile( Name = packageId.ToString(), Version = version, Type = LibraryType.Package, + // TODO: What about DotnetToolRidPackage type? PackageType = [PackageType.DotnetTool] }; @@ -358,6 +359,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); 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..be6e69c0d5e7 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,93 @@ 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 = runtimeIdentifierPackage.GetMetadata("Id"); + if (string.IsNullOrEmpty(ridPackageId)) + { + 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/targets/Microsoft.NET.PackTool.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.PackTool.targets index dbc4c9ee1bf0..9755c647cbfd 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 @@ -29,14 +29,41 @@ Copyright (c) .NET Foundation. All rights reserved. <_ToolsSettingsFilePath>$(IntermediateOutputPath)DotnetToolSettings.xml true - <_PackToolPublishDependency Condition=" ('$(GeneratePackageOnBuild)' != 'true' and '$(NoBuild)' != 'true') and $(IsPublishable) == 'true' ">_PublishBuildAlternative - <_PackToolPublishDependency Condition=" ('$(GeneratePackageOnBuild)' == 'true' or '$(NoBuild)' == 'true') and $(IsPublishable) == 'true' ">$(_PublishNoBuildAlternativeDependsOn) + - + + + + + + <_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) + + + + + <_GeneratedFiles Include="$(PublishDepsFilePath)" Condition="'$(GenerateDependencyFile)' != 'true' or '$(_UseBuildDependencyFile)' == 'true'" /> <_GeneratedFiles Include="$(PublishRuntimeConfigFilePath)"/> + + + + + + + + + <_GeneratedFiles Include="$(_ToolsSettingsFilePath)"/> @@ -64,6 +91,8 @@ Copyright (c) .NET Foundation. All rights reserved. $(TargetName) $(TargetFileName) + dotnet + $(RuntimeIdentifier) <_GenerateToolsSettingsFileCacheFile Condition="'$(_GenerateToolsSettingsFileCacheFile)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).toolssettingsinput.cache <_GenerateToolsSettingsFileCacheFile>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), $(_GenerateToolsSettingsFileCacheFile))) @@ -72,6 +101,11 @@ Copyright (c) .NET Foundation. All rights reserved. <_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 +132,11 @@ Copyright (c) .NET Foundation. All rights reserved. From f182648c7f6cefce2858b4cbd2d38fc94ef092a7 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Mon, 7 Apr 2025 20:43:25 -0400 Subject: [PATCH 02/11] Support packing NativeAot .NET Tools --- .../GenerateToolsSettingsFile.cs | 6 +-- .../targets/Microsoft.NET.PackTool.targets | 48 +++++++++++++++---- 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs index be6e69c0d5e7..7a0935db3ba8 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs +++ b/src/Tasks/Microsoft.NET.Build.Tasks/GenerateToolsSettingsFile.cs @@ -67,11 +67,7 @@ internal static XDocument GenerateDocument(string entryPointRelativePath, string var packageNode = new XElement("RuntimeIdentifierPackage"); packageNode.Add(new XAttribute("RuntimeIdentifier", toolPackageRuntimeIdentifier)); - string ridPackageId = runtimeIdentifierPackage.GetMetadata("Id"); - if (string.IsNullOrEmpty(ridPackageId)) - { - ridPackageId = toolPackageId + "." + toolPackageRuntimeIdentifier; - } + string ridPackageId = toolPackageId + "." + toolPackageRuntimeIdentifier; packageNode.Add(new XAttribute("Id", ridPackageId)); string ridPackageVersion = runtimeIdentifierPackage.GetMetadata("Version"); 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 9755c647cbfd..867cb0e74674 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. + + + @@ -46,7 +53,32 @@ Copyright (c) .NET Foundation. All rights reserved. <_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) + + @@ -90,13 +122,16 @@ Copyright (c) .NET Foundation. All rights reserved. $(TargetName) - $(TargetFileName) - dotnet $(RuntimeIdentifier) <_GenerateToolsSettingsFileCacheFile Condition="'$(_GenerateToolsSettingsFileCacheFile)' == ''">$(IntermediateOutputPath)$(MSBuildProjectName).toolssettingsinput.cache <_GenerateToolsSettingsFileCacheFile>$([MSBuild]::NormalizePath($(MSBuildProjectDirectory), $(_GenerateToolsSettingsFileCacheFile))) + + dotnet + executable + + <_GenerateToolsSettingsFileInputCacheToHash Include="$(ToolEntryPoint)" /> @@ -150,9 +185,6 @@ Copyright (c) .NET Foundation. All rights reserved. - - From bf848853619088ebe16c3cbd6c6ceac7cb244509 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Thu, 10 Apr 2025 07:31:37 -0400 Subject: [PATCH 03/11] Start of support for downloading RID-specific tool packages --- src/Cli/dotnet/ToolPackage/IToolPackage.cs | 4 + .../ToolPackage/LocalToolsResolverCache.cs | 3 + .../ToolPackage/RestoredCommandIdentifier.cs | 3 + .../dotnet/ToolPackage/ToolConfiguration.cs | 8 + .../DotNetCliTool.cs | 3 + .../DotNetCliToolRuntimeIdentifierPackage.cs | 22 +++ .../ToolConfigurationDeserializer.cs | 7 +- .../ToolPackage/ToolPackageDownloader.cs | 164 +++++++++++++----- .../dotnet/ToolPackage/ToolPackageInstance.cs | 35 ++-- .../ToolPackage/ToolPackageStoreAndQuery.cs | 4 + src/Cli/dotnet/dotnet.csproj | 1 + .../NuGetUtils.NuGet.cs | 0 .../Microsoft.NET.Build.Tasks.csproj | 1 + .../targets/Microsoft.NET.PackTool.targets | 2 + .../ToolPackageDownloaderMock.cs | 4 + .../ToolPackageMock.cs | 4 + 16 files changed, 208 insertions(+), 57 deletions(-) create mode 100644 src/Cli/dotnet/ToolPackage/ToolConfigurationDeserialization/DotNetCliToolRuntimeIdentifierPackage.cs rename src/{Tasks/Microsoft.NET.Build.Tasks => Common}/NuGetUtils.NuGet.cs (100%) 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..172446d81633 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,7 @@ internal class ToolConfiguration public ToolConfiguration( string commandName, string toolAssemblyEntryPoint, + IDictionary ridSpecificPackages = null, IEnumerable warnings = null) { if (string.IsNullOrWhiteSpace(commandName)) @@ -55,7 +58,12 @@ private static void EnsureNoLeadingDot(string commandName) } } + + public string CommandName { get; } public string ToolAssemblyEntryPoint { 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 935da6d34929..6bb782f04eae 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; @@ -60,10 +61,14 @@ 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); + 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 9b6802762943..7c3bdc52a521 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -76,7 +76,7 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa ) { var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - string rollbackDirectory = null; + return TransactionalAction.Run( action: () => @@ -114,7 +114,7 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa } NuGetVersion packageVersion = nugetPackageDownloader.GetBestPackageVersionAsync(packageId, versionRange, packageSourceLocation).GetAwaiter().GetResult(); - rollbackDirectory = isGlobalTool ? toolDownloadDir.Value: new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); + if (isGlobalTool) { @@ -131,41 +131,59 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa } } NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); - var package = localRepository.FindPackage(packageId.ToString(), packageVersion); - 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; - 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 + // Refactored from inner method to direct call + (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( + isGlobalTool, + toolDownloadDir, + packageId, + packageVersion, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + + var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); + if (toolConfiguration.RidSpecificPackages?.Any() == true) { - toolReturnPackageDirectory = toolDownloadDir; - toolReturnJsonParentDirectory = _localToolAssetDir; + 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( + isGlobalTool, + toolDownloadDir, + new PackageId(resolvedPackage.Id), + resolvedPackage.Version, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package + // TODO: Probably can't use the same asset directory as the primary package + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); } + var toolPackageInstance = new ToolPackageInstance(id: packageId, version: packageVersion, packageDirectory: toolReturnPackageDirectory, @@ -177,22 +195,82 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa } return toolPackageInstance; - }, - rollback: () => - { - if (rollbackDirectory != null && Directory.Exists(rollbackDirectory)) - { - Directory.Delete(rollbackDirectory, true); - } - // Delete the root if it is empty - if (Directory.Exists(packageRootDirectory.Value) && - !Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any()) - { - Directory.Delete(packageRootDirectory.Value, false); - } }); } + private (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( + bool isGlobalTool, + DirectoryPath toolDownloadDir, + PackageId packageId, + NuGetVersion packageVersion, + NuGetv3LocalRepository localRepository, + INuGetPackageDownloader nugetPackageDownloader, + PackageSourceLocation packageSourceLocation, + bool givenSpecificVersion, + DirectoryPath assetFileDirectory, + string targetFramework, + DirectoryPath packageRootDirectory, + string runtimeJsonPath, + IToolPackageStore toolPackageStore, + DirectoryPath globalToolStageDir) + { + string rollbackDirectory = null; + rollbackDirectory = isGlobalTool ? toolDownloadDir.Value : new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); + + return TransactionalAction.Run(() => + { + DirectoryPath toolReturnPackageDirectory; + DirectoryPath toolReturnJsonParentDirectory; + + var package = localRepository.FindPackage(packageId.ToString(), packageVersion); + + 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); + + if (isGlobalTool) + { + toolReturnPackageDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); + toolReturnJsonParentDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); + var rootPackageDirectory = toolPackageStore.GetRootPackageDirectory(packageId); + Directory.CreateDirectory(rootPackageDirectory.Value); + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(globalToolStageDir.Value, toolReturnPackageDirectory.Value)); + rollbackDirectory = toolReturnPackageDirectory.Value; + } + else + { + toolReturnPackageDirectory = toolDownloadDir; + toolReturnJsonParentDirectory = assetFileDirectory; + } + + return (toolReturnPackageDirectory, toolReturnJsonParentDirectory); + }, + rollback: () => + { + if (rollbackDirectory != null && Directory.Exists(rollbackDirectory)) + { + Directory.Delete(rollbackDirectory, true); + } + // Delete the root if it is empty + if (Directory.Exists(packageRootDirectory.Value) && + !Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any()) + { + Directory.Delete(packageRootDirectory.Value, false); + } + }); + } + // The following methods are copied from the LockFileUtils class in Nuget.Client private static void AddToolsAssets( ManagedCodeConventions managedCodeConventions, diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs index a294e4993cf0..5d4744ffc6d1 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs @@ -13,15 +13,6 @@ 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; @@ -30,6 +21,10 @@ public static ToolPackageInstance CreateFromAssetFile(PackageId id, DirectoryPat 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 @@ -54,7 +49,10 @@ public IReadOnlyList PackagedShims private const string ToolSettingsFileName = "DotnetToolSettings.xml"; private readonly Lazy _command; + + // TODO: This probably doesn't need to be lazy private readonly Lazy _toolConfiguration; + private readonly Lazy _lockFile; private readonly Lazy> _packagedShims; @@ -120,12 +118,23 @@ private FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, .WithFile(lockFileRelativePath); } + + public static ToolConfiguration GetToolConfiguration(PackageId id, + DirectoryPath packageDirectory, + DirectoryPath assetsJsonParentDirectory) + { + var lockFile = new LockFileFormat().Read(assetsJsonParentDirectory.WithFile(AssetsFileName).Value); + var lockFileTargetLibrary = FindLibraryInLockFile(lockFile, id); + return DeserializeToolConfiguration(lockFileTargetLibrary, packageDirectory, id); + + } + private ToolConfiguration GetToolConfiguration() { try { var library = FindLibraryInLockFile(_lockFile.Value); - return DeserializeToolConfiguration(library); + return DeserializeToolConfiguration(library, PackageDirectory, Id); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { @@ -181,7 +190,7 @@ private IReadOnlyList GetPackagedShims() } } - private ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library) + private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory, PackageId id) { var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName); if (dotnetToolSettings == null) @@ -191,9 +200,9 @@ private ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary lib } var toolConfigurationPath = - PackageDirectory + packageDirectory .WithSubDirectories( - Id.ToString(), + id.ToString(), library.Version.ToNormalizedString().ToLowerInvariant()) .WithFile(dotnetToolSettings.Path); 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/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 867cb0e74674..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 @@ -79,6 +79,8 @@ Copyright (c) .NET Foundation. All rights reserved. $(PackageId).$(RuntimeIdentifier) + + 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 From 0782a4e8a5ed8a5b37dc13e080a38cb9ee13c86c Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Tue, 15 Apr 2025 18:16:45 -0400 Subject: [PATCH 04/11] Copilot WIP --- .../ToolPackage/ToolPackageDownloader.cs | 177 +++++++++++++++--- 1 file changed, 156 insertions(+), 21 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 7c3bdc52a521..8a16523e2354 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -74,9 +74,43 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa bool verifySignatures = true, RestoreActionConfig restoreActionConfig = null ) + { + if (isGlobalTool) + { + return InstallGlobalToolPackage( + packageLocation, + packageId, + verbosity, + versionRange, + targetFramework, + isGlobalToolRollForward, + verifySignatures, + restoreActionConfig); + } + else + { + return InstallLocalToolPackage( + packageLocation, + packageId, + verbosity, + versionRange, + targetFramework, + verifySignatures, + restoreActionConfig); + } + } + + private IToolPackage InstallGlobalToolPackage( + PackageLocation packageLocation, + PackageId packageId, + VerbosityOptions verbosity = VerbosityOptions.normal, + VersionRange versionRange = null, + string targetFramework = null, + bool isGlobalToolRollForward = false, + bool verifySignatures = true, + RestoreActionConfig restoreActionConfig = null) { var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - return TransactionalAction.Run( action: () => @@ -93,8 +127,8 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa versionRange = VersionRange.Parse(versionString); } - var toolDownloadDir = isGlobalTool ? _globalToolStageDir : _localToolDownloadDir; - var assetFileDirectory = isGlobalTool ? _globalToolStageDir : _localToolAssetDir; + var toolDownloadDir = _globalToolStageDir; + var assetFileDirectory = _globalToolStageDir; var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( toolDownloadDir, @@ -114,30 +148,26 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa } NuGetVersion packageVersion = nugetPackageDownloader.GetBestPackageVersionAsync(packageId, versionRange, packageSourceLocation).GetAwaiter().GetResult(); - + // 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 (isGlobalTool) + if (globalPackage != null) { - 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())); - } + throw new ToolPackageException( + string.Format( + CliStrings.ToolPackageConflictPackageId, + packageId, + packageVersion.ToNormalizedString())); } + NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); DirectoryPath toolReturnPackageDirectory; DirectoryPath toolReturnJsonParentDirectory; - // Refactored from inner method to direct call (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - isGlobalTool, + true, // isGlobalTool toolDownloadDir, packageId, packageVersion, @@ -166,7 +196,7 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa var resolvedPackage = toolConfiguration.RidSpecificPackages[bestRuntimeIdentifier]; var (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - isGlobalTool, + true, // isGlobalTool toolDownloadDir, new PackageId(resolvedPackage.Id), resolvedPackage.Version, @@ -174,7 +204,6 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa nugetPackageDownloader, packageSourceLocation, givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - // TODO: Probably can't use the same asset directory as the primary package assetFileDirectory, targetFramework, packageRootDirectory, @@ -183,7 +212,6 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa _globalToolStageDir); } - var toolPackageInstance = new ToolPackageInstance(id: packageId, version: packageVersion, packageDirectory: toolReturnPackageDirectory, @@ -198,6 +226,113 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa }); } + private IToolPackage InstallLocalToolPackage( + PackageLocation packageLocation, + PackageId packageId, + VerbosityOptions verbosity = VerbosityOptions.normal, + VersionRange versionRange = null, + string targetFramework = null, + bool verifySignatures = true, + RestoreActionConfig restoreActionConfig = null) + { + var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); + + return TransactionalAction.Run( + action: () => + { + ILogger nugetLogger = new NullLogger(); + if (verbosity.IsDetailedOrDiagnostic()) + { + nugetLogger = new NuGetConsoleLogger(); + } + + if (versionRange == null) + { + var versionString = "*"; + versionRange = VersionRange.Parse(versionString); + } + + var toolDownloadDir = _localToolDownloadDir; + var assetFileDirectory = _localToolAssetDir; + + 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); + + 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(); + + NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + + DirectoryPath toolReturnPackageDirectory; + DirectoryPath toolReturnJsonParentDirectory; + + (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( + false, // isGlobalTool + toolDownloadDir, + packageId, + packageVersion, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + + var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); + 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( + false, // isGlobalTool + toolDownloadDir, + new PackageId(resolvedPackage.Id), + resolvedPackage.Version, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + } + + var toolPackageInstance = new ToolPackageInstance(id: packageId, + version: packageVersion, + packageDirectory: toolReturnPackageDirectory, + assetsJsonParentDirectory: toolReturnJsonParentDirectory); + + return toolPackageInstance; + }); + } + private (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( bool isGlobalTool, DirectoryPath toolDownloadDir, From 4bfb03f3d8b3defb956d5eb2e6d04e1947518a17 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Tue, 15 Apr 2025 18:25:16 -0400 Subject: [PATCH 05/11] Copilot WIP --- .../ToolPackage/ToolPackageDownloader.cs | 442 +++++++++--------- 1 file changed, 220 insertions(+), 222 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 8a16523e2354..244c39f84107 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -74,263 +74,261 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa bool verifySignatures = true, RestoreActionConfig restoreActionConfig = null ) - { - if (isGlobalTool) - { - return InstallGlobalToolPackage( - packageLocation, - packageId, - verbosity, - versionRange, - targetFramework, - isGlobalToolRollForward, - verifySignatures, - restoreActionConfig); - } - else - { - return InstallLocalToolPackage( - packageLocation, - packageId, - verbosity, - versionRange, - targetFramework, - verifySignatures, - restoreActionConfig); - } - } - - private IToolPackage InstallGlobalToolPackage( - PackageLocation packageLocation, - PackageId packageId, - VerbosityOptions verbosity = VerbosityOptions.normal, - VersionRange versionRange = null, - string targetFramework = null, - bool isGlobalToolRollForward = false, - bool verifySignatures = true, - RestoreActionConfig restoreActionConfig = null) { var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - + return TransactionalAction.Run( action: () => { - ILogger nugetLogger = new NullLogger(); - if (verbosity.IsDetailedOrDiagnostic()) + if (isGlobalTool) { - nugetLogger = new NuGetConsoleLogger(); + return InstallGlobalToolPackageInternal( + packageLocation, + packageId, + verbosity, + versionRange, + targetFramework, + isGlobalToolRollForward, + verifySignatures, + restoreActionConfig, + packageRootDirectory); } - - if (versionRange == null) + else { - var versionString = "*"; - versionRange = VersionRange.Parse(versionString); + return InstallLocalToolPackageInternal( + packageLocation, + packageId, + verbosity, + versionRange, + targetFramework, + verifySignatures, + restoreActionConfig, + packageRootDirectory); } + }); + } - var toolDownloadDir = _globalToolStageDir; - var assetFileDirectory = _globalToolStageDir; + private IToolPackage InstallGlobalToolPackageInternal( + PackageLocation packageLocation, + PackageId packageId, + VerbosityOptions verbosity, + VersionRange versionRange, + string targetFramework, + bool isGlobalToolRollForward, + bool verifySignatures, + RestoreActionConfig restoreActionConfig, + DirectoryPath packageRootDirectory) + { + ILogger nugetLogger = new NullLogger(); + if (verbosity.IsDetailedOrDiagnostic()) + { + nugetLogger = new NuGetConsoleLogger(); + } - var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( - toolDownloadDir, - verboseLogger: nugetLogger, - verifySignatures: verifySignatures, - shouldUsePackageSourceMapping: true, - restoreActionConfig: restoreActionConfig, - verbosityOptions: verbosity, - currentWorkingDirectory: _currentWorkingDirectory); + if (versionRange == null) + { + var versionString = "*"; + versionRange = VersionRange.Parse(versionString); + } - var packageSourceLocation = new PackageSourceLocation(packageLocation.NugetConfig, packageLocation.RootConfigDirectory, packageLocation.SourceFeedOverrides, packageLocation.AdditionalFeeds); + var toolDownloadDir = _globalToolStageDir; + var assetFileDirectory = _globalToolStageDir; - 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(); + var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( + toolDownloadDir, + verboseLogger: nugetLogger, + verifySignatures: verifySignatures, + shouldUsePackageSourceMapping: true, + restoreActionConfig: restoreActionConfig, + verbosityOptions: verbosity, + currentWorkingDirectory: _currentWorkingDirectory); - // 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); + var packageSourceLocation = new PackageSourceLocation(packageLocation.NugetConfig, packageLocation.RootConfigDirectory, packageLocation.SourceFeedOverrides, packageLocation.AdditionalFeeds); - if (globalPackage != null) - { - throw new ToolPackageException( - string.Format( - CliStrings.ToolPackageConflictPackageId, - packageId, - packageVersion.ToNormalizedString())); - } - - NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + 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(); - DirectoryPath toolReturnPackageDirectory; - DirectoryPath toolReturnJsonParentDirectory; + // 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); - (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - true, // isGlobalTool - toolDownloadDir, + if (globalPackage != null) + { + throw new ToolPackageException( + string.Format( + CliStrings.ToolPackageConflictPackageId, packageId, - packageVersion, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion, - assetFileDirectory, - targetFramework, - packageRootDirectory, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - - var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); - 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}"); - } + packageVersion.ToNormalizedString())); + } + + NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + + DirectoryPath toolReturnPackageDirectory; + DirectoryPath toolReturnJsonParentDirectory; + + (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( + true, // isGlobalTool + toolDownloadDir, + packageId, + packageVersion, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + + var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); + 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - true, // isGlobalTool - toolDownloadDir, - new PackageId(resolvedPackage.Id), - resolvedPackage.Version, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - assetFileDirectory, - targetFramework, - packageRootDirectory, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - } + var resolvedPackage = toolConfiguration.RidSpecificPackages[bestRuntimeIdentifier]; + + var (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( + true, // isGlobalTool + toolDownloadDir, + new PackageId(resolvedPackage.Id), + resolvedPackage.Version, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + } - var toolPackageInstance = new ToolPackageInstance(id: packageId, - version: packageVersion, - packageDirectory: toolReturnPackageDirectory, - assetsJsonParentDirectory: toolReturnJsonParentDirectory); + var toolPackageInstance = new ToolPackageInstance(id: packageId, + version: packageVersion, + packageDirectory: toolReturnPackageDirectory, + assetsJsonParentDirectory: toolReturnJsonParentDirectory); - if (isGlobalToolRollForward) - { - UpdateRuntimeConfig(toolPackageInstance); - } + if (isGlobalToolRollForward) + { + UpdateRuntimeConfig(toolPackageInstance); + } - return toolPackageInstance; - }); + return toolPackageInstance; } - private IToolPackage InstallLocalToolPackage( + private IToolPackage InstallLocalToolPackageInternal( PackageLocation packageLocation, PackageId packageId, - VerbosityOptions verbosity = VerbosityOptions.normal, - VersionRange versionRange = null, - string targetFramework = null, - bool verifySignatures = true, - RestoreActionConfig restoreActionConfig = null) + VerbosityOptions verbosity, + VersionRange versionRange, + string targetFramework, + bool verifySignatures, + RestoreActionConfig restoreActionConfig, + DirectoryPath packageRootDirectory) { - var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - - return TransactionalAction.Run( - action: () => - { - ILogger nugetLogger = new NullLogger(); - if (verbosity.IsDetailedOrDiagnostic()) - { - nugetLogger = new NuGetConsoleLogger(); - } - - if (versionRange == null) - { - var versionString = "*"; - versionRange = VersionRange.Parse(versionString); - } - - var toolDownloadDir = _localToolDownloadDir; - var assetFileDirectory = _localToolAssetDir; - - var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( - toolDownloadDir, - verboseLogger: nugetLogger, - verifySignatures: verifySignatures, - shouldUsePackageSourceMapping: true, - restoreActionConfig: restoreActionConfig, - verbosityOptions: verbosity, - currentWorkingDirectory: _currentWorkingDirectory); + ILogger nugetLogger = new NullLogger(); + if (verbosity.IsDetailedOrDiagnostic()) + { + nugetLogger = new NuGetConsoleLogger(); + } - var packageSourceLocation = new PackageSourceLocation(packageLocation.NugetConfig, packageLocation.RootConfigDirectory, packageLocation.SourceFeedOverrides, packageLocation.AdditionalFeeds); + if (versionRange == null) + { + var versionString = "*"; + versionRange = VersionRange.Parse(versionString); + } - 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(); + var toolDownloadDir = _localToolDownloadDir; + var assetFileDirectory = _localToolAssetDir; - NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( + toolDownloadDir, + verboseLogger: nugetLogger, + verifySignatures: verifySignatures, + shouldUsePackageSourceMapping: true, + restoreActionConfig: restoreActionConfig, + verbosityOptions: verbosity, + currentWorkingDirectory: _currentWorkingDirectory); - DirectoryPath toolReturnPackageDirectory; - DirectoryPath toolReturnJsonParentDirectory; + var packageSourceLocation = new PackageSourceLocation(packageLocation.NugetConfig, packageLocation.RootConfigDirectory, packageLocation.SourceFeedOverrides, packageLocation.AdditionalFeeds); - (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - false, // isGlobalTool - toolDownloadDir, - packageId, - packageVersion, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion, - assetFileDirectory, - targetFramework, - packageRootDirectory, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - - var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); - 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}"); - } + 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(); + + NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + + DirectoryPath toolReturnPackageDirectory; + DirectoryPath toolReturnJsonParentDirectory; + + (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( + false, // isGlobalTool + toolDownloadDir, + packageId, + packageVersion, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + + var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); + 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - false, // isGlobalTool - toolDownloadDir, - new PackageId(resolvedPackage.Id), - resolvedPackage.Version, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - assetFileDirectory, - targetFramework, - packageRootDirectory, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - } + var resolvedPackage = toolConfiguration.RidSpecificPackages[bestRuntimeIdentifier]; + + var (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( + false, // isGlobalTool + toolDownloadDir, + new PackageId(resolvedPackage.Id), + resolvedPackage.Version, + localRepository, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package + assetFileDirectory, + targetFramework, + packageRootDirectory, + _runtimeJsonPath, + _toolPackageStore, + _globalToolStageDir); + } - var toolPackageInstance = new ToolPackageInstance(id: packageId, - version: packageVersion, - packageDirectory: toolReturnPackageDirectory, - assetsJsonParentDirectory: toolReturnJsonParentDirectory); + var toolPackageInstance = new ToolPackageInstance(id: packageId, + version: packageVersion, + packageDirectory: toolReturnPackageDirectory, + assetsJsonParentDirectory: toolReturnJsonParentDirectory); - return toolPackageInstance; - }); + return toolPackageInstance; } private (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( From 126a25fb164a1895f32988e1a7b7d539dc43c15d Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Tue, 15 Apr 2025 22:04:24 -0400 Subject: [PATCH 06/11] Refactoring --- .../NuGetPackageDownloader.cs | 8 +- .../ToolPackage/ToolPackageDownloader.cs | 150 +++++++----------- 2 files changed, 65 insertions(+), 93 deletions(-) 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/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 244c39f84107..ef64eb3a7904 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -75,82 +75,82 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa RestoreActionConfig restoreActionConfig = null ) { - var packageRootDirectory = _toolPackageStore.GetRootPackageDirectory(packageId); - + + ILogger nugetLogger = new NullLogger(); + if (verbosity.IsDetailedOrDiagnostic()) + { + nugetLogger = new NuGetConsoleLogger(); + } + + if (versionRange == null) + { + var versionString = "*"; + versionRange = VersionRange.Parse(versionString); + } + + var nugetPackageDownloader = new NuGetPackageDownloader.NuGetPackageDownloader( + new DirectoryPath(), + verboseLogger: nugetLogger, + verifySignatures: verifySignatures, + shouldUsePackageSourceMapping: true, + restoreActionConfig: restoreActionConfig, + verbosityOptions: verbosity, + currentWorkingDirectory: _currentWorkingDirectory); + + return TransactionalAction.Run( action: () => { + 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; + } + + if (isGlobalTool) { return InstallGlobalToolPackageInternal( - packageLocation, + packageSourceLocation, + nugetPackageDownloader, packageId, - verbosity, - versionRange, + packageVersion, + givenSpecificVersion, targetFramework, - isGlobalToolRollForward, - verifySignatures, - restoreActionConfig, - packageRootDirectory); + isGlobalToolRollForward); } else { return InstallLocalToolPackageInternal( - packageLocation, - packageId, - verbosity, - versionRange, - targetFramework, - verifySignatures, - restoreActionConfig, - packageRootDirectory); + packageSourceLocation, + nugetPackageDownloader, + packageId, + packageVersion, + givenSpecificVersion, + targetFramework); } }); } private IToolPackage InstallGlobalToolPackageInternal( - PackageLocation packageLocation, + PackageSourceLocation packageSourceLocation, + NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, PackageId packageId, - VerbosityOptions verbosity, - VersionRange versionRange, + NuGetVersion packageVersion, + bool givenSpecificVersion, string targetFramework, - bool isGlobalToolRollForward, - bool verifySignatures, - RestoreActionConfig restoreActionConfig, - DirectoryPath packageRootDirectory) + bool isGlobalToolRollForward) { - ILogger nugetLogger = new NullLogger(); - if (verbosity.IsDetailedOrDiagnostic()) - { - nugetLogger = new NuGetConsoleLogger(); - } - if (versionRange == null) - { - var versionString = "*"; - versionRange = VersionRange.Parse(versionString); - } var toolDownloadDir = _globalToolStageDir; var assetFileDirectory = _globalToolStageDir; - 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); - 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(); // Check if package already exists in global tools location NuGetv3LocalRepository nugetPackageRootDirectory = new(new VersionFolderPathResolver(_toolPackageStore.Root.Value).GetInstallPath(packageId.ToString(), packageVersion)); @@ -181,7 +181,6 @@ private IToolPackage InstallGlobalToolPackageInternal( givenSpecificVersion, assetFileDirectory, targetFramework, - packageRootDirectory, _runtimeJsonPath, _toolPackageStore, _globalToolStageDir); @@ -210,7 +209,6 @@ private IToolPackage InstallGlobalToolPackageInternal( givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package assetFileDirectory, targetFramework, - packageRootDirectory, _runtimeJsonPath, _toolPackageStore, _globalToolStageDir); @@ -230,48 +228,17 @@ private IToolPackage InstallGlobalToolPackageInternal( } private IToolPackage InstallLocalToolPackageInternal( - PackageLocation packageLocation, + PackageSourceLocation packageSourceLocation, + NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, PackageId packageId, - VerbosityOptions verbosity, - VersionRange versionRange, - string targetFramework, - bool verifySignatures, - RestoreActionConfig restoreActionConfig, - DirectoryPath packageRootDirectory) + NuGetVersion packageVersion, + bool givenSpecificVersion, + string targetFramework) { - ILogger nugetLogger = new NullLogger(); - if (verbosity.IsDetailedOrDiagnostic()) - { - nugetLogger = new NuGetConsoleLogger(); - } - - if (versionRange == null) - { - var versionString = "*"; - versionRange = VersionRange.Parse(versionString); - } var toolDownloadDir = _localToolDownloadDir; var assetFileDirectory = _localToolAssetDir; - 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); - - 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(); - NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); DirectoryPath toolReturnPackageDirectory; @@ -288,7 +255,6 @@ private IToolPackage InstallLocalToolPackageInternal( givenSpecificVersion, assetFileDirectory, targetFramework, - packageRootDirectory, _runtimeJsonPath, _toolPackageStore, _globalToolStageDir); @@ -317,7 +283,6 @@ private IToolPackage InstallLocalToolPackageInternal( givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package assetFileDirectory, targetFramework, - packageRootDirectory, _runtimeJsonPath, _toolPackageStore, _globalToolStageDir); @@ -331,7 +296,7 @@ private IToolPackage InstallLocalToolPackageInternal( return toolPackageInstance; } - private (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( + private static (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( bool isGlobalTool, DirectoryPath toolDownloadDir, PackageId packageId, @@ -342,11 +307,12 @@ private IToolPackage InstallLocalToolPackageInternal( bool givenSpecificVersion, DirectoryPath assetFileDirectory, string targetFramework, - DirectoryPath packageRootDirectory, string runtimeJsonPath, IToolPackageStore toolPackageStore, DirectoryPath globalToolStageDir) { + DirectoryPath packageRootDirectory = toolPackageStore.GetRootPackageDirectory(packageId); + string rollbackDirectory = null; rollbackDirectory = isGlobalTool ? toolDownloadDir.Value : new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); From fa9f9630b88b7f5e7af69d7281fc7167d10363d5 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Wed, 16 Apr 2025 10:09:47 -0400 Subject: [PATCH 07/11] Refactoring global tool installation --- .../ToolPackage/ToolPackageDownloader.cs | 227 +++++++++++++----- 1 file changed, 164 insertions(+), 63 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index ef64eb3a7904..0da91130583b 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -145,13 +145,6 @@ private IToolPackage InstallGlobalToolPackageInternal( string targetFramework, bool isGlobalToolRollForward) { - - - var toolDownloadDir = _globalToolStageDir; - var assetFileDirectory = _globalToolStageDir; - - - // 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); @@ -164,69 +157,149 @@ private IToolPackage InstallGlobalToolPackageInternal( packageId, packageVersion.ToNormalizedString())); } - - NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); - DirectoryPath toolReturnPackageDirectory; - DirectoryPath toolReturnJsonParentDirectory; - - (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - true, // isGlobalTool - toolDownloadDir, - packageId, - packageVersion, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion, - assetFileDirectory, - targetFramework, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); + string rollbackDirectory = _globalToolStageDir.Value; - var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); - 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) + return TransactionalAction.Run( + action: () => { - // TODO: Localize - throw new ToolPackageException($"The tool does not support the current architecture or operating system (Runtime Identifier {RuntimeInformation.RuntimeIdentifier}"); - } + DownloadTool( + packageDownloadDir: _globalToolStageDir, + packageId, + packageVersion, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory: _globalToolStageDir, + targetFramework, + _runtimeJsonPath); - var resolvedPackage = toolConfiguration.RidSpecificPackages[bestRuntimeIdentifier]; + var toolStoreTargetDirectory = _toolPackageStore.GetPackageDirectory(packageId, packageVersion); - var (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - true, // isGlobalTool - toolDownloadDir, - new PackageId(resolvedPackage.Id), - resolvedPackage.Version, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - assetFileDirectory, - targetFramework, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - } + // Create parent directory in global tool store, for example dotnet\tools\.store\powershell + Directory.CreateDirectory(toolStoreTargetDirectory.GetParentPath().Value); - var toolPackageInstance = new ToolPackageInstance(id: packageId, - version: packageVersion, - packageDirectory: toolReturnPackageDirectory, - assetsJsonParentDirectory: toolReturnJsonParentDirectory); + // Move tool files from stage to final location + FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(_globalToolStageDir.Value, toolStoreTargetDirectory.Value)); - if (isGlobalToolRollForward) - { - UpdateRuntimeConfig(toolPackageInstance); - } + rollbackDirectory = toolStoreTargetDirectory.Value; - return toolPackageInstance; + var toolPackageInstance = new ToolPackageInstance(id: packageId, + version: packageVersion, + packageDirectory: toolStoreTargetDirectory, + assetsJsonParentDirectory: toolStoreTargetDirectory); + + if (isGlobalToolRollForward) + { + UpdateRuntimeConfig(toolPackageInstance); + } + + return toolPackageInstance; + }, + rollback: () => + { + if (rollbackDirectory != null && Directory.Exists(rollbackDirectory)) + { + Directory.Delete(rollbackDirectory, true); + } + + // 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()) + { + Directory.Delete(packageRootDirectory.Value, false); + } + }); } + //private IToolPackage InstallGlobalToolPackageInternal( + // PackageSourceLocation packageSourceLocation, + // NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, + // PackageId packageId, + // NuGetVersion packageVersion, + // bool givenSpecificVersion, + // string targetFramework, + // bool isGlobalToolRollForward) + //{ + // var toolDownloadDir = _globalToolStageDir; + // var assetFileDirectory = _globalToolStageDir; + + // // 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( + // CommonLocalizableStrings.ToolPackageConflictPackageId, + // packageId, + // packageVersion.ToNormalizedString())); + // } + + // NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + + // DirectoryPath toolReturnPackageDirectory; + // DirectoryPath toolReturnJsonParentDirectory; + + // (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( + // true, // isGlobalTool + // toolDownloadDir, + // packageId, + // packageVersion, + // localRepository, + // nugetPackageDownloader, + // packageSourceLocation, + // givenSpecificVersion, + // assetFileDirectory, + // targetFramework, + // _runtimeJsonPath, + // _toolPackageStore, + // _globalToolStageDir); + + // var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); + // 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( + // true, // isGlobalTool + // toolDownloadDir, + // new PackageId(resolvedPackage.Id), + // resolvedPackage.Version, + // localRepository, + // nugetPackageDownloader, + // packageSourceLocation, + // givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package + // assetFileDirectory, + // targetFramework, + // _runtimeJsonPath, + // _toolPackageStore, + // _globalToolStageDir); + // } + + // var toolPackageInstance = new ToolPackageInstance(id: packageId, + // version: packageVersion, + // packageDirectory: toolReturnPackageDirectory, + // assetsJsonParentDirectory: toolReturnJsonParentDirectory); + + // if (isGlobalToolRollForward) + // { + // UpdateRuntimeConfig(toolPackageInstance); + // } + + // return toolPackageInstance; + //} + private IToolPackage InstallLocalToolPackageInternal( PackageSourceLocation packageSourceLocation, NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, @@ -296,6 +369,34 @@ private IToolPackage InstallLocalToolPackageInternal( return toolPackageInstance; } + private void DownloadTool( + DirectoryPath packageDownloadDir, + PackageId packageId, + NuGetVersion packageVersion, + INuGetPackageDownloader nugetPackageDownloader, + PackageSourceLocation packageSourceLocation, + bool givenSpecificVersion, + DirectoryPath assetFileDirectory, + string targetFramework, + string runtimeJsonPath) + { + // Need to do outside this method for Global tools: + // - Check to see if the package already exists in the repository, error if so + + 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); + + // TODO: Also download RID-specific package if needed + } + private static (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( bool isGlobalTool, DirectoryPath toolDownloadDir, @@ -336,7 +437,7 @@ private static (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolRetu packageVersion.ToNormalizedString())); } - CreateAssetFile(packageId, packageVersion, toolDownloadDir, assetFileDirectory, runtimeJsonPath, targetFramework); + CreateAssetFile(packageId, packageVersion, toolDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), runtimeJsonPath, targetFramework); if (isGlobalTool) { @@ -489,7 +590,7 @@ private static void CreateAssetFile( PackageId packageId, NuGetVersion version, DirectoryPath packagesRootPath, - DirectoryPath assetFileDirectory, + string assetFilePath, string runtimeJsonGraph, string targetFramework = null ) @@ -550,7 +651,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( From 07863ca18bd5770fa1b3049ff398b0f45fc8b7ae Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Wed, 16 Apr 2025 13:17:12 -0400 Subject: [PATCH 08/11] Refactoring local tool installation --- .../ToolPackage/ToolPackageDownloader.cs | 343 ++++++++++-------- 1 file changed, 193 insertions(+), 150 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 0da91130583b..89e084a318de 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -72,8 +72,7 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa bool isGlobalTool = false, bool isGlobalToolRollForward = false, bool verifySignatures = true, - RestoreActionConfig restoreActionConfig = null - ) + RestoreActionConfig restoreActionConfig = null) { ILogger nugetLogger = new NullLogger(); @@ -97,7 +96,7 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa verbosityOptions: verbosity, currentWorkingDirectory: _currentWorkingDirectory); - + // TODO: This transaction scope probably isn't necessary return TransactionalAction.Run( action: () => { @@ -111,7 +110,6 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa givenSpecificVersion = true; } - if (isGlobalTool) { return InstallGlobalToolPackageInternal( @@ -171,8 +169,7 @@ private IToolPackage InstallGlobalToolPackageInternal( packageSourceLocation, givenSpecificVersion, assetFileDirectory: _globalToolStageDir, - targetFramework, - _runtimeJsonPath); + targetFramework); var toolStoreTargetDirectory = _toolPackageStore.GetPackageDirectory(packageId, packageVersion); @@ -237,7 +234,7 @@ private IToolPackage InstallGlobalToolPackageInternal( // packageId, // packageVersion.ToNormalizedString())); // } - + // NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); // DirectoryPath toolReturnPackageDirectory; @@ -308,67 +305,97 @@ private IToolPackage InstallLocalToolPackageInternal( bool givenSpecificVersion, string targetFramework) { - - var toolDownloadDir = _localToolDownloadDir; - var assetFileDirectory = _localToolAssetDir; - - NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); - - DirectoryPath toolReturnPackageDirectory; - DirectoryPath toolReturnJsonParentDirectory; - - (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - false, // isGlobalTool - toolDownloadDir, - packageId, - packageVersion, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion, - assetFileDirectory, - targetFramework, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - - var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); - 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) + return TransactionalAction.Run( + action: () => { - // 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - false, // isGlobalTool - toolDownloadDir, - new PackageId(resolvedPackage.Id), - resolvedPackage.Version, - localRepository, - nugetPackageDownloader, - packageSourceLocation, - givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - assetFileDirectory, - targetFramework, - _runtimeJsonPath, - _toolPackageStore, - _globalToolStageDir); - } + DownloadTool( + packageDownloadDir: _localToolDownloadDir, + packageId, + packageVersion, + nugetPackageDownloader, + packageSourceLocation, + givenSpecificVersion, + assetFileDirectory: _localToolAssetDir, + targetFramework); - var toolPackageInstance = new ToolPackageInstance(id: packageId, - version: packageVersion, - packageDirectory: toolReturnPackageDirectory, - assetsJsonParentDirectory: toolReturnJsonParentDirectory); + var toolPackageInstance = new ToolPackageInstance(id: packageId, + version: packageVersion, + packageDirectory: _localToolDownloadDir, + assetsJsonParentDirectory: _localToolAssetDir); - return toolPackageInstance; + return toolPackageInstance; + }); } + //private IToolPackage InstallLocalToolPackageInternal( + // PackageSourceLocation packageSourceLocation, + // NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, + // PackageId packageId, + // NuGetVersion packageVersion, + // bool givenSpecificVersion, + // string targetFramework) + //{ + + // var toolDownloadDir = _localToolDownloadDir; + // var assetFileDirectory = _localToolAssetDir; + + // NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); + + // DirectoryPath toolReturnPackageDirectory; + // DirectoryPath toolReturnJsonParentDirectory; + + // (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( + // false, // isGlobalTool + // toolDownloadDir, + // packageId, + // packageVersion, + // localRepository, + // nugetPackageDownloader, + // packageSourceLocation, + // givenSpecificVersion, + // assetFileDirectory, + // targetFramework, + // _runtimeJsonPath, + // _toolPackageStore, + // _globalToolStageDir); + + // var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); + // 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( + // false, // isGlobalTool + // toolDownloadDir, + // new PackageId(resolvedPackage.Id), + // resolvedPackage.Version, + // localRepository, + // nugetPackageDownloader, + // packageSourceLocation, + // givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package + // assetFileDirectory, + // targetFramework, + // _runtimeJsonPath, + // _toolPackageStore, + // _globalToolStageDir); + // } + + // var toolPackageInstance = new ToolPackageInstance(id: packageId, + // version: packageVersion, + // packageDirectory: toolReturnPackageDirectory, + // assetsJsonParentDirectory: toolReturnJsonParentDirectory); + + // return toolPackageInstance; + //} + private void DownloadTool( DirectoryPath packageDownloadDir, PackageId packageId, @@ -377,12 +404,8 @@ private void DownloadTool( PackageSourceLocation packageSourceLocation, bool givenSpecificVersion, DirectoryPath assetFileDirectory, - string targetFramework, - string runtimeJsonPath) + string targetFramework) { - // Need to do outside this method for Global tools: - // - Check to see if the package already exists in the repository, error if so - NuGetv3LocalRepository nugetLocalRepository = new(packageDownloadDir.Value); var package = nugetLocalRepository.FindPackage(packageId.ToString(), packageVersion); @@ -392,84 +415,84 @@ private void DownloadTool( DownloadAndExtractPackage(packageId, nugetPackageDownloader, packageDownloadDir.Value, packageVersion, packageSourceLocation, includeUnlisted: givenSpecificVersion).GetAwaiter().GetResult(); } - CreateAssetFile(packageId, packageVersion, packageDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), runtimeJsonPath, targetFramework); + CreateAssetFile(packageId, packageVersion, packageDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), _runtimeJsonPath, targetFramework); // TODO: Also download RID-specific package if needed } - private static (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( - bool isGlobalTool, - DirectoryPath toolDownloadDir, - PackageId packageId, - NuGetVersion packageVersion, - NuGetv3LocalRepository localRepository, - INuGetPackageDownloader nugetPackageDownloader, - PackageSourceLocation packageSourceLocation, - bool givenSpecificVersion, - DirectoryPath assetFileDirectory, - string targetFramework, - string runtimeJsonPath, - IToolPackageStore toolPackageStore, - DirectoryPath globalToolStageDir) - { - DirectoryPath packageRootDirectory = toolPackageStore.GetRootPackageDirectory(packageId); + //private static (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( + // bool isGlobalTool, + // DirectoryPath toolDownloadDir, + // PackageId packageId, + // NuGetVersion packageVersion, + // NuGetv3LocalRepository localRepository, + // INuGetPackageDownloader nugetPackageDownloader, + // PackageSourceLocation packageSourceLocation, + // bool givenSpecificVersion, + // DirectoryPath assetFileDirectory, + // string targetFramework, + // string runtimeJsonPath, + // IToolPackageStore toolPackageStore, + // DirectoryPath globalToolStageDir) + //{ + // DirectoryPath packageRootDirectory = toolPackageStore.GetRootPackageDirectory(packageId); - string rollbackDirectory = null; - rollbackDirectory = isGlobalTool ? toolDownloadDir.Value : new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); + // string rollbackDirectory = null; + // rollbackDirectory = isGlobalTool ? toolDownloadDir.Value : new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); - return TransactionalAction.Run(() => - { - DirectoryPath toolReturnPackageDirectory; - DirectoryPath toolReturnJsonParentDirectory; + // return TransactionalAction.Run(() => + // { + // DirectoryPath toolReturnPackageDirectory; + // DirectoryPath toolReturnJsonParentDirectory; - var package = localRepository.FindPackage(packageId.ToString(), packageVersion); + // var package = localRepository.FindPackage(packageId.ToString(), packageVersion); - 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())); - } + // if (package == null) + // { + // DownloadAndExtractPackage(packageId, nugetPackageDownloader, toolDownloadDir.Value, packageVersion, packageSourceLocation, includeUnlisted: givenSpecificVersion).GetAwaiter().GetResult(); + // } + // else if (isGlobalTool) + // { + // throw new ToolPackageException( + // string.Format( + // CommonLocalizableStrings.ToolPackageConflictPackageId, + // packageId, + // packageVersion.ToNormalizedString())); + // } - CreateAssetFile(packageId, packageVersion, toolDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), runtimeJsonPath, targetFramework); + // CreateAssetFile(packageId, packageVersion, toolDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), runtimeJsonPath, targetFramework); - if (isGlobalTool) - { - toolReturnPackageDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); - toolReturnJsonParentDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); - var rootPackageDirectory = toolPackageStore.GetRootPackageDirectory(packageId); - Directory.CreateDirectory(rootPackageDirectory.Value); - FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(globalToolStageDir.Value, toolReturnPackageDirectory.Value)); - rollbackDirectory = toolReturnPackageDirectory.Value; - } - else - { - toolReturnPackageDirectory = toolDownloadDir; - toolReturnJsonParentDirectory = assetFileDirectory; - } + // if (isGlobalTool) + // { + // toolReturnPackageDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); + // toolReturnJsonParentDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); + // var rootPackageDirectory = toolPackageStore.GetRootPackageDirectory(packageId); + // Directory.CreateDirectory(rootPackageDirectory.Value); + // FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(globalToolStageDir.Value, toolReturnPackageDirectory.Value)); + // rollbackDirectory = toolReturnPackageDirectory.Value; + // } + // else + // { + // toolReturnPackageDirectory = toolDownloadDir; + // toolReturnJsonParentDirectory = assetFileDirectory; + // } - return (toolReturnPackageDirectory, toolReturnJsonParentDirectory); - }, - rollback: () => - { - if (rollbackDirectory != null && Directory.Exists(rollbackDirectory)) - { - Directory.Delete(rollbackDirectory, true); - } - // Delete the root if it is empty - if (Directory.Exists(packageRootDirectory.Value) && - !Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any()) - { - Directory.Delete(packageRootDirectory.Value, false); - } - }); - } + // return (toolReturnPackageDirectory, toolReturnJsonParentDirectory); + // }, + // rollback: () => + // { + // if (rollbackDirectory != null && Directory.Exists(rollbackDirectory)) + // { + // Directory.Delete(rollbackDirectory, true); + // } + // // Delete the root if it is empty + // if (Directory.Exists(packageRootDirectory.Value) && + // !Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any()) + // { + // Directory.Delete(packageRootDirectory.Value, false); + // } + // }); + //} // The following methods are copied from the LockFileUtils class in Nuget.Client private static void AddToolsAssets( @@ -562,28 +585,48 @@ 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( From 4f44978f5b1f5a9a489627dc5c9d616c45e10ed0 Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Wed, 16 Apr 2025 21:53:29 -0400 Subject: [PATCH 09/11] Download RID-specific tool package in ToolPackageDownloader --- .../ToolPackage/ToolPackageDownloader.cs | 315 +++--------------- 1 file changed, 51 insertions(+), 264 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index 89e084a318de..f4a34687bb3f 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; @@ -96,42 +97,37 @@ public IToolPackage InstallPackage(PackageLocation packageLocation, PackageId pa verbosityOptions: verbosity, currentWorkingDirectory: _currentWorkingDirectory); - // TODO: This transaction scope probably isn't necessary - return TransactionalAction.Run( - action: () => - { - 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(); + 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; - } + bool givenSpecificVersion = false; + if (versionRange.MinVersion != null && versionRange.MaxVersion != null && versionRange.MinVersion == versionRange.MaxVersion) + { + givenSpecificVersion = true; + } - if (isGlobalTool) - { - return InstallGlobalToolPackageInternal( - packageSourceLocation, - nugetPackageDownloader, - packageId, - packageVersion, - givenSpecificVersion, - targetFramework, - isGlobalToolRollForward); - } - else - { - return InstallLocalToolPackageInternal( - packageSourceLocation, - nugetPackageDownloader, - packageId, - packageVersion, - givenSpecificVersion, - targetFramework); - } - }); + if (isGlobalTool) + { + return InstallGlobalToolPackageInternal( + packageSourceLocation, + nugetPackageDownloader, + packageId, + packageVersion, + givenSpecificVersion, + targetFramework, + isGlobalToolRollForward); + } + else + { + return InstallLocalToolPackageInternal( + packageSourceLocation, + nugetPackageDownloader, + packageId, + packageVersion, + givenSpecificVersion, + targetFramework); + } } private IToolPackage InstallGlobalToolPackageInternal( @@ -210,93 +206,6 @@ private IToolPackage InstallGlobalToolPackageInternal( }); } - //private IToolPackage InstallGlobalToolPackageInternal( - // PackageSourceLocation packageSourceLocation, - // NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, - // PackageId packageId, - // NuGetVersion packageVersion, - // bool givenSpecificVersion, - // string targetFramework, - // bool isGlobalToolRollForward) - //{ - // var toolDownloadDir = _globalToolStageDir; - // var assetFileDirectory = _globalToolStageDir; - - // // 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( - // CommonLocalizableStrings.ToolPackageConflictPackageId, - // packageId, - // packageVersion.ToNormalizedString())); - // } - - // NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); - - // DirectoryPath toolReturnPackageDirectory; - // DirectoryPath toolReturnJsonParentDirectory; - - // (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - // true, // isGlobalTool - // toolDownloadDir, - // packageId, - // packageVersion, - // localRepository, - // nugetPackageDownloader, - // packageSourceLocation, - // givenSpecificVersion, - // assetFileDirectory, - // targetFramework, - // _runtimeJsonPath, - // _toolPackageStore, - // _globalToolStageDir); - - // var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); - // 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - // true, // isGlobalTool - // toolDownloadDir, - // new PackageId(resolvedPackage.Id), - // resolvedPackage.Version, - // localRepository, - // nugetPackageDownloader, - // packageSourceLocation, - // givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - // assetFileDirectory, - // targetFramework, - // _runtimeJsonPath, - // _toolPackageStore, - // _globalToolStageDir); - // } - - // var toolPackageInstance = new ToolPackageInstance(id: packageId, - // version: packageVersion, - // packageDirectory: toolReturnPackageDirectory, - // assetsJsonParentDirectory: toolReturnJsonParentDirectory); - - // if (isGlobalToolRollForward) - // { - // UpdateRuntimeConfig(toolPackageInstance); - // } - - // return toolPackageInstance; - //} - private IToolPackage InstallLocalToolPackageInternal( PackageSourceLocation packageSourceLocation, NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, @@ -327,75 +236,6 @@ private IToolPackage InstallLocalToolPackageInternal( }); } - //private IToolPackage InstallLocalToolPackageInternal( - // PackageSourceLocation packageSourceLocation, - // NuGetPackageDownloader.NuGetPackageDownloader nugetPackageDownloader, - // PackageId packageId, - // NuGetVersion packageVersion, - // bool givenSpecificVersion, - // string targetFramework) - //{ - - // var toolDownloadDir = _localToolDownloadDir; - // var assetFileDirectory = _localToolAssetDir; - - // NuGetv3LocalRepository localRepository = new(toolDownloadDir.Value); - - // DirectoryPath toolReturnPackageDirectory; - // DirectoryPath toolReturnJsonParentDirectory; - - // (toolReturnPackageDirectory, toolReturnJsonParentDirectory) = DownloadAndMovePackage( - // false, // isGlobalTool - // toolDownloadDir, - // packageId, - // packageVersion, - // localRepository, - // nugetPackageDownloader, - // packageSourceLocation, - // givenSpecificVersion, - // assetFileDirectory, - // targetFramework, - // _runtimeJsonPath, - // _toolPackageStore, - // _globalToolStageDir); - - // var toolConfiguration = ToolPackageInstance.GetToolConfiguration(packageId, toolReturnPackageDirectory, toolReturnJsonParentDirectory); - // 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 (resolvedPackageDirectory, resolvedAssetsJsonParentDirectory) = DownloadAndMovePackage( - // false, // isGlobalTool - // toolDownloadDir, - // new PackageId(resolvedPackage.Id), - // resolvedPackage.Version, - // localRepository, - // nugetPackageDownloader, - // packageSourceLocation, - // givenSpecificVersion: true, // Primary package manifest should always specify a specific version of RID-specific package - // assetFileDirectory, - // targetFramework, - // _runtimeJsonPath, - // _toolPackageStore, - // _globalToolStageDir); - // } - - // var toolPackageInstance = new ToolPackageInstance(id: packageId, - // version: packageVersion, - // packageDirectory: toolReturnPackageDirectory, - // assetsJsonParentDirectory: toolReturnJsonParentDirectory); - - // return toolPackageInstance; - //} - private void DownloadTool( DirectoryPath packageDownloadDir, PackageId packageId, @@ -417,82 +257,29 @@ private void DownloadTool( CreateAssetFile(packageId, packageVersion, packageDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), _runtimeJsonPath, targetFramework); - // TODO: Also download RID-specific package if needed - } + // 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}"); + } - //private static (DirectoryPath toolReturnPackageDirectory, DirectoryPath toolReturnJsonParentDirectory) DownloadAndMovePackage( - // bool isGlobalTool, - // DirectoryPath toolDownloadDir, - // PackageId packageId, - // NuGetVersion packageVersion, - // NuGetv3LocalRepository localRepository, - // INuGetPackageDownloader nugetPackageDownloader, - // PackageSourceLocation packageSourceLocation, - // bool givenSpecificVersion, - // DirectoryPath assetFileDirectory, - // string targetFramework, - // string runtimeJsonPath, - // IToolPackageStore toolPackageStore, - // DirectoryPath globalToolStageDir) - //{ - // DirectoryPath packageRootDirectory = toolPackageStore.GetRootPackageDirectory(packageId); - - // string rollbackDirectory = null; - // rollbackDirectory = isGlobalTool ? toolDownloadDir.Value : new VersionFolderPathResolver(toolDownloadDir.Value).GetInstallPath(packageId.ToString(), packageVersion); - - // return TransactionalAction.Run(() => - // { - // DirectoryPath toolReturnPackageDirectory; - // DirectoryPath toolReturnJsonParentDirectory; - - // var package = localRepository.FindPackage(packageId.ToString(), packageVersion); - - // if (package == null) - // { - // DownloadAndExtractPackage(packageId, nugetPackageDownloader, toolDownloadDir.Value, packageVersion, packageSourceLocation, includeUnlisted: givenSpecificVersion).GetAwaiter().GetResult(); - // } - // else if (isGlobalTool) - // { - // throw new ToolPackageException( - // string.Format( - // CommonLocalizableStrings.ToolPackageConflictPackageId, - // packageId, - // packageVersion.ToNormalizedString())); - // } - - // CreateAssetFile(packageId, packageVersion, toolDownloadDir, Path.Combine(assetFileDirectory.Value, "project.assets.json"), runtimeJsonPath, targetFramework); - - // if (isGlobalTool) - // { - // toolReturnPackageDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); - // toolReturnJsonParentDirectory = toolPackageStore.GetPackageDirectory(packageId, packageVersion); - // var rootPackageDirectory = toolPackageStore.GetRootPackageDirectory(packageId); - // Directory.CreateDirectory(rootPackageDirectory.Value); - // FileAccessRetrier.RetryOnMoveAccessFailure(() => Directory.Move(globalToolStageDir.Value, toolReturnPackageDirectory.Value)); - // rollbackDirectory = toolReturnPackageDirectory.Value; - // } - // else - // { - // toolReturnPackageDirectory = toolDownloadDir; - // toolReturnJsonParentDirectory = assetFileDirectory; - // } - - // return (toolReturnPackageDirectory, toolReturnJsonParentDirectory); - // }, - // rollback: () => - // { - // if (rollbackDirectory != null && Directory.Exists(rollbackDirectory)) - // { - // Directory.Delete(rollbackDirectory, true); - // } - // // Delete the root if it is empty - // if (Directory.Exists(packageRootDirectory.Value) && - // !Directory.EnumerateFileSystemEntries(packageRootDirectory.Value).Any()) - // { - // Directory.Delete(packageRootDirectory.Value, false); - // } - // }); - //} + 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, "project.assets.ridpackage.json"), _runtimeJsonPath, targetFramework); + } + } // The following methods are copied from the LockFileUtils class in Nuget.Client private static void AddToolsAssets( From c406b750014728dd150d6935d00094185905bc1c Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Thu, 17 Apr 2025 14:11:25 -0400 Subject: [PATCH 10/11] Handle RID-specific packages for global tools --- .../dotnet/ToolPackage/ToolConfiguration.cs | 6 +- .../ToolConfigurationDeserializer.cs | 5 +- .../ToolPackage/ToolPackageDownloader.cs | 2 +- .../dotnet/ToolPackage/ToolPackageInstance.cs | 240 +++++++++--------- .../ToolConfigurationDeserializerTests.cs | 4 +- 5 files changed, 128 insertions(+), 129 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs index 172446d81633..f55b3c33dffc 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs @@ -10,6 +10,7 @@ internal class ToolConfiguration public ToolConfiguration( string commandName, string toolAssemblyEntryPoint, + string runner, IDictionary ridSpecificPackages = null, IEnumerable warnings = null) { @@ -18,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( @@ -31,6 +32,8 @@ public ToolConfiguration( CommandName = commandName; ToolAssemblyEntryPoint = toolAssemblyEntryPoint; + Runner = runner; + RidSpecificPackages = ridSpecificPackages; Warnings = warnings ?? []; } @@ -62,6 +65,7 @@ private static void EnsureNoLeadingDot(string commandName) public string CommandName { get; } public string ToolAssemblyEntryPoint { get; } + public string Runner { get; } public IDictionary RidSpecificPackages { get; } diff --git a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs index 6bb782f04eae..9d65cd23de1f 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfigurationDeserializer.cs @@ -52,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( @@ -67,6 +69,7 @@ public static ToolConfiguration Deserialize(string pathToXml) return new ToolConfiguration( dotNetCliTool.Commands[0].Name, dotNetCliTool.Commands[0].EntryPoint, + dotNetCliTool.Commands[0].Runner, ridSpecificPackages: ridSpecificPackages, warnings: warnings); } diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs index f4a34687bb3f..1e7ad6966ad1 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageDownloader.cs @@ -277,7 +277,7 @@ private void DownloadTool( 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, "project.assets.ridpackage.json"), _runtimeJsonPath, targetFramework); + CreateAssetFile(new PackageId(resolvedPackage.Id), resolvedPackage.Version, packageDownloadDir, Path.Combine(assetFileDirectory.Value, ToolPackageInstance.RidSpecificPackageAssetsFileName), _runtimeJsonPath, targetFramework); } } diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs index 5d4744ffc6d1..2f50bdb98461 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; @@ -15,7 +17,7 @@ internal class ToolPackageInstance : IToolPackage { private const string PackagedShimsDirectoryConvention = "shims"; - public IEnumerable Warnings => _toolConfiguration.Value.Warnings; + public IEnumerable Warnings { get; private set; } public PackageId Id { get; private set; } @@ -27,93 +29,133 @@ internal class ToolPackageInstance : IToolPackage 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; - - // TODO: This probably doesn't need to be lazy - 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 + bool usingRidSpecificPackage = File.Exists(assetsJsonParentDirectory.WithFile(RidSpecificPackageAssetsFileName).Value); + + string resolvedAssetsFileNameFullPath; + if (usingRidSpecificPackage) { - 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)); - } + resolvedAssetsFileNameFullPath = assetsJsonParentDirectory.WithFile(RidSpecificPackageAssetsFileName).Value; + } + else + { + resolvedAssetsFileNameFullPath = assetsJsonParentDirectory.WithFile(AssetsFileName).Value; + } - // Currently only "dotnet" commands are supported - return new RestoredCommand( - new ToolCommandName(configuration.CommandName), - "dotnet", - LockFileRelativePathToFullFilePath(entryPointFromLockFile.Path, library)); + LockFile lockFile; + + try + { + 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); } + + var library = lockFile.Targets?.SingleOrDefault().Libraries.SingleOrDefault(); + if (library == null) + { + throw new ToolPackageException( + string.Format( + // TODO: Change exception message + CliStrings.FailedToReadNuGetLockFile, + Id, + resolvedAssetsFileNameFullPath)); + } + + + if (usingRidSpecificPackage) + { + ResolvedPackageId = new PackageId(library.Name); + ResolvedPackageVersion = library.Version; + } + else + { + ResolvedPackageId = Id; + ResolvedPackageVersion = Version; + } + + var toolConfiguration = DeserializeToolConfiguration(library, packageDirectory, ResolvedPackageId); + 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 (filesUnderShimsDirectory == null) + { + PackagedShims = []; + } + else + { + 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 FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, LockFileTargetLibrary library) { return PackageDirectory .WithSubDirectories( - Id.ToString(), + library.Name, library.Version.ToNormalizedString().ToLowerInvariant()) .WithFile(lockFileRelativePath); } @@ -129,12 +171,26 @@ public static ToolConfiguration GetToolConfiguration(PackageId id, } - private ToolConfiguration GetToolConfiguration() + private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory, PackageId id) { try { - var library = FindLibraryInLockFile(_lockFile.Value); - return DeserializeToolConfiguration(library, PackageDirectory, Id); + var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName); + if (dotnetToolSettings == null) + { + throw new ToolConfigurationException( + CliStrings.MissingToolSettingsFile); + } + + var toolConfigurationPath = + packageDirectory + .WithSubDirectories( + id.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) { @@ -146,70 +202,6 @@ private ToolConfiguration GetToolConfiguration() } } - private IReadOnlyList GetPackagedShims() - { - LockFileTargetLibrary library; - try - { - library = FindLibraryInLockFile(_lockFile.Value); - } - catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) - { - throw new ToolPackageException( - string.Format( - CliStrings.FailedToReadNuGetLockFile, - Id, - ex.Message), - ex); - } - - IEnumerable filesUnderShimsDirectory = library - ?.ToolsAssemblies - ?.Where(t => LockFileMatcher.MatchesDirectoryPath(t, PackagedShimsDirectoryConvention)); - - if (filesUnderShimsDirectory == null) - { - 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)) - { - return library?.ToolsAssemblies?.Where(l => LockFileMatcher.MatchesDirectoryPath(l, $"{PackagedShimsDirectoryConvention}/{mostFitRuntimeIdentifier}")) - .Select(l => LockFileRelativePathToFullFilePath(l.Path, library)).ToArray() ?? []; - } - else - { - return []; - } - } - - private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory, PackageId id) - { - var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName); - if (dotnetToolSettings == null) - { - throw new ToolConfigurationException( - CliStrings.MissingToolSettingsFile); - } - - var toolConfigurationPath = - packageDirectory - .WithSubDirectories( - id.ToString(), - library.Version.ToNormalizedString().ToLowerInvariant()) - .WithFile(dotnetToolSettings.Path); - - var configuration = ToolConfigurationDeserializer.Deserialize(toolConfigurationPath.Value); - return configuration; - } - private static LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile, PackageId id) { return lockFile 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( From 11fc7ece7f75d8c8a1c1789fdb43bd94209c188f Mon Sep 17 00:00:00 2001 From: Daniel Plaisted Date: Thu, 17 Apr 2025 18:27:36 -0400 Subject: [PATCH 11/11] Fix handling tool assets files with RID-less targets --- .../dotnet/ToolPackage/ToolPackageInstance.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs index 2f50bdb98461..62dbc64502cd 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageInstance.cs @@ -76,7 +76,7 @@ public ToolPackageInstance(PackageId id, ex); } - var library = lockFile.Targets?.SingleOrDefault().Libraries.SingleOrDefault(); + var library = FindLibraryInLockFile(lockFile); if (library == null) { throw new ToolPackageException( @@ -99,7 +99,7 @@ public ToolPackageInstance(PackageId id, ResolvedPackageVersion = Version; } - var toolConfiguration = DeserializeToolConfiguration(library, packageDirectory, ResolvedPackageId); + var toolConfiguration = DeserializeToolConfiguration(library, packageDirectory); Warnings = toolConfiguration.Warnings; var installPath = new VersionFolderPathResolver(PackageDirectory.Value).GetInstallPath(ResolvedPackageId.ToString(), ResolvedPackageVersion); @@ -166,12 +166,12 @@ public static ToolConfiguration GetToolConfiguration(PackageId id, DirectoryPath assetsJsonParentDirectory) { var lockFile = new LockFileFormat().Read(assetsJsonParentDirectory.WithFile(AssetsFileName).Value); - var lockFileTargetLibrary = FindLibraryInLockFile(lockFile, id); - return DeserializeToolConfiguration(lockFileTargetLibrary, packageDirectory, id); + var lockFileTargetLibrary = FindLibraryInLockFile(lockFile); + return DeserializeToolConfiguration(lockFileTargetLibrary, packageDirectory); } - private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory, PackageId id) + private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibrary library, DirectoryPath packageDirectory) { try { @@ -185,7 +185,7 @@ private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibr var toolConfigurationPath = packageDirectory .WithSubDirectories( - id.ToString(), + new PackageId(library.Name).ToString(), library.Version.ToNormalizedString().ToLowerInvariant()) .WithFile(dotnetToolSettings.Path); @@ -202,17 +202,11 @@ private static ToolConfiguration DeserializeToolConfiguration(LockFileTargetLibr } } - private static LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile, PackageId id) + private static LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile) { return lockFile ?.Targets?.SingleOrDefault(t => t.RuntimeIdentifier != null) - ?.Libraries?.SingleOrDefault(l => - string.Compare(l.Name, id.ToString(), StringComparison.OrdinalIgnoreCase) == 0); - } - - private LockFileTargetLibrary FindLibraryInLockFile(LockFile lockFile) - { - return FindLibraryInLockFile(lockFile, Id); + ?.Libraries?.SingleOrDefault(); } private static LockFileItem FindItemInTargetLibrary(LockFileTargetLibrary library, string targetRelativeFilePath)