forked from dotnet/sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ResolveRuntimePackAssets.cs
275 lines (233 loc) · 14.7 KB
/
ResolveRuntimePackAssets.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.NET.Build.Tasks
{
public class ResolveRuntimePackAssets : TaskBase
{
public ITaskItem[] ResolvedRuntimePacks { get; set; }
public ITaskItem[] FrameworkReferences { get; set; } = Array.Empty<ITaskItem>();
public ITaskItem[] UnavailableRuntimePacks { get; set; } = Array.Empty<ITaskItem>();
public ITaskItem[] SatelliteResourceLanguages { get; set; } = Array.Empty<ITaskItem>();
public ITaskItem[] RuntimeFrameworks { get; set; }
public bool DesignTimeBuild { get; set; }
public bool DisableTransitiveFrameworkReferenceDownloads { get; set; }
[Output]
public ITaskItem[] RuntimePackAssets { get; set; }
protected override void ExecuteCore()
{
var runtimePackAssets = new List<ITaskItem>();
// Find any RuntimeFrameworks that matches with FrameworkReferences, so that we can apply that RuntimeFrameworks profile to the corresponding RuntimePack.
// This is done in 2 parts, First part (see comments for 2nd part further below), we match the RuntimeFramework with the FrameworkReference by using the following metadata.
// RuntimeFrameworks.GetMetadata("FrameworkName")==FrameworkReferences.ItemSpec
// For example, A WinForms app that uses useWindowsForms (and useWPF will be set to false) has the following values that will result in a match of the below RuntimeFramework.
// FrameworkReferences with an ItemSpec "Microsoft.WindowsDesktop.App.WindowsForms" will match with
// RuntimeFramework with an ItemSpec => "Microsoft.WindowsDesktop.App", GetMetadata("FrameworkName") => "Microsoft.WindowsDesktop.App.WindowsForms", GetMetadata("Profile") => "WindowsForms"
List<ITaskItem> matchingRuntimeFrameworks = RuntimeFrameworks != null ? FrameworkReferences
.SelectMany(fxReference => RuntimeFrameworks.Where(rtFx =>
fxReference.ItemSpec.Equals(rtFx.GetMetadata(MetadataKeys.FrameworkName), StringComparison.OrdinalIgnoreCase)))
.ToList() : null;
HashSet<string> frameworkReferenceNames = new(FrameworkReferences.Select(item => item.ItemSpec), StringComparer.OrdinalIgnoreCase);
foreach (var unavailableRuntimePack in UnavailableRuntimePacks)
{
if (frameworkReferenceNames.Contains(unavailableRuntimePack.ItemSpec))
{
// This is a runtime pack that should be used, but wasn't available for the specified RuntimeIdentifier
// NETSDK1082: There was no runtime pack for {0} available for the specified RuntimeIdentifier '{1}'.
Log.LogError(Strings.NoRuntimePackAvailable, unavailableRuntimePack.ItemSpec,
unavailableRuntimePack.GetMetadata(MetadataKeys.RuntimeIdentifier));
}
}
HashSet<string> processedRuntimePackRoots = new(StringComparer.OrdinalIgnoreCase);
foreach (var runtimePack in ResolvedRuntimePacks)
{
if (!frameworkReferenceNames.Contains(runtimePack.GetMetadata(MetadataKeys.FrameworkName)))
{
var additionalFrameworkReferences = runtimePack.GetMetadata(MetadataKeys.AdditionalFrameworkReferences);
if (additionalFrameworkReferences == null ||
!additionalFrameworkReferences.Split(';').Any(afr => frameworkReferenceNames.Contains(afr)))
{
// This is a runtime pack for a shared framework that ultimately wasn't referenced, so don't include its assets
continue;
}
}
// For any RuntimeFrameworks that matches with FrameworkReferences, we can apply that RuntimeFrameworks profile to the corresponding RuntimePack.
// This is done in 2 parts, second part (see comments for 1st part above), Matches the RuntimeFramework with the ResolvedRuntimePacks by comparing the following metadata.
// RuntimeFrameworks.ItemSpec == ResolvedRuntimePacks.GetMetadata("FrameworkName")
// For example, A WinForms app that uses useWindowsForms (and useWPF will be set to false) has the following values that will result in a match of the below RuntimeFramework
// matchingRTReference.GetMetadata("Profile") will be "WindowsForms". 'Profile' will be an empty string if no matching RuntimeFramework is found
HashSet<string> profiles = matchingRuntimeFrameworks?
.Where(matchingRTReference => runtimePack.GetMetadata("FrameworkName").Equals(matchingRTReference.ItemSpec))
.Select(matchingRTReference => matchingRTReference.GetMetadata("Profile")).ToHashSet() ?? [];
// Special case the Windows SDK projections. Normally the Profile information flows through the RuntimeFramework items,
// but those aren't created for RuntimePackAlwaysCopyLocal references. This logic could be revisited later to be generalized in some way.
if (runtimePack.GetMetadata(MetadataKeys.FrameworkName) == "Microsoft.Windows.SDK.NET.Ref")
{
if (FrameworkReferences?.Any(fxReference => fxReference.ItemSpec == "Microsoft.Windows.SDK.NET.Ref.Windows") == true)
{
profiles.Add("Windows");
}
if (FrameworkReferences?.Any(fxReference => fxReference.ItemSpec == "Microsoft.Windows.SDK.NET.Ref.Xaml") == true)
{
profiles.Add("Xaml");
}
}
// If we have a runtime framework with an empty profile, it means that we should use all of the contents of the runtime pack,
// so we can clear the profile list
if (profiles.Contains(string.Empty))
{
profiles.Clear();
}
string runtimePackRoot = runtimePack.GetMetadata(MetadataKeys.PackageDirectory);
if (string.IsNullOrEmpty(runtimePackRoot) || !Directory.Exists(runtimePackRoot))
{
if (!DesignTimeBuild)
{
// Don't treat this as an error if we are doing a design-time build. This is because the design-time
// build needs to succeed in order to get the right information in order to run a restore to download
// the runtime pack.
if (DisableTransitiveFrameworkReferenceDownloads)
{
Log.LogError(Strings.RuntimePackNotRestored_TransitiveDisabled, runtimePack.ItemSpec);
}
else
{
Log.LogError(Strings.RuntimePackNotDownloaded, runtimePack.ItemSpec,
runtimePack.GetMetadata(MetadataKeys.RuntimeIdentifier));
}
}
continue;
}
if (!processedRuntimePackRoots.Add(runtimePackRoot))
{
// We already added assets from this runtime pack (which can happen with FrameworkReferences to different
// profiles of the same shared framework)
continue;
}
var runtimeListPath = Path.Combine(runtimePackRoot, "data", "RuntimeList.xml");
if (File.Exists(runtimeListPath))
{
var runtimePackAlwaysCopyLocal = runtimePack.HasMetadataValue(MetadataKeys.RuntimePackAlwaysCopyLocal, "true");
AddRuntimePackAssetsFromManifest(runtimePackAssets, runtimePackRoot, runtimeListPath, runtimePack, runtimePackAlwaysCopyLocal, profiles);
}
else
{
throw new BuildErrorException(string.Format(Strings.RuntimeListNotFound, runtimeListPath));
}
}
RuntimePackAssets = runtimePackAssets.ToArray();
}
private void AddRuntimePackAssetsFromManifest(List<ITaskItem> runtimePackAssets, string runtimePackRoot,
string runtimeListPath, ITaskItem runtimePack, bool runtimePackAlwaysCopyLocal, HashSet<string> profiles)
{
var assetSubPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
XDocument frameworkListDoc = XDocument.Load(runtimeListPath);
// profile feature is only supported in net9.0 and later. We would ignore it for previous versions.
bool profileSupported = false;
string targetFrameworkVersion = frameworkListDoc.Root.Attribute("TargetFrameworkVersion")?.Value;
if (!string.IsNullOrEmpty(targetFrameworkVersion))
{
string[] parts = targetFrameworkVersion.Split('.');
if (parts.Length > 0 && int.TryParse(parts[0], out int versionNumber))
{
// The Windows SDK projections use profiles and need to be supported on .NET 8 as well.
// No other packages are supported using profiles below .NET 9, so we can special case.
if (versionNumber >= 9 ||
(versionNumber >= 8 && frameworkListDoc.Root.Attribute("FrameworkName")?.Value == "Microsoft.Windows.SDK.NET.Ref"))
{
profileSupported = true;
}
}
}
foreach (var fileElement in frameworkListDoc.Root.Elements("File"))
{
if (profileSupported && profiles.Count != 0)
{
var profileAttributeValue = fileElement.Attribute("Profile")?.Value;
var assemblyProfiles = profileAttributeValue?.Split(';');
if (profileAttributeValue == null || !assemblyProfiles.Any(p => profiles.Contains(p)))
{
// Assembly wasn't in the profile specified, so don't reference it
continue;
}
}
// Call GetFullPath to normalize slashes
string assetPath = Path.GetFullPath(Path.Combine(runtimePackRoot, fileElement.Attribute("Path").Value));
string typeAttributeValue = fileElement.Attribute("Type").Value;
string assetType;
string culture = null;
if (typeAttributeValue.Equals("Managed", StringComparison.OrdinalIgnoreCase))
{
assetType = "runtime";
}
else if (typeAttributeValue.Equals("Native", StringComparison.OrdinalIgnoreCase))
{
assetType = "native";
}
else if (typeAttributeValue.Equals("PgoData", StringComparison.OrdinalIgnoreCase))
{
assetType = "pgodata";
}
else if (typeAttributeValue.Equals("Resources", StringComparison.OrdinalIgnoreCase))
{
assetType = "resources";
culture = fileElement.Attribute("Culture")?.Value;
if (culture == null)
{
throw new BuildErrorException($"Culture not set in runtime manifest for {assetPath}");
}
if (SatelliteResourceLanguages.Length >= 1 &&
!SatelliteResourceLanguages.Any(lang => string.Equals(lang.ItemSpec, culture, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
}
else
{
throw new BuildErrorException($"Unrecognized file type '{typeAttributeValue}' in {runtimeListPath}");
}
var assetItem = CreateAssetItem(assetPath, assetType, runtimePack, culture);
// Ensure the asset item's destination sub-path is unique
var assetSubPath = assetItem.GetMetadata(MetadataKeys.DestinationSubPath);
if (!assetSubPaths.Add(assetSubPath))
{
Log.LogError(Strings.DuplicateRuntimePackAsset, assetSubPath);
continue;
}
assetItem.SetMetadata("AssemblyVersion", fileElement.Attribute("AssemblyVersion")?.Value);
assetItem.SetMetadata("FileVersion", fileElement.Attribute("FileVersion")?.Value);
assetItem.SetMetadata("PublicKeyToken", fileElement.Attribute("PublicKeyToken")?.Value);
assetItem.SetMetadata("DropFromSingleFile", fileElement.Attribute("DropFromSingleFile")?.Value);
if (runtimePackAlwaysCopyLocal)
{
assetItem.SetMetadata(MetadataKeys.RuntimePackAlwaysCopyLocal, "true");
}
runtimePackAssets.Add(assetItem);
}
}
private static TaskItem CreateAssetItem(string assetPath, string assetType, ITaskItem runtimePack, string culture)
{
string runtimeIdentifier = runtimePack.GetMetadata(MetadataKeys.RuntimeIdentifier);
var assetItem = new TaskItem(assetPath);
if (assetType != "pgodata")
assetItem.SetMetadata(MetadataKeys.CopyLocal, "true");
if (string.IsNullOrEmpty(culture))
{
assetItem.SetMetadata(MetadataKeys.DestinationSubPath, Path.GetFileName(assetPath));
}
else
{
assetItem.SetMetadata(MetadataKeys.DestinationSubDirectory, culture + Path.DirectorySeparatorChar);
assetItem.SetMetadata(MetadataKeys.DestinationSubPath, Path.Combine(culture, Path.GetFileName(assetPath)));
assetItem.SetMetadata(MetadataKeys.Culture, culture);
}
assetItem.SetMetadata(MetadataKeys.AssetType, assetType);
assetItem.SetMetadata(MetadataKeys.NuGetPackageId, runtimePack.GetMetadata(MetadataKeys.NuGetPackageId));
assetItem.SetMetadata(MetadataKeys.NuGetPackageVersion, runtimePack.GetMetadata(MetadataKeys.NuGetPackageVersion));
assetItem.SetMetadata(MetadataKeys.RuntimeIdentifier, runtimeIdentifier);
assetItem.SetMetadata(MetadataKeys.IsTrimmable, runtimePack.GetMetadata(MetadataKeys.IsTrimmable));
return assetItem;
}
}
}