Skip to content

Commit 611cf23

Browse files
authored
Merge pull request github#16195 from tamasvajk/depManager/refactoring
C#: Split `DependencyManager` into multiple classes
2 parents 0bed221 + 69c4309 commit 611cf23

File tree

6 files changed

+324
-215
lines changed

6 files changed

+324
-215
lines changed

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/DependencyManager.cs

Lines changed: 55 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,26 @@
1212

1313
namespace Semmle.Extraction.CSharp.DependencyFetching
1414
{
15+
public interface ICompilationInfoContainer
16+
{
17+
/// <summary>
18+
/// List of `(key, value)` tuples, that are stored in the DB for telemetry purposes.
19+
/// </summary>
20+
List<(string, string)> CompilationInfos { get; }
21+
}
22+
1523
/// <summary>
1624
/// Main implementation of the build analysis.
1725
/// </summary>
18-
public sealed partial class DependencyManager : IDisposable
26+
public sealed partial class DependencyManager : IDisposable, ICompilationInfoContainer
1927
{
2028
private readonly AssemblyCache assemblyCache;
2129
private readonly ILogger logger;
2230
private readonly IDiagnosticsWriter diagnosticsWriter;
31+
private readonly NugetPackageRestorer nugetPackageRestorer;
32+
private readonly IDotNet dotnet;
33+
private readonly FileContent fileContent;
34+
private readonly FileProvider fileProvider;
2335

2436
// Only used as a set, but ConcurrentDictionary is the only concurrent set in .NET.
2537
private readonly IDictionary<string, bool> usedReferences = new ConcurrentDictionary<string, bool>();
@@ -30,17 +42,14 @@ public sealed partial class DependencyManager : IDisposable
3042
private int conflictedReferences = 0;
3143
private readonly DirectoryInfo sourceDir;
3244
private string? dotnetPath;
33-
private readonly IDotNet dotnet;
34-
private readonly FileContent fileContent;
35-
private readonly TemporaryDirectory packageDirectory;
36-
private readonly TemporaryDirectory legacyPackageDirectory;
37-
private readonly TemporaryDirectory missingPackageDirectory;
45+
3846
private readonly TemporaryDirectory tempWorkingDirectory;
3947
private readonly bool cleanupTempWorkingDirectory;
4048

4149
private readonly Lazy<Runtime> runtimeLazy;
4250
private Runtime Runtime => runtimeLazy.Value;
43-
private readonly int threads = EnvironmentVariables.GetDefaultNumberOfThreads();
51+
52+
internal static readonly int Threads = EnvironmentVariables.GetDefaultNumberOfThreads();
4453

4554
/// <summary>
4655
/// Performs C# dependency fetching.
@@ -73,26 +82,15 @@ public DependencyManager(string srcDir, ILogger logger)
7382
$"dependency-manager-{DateTime.UtcNow:yyyyMMddHHmm}-{Environment.ProcessId}.jsonc"));
7483
this.sourceDir = new DirectoryInfo(srcDir);
7584

76-
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "packages"));
77-
legacyPackageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "legacypackages"));
78-
missingPackageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName, "missingpackages"));
79-
80-
tempWorkingDirectory = new TemporaryDirectory(FileUtils.GetTemporaryWorkingDirectory(out cleanupTempWorkingDirectory));
85+
tempWorkingDirectory = new TemporaryDirectory(
86+
FileUtils.GetTemporaryWorkingDirectory(out cleanupTempWorkingDirectory),
87+
"temporary working",
88+
logger);
8189

