Skip to content
Draft
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
7 changes: 4 additions & 3 deletions src/BuiltInTools/dotnet-watch/Aspire/AspireServiceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Channels;
using Aspire.Tools.Service;
using Microsoft.Build.Graph;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.Extensions.Logging;

namespace Microsoft.DotNet.Watch;
Expand Down Expand Up @@ -106,7 +107,7 @@ public async ValueTask<RunningProject> StartProjectAsync(string dcpId, string se
{
ObjectDisposedException.ThrowIf(_isDisposed, this);

_logger.LogDebug("Starting project: {Path}", projectOptions.ProjectPath);
_logger.LogDebug("Starting: '{Path}'", projectOptions.Representation.ProjectOrEntryPointFilePath);

var processTerminationSource = new CancellationTokenSource();
var outputChannel = Channel.CreateUnbounded<OutputLine>(s_outputChannelOptions);
Expand Down Expand Up @@ -143,7 +144,7 @@ public async ValueTask<RunningProject> StartProjectAsync(string dcpId, string se
if (runningProject == null)
{
// detailed error already reported:
throw new ApplicationException($"Failed to launch project '{projectOptions.ProjectPath}'.");
throw new ApplicationException($"Failed to launch '{projectOptions.Representation.ProjectOrEntryPointFilePath}'.");
}

await _service.NotifySessionStartedAsync(dcpId, sessionId, runningProject.ProcessId, cancellationToken);
Expand Down Expand Up @@ -221,7 +222,7 @@ private ProjectOptions GetProjectOptions(ProjectLaunchRequest projectLaunchInfo)
return new()
{
IsRootProject = false,
ProjectPath = projectLaunchInfo.ProjectPath,
Representation = ProjectRepresentation.FromProjectOrEntryPointFilePath(projectLaunchInfo.ProjectPath),
WorkingDirectory = Path.GetDirectoryName(projectLaunchInfo.ProjectPath) ?? throw new InvalidOperationException(),
BuildArguments = _hostProjectOptions.BuildArguments,
Command = "run",
Expand Down
2 changes: 1 addition & 1 deletion src/BuiltInTools/dotnet-watch/Browser/BrowserLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,6 @@ private bool CanLaunchBrowser(ProjectOptions projectOptions, [NotNullWhen(true)]
private LaunchSettingsProfile GetLaunchProfile(ProjectOptions projectOptions)
{
return (projectOptions.NoLaunchProfile == true
? null : LaunchSettingsProfile.ReadLaunchProfile(projectOptions.ProjectPath, projectOptions.LaunchProfileName, logger)) ?? new();
? null : LaunchSettingsProfile.ReadLaunchProfile(projectOptions.Representation, projectOptions.LaunchProfileName, logger)) ?? new();
}
}
34 changes: 17 additions & 17 deletions src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,25 @@
fileWatcher.WatchFiles(BuildFiles);
}

public static ImmutableDictionary<string, string> GetGlobalBuildOptions(IEnumerable<string> buildArguments, EnvironmentOptions environmentOptions)
{
// See https://github.com/dotnet/project-system/blob/main/docs/well-known-project-properties.md

return CommandLineOptions.ParseBuildProperties(buildArguments)
.ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value)
.SetItem(PropertyNames.DotNetWatchBuild, "true")
.SetItem(PropertyNames.DesignTimeBuild, "true")
.SetItem(PropertyNames.SkipCompilerExecution, "true")
.SetItem(PropertyNames.ProvideCommandLineArgs, "true")
// F# targets depend on host path variable:
.SetItem("DOTNET_HOST_PATH", environmentOptions.MuxerPath);
}

/// <summary>
/// Loads project graph and performs design-time build.
/// </summary>
public static EvaluationResult? TryCreate(
string rootProjectPath,
IEnumerable<string> buildArguments,
ProjectGraphFactory factory,
ILogger logger,
GlobalOptions options,
EnvironmentOptions environmentOptions,
Expand All @@ -46,20 +59,7 @@
{
var buildReporter = new BuildReporter(logger, options, environmentOptions);

// See https://github.com/dotnet/project-system/blob/main/docs/well-known-project-properties.md

var globalOptions = CommandLineOptions.ParseBuildProperties(buildArguments)
.ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value)
.SetItem(PropertyNames.DotNetWatchBuild, "true")
.SetItem(PropertyNames.DesignTimeBuild, "true")
.SetItem(PropertyNames.SkipCompilerExecution, "true")
.SetItem(PropertyNames.ProvideCommandLineArgs, "true")
// F# targets depend on host path variable:
.SetItem("DOTNET_HOST_PATH", environmentOptions.MuxerPath);

var projectGraph = ProjectGraphUtilities.TryLoadProjectGraph(
rootProjectPath,
globalOptions,
var projectGraph = factory.TryLoadProjectGraph(
logger,
projectGraphRequired: true,
cancellationToken);
Expand All @@ -77,7 +77,7 @@
{
if (!rootNode.ProjectInstance.Build([TargetNames.Restore], loggers))
{
logger.LogError("Failed to restore project '{Path}'.", rootProjectPath);
logger.LogError("Failed to restore '{Path}'.", rootProjectPath);

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build TemplateEngine: linux (x64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build TestBuild: linux (arm64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build TestBuild: linux (x64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build TestBuild: macOS (x64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build TestBuild: macOS (arm64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build TemplateEngine: macOS (x64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci (Build AoT: macOS (x64))

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context

Check failure on line 80 in src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

View check run for this annotation

Azure Pipelines / dotnet-sdk-public-ci

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs#L80

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs(80,68): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'rootProjectPath' does not exist in the current context
loggers.ReportOutput();
return null;
}
Expand Down
121 changes: 121 additions & 0 deletions src/BuiltInTools/dotnet-watch/Build/ProjectGraphFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Evaluation.Context;
using Microsoft.Build.Execution;
using Microsoft.Build.Graph;
using Microsoft.DotNet.Cli.Commands.Run;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace Microsoft.DotNet.Watch;

internal sealed class ProjectGraphFactory
{
/// <summary>
/// Reuse <see cref="ProjectCollection"/> with XML element caching to improve performance.
///
/// The cache is automatically updated when build files change.
/// https://github.com/dotnet/msbuild/blob/b6f853defccd64ae1e9c7cf140e7e4de68bff07c/src/Build/Definition/ProjectCollection.cs#L343-L354
/// </summary>
private readonly ProjectCollection _collection;

private readonly ImmutableDictionary<string, string> _globalOptions;
private readonly ProjectRepresentation _rootProject;

// TODO: support for non-root virtual projects
private readonly VirtualProjectBuildingCommand? _virtualRootProjectBuilder;

public ProjectGraphFactory(
ProjectRepresentation rootProject,
ImmutableDictionary<string, string> globalOptions)
{
_collection = new(
globalProperties: globalOptions,
loggers: [],
remoteLoggers: [],
ToolsetDefinitionLocations.Default,
maxNodeCount: 1,
onlyLogCriticalEvents: false,
loadProjectsReadOnly: false,
useAsynchronousLogging: false,
reuseProjectRootElementCache: true);

_globalOptions = globalOptions;
_rootProject = rootProject;

if (rootProject.EntryPointFilePath != null)
{
_virtualRootProjectBuilder = new VirtualProjectBuildingCommand(rootProject.EntryPointFilePath, MSBuildArgs.FromProperties(new ReadOnlyDictionary<string, string>(globalOptions)));
}
}

/// <summary>
/// Tries to create a project graph by running the build evaluation phase on the <see cref="rootProjectFile"/>.
/// </summary>
public ProjectGraph? TryLoadProjectGraph(
ILogger logger,
bool projectGraphRequired,
CancellationToken cancellationToken)
{
var entryPoint = new ProjectGraphEntryPoint(_rootProject.ProjectGraphPath, _globalOptions);
try
{
return new ProjectGraph([entryPoint], _collection, CreateProjectInstance, cancellationToken);
}
catch (Exception e) when (e is not OperationCanceledException)
{
// ProejctGraph aggregates OperationCanceledException exception,
// throw here to propagate the cancellation.
cancellationToken.ThrowIfCancellationRequested();

logger.LogDebug("Failed to load project graph.");

if (e is AggregateException { InnerExceptions: var innerExceptions })
{
foreach (var inner in innerExceptions)
{
Report(inner);
}
}
else
{
Report(e);
}

void Report(Exception e)
{
if (projectGraphRequired)
{
logger.LogError(e.Message);
}
else
{
logger.LogWarning(e.Message);
}
}
}

return null;
}

private ProjectInstance CreateProjectInstance(string projectPath, Dictionary<string, string> globalProperties, ProjectCollection projectCollection)
{
if (_virtualRootProjectBuilder != null && projectPath == _rootProject.ProjectGraphPath)
{
return _virtualRootProjectBuilder.CreateProjectInstance(projectCollection);
}

return new ProjectInstance(
projectPath,
globalProperties,
toolsVersion: "Current",
subToolsetVersion: null,
projectCollection);
}
}
68 changes: 0 additions & 68 deletions src/BuiltInTools/dotnet-watch/Build/ProjectGraphUtilities.cs
Original file line number Diff line number Diff line change
@@ -1,82 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Graph;
using Microsoft.DotNet.Cli;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;

namespace Microsoft.DotNet.Watch;

internal static class ProjectGraphUtilities
{
/// <summary>
/// Tries to create a project graph by running the build evaluation phase on the <see cref="rootProjectFile"/>.
/// </summary>
public static ProjectGraph? TryLoadProjectGraph(
string rootProjectFile,
ImmutableDictionary<string, string> globalOptions,
ILogger logger,
bool projectGraphRequired,
CancellationToken cancellationToken)
{
var entryPoint = new ProjectGraphEntryPoint(rootProjectFile, globalOptions);
try
{
// Create a new project collection that does not reuse element cache
// to work around https://github.com/dotnet/msbuild/issues/12064:
var collection = new ProjectCollection(
globalProperties: globalOptions,
loggers: [],
remoteLoggers: [],
ToolsetDefinitionLocations.Default,
maxNodeCount: 1,
onlyLogCriticalEvents: false,
loadProjectsReadOnly: false,
useAsynchronousLogging: false,
reuseProjectRootElementCache: false);

return new ProjectGraph([entryPoint], collection, projectInstanceFactory: null, cancellationToken);
}
catch (Exception e) when (e is not OperationCanceledException)
{
// ProejctGraph aggregates OperationCanceledException exception,
// throw here to propagate the cancellation.
cancellationToken.ThrowIfCancellationRequested();

logger.LogDebug("Failed to load project graph.");

if (e is AggregateException { InnerExceptions: var innerExceptions })
{
foreach (var inner in innerExceptions)
{
Report(inner);
}
}
else
{
Report(e);
}

void Report(Exception e)
{
if (projectGraphRequired)
{
logger.LogError(e.Message);
}
else
{
logger.LogWarning(e.Message);
}
}
}

return null;
}

public static string GetDisplayName(this ProjectGraphNode projectNode)
=> $"{Path.GetFileNameWithoutExtension(projectNode.ProjectInstance.FullPath)} ({projectNode.GetTargetFramework()})";

Expand Down
8 changes: 4 additions & 4 deletions src/BuiltInTools/dotnet-watch/Build/ProjectNodeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public IReadOnlyList<ProjectGraphNode> GetProjectNodes(string projectPath)
return rootProjectNodes;
}

logger.LogError("Project '{ProjectPath}' not found in the project graph.", projectPath);
logger.LogError("Project '{PhysicalPath}' not found in the project graph.", projectPath);
return [];
}

Expand All @@ -40,7 +40,7 @@ public IReadOnlyList<ProjectGraphNode> GetProjectNodes(string projectPath)
{
if (projectNodes.Count > 1)
{
logger.LogError("Project '{ProjectPath}' targets multiple frameworks. Specify which framework to run using '--framework'.", projectPath);
logger.LogError("Project '{PhysicalPath}' targets multiple frameworks. Specify which framework to run using '--framework'.", projectPath);
return null;
}

Expand All @@ -55,7 +55,7 @@ public IReadOnlyList<ProjectGraphNode> GetProjectNodes(string projectPath)
if (candidate != null)
{
// shouldn't be possible:
logger.LogWarning("Project '{ProjectPath}' has multiple instances targeting {TargetFramework}.", projectPath, targetFramework);
logger.LogWarning("Project '{PhysicalPath}' has multiple instances targeting {TargetFramework}.", projectPath, targetFramework);
return candidate;
}

Expand All @@ -65,7 +65,7 @@ public IReadOnlyList<ProjectGraphNode> GetProjectNodes(string projectPath)

if (candidate == null)
{
logger.LogError("Project '{ProjectPath}' doesn't have a target for {TargetFramework}.", projectPath, targetFramework);
logger.LogError("Project '{PhysicalPath}' doesn't have a target for {TargetFramework}.", projectPath, targetFramework);
}

return candidate;
Expand Down
Loading
Loading