mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
305 lines
11 KiB
C#
305 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
|
|
{
|
|
/// <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>
|
|
internal class NugetExeWrapper : IDisposable
|
|
{
|
|
private readonly string? nugetExe;
|
|
private readonly Semmle.Util.Logging.ILogger logger;
|
|
|
|
public int PackageCount => fileProvider.PackagesConfigs.Count;
|
|
|
|
private readonly string? backupNugetConfig;
|
|
private readonly string? nugetConfigPath;
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Create the package manager for a specified source tree.
|
|
/// </summary>
|
|
public NugetExeWrapper(FileProvider fileProvider, DependencyDirectory packageDirectory, Semmle.Util.Logging.ILogger logger)
|
|
{
|
|
this.fileProvider = fileProvider;
|
|
this.packageDirectory = packageDirectory;
|
|
this.logger = logger;
|
|
|
|
if (fileProvider.PackagesConfigs.Count > 0)
|
|
{
|
|
logger.LogInfo($"Found packages.config files, trying to use nuget.exe for package restore");
|
|
nugetExe = ResolveNugetExe();
|
|
if (HasNoPackageSource())
|
|
{
|
|
// We only modify or add a top level nuget.config file
|
|
nugetConfigPath = Path.Combine(fileProvider.SourceDir.FullName, "nuget.config");
|
|
try
|
|
{
|
|
if (File.Exists(nugetConfigPath))
|
|
{
|
|
var tempFolderPath = FileUtils.GetTemporaryWorkingDirectory(out _);
|
|
|
|
do
|
|
{
|
|
backupNugetConfig = Path.Combine(tempFolderPath, Path.GetRandomFileName());
|
|
}
|
|
while (File.Exists(backupNugetConfig));
|
|
File.Copy(nugetConfigPath, backupNugetConfig, true);
|
|
}
|
|
else
|
|
{
|
|
File.WriteAllText(nugetConfigPath,
|
|
"""
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<configuration>
|
|
<packageSources>
|
|
</packageSources>
|
|
</configuration>
|
|
""");
|
|
}
|
|
AddDefaultPackageSource(nugetConfigPath);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.LogError($"Failed to add default package source to {nugetConfigPath}: {e}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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 = 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;
|
|
}
|
|
|
|
throw new Exception("Could not find or download nuget.exe.");
|
|
}
|
|
|
|
private string DownloadNugetExe(string sourceDir)
|
|
{
|
|
var directory = Path.Combine(sourceDir, ".nuget");
|
|
var nuget = Path.Combine(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 => !Win32.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}\"...");
|
|
|
|
/* 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}\" \"{packagesConfig}\"";
|
|
}
|
|
else
|
|
{
|
|
exe = nugetExe!;
|
|
args = $"install -OutputDirectory \"{packageDirectory}\" \"{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 bool HasNoPackageSource()
|
|
{
|
|
if (Win32.IsWindows())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
logger.LogInfo("Checking if default package source is available...");
|
|
RunMonoNugetCommand("sources list -ForceEnglishOutput", out var stdout);
|
|
if (stdout.All(line => line != "No sources found."))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.LogWarning($"Failed to check if default package source is added: {e}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void RunMonoNugetCommand(string command, out IList<string> stdout)
|
|
{
|
|
string exe, args;
|
|
if (RunWithMono)
|
|
{
|
|
exe = "mono";
|
|
args = $"\"{nugetExe}\" {command}";
|
|
}
|
|
else
|
|
{
|
|
exe = nugetExe!;
|
|
args = command;
|
|
}
|
|
|
|
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);
|
|
pi.ReadOutput(out stdout, onOut, onError);
|
|
}
|
|
|
|
private void AddDefaultPackageSource(string nugetConfig)
|
|
{
|
|
logger.LogInfo("Adding default package source...");
|
|
RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {NugetPackageRestorer.PublicNugetOrgFeed} -ConfigFile \"{nugetConfig}\"", out _);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (nugetConfigPath is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (backupNugetConfig is null)
|
|
{
|
|
logger.LogInfo("Removing nuget.config file");
|
|
File.Delete(nugetConfigPath);
|
|
return;
|
|
}
|
|
|
|
logger.LogInfo("Reverting nuget.config file content");
|
|
// The content of the original nuget.config file is reverted without changing the file's attributes or casing:
|
|
using (var backup = File.OpenRead(backupNugetConfig))
|
|
using (var current = File.OpenWrite(nugetConfigPath))
|
|
{
|
|
current.SetLength(0); // Truncate file
|
|
backup.CopyTo(current); // Restore original content
|
|
}
|
|
|
|
logger.LogInfo("Deleting backup nuget.config file");
|
|
File.Delete(backupNugetConfig);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
logger.LogError($"Failed to restore original nuget.config file: {exc}");
|
|
}
|
|
}
|
|
}
|
|
}
|