82-
logger.LogInfo($"Finding files in {srcDir}...");
83-
84-
var allFiles = GetAllFiles().ToList();
85-
var binaryFileExtensions = new HashSet<string>(new[] { ".dll", ".exe" }); // TODO: add more binary file extensions.
86-
var allNonBinaryFiles = allFiles.Where(f => !binaryFileExtensions.Contains(f.Extension.ToLowerInvariant())).ToList();
87-
var smallNonBinaryFiles = allNonBinaryFiles.SelectSmallFiles(logger).SelectFileNames().ToList();
88-
this.fileContent = new FileContent(logger, smallNonBinaryFiles);
89-
this.nonGeneratedSources = allNonBinaryFiles.SelectFileNamesByExtension(".cs").ToList();
90-
this.generatedSources = new();
91-
var allProjects = allNonBinaryFiles.SelectFileNamesByExtension(".csproj").ToList();
92-
var allSolutions = allNonBinaryFiles.SelectFileNamesByExtension(".sln").ToList();
93-
var dllLocations = allFiles.SelectFileNamesByExtension(".dll").Select(x => new AssemblyLookupLocation(x)).ToHashSet();
94-
95-
logger.LogInfo($"Found {allFiles.Count} files, {nonGeneratedSources.Count} source files, {allProjects.Count} project files, {allSolutions.Count} solution files, {dllLocations.Count} DLLs.");
90+
this.fileProvider = new FileProvider(sourceDir, logger);
91+
this.fileContent = new FileContent(logger, this.fileProvider.SmallNonBinary);
92+
this.nonGeneratedSources = fileProvider.Sources.ToList();
93+
this.generatedSources = [];
9694

9795
void startCallback(string s, bool silent)
9896
{
@@ -104,7 +102,7 @@ void exitCallback(int ret, string msg, bool silent)
104102
logger.Log(silent ? Severity.Debug : Severity.Info, $"Exit code {ret}{(string.IsNullOrEmpty(msg) ? "" : $": {msg}")}");
105103
}
106104

