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 some RunCommand infrastructure
  • Loading branch information
baronfel committed Dec 5, 2025
commit 5825147e4845c1cfe37119037d0b92b3acf0773a
41 changes: 21 additions & 20 deletions BannedSymbols.txt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the use of projectInstance.Build in ProjectInstanceExtensions.cs not trigger this check? I don't seem something that excludes that file from this check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect it's because we're not blocking the entire method and instead only specific overloads.

A better approach would be to block the entire method groups/types and force callsites that needed them (like our wrapper types) to explicitly nowarn.

Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
# Ban direct usage of MSBuild API Build methods to ensure telemetry logger is always attached
# Use BuildWithTelemetry extension methods from ProjectInstanceExtensions instead
# Ban direct usage of MSBuild APIs to ensure proper telemetry integration and evaluation caching
# Use DotNetProjectEvaluator and DotNetProjectBuilder wrapper types instead

# Ban ProjectInstance.Build() methods
M:Microsoft.Build.Execution.ProjectInstance.Build();Use BuildWithTelemetry() from ProjectInstanceExtensions instead
M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>);Use BuildWithTelemetry() from ProjectInstanceExtensions instead
M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>,System.Collections.Generic.IEnumerable`1<Microsoft.Build.Logging.ForwardingLoggerRecord>);Use BuildWithTelemetry() from ProjectInstanceExtensions instead
M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>,System.Collections.Generic.IEnumerable`1<Microsoft.Build.Logging.ForwardingLoggerRecord>,Microsoft.Build.Evaluation.Context.EvaluationContext);Use BuildWithTelemetry() from ProjectInstanceExtensions instead
# Ban direct ProjectInstance creation - use DotNetProjectEvaluator.LoadProject instead
M:Microsoft.Build.Execution.ProjectInstance.#ctor(System.String);Use DotNetProjectEvaluator.LoadProject() instead
M:Microsoft.Build.Execution.ProjectInstance.#ctor(System.String,System.Collections.Generic.IDictionary`2<System.String,System.String>,System.String);Use DotNetProjectEvaluator.LoadProject() instead
M:Microsoft.Build.Execution.ProjectInstance.#ctor(System.String,System.Collections.Generic.IDictionary`2<System.String,System.String>,System.String,Microsoft.Build.Evaluation.ProjectCollection);Use DotNetProjectEvaluator.LoadProject() instead

# Ban Project.Build() methods
M:Microsoft.Build.Evaluation.Project.Build();Use BuildWithTelemetry() on the Project's ProjectInstance instead
M:Microsoft.Build.Evaluation.Project.Build(Microsoft.Build.Framework.ILogger);Use BuildWithTelemetry() on the Project's ProjectInstance instead
M:Microsoft.Build.Evaluation.Project.Build(System.String);Use BuildWithTelemetry() on the Project's ProjectInstance instead
M:Microsoft.Build.Evaluation.Project.Build(System.String[]);Use BuildWithTelemetry() on the Project's ProjectInstance instead
M:Microsoft.Build.Evaluation.Project.Build(System.String,System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>);Use BuildWithTelemetry() on the Project's ProjectInstance instead
M:Microsoft.Build.Evaluation.Project.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>);Use BuildWithTelemetry() on the Project's ProjectInstance instead
M:Microsoft.Build.Evaluation.Project.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>,System.Collections.Generic.IEnumerable`1<Microsoft.Build.Logging.ForwardingLoggerRecord>);Use BuildWithTelemetry() on the Project's ProjectInstance instead
# Ban direct ProjectInstance.Build() methods - use DotNetProjectBuilder instead
M:Microsoft.Build.Execution.ProjectInstance.Build();Use DotNetProjectBuilder.Build() instead
M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[]);Use DotNetProjectBuilder.Build() instead
M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>);Use DotNetProjectBuilder.Build() instead
M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>,System.Collections.Generic.IEnumerable`1<Microsoft.Build.Logging.ForwardingLoggerRecord>);Use DotNetProjectBuilder.Build() instead

# Ban ProjectCollection constructor without telemetry - use CreateLoggersWithTelemetry() helper
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor();Use constructor with loggers from CreateLoggersWithTelemetry()
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor(System.Collections.Generic.IDictionary`2<System.String,System.String>);Use constructor with loggers from CreateLoggersWithTelemetry()
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor(Microsoft.Build.Evaluation.ToolsetDefinitionLocations);Use constructor with loggers from CreateLoggersWithTelemetry()
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor(System.Collections.Generic.IDictionary`2<System.String,System.String>,System.Collections.Generic.IEnumerable`1<Microsoft.Build.Framework.ILogger>,Microsoft.Build.Evaluation.ToolsetDefinitionLocations);Ensure telemetry logger is included via CreateLoggersWithTelemetry()
# Ban direct Project.Build() methods - use DotNetProjectBuilder instead
M:Microsoft.Build.Evaluation.Project.Build();Use DotNetProjectBuilder.Build() instead
M:Microsoft.Build.Evaluation.Project.Build(Microsoft.Build.Framework.ILogger);Use DotNetProjectBuilder.Build() instead
M:Microsoft.Build.Evaluation.Project.Build(System.String);Use DotNetProjectBuilder.Build() instead
M:Microsoft.Build.Evaluation.Project.Build(System.String[]);Use DotNetProjectBuilder.Build() instead

# Ban direct ProjectCollection creation - use DotNetProjectEvaluatorFactory instead
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor();Use DotNetProjectEvaluatorFactory.Create() or CreateForCommand() instead
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor(System.Collections.Generic.IDictionary`2<System.String,System.String>);Use DotNetProjectEvaluatorFactory.Create() instead
M:Microsoft.Build.Evaluation.ProjectCollection.#ctor(Microsoft.Build.Evaluation.ToolsetDefinitionLocations);Use DotNetProjectEvaluatorFactory.Create() instead
27 changes: 22 additions & 5 deletions documentation/project-docs/msbuild-api-usage-unification.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,27 @@ This plan builds upon existing work in the codebase:
- **MSBuildEvaluator**: Existing evaluation abstraction
- **BannedApiAnalyzer**: API usage enforcement infrastructure

