Move Nuget restore logic from DependencyManager to dedicated class

This commit is contained in:
Tamas Vajk
2024-04-12 15:04:42 +02:00
parent 5406fac834
commit e3fe9f7ca5
4 changed files with 162 additions and 114 deletions

View File

@@ -12,14 +12,26 @@ using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
public interface ICompilationInfoContainer
{
/// <summary>
/// List of `(key, value)` tuples, that are stored in the DB for telemetry purposes.
/// </summary>
List<(string, string)> CompilationInfos { get; }
}
/// <summary>
/// Main implementation of the build analysis.
/// </summary>
public sealed partial class DependencyManager : IDisposable
public sealed partial class DependencyManager : IDisposable, ICompilationInfoContainer
{
private readonly AssemblyCache assemblyCache;
private readonly ILogger logger;
private readonly IDiagnosticsWriter diagnosticsWriter;
private readonly NugetPackageRestorer nugetPackageRestorer;
private readonly IDotNet dotnet;
private readonly FileContent fileContent;
private readonly FileProvider fileProvider;
// Only used as a set, but ConcurrentDictionary is the only concurrent set in .NET.
private readonly IDictionary<string, bool> usedReferences = new ConcurrentDictionary<string, bool>();
@@ -30,18 +42,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private int conflictedReferences = 0;
private readonly DirectoryInfo sourceDir;
private string? dotnetPath;
private readonly IDotNet dotnet;
private readonly FileContent fileContent;
private readonly TemporaryDirectory packageDirectory;
private readonly TemporaryDirectory legacyPackageDirectory;
private readonly TemporaryDirectory missingPackageDirectory;
private readonly TemporaryDirectory tempWorkingDirectory;
private readonly FileProvider fileProvider;
private readonly bool cleanupTempWorkingDirectory;
private readonly Lazy<Runtime> runtimeLazy;
private Runtime Runtime => runtimeLazy.Value;
private readonly int threads = EnvironmentVariables.GetDefaultNumberOfThreads();
internal static readonly int Threads = EnvironmentVariables.GetDefaultNumberOfThreads();
/// <summary>
/// Performs C# dependency fetching.
@@ -74,10 +82,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
$"dependency-manager-{DateTime.UtcNow:yyyyMMddHHmm}-{Environment.ProcessId}.jsonc"));
this.sourceDir = new DirectoryInfo(srcDir);
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "packages"));
legacyPackageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "legacypackages"));
missingPackageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "missingpackages"));
tempWorkingDirectory = new TemporaryDirectory(FileUtils.GetTemporaryWorkingDirectory(out cleanupTempWorkingDirectory));
this.fileProvider = new FileProvider(sourceDir, logger);
@@ -112,8 +116,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
throw;
}
nugetPackageRestorer = new NugetPackageRestorer(fileProvider, fileContent, dotnet, diagnosticsWriter, logger, this);
var dllLocations = fileProvider.Dlls.Select(x => new AssemblyLookupLocation(x)).ToHashSet();
RestoreNugetPackages(dllLocations);
dllLocations.UnionWith(nugetPackageRestorer.Restore());
// Find DLLs in the .Net / Asp.Net Framework
// This needs to come after the nuget restore, because the nuget restore might fetch the .NET Core/Framework reference assemblies.
var frameworkLocations = AddFrameworkDlls(dllLocations);
@@ -221,11 +227,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private void RemoveNugetAnalyzerReferences()
{
var packageFolder = packageDirectory.DirInfo.FullName.ToLowerInvariant();
if (packageFolder == null)
{
return;
}
var packageFolder = nugetPackageRestorer.PackageDirectory.DirInfo.FullName.ToLowerInvariant();
foreach (var filename in usedReferences.Keys)
{
@@ -299,7 +301,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var packagesInPrioOrder = FrameworkPackageNames.NetFrameworks;
var frameworkPaths = packagesInPrioOrder
.Select((s, index) => (Index: index, Path: GetPackageDirectory(s, packageDirectory)))
.Select((s, index) => (Index: index, Path: GetPackageDirectory(s)))
.Where(pair => pair.Path is not null)
.ToArray();
@@ -330,11 +332,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
if (runtimeLocation is null)
{
logger.LogInfo("No .NET Desktop Runtime location found. Attempting to restore the .NET Framework reference assemblies manually.");
if (TryRestorePackageManually(FrameworkPackageNames.LatestNetFrameworkReferenceAssemblies))
{
runtimeLocation = GetPackageDirectory(FrameworkPackageNames.LatestNetFrameworkReferenceAssemblies, missingPackageDirectory);
}
runtimeLocation = nugetPackageRestorer.TryRestoreLatestNetFrameworkReferenceAssemblies();
}
}
@@ -354,12 +352,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private void RemoveNugetPackageReference(string packagePrefix, ISet<AssemblyLookupLocation> dllLocations)
{
var packageFolder = packageDirectory.DirInfo.FullName.ToLowerInvariant();
if (packageFolder == null)
{
return;
}
var packageFolder = nugetPackageRestorer.PackageDirectory.DirInfo.FullName.ToLowerInvariant();
var packagePathPrefix = Path.Combine(packageFolder, packagePrefix.ToLowerInvariant());
var toRemove = dllLocations.Where(s => s.Path.StartsWith(packagePathPrefix, StringComparison.InvariantCultureIgnoreCase));
foreach (var path in toRemove)
@@ -382,7 +375,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
// First try to find ASP.NET Core assemblies in the NuGet packages
if (GetPackageDirectory(FrameworkPackageNames.AspNetCoreFramework, packageDirectory) is string aspNetCorePackage)
if (GetPackageDirectory(FrameworkPackageNames.AspNetCoreFramework) is string aspNetCorePackage)
{
SelectNewestFrameworkPath(aspNetCorePackage, "ASP.NET Core", dllLocations, frameworkLocations);
return;
@@ -398,15 +391,20 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private void AddMicrosoftWindowsDesktopDlls(ISet<AssemblyLookupLocation> dllLocations, ISet<string> frameworkLocations)
{
if (GetPackageDirectory(FrameworkPackageNames.WindowsDesktopFramework, packageDirectory) is string windowsDesktopApp)
if (GetPackageDirectory(FrameworkPackageNames.WindowsDesktopFramework) is string windowsDesktopApp)
{
SelectNewestFrameworkPath(windowsDesktopApp, "Windows Desktop App", dllLocations, frameworkLocations);
}
}
private string? GetPackageDirectory(string packagePrefix, TemporaryDirectory root)
private string? GetPackageDirectory(string packagePrefix)
{
return new DirectoryInfo(root.DirInfo.FullName)
return GetPackageDirectory(packagePrefix, nugetPackageRestorer.PackageDirectory.DirInfo);
}
internal static string? GetPackageDirectory(string packagePrefix, DirectoryInfo root)
{
return new DirectoryInfo(root.FullName)
.EnumerateDirectories(packagePrefix + "*", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = false })
.FirstOrDefault()?
.FullName;
@@ -495,22 +493,6 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
/// <summary>
/// Computes a unique temp directory for the packages associated
/// with this source tree. Use a SHA1 of the directory name.
/// </summary>
/// <returns>The full path of the temp directory.</returns>
private static string ComputeTempDirectory(string srcDir, string subfolderName)
{
var bytes = Encoding.Unicode.GetBytes(srcDir);
var sha = SHA1.HashData(bytes);
var sb = new StringBuilder();
foreach (var b in sha.Take(8))
sb.AppendFormat("{0:x2}", b);
return Path.Combine(FileUtils.GetTemporaryWorkingDirectory(out var _), sb.ToString(), subfolderName);
}
/// <summary>
/// Creates a temporary directory with the given subfolder name.
/// The created directory might be inside the repo folder, and it is deleted when the object is disposed.
@@ -634,7 +616,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private void AnalyseSolutions(IEnumerable<string> solutions)
{
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = threads }, solutionFile =>
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = Threads }, solutionFile =>
{
try
{
@@ -683,7 +665,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
}
public void Dispose(TemporaryDirectory? dir, string name)
public static void DisposeTempDirectory(TemporaryDirectory? dir, string name, ILogger logger)
{
try
{
@@ -697,15 +679,13 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
public void Dispose()
{
Dispose(packageDirectory, "package");
Dispose(legacyPackageDirectory, "legacy package");
Dispose(missingPackageDirectory, "missing package");
if (cleanupTempWorkingDirectory)
{
Dispose(tempWorkingDirectory, "temporary working");
DisposeTempDirectory(tempWorkingDirectory, "temporary working", logger);
}
diagnosticsWriter?.Dispose();
nugetPackageRestorer?.Dispose();
}
}
}

View File

@@ -12,7 +12,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private static readonly HashSet<string> binaryFileExtensions = [".dll", ".exe"]; // TODO: add more binary file extensions.
private readonly ILogger logger;
private readonly Lazy<FileInfo[]> all;
private readonly FileInfo[] all;
private readonly Lazy<FileInfo[]> allNonBinary;
private readonly Lazy<string[]> smallNonBinary;
private readonly Lazy<string[]> sources;
@@ -29,8 +29,8 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
SourceDir = sourceDir;
this.logger = logger;
all = new Lazy<FileInfo[]>(GetAllFiles);
allNonBinary = new Lazy<FileInfo[]>(() => all.Value.Where(f => !binaryFileExtensions.Contains(f.Extension.ToLowerInvariant())).ToArray());
all = GetAllFiles();
allNonBinary = new Lazy<FileInfo[]>(() => all.Where(f => !binaryFileExtensions.Contains(f.Extension.ToLowerInvariant())).ToArray());
smallNonBinary = new Lazy<string[]>(() =>
{
var ret = SelectSmallFiles(allNonBinary.Value).SelectFileNames().ToArray();
@@ -45,7 +45,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
globalJsons = new Lazy<string[]>(() => allNonBinary.Value.SelectFileNamesByName("global.json").ToArray());
razorViews = new Lazy<string[]>(() => SelectTextFileNamesByExtension("razor view", ".cshtml", ".razor"));
rootNugetConfig = new Lazy<string?>(() => all.Value.SelectRootFiles(SourceDir).SelectFileNamesByName("nuget.config").FirstOrDefault());
rootNugetConfig = new Lazy<string?>(() => all.SelectRootFiles(SourceDir).SelectFileNamesByName("nuget.config").FirstOrDefault());
}
private string[] SelectTextFileNamesByExtension(string filetype, params string[] extensions)
@@ -57,7 +57,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private string[] SelectBinaryFileNamesByExtension(string filetype, params string[] extensions)
{
var ret = all.Value.SelectFileNamesByExtension(extensions).ToArray();
var ret = all.SelectFileNamesByExtension(extensions).ToArray();
logger.LogInfo($"Found {ret.Length} {filetype} files in {SourceDir}.");
return ret;
}

View File

@@ -8,11 +8,11 @@ using Semmle.Util;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
/// <summary>
/// Manage the downloading of NuGet packages.
/// 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 NugetPackages : IDisposable
internal class NugetExeWrapper : IDisposable
{
private readonly string? nugetExe;
private readonly Util.Logging.ILogger logger;
@@ -37,7 +37,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// <summary>
/// Create the package manager for a specified source tree.
/// </summary>
public NugetPackages(string sourceDir, TemporaryDirectory packageDirectory, Util.Logging.ILogger logger)
public NugetExeWrapper(string sourceDir, TemporaryDirectory packageDirectory, Util.Logging.ILogger logger)
{
this.packageDirectory = packageDirectory;
this.logger = logger;
@@ -243,7 +243,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private void AddDefaultPackageSource(string nugetConfig)
{
logger.LogInfo("Adding default package source...");
RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {DependencyManager.PublicNugetFeed} -ConfigFile \"{nugetConfig}\"", out var _);
RunMonoNugetCommand($"sources add -Name DefaultNugetOrg -Source {NugetPackageRestorer.PublicNugetOrgFeed} -ConfigFile \"{nugetConfig}\"", out var _);
}
public void Dispose()

View File

@@ -3,36 +3,84 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Semmle.Util;
using Semmle.Util.Logging;
namespace Semmle.Extraction.CSharp.DependencyFetching
{
public sealed partial class DependencyManager
internal sealed partial class NugetPackageRestorer : IDisposable
{
private void RestoreNugetPackages(HashSet<AssemblyLookupLocation> dllLocations)
internal const string PublicNugetOrgFeed = "https://api.nuget.org/v3/index.json";
private readonly FileProvider fileProvider;
private readonly FileContent fileContent;
private readonly IDotNet dotnet;
private readonly IDiagnosticsWriter diagnosticsWriter;
private readonly TemporaryDirectory legacyPackageDirectory;
private readonly TemporaryDirectory missingPackageDirectory;
private readonly ILogger logger;
private readonly ICompilationInfoContainer compilationInfoContainer;
public TemporaryDirectory PackageDirectory { get; }
public NugetPackageRestorer(
FileProvider fileProvider,
FileContent fileContent,
IDotNet dotnet,
IDiagnosticsWriter diagnosticsWriter,
ILogger logger,
ICompilationInfoContainer compilationInfoContainer)
{
this.fileProvider = fileProvider;
this.fileContent = fileContent;
this.dotnet = dotnet;
this.diagnosticsWriter = diagnosticsWriter;
this.logger = logger;
this.compilationInfoContainer = compilationInfoContainer;
PackageDirectory = new TemporaryDirectory(ComputeTempDirectoryPath(fileProvider.SourceDir.FullName, "packages"));
legacyPackageDirectory = new TemporaryDirectory(ComputeTempDirectoryPath(fileProvider.SourceDir.FullName, "legacypackages"));
missingPackageDirectory = new TemporaryDirectory(ComputeTempDirectoryPath(fileProvider.SourceDir.FullName, "missingpackages"));
}
public string? TryRestoreLatestNetFrameworkReferenceAssemblies()
{
if (TryRestorePackageManually(FrameworkPackageNames.LatestNetFrameworkReferenceAssemblies))
{
return DependencyManager.GetPackageDirectory(FrameworkPackageNames.LatestNetFrameworkReferenceAssemblies, missingPackageDirectory.DirInfo);
}
return null;
}
public HashSet<AssemblyLookupLocation> Restore()
{
var assemblyLookupLocations = new HashSet<AssemblyLookupLocation>();
var checkNugetFeedResponsiveness = EnvironmentVariables.GetBoolean(EnvironmentVariableNames.CheckNugetFeedResponsiveness);
try
{
if (checkNugetFeedResponsiveness && !CheckFeeds())
{
// todo: we could also check the reachability of the inherited nuget feeds, but to use those in the fallback we would need to handle authentication too.
DownloadMissingPackagesFromSpecificFeeds(dllLocations);
return;
var unresponsiveMissingPackageLocation = DownloadMissingPackagesFromSpecificFeeds();
return unresponsiveMissingPackageLocation is null
? []
: [unresponsiveMissingPackageLocation];
}
using (var nuget = new NugetPackages(sourceDir.FullName, legacyPackageDirectory, logger))
using (var nuget = new NugetExeWrapper(fileProvider.SourceDir.FullName, legacyPackageDirectory, logger))
{
var count = nuget.InstallPackages();
if (nuget.PackageCount > 0)
{
CompilationInfos.Add(("packages.config files", nuget.PackageCount.ToString()));
CompilationInfos.Add(("Successfully restored packages.config files", count.ToString()));
compilationInfoContainer.CompilationInfos.Add(("packages.config files", nuget.PackageCount.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Successfully restored packages.config files", count.ToString()));
}
}
@@ -57,7 +105,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
nugetPackageDllPaths.ExceptWith(excludedPaths);
dllLocations.UnionWith(nugetPackageDllPaths.Select(p => new AssemblyLookupLocation(p)));
assemblyLookupLocations.UnionWith(nugetPackageDllPaths.Select(p => new AssemblyLookupLocation(p)));
}
catch (Exception exc)
{
@@ -72,31 +120,30 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var paths = dependencies
.Paths
.Select(d => Path.Combine(packageDirectory.DirInfo.FullName, d))
.Select(d => Path.Combine(PackageDirectory.DirInfo.FullName, d))
.ToList();
dllLocations.UnionWith(paths.Select(p => new AssemblyLookupLocation(p)));
assemblyLookupLocations.UnionWith(paths.Select(p => new AssemblyLookupLocation(p)));
LogAllUnusedPackages(dependencies);
if (checkNugetFeedResponsiveness)
{
DownloadMissingPackagesFromSpecificFeeds(dllLocations);
}
else
{
DownloadMissingPackages(dllLocations);
}
}
var missingPackageLocation = checkNugetFeedResponsiveness
? DownloadMissingPackagesFromSpecificFeeds()
: DownloadMissingPackages();
internal const string PublicNugetFeed = "https://api.nuget.org/v3/index.json";
if (missingPackageLocation is not null)
{
assemblyLookupLocations.Add(missingPackageLocation);
}
return assemblyLookupLocations;
}
private List<string> GetReachableFallbackNugetFeeds()
{
var fallbackFeeds = EnvironmentVariables.GetURLs(EnvironmentVariableNames.FallbackNugetFeeds).ToHashSet();
if (fallbackFeeds.Count == 0)
{
fallbackFeeds.Add(PublicNugetFeed);
logger.LogInfo($"No fallback Nuget feeds specified. Using default feed: {PublicNugetFeed}");
fallbackFeeds.Add(PublicNugetOrgFeed);
logger.LogInfo($"No fallback Nuget feeds specified. Using default feed: {PublicNugetOrgFeed}");
}
logger.LogInfo($"Checking fallback Nuget feed reachability on feeds: {string.Join(", ", fallbackFeeds.OrderBy(f => f))}");
@@ -130,7 +177,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var projects = fileProvider.Solutions.SelectMany(solution =>
{
logger.LogInfo($"Restoring solution {solution}...");
var res = dotnet.Restore(new(solution, packageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true));
var res = dotnet.Restore(new(solution, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true));
if (res.Success)
{
successCount++;
@@ -143,9 +190,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return res.RestoredProjects;
}).ToList();
assets = assetFiles;
CompilationInfos.Add(("Successfully restored solution files", successCount.ToString()));
CompilationInfos.Add(("Failed solution restore with package source error", nugetSourceFailures.ToString()));
CompilationInfos.Add(("Restored projects through solution files", projects.Count.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Successfully restored solution files", successCount.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Failed solution restore with package source error", nugetSourceFailures.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Restored projects through solution files", projects.Count.ToString()));
return projects;
}
@@ -161,10 +208,10 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
var nugetSourceFailures = 0;
var assetFiles = new List<string>();
var sync = new object();
Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = threads }, project =>
Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = DependencyManager.Threads }, project =>
{
logger.LogInfo($"Restoring project {project}...");
var res = dotnet.Restore(new(project, packageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true));
var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true));
lock (sync)
{
if (res.Success)
@@ -179,26 +226,25 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
});
assets = assetFiles;
CompilationInfos.Add(("Successfully restored project files", successCount.ToString()));
CompilationInfos.Add(("Failed project restore with package source error", nugetSourceFailures.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Successfully restored project files", successCount.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Failed project restore with package source error", nugetSourceFailures.ToString()));
}
private void DownloadMissingPackagesFromSpecificFeeds(HashSet<AssemblyLookupLocation> dllLocations)
private AssemblyLookupLocation? DownloadMissingPackagesFromSpecificFeeds()
{
var reachableFallbackFeeds = GetReachableFallbackNugetFeeds();
if (reachableFallbackFeeds.Count > 0)
{
DownloadMissingPackages(dllLocations, fallbackNugetFeeds: reachableFallbackFeeds);
}
else
{
logger.LogWarning("Skipping download of missing packages from specific feeds as no fallback Nuget feeds are reachable.");
return DownloadMissingPackages(fallbackNugetFeeds: reachableFallbackFeeds);
}
logger.LogWarning("Skipping download of missing packages from specific feeds as no fallback Nuget feeds are reachable.");
return null;
}
private void DownloadMissingPackages(HashSet<AssemblyLookupLocation> dllLocations, IEnumerable<string>? fallbackNugetFeeds = null)
private AssemblyLookupLocation? DownloadMissingPackages(IEnumerable<string>? fallbackNugetFeeds = null)
{
var alreadyDownloadedPackages = GetRestoredPackageDirectoryNames(packageDirectory.DirInfo);
var alreadyDownloadedPackages = GetRestoredPackageDirectoryNames(PackageDirectory.DirInfo);
var alreadyDownloadedLegacyPackages = GetRestoredLegacyPackageNames();
var notYetDownloadedPackages = new HashSet<PackageReference>(fileContent.AllPackages);
@@ -213,7 +259,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
if (notYetDownloadedPackages.Count == 0)
{
return;
return null;
}
var multipleVersions = notYetDownloadedPackages
@@ -229,17 +275,17 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
logger.LogInfo($"Found {notYetDownloadedPackages.Count} packages that are not yet restored");
using var tempDir = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "nugetconfig"));
using var tempDir = new TemporaryDirectory(ComputeTempDirectoryPath(fileProvider.SourceDir.FullName, "nugetconfig"));
var nugetConfig = fallbackNugetFeeds is null
? GetNugetConfig()
: CreateFallbackNugetConfig(fallbackNugetFeeds, tempDir.DirInfo.FullName);
CompilationInfos.Add(("Fallback nuget restore", notYetDownloadedPackages.Count.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Fallback nuget restore", notYetDownloadedPackages.Count.ToString()));
var successCount = 0;
var sync = new object();
Parallel.ForEach(notYetDownloadedPackages, new ParallelOptions { MaxDegreeOfParallelism = threads }, package =>
Parallel.ForEach(notYetDownloadedPackages, new ParallelOptions { MaxDegreeOfParallelism = DependencyManager.Threads }, package =>
{
var success = TryRestorePackageManually(package.Name, nugetConfig, package.PackageReferenceSource, tryWithoutNugetConfig: fallbackNugetFeeds is null);
if (!success)
@@ -253,9 +299,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}
});
CompilationInfos.Add(("Successfully ran fallback nuget restore", successCount.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Successfully ran fallback nuget restore", successCount.ToString()));
dllLocations.Add(missingPackageDirectory.DirInfo.FullName);
return missingPackageDirectory.DirInfo.FullName;
}
private string? CreateFallbackNugetConfig(IEnumerable<string> fallbackNugetFeeds, string folderPath)
@@ -318,10 +364,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
.ForEach(package => logger.LogInfo($"Unused package: {package}"));
}
private ICollection<string> GetAllPackageDirectories()
{
return new DirectoryInfo(packageDirectory.DirInfo.FullName)
return new DirectoryInfo(PackageDirectory.DirInfo.FullName)
.EnumerateDirectories("*", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = false })
.Select(d => d.Name)
.ToList();
@@ -366,7 +411,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
private bool TryRestorePackageManually(string package, string? nugetConfig = null, PackageReferenceSource packageReferenceSource = PackageReferenceSource.SdkCsProj, bool tryWithoutNugetConfig = true)
{
logger.LogInfo($"Restoring package {package}...");
using var tempDir = new TemporaryDirectory(ComputeTempDirectory(package, "missingpackages_workingdir"));
using var tempDir = new TemporaryDirectory(ComputeTempDirectoryPath(package, "missingpackages_workingdir"));
var success = dotnet.New(tempDir.DirInfo.FullName);
if (!success)
{
@@ -534,14 +579,14 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
severity: DiagnosticMessage.TspSeverity.Warning
));
}
CompilationInfos.Add(("All Nuget feeds reachable", allFeedsReachable ? "1" : "0"));
compilationInfoContainer.CompilationInfos.Add(("All Nuget feeds reachable", allFeedsReachable ? "1" : "0"));
var inheritedFeeds = allFeeds.Except(explicitFeeds).ToHashSet();
if (inheritedFeeds.Count > 0)
{
logger.LogInfo($"Inherited Nuget feeds (not checked for reachability): {string.Join(", ", inheritedFeeds.OrderBy(f => f))}");
CompilationInfos.Add(("Inherited Nuget feed count", inheritedFeeds.Count.ToString()));
compilationInfoContainer.CompilationInfos.Add(("Inherited Nuget feed count", inheritedFeeds.Count.ToString()));
}
return allFeedsReachable;
@@ -627,5 +672,28 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
[GeneratedRegex(@"^E\s(.*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
private static partial Regex EnabledNugetFeed();
public void Dispose()
{
DependencyManager.DisposeTempDirectory(PackageDirectory, "package", logger);
DependencyManager.DisposeTempDirectory(legacyPackageDirectory, "legacy package", logger);
DependencyManager.DisposeTempDirectory(missingPackageDirectory, "missing package", logger);
}
/// <summary>
/// Computes a unique temp directory for the packages associated
/// with this source tree. Use a SHA1 of the directory name.
/// </summary>
/// <returns>The full path of the temp directory.</returns>
private static string ComputeTempDirectoryPath(string srcDir, string subfolderName)
{
var bytes = Encoding.Unicode.GetBytes(srcDir);
var sha = SHA1.HashData(bytes);
var sb = new StringBuilder();
foreach (var b in sha.Take(8))
sb.AppendFormat("{0:x2}", b);
return Path.Combine(FileUtils.GetTemporaryWorkingDirectory(out var _), sb.ToString(), subfolderName);
}
}
}