Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
58aeab6
Add telemetry logger support for API-based MSBuild usage
Copilot Sep 30, 2025
0b8fb6f
Add tests for ProjectInstanceExtensions telemetry logger creation
Copilot Sep 30, 2025
c37c61a
Fix telemetry logger to use distributed logging pattern with central …
Copilot Sep 30, 2025
1dc7f54
Fix ForwardingLoggerRecord to use central logger instance with forwar…
Copilot Sep 30, 2025
c201734
Share telemetry central logger instance between ProjectCollection and…
Copilot Sep 30, 2025
6d409d6
Add integration test for telemetry logger receiving events from API-b…
Copilot Sep 30, 2025
51f1399
Add telemetry logger support to VirtualProjectBuildingCommand
Copilot Oct 1, 2025
9343a69
Add telemetry logger support to PackageAddCommand for project evaluation
Copilot Oct 1, 2025
65f60f0
Add BannedApiAnalyzer to prevent direct MSBuild API usage without tel…
Copilot Nov 4, 2025
102371a
Scope BannedApiAnalyzer to src/Cli and fix all ProjectCollection viol…
Copilot Nov 4, 2025
3cd34a8
Use version property for BannedApiAnalyzers and update copilot-instru…
Copilot Nov 7, 2025
3093c60
Remove formatting-only changes to reduce PR noise
Copilot Nov 20, 2025
31e17b7
Restore BuildFinished event registration outside telemetry check
Copilot Nov 20, 2025
6eb6064
Revert accidental removal of functional code in TestCommand and telem…
Copilot Nov 20, 2025
243ad7f
react to changes post-rebase
baronfel Nov 21, 2025
151ef1a
Add unification plan document
baronfel Nov 23, 2025
bb53470
update local build instructions for copilot
baronfel Nov 23, 2025
ef5751b
big giant refactor towards more explicit types
baronfel Nov 24, 2025
5825147
Update some RunCommand infrastructure
baronfel Nov 25, 2025
6de6502
Tweak
baronfel Nov 25, 2025
ea3fc88
a lot more consolidation
baronfel Nov 25, 2025
ea27e91
finally plumb through centralizing on DotNetProject
baronfel Nov 29, 2025
4540b6a
update runcommand target framework selection to use evaluator
baronfel Nov 30, 2025
d3b9d3d
react to rebase
baronfel Dec 5, 2025
d2a96eb
prevent some duplicate loads when using the raw methods of the evaluator
baronfel Dec 5, 2025
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
Prev Previous commit
Next Next commit
update runcommand target framework selection to use evaluator
  • Loading branch information
baronfel committed Dec 5, 2025
commit 4540b6a46d6f52e9aceab3826e58c1a6f2f2ab39
22 changes: 12 additions & 10 deletions src/Cli/dotnet/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Microsoft.DotNet.Cli.MSBuildEvaluation;
using Microsoft.DotNet.FileBasedPrograms;
using Microsoft.DotNet.ProjectTools;
using NuGet.Frameworks;

namespace Microsoft.DotNet.Cli.Commands.Run;

Expand Down Expand Up @@ -129,8 +130,10 @@ public int Execute()
return 1;
}

using var topLevelEvaluator = DotNetProjectEvaluatorFactory.CreateForCommand(MSBuildArgs);

// Pre-run evaluation: Handle target framework selection for multi-targeted projects
if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkIfNeeded())
if (ProjectFileFullPath is not null && !TrySelectTargetFrameworkIfNeeded(topLevelEvaluator))
{
return 1;
}
Expand Down Expand Up @@ -205,16 +208,15 @@ public int Execute()
/// If needed and we're in non-interactive mode, shows an error.
/// </summary>
/// <returns>True if we can continue, false if we should exit</returns>
private bool TrySelectTargetFrameworkIfNeeded()
private bool TrySelectTargetFrameworkIfNeeded(DotNetProjectEvaluator evaluator)
{
Debug.Assert(ProjectFileFullPath is not null);

var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(MSBuildArgs);
if (TargetFrameworkSelector.TrySelectTargetFramework(
ProjectFileFullPath,
globalProperties,
evaluator,
Interactive,
out string? selectedFramework))
out NuGetFramework? selectedFramework))
{
ApplySelectedFramework(selectedFramework);
return true;
Expand Down Expand Up @@ -248,7 +250,7 @@ private bool TrySelectTargetFrameworkForFileBasedProject()
}

// Use TargetFrameworkSelector to handle multi-target selection (or single framework selection)
if (TargetFrameworkSelector.TrySelectTargetFramework(frameworks, Interactive, out string? selectedFramework))
if (TargetFrameworkSelector.TrySelectTargetFramework(frameworks, Interactive, out var selectedFramework))
{
ApplySelectedFramework(selectedFramework);
return true;
Expand All @@ -261,7 +263,7 @@ private bool TrySelectTargetFrameworkForFileBasedProject()
/// Parses a source file to extract target frameworks from directives.
/// </summary>
/// <returns>Array of frameworks if TargetFrameworks is specified, null otherwise</returns>
private static string[]? GetTargetFrameworksFromSourceFile(string sourceFilePath)
private static NuGetFramework[]? GetTargetFrameworksFromSourceFile(string sourceFilePath)
{
var sourceFile = SourceFile.Load(sourceFilePath);
var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: false, ErrorReporters.IgnoringReporter);
Expand All @@ -274,21 +276,21 @@ private bool TrySelectTargetFrameworkForFileBasedProject()
return null;
}

