Skip to content

Commit ca47a5c

Browse files
committed
WIP support real RID graphs
1 parent c75bf13 commit ca47a5c

File tree

5 files changed

+84
-33
lines changed

5 files changed

+84
-33
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@
1616
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
1717
<PackageVersion Include="Microsoft.Build.Locator" Version="1.5.5" />
1818
<PackageVersion Include="Valleysoft.DockerCredsProvider" Version="2.1.0" />
19+
<PackageVersion Include="NuGet.Packaging" Version="6.3.1" />
1920
</ItemGroup>
2021
</Project>

Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
<ItemGroup>
2424
<PackageReference Include="Microsoft.Build.Utilities.Core" PrivateAssets="all" ExcludeAssets="runtime" />
25+
<PackageReference Include="Nuget.Packaging" />
2526
<PackageReference Include="Valleysoft.DockerCredsProvider" />
2627
</ItemGroup>
2728

Microsoft.NET.Build.Containers/Registry.cs

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using Microsoft.VisualBasic;
21

2+
using NuGet.Packaging;
3+
using NuGet.RuntimeModel;
34
using System.Diagnostics;
45
using System.IO.Compression;
56
using System.Net;
@@ -23,7 +24,7 @@ public record struct ManifestV2(int schemaVersion, string tag, string mediaType,
2324
// public enum GoOS { linux, windows };
2425
// not a complete list, only the subset that we support
2526
// public enum GoArch { amd64, arm , arm64, [JsonStringEnumMember("386")] x386 };
26-
public record struct PlatformInformation(string architecture, string os, string? variant, string[] features);
27+
public record struct PlatformInformation(string architecture, string os, string? variant, string[] features, [property:JsonPropertyName("os.version")][field: JsonPropertyName("os.version")] string? version);
2728
public record struct PlatformSpecificManifest(string mediaType, long size, string digest, PlatformInformation platform);
2829
public record struct ManifestListV2(int schemaVersion, string mediaType, PlatformSpecificManifest[] manifests);
2930

@@ -35,14 +36,6 @@ public record Registry(Uri BaseUri)
3536
private const string DockerContainerV1 = "application/vnd.docker.container.image.v1+json";
3637
private const int MaxChunkSizeBytes = 1024 * 64;
3738

38-
public static string[] SupportedRuntimeIdentifiers = new [] {
39-
"linux-x86",
40-
"linux-x64",
41-
"linux-arm",
42-
"linux-arm64",
43-
"win-x64"
44-
};
45-
4639
private string RegistryName { get; } = BaseUri.Host;
4740

4841
public async Task<Image?> GetImageManifest(string name, string reference, string runtimeIdentifier)
@@ -83,29 +76,83 @@ async Task<HttpResponseMessage> GetBlob(string digest) {
8376
}
8477

8578
async Task<Image?> TryPickBestImageFromManifestList(ManifestListV2 manifestList, string runtimeIdentifier) {
86-
// TODO: we probably need to pull in actual RID parsing code and look for 'platform' here.
87-
// 'win' can take a version number and we'd break.
88-
// Also, there are more specific linux RIDs (like rhel) that we should instead be looking for the correct 'family' for?
89-
// we probably also need to look at the 'variant' field if the RID contains a version.
90-
(string os, string arch, string? variant) = runtimeIdentifier.Split('-') switch {
91-
["linux", "x64"] => ("linux", "amd64", null),
92-
["linux", "x86"] => ("linux", "386", null),
93-
["linux", "arm"] => ("linux", "arm", "v7"),
94-
["linux", "arm64"] => ("linux", "arm64", "v8"),
95-
["win", "x64"] => ("windows", "amd64", null),
96-
var parts => throw new ArgumentException($"The runtimeIdentifier '{runtimeIdentifier}' is not supported. The supported RuntimeIdentifiers are {Registry.SupportedRuntimeIdentifiers}.")
97-
};
98-
99-
var potentialManifest = manifestList.manifests.SingleOrDefault(manifest => manifest.platform.os == os && manifest.platform.architecture == arch && manifest.platform.variant == variant);
100-
if (potentialManifest != default) {
101-
var manifestResponse = await GetManifest(potentialManifest.digest);
102-
return await TryReadSingleImage(await manifestResponse.Content.ReadFromJsonAsync<ManifestV2>());
103-
} else {
104-
return null;
79+
var runtimeGraph = GetRuntimeGraphForDotNet();
80+
var compatibleRuntimesForUserRid = runtimeGraph.ExpandRuntime(runtimeIdentifier);
81+
var (ridDict, graphForManifestList) = ConstructRuntimeGraphForManifestList(manifestList, runtimeGraph);
82+
var bestManifestRid = PickBestRidFromCompatibleUserRids(graphForManifestList, compatibleRuntimesForUserRid);
83+
if (bestManifestRid is null) {
84+
throw new ArgumentException($"The runtimeIdentifier '{runtimeIdentifier}' is not supported. The supported RuntimeIdentifiers for the base image {name}:{reference} are {String.Join(",", compatibleRuntimesForUserRid)}");
10585
}
86+
var matchingManifest = ridDict[bestManifestRid];
87+
var manifestResponse = await GetManifest(matchingManifest.digest);
88+
return await TryReadSingleImage(await manifestResponse.Content.ReadFromJsonAsync<ManifestV2>());
10689
}
10790
}
10891

92+
private string? PickBestRidFromCompatibleUserRids(RuntimeGraph graphForManifestList, IEnumerable<string> compatibleRuntimesForUserRid)
93+
{
94+
return compatibleRuntimesForUserRid.First(rid => graphForManifestList.Runtimes.ContainsKey(rid));
95+
}
96+
97+
private (IReadOnlyDictionary<string, PlatformSpecificManifest>, RuntimeGraph) ConstructRuntimeGraphForManifestList(ManifestListV2 manifestList, RuntimeGraph dotnetRuntimeGraph)
98+
{
99+
var ridDict = new Dictionary<string, PlatformSpecificManifest>();
100+
var runtimeDescriptionSet = new HashSet<RuntimeDescription>();
101+
foreach (var manifest in manifestList.manifests) {
102+
if (CreateRidForPlatform(manifest.platform) is { } rid)
103+
{
104+
if (ridDict.TryAdd(rid, manifest))
105+
{
106+
AddRidAndDescendentsToSet(runtimeDescriptionSet, rid, dotnetRuntimeGraph);
107+
}
108+
}
109+
}
110+
111+
// todo: inheritance relationships for these RIDs?
112+
var graph = new RuntimeGraph(runtimeDescriptionSet);
113+
return (ridDict, graph);
114+
}
115+
116+
private void AddRidAndDescendentsToSet(HashSet<RuntimeDescription> runtimeDescriptionSet, string rid, RuntimeGraph dotnetRuntimeGraph)
117+
{
118+
var R = dotnetRuntimeGraph.Runtimes[rid];
119+
runtimeDescriptionSet.Add(R);
120+
foreach (var r in R.InheritedRuntimes) AddRidAndDescendentsToSet(runtimeDescriptionSet, r, dotnetRuntimeGraph);
121+
}
122+
123+
private string? CreateRidForPlatform(PlatformInformation platform)
124+
{
125+
var osPart = platform.os switch
126+
{
127+
"linux" => "linux",
128+
"windows" => "win",
129+
_ => null
130+
};
131+
// TODO: this part needs a lot of work, the RID graph isn't super precise here and version numbers (especially on windows) are _whack_
132+
var versionPart = platform.version?.Split('.') switch
133+
{
134+
[var major, .. ] => major,
135+
_ => null
136+
};
137+
var platformPart = platform.architecture switch
138+
{
139+
"amd64" => "x64",
140+
"x386" => "x86",
141+
"arm" => $"arm{(platform.variant != "v7" ? platform.variant : "")}",
142+
"arm64" => "arm64"
143+
};
144+
145+
146+
if (osPart is null || platformPart is null) return null;
147+
return $"{osPart}{versionPart ?? ""}-{platformPart}";
148+
}
149+
150+
private RuntimeGraph GetRuntimeGraphForDotNet()
151+
{
152+
var runtimePath = Path.Combine("C:", "Program Files","dotnet", "sdk", "7.0.100", "RuntimeIdentifierGraph.json"); // how to get this at runtime?
153+
return JsonRuntimeFormat.ReadRuntimeGraph(runtimePath);
154+
}
155+
109156
/// <summary>
110157
/// Ensure a blob associated with <paramref name="name"/> from the registry is available locally.
111158
/// </summary>

Test.Microsoft.NET.Build.Containers.Filesystem/DockerRegistryManager.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public static void StartAndPopulateDockerRegistry(TestContext context)
5454

5555
s_registryContainerId = registryContainerId;
5656

57-
foreach (var tag in new [] { Net6ImageTag, Net7ImageTag}) {
57+
foreach (var tag in new[] { Net6ImageTag, Net7ImageTag })
58+
{
5859
Exec("docker", $"pull {BaseImageSource}{BaseImage}:{tag}");
5960
Exec("docker", $"tag {BaseImageSource}{BaseImage}:{tag} {LocalRegistry}/{BaseImage}:{tag}");
6061
Exec("docker", $"push {LocalRegistry}/{BaseImage}:{tag}");

Test.Microsoft.NET.Build.Containers.Filesystem/EndToEnd.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,12 @@ public async Task EndToEnd_NoAPI()
276276
privateNuGetAssets.Delete(true);
277277
}
278278

279-
[DataRow("linux-x86", false, "/app")] // packaging framework-dependent because missing runtime packs for x86 linux.
280-
[DataRow("linux-x64", true, "/app")]
281279
[DataRow("linux-arm", false, "/app")] // packaging framework-dependent because emulating arm on x64 Docker host doesn't work
280+
//[DataRow("linux-x86", false, "/app")] // packaging framework-dependent because missing runtime packs for x86 linux. // MS doesn't ship a linux-x86 image
282281
[DataRow("linux-arm64", false, "/app")] // packaging framework-dependent because emulating arm64 on x64 Docker host doesn't work
283282
[DataRow("win-x64", true, "C:\\app")]
283+
[DataRow("linux-x64", true, "/app")]
284+
[DataRow("debian.11-x64", true, "/app")] // user-reported RID. proves dependency relationships
284285
[TestMethod]
285286
public async Task CanPackageForAllSupportedContainerRIDs(string rid, bool isRIDSpecific, string workingDir) {
286287
if (rid == "win-x64") {
@@ -290,7 +291,7 @@ public async Task CanPackageForAllSupportedContainerRIDs(string rid, bool isRIDS
290291
string publishDirectory = await BuildLocalApp(tfm : "net7.0", rid : (isRIDSpecific ? rid : null));
291292

292293
// Build the image
293-
Registry registry = new Registry(ContainerHelpers.TryExpandRegistryToUri(DockerRegistryManager.LocalRegistry));
294+
Registry registry = new Registry(ContainerHelpers.TryExpandRegistryToUri(DockerRegistryManager.BaseImageSource));
294295

295296
Image x = await registry.GetImageManifest(DockerRegistryManager.BaseImage, DockerRegistryManager.Net7ImageTag, rid);
296297

0 commit comments

Comments
 (0)