Skip to content

Commit 4cebb7e

Browse files
committed
C#: Integrate the assets parsing and fetching in the dependency manager.
1 parent f8d4273 commit 4cebb7e

File tree

2 files changed

+92
-79
lines changed

2 files changed

+92
-79
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ public bool TryParse(string json, out IEnumerable<string> dependencies)
159159
return false;
160160
}
161161
}
162+
163+
public static IEnumerable<string> GetCompilationDependencies(ProgressMonitor progressMonitor, IEnumerable<string> assets)
164+
{
165+
var parser = new Assets(progressMonitor);
166+
return assets.SelectMany(asset =>
167+
{
168+
var json = File.ReadAllText(asset);
169+
return parser.TryParse(json, out var dependencies) ? dependencies : Array.Empty<string>();
170+
});
171+
}
162172
}
163173

164174
internal static class JsonExtensions

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

Lines changed: 82 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public sealed class DependencyManager : IDisposable
3434
private readonly TemporaryDirectory tempWorkingDirectory;
3535
private readonly bool cleanupTempWorkingDirectory;
3636

37+
private readonly Lazy<Runtime> runtimeLazy;
38+
private Runtime Runtime => runtimeLazy.Value;
39+
3740
/// <summary>
3841
/// Performs C# dependency fetching.
3942
/// </summary>
@@ -53,6 +56,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
5356
try
5457
{
5558
this.dotnet = DotNet.Make(options, progressMonitor, tempWorkingDirectory);
59+
runtimeLazy = new Lazy<Runtime>(() => new Runtime(dotnet));
5660
}
5761
catch
5862
{
@@ -80,7 +84,6 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
8084

8185
if (options.UseNuGet)
8286
{
83-
dllDirNames.Add(packageDirectory.DirInfo.FullName);
8487
try
8588
{
8689
var nuget = new NugetPackages(sourceDir.FullName, packageDirectory, progressMonitor);
@@ -91,37 +94,27 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
9194
progressMonitor.MissingNuGet();
9295
}
9396

94-
var restoredProjects = RestoreSolutions(solutions);
97+
var restoredProjects = RestoreSolutions(solutions, out var assets1);
9598
var projects = allProjects.Except(restoredProjects);
96-
RestoreProjects(projects);
99+
RestoreProjects(projects, out var assets2);
100+
101+
var dependencies = Assets.GetCompilationDependencies(progressMonitor, assets1.Union(assets2));
102+
103+
var paths = dependencies
104+
.Select(d => Path.Combine(packageDirectory.DirInfo.FullName, d))
105+
.ToList();
106+
107+
// TODO: Rename the dllDirNames var - it's not only dirs anymore.
108+
dllDirNames.AddRange(paths);
97109
DownloadMissingPackages(allNonBinaryFiles);
98110
}
99111

100-
var existsNetCoreRefNugetPackage = false;
101-
var existsNetFrameworkRefNugetPackage = false;
102-
var existsNetstandardLibRefNugetPackage = false;
103-
var existsNetstandardLibNugetPackage = false;
104-
105112
// Find DLLs in the .Net / Asp.Net Framework
106113
// This block needs to come after the nuget restore, because the nuget restore might fetch the .NET Core/Framework reference assemblies.
107114
if (options.ScanNetFrameworkDlls)
108115
{
109-
existsNetCoreRefNugetPackage = IsNugetPackageAvailable("microsoft.netcore.app.ref");
110-
existsNetFrameworkRefNugetPackage = IsNugetPackageAvailable("microsoft.netframework.referenceassemblies");
111-
existsNetstandardLibRefNugetPackage = IsNugetPackageAvailable("netstandard.library.ref");
112-
existsNetstandardLibNugetPackage = IsNugetPackageAvailable("netstandard.library");
113-
114-
if (existsNetCoreRefNugetPackage
115-
|| existsNetFrameworkRefNugetPackage
116-
|| existsNetstandardLibRefNugetPackage
117-
|| existsNetstandardLibNugetPackage)
118-
{
119-
progressMonitor.LogInfo("Found .NET Core/Framework DLLs in NuGet packages. Not adding installation directory.");
120-
}
121-
else
122-
{
123-
AddNetFrameworkDlls(dllDirNames);
124-
}
116+
AddNetFrameworkDlls(dllDirNames);
117+
AddAspNetFrameworkDlls(dllDirNames);
125118
}
126119

127120
assemblyCache = new AssemblyCache(dllDirNames, progressMonitor);
@@ -132,7 +125,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
132125
UseReference(filename);
133126
}
134127