return targetFrameworksDirective.Value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return targetFrameworksDirective.Value.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Select(NuGetFramework.Parse).ToArray();
}

/// <summary>
/// Applies the selected target framework to MSBuildArgs if a framework was provided.
/// </summary>
/// <param name="selectedFramework">The framework to apply, or null if no framework selection was needed</param>
private void ApplySelectedFramework(string? selectedFramework)
private void ApplySelectedFramework(NuGetFramework? selectedFramework)
{
// If selectedFramework is null, it means no framework selection was needed
// (e.g., user already specified --framework, or single-target project)
if (selectedFramework is not null)
{
var additionalProperties = new ReadOnlyDictionary<string, string>(
new Dictionary<string, string> { { "TargetFramework", selectedFramework } });
new Dictionary<string, string> { { "TargetFramework", selectedFramework.GetShortFolderName() } });
MSBuildArgs = MSBuildArgs.CloneWithAdditionalProperties(additionalProperties);
}
}
Expand Down
37 changes: 13 additions & 24 deletions src/Cli/dotnet/Commands/Run/TargetFrameworkSelector.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.DotNet.Cli.MSBuildEvaluation;
using Microsoft.DotNet.Cli.Utils;
using NuGet.Frameworks;
using Spectre.Console;

namespace Microsoft.DotNet.Cli.Commands.Run;
Expand All @@ -21,43 +21,31 @@ internal static class TargetFrameworkSelector
/// <returns>True if we should continue, false if we should exit with error</returns>
public static bool TrySelectTargetFramework(
string projectFilePath,
Dictionary<string, string> globalProperties,
DotNetProjectEvaluator evaluator,
bool isInteractive,
out string? selectedFramework)
out NuGetFramework? selectedFramework)
{
selectedFramework = null;

// If a framework is already specified, no need to prompt
if (globalProperties.TryGetValue("TargetFramework", out var existingFramework) && !string.IsNullOrWhiteSpace(existingFramework))
if (evaluator.GlobalProperties.TryGetValue("TargetFramework", out var existingFramework) && !string.IsNullOrWhiteSpace(existingFramework))
{
return true;
}

// Evaluate the project to get TargetFrameworks
string targetFrameworks;
try
{
using var collection = new ProjectCollection(globalProperties: globalProperties);
var project = collection.LoadProject(projectFilePath);
targetFrameworks = project.GetPropertyValue("TargetFrameworks");
}
catch (InvalidProjectFileException)
{
// Invalid project file, return true to continue for normal error handling
return true;
}
var project = evaluator.LoadProject(projectFilePath, additionalGlobalProperties: null, useFlexibleLoading: true);
var targetFrameworks = project.TargetFrameworks;

// If there's no TargetFrameworks property or only one framework, no selection needed
if (string.IsNullOrWhiteSpace(targetFrameworks))
if (targetFrameworks is null or { Length: 0 or 1 })
{
return true;
}

// parse the TargetFrameworks property and make sure to account for any additional whitespace
// users may have added for formatting reasons.
var frameworks = targetFrameworks.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

return TrySelectTargetFramework(frameworks, isInteractive, out selectedFramework);
return TrySelectTargetFramework(targetFrameworks, isInteractive, out selectedFramework);
}

/// <summary>
Expand All @@ -69,7 +57,7 @@ public static bool TrySelectTargetFramework(
/// <param name="isInteractive">Whether we're running in interactive mode (can prompt user)</param>
/// <param name="selectedFramework">The selected target framework, or null if selection was cancelled</param>
/// <returns>True if we should continue, false if we should exit with error</returns>
public static bool TrySelectTargetFramework(string[] frameworks, bool isInteractive, out string? selectedFramework)
public static bool TrySelectTargetFramework(NuGetFramework[] frameworks, bool isInteractive, out NuGetFramework? selectedFramework)
{
// If there's only one framework in the TargetFrameworks, we do need to pick it to force the subsequent builds/evaluations
// to act against the correct 'view' of the project
Expand Down Expand Up @@ -107,15 +95,16 @@ public static bool TrySelectTargetFramework(string[] frameworks, bool isInteract
/// <summary>
/// Prompts the user to select a target framework from the available options using Spectre.Console.
/// </summary>
private static string? PromptForTargetFramework(string[] frameworks)
private static NuGetFramework? PromptForTargetFramework(NuGetFramework[] frameworks)
{
try
{
var prompt = new SelectionPrompt<string>()
var prompt = new SelectionPrompt<NuGetFramework>()
.Title($"[cyan]{Markup.Escape(CliCommandStrings.RunCommandSelectTargetFrameworkPrompt)}[/]")
.PageSize(10)
.MoreChoicesText($"[grey]({Markup.Escape(CliCommandStrings.RunCommandMoreFrameworksText)})[/]")
.AddChoices(frameworks)
.UseConverter(framework => framework.GetShortFolderName())
.EnableSearch()
.SearchPlaceholderText(CliCommandStrings.RunCommandSearchPlaceholderText);

Expand Down