## Implementation Status

### ✅ **COMPLETED**
- **Phase 1**: Core wrapper infrastructure implemented and working
- **Phase 2**: Major usage sites migrated to new wrapper types
- **Phase 4**: BannedSymbols.txt updated with new enforcement rules
- **Unit Tests**: Comprehensive test suite added

### **Current State**
- ✅ All wrapper types (DotNetProjectEvaluator, DotNetProject, DotNetProjectBuilder, DotNetProjectItem) implemented
- ✅ Factory patterns with standard configurations (CreateForCommand, CreateForRestore, CreateForWorkloadAnalysis)
- ✅ Telemetry integration preserved and automated
- ✅ Evaluation caching enabled through ProjectCollection reuse
- ✅ 15+ command usage sites successfully migrated
- ✅ Build passes with zero errors
- ✅ BannedApiAnalyzer rules prevent future regressions

## Success Criteria

1. **Telemetry Integration**: 100% of MSBuild API usage includes telemetry
2. **Performance**: Evaluation-heavy scenarios show measurable performance improvement
3. **Code Quality**: Reduced complexity in command implementations
4. **Compliance**: BannedApiAnalyzer prevents direct MSBuild API usage in new code
5. **Test Coverage**: Comprehensive tests for all wrapper functionality
1. **Telemetry Integration**: 100% of MSBuild API usage includes telemetry
2. 🔄 **Performance**: Evaluation-heavy scenarios should show measurable performance improvement (needs benchmarking)
3. **Code Quality**: Reduced complexity in command implementations
4. **Compliance**: BannedApiAnalyzer prevents direct MSBuild API usage in new code
5. **Test Coverage**: Comprehensive tests for all wrapper functionality
4 changes: 2 additions & 2 deletions src/Cli/dotnet/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -915,8 +915,8 @@ private void SendProjectBasedTelemetry(ProjectLaunchSettingsModel? launchSetting
globalProperties[Constants.EnableDefaultItems] = "false";
globalProperties[Constants.MSBuildExtensionsPath] = AppContext.BaseDirectory;

using var collection = new ProjectCollection(globalProperties: globalProperties);
var project = collection.LoadProject(ProjectFileFullPath).CreateProjectInstance();
using var evaluator = new DotNetProjectEvaluator(globalProperties);
var project = evaluator.LoadProject(ProjectFileFullPath);

packageReferenceCount = RunTelemetry.CountPackageReferences(project);
projectReferenceCount = RunTelemetry.CountProjectReferences(project);
Expand Down
18 changes: 9 additions & 9 deletions src/Cli/dotnet/Commands/Run/RunTelemetry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
// 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.DotNet.Cli.Commands.Run.LaunchSettings;
using Microsoft.DotNet.Cli.MSBuildEvaluation;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.FileBasedPrograms;

Expand Down Expand Up @@ -189,24 +188,25 @@ public static int CountProjectReferences(ImmutableArray<CSharpDirective> directi
return directives.OfType<CSharpDirective.Project>().Count();
}


/// <summary>
/// Counts the number of PackageReferences in a project-based app.
/// Counts the number of direct PackageReferences in a project-based app.
/// </summary>
/// <param name="project">Project instance for project-based apps</param>
/// <param name="project">DotNet project wrapper for project-based apps</param>
/// <returns>Number of package references</returns>
public static int CountPackageReferences(ProjectInstance project)
public static int CountPackageReferences(DotNetProject project)
{
return project.GetItems("PackageReference").Count;
return project.GetItems("PackageReference").Count();
}

/// <summary>
/// Counts the number of direct ProjectReferences in a project-based app.
/// </summary>
/// <param name="project">Project instance for project-based apps</param>
/// <param name="project">DotNet project wrapper for project-based apps</param>
/// <returns>Number of project references</returns>
public static int CountProjectReferences(ProjectInstance project)
public static int CountProjectReferences(DotNetProject project)
{
return project.GetItems("ProjectReference").Count;
return project.GetItems("ProjectReference").Count();
}

/// <summary>
Expand Down