135-
RemoveUnnecessaryNugetPackages(existsNetCoreRefNugetPackage, existsNetFrameworkRefNugetPackage, existsNetstandardLibRefNugetPackage, existsNetstandardLibNugetPackage);
128+
RemoveUnnecessaryNugetPackages();
136129
ResolveConflicts();
137130

138131
// Output the findings
@@ -167,8 +160,7 @@ public DependencyManager(string srcDir, IDependencyOptions options, ILogger logg
167160
DateTime.Now - startTime);
168161
}
169162

170-
private void RemoveUnnecessaryNugetPackages(bool existsNetCoreRefNugetPackage, bool existsNetFrameworkRefNugetPackage,
171-
bool existsNetstandardLibRefNugetPackage, bool existsNetstandardLibNugetPackage)
163+
private void RemoveUnnecessaryNugetPackages()
172164
{
173165
RemoveNugetAnalyzerReferences();
174166
RemoveRuntimeNugetPackageReferences();
@@ -180,40 +172,6 @@ private void RemoveUnnecessaryNugetPackages(bool existsNetCoreRefNugetPackage, b
180172
RemoveNugetPackageReference("microsoft.aspnetcore.app.ref");
181173
}
182174

183-
// Multiple dotnet framework packages could be present. We keep only one.
184-
// The order of the packages is important, we're keeping the first one that is present in the nuget cache.
185-
var packagesInPrioOrder = new (bool isPresent, string prefix)[]
186-
{
187-
// net7.0, ... net5.0, netcoreapp3.1, netcoreapp3.0
188-
(existsNetCoreRefNugetPackage, "microsoft.netcore.app.ref"),
189-
// net48, ..., net20
190-
(existsNetFrameworkRefNugetPackage, "microsoft.netframework.referenceassemblies."),
191-
// netstandard2.1
192-
(existsNetstandardLibRefNugetPackage, "netstandard.library.ref"),
193-
// netstandard2.0
194-
(existsNetstandardLibNugetPackage, "netstandard.library")
195-
};
196-
197-
for (var i = 0; i < packagesInPrioOrder.Length; i++)
198-
{
199-
var (isPresent, _) = packagesInPrioOrder[i];
200-
if (!isPresent)
201-
{
202-
continue;
203-
}
204-
205-
// Package is present, remove all the lower priority packages:
206-
for (var j = i + 1; j < packagesInPrioOrder.Length; j++)
207-
{
208-
var (otherIsPresent, otherPrefix) = packagesInPrioOrder[j];
209-
if (otherIsPresent)
210-
{
211-
RemoveNugetPackageReference(otherPrefix);
212-
}
213-
}
214-
break;
215-
}
216-
217175
// TODO: There could be multiple `microsoft.netframework.referenceassemblies` packages,
218176
// we could keep the newest one, but this is covered by the conflict resolution logic
219177
// (if the file names match)
@@ -258,35 +216,68 @@ private void RemoveNugetAnalyzerReferences()
258216
}
259217
}
260218
}
219+
261220
private void AddNetFrameworkDlls(List<string> dllDirNames)
262221
{
263-
var runtime = new Runtime(dotnet);
222+
// Multiple dotnet framework packages could be present.
223+
// The order of the packages is important, we're adding the first one that is present in the nuget cache.
224+
var packagesInPrioOrder = new string[]
225+
{
226+
"microsoft.netcore.app.ref", // net7.0, ... net5.0, netcoreapp3.1, netcoreapp3.0
227+
"microsoft.netframework.referenceassemblies.", // net48, ..., net20
228+
"netstandard.library.ref", // netstandard2.1
229+
"netstandard.library" // netstandard2.0
230+
};
231+
232+
var frameworkPath = packagesInPrioOrder
233+
.Select(GetPackageDirectory)
234+
.FirstOrDefault(dir => dir is not null);
235+
236+
if (frameworkPath is not null)
237+
{
238+
dllDirNames.Add(frameworkPath);
239+
progressMonitor.LogInfo("Found .NET Core/Framework DLLs in NuGet packages. Not adding installation directory.");
240+
return;
241+
}
242+
264243
string? runtimeLocation = null;
265244

266245
if (options.UseSelfContainedDotnet)
267246
{
268-
runtimeLocation = runtime.ExecutingRuntime;
247+
runtimeLocation = Runtime.ExecutingRuntime;
269248
}
270249
else if (fileContent.IsNewProjectStructureUsed)
271250
{
272-
runtimeLocation = runtime.NetCoreRuntime;
251+
runtimeLocation = Runtime.NetCoreRuntime;
273252
}
274253
else if (fileContent.IsLegacyProjectStructureUsed)
275254
{
276-
runtimeLocation = runtime.DesktopRuntime;
255+
runtimeLocation = Runtime.DesktopRuntime;
277256
}
278257

279-
runtimeLocation ??= runtime.ExecutingRuntime;
258+
runtimeLocation ??= Runtime.ExecutingRuntime;
280259

281260
progressMonitor.LogInfo($".NET runtime location selected: {runtimeLocation}");
282261
dllDirNames.Add(runtimeLocation);
262+
}
283263