107-
DotNet.WithDotNet(SystemBuildActions.Instance, logger, smallNonBinaryFiles, tempWorkingDirectory.ToString(), shouldCleanUp: false, ensureDotNetAvailable: true, version: null, installDir =>
105+
DotNet.WithDotNet(SystemBuildActions.Instance, logger, fileProvider.GlobalJsons, tempWorkingDirectory.ToString(), shouldCleanUp: false, ensureDotNetAvailable: true, version: null, installDir =>
108106
{
109107
this.dotnetPath = installDir;
110108
return BuildScript.Success;
@@ -121,13 +119,16 @@ void exitCallback(int ret, string msg, bool silent)
121119
throw;
122120
}
123121

124-
RestoreNugetPackages(allNonBinaryFiles, allProjects, allSolutions, dllLocations);
122+
nugetPackageRestorer = new NugetPackageRestorer(fileProvider, fileContent, dotnet, diagnosticsWriter, logger, this);
123+
124+
var dllLocations = fileProvider.Dlls.Select(x => new AssemblyLookupLocation(x)).ToHashSet();
125+
dllLocations.UnionWith(nugetPackageRestorer.Restore());
125126
// Find DLLs in the .Net / Asp.Net Framework
126127
// This needs to come after the nuget restore, because the nuget restore might fetch the .NET Core/Framework reference assemblies.
127128
var frameworkLocations = AddFrameworkDlls(dllLocations);
128129

129130
assemblyCache = new AssemblyCache(dllLocations, frameworkLocations, logger);
130-
AnalyseSolutions(allSolutions);
131+
AnalyseSolutions(fileProvider.Solutions);
131132

132133
foreach (var filename in assemblyCache.AllAssemblies.Select(a => a.Filename))
133134
{
@@ -154,7 +155,7 @@ void exitCallback(int ret, string msg, bool silent)
154155
shouldExtractWebViews)
155156
{
156157
CompilationInfos.Add(("WebView extraction enabled", "1"));
157-
GenerateSourceFilesFromWebViews(allNonBinaryFiles);
158+
GenerateSourceFilesFromWebViews();
158159
}
159160
else
160161
{
@@ -171,8 +172,8 @@ void exitCallback(int ret, string msg, bool silent)
171172
logger.LogInfo("Build analysis summary:");
172173
logger.LogInfo($"{nonGeneratedSources.Count,align} source files found on the filesystem");
173174
logger.LogInfo($"{generatedSources.Count,align} source files have been generated");
174-
logger.LogInfo($"{allSolutions.Count,align} solution files found on the filesystem");
175-
logger.LogInfo($"{allProjects.Count,align} project files found on the filesystem");
175+
logger.LogInfo($"{fileProvider.Solutions.Count,align} solution files found on the filesystem");
176+
logger.LogInfo($"{fileProvider.Projects.Count,align} project files found on the filesystem");
176177
logger.LogInfo($"{usedReferences.Keys.Count,align} resolved references");
177178
logger.LogInfo($"{unresolvedReferences.Count,align} unresolved references");
178179
logger.LogInfo($"{conflictedReferences,align} resolved assembly conflicts");
@@ -182,8 +183,8 @@ void exitCallback(int ret, string msg, bool silent)
182183
CompilationInfos.AddRange([
183184
("Source files on filesystem", nonGeneratedSources.Count.ToString()),
184185
("Source files generated", generatedSources.Count.ToString()),
185-
("Solution files on filesystem", allSolutions.Count.ToString()),
186-
("Project files on filesystem", allProjects.Count.ToString()),
186+
("Solution files on filesystem", fileProvider.Solutions.Count.ToString()),
187+
("Project files on filesystem", fileProvider.Projects.Count.ToString()),
187188
("Resolved references", usedReferences.Keys.Count.ToString()),
188189
("Unresolved references", unresolvedReferences.Count.ToString()),
189190
("Resolved assembly conflicts", conflictedReferences.ToString()),
@@ -229,11 +230,7 @@ private HashSet<string> AddFrameworkDlls(HashSet<AssemblyLookupLocation> dllLoca
229230

230231
private void RemoveNugetAnalyzerReferences()
231232
{
232-
var packageFolder = packageDirectory.DirInfo.FullName.ToLowerInvariant();
233-
if (packageFolder == null)
234-
{
235-
return;
236-
}
233+
var packageFolder = nugetPackageRestorer.PackageDirectory.DirInfo.FullName.ToLowerInvariant();
237234

238235
foreach (var filename in usedReferences.Keys)
239236
{
@@ -307,7 +304,7 @@ private void AddNetFrameworkDlls(ISet<AssemblyLookupLocation> dllLocations, ISet
307304
var packagesInPrioOrder = FrameworkPackageNames.NetFrameworks;
308305

309306
var frameworkPaths = packagesInPrioOrder
310-
.Select((s, index) => (Index: index, Path: GetPackageDirectory(s, packageDirectory)))
307+
.Select((s, index) => (Index: index, Path: GetPackageDirectory(s)))
311308
.Where(pair => pair.Path is not null)
312309
.ToArray();
313310

@@ -338,11 +335,7 @@ private void AddNetFrameworkDlls(ISet<AssemblyLookupLocation> dllLocations, ISet
338335
if (runtimeLocation is null)
339336
{
340337
logger.LogInfo("No .NET Desktop Runtime location found. Attempting to restore the .NET Framework reference assemblies manually.");
341-
342-
if (TryRestorePackageManually(FrameworkPackageNames.LatestNetFrameworkReferenceAssemblies))
343-
{
344-
runtimeLocation = GetPackageDirectory(FrameworkPackageNames.LatestNetFrameworkReferenceAssemblies, missingPackageDirectory);
345-
}
338+
runtimeLocation = nugetPackageRestorer.TryRestoreLatestNetFrameworkReferenceAssemblies();
346339
}
347340
}
348341

@@ -362,12 +355,7 @@ private void AddNetFrameworkDlls(ISet<AssemblyLookupLocation> dllLocations, ISet
362355

363356
private void RemoveNugetPackageReference(string packagePrefix, ISet<AssemblyLookupLocation> dllLocations)
364357
{
365-
var packageFolder = packageDirectory.DirInfo.FullName.ToLowerInvariant();
366-
if (packageFolder == null)
367-
{
368-
return;
369-
}
370-
358+
var packageFolder = nugetPackageRestorer.PackageDirectory.DirInfo.FullName.ToLowerInvariant();
371359
var packagePathPrefix = Path.Combine(packageFolder, packagePrefix.ToLowerInvariant());
372360
var toRemove = dllLocations.Where(s => s.Path.StartsWith(packagePathPrefix, StringComparison.InvariantCultureIgnoreCase));
373361
foreach (var path in toRemove)
@@ -390,7 +378,7 @@ private void AddAspNetCoreFrameworkDlls(ISet<AssemblyLookupLocation> dllLocation
390378
}
391379

392380
// First try to find ASP.NET Core assemblies in the NuGet packages
393-
if (GetPackageDirectory(FrameworkPackageNames.AspNetCoreFramework, packageDirectory) is string aspNetCorePackage)
381+
if (GetPackageDirectory(FrameworkPackageNames.AspNetCoreFramework) is string aspNetCorePackage)
394382
{
395383
SelectNewestFrameworkPath(aspNetCorePackage, "ASP.NET Core", dllLocations, frameworkLocations);
396384
return;
@@ -406,15 +394,20 @@ private void AddAspNetCoreFrameworkDlls(ISet<AssemblyLookupLocation> dllLocation
406394

407395
private void AddMicrosoftWindowsDesktopDlls(ISet<AssemblyLookupLocation> dllLocations, ISet<string> frameworkLocations)
408396
{
409-
if (GetPackageDirectory(FrameworkPackageNames.WindowsDesktopFramework, packageDirectory) is string windowsDesktopApp)
397+
if (GetPackageDirectory(FrameworkPackageNames.WindowsDesktopFramework) is string windowsDesktopApp)
410398
{
411399
SelectNewestFrameworkPath(windowsDesktopApp, "Windows Desktop App", dllLocations, frameworkLocations);
412400
}
413401
}
414402

415-
private string? GetPackageDirectory(string packagePrefix, TemporaryDirectory root)
403+
private string? GetPackageDirectory(string packagePrefix)
404+
{
405+
return GetPackageDirectory(packagePrefix, nugetPackageRestorer.PackageDirectory.DirInfo);
406+
}
407+
408+
internal static string? GetPackageDirectory(string packagePrefix, DirectoryInfo root)
416409
{
417-
return new DirectoryInfo(root.DirInfo.FullName)
410+
return new DirectoryInfo(root.FullName)
418411
.EnumerateDirectories(packagePrefix + "*", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = false })
419412
.FirstOrDefault()?
420413
.FullName;
@@ -467,15 +460,15 @@ private void GenerateSourceFileFromImplicitUsings()
467460
}
468461
}
469462

470-
private void GenerateSourceFilesFromWebViews(List<FileInfo> allFiles)
463+
private void GenerateSourceFilesFromWebViews()
471464
{
472-
var views = allFiles.SelectFileNamesByExtension(".cshtml", ".razor").ToArray();
473-
if (views.Length == 0)
465+
var views = fileProvider.RazorViews;
466+
if (views.Count == 0)
474467
{
475468
return;
476469
}
477470

478-
logger.LogInfo($"Found {views.Length} cshtml and razor files.");
471+
logger.LogInfo($"Found {views.Count} cshtml and razor files.");
479472

480473
if (!IsAspNetCoreDetected())
481474
{
@@ -503,54 +496,6 @@ private void GenerateSourceFilesFromWebViews(List<FileInfo> allFiles)
503496
}
504497
}
505498

506-
private IEnumerable<FileInfo> GetAllFiles()
507-
{
508-
IEnumerable<FileInfo> files = sourceDir.GetFiles("*.*", new EnumerationOptions { RecurseSubdirectories = true });
509-
510-
if (dotnetPath != null)
511-
{
512-
files = files.Where(f => !f.FullName.StartsWith(dotnetPath, StringComparison.OrdinalIgnoreCase));
513-
}
514-
515-
files = files.Where(f =>
516-
{
517-
try
518-
{
519-
if (f.Exists)
520-
{
521-
return true;
522-
}
523-
524-
logger.LogWarning($"File {f.FullName} could not be processed.");
525-
return false;
526-
}
527-
catch (Exception ex)
528-
{
529-
logger.LogWarning($"File {f.FullName} could not be processed: {ex.Message}");
530-
return false;
531-
}
532-
});
533-
534-
files = new FilePathFilter(sourceDir, logger).Filter(files);
535-
return files;
536-
}
537-
538-
/// <summary>
539-
/// Computes a unique temp directory for the packages associated
540-
/// with this source tree. Use a SHA1 of the directory name.
541-
/// </summary>
542-
/// <returns>The full path of the temp directory.</returns>
543-
private static string ComputeTempDirectory(string srcDir, string subfolderName)
544-
{
545-
var bytes = Encoding.Unicode.GetBytes(srcDir);
546-
var sha = SHA1.HashData(bytes);
547-
var sb = new StringBuilder();
548-
foreach (var b in sha.Take(8))
549-
sb.AppendFormat("{0:x2}", b);
550-
551-
return Path.Combine(FileUtils.GetTemporaryWorkingDirectory(out var _), sb.ToString(), subfolderName);
552-
}
553-
554499
/// <summary>
555500
/// Creates a temporary directory with the given subfolder name.
556501
/// The created directory might be inside the repo folder, and it is deleted when the object is disposed.
@@ -674,7 +619,7 @@ private void ResolveConflicts(IEnumerable<string> frameworkPaths)
674619

675620
private void AnalyseSolutions(IEnumerable<string> solutions)
676621
{
677-
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = threads }, solutionFile =>
622+
Parallel.ForEach(solutions, new ParallelOptions { MaxDegreeOfParallelism = Threads }, solutionFile =>
678623
{
679624
try
680625
{
@@ -723,29 +668,11 @@ private void AnalyseProject(FileInfo project)
723668
}
724669
}
725670

726-
public void Dispose(TemporaryDirectory? dir, string name)
727-
{
728-
try
729-
{
730-
dir?.Dispose();
731-
}
732-
catch (Exception exc)
733-
{
734-
logger.LogInfo($"Couldn't delete {name} directory {exc.Message}");
735-
}
736-
}
737-
738671
public void Dispose()
739672
{
740-
Dispose(packageDirectory, "package");
741-
Dispose(legacyPackageDirectory, "legacy package");
742-
Dispose(missingPackageDirectory, "missing package");
743-
if (cleanupTempWorkingDirectory)
744-
{
745-
Dispose(tempWorkingDirectory, "temporary working");
746-
}
747-
673+
tempWorkingDirectory?.Dispose();
748674
diagnosticsWriter?.Dispose();
675+
nugetPackageRestorer?.Dispose();
749676
}
750677
}
751678
}

csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/FileInfoExtensions.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,6 @@ private static IEnumerable<string> SelectFilesAux(this IEnumerable<FileInfo> fil
1414
public static IEnumerable<FileInfo> SelectRootFiles(this IEnumerable<FileInfo> files, DirectoryInfo dir) =>
1515
files.Where(file => file.DirectoryName == dir.FullName);
1616

17-
internal static IEnumerable<FileInfo> SelectSmallFiles(this IEnumerable<FileInfo> files, ILogger logger)
18-
{
19-
const int oneMb = 1_048_576;
20-
return files.Where(file =>
21-
{
22-
if (file.Length > oneMb)
23-
{
24-
logger.LogDebug($"Skipping {file.FullName} because it is bigger than 1MB.");
25-
return false;
26-
}
27-
return true;
28-
});
29-
}
30-
3117
public static IEnumerable<string> SelectFileNamesByExtension(this IEnumerable<FileInfo> files, params string[] extensions) =>
3218
files.SelectFilesAux(fi => extensions.Contains(fi.Extension));
3319

0 commit comments

Comments
 (0)