| File: Program.cs | Web Access |
| Project: src\sdk\src\Cli\dotnet\dotnet.csproj (dotnet) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; using System.Diagnostics; using Microsoft.DotNet.Cli.Extensions; using Microsoft.DotNet.Cli.Utils; using System.CommandLine.Parsing; using Microsoft.DotNet.Cli.CommandFactory; using Microsoft.DotNet.Cli.CommandFactory.CommandResolution; using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils.Extensions; using Microsoft.DotNet.ProjectTools; using Microsoft.DotNet.Utilities; using NuGet.Frameworks; using CommandResult = System.CommandLine.Parsing.CommandResult; namespace Microsoft.DotNet.Cli; public class Program { private static readonly Activity? s_mainActivity; private static readonly PosixSignalRegistration s_sigIntRegistration; private static readonly PosixSignalRegistration s_sigQuitRegistration; private static readonly PosixSignalRegistration s_sigTermRegistration; private static readonly string? s_globalJsonState; public static ITelemetryClient TelemetryInstance { get; private set; } static Program() { var preTelemetry = DateTime.UtcNow; s_sigIntRegistration = PosixSignalRegistration.Create(PosixSignal.SIGINT, Shutdown); s_sigQuitRegistration = PosixSignalRegistration.Create(PosixSignal.SIGQUIT, Shutdown); s_sigTermRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, Shutdown); // Note: This TelemetryClient instance needs to be created prior to calculating ActivityKind and ParentActivityContext, // used in the main activity creation below. TelemetryInstance = new TelemetryClient(); TelemetryEventEntry.Subscribe(TelemetryInstance.TrackEvent); TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing); var postTelemetry = DateTime.UtcNow; s_mainActivity = Activities.Source.CreateActivity("main", TelemetryClient.ActivityKind, TelemetryClient.ParentActivityContext) ?.Start() ?.SetStartTime(Process.GetCurrentProcess().StartTime) ?.AddTag("process.pid", Process.GetCurrentProcess().Id) ?.AddTag("process.executable.name", "dotnet") ?.AddTag("cli.runtime", "managed"); using (var telemetrySetupActivity = Activities.Source.StartActivity("telemetry-setup")) { telemetrySetupActivity?.SetStartTime(preTelemetry); telemetrySetupActivity?.SetEndTime(postTelemetry); } if (CommandLoggingContext.IsVerbose) { Reporter.Verbose.WriteLine($"Telemetry is: {(TelemetryInstance.Enabled ? "Enabled" : "Disabled")}"); } if (TelemetryInstance.Enabled && s_mainActivity is not null) { s_globalJsonState = NativeWrapper.NETCoreSdkResolverNativeWrapper.GetGlobalJsonState(Environment.CurrentDirectory); s_mainActivity.AddTag("dotnet.globalJson", s_globalJsonState); } // We have some behaviors in MSBuild that we want to enforce (either when using MSBuild API or by shelling out to it), // so we set those ASAP as globally as possible. if (string.IsNullOrEmpty(Env.GetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD"))) { Environment.SetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD", "1"); } } public static int Main(string[] args) { // Register a handler for SIGTERM to allow graceful shutdown of the application on Unix. // See https://github.com/dotnet/docs/issues/46226. using var termSignalRegistration = PosixSignalRegistration.Create(PosixSignal.SIGTERM, _ => Environment.Exit(0)); using AutomaticEncodingRestorer _ = new(); if (Env.GetEnvironmentVariable(EnvironmentVariableNames.DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING) != "1" // Setting output encoding is not available on those platforms && UILanguageOverride.OperatingSystemSupportsUtf8()) { Console.OutputEncoding = Encoding.UTF8; } DebugHelper.HandleDebugSwitch(ref args); // By default, .NET Core doesn't have all code pages needed for Console apps. // See the .NET Core Notes: https://docs.microsoft.com/dotnet/api/system.diagnostics.process#-notes Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); UILanguageOverride.Setup(); var exitCode = 1; try { exitCode = ProcessArgsAndExecute(args); s_mainActivity?.AddTag("process.exit.code", exitCode)?.SetStatus(ActivityStatusCode.Ok); return exitCode; } catch (Exception e) when (e.ShouldBeDisplayedAsError()) { Reporter.Error.WriteLine(CommandLoggingContext.IsVerbose ? e.ToString().Red().Bold() : e.Message.Red().Bold()); if (e is CommandParsingException { ParseResult: {} exceptionParseResult } ) { exceptionParseResult.ShowHelp(); } s_mainActivity?.AddTag("process.exit.code", exitCode)?.SetStatus(ActivityStatusCode.Error); return exitCode; } catch (Exception e) when (!e.ShouldBeDisplayedAsError()) { TelemetryEventEntry.SendFiltered(e); Reporter.Error.WriteLine(e.ToString().Red().Bold()); s_mainActivity?.AddTag("process.exit.code", exitCode)?.SetStatus(ActivityStatusCode.Error); return exitCode; } finally { TelemetryInstance.TrackEvent("command/finish", new Dictionary<string, string?> { { "exitCode", exitCode.ToString() } }); Shutdown(default!); TelemetryClient.WriteLogIfNecessary(); } } internal static int ProcessArgsAndExecute(string[] args) { ParseResult parseResult = ParseArgs(args); // Run the cross-cutting first-run experience (first-time-use notice, telemetry message, // ASP.NET dev cert, global-tools PATH, workload integrity check). Terminating options such as // "dotnet --version" are skipped inside Setup. FirstRunExperience.Setup(parseResult); TelemetryEventEntry.SendFiltered(new ParseResultWithGlobalJsonState(parseResult, s_globalJsonState)); if (parseResult.CanBeInvoked()) { return CommandInvocation.ExecuteInternalCommand(parseResult); } try { return ExecuteExternalCommand(args, parseResult); } catch (CommandUnknownException e) { Reporter.Error.WriteLine(e.Message.Red()); Reporter.Output.WriteLine(e.InstructionMessage); return 1; } static ParseResult ParseArgs(string[] args) { ParseResult parseResult; using (var parseActivity = Activities.Source.StartActivity("parse")) { parseResult = Parser.Parse(args); // Avoid create temp directory with root permission and later prevent access in non sudo // This method need to be run very early before temp folder get created // https://github.com/dotnet/sdk/issues/20195 SudoEnvironmentDirectoryOverride.OverrideEnvironmentVariableToTmp(parseResult); } s_mainActivity.SetDisplayName(parseResult); return parseResult; } } private static int ExecuteExternalCommand(string[] args, ParseResult parseResult) { string commandName = "dotnet-" + parseResult.GetValue(Parser.RootCommand.DotnetSubCommand); CommandSpec? resolvedCommandSpec = null; using (var lookupActivity = Activities.Source.StartActivity("lookup-external-command")) { lookupActivity?.AddTag("command.name", commandName); resolvedCommandSpec = CommandResolver.TryResolveCommandSpec( new DefaultCommandResolverPolicy(), commandName, args.GetSubArguments(), FrameworkConstants.CommonFrameworks.NetStandardApp15); } if (resolvedCommandSpec is null && TryRunFileBasedApp(parseResult) is { } fileBasedAppExitCode) { return fileBasedAppExitCode; } var resolvedCommand = CommandFactoryUsingResolver.CreateOrThrow(commandName, resolvedCommandSpec); using var __ = Activities.Source.StartActivity("execute-extensible-command"); return resolvedCommand.Execute().ExitCode; } private static int? TryRunFileBasedApp(ParseResult parseResult) { // If we didn't match any built-in commands, and a C# file path is the first argument, // parse as `dotnet run file.cs ..rest_of_args` instead. if (parseResult.GetFileBasedAppEntryPointToken() is { } unmatchedCommandOrFile) { List<string> otherTokens = new(parseResult.Tokens.Count - 1); foreach (var token in parseResult.Tokens) { if (token.Type != TokenType.Argument || token != unmatchedCommandOrFile) { otherTokens.Add(token.Value); } } parseResult = Parser.Parse(["run", "--file", unmatchedCommandOrFile.Value, .. otherTokens]); return CommandInvocation.ExecuteInternalCommand(parseResult); } return null; } public static void Shutdown(PosixSignalContext context) { s_sigIntRegistration.Dispose(); s_sigQuitRegistration.Dispose(); s_sigTermRegistration.Dispose(); s_mainActivity?.Stop(); TelemetryClient.FlushProviders(); Activities.Source.Dispose(); } }