Skip to content

Commit dbb8b6b

Browse files
committed
Refactor dotnet-watch into a package consumable from Aspire.CLI
1 parent 110618b commit dbb8b6b

File tree

98 files changed

+811
-195
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+811
-195
lines changed

Directory.Build.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
<ItemGroup Condition="$(MicrosoftAspNetCoreAppRefPackageVersion.StartsWith('$(_TargetFrameworkVersionWithoutV)'))">
7373
<KnownFrameworkReference Update="Microsoft.AspNetCore.App">
7474
<LatestRuntimeFrameworkVersion>$(MicrosoftAspNetCoreAppRefPackageVersion)</LatestRuntimeFrameworkVersion>
75-
<RuntimePackRuntimeIdentifiers>${SupportedRuntimeIdentifiers}</RuntimePackRuntimeIdentifiers>
75+
<RuntimePackRuntimeIdentifiers>$(SupportedRuntimeIdentifiers)</RuntimePackRuntimeIdentifiers>
7676
<TargetingPackVersion>$(MicrosoftAspNetCoreAppRefPackageVersion)</TargetingPackVersion>
7777
<DefaultRuntimeFrameworkVersion>$(MicrosoftAspNetCoreAppRefPackageVersion)</DefaultRuntimeFrameworkVersion>
7878
</KnownFrameworkReference>

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
3737
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(MicrosoftCodeAnalysisPackageVersion)" />
3838
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="$(MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion)" />
39+
<PackageVersion Include="Microsoft.CodeAnalysis.ExternalAccess.HotReload" Version="$(MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion)" />
3940

4041
<!-- roslyn-sdk dependencies-->
4142
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />

eng/Version.Details.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ This file should be imported by eng/Versions.props
4545
<MicrosoftCodeAnalysisRazorToolingInternalPackageVersion>10.0.0-preview.25479.108</MicrosoftCodeAnalysisRazorToolingInternalPackageVersion>
4646
<MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>5.0.0-2.25479.108</MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
4747
<MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>5.0.0-2.25479.108</MicrosoftCodeAnalysisWorkspacesMSBuildPackageVersion>
48+
<MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion>5.0.0-2.25479.108</MicrosoftCodeAnalysisExternalAccessHotReloadPackageVersion>
4849
<MicrosoftDeploymentDotNetReleasesPackageVersion>2.0.0-preview.1.25479.108</MicrosoftDeploymentDotNetReleasesPackageVersion>
4950
<MicrosoftDiaSymReaderPackageVersion>2.2.0-beta.25479.108</MicrosoftDiaSymReaderPackageVersion>
5051
<MicrosoftDotNetArcadeSdkPackageVersion>11.0.0-beta.25479.108</MicrosoftDotNetArcadeSdkPackageVersion>

sdk.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
<Project Path="src/BuiltInTools/HotReloadAgent/Microsoft.DotNet.HotReload.Agent.shproj" />
5757
<Project Path="src/BuiltInTools/HotReloadClient/Microsoft.DotNet.HotReload.Client.Package.csproj" />
5858
<Project Path="src/BuiltInTools/HotReloadClient/Microsoft.DotNet.HotReload.Client.shproj" Id="a78ff92a-d715-4249-9e3d-40d9997a098f" />
59+
<Project Path="src/BuiltInTools/Watch.Aspire/Microsoft.DotNet.HotReload.Watch.Aspire.csproj" />
60+
<Project Path="src/BuiltInTools/Watch/Microsoft.DotNet.HotReload.Watch.csproj" />
5961
</Folder>
6062
<Folder Name="/src/Cli/">
6163
<Project Path="src/Cli/dotnet/dotnet.csproj" />
@@ -319,6 +321,8 @@
319321
<Project Path="test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj" />
320322
<Project Path="test/Microsoft.DotNet.Cli.Utils.Tests/Microsoft.DotNet.Cli.Utils.Tests.csproj" />
321323
<Project Path="test/Microsoft.DotNet.HotReload.Client.Tests/Microsoft.DotNet.HotReload.Client.Tests.csproj" />
324+
<Project Path="test/Microsoft.DotNet.HotReload.Test.Utilities/Microsoft.DotNet.HotReload.Test.Utilities.csproj" />
325+
<Project Path="test/Microsoft.DotNet.HotReload.Watch.Aspire.Tests/Microsoft.DotNet.HotReload.Watch.Aspire.Tests.csproj" />
322326
<Project Path="test/Microsoft.DotNet.MSBuildSdkResolver.Tests/Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj" />
323327
<Project Path="test/Microsoft.DotNet.PackageInstall.Tests/Microsoft.DotNet.PackageInstall.Tests.csproj" />
324328
<Project Path="test/Microsoft.DotNet.TemplateLocator.Tests/Microsoft.DotNet.TemplateLocator.Tests.csproj" />

