mirror of
https://github.com/github/codeql.git
synced 2026-07-04 11:05:33 +02:00
261 lines
11 KiB
C#
261 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Semmle.Util;
|
|
|
|
namespace Semmle.Extraction.CSharp.DependencyFetching
|
|
{
|
|
internal interface IPackagesConfigRestore
|
|
{
|
|
/// <summary>
|
|
/// The number of packages.config files found in the source tree.
|
|
/// </summary>
|
|
int PackageCount { get; }
|
|
|
|
/// <summary>
|
|
/// Download the packages to the temp folder.
|
|
/// </summary>
|
|
int InstallPackages();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Factory for creating a package manager to restore NuGet packages referenced in packages.config files.
|
|
/// If the environment doesn't support using nuget.exe to restore packages from packages.config files, a no-op implementation is returned.
|
|
/// It is worth noting that for macOS and Linux, nuget.exe is used with mono. However, mono is being deprecated and the last GitHub images
|
|
/// to contain mono are:
|
|
/// - Ubuntu 22.04
|
|
/// - macOS 14
|
|
///
|
|
/// If the packages from the packages.config files are not restored with the packages.config restore functionality below, there is a subsequent
|
|
/// step that still may succeed in restoring the packages without the help of nuget.exe (by attempting to restore using dotnet).
|
|
/// </summary>
|
|
internal class PackagesConfigRestoreFactory
|
|
{
|
|
public static IPackagesConfigRestore Create(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager, HashSet<string> reachableFeeds)
|
|
{
|
|
if (SystemBuildActions.Instance.IsWindows() || SystemBuildActions.Instance.IsMonoInstalled())
|
|
{
|
|
return new NugetExeWrapper(fileProvider, packageDirectory, logger, feedManager, reachableFeeds);
|
|
}
|
|
|
|
return new NoOpPackagesConfig(fileProvider.PackagesConfigs, logger);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Manage the downloading of NuGet packages with nuget.exe.
|
|
/// Locates packages in a source tree and downloads all of the
|
|
/// referenced assemblies to a temp folder.
|
|
/// </summary>
|
|
private class NugetExeWrapper : IPackagesConfigRestore
|
|
{
|
|
private readonly string? nugetExe;
|
|
private readonly Semmle.Util.Logging.ILogger logger;
|
|
|
|
public int PackageCount => fileProvider.PackagesConfigs.Count;
|
|
|
|
private readonly FileProvider fileProvider;
|
|
|
|
/// <summary>
|
|
/// The packages directory.
|
|
/// This will be in the user-specified or computed Temp location
|
|
/// so as to not trample the source tree.
|
|
/// </summary>
|
|
private readonly DependencyDirectory packageDirectory;
|
|
private readonly FeedManager feedManager;
|
|
private readonly HashSet<string> reachableFeeds;
|
|
|
|
private bool IsWindows => SystemBuildActions.Instance.IsWindows();
|
|
|
|
private bool? isDefaultFeedReachable;
|
|
private bool IsDefaultFeedReachable =>
|
|
isDefaultFeedReachable ??= feedManager.IsDefaultFeedReachable();
|
|
|
|
/// <summary>
|
|
/// Create the package manager for a specified source tree.
|
|
/// </summary>
|
|
public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger, FeedManager feedManager, HashSet<string> reachableFeeds)
|
|
{
|
|
this.fileProvider = fileProvider;
|
|
this.packageDirectory = packageDirectory;
|
|
this.logger = logger;
|
|
this.feedManager = feedManager;
|
|
this.reachableFeeds = reachableFeeds;
|
|
|
|
if (fileProvider.PackagesConfigs.Count > 0)
|
|
{
|
|
logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore");
|
|
nugetExe = ResolveNugetExe();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private string ResolveNugetExe()
|
|
{
|
|
var envVarPath = Environment.GetEnvironmentVariable(EnvironmentVariableNames.NugetExePath);
|
|
if (!string.IsNullOrEmpty(envVarPath))
|
|
{
|
|
logger.LogInfo($"Using nuget.exe from environment variable: '{envVarPath}'");
|
|
return envVarPath;
|
|
}
|
|
|
|
try
|
|
{
|
|
return DownloadNugetExe(fileProvider.SourceDir.FullName);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logger.LogInfo($"Download of nuget.exe failed: {exc.Message}");
|
|
}
|
|
|
|
var nugetExesInRepo = fileProvider.NugetExes;
|
|
if (nugetExesInRepo.Count > 1)
|
|
{
|
|
logger.LogInfo($"Found multiple nuget.exe files in the repository: {string.Join(", ", nugetExesInRepo.OrderBy(s => s))}");
|
|
}
|
|
|
|
if (nugetExesInRepo.Count > 0)
|
|
{
|
|
var path = nugetExesInRepo.First();
|
|
logger.LogInfo($"Using nuget.exe from path '{path}'");
|
|
return path;
|
|
}
|
|
|
|
var executableName = IsWindows ? "nuget.exe" : "nuget";
|
|
var nugetPath = FileUtils.FindProgramOnPath(executableName);
|
|
if (nugetPath is not null)
|
|
{
|
|
nugetPath = Path.Join(nugetPath, executableName);
|
|
logger.LogInfo($"Using nuget.exe from PATH: {nugetPath}");
|
|
return nugetPath;
|
|
}
|
|
|
|
throw new Exception("Could not find or download nuget.exe.");
|
|
}
|
|
|
|
private string DownloadNugetExe(string sourceDir)
|
|
{
|
|
var directory = Path.Join(sourceDir, ".nuget");
|
|
var nuget = Path.Join(directory, "nuget.exe");
|
|
|
|
// Nuget.exe already exists in the .nuget directory.
|
|
if (File.Exists(nuget))
|
|
{
|
|
logger.LogInfo($"Found nuget.exe at {nuget}");
|
|
return nuget;
|
|
}
|
|
|
|
Directory.CreateDirectory(directory);
|
|
logger.LogInfo("Attempting to download nuget.exe");
|
|
FileUtils.DownloadFile(FileUtils.NugetExeUrl, nuget, logger);
|
|
logger.LogInfo($"Downloaded nuget.exe to {nuget}");
|
|
return nuget;
|
|
}
|
|
|
|
private bool RunWithMono => !IsWindows && !string.IsNullOrEmpty(Path.GetExtension(nugetExe));
|
|
|
|
/// <summary>
|
|
/// Restore all packages in the specified packages.config file.
|
|
/// </summary>
|
|
/// <param name="packagesConfig">The packages.config file.</param>
|
|
private bool TryRestoreNugetPackage(string packagesConfig)
|
|
{
|
|
logger.LogInfo($"Restoring file \"{packagesConfig}\"...");
|
|
|
|
var sourcesArgument = "";
|
|
var feedsToUse = feedManager.FeedsToUse(packagesConfig, reachableFeeds).ToList();
|
|
var useDefaultFeed = feedsToUse.Count == 0 && IsDefaultFeedReachable;
|
|
|
|
// Explicitly construct the sources to be used for the restore command when checking feed
|
|
// responsiveness, using private registries, or falling back to nuget.org.
|
|
if (feedManager.CheckNugetFeedResponsiveness || feedManager.HasPrivateRegistryFeeds || useDefaultFeed)
|
|
{
|
|
if (useDefaultFeed)
|
|
{
|
|
feedsToUse.Add(FeedManager.PublicNugetOrgFeed);
|
|
}
|
|
sourcesArgument = feedManager.FeedsToRestoreArgument(feedsToUse, "-Source");
|
|
}
|
|
|
|
/* Use nuget.exe to install a package.
|
|
* Note that there is a clutch of NuGet assemblies which could be used to
|
|
* invoke this directly, which would arguably be nicer. However they are
|
|
* really unwieldy and this solution works for now.
|
|
*/
|
|
|
|
string exe, args;
|
|
if (RunWithMono)
|
|
{
|
|
exe = "mono";
|
|
args = $"\"{nugetExe}\" install -OutputDirectory \"{packageDirectory}\" {sourcesArgument} \"{packagesConfig}\"";
|
|
}
|
|
else
|
|
{
|
|
exe = nugetExe!;
|
|
args = $"install -OutputDirectory \"{packageDirectory}\" {sourcesArgument} \"{packagesConfig}\"";
|
|
}
|
|
|
|
var pi = new ProcessStartInfo(exe, args)
|
|
{
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false
|
|
};
|
|
|
|
var threadId = Environment.CurrentManagedThreadId;
|
|
void onOut(string s) => logger.LogDebug(s, threadId);
|
|
void onError(string s) => logger.LogError(s, threadId);
|
|
var exitCode = pi.ReadOutput(out _, onOut, onError);
|
|
if (exitCode != 0)
|
|
{
|
|
logger.LogError($"Command {pi.FileName} {pi.Arguments} failed with exit code {exitCode}");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
logger.LogInfo($"Restored file \"{packagesConfig}\"");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Download the packages to the temp folder.
|
|
/// </summary>
|
|
public int InstallPackages()
|
|
{
|
|
return fileProvider.PackagesConfigs.Count(TryRestoreNugetPackage);
|
|
}
|
|
}
|
|
|
|
private class NoOpPackagesConfig : IPackagesConfigRestore
|
|
{
|
|
private readonly Semmle.Util.Logging.ILogger logger;
|
|
private readonly ICollection<string> packagesConfigs;
|
|
|
|
public NoOpPackagesConfig(ICollection<string> packagesConfigs, Semmle.Util.Logging.ILogger logger)
|
|
{
|
|
this.packagesConfigs = packagesConfigs;
|
|
this.logger = logger;
|
|
}
|
|
|
|
public int PackageCount => packagesConfigs.Count;
|
|
|
|
public int InstallPackages()
|
|
{
|
|
if (PackageCount > 0)
|
|
{
|
|
logger.LogInfo("Found packages.config files, but nuget.exe cannot be used to restore packages on this platform. Skipping restore of packages.config files.");
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|