From a79105e2ae550b8caacde682ebf3f4d0d7927710 Mon Sep 17 00:00:00 2001 From: Tamas Vajk Date: Wed, 15 May 2024 15:00:19 +0200 Subject: [PATCH] C#: Use nuget.exe from the executing machine instead of always downloading it --- .../EnvironmentVariableNames.cs | 5 ++ .../FileProvider.cs | 3 + .../NugetExeWrapper.cs | 88 ++++++++++++------- 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/EnvironmentVariableNames.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/EnvironmentVariableNames.cs index d03a2fc473ce..134b1857f8fb 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/EnvironmentVariableNames.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/EnvironmentVariableNames.cs @@ -60,6 +60,11 @@ internal class EnvironmentVariableNames /// public const string FallbackNugetFeeds = "CODEQL_EXTRACTOR_CSHARP_BUILDLESS_NUGET_FEEDS_FALLBACK"; + /// + /// Specifies the path to the nuget executable to be used for package restoration. + /// + public const string NugetExePath = "CODEQL_EXTRACTOR_CSHARP_BUILDLESS_NUGET_PATH"; + /// /// Specifies the location of the diagnostic directory. /// diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FileProvider.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FileProvider.cs index 7b88a1fc1a28..e908855df0a9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FileProvider.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FileProvider.cs @@ -20,6 +20,7 @@ public class FileProvider private readonly Lazy solutions; private readonly Lazy dlls; private readonly Lazy nugetConfigs; + private readonly Lazy nugetExes; private readonly Lazy globalJsons; private readonly Lazy packagesConfigs; private readonly Lazy razorViews; @@ -45,6 +46,7 @@ public FileProvider(DirectoryInfo sourceDir, ILogger logger) resources = new Lazy(() => SelectTextFileNamesByExtension("resource", ".resx")); rootNugetConfig = new Lazy(() => all.SelectRootFiles(SourceDir).SelectFileNamesByName("nuget.config").FirstOrDefault()); + nugetExes = new Lazy(() => all.SelectFileNamesByName("nuget.exe").ToArray()); } private string[] ReturnAndLogFiles(string filetype, IEnumerable files) @@ -123,6 +125,7 @@ private FileInfo[] GetAllFiles() public ICollection Solutions => solutions.Value; public IEnumerable Dlls => dlls.Value; public ICollection NugetConfigs => nugetConfigs.Value; + public ICollection NugetExes => nugetExes.Value; public string? RootNugetConfig => rootNugetConfig.Value; public IEnumerable GlobalJsons => globalJsons.Value; public ICollection PackagesConfigs => packagesConfigs.Value; diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs index 8537e4b5e0ee..e0a1822a7eee 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetExeWrapper.cs @@ -17,15 +17,11 @@ internal class NugetExeWrapper : IDisposable private readonly string? nugetExe; private readonly Util.Logging.ILogger logger; - /// - /// The list of package files. - /// - private readonly ICollection packageFiles; - - public int PackageCount => packageFiles.Count; + public int PackageCount => fileProvider.PackagesConfigs.Count; private readonly string? backupNugetConfig; private readonly string? nugetConfigPath; + private readonly FileProvider fileProvider; /// /// The computed packages directory. @@ -39,15 +35,14 @@ internal class NugetExeWrapper : IDisposable /// public NugetExeWrapper(FileProvider fileProvider, TemporaryDirectory packageDirectory, Util.Logging.ILogger logger) { + this.fileProvider = fileProvider; this.packageDirectory = packageDirectory; this.logger = logger; - packageFiles = fileProvider.PackagesConfigs; - - if (packageFiles.Count > 0) + if (fileProvider.PackagesConfigs.Count > 0) { logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore"); - nugetExe = ResolveNugetExe(fileProvider.SourceDir.FullName); + nugetExe = ResolveNugetExe(); if (HasNoPackageSource()) { // We only modify or add a top level nuget.config file @@ -87,25 +82,44 @@ public NugetExeWrapper(FileProvider fileProvider, TemporaryDirectory packageDire } /// - /// Tries to find the location of `nuget.exe` in the nuget directory under the directory - /// containing the executing assembly. If it can't be found, it is downloaded to the - /// `.nuget` directory under the source directory. + /// Tries to find the location of `nuget.exe`. It looks for + /// - the environment variable specifying a location, + /// - files in the repository, + /// - tries to resolve nuget from the PATH, or + /// - downloads it if it is not found. /// - /// The source directory. - private string ResolveNugetExe(string sourceDir) + private string ResolveNugetExe() { - var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location; - var directory = Path.GetDirectoryName(currentAssembly) - ?? throw new FileNotFoundException($"Directory path '{currentAssembly}' of current assembly is null"); + var envVarPath = Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetExePath); + if (!string.IsNullOrEmpty(envVarPath)) + { + logger.LogInfo($"Using nuget.exe from environment variable: '{envVarPath}'"); + return envVarPath; + } - var nuget = Path.Combine(directory, "nuget", "nuget.exe"); - if (File.Exists(nuget)) + var nugetExesInRepo = fileProvider.NugetExes; + if (nugetExesInRepo.Count > 1) { - logger.LogInfo($"Found nuget.exe at {nuget}"); - return nuget; + logger.LogInfo($"Found multiple nuget.exe files in the repository: {string.Join(", ", nugetExesInRepo.OrderBy(s => s))}"); } - return DownloadNugetExe(sourceDir); + if (nugetExesInRepo.Count > 0) + { + var path = nugetExesInRepo.First(); + logger.LogInfo($"Using nuget.exe from path '{path}'"); + return path; + } + + var executableName = Win32.IsWindows() ? "nuget.exe" : "nuget"; + var nugetPath = FileUtils.FindProgramOnPath(executableName); + if (nugetPath is not null) + { + nugetPath = Path.Combine(nugetPath, executableName); + logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}"); + return nugetPath; + } + + return DownloadNugetExe(fileProvider.SourceDir.FullName); } private string DownloadNugetExe(string sourceDir) @@ -135,6 +149,8 @@ private string DownloadNugetExe(string sourceDir) } } + private bool RunWithMono => !Win32.IsWindows() && !string.IsNullOrEmpty(Path.GetExtension(nugetExe)); + /// /// Restore all files in a specified package. /// @@ -150,15 +166,15 @@ private bool TryRestoreNugetPackage(string package) */ string exe, args; - if (Win32.IsWindows()) + if (RunWithMono) { - exe = nugetExe!; - args = $"install -OutputDirectory {packageDirectory} {package}"; + exe = "mono"; + args = $"{nugetExe} install -OutputDirectory {packageDirectory} {package}"; } else { - exe = "mono"; - args = $"{nugetExe} install -OutputDirectory {packageDirectory} {package}"; + exe = nugetExe!; + args = $"install -OutputDirectory {packageDirectory} {package}"; } var pi = new ProcessStartInfo(exe, args) @@ -189,7 +205,7 @@ private bool TryRestoreNugetPackage(string package) /// public int InstallPackages() { - return packageFiles.Count(package => TryRestoreNugetPackage(package)); + return fileProvider.PackagesConfigs.Count(package => TryRestoreNugetPackage(package)); } private bool HasNoPackageSource() @@ -219,8 +235,18 @@ private bool HasNoPackageSource() private void RunMonoNugetCommand(string command, out IList stdout) { - var exe = "mono"; - var args = $"{nugetExe} {command}"; + string exe, args; + if (RunWithMono) + { + exe = "mono"; + args = $"{nugetExe} {command}"; + } + else + { + exe = nugetExe!; + args = command; + } + var pi = new ProcessStartInfo(exe, args) { RedirectStandardOutput = true,