src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<FrameworkReference Update="Microsoft.NETCore.App" TargetingPackVersion="6.0.0" />
2727
</ItemGroup>
2828
<ItemGroup>
29-
<Compile Include="..\dotnet-watch\Utilities\ProcessUtilities.cs" Link="ProcessUtilities.cs" />
29+
<Compile Include="..\Watch\Utilities\ProcessUtilities.cs" Link="ProcessUtilities.cs" />
3030
</ItemGroup>
3131

3232
<ItemGroup>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace Microsoft.DotNet.Watch;
7+
8+
internal static class DotNetWatchLauncher
9+
{
10+
public static async Task<bool> RunAsync(string workingDirectory, DotNetWatchOptions options)
11+
{
12+
var globalOptions = new GlobalOptions()
13+
{
14+
Quiet = options.IsQuiet,
15+
Verbose = options.IsVerbose,
16+
NoHotReload = false,
17+
NonInteractive = true,
18+
};
19+
20+
var commandArguments = new List<string>();
21+
if (options.NoLaunchProfile)
22+
{
23+
commandArguments.Add("--no-launch-profile");
24+
}
25+
26+
commandArguments.AddRange(options.ApplicationArguments);
27+
28+
var rootProjectOptions = new ProjectOptions()
29+
{
30+
IsRootProject = true,
31+
ProjectPath = options.ProjectPath,
32+
WorkingDirectory = workingDirectory,
33+
TargetFramework = null,
34+
BuildArguments = [],
35+
NoLaunchProfile = options.NoLaunchProfile,
36+
LaunchProfileName = null,
37+
Command = "run",
38+
CommandArguments = [.. commandArguments],
39+
LaunchEnvironmentVariables = [],
40+
};
41+
42+
var muxerPath = Path.GetFullPath(Path.Combine(options.SdkDirectory, "..", "..", "dotnet" + PathUtilities.ExecutableExtension));
43+
44+
var console = new PhysicalConsole(TestFlags.None);
45+
var reporter = new ConsoleReporter(console, globalOptions.Verbose, globalOptions.Quiet, suppressEmojis: false);
46+
var environmentOptions = EnvironmentOptions.FromEnvironment(muxerPath);
47+
var processRunner = new ProcessRunner(environmentOptions.GetProcessCleanupTimeout(isHotReloadEnabled: true));
48+
var loggerFactory = new LoggerFactory(reporter);
49+
var logger = loggerFactory.CreateLogger(DotNetWatchContext.DefaultLogComponentName);
50+
51+
using var context = new DotNetWatchContext()
52+
{
53+
ProcessOutputReporter = reporter,
54+
LoggerFactory = loggerFactory,
55+
Logger = logger,
56+
BuildLogger = loggerFactory.CreateLogger(DotNetWatchContext.BuildLogComponentName),
57+
ProcessRunner = processRunner,
58+
Options = globalOptions,
59+
EnvironmentOptions = environmentOptions,
60+
RootProjectOptions = rootProjectOptions,
61+
BrowserRefreshServerFactory = new BrowserRefreshServerFactory(),
62+
BrowserLauncher = new BrowserLauncher(logger, environmentOptions),
63+
};
64+
65+
using var shutdownHandler = new ShutdownHandler(console, logger);
66+
67+
try
68+
{
69+
var watcher = new HotReloadDotNetWatcher(context, console, runtimeProcessLauncherFactory: null);
70+
await watcher.WatchAsync(shutdownHandler.CancellationToken);
71+
}
72+
catch (OperationCanceledException) when (shutdownHandler.CancellationToken.IsCancellationRequested)
73+
{
74+
// Ctrl+C forced an exit
75+
}
76+
catch (Exception e)
77+
{
78+
logger.LogError("An unexpected error occurred: {Exception}", e.ToString());
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
using System.CommandLine;
6+
using System.Diagnostics.CodeAnalysis;
7+
8+
namespace Microsoft.DotNet.Watch;
9+
10+
internal sealed class DotNetWatchOptions
11+
{
12+
/// <summary>
13+
/// The .NET SDK directory to load msbuild from (e.g. C:\Program Files\dotnet\sdk\10.0.100).
14+
/// Also used to locate `dotnet` executable.
15+
/// </summary>
16+
public required string SdkDirectory { get; init; }
17+
18+
public required string ProjectPath { get; init; }
19+
public required ImmutableArray<string> ApplicationArguments { get; init; }
20+
public bool IsVerbose { get; init; }
21+
public bool IsQuiet { get; init; }
22+
public bool NoLaunchProfile { get; init; }
23+
24+
public static bool TryParse(string[] args, [NotNullWhen(true)] out DotNetWatchOptions? options)
25+
{
26+
var sdkOption = new Option<string>("--sdk") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };
27+
var projectOption = new Option<string>("--project") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };
28+
var quietOption = new Option<bool>("--quiet") { Arity = ArgumentArity.Zero };
29+
var verboseOption = new Option<bool>("--verbose") { Arity = ArgumentArity.Zero };
30+
var noLaunchProfileOption = new Option<bool>("--no-launch-profile") { Arity = ArgumentArity.Zero };
31+
var applicationArguments = new Argument<string[]>("arguments") { Arity = ArgumentArity.ZeroOrMore };
32+
33+
verboseOption.Validators.Add(v =>
34+
{
35+
if (v.GetValue(quietOption) && v.GetValue(verboseOption))
36+
{
37+
v.AddError("Cannot specify both '--quiet' and '--verbose' options.");
38+
}
39+
});
40+
41+
var rootCommand = new RootCommand()
42+
{
43+
Directives = { new EnvironmentVariablesDirective() },
44+
Options =
45+
{
46+
sdkOption,
47+
projectOption,
48+
quietOption,
49+
verboseOption,
50+
noLaunchProfileOption
51+
},
52+
Arguments =
53+
{
54+
applicationArguments
55+
}
56+
};
57+
58+
var parseResult = rootCommand.Parse(args);
59+
if (parseResult.Errors.Count > 0)
60+
{
61+
foreach (var error in parseResult.Errors)
62+
{
63+
Console.Error.WriteLine(error);
64+
}
65+
66+
options = null;
67+
return false;
68+
}
69+
70+
options = new DotNetWatchOptions()
71+
{
72+
SdkDirectory = parseResult.GetRequiredValue(sdkOption),
73+
ProjectPath = parseResult.GetRequiredValue(projectOption),
74+
IsQuiet = parseResult.GetValue(quietOption),
75+
IsVerbose = parseResult.GetValue(verboseOption),
76+
ApplicationArguments = [.. parseResult.GetValue(applicationArguments) ?? []],
77+
NoLaunchProfile = parseResult.GetValue(noLaunchProfileOption),
78+
};
79+
80+
return true;
81+
}
82+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
4+
<PropertyGroup>
5+
<TargetFramework>$(SdkTargetFramework)</TargetFramework>
6+
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>
7+
<OutputType>Exe</OutputType>
8+
<RootNamespace>Microsoft.DotNet.Watch</RootNamespace>
9+
<IsShipping>true</IsShipping>
10+
11+
<!-- NuGet -->
12+
<IsPackable>true</IsPackable>
13+
<IsShippingPackage>true</IsShippingPackage>
14+
<PackAsTool>true</PackAsTool>
15+
<PackageId>Microsoft.DotNet.HotReload.Watch.Aspire</PackageId>
16+
<PackageDescription>
17+
A supporting package for Aspire CLI:
18+
https://github.com/dotnet/aspire
19+
</PackageDescription>
20+
</PropertyGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="System.CommandLine" />
24+
<PackageReference Include="Microsoft.Build.Locator" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\Watch\Microsoft.DotNet.HotReload.Watch.csproj" />
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<PublicAPI Include="PublicAPI.Shipped.txt" />
33+
<PublicAPI Include="PublicAPI.Unshipped.txt" />
34+
<PublicAPI Include="InternalAPI.Shipped.txt" />
35+
<PublicAPI Include="InternalAPI.Unshipped.txt" />
36+
</ItemGroup>
37+
38+
<Import Project="..\Watch\RuntimeDependencies.props" />
39+
</Project>
40+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.Build.Locator;
2+
using Microsoft.DotNet.Watch;
3+
4+
if (!DotNetWatchOptions.TryParse(args, out var options))
5+
{
6+
return -1;
7+
}
8+
9+
MSBuildLocator.RegisterMSBuildPath(options.SdkDirectory);
10+
11+
var workingDirectory = Directory.GetCurrentDirectory();
12+
return await DotNetWatchLauncher.RunAsync(workingDirectory, options) ? 0 : 1;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
[assembly: InternalsVisibleTo("Microsoft.DotNet.HotReload.Watch.Aspire.Tests, PublicKey = 0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
7+
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

0 commit comments

Comments
 (0)