diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d779da812a0f..b7453b46405a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -36,3 +36,9 @@ External Dependencies: - Changes that require modifications to the dotnet/templating repository (Microsoft.TemplateEngine packages) should be made directly in that repository, not worked around in this repo. - The dotnet/templating repository owns the TemplateEngine.Edge, TemplateEngine.Abstractions, and related packages. - If a change requires updates to template engine behavior or formatting (e.g., DisplayName properties), file an issue in dotnet/templating and make the changes there rather than adding workarounds in this SDK repository. + +Package Versioning: +- Package versions should be managed through version property files, not hard-coded in Directory.Packages.props. +- For packages from .NET repos (dotnet/runtime, dotnet/roslyn, etc.): Add version to eng/Version.Details.xml and eng/Version.Details.props. These are auto-updated by Maestro dependency flow. +- For packages from other sources: Add version to eng/Versions.props for manual version management. +- In Directory.Packages.props, always reference the version property (e.g., $(MicrosoftCodeAnalysisBannedApiAnalyzersPackageVersion)) instead of hard-coding version numbers. diff --git a/BannedSymbols.txt b/BannedSymbols.txt new file mode 100644 index 000000000000..f2e7f661c69f --- /dev/null +++ b/BannedSymbols.txt @@ -0,0 +1,23 @@ +# Ban direct usage of MSBuild API Build methods to ensure telemetry logger is always attached +# Use BuildWithTelemetry extension methods from ProjectInstanceExtensions 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);Use BuildWithTelemetry() from ProjectInstanceExtensions instead +M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1,System.Collections.Generic.IEnumerable`1);Use BuildWithTelemetry() from ProjectInstanceExtensions instead +M:Microsoft.Build.Execution.ProjectInstance.Build(System.String[],System.Collections.Generic.IEnumerable`1,System.Collections.Generic.IEnumerable`1,Microsoft.Build.Evaluation.Context.EvaluationContext);Use BuildWithTelemetry() from ProjectInstanceExtensions 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);Use BuildWithTelemetry() on the Project's ProjectInstance instead +M:Microsoft.Build.Evaluation.Project.Build(System.String[],System.Collections.Generic.IEnumerable`1);Use BuildWithTelemetry() on the Project's ProjectInstance instead +M:Microsoft.Build.Evaluation.Project.Build(System.String[],System.Collections.Generic.IEnumerable`1,System.Collections.Generic.IEnumerable`1);Use BuildWithTelemetry() on the Project's ProjectInstance 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);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.Collections.Generic.IEnumerable`1,Microsoft.Build.Evaluation.ToolsetDefinitionLocations);Ensure telemetry logger is included via CreateLoggersWithTelemetry() diff --git a/Directory.Packages.props b/Directory.Packages.props index ecc47ba3f164..a7be7ec8b030 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,6 +29,7 @@ + diff --git a/eng/Analyzers.props b/eng/Analyzers.props index 170e51092bb9..c8731dabfadf 100644 --- a/eng/Analyzers.props +++ b/eng/Analyzers.props @@ -1,5 +1,10 @@ + + + + + \ No newline at end of file diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 5980474f1ad2..f67ba3f340bc 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -41,6 +41,7 @@ This file should be imported by eng/Versions.props 5.0.0-2.25502.107 5.0.0-2.25502.107 5.0.0-2.25502.107 + 5.0.0-2.25502.107 5.0.0-2.25502.107 10.0.0-preview.25502.107 5.0.0-2.25502.107 @@ -183,6 +184,7 @@ This file should be imported by eng/Versions.props $(MicrosoftCodeAnalysisCSharpCodeStylePackageVersion) $(MicrosoftCodeAnalysisCSharpFeaturesPackageVersion) $(MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion) + $(MicrosoftCodeAnalysisBannedApiAnalyzersPackageVersion) $(MicrosoftCodeAnalysisPublicApiAnalyzersPackageVersion) $(MicrosoftCodeAnalysisRazorToolingInternalPackageVersion) $(MicrosoftCodeAnalysisWorkspacesCommonPackageVersion) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 755d740bac77..a89e612fc733 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -124,6 +124,10 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-dotnet 89c8f6a112d37d2ea8b77821e56d170a1bccdc5a + + https://dev.azure.com/dnceng/internal/_git/dotnet-dotnet + 89c8f6a112d37d2ea8b77821e56d170a1bccdc5a + https://dev.azure.com/dnceng/internal/_git/dotnet-dotnet 89c8f6a112d37d2ea8b77821e56d170a1bccdc5a diff --git a/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs b/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs index 966ea12c87c4..76f8cba3ee60 100644 --- a/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs +++ b/src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using Microsoft.Build.Graph; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch; @@ -75,7 +76,7 @@ public void WatchFiles(FileWatcher fileWatcher) { using (var loggers = buildReporter.GetLoggers(rootNode.ProjectInstance.FullPath, "Restore")) { - if (!rootNode.ProjectInstance.Build([TargetNames.Restore], loggers)) + if (!rootNode.ProjectInstance.BuildWithTelemetry([TargetNames.Restore], loggers)) { logger.LogError("Failed to restore project '{Path}'.", rootProjectPath); loggers.ReportOutput(); @@ -103,7 +104,7 @@ public void WatchFiles(FileWatcher fileWatcher) using (var loggers = buildReporter.GetLoggers(projectInstance.FullPath, "DesignTimeBuild")) { - if (!projectInstance.Build([TargetNames.Compile, .. customCollectWatchItems], loggers)) + if (!projectInstance.BuildWithTelemetry([TargetNames.Compile, .. customCollectWatchItems], loggers)) { logger.LogError("Failed to build project '{Path}'.", projectInstance.FullPath); loggers.ReportOutput(); diff --git a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs index 565bc7f9062b..a74bd71fb143 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using Microsoft.Build.Graph; using Microsoft.CodeAnalysis; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.HotReload; using Microsoft.Extensions.Logging; @@ -582,7 +583,7 @@ private void DeployProjectDependencies(ProjectGraph graph, ImmutableArray items, ChangeKind kind) - => items is [{Item: var item }] + => items is [{ Item: var item }] ? GetSingularMessage(kind) + ": " + GetRelativeFilePath(item.FilePath) : GetPluralMessage(kind) + ": " + string.Join(", ", items.Select(f => GetRelativeFilePath(f.Item.FilePath))); diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs index 09e33759e7a4..645bea82d951 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs @@ -3,6 +3,7 @@ using Microsoft.Build.Graph; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch @@ -61,7 +62,7 @@ public async ValueTask HandleFileChangesAsync(IReadOnlyList files, using var loggers = buildReporter.GetLoggers(projectNode.ProjectInstance.FullPath, BuildTargetName); // Deep copy so that we don't pollute the project graph: - if (!projectNode.ProjectInstance.DeepCopy().Build(BuildTargetName, loggers)) + if (!projectNode.ProjectInstance.DeepCopy().BuildWithTelemetry([BuildTargetName], loggers)) { loggers.ReportOutput(); return null; diff --git a/src/Cli/dotnet/CliCompletion.cs b/src/Cli/dotnet/CliCompletion.cs index 6f3f930bda97..4baf86420682 100644 --- a/src/Cli/dotnet/CliCompletion.cs +++ b/src/Cli/dotnet/CliCompletion.cs @@ -5,6 +5,7 @@ using System.CommandLine.Completions; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using static System.Array; @@ -69,8 +70,9 @@ private static MsbuildProject GetMSBuildProject() { try { + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); return MsbuildProject.FromFileOrDirectory( - new ProjectCollection(), + new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default), Directory.GetCurrentDirectory(), interactive: false); } catch (Exception e) diff --git a/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs b/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs index 2f8bb7badd98..be3d9294b176 100644 --- a/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs +++ b/src/Cli/dotnet/CommandFactory/CommandResolution/ProjectToolsCommandResolver.cs @@ -385,7 +385,7 @@ internal void GenerateDepsJsonFile( string? stdOut; string? stdErr; - var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments([..args], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, BuildCommandParser.TargetOption, BuildCommandParser.VerbosityOption); + var msbuildArgs = MSBuildArgs.AnalyzeMSBuildArguments([.. args], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, BuildCommandParser.TargetOption, BuildCommandParser.VerbosityOption); var forwardingAppWithoutLogging = new MSBuildForwardingAppWithoutLogging(msbuildArgs, msBuildExePath); if (forwardingAppWithoutLogging.ExecuteMSBuildOutOfProc) { diff --git a/src/Cli/dotnet/Commands/Build/BuildCommand.cs b/src/Cli/dotnet/Commands/Build/BuildCommand.cs index 871ead794e84..4d8e425dace9 100644 --- a/src/Cli/dotnet/Commands/Build/BuildCommand.cs +++ b/src/Cli/dotnet/Commands/Build/BuildCommand.cs @@ -12,7 +12,7 @@ public static class BuildCommand { public static CommandBase FromArgs(string[] args, string? msbuildPath = null) { - var parseResult = Parser.Parse(["dotnet", "build", ..args]); + var parseResult = Parser.Parse(["dotnet", "build", .. args]); return FromParseResult(parseResult, msbuildPath); } diff --git a/src/Cli/dotnet/Commands/Clean/CleanCommand.cs b/src/Cli/dotnet/Commands/Clean/CleanCommand.cs index 1290b8b68cfd..7c5516b05dd3 100644 --- a/src/Cli/dotnet/Commands/Clean/CleanCommand.cs +++ b/src/Cli/dotnet/Commands/Clean/CleanCommand.cs @@ -13,7 +13,7 @@ public class CleanCommand(MSBuildArgs msbuildArgs, string? msbuildPath = null) : { public static CommandBase FromArgs(string[] args, string? msbuildPath = null) { - var result = Parser.Parse(["dotnet", "clean", ..args]); + var result = Parser.Parse(["dotnet", "clean", .. args]); return FromParseResult(result, msbuildPath); } @@ -33,7 +33,7 @@ public static CommandBase FromParseResult(ParseResult result, string? msbuildPat NoWriteBuildMarkers = true, }, static (msbuildArgs, msbuildPath) => new CleanCommand(msbuildArgs, msbuildPath), - [ CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CleanCommandParser.TargetOption, CleanCommandParser.VerbosityOption ], + [CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, CleanCommandParser.TargetOption, CleanCommandParser.VerbosityOption], result, msbuildPath ); diff --git a/src/Cli/dotnet/Commands/Format/FormatCommand.cs b/src/Cli/dotnet/Commands/Format/FormatCommand.cs index 9ee9296172fa..d6629af67720 100644 --- a/src/Cli/dotnet/Commands/Format/FormatCommand.cs +++ b/src/Cli/dotnet/Commands/Format/FormatCommand.cs @@ -13,7 +13,7 @@ public class FormatCommand(IEnumerable argsToForward) : FormatForwarding { public static FormatCommand FromArgs(string[] args) { - var result = Parser.Parse(["dotnet", "format", ..args]); + var result = Parser.Parse(["dotnet", "format", .. args]); return FromParseResult(result); } diff --git a/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs b/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs index 5cdf66cfca6e..33904941b817 100644 --- a/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs +++ b/src/Cli/dotnet/Commands/Hidden/Complete/CompleteCommand.cs @@ -19,7 +19,7 @@ public static int Run(ParseResult parseResult) public static int RunWithReporter(string[] args, IReporter reporter) { - var result = Parser.Parse(["dotnet", "complete", ..args]); + var result = Parser.Parse(["dotnet", "complete", .. args]); return RunWithReporter(result, reporter); } diff --git a/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs b/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs index 744289023948..bed479d01816 100644 --- a/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs +++ b/src/Cli/dotnet/Commands/Hidden/InternalReportInstallSuccess/InternalReportInstallSuccessCommand.cs @@ -25,7 +25,7 @@ public static int Run(ParseResult parseResult) public static void ProcessInputAndSendTelemetry(string[] args, ITelemetry telemetry) { - var result = Parser.Parse(["dotnet", "internal-reportinstallsuccess", ..args]); + var result = Parser.Parse(["dotnet", "internal-reportinstallsuccess", .. args]); ProcessInputAndSendTelemetry(result, telemetry); } diff --git a/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs b/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs index cf0b7e06c660..5deae21cb609 100644 --- a/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs +++ b/src/Cli/dotnet/Commands/MSBuild/MSBuildCommand.cs @@ -10,11 +10,11 @@ namespace Microsoft.DotNet.Cli.Commands.MSBuild; public class MSBuildCommand( IEnumerable msbuildArgs, string? msbuildPath = null -) : MSBuildForwardingApp(MSBuildArgs.AnalyzeMSBuildArguments([..msbuildArgs], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, MSBuildCommandParser.TargetOption, CommonOptions.VerbosityOption()), msbuildPath, includeLogo: true) +) : MSBuildForwardingApp(MSBuildArgs.AnalyzeMSBuildArguments([.. msbuildArgs], CommonOptions.PropertiesOption, CommonOptions.RestorePropertiesOption, MSBuildCommandParser.TargetOption, CommonOptions.VerbosityOption()), msbuildPath, includeLogo: true) { public static MSBuildCommand FromArgs(string[] args, string? msbuildPath = null) { - var result = Parser.Parse(["dotnet", "msbuild", ..args]); + var result = Parser.Parse(["dotnet", "msbuild", .. args]); return FromParseResult(result, msbuildPath); } diff --git a/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs b/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs index 265b1eafbb76..b356f5bf3062 100644 --- a/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs +++ b/src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; diff --git a/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs b/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs index a79c2ec2af7c..8942cda59a4f 100644 --- a/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs +++ b/src/Cli/dotnet/Commands/New/MSBuildEvaluation/MSBuildEvaluator.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using Microsoft.Build.Evaluation; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.Logging; using Microsoft.TemplateEngine.Abstractions; @@ -13,7 +14,7 @@ namespace Microsoft.DotNet.Cli.Commands.New.MSBuildEvaluation; internal class MSBuildEvaluator : IIdentifiedComponent { - private readonly ProjectCollection _projectCollection = new(); + private readonly ProjectCollection _projectCollection; private readonly object _lockObj = new(); private IEngineEnvironmentSettings? _settings; @@ -24,12 +25,16 @@ internal class MSBuildEvaluator : IIdentifiedComponent internal MSBuildEvaluator() { _outputDirectory = Directory.GetCurrentDirectory(); + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + _projectCollection = new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); } internal MSBuildEvaluator(string? outputDirectory = null, string? projectPath = null) { _outputDirectory = outputDirectory ?? Directory.GetCurrentDirectory(); _projectFullPath = projectPath != null ? Path.GetFullPath(projectPath) : null; + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + _projectCollection = new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); } public Guid Id => Guid.Parse("{6C2CB5CA-06C3-460A-8ADB-5F21E113AB24}"); diff --git a/src/Cli/dotnet/Commands/Pack/PackCommand.cs b/src/Cli/dotnet/Commands/Pack/PackCommand.cs index 1e88f22688f5..3d574c30bf18 100644 --- a/src/Cli/dotnet/Commands/Pack/PackCommand.cs +++ b/src/Cli/dotnet/Commands/Pack/PackCommand.cs @@ -24,7 +24,7 @@ public class PackCommand( { public static CommandBase FromArgs(string[] args, string? msbuildPath = null) { - var parseResult = Parser.Parse(["dotnet", "pack", ..args]); + var parseResult = Parser.Parse(["dotnet", "pack", .. args]); return FromParseResult(parseResult, msbuildPath); } @@ -92,14 +92,14 @@ public static int RunPackCommand(ParseResult parseResult) if (args.Count != 1) { - Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed); + Console.Error.WriteLine(CliStrings.PackCmd_OneNuspecAllowed); return 1; } var nuspecPath = args[0]; var packArgs = new PackArgs() - { + { Logger = new NuGetConsoleLogger(), Exclude = new List(), OutputDirectory = parseResult.GetValue(PackCommandParser.OutputOption), diff --git a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs index 93bb43ef8c24..f23109c98afc 100644 --- a/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Add/PackageAddCommand.cs @@ -191,7 +191,10 @@ private int ExecuteForFileBasedApp(string path) NoCache = true, NoBuild = true, }; - var projectCollection = new ProjectCollection(); + + // Include telemetry logger for project evaluation + var (loggersWithTelemetry, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry([]); + var projectCollection = new ProjectCollection(globalProperties: null, loggersWithTelemetry, ToolsetDefinitionLocations.Default); var projectInstance = command.CreateProjectInstance(projectCollection); // Set initial version to Directory.Packages.props and/or C# file diff --git a/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs b/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs index 27520377cad2..7e340ac81fa7 100644 --- a/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs +++ b/src/Cli/dotnet/Commands/Package/List/PackageListCommand.cs @@ -4,12 +4,12 @@ #nullable disable using System.CommandLine; +using System.Globalization; using Microsoft.DotNet.Cli.Commands.Hidden.List; +using Microsoft.DotNet.Cli.Commands.MSBuild; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; -using System.Globalization; -using Microsoft.DotNet.Cli.Commands.MSBuild; namespace Microsoft.DotNet.Cli.Commands.Package.List; diff --git a/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs b/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs index 4317f96329be..8bbfd5261cdc 100644 --- a/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs +++ b/src/Cli/dotnet/Commands/Package/Search/PackageSearchCommand.cs @@ -3,9 +3,9 @@ #nullable disable +using System.CommandLine; using Microsoft.DotNet.Cli.Commands.NuGet; using Microsoft.DotNet.Cli.Extensions; -using System.CommandLine; namespace Microsoft.DotNet.Cli.Commands.Package.Search; diff --git a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs index e41367a34fd0..75aa1c1ff20e 100644 --- a/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs +++ b/src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Evaluation; using Microsoft.DotNet.Cli.Commands.Run; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.FileBasedPrograms; using Microsoft.TemplateEngine.Cli.Commands; @@ -34,7 +35,8 @@ public override int Execute() var directives = FileLevelDirectiveHelpers.FindDirectives(sourceFile, reportAllErrors: !_force, DiagnosticBag.ThrowOnFirst()); // Create a project instance for evaluation. - var projectCollection = new ProjectCollection(); + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + var projectCollection = new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); var command = new VirtualProjectBuildingCommand( entryPointFileFullPath: file, msbuildArgs: MSBuildArgs.FromOtherArgs([])) diff --git a/src/Cli/dotnet/Commands/Publish/PublishCommand.cs b/src/Cli/dotnet/Commands/Publish/PublishCommand.cs index 45dc32c84300..dfafac3d4807 100644 --- a/src/Cli/dotnet/Commands/Publish/PublishCommand.cs +++ b/src/Cli/dotnet/Commands/Publish/PublishCommand.cs @@ -21,7 +21,7 @@ private PublishCommand( public static CommandBase FromArgs(string[] args, string? msbuildPath = null) { - var parseResult = Parser.Parse(["dotnet", "publish", ..args]); + var parseResult = Parser.Parse(["dotnet", "publish", .. args]); return FromParseResult(parseResult); } diff --git a/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs b/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs index b60050fb12cc..8b708ad3bd3c 100644 --- a/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/Add/ReferenceAddCommand.cs @@ -20,7 +20,8 @@ internal class ReferenceAddCommand(ParseResult parseResult) : CommandBase(parseR public override int Execute() { - using var projects = new ProjectCollection(); + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + using var projects = new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); bool interactive = _parseResult.GetValue(ReferenceAddCommandParser.InteractiveOption); MsbuildProject msbuildProj = MsbuildProject.FromFileOrDirectory( projects, diff --git a/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs b/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs index 55d54b3d1103..233c60edad63 100644 --- a/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/List/ReferenceListCommand.cs @@ -30,7 +30,8 @@ public ReferenceListCommand(ParseResult parseResult) : base(parseResult) public override int Execute() { - var msbuildProj = MsbuildProject.FromFileOrDirectory(new ProjectCollection(), _fileOrDirectory, false); + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + var msbuildProj = MsbuildProject.FromFileOrDirectory(new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default), _fileOrDirectory, false); var p2ps = msbuildProj.GetProjectToProjectReferences(); if (!p2ps.Any()) { diff --git a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs index 64db0bec340d..e62ffa6240e6 100644 --- a/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Reference/Remove/ReferenceRemoveCommand.cs @@ -32,7 +32,8 @@ public ReferenceRemoveCommand( public override int Execute() { - var msbuildProj = MsbuildProject.FromFileOrDirectory(new ProjectCollection(), _fileOrDirectory, false); + var (loggers, _) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + var msbuildProj = MsbuildProject.FromFileOrDirectory(new ProjectCollection(globalProperties: null, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default), _fileOrDirectory, false); var references = _arguments.Select(p => { var fullPath = Path.GetFullPath(p); diff --git a/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs b/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs index 6eb650b0e261..bd685b2b6ec2 100644 --- a/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs +++ b/src/Cli/dotnet/Commands/Restore/RestoreCommand.cs @@ -13,7 +13,7 @@ public static class RestoreCommand { public static CommandBase FromArgs(string[] args, string? msbuildPath = null) { - var result = Parser.Parse(["dotnet", "restore", ..args]); + var result = Parser.Parse(["dotnet", "restore", .. args]); return FromParseResult(result, msbuildPath); } diff --git a/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs b/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs index dd2e961c1524..ea92c35ab063 100644 --- a/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs +++ b/src/Cli/dotnet/Commands/Restore/RestoringCommand.cs @@ -37,7 +37,7 @@ public RestoringCommand( string? msbuildPath = null, string? userProfileDir = null, bool? advertiseWorkloadUpdates = null) - : base(GetCommandArguments(msbuildArgs, noRestore), msbuildPath) + : base(GetCommandArguments(msbuildArgs, noRestore), msbuildPath) { userProfileDir = CliFolderPathCalculator.DotnetUserProfileFolderPath; Task.Run(() => WorkloadManifestUpdater.BackgroundUpdateAdvertisingManifestsAsync(userProfileDir)); @@ -122,13 +122,13 @@ private static MSBuildArgs GetCommandArguments( ReadOnlyDictionary restoreProperties = msbuildArgs.GlobalProperties? .Where(kvp => !IsPropertyExcludedFromRestore(kvp.Key))? - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList): ReadOnlyDictionary.Empty; + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase) is { } filteredList ? new(filteredList) : ReadOnlyDictionary.Empty; var restoreMSBuildArgs = MSBuildArgs.FromProperties(RestoreOptimizationProperties) .CloneWithAdditionalTargets("Restore") .CloneWithExplicitArgs([.. newArgumentsToAdd, .. existingArgumentsToForward]) .CloneWithAdditionalProperties(restoreProperties); - if (msbuildArgs.Verbosity is {} verbosity) + if (msbuildArgs.Verbosity is { } verbosity) { restoreMSBuildArgs = restoreMSBuildArgs.CloneWithVerbosity(verbosity); } @@ -175,7 +175,7 @@ private static bool HasPropertyToExcludeFromRestore(MSBuildArgs msbuildArgs) private static readonly List FlagsThatTriggerSilentSeparateRestore = [.. ComputeFlags(FlagsThatTriggerSilentRestore)]; - private static readonly List PropertiesToExcludeFromSeparateRestore = [ .. PropertiesToExcludeFromRestore ]; + private static readonly List PropertiesToExcludeFromSeparateRestore = [.. PropertiesToExcludeFromRestore]; /// /// We investigate the arguments we're about to send to a separate restore call and filter out diff --git a/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs b/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs index a1776027476d..3e4c7b2bd53e 100644 --- a/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs +++ b/src/Cli/dotnet/Commands/Run/LaunchSettings/LaunchSettingsManager.cs @@ -84,7 +84,7 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett { if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String) { - if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey)) + if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey)) { profileObject = prop.Value; break; @@ -120,7 +120,7 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett } } - private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)]out ILaunchSettingsProvider? provider) + private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)] out ILaunchSettingsProvider? provider) { if (commandName == null) { diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs index 9c11228ab213..d49df20c22e7 100644 --- a/src/Cli/dotnet/Commands/Run/RunCommand.cs +++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs @@ -403,29 +403,36 @@ internal ICommand GetTargetCommand(Func? pro Reporter.Verbose.WriteLine("Getting target command: evaluating project."); FacadeLogger? logger = LoggerUtility.DetermineBinlogger([.. MSBuildArgs.OtherMSBuildArgs], "dotnet-run"); - var project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger); + var (project, telemetryCentralLogger) = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger); ValidatePreconditions(project); - InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs); + InvokeRunArgumentsTarget(project, NoBuild, logger, MSBuildArgs, telemetryCentralLogger); logger?.ReallyShutdown(); var runProperties = RunProperties.FromProject(project).WithApplicationArguments(ApplicationArgs); var command = CreateCommandFromRunProperties(runProperties); return command; - static ProjectInstance EvaluateProject(string? projectFilePath, Func? projectFactory, MSBuildArgs msbuildArgs, ILogger? binaryLogger) + static (ProjectInstance project, ILogger? telemetryCentralLogger) EvaluateProject(string? projectFilePath, Func? projectFactory, MSBuildArgs msbuildArgs, ILogger? binaryLogger) { Debug.Assert(projectFilePath is not null || projectFactory is not null); var globalProperties = CommonRunHelpers.GetGlobalPropertiesFromArgs(msbuildArgs); - var collection = new ProjectCollection(globalProperties: globalProperties, loggers: binaryLogger is null ? null : [binaryLogger], toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); + // Include telemetry logger for evaluation and capture it for reuse in builds + var (loggers, telemetryCentralLogger) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(binaryLogger is null ? null : [binaryLogger]); + var collection = new ProjectCollection(globalProperties: globalProperties, loggers: loggers, toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); + ProjectInstance projectInstance; if (projectFilePath is not null) { - return collection.LoadProject(projectFilePath).CreateProjectInstance(); + projectInstance = collection.LoadProject(projectFilePath).CreateProjectInstance(); + } + else + { + Debug.Assert(projectFactory is not null); + projectInstance = projectFactory(collection); } - Debug.Assert(projectFactory is not null); - return projectFactory(collection); + return (projectInstance, telemetryCentralLogger); } static void ValidatePreconditions(ProjectInstance project) @@ -481,7 +488,7 @@ static ICommand CreateCommandForCscBuiltProgram(string entryPointFileFullPath, s return command; } - static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, FacadeLogger? binaryLogger, MSBuildArgs buildArgs) + static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, FacadeLogger? binaryLogger, MSBuildArgs buildArgs, ILogger? telemetryCentralLogger) { List loggersForBuild = [ CommonRunHelpers.GetConsoleLogger( @@ -493,7 +500,7 @@ static void InvokeRunArgumentsTarget(ProjectInstance project, bool noBuild, Faca loggersForBuild.Add(binaryLogger); } - if (!project.Build([Constants.ComputeRunArguments], loggers: loggersForBuild, remoteLoggers: null, out _)) + if (!project.BuildWithTelemetry([Constants.ComputeRunArguments], loggersForBuild, null, out _, telemetryCentralLogger)) { throw new GracefulException(CliCommandStrings.RunCommandEvaluationExceptionBuildFailed, Constants.ComputeRunArguments); } diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs index 7a4ff1295838..e21aec167a80 100644 --- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs +++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs @@ -24,6 +24,7 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.Cli.Commands.Clean.FileBasedAppArtifacts; using Microsoft.DotNet.Cli.Commands.Restore; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.DotNet.FileBasedPrograms; @@ -298,14 +299,17 @@ public override int Execute() // Set up MSBuild. ReadOnlySpan binaryLoggers = binaryLogger is null ? [] : [binaryLogger.Value]; - IEnumerable loggers = [.. binaryLoggers, consoleLogger]; + IEnumerable existingLoggers = [.. binaryLoggers, consoleLogger]; + + // Include telemetry logger for evaluation and capture it for potential future use + var (loggersWithTelemetry, telemetryCentralLogger) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(existingLoggers); var projectCollection = new ProjectCollection( MSBuildArgs.GlobalProperties, - loggers, + loggersWithTelemetry, ToolsetDefinitionLocations.Default); var parameters = new BuildParameters(projectCollection) { - Loggers = loggers, + Loggers = loggersWithTelemetry, LogTaskInputs = binaryLoggers.Length != 0, }; diff --git a/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs b/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs index 074431c8981b..8c445a02d87f 100644 --- a/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Migrate/SolutionMigrateCommand.cs @@ -29,7 +29,9 @@ public override int Execute() { ConvertToSlnxAsync(slnFileFullPath, slnxFileFullPath, CancellationToken.None).Wait(); return 0; - } catch (Exception ex) { + } + catch (Exception ex) + { throw new GracefulException(ex.Message, ex); } } diff --git a/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs b/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs index 36030bd22621..a0e624ef5a4a 100644 --- a/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs +++ b/src/Cli/dotnet/Commands/Solution/Remove/SolutionRemoveCommand.cs @@ -124,7 +124,7 @@ private static async Task RemoveProjectsAsync(string solutionFileFullPath, IEnum { solution.RemoveFolder(folder); // After removal, adjust index and continue to avoid skipping folders after removal - i--; + i--; } } diff --git a/src/Cli/dotnet/Commands/Store/StoreCommand.cs b/src/Cli/dotnet/Commands/Store/StoreCommand.cs index 0c7846c513e2..f02b52b641dc 100644 --- a/src/Cli/dotnet/Commands/Store/StoreCommand.cs +++ b/src/Cli/dotnet/Commands/Store/StoreCommand.cs @@ -19,7 +19,7 @@ private StoreCommand(IEnumerable msbuildArgs, string msbuildPath = null) public static StoreCommand FromArgs(string[] args, string msbuildPath = null) { - var result = Parser.Parse(["dotnet", "store", ..args]); + var result = Parser.Parse(["dotnet", "store", .. args]); return FromParseResult(result, msbuildPath); } diff --git a/src/Cli/dotnet/Commands/Test/MTP/MSBuildUtility.cs b/src/Cli/dotnet/Commands/Test/MTP/MSBuildUtility.cs index c83efdecce8d..f950dee0a815 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/MSBuildUtility.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/MSBuildUtility.cs @@ -6,6 +6,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.DotNet.Cli.Commands.Restore; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Extensions; @@ -37,9 +38,11 @@ public static (IEnumerable projects = GetProjectsProperties(collection, evaluationContext, solutionModel.SolutionProjects.Select(p => Path.Combine(rootDirectory, p.FilePath)), buildOptions); + ConcurrentBag projects = GetProjectsProperties(collection, evaluationContext, solutionModel.SolutionProjects.Select(p => Path.Combine(rootDirectory, p.FilePath)), buildOptions, telemetryCentralLogger); logger?.ReallyShutdown(); collection.UnloadAllProjects(); @@ -59,9 +62,11 @@ public static (IEnumerable projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, collection, evaluationContext, buildOptions); + IEnumerable projects = SolutionAndProjectUtility.GetProjectProperties(projectFilePath, collection, evaluationContext, buildOptions, telemetryCentralLogger); logger?.ReallyShutdown(); collection.UnloadAllProjects(); return (projects, isBuiltOrRestored); @@ -130,7 +135,7 @@ private static bool BuildOrRestoreProjectOrSolution(string filePath, BuildOption return result == (int)BuildResultCode.Success; } - private static ConcurrentBag GetProjectsProperties(ProjectCollection projectCollection, EvaluationContext evaluationContext, IEnumerable projects, BuildOptions buildOptions) + private static ConcurrentBag GetProjectsProperties(ProjectCollection projectCollection, EvaluationContext evaluationContext, IEnumerable projects, BuildOptions buildOptions, ILogger? telemetryCentralLogger) { var allProjects = new ConcurrentBag(); @@ -141,7 +146,7 @@ private static ConcurrentBag { - IEnumerable projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project, projectCollection, evaluationContext, buildOptions); + IEnumerable projectsMetadata = SolutionAndProjectUtility.GetProjectProperties(project, projectCollection, evaluationContext, buildOptions, telemetryCentralLogger); foreach (var projectMetadata in projectsMetadata) { allProjects.Add(projectMetadata); diff --git a/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs b/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs index c5ec75139a91..8f530a12de52 100644 --- a/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs +++ b/src/Cli/dotnet/Commands/Test/MTP/SolutionAndProjectUtility.cs @@ -6,8 +6,10 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Execution; +using Microsoft.Build.Framework; using Microsoft.DotNet.Cli.Commands.Run; using Microsoft.DotNet.Cli.Commands.Run.LaunchSettings; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils.Extensions; @@ -216,7 +218,7 @@ public static string GetRootDirectory(string solutionOrProjectFilePath) return string.IsNullOrEmpty(fileDirectory) ? Directory.GetCurrentDirectory() : fileDirectory; } - public static IEnumerable GetProjectProperties(string projectFilePath, ProjectCollection projectCollection, EvaluationContext evaluationContext, BuildOptions buildOptions) + public static IEnumerable GetProjectProperties(string projectFilePath, ProjectCollection projectCollection, EvaluationContext evaluationContext, BuildOptions buildOptions, ILogger? telemetryCentralLogger = null) { var projects = new List(); ProjectInstance projectInstance = EvaluateProject(projectCollection, evaluationContext, projectFilePath, null); @@ -228,7 +230,7 @@ public static IEnumerable(); innerModules.Add(module); @@ -287,7 +289,7 @@ public static IEnumerable a != "--")]; @@ -260,9 +260,10 @@ private static TestCommand FromParseResult(ParseResult result, string[] settings } } - + Dictionary variables = VSTestForwardingApp.GetVSTestRootVariables(); - foreach (var (rootVariableName, rootValue) in variables) { + foreach (var (rootVariableName, rootValue) in variables) + { testCommand.EnvironmentVariable(rootVariableName, rootValue); VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}"); } diff --git a/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs b/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs index fb81e15466f9..26a021485c97 100644 --- a/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs +++ b/src/Cli/dotnet/Commands/Test/VSTest/VSTestForwardingApp.cs @@ -20,7 +20,7 @@ public VSTestForwardingApp(IEnumerable argsToForward) WithEnvironmentVariable(rootVariableName, rootValue); VSTestTrace.SafeWriteTrace(() => $"Root variable set {rootVariableName}:{rootValue}"); } - + VSTestTrace.SafeWriteTrace(() => $"Forwarding to '{GetVSTestExePath()}' with args \"{argsToForward?.Aggregate((a, b) => $"{a} | {b}")}\""); } diff --git a/src/Cli/dotnet/Commands/Tool/Install/ToolInstallGlobalOrToolPathCommand.cs b/src/Cli/dotnet/Commands/Tool/Install/ToolInstallGlobalOrToolPathCommand.cs index c465e20372e5..431b92f2c654 100644 --- a/src/Cli/dotnet/Commands/Tool/Install/ToolInstallGlobalOrToolPathCommand.cs +++ b/src/Cli/dotnet/Commands/Tool/Install/ToolInstallGlobalOrToolPathCommand.cs @@ -5,20 +5,20 @@ using System.CommandLine; using System.Transactions; +using Microsoft.DotNet.Cli.Commands.Tool.Common; +using Microsoft.DotNet.Cli.Commands.Tool.List; +using Microsoft.DotNet.Cli.Commands.Tool.Uninstall; +using Microsoft.DotNet.Cli.Commands.Tool.Update; +using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.Cli.ShellShim; using Microsoft.DotNet.Cli.ToolPackage; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Common; using NuGet.Frameworks; using NuGet.Versioning; -using Microsoft.DotNet.Cli.Utils.Extensions; -using Microsoft.DotNet.Cli.Extensions; -using Microsoft.DotNet.Cli.ShellShim; -using Microsoft.DotNet.Cli.Commands.Tool.Update; -using Microsoft.DotNet.Cli.Commands.Tool.Common; -using Microsoft.DotNet.Cli.Commands.Tool.Uninstall; -using Microsoft.DotNet.Cli.Commands.Tool.List; namespace Microsoft.DotNet.Cli.Commands.Tool.Install; @@ -187,7 +187,7 @@ private int ExecuteInstallCommand(PackageId packageId) { _reporter.WriteLine(string.Format(CliCommandStrings.ToolAlreadyInstalled, oldPackageNullable.Id, oldPackageNullable.Version.ToNormalizedString()).Green()); return 0; - } + } } TransactionalAction.Run(() => @@ -318,7 +318,7 @@ private static void RunWithHandlingUninstallError(Action uninstallAction, Packag { try { - uninstallAction(); + uninstallAction(); } catch (Exception ex) when (ToolUninstallCommandLowLevelErrorConverter.ShouldConvertToUserFacingError(ex)) @@ -396,7 +396,7 @@ private void PrintSuccessMessage(IToolPackage oldPackage, IToolPackage newInstal { _reporter.WriteLine( string.Format( - + newInstalledPackage.Version.IsPrerelease ? CliCommandStrings.UpdateSucceededPreVersionNoChange : CliCommandStrings.UpdateSucceededStableVersionNoChange, newInstalledPackage.Id, newInstalledPackage.Version).Green()); diff --git a/src/Cli/dotnet/Commands/Tool/Install/ToolInstallLocalCommand.cs b/src/Cli/dotnet/Commands/Tool/Install/ToolInstallLocalCommand.cs index 87fb7860f992..e0bf8ccd3247 100644 --- a/src/Cli/dotnet/Commands/Tool/Install/ToolInstallLocalCommand.cs +++ b/src/Cli/dotnet/Commands/Tool/Install/ToolInstallLocalCommand.cs @@ -83,7 +83,7 @@ public override int Execute() } else { - return ExecuteInstallCommand((PackageId) _packageId); + return ExecuteInstallCommand((PackageId)_packageId); } } diff --git a/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs b/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs index 2ff9552ceeca..914f19efe192 100644 --- a/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs +++ b/src/Cli/dotnet/Commands/Tool/List/ToolListJsonHelper.cs @@ -10,12 +10,12 @@ namespace Microsoft.DotNet.Cli.Commands.Tool.List; internal sealed class VersionedDataContract { - /// - /// The version of the JSON format for dotnet tool list. - /// + /// + /// The version of the JSON format for dotnet tool list. + /// [JsonPropertyName("version")] public int Version { get; init; } = 1; - + [JsonPropertyName("data")] public required TContract Data { get; init; } } @@ -24,10 +24,10 @@ internal class ToolListJsonContract { [JsonPropertyName("packageId")] public required string PackageId { get; init; } - + [JsonPropertyName("version")] public required string Version { get; init; } - + [JsonPropertyName("commands")] public required string[] Commands { get; init; } } diff --git a/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs b/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs index b1c3b3f4ed52..1377a97cb006 100644 --- a/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs +++ b/src/Cli/dotnet/Commands/Tool/Restore/ToolPackageRestorer.cs @@ -109,7 +109,7 @@ private static bool ManifestCommandMatchesActualInPackage( IReadOnlyList toolPackageCommands) { ToolCommandName[] commandsFromPackage = [.. toolPackageCommands.Select(t => t.Name)]; -return !commandsFromManifest.Any(cmd => !commandsFromPackage.Contains(cmd)) && !commandsFromPackage.Any(cmd => !commandsFromManifest.Contains(cmd)); + return !commandsFromManifest.Any(cmd => !commandsFromPackage.Contains(cmd)) && !commandsFromPackage.Any(cmd => !commandsFromManifest.Contains(cmd)); } public bool PackageHasBeenRestored( diff --git a/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs b/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs index 58db9f55cc04..6db95e91941a 100644 --- a/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs +++ b/src/Cli/dotnet/Commands/Tool/Uninstall/ToolUninstallGlobalOrToolPathCommand.cs @@ -73,7 +73,7 @@ public override int Execute() TransactionalAction.Run(() => { shellShimRepository.RemoveShim(package.Command); - + toolPackageUninstaller.Uninstall(package.PackageDirectory); }); diff --git a/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs b/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs index 4c73cebd76f0..2d4c881bbc83 100644 --- a/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs +++ b/src/Cli/dotnet/Commands/Tool/Update/ToolUpdateGlobalOrToolPathCommand.cs @@ -4,12 +4,12 @@ #nullable disable using System.CommandLine; +using Microsoft.DotNet.Cli.Commands.Tool.Install; +using Microsoft.DotNet.Cli.ShellShim; +using Microsoft.DotNet.Cli.ToolPackage; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.EnvironmentAbstractions; -using Microsoft.DotNet.Cli.ToolPackage; using CreateShellShimRepository = Microsoft.DotNet.Cli.Commands.Tool.Install.CreateShellShimRepository; -using Microsoft.DotNet.Cli.ShellShim; -using Microsoft.DotNet.Cli.Commands.Tool.Install; namespace Microsoft.DotNet.Cli.Commands.Tool.Update; diff --git a/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs b/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs index ceebc46404a9..cbb727effd59 100644 --- a/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs +++ b/src/Cli/dotnet/Commands/Workload/History/WorkloadHistoryCommand.cs @@ -4,11 +4,11 @@ #nullable disable using System.CommandLine; +using Microsoft.Deployment.DotNet.Releases; +using Microsoft.DotNet.Cli.Commands.Workload.Install; using Microsoft.DotNet.Cli.NuGetPackageDownloader; using Microsoft.DotNet.Cli.Utils; using Microsoft.NET.Sdk.WorkloadManifestReader; -using Microsoft.Deployment.DotNet.Releases; -using Microsoft.DotNet.Cli.Commands.Workload.Install; namespace Microsoft.DotNet.Cli.Commands.Workload.History; diff --git a/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs b/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs index 1dbc16110933..70902ad7da46 100644 --- a/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs +++ b/src/Cli/dotnet/Commands/Workload/Restore/WorkloadRestoreCommand.cs @@ -60,7 +60,7 @@ public override int Execute() }); workloadInstaller.Shutdown(); - + return 0; } @@ -82,7 +82,7 @@ private List RunTargetToGetWorkloadIds(IEnumerable allProjec continue; } - bool buildResult = project.Build([GetRequiredWorkloadsTargetName], + bool buildResult = project.BuildWithTelemetry([GetRequiredWorkloadsTargetName], loggers: [ new ConsoleLogger(Verbosity.ToLoggerVerbosity()) ], diff --git a/src/Cli/dotnet/Commands/Workload/WorkloadCommandBase.cs b/src/Cli/dotnet/Commands/Workload/WorkloadCommandBase.cs index 44b441349be3..83c3622afd18 100644 --- a/src/Cli/dotnet/Commands/Workload/WorkloadCommandBase.cs +++ b/src/Cli/dotnet/Commands/Workload/WorkloadCommandBase.cs @@ -96,7 +96,7 @@ public WorkloadCommandBase( Verbosity = verbosityOptions == null ? parseResult.GetValue(CommonOptions.VerbosityOption(VerbosityOptions.normal)) - : parseResult.GetValue(verbosityOptions) ; + : parseResult.GetValue(verbosityOptions); ILogger nugetLogger = Verbosity.IsDetailedOrDiagnostic() ? new NuGetConsoleLogger() : new NullLogger(); diff --git a/src/Cli/dotnet/Commands/Workload/WorkloadCommandParser.cs b/src/Cli/dotnet/Commands/Workload/WorkloadCommandParser.cs index 32a38bdd9af9..4910fa324c34 100644 --- a/src/Cli/dotnet/Commands/Workload/WorkloadCommandParser.cs +++ b/src/Cli/dotnet/Commands/Workload/WorkloadCommandParser.cs @@ -20,8 +20,8 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.NET.Sdk.WorkloadManifestReader; using Microsoft.TemplateEngine.Cli.Commands; -using IReporter = Microsoft.DotNet.Cli.Utils.IReporter; using Command = System.CommandLine.Command; +using IReporter = Microsoft.DotNet.Cli.Utils.IReporter; namespace Microsoft.DotNet.Cli.Commands.Workload; diff --git a/src/Cli/dotnet/CommonOptions.cs b/src/Cli/dotnet/CommonOptions.cs index aa1730a23525..2b0c376c906f 100644 --- a/src/Cli/dotnet/CommonOptions.cs +++ b/src/Cli/dotnet/CommonOptions.cs @@ -348,7 +348,7 @@ public static ForwardedOption InteractiveOption(bool acceptArgument = fals }; public static readonly Option> EnvOption = CreateEnvOption(CliStrings.CmdEnvironmentVariableDescription); - + public static readonly Option> TestEnvOption = CreateEnvOption(CliStrings.CmdTestEnvironmentVariableDescription); private static IReadOnlyDictionary ParseEnvironmentVariables(ArgumentResult argumentResult) diff --git a/src/Cli/dotnet/DotNetCommandFactory.cs b/src/Cli/dotnet/DotNetCommandFactory.cs index ea5eb912e8f6..dcb70b05e6c9 100644 --- a/src/Cli/dotnet/DotNetCommandFactory.cs +++ b/src/Cli/dotnet/DotNetCommandFactory.cs @@ -38,7 +38,7 @@ private static bool TryGetBuiltInCommand(string commandName, out Func Parser.Invoke([commandName, ..args]); + commandFunc = (args) => Parser.Invoke([commandName, .. args]); return true; } commandFunc = null; diff --git a/src/Cli/dotnet/Extensions/CommonOptionsExtensions.cs b/src/Cli/dotnet/Extensions/CommonOptionsExtensions.cs index 9254bbd73b77..a225056f02f8 100644 --- a/src/Cli/dotnet/Extensions/CommonOptionsExtensions.cs +++ b/src/Cli/dotnet/Extensions/CommonOptionsExtensions.cs @@ -4,8 +4,8 @@ #nullable disable using Microsoft.Build.Framework; -using Microsoft.Extensions.Logging; using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Cli.Extensions; diff --git a/src/Cli/dotnet/Extensions/ProjectInstanceExtensions.cs b/src/Cli/dotnet/Extensions/ProjectInstanceExtensions.cs index 749007923950..41a99dcb3285 100644 --- a/src/Cli/dotnet/Extensions/ProjectInstanceExtensions.cs +++ b/src/Cli/dotnet/Extensions/ProjectInstanceExtensions.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.Build.Logging; +using Microsoft.DotNet.Cli.Commands.MSBuild; namespace Microsoft.DotNet.Cli.Extensions; @@ -47,4 +50,200 @@ public static IEnumerable GetConfigurations(this ProjectInstance project .Where(c => !string.IsNullOrWhiteSpace(c)) .DefaultIfEmpty("Debug"); } + + /// + /// Creates the central telemetry logger for API-based MSBuild usage if telemetry is enabled. + /// This logger should be used for evaluation (ProjectCollection) and as a central logger for builds. + /// Returns null if telemetry is not enabled or if there's an error creating the logger. + /// + /// The central telemetry logger, or null if telemetry is disabled. + public static ILogger? CreateTelemetryCentralLogger() + { + if (Telemetry.Telemetry.CurrentSessionId != null) + { + try + { + return new MSBuildLogger(); + } + catch (Exception) + { + // Exceptions during telemetry shouldn't cause anything else to fail + } + } + return null; + } + + /// + /// Creates the forwarding logger record for distributed builds using the provided central logger. + /// This should be used with the remoteLoggers parameter of ProjectInstance.Build. + /// The same central logger instance from ProjectCollection should be reused here. + /// Returns an empty collection if the central logger is null or if there's an error. + /// + /// The central logger instance (typically the same one used in ProjectCollection). + /// An array containing the forwarding logger record, or empty array if central logger is null. + public static ForwardingLoggerRecord[] CreateTelemetryForwardingLoggerRecords(ILogger? centralLogger) + { + if (centralLogger is MSBuildLogger msbuildLogger) + { + try + { + // LoggerDescription describes the forwarding logger that worker nodes will create + var forwardingLoggerDescription = new Microsoft.Build.Logging.LoggerDescription( + loggerClassName: typeof(MSBuildForwardingLogger).FullName!, + loggerAssemblyName: typeof(MSBuildForwardingLogger).Assembly.Location, + loggerAssemblyFile: null, + loggerSwitchParameters: null, + verbosity: LoggerVerbosity.Normal); + + var loggerRecord = new ForwardingLoggerRecord(msbuildLogger, forwardingLoggerDescription); + return [loggerRecord]; + } + catch (Exception) + { + // Exceptions during telemetry shouldn't cause anything else to fail + } + } + return []; + } + + /// + /// Builds the project with the specified targets, automatically including telemetry loggers + /// as a distributed logger (central logger + forwarding logger). + /// + /// The project instance to build. + /// The targets to build. + /// Additional loggers to include. + /// Optional telemetry central logger from ProjectCollection. If null, creates a new one. + public static bool BuildWithTelemetry( + this ProjectInstance projectInstance, + string[] targets, + IEnumerable? additionalLoggers = null, + ILogger? telemetryCentralLogger = null) + { + var loggers = new List(); + var forwardingLoggers = new List(); + + // Add telemetry as a distributed logger via ForwardingLoggerRecord + // Use provided central logger or create a new one + var centralLogger = telemetryCentralLogger ?? CreateTelemetryCentralLogger(); + forwardingLoggers.AddRange(CreateTelemetryForwardingLoggerRecords(centralLogger)); + + if (additionalLoggers != null) + { + loggers.AddRange(additionalLoggers); + } + + // Use the overload that accepts forwarding loggers for proper distributed logging + return projectInstance.Build( + targets, + loggers.Count > 0 ? loggers : null, + forwardingLoggers.Count > 0 ? forwardingLoggers : null, + out _); + } + + /// + /// Builds the project with the specified targets, automatically including telemetry loggers + /// as a distributed logger (central logger + forwarding logger). + /// + /// The project instance to build. + /// The targets to build. + /// Loggers to include. + /// The outputs from the build. + /// Optional telemetry central logger from ProjectCollection. If null, creates a new one. + public static bool BuildWithTelemetry( + this ProjectInstance projectInstance, + string[] targets, + IEnumerable? loggers, + out IDictionary targetOutputs, + ILogger? telemetryCentralLogger = null) + { + var allLoggers = new List(); + var forwardingLoggers = new List(); + + // Add telemetry as a distributed logger via ForwardingLoggerRecord + // Use provided central logger or create a new one + var centralLogger = telemetryCentralLogger ?? CreateTelemetryCentralLogger(); + forwardingLoggers.AddRange(CreateTelemetryForwardingLoggerRecords(centralLogger)); + + if (loggers != null) + { + allLoggers.AddRange(loggers); + } + + // Use the overload that accepts forwarding loggers for proper distributed logging + return projectInstance.Build( + targets, + allLoggers.Count > 0 ? allLoggers : null, + forwardingLoggers.Count > 0 ? forwardingLoggers : null, + out targetOutputs); + } + + /// + /// Builds the project with the specified targets, automatically including telemetry loggers + /// as a distributed logger (central logger + forwarding logger). + /// + /// The project instance to build. + /// The targets to build. + /// Loggers to include. + /// Remote/forwarding loggers to include. + /// The outputs from the build. + /// Optional telemetry central logger from ProjectCollection. If null, creates a new one. + public static bool BuildWithTelemetry( + this ProjectInstance projectInstance, + string[] targets, + IEnumerable? loggers, + IEnumerable? remoteLoggers, + out IDictionary targetOutputs, + ILogger? telemetryCentralLogger = null) + { + var allLoggers = new List(); + var allForwardingLoggers = new List(); + + // Add telemetry as a distributed logger via ForwardingLoggerRecord + // Use provided central logger or create a new one + var centralLogger = telemetryCentralLogger ?? CreateTelemetryCentralLogger(); + allForwardingLoggers.AddRange(CreateTelemetryForwardingLoggerRecords(centralLogger)); + + if (loggers != null) + { + allLoggers.AddRange(loggers); + } + + if (remoteLoggers != null) + { + allForwardingLoggers.AddRange(remoteLoggers); + } + + return projectInstance.Build( + targets, + allLoggers.Count > 0 ? allLoggers : null, + allForwardingLoggers.Count > 0 ? allForwardingLoggers : null, + out targetOutputs); + } + + /// + /// Creates a logger collection that includes the telemetry central logger. + /// This is useful for ProjectCollection scenarios where evaluation needs telemetry. + /// Returns both the logger array and the telemetry central logger instance for reuse in subsequent builds. + /// + /// Additional loggers to include in the collection. + /// A tuple containing the logger array and the telemetry central logger (or null if no telemetry). + public static (ILogger[]? loggers, ILogger? telemetryCentralLogger) CreateLoggersWithTelemetry(IEnumerable? additionalLoggers = null) + { + var loggers = new List(); + + // Add central telemetry logger for evaluation + var telemetryCentralLogger = CreateTelemetryCentralLogger(); + if (telemetryCentralLogger != null) + { + loggers.Add(telemetryCentralLogger); + } + + if (additionalLoggers != null) + { + loggers.AddRange(additionalLoggers); + } + + return (loggers.Count > 0 ? loggers.ToArray() : null, telemetryCentralLogger); + } } diff --git a/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs index a5e54ba06bb9..0c606c61dbf7 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/INuGetPackageDownloader.cs @@ -43,4 +43,4 @@ Task GetBestPackageVersionAsync(PackageId packageId, Task<(NuGetVersion version, PackageSource source)> GetBestPackageVersionAndSourceAsync(PackageId packageId, VersionRange versionRange, PackageSourceLocation packageSourceLocation = null); -} +} diff --git a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs index a311e88c646d..a0ce16fe6d0b 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs @@ -75,7 +75,7 @@ public NuGetPackageDownloader( _retryTimer = timer; _sourceRepositories = new(); // If windows or env variable is set, verify signatures - _verifySignatures = verifySignatures && (OperatingSystem.IsWindows() ? true + _verifySignatures = verifySignatures && (OperatingSystem.IsWindows() ? true : bool.TryParse(Environment.GetEnvironmentVariable(NuGetSignatureVerificationEnabler.DotNetNuGetSignatureVerification), out var shouldVerifySignature) ? shouldVerifySignature : OperatingSystem.IsLinux()); _cacheSettings = new SourceCacheContext @@ -122,7 +122,7 @@ public async Task DownloadPackageAsync(PackageId packageId, throw new ArgumentException($"Package download folder must be specified either via {nameof(NuGetPackageDownloader)} constructor or via {nameof(downloadFolder)} method argument."); } var pathResolver = new VersionFolderPathResolver(resolvedDownloadFolder); - + string nupkgPath = pathResolver.GetPackageFilePath(packageId.ToString(), resolvedPackageVersion); Directory.CreateDirectory(Path.GetDirectoryName(nupkgPath)); diff --git a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs index e85fb9878d4c..7c03df034464 100644 --- a/src/Cli/dotnet/ReleasePropertyProjectLocator.cs +++ b/src/Cli/dotnet/ReleasePropertyProjectLocator.cs @@ -230,7 +230,8 @@ DependentCommandOptions commandOptions { return projectData; } - }; + } + ; return null; } diff --git a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs index 015af6723629..7960deb22cc7 100644 --- a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs +++ b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs @@ -85,11 +85,11 @@ private static void CacheDeviceId(string deviceId) // Cache device Id in Windows registry matching the OS architecture using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64)) { - using(var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools")) + using (var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools")) { if (key != null) { - key.SetValue("deviceid", deviceId); + key.SetValue("deviceid", deviceId); } } } diff --git a/src/Cli/dotnet/Telemetry/Telemetry.cs b/src/Cli/dotnet/Telemetry/Telemetry.cs index d9c3a59bd8a1..38f0d1c7ca19 100644 --- a/src/Cli/dotnet/Telemetry/Telemetry.cs +++ b/src/Cli/dotnet/Telemetry/Telemetry.cs @@ -258,6 +258,6 @@ static IDictionary Combine(IDictionary { eventMeasurements[measurement.Key] = measurement.Value; } - return eventMeasurements; - } + return eventMeasurements; + } } diff --git a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs index 641c8c583a7c..9da8558f5384 100644 --- a/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs +++ b/src/Cli/dotnet/ToolPackage/ToolConfiguration.cs @@ -62,7 +62,7 @@ private static void EnsureNoLeadingDot(string commandName) } } - + public string CommandName { get; } public string ToolAssemblyEntryPoint { get; } diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenProjectInstanceExtensions.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenProjectInstanceExtensions.cs new file mode 100644 index 000000000000..ec452cf1ec70 --- /dev/null +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenProjectInstanceExtensions.cs @@ -0,0 +1,165 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.DotNet.Cli.Extensions; +using Microsoft.DotNet.Cli.Telemetry; + +namespace Microsoft.DotNet.Cli.MSBuild.Tests; + +public class GivenProjectInstanceExtensions +{ + [Fact] + public void CreateTelemetryCentralLogger_WhenTelemetryDisabled_ReturnsNull() + { + // Ensure telemetry is disabled + Telemetry.Telemetry.CurrentSessionId = null; + + var logger = ProjectInstanceExtensions.CreateTelemetryCentralLogger(); + + logger.Should().BeNull(); + } + + [Fact] + public void CreateTelemetryCentralLogger_WhenTelemetryEnabled_ReturnsLogger() + { + // Enable telemetry with a session ID + var originalSessionId = Telemetry.Telemetry.CurrentSessionId; + try + { + Telemetry.Telemetry.CurrentSessionId = Guid.NewGuid().ToString(); + + var logger = ProjectInstanceExtensions.CreateTelemetryCentralLogger(); + + logger.Should().NotBeNull(); + logger.Should().BeOfType(); + } + finally + { + // Restore original session ID + Telemetry.Telemetry.CurrentSessionId = originalSessionId; + } + } + + [Fact] + public void CreateTelemetryForwardingLoggerRecords_WhenTelemetryDisabled_ReturnsEmpty() + { + // Ensure telemetry is disabled + Telemetry.Telemetry.CurrentSessionId = null; + + var centralLogger = ProjectInstanceExtensions.CreateTelemetryCentralLogger(); + var loggerRecords = ProjectInstanceExtensions.CreateTelemetryForwardingLoggerRecords(centralLogger); + + loggerRecords.Should().BeEmpty(); + } + + [Fact] + public void CreateTelemetryForwardingLoggerRecords_WhenTelemetryEnabled_ReturnsLoggerRecords() + { + // Enable telemetry with a session ID + var originalSessionId = Telemetry.Telemetry.CurrentSessionId; + try + { + Telemetry.Telemetry.CurrentSessionId = Guid.NewGuid().ToString(); + + var centralLogger = ProjectInstanceExtensions.CreateTelemetryCentralLogger(); + var loggerRecords = ProjectInstanceExtensions.CreateTelemetryForwardingLoggerRecords(centralLogger); + + loggerRecords.Should().NotBeEmpty(); + loggerRecords.Should().HaveCount(1); + // ForwardingLoggerRecord contains the ForwardingLogger and LoggerDescription + loggerRecords[0].Should().NotBeNull(); + } + finally + { + // Restore original session ID + Telemetry.Telemetry.CurrentSessionId = originalSessionId; + } + } + + [Fact] + public void BuildWithTelemetry_WhenTelemetryEnabled_CreatesDistributedLogger() + { + // Enable telemetry with a session ID + var originalSessionId = Telemetry.Telemetry.CurrentSessionId; + try + { + Telemetry.Telemetry.CurrentSessionId = Guid.NewGuid().ToString(); + + // CreateTelemetryCentralLogger should return logger when telemetry is enabled + var centralLogger = ProjectInstanceExtensions.CreateTelemetryCentralLogger(); + centralLogger.Should().NotBeNull(); + + // CreateTelemetryForwardingLoggerRecords should return forwarding logger when telemetry is enabled + // using the same central logger instance + var forwardingLoggers = ProjectInstanceExtensions.CreateTelemetryForwardingLoggerRecords(centralLogger); + forwardingLoggers.Should().NotBeEmpty(); + } + finally + { + // Restore original session ID + Telemetry.Telemetry.CurrentSessionId = originalSessionId; + } + } + + [Fact] + public void TelemetryLogger_ReceivesEventsFromAPIBasedBuild() + { + // Enable telemetry with a session ID + var originalSessionId = Telemetry.Telemetry.CurrentSessionId; + try + { + Telemetry.Telemetry.CurrentSessionId = Guid.NewGuid().ToString(); + + // Create ProjectCollection with telemetry logger + var (loggers, telemetryCentralLogger) = ProjectInstanceExtensions.CreateLoggersWithTelemetry(); + using var collection = new ProjectCollection( + globalProperties: null, + loggers: loggers, + toolsetDefinitionLocations: ToolsetDefinitionLocations.Default); + + // Verify telemetry logger was created and included in the loggers array + telemetryCentralLogger.Should().NotBeNull(); + loggers.Should().Contain(telemetryCentralLogger); + + // Verify the collection was created successfully with loggers + collection.Should().NotBeNull(); + collection.Loggers.Should().NotBeEmpty(); + } + finally + { + // Restore original session ID + Telemetry.Telemetry.CurrentSessionId = originalSessionId; + } + } + + /// + /// Simple logger to track build events for testing + /// + private class TestEventLogger : ILogger + { + public int BuildStartedCount { get; private set; } + public int BuildFinishedCount { get; private set; } + public int TargetStartedCount { get; private set; } + public int TargetFinishedCount { get; private set; } + + public LoggerVerbosity Verbosity { get; set; } + public string Parameters { get; set; } + + public void Initialize(IEventSource eventSource) + { + eventSource.BuildStarted += (sender, e) => BuildStartedCount++; + eventSource.BuildFinished += (sender, e) => BuildFinishedCount++; + eventSource.TargetStarted += (sender, e) => TargetStartedCount++; + eventSource.TargetFinished += (sender, e) => TargetFinishedCount++; + } + + public void Shutdown() + { + } + } +}