-
Notifications
You must be signed in to change notification settings - Fork 816
Port .NET Tool Integration from PR #13168 #13169
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
Changes from all commits
5ec4c5d
257a01e
2c67f06
f60451b
d2a0efe
dbcb13e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| using DotnetTool.AppHost; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| var builder = DistributedApplication.CreateBuilder(args); | ||
|
|
||
| var simpleUsage = builder.AddDotnetTool("simpleUsage", "dotnet-ef"); | ||
|
|
||
| var wildcardVersion = builder.AddDotnetTool("wildcard", "dotnet-ef") | ||
| .WithPackageVersion("10.0.*") | ||
| .WithParentRelationship(simpleUsage); | ||
|
|
||
| var preRelease = builder.AddDotnetTool("prerelease", "dotnet-ef") | ||
| .WithPackagePrerelease() | ||
| .WithParentRelationship(simpleUsage); | ||
|
|
||
| // Multiple versions | ||
| var differentVersion = builder.AddDotnetTool("sameToolDifferentVersion1", "dotnet-dump") | ||
| .WithArgs("--version") | ||
| .WithPackageVersion("9.0.652701"); | ||
| builder.AddDotnetTool("sameToolDifferentVersion2", "dotnet-dump") | ||
| .WithPackageVersion("9.0.621003") | ||
| .WithArgs("--version") | ||
| .WithParentRelationship(differentVersion); | ||
|
|
||
| // Concurrency | ||
| IResourceBuilder<DotnetToolResource>? concurrencyParent = null; | ||
| for (int i = 0; i < 5; i++) | ||
| { | ||
| var concurrency = builder.AddDotnetTool($"sametoolconcurrency-{i}", "dotnet-trace") | ||
| .WithArgs("--version"); | ||
|
|
||
| if (concurrencyParent == null) | ||
| { | ||
| concurrencyParent = concurrency; | ||
| } | ||
| else | ||
| { | ||
| concurrency.WithParentRelationship(concurrencyParent); | ||
| } | ||
| } | ||
|
|
||
| // Substitution | ||
| var substituted = builder.AddDotnetTool("substituted", "dotnet-ef") | ||
| .WithCommand("calc") | ||
| .WithIconName("Calculator") | ||
| .WithExplicitStart(); | ||
| foreach(var toolAnnotation in substituted.Resource.Annotations.OfType<DotNetToolAnnotation>().ToList()) | ||
| { | ||
| substituted.Resource.Annotations.Remove(toolAnnotation); | ||
| } | ||
|
|
||
| // Fake Offline by using "empty" package feeds | ||
| var fakeSourcesPath = Path.Combine(Path.GetTempPath(), "does-not-exist", Guid.NewGuid().ToString()); | ||
| var offline = builder.AddDotnetTool("offlineSimpleUsage", "dotnet-ef") | ||
| .WaitForCompletion(simpleUsage) | ||
| .WithPackageSource(fakeSourcesPath) | ||
| .WithPackageIgnoreExistingFeeds() | ||
| .WithPackageIgnoreFailedSources() | ||
| ; | ||
|
|
||
| builder.AddDotnetTool("offlineWildcard", "dotnet-ef") | ||
| .WithPackageVersion("10.0.*") | ||
| .WaitForCompletion(wildcardVersion) | ||
| .WithParentRelationship(offline) | ||
| .WithPackageSource(fakeSourcesPath) | ||
| .WithPackageIgnoreExistingFeeds() | ||
| .WithPackageIgnoreFailedSources(); | ||
|
|
||
| builder.AddDotnetTool("offlinePrerelease", "dotnet-ef") | ||
| .WithPackagePrerelease() | ||
| .WaitForCompletion(preRelease) | ||
| .WithParentRelationship(offline) | ||
| .WithPackageSource(fakeSourcesPath) | ||
| .WithPackageIgnoreExistingFeeds() | ||
| .WithPackageIgnoreFailedSources(); | ||
|
|
||
| // Some issues only show up when installing for first time, rather than using existing downloaded versions | ||
| // Use a specific NUGET_PACKAGES path for these playground tools, so we can easily reset them | ||
| builder.Eventing.Subscribe<BeforeStartEvent>(async (evt, _) => | ||
| { | ||
| var nugetPackagesPath = Path.Join(evt.Services.GetRequiredService<IAspireStore>().BasePath, "nuget"); | ||
|
|
||
| foreach (var resource in builder.Resources.OfType<DotnetToolResource>()) | ||
| { | ||
| builder.CreateResourceBuilder(resource) | ||
| .WithEnvironment("NUGET_PACKAGES", nugetPackagesPath) | ||
| .WithCommand("reset", "Reset Packages", ctx => | ||
| { | ||
| try | ||
| { | ||
| Directory.Delete(nugetPackagesPath, true); | ||
| return Task.FromResult(CommandResults.Success()); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| return Task.FromResult(CommandResults.Failure(ex)); | ||
| } | ||
| }, new CommandOptions | ||
| { | ||
| IconName = "Delete" | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| #if !SKIP_DASHBOARD_REFERENCE | ||
| // This project is only added in playground projects to support development/debugging | ||
| // of the dashboard. It is not required in end developer code. Comment out this code | ||
| // or build with `/p:SkipDashboardReference=true`, to test end developer | ||
| // dashboard launch experience, Refer to Directory.Build.props for the path to | ||
| // the dashboard binary (defaults to the Aspire.Dashboard bin output in the | ||
| // artifacts dir). | ||
| builder.AddProject<Projects.Aspire_Dashboard>(KnownResourceNames.AspireDashboard); | ||
| #endif | ||
|
|
||
| builder.Build().Run(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| #pragma warning disable IDE0005 // Using directive is unnecessary (needed when file is linked to test project) | ||
| using Aspire.Hosting.ApplicationModel; | ||
| #pragma warning restore IDE0005 | ||
|
|
||
| namespace DotnetTool.AppHost; | ||
|
|
||
| public class DotNetToolAnnotation : IResourceAnnotation | ||
| { | ||
| public required string PackageId { get; set; } | ||
| public string? Version { get; set; } | ||
| public bool Prerelease { get; set; } | ||
| public List<string> Sources { get; } = []; | ||
| public bool IgnoreExistingFeeds { get; set; } | ||
| public bool IgnoreFailedSources { get; set; } | ||
| public bool AllowDowngrade { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,112 @@ | ||||||
| #pragma warning disable IDE0005 // Using directive is unnecessary (needed when file is linked to test project) | ||||||
| using Aspire.Hosting; | ||||||
| using Aspire.Hosting.ApplicationModel; | ||||||
| #pragma warning restore IDE0005 | ||||||
|
|
||||||
| namespace DotnetTool.AppHost; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Provides extension methods for adding Dotnet Tool resources to the application model. | ||||||
| /// </summary> | ||||||
| public static class DotNetToolExtensions | ||||||
| { | ||||||
| public static IResourceBuilder<DotnetToolResource> AddDotnetTool(this IDistributedApplicationBuilder builder, string name, string packageId) | ||||||
| => builder.AddDotnetTool(new DotnetToolResource(name, packageId)); | ||||||
|
|
||||||
| public static IResourceBuilder<T> AddDotnetTool<T>(this IDistributedApplicationBuilder builder, T resource) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| return builder.AddResource(resource) | ||||||
| .WithIconName("Toolbox") | ||||||
| .WithCommand("dotnet") | ||||||
| .WithArgs(x => | ||||||
| { | ||||||
| if (!x.Resource.TryGetLastAnnotation<DotNetToolAnnotation>(out var toolConfig)) | ||||||
| { | ||||||
| // If the annotation has been removed, don't add any dotnet tool arguments. | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| x.Args.Add("tool"); | ||||||
| x.Args.Add("exec"); | ||||||
| x.Args.Add(toolConfig.PackageId); | ||||||
|
|
||||||
| var sourceArg = toolConfig.IgnoreExistingFeeds ? "--source" : "--add-source"; | ||||||
|
|
||||||
| foreach (var source in toolConfig.Sources) | ||||||
| { | ||||||
| x.Args.Add(sourceArg); | ||||||
| x.Args.Add(source); | ||||||
| } | ||||||
|
|
||||||
| if (toolConfig.IgnoreFailedSources) | ||||||
| { | ||||||
| x.Args.Add("--ignore-failed-sources"); | ||||||
| } | ||||||
|
|
||||||
| if (toolConfig.Version is not null) | ||||||
| { | ||||||
| x.Args.Add("--version"); | ||||||
| x.Args.Add(toolConfig.Version); | ||||||
| } | ||||||
| else if (toolConfig.Prerelease) | ||||||
| { | ||||||
| x.Args.Add("--prerelease"); | ||||||
| } | ||||||
|
|
||||||
| x.Args.Add("--verbosity"); | ||||||
| x.Args.Add("detailed"); | ||||||
|
Comment on lines
+57
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verbose output made sense when
Suggested change
|
||||||
| x.Args.Add("--yes"); | ||||||
| x.Args.Add("--"); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| public static IResourceBuilder<T> WithPackageId<T>(this IResourceBuilder<T> builder, string packageId) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| builder.Resource.ToolConfiguration.PackageId = packageId; | ||||||
| return builder; | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Set the package version for a tool to use | ||||||
| /// </summary> | ||||||
| /// <typeparam name="T">The Dotnet Tool resource type</typeparam> | ||||||
| /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param> | ||||||
| /// <param name="version">The package version to use</param> | ||||||
| /// <returns>The <see cref="IResourceBuilder{T}"/> for chaining.</returns> | ||||||
| public static IResourceBuilder<T> WithPackageVersion<T>(this IResourceBuilder<T> builder, string version) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| builder.Resource.ToolConfiguration.Version = version; | ||||||
| return builder; | ||||||
| } | ||||||
|
|
||||||
| public static IResourceBuilder<T> WithPackagePrerelease<T>(this IResourceBuilder<T> builder) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| builder.Resource.ToolConfiguration.Prerelease = true; | ||||||
| return builder; | ||||||
| } | ||||||
|
|
||||||
| public static IResourceBuilder<T> WithPackageSource<T>(this IResourceBuilder<T> builder, string source) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| builder.Resource.ToolConfiguration.Sources.Add(source); | ||||||
| return builder; | ||||||
| } | ||||||
|
|
||||||
| public static IResourceBuilder<T> WithPackageIgnoreExistingFeeds<T>(this IResourceBuilder<T> builder) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| builder.Resource.ToolConfiguration.IgnoreExistingFeeds = true; | ||||||
| return builder; | ||||||
| } | ||||||
|
|
||||||
| public static IResourceBuilder<T> WithPackageIgnoreFailedSources<T>(this IResourceBuilder<T> builder) | ||||||
| where T : DotnetToolResource | ||||||
| { | ||||||
| builder.Resource.ToolConfiguration.IgnoreFailedSources = true; | ||||||
| return builder; | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>$(DefaultTargetFramework)</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| <IsAspireHost>true</IsAspireHost> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Compile Include="..\..\KnownResourceNames.cs" Link="KnownResourceNames.cs" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <AspireProjectOrPackageReference Include="Aspire.Hosting.AppHost" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="NuGet.Configuration" /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reference isn't needed after moving from
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot do that |
||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| #pragma warning disable IDE0005 // Using directive is unnecessary (needed when file is linked to test project) | ||
| using Aspire.Hosting.ApplicationModel; | ||
| #pragma warning restore IDE0005 | ||
|
|
||
| namespace DotnetTool.AppHost; | ||
|
|
||
| /// <summary> | ||
| /// Represents a .NET tool resource that encapsulates metadata about a .NET CLI tool, including its name, package ID, | ||
| /// and command. | ||
| /// </summary> | ||
| /// <remarks>This class is used to define and manage resources for .NET CLI tools. It associates a tool's name and | ||
| /// command with its package ID, and ensures that the required metadata is properly annotated.</remarks> | ||
| public class DotnetToolResource : ExecutableResource | ||
| { | ||
| /// <param name="name">The name of the resource.</param> | ||
| /// <param name="packageId">The package id of the tool</param> | ||
| public DotnetToolResource(string name, string packageId) | ||
| : base(name, "dotnet", ".") | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(packageId, nameof(packageId)); | ||
| Annotations.Add(new DotNetToolAnnotation { PackageId = packageId }); | ||
| } | ||
|
|
||
| internal DotNetToolAnnotation ToolConfiguration | ||
| { | ||
| get | ||
| { | ||
| if (!this.TryGetLastAnnotation<DotNetToolAnnotation>(out var toolConfig)) | ||
| { | ||
| throw new InvalidOperationException("DotNetToolAnnotation is missing"); | ||
| } | ||
| return toolConfig; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| { | ||
| "$schema": "http://json.schemastore.org/launchsettings.json", | ||
| "profiles": { | ||
| "https": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "launchBrowser": true, | ||
| "applicationUrl": "https://localhost:16179;http://localhost:16180", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development", | ||
| "DOTNET_ENVIRONMENT": "Development", | ||
| "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:17119", | ||
| "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:18036", | ||
| "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17119", | ||
| "ASPIRE_SHOW_DASHBOARD_RESOURCES": "true" | ||
| } | ||
| }, | ||
| "http": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "launchBrowser": true, | ||
| "applicationUrl": "http://localhost:16180", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development", | ||
| "DOTNET_ENVIRONMENT": "Development", | ||
| "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:17120", | ||
| "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18037", | ||
| "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:17120", | ||
| "ASPIRE_SHOW_DASHBOARD_RESOURCES": "true", | ||
| "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" | ||
| } | ||
| }, | ||
| "generate-manifest": { | ||
| "commandName": "Project", | ||
| "dotnetRunMessages": true, | ||
| "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", | ||
| "applicationUrl": "http://localhost:16180", | ||
| "environmentVariables": { | ||
| "ASPNETCORE_ENVIRONMENT": "Development", | ||
| "DOTNET_ENVIRONMENT": "Development", | ||
| "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:17120" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "Logging": { | ||
| "LogLevel": { | ||
| "Default": "Information", | ||
| "Microsoft.AspNetCore": "Warning", | ||
| "Aspire.Hosting.Dcp": "Warning" | ||
| } | ||
| } | ||
| } |
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.