-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Support RID-specific .NET Tool packages #48575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
3475e6a to
4359417
Compare
79fc176 to
a3a3217
Compare
Create a new abstract base class called ToolPackageDownloaderBase. Refactor #ToolPackageDownloader to derive from ToolPackageDownloaderBase, and move the implementation into the base class.
baronfel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few notes. I think this is good to go into preview as it is. There are a few follow-up questions and scenarios I want to cover:
- I think we need to be able to support both RID-agnostic and RID-specific tools at install-time (and when packaging) to make sure that older SDKs can continue to install tools that start opting in to RID-specific mode
- I don't want users to have to make MSBuild Items - we already know that users don't understand/think about Items, and they're used to dealing with RIDs as properties
- I really want to get to a single-command invocation when possible (i.e. when NativeAOT isn't happening) instead of having to do N+1 invocations, where N is the number of RIDs to publish for.
| 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) | ||
| { | ||
| throw new ToolPackageException(string.Format(CliStrings.ToolUnsupportedRuntimeIdentifier, RuntimeInformation.RuntimeIdentifier, | ||
| string.Join(" ", toolConfiguration.RidSpecificPackages.Keys))); | ||
| } | ||
|
|
||
| var resolvedPackage = toolConfiguration.RidSpecificPackages[bestRuntimeIdentifier]; | ||
|
|
||
| if (!IsPackageInstalled(new PackageId(resolvedPackage.Id), resolvedPackage.Version, packageDownloadDir.Value)) | ||
| { | ||
| DownloadAndExtractPackage(new PackageId(resolvedPackage.Id), nugetPackageDownloader, packageDownloadDir.Value, resolvedPackage.Version, packageSourceLocation, includeUnlisted: true); | ||
| } | ||
|
|
||
| CreateAssetFile(new PackageId(resolvedPackage.Id), resolvedPackage.Version, packageDownloadDir, Path.Combine(assetFileDirectory.Value, ToolPackageInstance.RidSpecificPackageAssetsFileName), _runtimeJsonPath, targetFramework); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Effectively, this means that as soon as you have any RID-specific packages, you must have RID-specific packages for all platforms you intend to support. This seems limiting - ideally we'd be able to support a RID-agnostic package and a number of RID-specific packages, and in this way older SDKs can use the RID-agnostic package, and newer SDKs can use a RID-specific package when available, and then fallback to the RID-agnostic package if there's no best-fit in the RID-specific packages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's going to be tricky to support old SDKs. Older SDKs aren't going to be able to understand a newer package / tool configuration XML format. So I think the only way that would work is if the primary tool package still contained the RID-agnostic tool implementation. But then for newer SDKs you're downloading the whole RID-agnostic implementation just to figure out that you'd prefer a RID-specific package and then go download that.
I think it's probably OK if this is a new feature and if tools want to take advantage of it they won't work with older SDKs. There is a format version in the tool configuration XML, so older SDKs will get a warning message saying they don't support the package. (I would have made it an error probably, but for older SDKs we're stuck with existing behavior.)
| '@(ToolPackageRuntimeIdentifier)' != '' And | ||
| '$(RuntimeIdentifier)' == ''"> | ||
|
|
||
| <!-- If we are building the primary package for a tool with RID-specific packages, we don't include any of the build output in the package, so we don't need to run Build. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After talking with @nkolev92, we can potentially skip the need for this RemoveTargetFromList Task entirely if we set IncludeBuildOutput to false for the outer RID-specific tool package (see here). If we do this then even if the Build target is called no content from it will be auto-included in the package, which seems cleaner to me than modifying the Target list in this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IncludeBuildOutput is already set to false since the tool targets explicitly add the publish output to the package. I had to disable build from running because otherwise either PublishAot would cause a default RuntimeIdentifier to be set, and if I disabled that then the build would error out because PublishAot needs a RuntimeIdentifier.
| // | ||
| // The main thing this class can't handle is if the way the .NET SDK builds packages changes. In CI runs, we should use a clean test execution folder each time (I think!), so this shouldn't be an issue. | ||
| // For local testing, you may need to delete the artifacts\tmp\Debug\TestTools folder if the SDK changes in a way that affects the built package. | ||
| public class TestToolBuilder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thanks :)
| try | ||
| { | ||
| // NOTE: The manifest file may or may not be under a .config folder. If it is, we will use | ||
| // that directory as the root config directory. This should be OK, as usually there won't be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Extra space in the comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean after the period? I often put two spaces after the end of a sentence as that's what I learned when I was learning to type (on a mechanical typewriter!).
| if (OperatingSystem.IsWindows()) | ||
| { | ||
| return _shimsDirectory.WithFile(commandName.Value + ".exe"); | ||
| if (toolCommand.Runner == "dotnet") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to support Powershell at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, but testing just now in powershell it seems like it will resolve .cmd files that are in the path:
PS C:\Users\Daniel> ToolTest
Hello, Tool!
PS C:\Users\Daniel> (Get-Command ToolTest).Path
C:\Users\Daniel\.dotnet\tools\ToolTest.cmd
So it looks like we don't need to do anything more.
| bool isGlobalTool = false, | ||
| RestoreActionConfig restoreActionConfig = null) | ||
|
|
||
| // The following methods are copied from the LockFileUtils class in Nuget.Client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this risk missing out on changes to the Nuget.Client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's possible. In practice it's not a whole lot of code. Also it's code that was already copied into the SDK repo when we switched from running Restore on a temporary project to using the NuGet package download APIs. This PR just refactors and moves the code around.
|
|
||
| if (commandRunner != "dotnet") | ||
| { | ||
| formatVersion = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be linked back to the other formatVersion in the CLI or does that not make sense, something like const int LatestToolFormatVersion = 2; ? Also, should we check for specific command runners
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't do something like that since the version written depends on the features you use, so if you don't use new features we'll use the old format version and be backwards compatible.
I have now just added an error if you set the runner to something besides "dotnet" or "executable".
nagilson
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for responding to my changes. I would like to do more testing, but I know that we'd like to get this in quickly. I agree with @baronfel's suggestions about a future improvement PR. In the future, it'd be nice to have more telem to understand how users are interfacing with/using the feature. Excited for this change and it's a big feature! 🙌
Yes, I had forgotten to ping Rainer about that. Also @nkolev92 @dotnet/nuget-team this is a pretty big PR, but you might want to look at the build logic parts of it. |
|
/azp run dotnet-sdk-public-ci |
|
Azure Pipelines successfully started running 1 pipeline(s). |
| return packageOutputPath; | ||
| } | ||
|
|
||
| public void RemovePackageFromGlobalPackages(ITestOutputHelper log, string packageId, string version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case you're not already aware, the global packages folder can be changed via nuget.config, with the globalPackagesFolder config. NuGet does this in the NuGet.Client repo for restore tests, and deletes the entire directory after each test. But considering this PR is about tools, I'm not sure if dotnet tools loads nuget.config files, and if so, if it takes into account the CWD, or if it looks at the user nuget.config only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we redirect the global packages folder to a local folder, but we may not clean it each time for perf reasons. So this method uses the NuGet locals command to resolve the correct global packages folder in the current context (and tools do take into account NuGet.config configuration from the current working directory).
| if (!Uri.IsWellFormedUriString(feed, UriKind.Absolute) && !Path.IsPathRooted(feed)) | ||
| { | ||
| localFeedsThatIsRooted[index] = Path.GetFullPath(feed); | ||
| localFeedsThatIsRooted[index] = Path.GetFullPath(feed, basePath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this order correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the second parameter is what replaces the current directory if you didn't pass it in.
|
Added When you commit this breaking change:
You can refer to the .NET SDK breaking change guidelines |
|
Added the breaking change label based on feedback from #49840 - we need to have a breaking change notice to the .NET 10 release notes that |
Work in progress, currently supports RID-specific packages for global tools.