dotnet-installer/src/dotnet/Program.cs
Peter Huene 92507184bd
Log a verbose message when DOTNET_CLI_HOME is being used.
This commit logs a diagnostic message when the `DOTNET_CLI_HOME` variable is
used.  This enables users to determine where first-run-experience and global
tool files are being written to.

Fixes #9510.
2018-06-26 16:34:00 -07:00

347 lines
15 KiB
C#

// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Configurer;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.DotNet.ShellShim;
using Microsoft.DotNet.Tools.Help;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Frameworks;
using Command = Microsoft.DotNet.Cli.Utils.Command;
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
using LocalizableStrings = Microsoft.DotNet.Cli.Utils.LocalizableStrings;
namespace Microsoft.DotNet.Cli
{
public class Program
{
private static readonly string ToolPathSentinelFileName = $"{Product.Version}.toolpath.sentinel";
public static int Main(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);
new MulticoreJitActivator().TryActivateMulticoreJit();
if (Env.GetEnvironmentVariableAsBool("DOTNET_CLI_CAPTURE_TIMING", false))
{
PerfTrace.Enabled = true;
}
InitializeProcess();
try
{
using (PerfTrace.Current.CaptureTiming())
{
return ProcessArgs(args);
}
}
catch (HelpException e)
{
Reporter.Output.WriteLine(e.Message);
return 0;
}
catch (Exception e) when (e.ShouldBeDisplayedAsError())
{
Reporter.Error.WriteLine(CommandContext.IsVerbose()
? e.ToString().Red().Bold()
: e.Message.Red().Bold());
var commandParsingException = e as CommandParsingException;
if (commandParsingException != null)
{
Reporter.Output.WriteLine(commandParsingException.HelpText);
}
return 1;
}
catch (Exception e) when (!e.ShouldBeDisplayedAsError())
{
Reporter.Error.WriteLine(e.ToString().Red().Bold());
return 1;
}
finally
{
if (PerfTrace.Enabled)
{
Reporter.Output.WriteLine("Performance Summary:");
PerfTraceOutput.Print(Reporter.Output, PerfTrace.GetEvents());
}
}
}
internal static int ProcessArgs(string[] args, ITelemetry telemetryClient = null)
{
// CommandLineApplication is a bit restrictive, so we parse things ourselves here. Individual apps should use CLA.
var success = true;
var command = string.Empty;
var lastArg = 0;
TopLevelCommandParserResult topLevelCommandParserResult = TopLevelCommandParserResult.Empty;
using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel())
using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel =
new FirstTimeUseNoticeSentinel())
{
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel = disposableFirstTimeUseNoticeSentinel;
IAspNetCertificateSentinel aspNetCertificateSentinel = new AspNetCertificateSentinel();
IFileSentinel toolPathSentinel = new FileSentinel(
new FilePath(
Path.Combine(
CliFolderPathCalculator.DotnetUserProfileFolderPath,
ToolPathSentinelFileName)));
for (; lastArg < args.Length; lastArg++)
{
if (IsArg(args[lastArg], "d", "diagnostics"))
{
Environment.SetEnvironmentVariable(CommandContext.Variables.Verbose, bool.TrueString);
CommandContext.SetVerbose(true);
}
else if (IsArg(args[lastArg], "version"))
{
PrintVersion();
return 0;
}
else if (IsArg(args[lastArg], "info"))
{
PrintInfo();
return 0;
}
else if (IsArg(args[lastArg], "h", "help") ||
args[lastArg] == "-?" ||
args[lastArg] == "/?")
{
HelpCommand.PrintHelp();
return 0;
}
else if (args[lastArg].StartsWith("-", StringComparison.OrdinalIgnoreCase))
{
Reporter.Error.WriteLine($"Unknown option: {args[lastArg]}");
success = false;
}
else
{
// It's the command, and we're done!
command = args[lastArg];
if (string.IsNullOrEmpty(command))
{
command = "help";
}
var environmentProvider = new EnvironmentProvider();
bool generateAspNetCertificate =
environmentProvider.GetEnvironmentVariableAsBool("DOTNET_GENERATE_ASPNET_CERTIFICATE", true);
bool printTelemetryMessage =
environmentProvider.GetEnvironmentVariableAsBool("DOTNET_PRINT_TELEMETRY_MESSAGE", true);
bool skipFirstRunExperience =
environmentProvider.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false);
ReportDotnetHomeUsage(environmentProvider);
topLevelCommandParserResult = new TopLevelCommandParserResult(command);
var hasSuperUserAccess = false;
if (IsDotnetBeingInvokedFromNativeInstaller(topLevelCommandParserResult))
{
aspNetCertificateSentinel = new NoOpAspNetCertificateSentinel();
firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel();
toolPathSentinel = new NoOpFileSentinel(exists: false);
hasSuperUserAccess = true;
// When running through a native installer, we want the cache expansion to happen, so
// we need to override this.
skipFirstRunExperience = false;
}
var dotnetFirstRunConfiguration = new DotnetFirstRunConfiguration(
generateAspNetCertificate,
printTelemetryMessage,
skipFirstRunExperience);
ConfigureDotNetForFirstTimeUse(
nugetCacheSentinel,
firstTimeUseNoticeSentinel,
aspNetCertificateSentinel,
toolPathSentinel,
hasSuperUserAccess,
dotnetFirstRunConfiguration,
environmentProvider);
break;
}
}
if (!success)
{
HelpCommand.PrintHelp();
return 1;
}
if (telemetryClient == null)
{
telemetryClient = new Telemetry.Telemetry(firstTimeUseNoticeSentinel);
}
TelemetryEventEntry.Subscribe(telemetryClient.TrackEvent);
TelemetryEventEntry.TelemetryFilter = new TelemetryFilter(Sha256Hasher.HashWithNormalizedCasing);
}
IEnumerable<string> appArgs =
(lastArg + 1) >= args.Length
? Enumerable.Empty<string>()
: args.Skip(lastArg + 1).ToArray();
if (CommandContext.IsVerbose())
{
Console.WriteLine($"Telemetry is: {(telemetryClient.Enabled ? "Enabled" : "Disabled")}");
}
TelemetryEventEntry.SendFiltered(topLevelCommandParserResult);
int exitCode;
if (BuiltInCommandsCatalog.Commands.TryGetValue(topLevelCommandParserResult.Command, out var builtIn))
{
var parseResult = Parser.Instance.ParseFrom($"dotnet {topLevelCommandParserResult.Command}", appArgs.ToArray());
if (!parseResult.Errors.Any())
{
TelemetryEventEntry.SendFiltered(parseResult);
}
exitCode = builtIn.Command(appArgs.ToArray());
}
else
{
CommandResult result = Command.Create(
"dotnet-" + topLevelCommandParserResult.Command,
appArgs,
FrameworkConstants.CommonFrameworks.NetStandardApp15)
.Execute();
exitCode = result.ExitCode;
}
return exitCode;
}
private static void ReportDotnetHomeUsage(IEnvironmentProvider provider)
{
var home = provider.GetEnvironmentVariable(CliFolderPathCalculator.DotnetHomeVariableName);
if (string.IsNullOrEmpty(home))
{
return;
}
Reporter.Verbose.WriteLine(
string.Format(
LocalizableStrings.DotnetCliHomeUsed,
home,
CliFolderPathCalculator.DotnetHomeVariableName));
}
private static bool IsDotnetBeingInvokedFromNativeInstaller(TopLevelCommandParserResult parseResult)
{
return parseResult.Command == "internal-reportinstallsuccess";
}
private static void ConfigureDotNetForFirstTimeUse(
INuGetCacheSentinel nugetCacheSentinel,
IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel,
IAspNetCertificateSentinel aspNetCertificateSentinel,
IFileSentinel toolPathSentinel,
bool hasSuperUserAccess,
DotnetFirstRunConfiguration dotnetFirstRunConfiguration,
IEnvironmentProvider environmentProvider)
{
using (PerfTrace.Current.CaptureTiming())
{
var nugetPackagesArchiver = new NuGetPackagesArchiver();
var environmentPath = EnvironmentPathFactory.CreateEnvironmentPath(hasSuperUserAccess, environmentProvider);
var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true);
var nugetCachePrimer = new NuGetCachePrimer(
nugetPackagesArchiver,
nugetCacheSentinel);
var aspnetCertificateGenerator = new AspNetCoreCertificateGenerator();
var dotnetConfigurer = new DotnetFirstTimeUseConfigurer(
nugetCachePrimer,
nugetCacheSentinel,
firstTimeUseNoticeSentinel,
aspNetCertificateSentinel,
aspnetCertificateGenerator,
toolPathSentinel,
dotnetFirstRunConfiguration,
Reporter.Output,
CliFolderPathCalculator.CliFallbackFolderPath,
environmentPath);
dotnetConfigurer.Configure();
}
}
private static void InitializeProcess()
{
// by default, .NET Core doesn't have all code pages needed for Console apps.
// see the .NET Core Notes in https://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
UILanguageOverride.Setup();
}
internal static bool TryGetBuiltInCommand(string commandName, out BuiltInCommandMetadata builtInCommand)
{
return BuiltInCommandsCatalog.Commands.TryGetValue(commandName, out builtInCommand);
}
private static void PrintVersion()
{
Reporter.Output.WriteLine(Product.Version);
}
private static void PrintInfo()
{
DotnetVersionFile versionFile = DotnetFiles.VersionFileObject;
var commitSha = versionFile.CommitSha ?? "N/A";
Reporter.Output.WriteLine($"{LocalizableStrings.DotNetSdkInfoLabel}");
Reporter.Output.WriteLine($" Version: {Product.Version}");
Reporter.Output.WriteLine($" Commit: {commitSha}");
Reporter.Output.WriteLine();
Reporter.Output.WriteLine($"{LocalizableStrings.DotNetRuntimeInfoLabel}");
Reporter.Output.WriteLine($" OS Name: {RuntimeEnvironment.OperatingSystem}");
Reporter.Output.WriteLine($" OS Version: {RuntimeEnvironment.OperatingSystemVersion}");
Reporter.Output.WriteLine($" OS Platform: {RuntimeEnvironment.OperatingSystemPlatform}");
Reporter.Output.WriteLine($" RID: {GetDisplayRid(versionFile)}");
Reporter.Output.WriteLine($" Base Path: {ApplicationEnvironment.ApplicationBasePath}");
}
private static bool IsArg(string candidate, string longName)
{
return IsArg(candidate, shortName: null, longName: longName);
}
private static bool IsArg(string candidate, string shortName, string longName)
{
return (shortName != null && candidate.Equals("-" + shortName, StringComparison.OrdinalIgnoreCase)) ||
(longName != null && candidate.Equals("--" + longName, StringComparison.OrdinalIgnoreCase));
}
private static string GetDisplayRid(DotnetVersionFile versionFile)
{
FrameworkDependencyFile fxDepsFile = new FrameworkDependencyFile();
string currentRid = RuntimeEnvironment.GetRuntimeIdentifier();
// if the current RID isn't supported by the shared framework, display the RID the CLI was
// built with instead, so the user knows which RID they should put in their "runtimes" section.
return fxDepsFile.IsRuntimeSupported(currentRid) ?
currentRid :
versionFile.BuildRid;
}
}
}