Skip to content

Commit a304e2d

Browse files
authored
Merge pull request #16248 from michaelnebel/csharp/groupsprojectbeforerestore
C#: Restore projects and collect dependencies for projects in the same folder sequentially.
2 parents 1c611fe + d62e888 commit a304e2d

File tree

12 files changed

+359
-109
lines changed

12 files changed

+359
-109
lines changed

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

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ internal class Assets
1616
{
1717
private readonly ILogger logger;
1818

19+
/// <summary>
20+
/// Contains the dependencies found in the parsed assets files.
21+
/// </summary>
22+
public DependencyContainer Dependencies { get; } = new();
23+
1924
internal Assets(ILogger logger)
2025
{
2126
this.logger = logger;
@@ -72,7 +77,7 @@ private record class ReferenceInfo(string? Type, Dictionary<string, object>? Com
7277
/// "json.net"
7378
/// }
7479
/// </summary>
75-
private void AddPackageDependencies(JObject json, DependencyContainer dependencies)
80+
private void AddPackageDependencies(JObject json)
7681
{
7782
// If there is more than one framework we need to pick just one.
7883
// To ensure stability we pick one based on the lexicographic order of
@@ -107,13 +112,13 @@ private void AddPackageDependencies(JObject json, DependencyContainer dependenci
107112
// If this is a framework reference then include everything.
108113
if (FrameworkPackageNames.AllFrameworks.Any(framework => name.StartsWith(framework)))
109114
{
110-
dependencies.AddFramework(name);
115+
Dependencies.AddFramework(name);
111116
}
112117
return;
113118
}
114119

115120
info.Compile
116-
.ForEach(r => dependencies.Add(name, r.Key));
121+
.ForEach(r => Dependencies.Add(name, r.Key));
117122
});
118123

119124
return;
@@ -149,7 +154,7 @@ private void AddPackageDependencies(JObject json, DependencyContainer dependenci
149154
/// "microsoft.netcore.app.ref"
150155
/// }
151156
/// </summary>
152-
private void AddFrameworkDependencies(JObject json, DependencyContainer dependencies)
157+
private void AddFrameworkDependencies(JObject json)
153158
{
154159

155160
var frameworks = json
@@ -178,21 +183,21 @@ private void AddFrameworkDependencies(JObject json, DependencyContainer dependen
178183

179184
references
180185
.Properties()
181-
.ForEach(f => dependencies.AddFramework($"{f.Name}.Ref".ToLowerInvariant()));
186+
.ForEach(f => Dependencies.AddFramework($"{f.Name}.Ref".ToLowerInvariant()));
182187
}
183188

184189
/// <summary>
185190
/// Parse `json` as project.assets.json content and add relative paths to the dependencies
186191
/// (together with used package information) required for compilation.
187192
/// </summary>
188193
/// <returns>True if parsing succeeds, otherwise false.</returns>
189-
public bool TryParse(string json, DependencyContainer dependencies)
194+
public bool TryParse(string json)
190195
{
191196
try
192197
{
193198
var obj = JObject.Parse(json);
194-
AddPackageDependencies(obj, dependencies);
195-
AddFrameworkDependencies(obj, dependencies);
199+
AddPackageDependencies(obj);
200+
AddFrameworkDependencies(obj);
196201
return true;
197202
}
198203
catch (Exception e)
@@ -217,19 +222,24 @@ private static bool TryReadAllText(string path, ILogger logger, [NotNullWhen(ret
217222
}
218223
}
219224

220-
public static DependencyContainer GetCompilationDependencies(ILogger logger, IEnumerable<string> assets)
225+
/// <summary>
226+
/// Add the dependencies from the assets file to the dependencies.
227+
/// </summary>
228+
/// <param name="asset">Path to an assets file.</param>
229+
public void AddDependencies(string asset)
221230
{
222-
var parser = new Assets(logger);
223-
var dependencies = new DependencyContainer();
224-
assets.ForEach(asset =>
231+
if (TryReadAllText(asset, logger, out var json))
225232
{
226-
if (TryReadAllText(asset, logger, out var json))
227-
{
228-
parser.TryParse(json, dependencies);
229-
}
230-
});
231-
return dependencies;
233+
TryParse(json);
234+
}
232235
}
236+
237+
/// <summary>
238+
/// Add the dependencies from the assets files to the dependencies.
239+
/// </summary>
240+
/// <param name="assets">Collection of paths to assets files.</param>
241+
public void AddDependenciesRange(IEnumerable<string> assets) =>
242+
assets.ForEach(AddDependencies);
233243
}
234244

235245
internal static class JsonExtensions

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal class DependencyContainer
1212
/// <summary>
1313
/// Paths to dependencies required for compilation.
1414
/// </summary>
15-
public List<string> Paths { get; } = new();
15+
public HashSet<string> Paths { get; } = new();
1616

1717
/// <summary>
1818
/// Packages that are used as a part of the required dependencies.
@@ -45,7 +45,7 @@ public void Add(string package, string dependency)
4545
var p = package.Replace('/', Path.DirectorySeparatorChar);
4646
var d = dependency.Replace('/', Path.DirectorySeparatorChar);
4747

48-
// In most cases paths in asset files point to dll's or the empty _._ file.
48+
// In most cases paths in assets files point to dll's or the empty _._ file.
4949
// That is, for _._ we don't need to add anything.
5050
if (Path.GetFileName(d) == "_._")
5151
{
@@ -68,4 +68,18 @@ public void AddFramework(string framework)
6868
Packages.Add(GetPackageName(p));
6969
}
7070
}
71-
}
71+
72+
internal static class DependencyContainerExtensions
73+
{
74+
/// <summary>
75+
/// Flatten a list of containers into a single container.
76+
/// </summary>
77+
public static DependencyContainer Flatten(this IEnumerable<DependencyContainer> containers, DependencyContainer init) =>
78+
containers.Aggregate(init, (acc, container) =>
79+
{
80+
acc.Paths.UnionWith(container.Paths);
81+
acc.Packages.UnionWith(container.Packages);
82+
return acc;
83+
});
84+
}
85+
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Linq;
6-
using System.Security.Cryptography;
7-
using System.Text;
86
using System.Threading.Tasks;
97

108
using Semmle.Util;

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

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
@@ -144,11 +145,11 @@ public HashSet<AssemblyLookupLocation> Restore()
144145
logger.LogError($"Failed to restore Nuget packages with nuget.exe: {exc.Message}");
145146
}
146147

147-
var restoredProjects = RestoreSolutions(out var assets1);
148+
var restoredProjects = RestoreSolutions(out var container);
148149
var projects = fileProvider.Projects.Except(restoredProjects);
149-
RestoreProjects(projects, out var assets2);
150+
RestoreProjects(projects, out var containers);
150151

151-
var dependencies = Assets.GetCompilationDependencies(logger, assets1.Union(assets2));
152+
var dependencies = containers.Flatten(container);
152153

153154
var paths = dependencies
154155
.Paths
@@ -198,14 +199,14 @@ private List<string> GetReachableFallbackNugetFeeds()
198199
/// As opposed to RestoreProjects this is not run in parallel using PLINQ
199200
/// as `dotnet restore` on a solution already uses multiple threads for restoring
200201
/// the projects (this can be disabled with the `--disable-parallel` flag).
201-
/// Populates assets with the relative paths to the assets files generated by the restore.
202+
/// Populates dependencies with the relevant dependencies from the assets files generated by the restore.
202203
/// Returns a list of projects that are up to date with respect to restore.
203204
/// </summary>
204-
private IEnumerable<string> RestoreSolutions(out IEnumerable<string> assets)
205+
private IEnumerable<string> RestoreSolutions(out DependencyContainer dependencies)
205206
{
206207
var successCount = 0;
207208
var nugetSourceFailures = 0;
208-
var assetFiles = new List<string>();
209+
var assets = new Assets(logger);
209210
var projects = fileProvider.Solutions.SelectMany(solution =>
210211
{
211212
logger.LogInfo($"Restoring solution {solution}...");
@@ -218,10 +219,10 @@ private IEnumerable<string> RestoreSolutions(out IEnumerable<string> assets)
218219
{
219220
nugetSourceFailures++;
220221
}
221-
assetFiles.AddRange(res.AssetsFilePaths);
222+
assets.AddDependenciesRange(res.AssetsFilePaths);
222223
return res.RestoredProjects;
223224
}).ToList();
224-
assets = assetFiles;
225+
dependencies = assets.Dependencies;
225226
compilationInfoContainer.CompilationInfos.Add(("Successfully restored solution files", successCount.ToString()));
226227
compilationInfoContainer.CompilationInfos.Add(("Failed solution restore with package source error", nugetSourceFailures.ToString()));
227228
compilationInfoContainer.CompilationInfos.Add(("Restored projects through solution files", projects.Count.ToString()));
@@ -231,33 +232,39 @@ private IEnumerable<string> RestoreSolutions(out IEnumerable<string> assets)
231232
/// <summary>
232233
/// Executes `dotnet restore` on all projects in projects.
233234
/// This is done in parallel for performance reasons.
234-
/// Populates assets with the relative paths to the assets files generated by the restore.
235+
/// Populates dependencies with the relative paths to the assets files generated by the restore.
235236
/// </summary>
236237
/// <param name="projects">A list of paths to project files.</param>
237-
private void RestoreProjects(IEnumerable<string> projects, out IEnumerable<string> assets)
238+
private void RestoreProjects(IEnumerable<string> projects, out ConcurrentBag<DependencyContainer> dependencies)
238239
{
239240
var successCount = 0;
240241
var nugetSourceFailures = 0;
241-
var assetFiles = new List<string>();
242+
ConcurrentBag<DependencyContainer> collectedDependencies = [];
242243
var sync = new object();
243-
Parallel.ForEach(projects, new ParallelOptions { MaxDegreeOfParallelism = DependencyManager.Threads }, project =>
244+
var projectGroups = projects.GroupBy(Path.GetDirectoryName);
245+
Parallel.ForEach(projectGroups, new ParallelOptions { MaxDegreeOfParallelism = DependencyManager.Threads }, projectGroup =>
244246
{
245-
logger.LogInfo($"Restoring project {project}...");
246-
var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true));
247-
lock (sync)
247+
var assets = new Assets(logger);
248+
foreach (var project in projectGroup)
248249
{
249-
if (res.Success)
250+
logger.LogInfo($"Restoring project {project}...");
251+
var res = dotnet.Restore(new(project, PackageDirectory.DirInfo.FullName, ForceDotnetRefAssemblyFetching: true));
252+
assets.AddDependenciesRange(res.AssetsFilePaths);
253+
lock (sync)
250254
{
251-
successCount++;
252-
}
253-
if (res.HasNugetPackageSourceError)
254-
{
255-
nugetSourceFailures++;
255+
if (res.Success)
256+
{
257+
successCount++;
258+
}
259+
if (res.HasNugetPackageSourceError)
260+
{
261+
nugetSourceFailures++;
262+
}
256263
}
257-
assetFiles.AddRange(res.AssetsFilePaths);
258264
}
265+
collectedDependencies.Add(assets.Dependencies);
259266
});
260-
assets = assetFiles;
267+
dependencies = collectedDependencies;
261268
compilationInfoContainer.CompilationInfos.Add(("Successfully restored project files", successCount.ToString()));
262269
compilationInfoContainer.CompilationInfos.Add(("Failed project restore with package source error", nugetSourceFailures.ToString()));
263270
}

0 commit comments

Comments
 (0)