-
Notifications
You must be signed in to change notification settings - Fork 43
/
DependencyAnalyzer.cs
333 lines (281 loc) · 15 KB
/
DependencyAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading;
using Buildalyzer;
using Depends.Core.Extensions;
using Depends.Core.Graph;
using Microsoft.Extensions.Logging;
using NuGet.Configuration;
using NuGet.Frameworks;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
using System.Threading.Tasks;
using NuGet.Resolver;
using NuGet.Packaging;
using Microsoft.Build.Construction;
namespace Depends.Core
{
public class DependencyAnalyzer
{
static DependencyAnalyzer()
{
_ = typeof(NuGet.Common.LogLevel);
}
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
public DependencyAnalyzer(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_logger = _loggerFactory.CreateLogger(typeof(DependencyAnalyzer));
}
public DependencyGraph Analyze(string packageId, string version, string framework)
{
var package = new PackageIdentity(packageId, NuGetVersion.Parse(version));
var settings = Settings.LoadDefaultSettings(root: null, configFileName: null, machineWideSettings: null);
var sourceRepositoryProvider = new SourceRepositoryProvider(new PackageSourceProvider(settings), Repository.Provider.GetCoreV3());
var nuGetFramework = NuGetFramework.ParseFolder(framework);
var nugetLogger = _logger.AsNuGetLogger();
using (var cacheContext = new SourceCacheContext())
{
var repositories = sourceRepositoryProvider.GetRepositories();
var resolvedPackages = new ConcurrentDictionary<PackageIdentity, SourcePackageDependencyInfo>(PackageIdentityComparer.Default);
ResolvePackage(package, nuGetFramework, cacheContext, nugetLogger, repositories, resolvedPackages).Wait();
var availablePackages = new HashSet<SourcePackageDependencyInfo>(resolvedPackages.Values);
var resolverContext = new PackageResolverContext(
DependencyBehavior.Lowest,
new[] { packageId },
Enumerable.Empty<string>(),
Enumerable.Empty<PackageReference>(),
Enumerable.Empty<PackageIdentity>(),
availablePackages,
sourceRepositoryProvider.GetRepositories().Select(s => s.PackageSource),
nugetLogger);
var resolver = new PackageResolver();
var prunedPackages = resolver.Resolve(resolverContext, CancellationToken.None)
.Select(x => resolvedPackages[x]);
var rootNode = new PackageReferenceNode(package.Id, package.Version.ToString());
var packageNodes = new Dictionary<string, PackageReferenceNode>(StringComparer.OrdinalIgnoreCase);
var builder = new DependencyGraph.Builder(rootNode);
foreach (var target in prunedPackages)
{
var downloadResource = target.Source.GetResource<DownloadResource>();
var downloadResult = downloadResource.GetDownloadResourceResultAsync(new PackageIdentity(target.Id, target.Version),
new PackageDownloadContext(cacheContext),
SettingsUtility.GetGlobalPackagesFolder(settings),
nugetLogger, CancellationToken.None).Result;
var libItems = downloadResult.PackageReader.GetLibItems();
var reducer = new FrameworkReducer();
var nearest = reducer.GetNearest(nuGetFramework, libItems.Select(x => x.TargetFramework));
var assemblyReferences = libItems
.Where(x => x.TargetFramework.Equals(nearest))
.SelectMany(x => x.Items)
.Where(x => Path.GetExtension(x).Equals(".dll", StringComparison.OrdinalIgnoreCase))
.Select(x => new AssemblyReferenceNode(Path.GetFileName(x)));
var frameworkItems = downloadResult.PackageReader.GetFrameworkItems();
nearest = reducer.GetNearest(nuGetFramework, frameworkItems.Select(x => x.TargetFramework));
assemblyReferences = assemblyReferences.Concat(frameworkItems
.Where(x => x.TargetFramework.Equals(nearest))
.SelectMany(x => x.Items)
.Select(x => new AssemblyReferenceNode(x)));
var packageReferenceNode = new PackageReferenceNode(target.Id, target.Version.ToString());
builder.WithNode(packageReferenceNode);
builder.WithNodes(assemblyReferences);
builder.WithEdges(assemblyReferences.Select(x => new Edge(packageReferenceNode, x)));
packageNodes.Add(target.Id, packageReferenceNode);
}
foreach (var target in prunedPackages)
{
var packageReferenceNode = packageNodes[target.Id];
builder.WithEdges(target.Dependencies.Select(x =>
new Edge(packageReferenceNode, packageNodes[x.Id], x.VersionRange.ToString())));
}
return builder.Build();
}
}
private static async Task ResolvePackage(PackageIdentity package,
NuGetFramework framework,
SourceCacheContext cacheContext,
NuGet.Common.ILogger logger,
IEnumerable<SourceRepository> repositories,
ConcurrentDictionary<PackageIdentity, SourcePackageDependencyInfo> availablePackages)
{
if (availablePackages.ContainsKey(package))
{
return;
}
// TODO
// Avoid getting info for e.g. netstandard1.x if our framework is highet (e.g. netstandard2.0)
//if (framework.IsPackageBased &&
// package.Id.Equals("netstandard.library", StringComparison.OrdinalIgnoreCase) &&
// NuGetFrameworkUtility.IsCompatibleWithFallbackCheck(framework,
// NuGetFramework.Parse($"netstandard{package.Version.Major}.{package.Version.Minor}")))
//{
// return;
//}
foreach (var sourceRepository in repositories)
{
var dependencyInfoResource = await sourceRepository.GetResourceAsync<DependencyInfoResource>();
var dependencyInfo = await dependencyInfoResource.ResolvePackage(
package, framework, cacheContext, logger, CancellationToken.None);
if (dependencyInfo == null)
{
continue;
}
if (availablePackages.TryAdd(new PackageIdentity(dependencyInfo.Id, dependencyInfo.Version), dependencyInfo))
{
await Task.WhenAll(dependencyInfo.Dependencies.Select(dependency =>
{
return ResolvePackage(new PackageIdentity(dependency.Id, dependency.VersionRange.MinVersion),
framework, cacheContext, logger, repositories, availablePackages);
}));
}
}
}
public DependencyGraph AnalyzeSolution(string solution, string framework = null)
{
var analyzerManager = new AnalyzerManager(solution, new AnalyzerManagerOptions
{
LoggerFactory = _loggerFactory
});
var solutionNode = new SolutionReferenceNode(solution);
var builder = new DependencyGraph.Builder(solutionNode);
foreach (var project in analyzerManager.Projects.Where(p => p.Value.ProjectInSolution.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat))
{
builder = CreateBuilder(project.Value, project.Key, builder, framework);
}
return builder.Build();
}
public DependencyGraph Analyze(string projectPath, string framework = null)
{
var analyzerManager = new AnalyzerManager( new AnalyzerManagerOptions
{
LoggerFactory = _loggerFactory
});
if (string.IsNullOrWhiteSpace(projectPath))
{
throw new ArgumentException("Empty parameter", nameof(projectPath));
}
if (!File.Exists(projectPath))
{
throw new ArgumentException("Project path does not exist.", nameof(projectPath));
}
var projectAnalyzer = analyzerManager.GetProject(projectPath);
return CreateBuilder(projectAnalyzer, projectPath, null, framework).Build();
}
private static DependencyGraph.Builder CreateBuilder(IProjectAnalyzer projectAnalyzer, string projectPath, DependencyGraph.Builder builder = null, string framework = null)
{
var analyzeResults = string.IsNullOrEmpty(framework) ?
projectAnalyzer.Build() : projectAnalyzer.Build(framework);
var analyzerResult = (string.IsNullOrEmpty(framework) ?
analyzeResults.FirstOrDefault() : analyzeResults[framework]) ?? throw new InvalidOperationException("Unable to load project.");
var projectNode = new ProjectReferenceNode(projectPath);
if (builder == null)
{
builder = new DependencyGraph.Builder(projectNode);
}
else
{
builder.WithNode(projectNode);
builder.WithEdge(new Edge(builder.Root, projectNode));
}
var projectAssetsFilePath = analyzerResult.GetProjectAssetsFilePath();
if (!File.Exists(projectAssetsFilePath))
{
if (analyzerResult.IsNetSdkProject())
{
// a new project doesn't have an asset file
throw new InvalidOperationException($"{projectAssetsFilePath} not found. Please run 'dotnet restore'");
}
// Old csproj
var oldStylePackageReferences = analyzerResult.GetItems("Reference").Where(x => x.ItemSpec.Contains("Version="));
foreach (var reference in oldStylePackageReferences)
{
var split = reference.ItemSpec.Split(',');
var version = split.Single(s => s.Contains("Version="))?.Split('=')[1];
var name = reference.ItemSpec.Split(',')[0];
var node = new PackageReferenceNode(name, version);
builder.WithNode(node);
builder.WithEdge(new Edge(projectNode, node, version));
}
}
else
{
// New csproj
var lockFile = new LockFileFormat().Read(projectAssetsFilePath);
var targetFramework = analyzerResult.GetTargetFramework();
var runtimeIdentifier = analyzerResult.GetRuntimeIdentifier();
var libraries = lockFile.Targets.Single(
x => x.TargetFramework == targetFramework && x.RuntimeIdentifier == runtimeIdentifier)
.Libraries.Where(x => x.IsPackage()).ToList();
var libraryNodes = new Dictionary<string, PackageReferenceNode>(StringComparer.OrdinalIgnoreCase);
foreach (var library in libraries)
{
var libraryNode = library.ToNode();
builder.WithNode(libraryNode);
// Overwrite any previous additions that may have been added by the loop below.
libraryNodes[libraryNode.PackageId] = libraryNode;
// Not all dependencies are necessarily included in the list of libraries
// for the current target framework and runtime identifier. Add these to libraryNodes
// so we can still record these dependencies.
foreach (var dependency in library.Dependencies)
{
// Add min version in version range if this dependency doesn't exist
libraryNodes.TryAdd(
dependency.Id,
new PackageReferenceNode(dependency.Id, dependency.VersionRange.MinVersion.ToString()));
}
if (library.FrameworkAssemblies.Count > 0)
{
var assemblyNodes = library.FrameworkAssemblies
.Select(x => new AssemblyReferenceNode($"{x}.dll"));
builder.WithNodes(assemblyNodes);
builder.WithEdges(assemblyNodes
.Select(x => new Edge(libraryNode, x)));
}
if (library.RuntimeAssemblies.Count > 0)
{
var assemblyNodes = library.RuntimeAssemblies
.Select(x => new AssemblyReferenceNode(Path.GetFileName(x.Path)))
.Where(x => x.Id != "_._");
if (assemblyNodes.Any())
{
builder.WithNodes(assemblyNodes);
builder.WithEdges(assemblyNodes
.Select(x => new Edge(libraryNode, x)));
}
}
//if (library.CompileTimeAssemblies.Count > 0)
//{
// var assemblyNodes = library.CompileTimeAssemblies
// .Select(x => new AssemblyReferenceNode(Path.GetFileName(x.Path)));
// builder.WithNodes(assemblyNodes);
// builder.WithEdges(assemblyNodes
// .Select(x => new Edge(libraryNode, x)));
//}
}
foreach (var library in libraries)
{
var libraryNode = library.ToNode();
if (library.Dependencies.Count > 0)
{
builder.WithEdges(library.Dependencies
.Select(x => new Edge(libraryNode, libraryNodes[x.Id], x.VersionRange.ToString())));
}
}
// Ignore unversioned references like implicit SDK packages
builder.WithEdges(analyzerResult.GetItems("PackageReference")
.Where(x => x.Metadata.ContainsKey("Version"))
.Select(x => new Edge(projectNode, libraryNodes[x.ItemSpec], x.Metadata["Version"])));
}
var references = analyzerResult.References.Select(x => new AssemblyReferenceNode(Path.GetFileName(x)));
builder.WithNodes(references);
builder.WithEdges(references.Select(x => new Edge(projectNode, x)));
return builder;
}
}
}