Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/Dotnet.Watch/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# EditorConfig to suppress warnings/errors for Watch solution

root = false

[*.cs]
# CA - Code Analysis warnings
dotnet_diagnostic.CA1305.severity = none # Specify IFormatProvider
dotnet_diagnostic.CA1822.severity = none # Mark members as static
dotnet_diagnostic.CA1835.severity = none # Prefer Memory-based overloads for ReadAsync/WriteAsync
dotnet_diagnostic.CA1852.severity = none # Seal internal types
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
dotnet_diagnostic.CA2201.severity = none # Do not raise reserved exception types
dotnet_diagnostic.CA2008.severity = none # Do not create tasks without passing a TaskScheduler

# CS - C# compiler warnings/errors
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1573.severity = none # Parameter 'sourceFile' has no matching param tag in the XML comment

# IDE - IDE/Style warnings
dotnet_diagnostic.IDE0005.severity = none # Using directive is unnecessary
dotnet_diagnostic.IDE0011.severity = none # Add braces
dotnet_diagnostic.IDE0036.severity = none # Order modifiers
dotnet_diagnostic.IDE0060.severity = none # Remove unused parameter
dotnet_diagnostic.IDE0073.severity = none # File header does not match required text
dotnet_diagnostic.IDE0161.severity = none # Convert to file-scoped namespace
dotnet_diagnostic.IDE1006.severity = none # Naming rule violation
6 changes: 6 additions & 0 deletions src/Dotnet.Watch/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<ProjectToolsProjectDir>$(RepoRoot)src\Microsoft.DotNet.ProjectTools\</ProjectToolsProjectDir>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private async Task ListenForResponsesAsync(CancellationToken cancellationToken)
{
if (!cancellationToken.IsCancellationRequested)
{
Logger.LogError("Failed to read response: {Message}", e.Message);
Logger.LogError("Failed to read response: {Exception}", e.ToString());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public override void ConfigureEnvironment(IDictionary<string, string> env)

public override async Task WaitForConnectionAsync(CancellationToken cancellationToken)
{
_logger.LogDebug("Waiting for application to connect to pipe {NamedPipeName}.", _namedPipeName);
_logger.LogDebug("Waiting for application to connect to pipe '{NamedPipeName}'.", _namedPipeName);

try
{
Expand Down
52 changes: 52 additions & 0 deletions src/Dotnet.Watch/Watch.Aspire/AspireLauncher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Logging;

namespace Microsoft.DotNet.Watch;

internal abstract class AspireLauncher
{
public EnvironmentOptions EnvironmentOptions { get; }
public GlobalOptions GlobalOptions { get; }
public PhysicalConsole Console { get; }
public ConsoleReporter Reporter { get; }
public LoggerFactory LoggerFactory { get; }
public ILogger Logger { get; }

public AspireLauncher(GlobalOptions globalOptions, EnvironmentOptions environmentOptions)
{
GlobalOptions = globalOptions;
EnvironmentOptions = environmentOptions;
Console = new PhysicalConsole(environmentOptions.TestFlags);
Reporter = new ConsoleReporter(Console, environmentOptions.LogMessagePrefix, environmentOptions.SuppressEmojis);
LoggerFactory = new LoggerFactory(Reporter, environmentOptions.CliLogLevel ?? globalOptions.LogLevel);
Logger = LoggerFactory.CreateLogger(DotNetWatchContext.DefaultLogComponentName);
}

public static AspireLauncher? TryCreate(string[] args)
{
var rootCommand = new AspireRootCommand();

var parseResult = rootCommand.Parse(args);
if (parseResult.Errors.Count > 0)
{
foreach (var error in parseResult.Errors)
{
System.Console.Error.WriteLine(error);
}

return null;
}

return parseResult.CommandResult.Command switch
{
AspireServerCommandDefinition serverCommand => AspireServerLauncher.TryCreate(parseResult, serverCommand),
AspireResourceCommandDefinition resourceCommand => AspireResourceLauncher.TryCreate(parseResult, resourceCommand),
AspireHostCommandDefinition hostCommand => AspireHostLauncher.TryCreate(parseResult, hostCommand),
_ => throw new InvalidOperationException(),
};
}

public abstract Task<int> LaunchAsync(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using Microsoft.Extensions.Logging;

namespace Microsoft.DotNet.Watch;

internal abstract class AspireCommandDefinition : Command
{
public readonly Option<bool> QuietOption = new("--quiet") { Arity = ArgumentArity.Zero };
public readonly Option<bool> VerboseOption = new("--verbose") { Arity = ArgumentArity.Zero };

public AspireCommandDefinition(string name, string description)
: base(name, description)
{
Options.Add(VerboseOption);
Options.Add(QuietOption);

VerboseOption.Validators.Add(v =>
{
if (v.HasOption(QuietOption) && v.HasOption(VerboseOption))
{
v.AddError("Cannot specify both '--quiet' and '--verbose' options.");
}
});
}

public LogLevel GetLogLevel(ParseResult parseResult)
=> parseResult.GetValue(QuietOption) ? LogLevel.Warning : parseResult.GetValue(VerboseOption) ? LogLevel.Debug : LogLevel.Information;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;

namespace Microsoft.DotNet.Watch;

internal sealed class AspireHostCommandDefinition : AspireCommandDefinition
{
public readonly Option<string> SdkOption = new("--sdk") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };

/// <summary>
/// Project or file.
/// </summary>
public readonly Option<string> EntryPointOption = new("--entrypoint") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };

public readonly Argument<string[]> ApplicationArguments = new("arguments") { Arity = ArgumentArity.ZeroOrMore };
public readonly Option<bool> NoLaunchProfileOption = new("--no-launch-profile") { Arity = ArgumentArity.Zero };
public readonly Option<string> LaunchProfileOption = new("--launch-profile", "-lp") { Arity = ArgumentArity.ExactlyOne };

public AspireHostCommandDefinition()
: base("host", "Starts AppHost project.")
{
Arguments.Add(ApplicationArguments);

Options.Add(SdkOption);
Options.Add(EntryPointOption);
Options.Add(NoLaunchProfileOption);
Options.Add(LaunchProfileOption);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Parsing;

namespace Microsoft.DotNet.Watch;

internal sealed class AspireResourceCommandDefinition : AspireCommandDefinition
{
public readonly Argument<string[]> ApplicationArguments = new("arguments") { Arity = ArgumentArity.ZeroOrMore };

/// <summary>
/// Server pipe name.
/// </summary>
public readonly Option<string> ServerOption = new("--server") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };

public readonly Option<string> EntryPointOption = new("--entrypoint") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };

public readonly Option<IReadOnlyDictionary<string, string>> EnvironmentOption = new("--environment", "-e")
{
Description = "Environment variables for the process",
CustomParser = ParseEnvironmentVariables,
AllowMultipleArgumentsPerToken = false
};

public readonly Option<bool> NoLaunchProfileOption = new("--no-launch-profile") { Arity = ArgumentArity.Zero };
public readonly Option<string> LaunchProfileOption = new("--launch-profile", "-lp") { Arity = ArgumentArity.ExactlyOne };

public AspireResourceCommandDefinition()
: base("resource", "Starts resource project.")
{
Arguments.Add(ApplicationArguments);

Options.Add(ServerOption);
Options.Add(EntryPointOption);
Options.Add(EnvironmentOption);
Options.Add(NoLaunchProfileOption);
Options.Add(LaunchProfileOption);
}

private static IReadOnlyDictionary<string, string> ParseEnvironmentVariables(ArgumentResult argumentResult)
{
var result = new Dictionary<string, string>(
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);

List<Token>? invalid = null;

foreach (var token in argumentResult.Tokens)
{
var separator = token.Value.IndexOf('=');
var (name, value) = (separator >= 0)
? (token.Value[0..separator], token.Value[(separator + 1)..])
: (token.Value, "");

name = name.Trim();

if (name != "")
{
result[name] = value;
}
else
{
invalid ??= [];
invalid.Add(token);
}
}

if (invalid != null)
{
argumentResult.AddError(string.Format(
"Incorrectly formatted environment variables {0}",
string.Join(", ", invalid.Select(x => $"'{x.Value}'"))));
}

return result;
}
}
22 changes: 22 additions & 0 deletions src/Dotnet.Watch/Watch.Aspire/Commands/AspireRootCommand.cs
Original file line number Diff line number Diff line change
@@ -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.CommandLine;

namespace Microsoft.DotNet.Watch;

internal sealed class AspireRootCommand : RootCommand
{
public readonly AspireServerCommandDefinition ServerCommand = new();
public readonly AspireResourceCommandDefinition ResourceCommand = new();
public readonly AspireHostCommandDefinition HostCommand = new();

public AspireRootCommand()
{
Directives.Add(new EnvironmentVariablesDirective());

Subcommands.Add(ServerCommand);
Subcommands.Add(ResourceCommand);
Subcommands.Add(HostCommand);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;

namespace Microsoft.DotNet.Watch;

internal sealed class AspireServerCommandDefinition : AspireCommandDefinition
{
/// <summary>
/// Server pipe name.
/// </summary>
public readonly Option<string> ServerOption = new("--server") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };

public readonly Option<string> SdkOption = new("--sdk") { Arity = ArgumentArity.ExactlyOne, Required = true, AllowMultipleArgumentsPerToken = false };

/// <summary>
/// Paths to resource projects or entry-point files.
/// </summary>
public readonly Option<string[]> ResourceOption = new("--resource") { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = true };

/// <summary>
/// Status pipe name for sending watch status events back to the AppHost.
/// </summary>
public readonly Option<string?> StatusPipeOption = new("--status-pipe") { Arity = ArgumentArity.ExactlyOne, AllowMultipleArgumentsPerToken = false };

/// <summary>
/// Control pipe name for receiving commands from the AppHost.
/// </summary>
public readonly Option<string?> ControlPipeOption = new("--control-pipe") { Arity = ArgumentArity.ExactlyOne, AllowMultipleArgumentsPerToken = false };

public AspireServerCommandDefinition()
: base("server", "Starts the dotnet watch server.")
{
Options.Add(ServerOption);
Options.Add(SdkOption);
Options.Add(ResourceOption);
Options.Add(StatusPipeOption);
Options.Add(ControlPipeOption);
}
}
13 changes: 13 additions & 0 deletions src/Dotnet.Watch/Watch.Aspire/Commands/OptionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Parsing;

namespace Microsoft.DotNet.Watch;

internal static class OptionExtensions
{
public static bool HasOption(this SymbolResult symbolResult, Option option)
=> symbolResult.GetResult(option) is OptionResult or && !or.Implicit;
}
Loading
Loading