-
Notifications
You must be signed in to change notification settings - Fork 0
/
FrameworkDependencyResolver.cs
222 lines (190 loc) · 8.91 KB
/
FrameworkDependencyResolver.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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Repositories;
using NuGet.Versioning;
namespace Afas.BazelDotnet.Nuget
{
internal class FrameworkDependencyResolver
{
private static readonly IReadOnlyDictionary<string, string> _frameworkReferenceTargetPackMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Microsoft.NETCore.App"] = "Microsoft.NETCore.App.Ref",
["Microsoft.AspNetCore.App"] = "Microsoft.AspNetCore.App.Ref",
// ["Microsoft.WindowsDesktop.App"] = "Microsoft.WindowsDesktop.App.Ref",
// ["Microsoft.WindowsDesktop.App.WPF"] = "Microsoft.WindowsDesktop.App.Ref",
// ["Microsoft.WindowsDesktop.App.WindowsForms"] = "Microsoft.WindowsDesktop.App.Ref",
};
private readonly TransitiveDependencyResolver _dependencyResolver;
internal FrameworkDependencyResolver(TransitiveDependencyResolver dependencyResolver)
{
_dependencyResolver = dependencyResolver;
}
private static string GetFrameworkVersion(string targetFramework)
{
var match = Regex.Match(targetFramework, "(?:net|netcoreapp)([0-9]+\\.[0-9]+)");
if(!match.Success)
{
throw new Exception($"Unsupported targetFramework {targetFramework}");
}
return $"{match.Groups[1].Value}.*";
}
public async Task<(IReadOnlyCollection<NugetRepositoryEntry> entries, IReadOnlyDictionary<string, string> overrides)> ResolveFrameworkPackages(
IReadOnlyCollection<NugetRepositoryEntry> localPackages, string targetFramework)
{
var existingPackagesLookup = localPackages.ToDictionary(l => l.LocalPackageSourceInfo.Package.Id, l => l, StringComparer.OrdinalIgnoreCase);
var entries = new List<NugetRepositoryEntry>();
var overrides = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
// Resolve distinct FrameworkReferences from each nuget package
var localTargetPackages = await Task.WhenAll(
GetTargetPackages(localPackages)
.Select(p => DownloadTargetPackage(p, targetFramework))
).ConfigureAwait(false);
foreach(var targetPackage in localTargetPackages)
{
var frameworkList = GetFrameworkList(targetPackage, targetFramework);
var packageOverrides = GetPackageOverrides(targetPackage);
// Resolve overrides as specified in the TargetPackage (FrameworkReference) data/PackageOverrides.txt
foreach(var (id, packageOverride) in packageOverrides)
{
// The override does not apply since there is nothing to override
if(!existingPackagesLookup.TryGetValue(id, out var existingPackage))
{
continue;
}
var existingPackageIsEmpty = existingPackage.RefItemGroups.SingleOrDefault()?.Items.Any() != true;
if(existingPackageIsEmpty || packageOverride >= existingPackage.Version)
{
if(frameworkList.TryGetValue(id, out var frameworkItem))
{
// override to a binary from this TargetPackage (data/FrameworkList.xml)
overrides.Add(id, $"//{targetPackage.Package.Id.ToLower()}:current/{frameworkItem.file}");
}
else
{
// override to null, resulting in dropping the binary reference
overrides.Add(id, null);
}
}
}
// Keep track of a list of packages that have higher versions than the TargetPackage provided ones
// These binaries are dropped from the TargetPackage and redirected (using deps) to the nuget package
var upgrades = new HashSet<string>();
// Resolve conflicts using the assembly version
foreach(var (id, (assemblyName, assemblyVersion)) in frameworkList)
{
// No conflict
if(!existingPackagesLookup.TryGetValue(id, out var existingPackage))
{
continue;
}
// Handled by the overrides, no conflict
if(overrides.ContainsKey(id))
{
continue;
}
if(OverridesAssemblyVersion(existingPackage, assemblyVersion))
{
overrides.Add(id, $"//{targetPackage.Package.Id.ToLower()}:current/{assemblyName}");
}
else
{
upgrades.Add(id);
}
}
entries.Add(
BuildEntry(targetPackage, targetFramework, frameworkList, upgrades)
);
}
return (entries, overrides);
bool OverridesAssemblyVersion(NugetRepositoryEntry package, Version frameworkListVersion)
{
var refItems = package.RefItemGroups.Single();
if(!refItems.Items.Any())
{
return true;
}
var refAssembly = refItems.Items.Single();
var conflictingFile = $"{package.LocalPackageSourceInfo.Package.ExpandedPath}/{refAssembly}";
var conflictingVersion = FileUtilities.GetAssemblyVersion(conflictingFile);
return frameworkListVersion >= conflictingVersion;
}
}
private IEnumerable<string> GetTargetPackages(IReadOnlyCollection<NugetRepositoryEntry> localPackages)
{
// see core_sdk\core\sdk\3.1.100\Microsoft.NETCoreSdk.BundledVersions.props
var frameworkRefs = localPackages
// TODO consider target framework when multiple specified
.SelectMany(l => l.LocalPackageSourceInfo.Package.Nuspec
.GetFrameworkRefGroups()
.SelectMany(g => g.FrameworkReferences))
// Temp solution: this is not included in packages props and should be included as an additional argument instead
.Prepend(new FrameworkReference("Microsoft.AspNetCore.App"))
.Distinct()
// Implicit dependency of al netcoreapp projects
.Prepend(new FrameworkReference("Microsoft.NETCore.App"))
.ToArray();
return frameworkRefs.Where(r => _frameworkReferenceTargetPackMap.ContainsKey(r.Name)).Select(r => _frameworkReferenceTargetPackMap[r.Name]);
}
private NugetRepositoryEntry BuildEntry(
LocalPackageSourceInfo targetPackage,
string targetFramework,
Dictionary<string, (string file, Version version)> frameworkList,
HashSet<string> upgrades)
{
var nuGetFramework = NuGetFramework.Parse(targetFramework);
var entry = new NugetRepositoryEntry(targetPackage);
entry.RefItemGroups.Add(new FrameworkSpecificGroup(
nuGetFramework,
frameworkList.Where(pair => !upgrades.Contains(pair.Key)).Select(pair => pair.Value.file).ToArray()));
entry.DependencyGroups.Add(new PackageDependencyGroup(
nuGetFramework,
upgrades.Select(id => new PackageDependency(id)).ToArray()));
return entry;
}
private async Task<LocalPackageSourceInfo> DownloadTargetPackage(string targetPackId, string targetFramework)
{
var packGraph = await _dependencyResolver
.ResolveFrameworkReference(targetPackId, GetFrameworkVersion(targetFramework), targetFramework).ConfigureAwait(false);
var packNode = packGraph.Flattened.Single();
return await _dependencyResolver.DownloadPackage(packNode).ConfigureAwait(false);
}
private Dictionary<string, (string file, Version version)> GetFrameworkList(LocalPackageSourceInfo targetPackage, string targetFramework)
{
var frameworkListPath = "data/FrameworkList.xml";
if(!targetPackage.Package.Files.Contains(frameworkListPath))
{
throw new Exception($"No data/FrameworkList.xml found in {targetPackage.Package.Id}");
}
return XDocument.Load(Path.Combine(targetPackage.Package.ExpandedPath, frameworkListPath))
.Descendants("File")
.Where(f => !string.Equals(f.Attribute("Type").Value, "Analyzer", StringComparison.OrdinalIgnoreCase))
.ToDictionary(
f => f.Attribute("AssemblyName").Value,
f => (file: FixPath(f.Attribute("Path").Value), version: Version.Parse(f.Attribute("AssemblyVersion").Value)),
StringComparer.OrdinalIgnoreCase);
// Some inconsistency in FrameworkList
string FixPath(string v) => v.StartsWith("ref/") ? v : $"ref/{targetFramework}/{v}";
}
private IReadOnlyDictionary<string, NuGetVersion> GetPackageOverrides(LocalPackageSourceInfo targetPackage)
{
var packageOverridesPath = "data/PackageOverrides.txt";
if(!targetPackage.Package.Files.Contains(packageOverridesPath))
{
return new Dictionary<string, NuGetVersion>();
}
return File.ReadAllLines(Path.Combine(targetPackage.Package.ExpandedPath, packageOverridesPath))
.Select(l => l.Split("|"))
.ToDictionary(s => s[0], s => NuGetVersion.Parse(s[1]), StringComparer.OrdinalIgnoreCase);
}
public static PackageDependency ConvertToDependency(FrameworkReference framework) =>
new PackageDependency(_frameworkReferenceTargetPackMap[framework.Name]);
}
}