284-
if (fileContent.IsNewProjectStructureUsed
285-
&& fileContent.UseAspNetCoreDlls
286-
&& runtime.AspNetCoreRuntime is string aspRuntime)
264+
private void AddAspNetFrameworkDlls(List<string> dllDirNames)
265+
{
266+
if (!fileContent.IsNewProjectStructureUsed || !fileContent.UseAspNetCoreDlls)
267+
{
268+
return;
269+
}
270+
271+
// First try to find ASP.NET assemblies in the NuGet packages
272+
if (GetPackageDirectory("microsoft.aspnetcore.app.ref") is string aspNetCorePackage)
273+
{
274+
progressMonitor.LogInfo($"Found ASP.NET Core in NuGet packages. Not adding installation directory.");
275+
dllDirNames.Add(aspNetCorePackage);
276+
}
277+
else if (Runtime.AspNetCoreRuntime is string aspNetCoreRuntime)
287278
{
288-
progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspRuntime}");
289-
dllDirNames.Add(aspRuntime);
279+
progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspNetCoreRuntime}");
280+
dllDirNames.Add(aspNetCoreRuntime);
290281
}
291282
}
292283

@@ -336,16 +327,17 @@ private void RemoveNugetPackageReference(params string[] packagePrefixes)
336327
}
337328
}
338329

339-
private bool IsNugetPackageAvailable(string packagePrefix)
330+
private string? GetPackageDirectory(string packagePrefix)
340331
{
341332
if (!options.UseNuGet)
342333
{
343-
return false;
334+
return null;
344335
}
345336

346337
return new DirectoryInfo(packageDirectory.DirInfo.FullName)
347338
.EnumerateDirectories(packagePrefix + "*", new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = false })
348-
.Any();
339+
.FirstOrDefault()?
340+
.FullName;
349341
}
350342

351343
private void GenerateSourceFileFromImplicitUsings()
@@ -634,27 +626,38 @@ private bool RestoreSolution(string solution, out IEnumerable<string> projects,
634626
/// As opposed to RestoreProjects this is not run in parallel using PLINQ
635627
/// as `dotnet restore` on a solution already uses multiple threads for restoring
636628
/// the projects (this can be disabled with the `--disable-parallel` flag).
629+
/// Populates assets with the relative paths to the assets files generated by the restore.
637630
/// Returns a list of projects that are up to date with respect to restore.
638631
/// </summary>
639632
/// <param name="solutions">A list of paths to solution files.</param>
640-
private IEnumerable<string> RestoreSolutions(IEnumerable<string> solutions) =>
641-
solutions.SelectMany(solution =>
633+
private IEnumerable<string> RestoreSolutions(IEnumerable<string> solutions, out IEnumerable<string> assets)
634+
{
635+
var assetFiles = new List<string>();
636+
var projects = solutions.SelectMany(solution =>
642637
{
643-
RestoreSolution(solution, out var restoredProjects, out var assets);
638+
RestoreSolution(solution, out var restoredProjects, out var a);
639+
assetFiles.AddRange(a);
644640
return restoredProjects;
645641
});
642+
assets = assetFiles;
643+
return projects;
644+
}
646645

647646
/// <summary>
648647
/// Executes `dotnet restore` on all projects in projects.
649648
/// This is done in parallel for performance reasons.
649+
/// Populates assets with the relative paths to the assets files generated by the restore.
650650
/// </summary>
651651
/// <param name="projects">A list of paths to project files.</param>
652-
private void RestoreProjects(IEnumerable<string> projects)
652+
private void RestoreProjects(IEnumerable<string> projects, out IEnumerable<string> assets)
653653
{
654+
var assetFiles = new List<string>();
654655
Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = options.Threads }, project =>
655656
{
656-
RestoreProject(project, forceDotnetRefAssemblyFetching: true, out var assets);
657+
RestoreProject(project, forceDotnetRefAssemblyFetching: true, out var a);
658+
assetFiles.AddRange(a);
657659
});
660+
assets = assetFiles;
658661
}
659662

660663
private void DownloadMissingPackages(List<FileInfo> allFiles)

0 commit comments

Comments
 (0)