src/NuGet.Clients/NuGet.CommandLine/Program.cs (412 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable disable extern alias CoreV2; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using System.Text; using Microsoft.Win32; using NuGet.Common; using NuGet.PackageManagement; namespace NuGet.CommandLine { public class Program { private const string Utf8Option = "-utf8"; private const string ForceEnglishOutputOption = "-forceEnglishOutput"; #if DEBUG private const string DebugOption = "--debug"; #endif private const string OSVersionRegistryKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion"; private const string FilesystemRegistryKey = @"SYSTEM\CurrentControlSet\Control\FileSystem"; private const string DotNetSetupRegistryKey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\"; private const int Net462ReleasedVersion = 394802; internal static readonly Assembly NuGetExeAssembly = typeof(Program).Assembly; private static readonly string ThisExecutableName = NuGetExeAssembly.GetName().Name; [Import] public HelpCommand HelpCommand { get; set; } [ImportMany] public IEnumerable<ICommand> Commands { get; set; } [Import] public ICommandManager Manager { get; set; } /// <summary> /// Flag meant for unit tests that prevents command line extensions from being loaded. /// </summary> public static bool IgnoreExtensions { get; set; } public static int Main(string[] args) { AppContext.SetSwitch("Switch.System.IO.UseLegacyPathHandling", false); AppContext.SetSwitch("Switch.System.IO.BlockLongPaths", false); #if DEBUG if (args.Contains(DebugOption, StringComparer.OrdinalIgnoreCase)) { args = args.Where(arg => !string.Equals(arg, DebugOption, StringComparison.OrdinalIgnoreCase)).ToArray(); System.Diagnostics.Debugger.Launch(); } #endif NuGet.Common.Migrations.MigrationRunner.Run(); #if IS_DESKTOP // Find any response files and resolve the args if (!RuntimeEnvironmentHelper.IsMono) { args = CommandLineResponseFile.ParseArgsResponseFiles(args); } #endif return MainCore(Directory.GetCurrentDirectory(), args, EnvironmentVariableWrapper.Instance); } public static int MainCore(string workingDirectory, string[] args, IEnvironmentVariableReader environmentVariableReader) { var console = new Console(); // First, optionally disable localization in resources. if (args.Any(arg => string.Equals(arg, ForceEnglishOutputOption, StringComparison.OrdinalIgnoreCase))) { CultureUtility.DisableLocalization(); } else { UILanguageOverride.Setup(console); } // set output encoding to UTF8 if -utf8 is specified var oldOutputEncoding = System.Console.OutputEncoding; if (args.Any(arg => string.Equals(arg, Utf8Option, StringComparison.OrdinalIgnoreCase))) { args = args.Where(arg => !string.Equals(arg, Utf8Option, StringComparison.OrdinalIgnoreCase)).ToArray(); SetConsoleOutputEncoding(Encoding.UTF8); } // Increase the maximum number of connections per server. if (!RuntimeEnvironmentHelper.IsMono) { ServicePointManager.DefaultConnectionLimit = 64; } else { // Keep mono limited to a single download to avoid issues. ServicePointManager.DefaultConnectionLimit = 1; } var fileSystem = new CoreV2.NuGet.PhysicalFileSystem(workingDirectory); try { // Remove NuGet.exe.old RemoveOldFile(fileSystem); // Import Dependencies var p = new Program(); p.Initialize(fileSystem, console); // Add commands to the manager foreach (var cmd in p.Commands) { p.Manager.RegisterCommand(cmd); } var parser = new CommandLineParser(p.Manager); // Parse the command var command = parser.ParseCommandLine(args) ?? p.HelpCommand; command.CurrentDirectory = workingDirectory; if (command is DownloadCommandBase downloadCommandBase && downloadCommandBase.NoCache) { // NoCache option is deprecated. Users should use NoHttpCache instead. console.LogInformation(NuGetCommand.Log_RestoreNoCacheInformation); } if (command is Command commandImpl) { console.Verbosity = commandImpl.Verbosity; } // Fallback on the help command if we failed to parse a valid command if (!ArgumentCountValid(command)) { // Get the command name and add it to the argument list of the help command var commandName = command.CommandAttribute.CommandName; // Print invalid arguments command error message in stderr console.WriteError(LocalizedResourceManager.GetString("InvalidArguments"), commandName); // then show help p.HelpCommand.ViewHelpForCommand(commandName); return 1; } else { if (command is Command baseCommand) { SetConsoleInteractivity(console, baseCommand, environmentVariableReader); } try { command.Execute(); } catch (CommandLineArgumentCombinationException e) { var commandName = command.CommandAttribute.CommandName; console.WriteLine($"{string.Format(CultureInfo.CurrentCulture, LocalizedResourceManager.GetString("InvalidArguments"), commandName)} {e.Message}"); p.HelpCommand.ViewHelpForCommand(commandName); return 1; } } } catch (AggregateException exception) { var unwrappedEx = ExceptionUtility.Unwrap(exception); LogException(unwrappedEx, console); return 1; } catch (ExitCodeException e) { return e.ExitCode; } catch (PathTooLongException e) { LogException(e, console); if (RuntimeEnvironmentHelper.IsWindows) { LogHelperMessageForPathTooLongException(console); } return 1; } catch (Exception exception) { LogException(exception, console); return 1; } finally { CoreV2.NuGet.OptimizedZipPackage.PurgeCache(); SetConsoleOutputEncoding(oldOutputEncoding); } return 0; } private void Initialize(CoreV2.NuGet.IFileSystem fileSystem, IConsole console) { AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; AppDomain.CurrentDomain.ResourceResolve += CurrentDomain_ResourceResolve; using (var catalog = new AggregateCatalog(new AssemblyCatalog(GetType().Assembly))) { if (!IgnoreExtensions) { AddExtensionsToCatalog(catalog, console); } try { using (var container = new CompositionContainer(catalog)) { container.ComposeExportedValue(console); container.ComposeExportedValue<CoreV2.NuGet.IPackageRepositoryFactory>(new CommandLineRepositoryFactory(console)); container.ComposeExportedValue(fileSystem); container.ComposeParts(this); } } catch (ReflectionTypeLoadException ex) when (ex?.LoaderExceptions.Length > 0) { throw new AggregateException(ex.LoaderExceptions); } } } private Assembly CurrentDomain_ResourceResolve(object sender, ResolveEventArgs args) { Assembly returnedResource = null; // We want to intercept NuGet.Resources resources and redirect it to nuget.exe assembly if (!args.Name.StartsWith("NuGet.CommandLine", StringComparison.OrdinalIgnoreCase) && args.Name.StartsWith("NuGet", StringComparison.OrdinalIgnoreCase) && string.Equals("NuGet.Resources", args.RequestingAssembly.GetName().Name, StringComparison.OrdinalIgnoreCase)) { ManifestResourceInfo resource = NuGetExeAssembly.GetManifestResourceInfo(args.Name); if (resource != null) { // Return nuget.exe assembly, since it contains the requested resource by NuGet.Resources assembly returnedResource = NuGetExeAssembly; } } return returnedResource; } // This method acts as a binding redirect private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { AssemblyName name = new AssemblyName(args.Name); Assembly customLoadedAssembly = null; if (string.Equals(name.Name, ThisExecutableName, StringComparison.OrdinalIgnoreCase)) { customLoadedAssembly = NuGetExeAssembly; } // .NET Framework 4.x now triggers AssemblyResolve event for resource assemblies // We want to catch failed NuGet.resources.dll assembly load to look for it in embedded resoruces else if (name.Name == "NuGet.resources") { // Load satellite resource assembly from embedded resources customLoadedAssembly = GetNuGetResourcesAssembly(name.Name, name.CultureInfo); } return customLoadedAssembly; } private static Assembly GetNuGetResourcesAssembly(string name, CultureInfo culture) { string resourceName = $"NuGet.CommandLine.{culture.Name}.{name}.dll"; Assembly resourceAssembly = LoadAssemblyFromEmbeddedResources(resourceName); if (resourceAssembly == null) { // Sometimes, embedded assembly names have dashes replaced by underscores string altResourceName = $"NuGet.CommandLine.{culture.Name.Replace("-", "_")}.{name}.dll"; resourceAssembly = LoadAssemblyFromEmbeddedResources(altResourceName); } return resourceAssembly; } private static Assembly LoadAssemblyFromEmbeddedResources(string resourceName) { Assembly resourceAssembly = null; using (var stream = NuGetExeAssembly.GetManifestResourceStream(resourceName)) { if (stream != null) { byte[] assemblyData = new byte[stream.Length]; stream.Read(assemblyData, offset: 0, assemblyData.Length); try { resourceAssembly = Assembly.Load(assemblyData); } catch (BadImageFormatException) { resourceAssembly = null; } } } return resourceAssembly; } [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't want to block the exe from usage if anything failed")] internal static void RemoveOldFile(CoreV2.NuGet.IFileSystem fileSystem) { var oldFile = NuGetExeAssembly.Location + ".old"; try { if (fileSystem.FileExists(oldFile)) { fileSystem.DeleteFile(oldFile); } } catch { // We don't want to block the exe from usage if anything failed } } public static bool ArgumentCountValid(ICommand command) { var attribute = command.CommandAttribute; return command.Arguments.Count >= attribute.MinArgs && command.Arguments.Count <= attribute.MaxArgs; } private static void AddExtensionsToCatalog(AggregateCatalog catalog, IConsole console) { var extensionLocator = new ExtensionLocator(); var files = extensionLocator.FindExtensions(); RegisterExtensions(catalog, files, console); } private static void RegisterExtensions(AggregateCatalog catalog, IEnumerable<string> enumerateFiles, IConsole console) { foreach (var item in enumerateFiles) { AssemblyCatalog assemblyCatalog = null; try { assemblyCatalog = new AssemblyCatalog(item); // get the parts - throw if something went wrong var parts = assemblyCatalog.Parts; // load all the types - throw if assembly cannot load (missing dependencies is a good example) var assembly = Assembly.LoadFile(item); assembly.GetTypes(); catalog.Catalogs.Add(assemblyCatalog); } catch (BadImageFormatException ex) { if (assemblyCatalog != null) { assemblyCatalog.Dispose(); } // Ignore if the dll wasn't a valid assembly console.WriteWarning(ex.Message); } catch (FileLoadException ex) { // Ignore if we couldn't load the assembly. if (assemblyCatalog != null) { assemblyCatalog.Dispose(); } var message = string.Format(CultureInfo.CurrentCulture, LocalizedResourceManager.GetString(nameof(NuGetResources.FailedToLoadExtension)), item); console.WriteWarning(message); console.WriteWarning(ex.Message); } catch (ReflectionTypeLoadException rex) { // ignore if the assembly is missing dependencies var resource = LocalizedResourceManager.GetString(nameof(NuGetResources.FailedToLoadExtensionDuringMefComposition)); var perAssemblyError = string.Empty; if (rex?.LoaderExceptions.Length > 0) { var builder = new StringBuilder(); builder.AppendLine(string.Empty); var errors = rex.LoaderExceptions.Select(e => e.Message).Distinct(StringComparer.Ordinal); foreach (var error in errors) { builder.AppendLine(error); } perAssemblyError = builder.ToString(); } var warning = string.Format(CultureInfo.CurrentCulture, resource, item, perAssemblyError); console.WriteWarning(warning); } } } private static void SetConsoleInteractivity(IConsole console, Command command, IEnvironmentVariableReader environmentVariableReader) { // Apply command setting console.IsNonInteractive = command.NonInteractive; // Global environment variable to prevent the exe for prompting for credentials if (!string.IsNullOrEmpty(environmentVariableReader.GetEnvironmentVariable("NUGET_EXE_NO_PROMPT"))) { console.IsNonInteractive = true; } // Disable non-interactive if force is set. var forceInteractive = environmentVariableReader.GetEnvironmentVariable("FORCE_NUGET_EXE_INTERACTIVE"); if (!string.IsNullOrEmpty(forceInteractive)) { console.IsNonInteractive = false; } } private static void SetConsoleOutputEncoding(System.Text.Encoding encoding) { try { System.Console.OutputEncoding = encoding; } catch (IOException) { } } private static void LogException(Exception exception, IConsole console) { var logStackAsError = console.Verbosity == Verbosity.Detailed; ExceptionUtilities.LogException(exception, console, logStackAsError); } private static void LogHelperMessageForPathTooLongException(Console logger) { if (!IsWindows10(logger)) { logger.WriteWarning(LocalizedResourceManager.GetString(nameof(NuGetResources.Warning_LongPath_UnsupportedOS))); } else if (!IsSupportLongPathEnabled(logger)) { logger.WriteWarning(LocalizedResourceManager.GetString(nameof(NuGetResources.Warning_LongPath_DisabledPolicy))); } else if (!IsRuntimeGreaterThanNet462(logger)) { logger.WriteWarning(LocalizedResourceManager.GetString(nameof(NuGetResources.Warning_LongPath_UnsupportedNetFramework))); } } private static bool IsWindows10(ILogger logger) { var productName = (string)RegistryKeyUtility.GetValueFromRegistryKey("ProductName", OSVersionRegistryKey, Registry.LocalMachine, logger); return productName != null && productName.StartsWith("Windows 10", StringComparison.Ordinal); } private static bool IsSupportLongPathEnabled(ILogger logger) { var longPathsEnabled = RegistryKeyUtility.GetValueFromRegistryKey("LongPathsEnabled", FilesystemRegistryKey, Registry.LocalMachine, logger); return longPathsEnabled != null && (int)longPathsEnabled > 0; } private static bool IsRuntimeGreaterThanNet462(ILogger logger) { var release = RegistryKeyUtility.GetValueFromRegistryKey("Release", DotNetSetupRegistryKey, Registry.LocalMachine, logger); return release != null && (int)release >= Net462ReleasedVersion; } } }