From 7eb0c7df41ee6547fbb3b5695e14589927c7d777 Mon Sep 17 00:00:00 2001 From: IG Date: Sun, 28 Jun 2026 03:02:42 +0400 Subject: [PATCH] fix(oci): render arm64 platform description without redundant v8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Platform.description` rendered the same arm64 platform two different ways depending on how the value was constructed: `linux/arm64` when the variant was `nil`, and `linux/arm64/v8` when the variant was set to `"v8"`. These are the same platform — `==`, `hash`, and Set membership already treat an arm64 `nil` variant as equivalent to `"v8"` — so two equal values produced different descriptions and drifted between `arm64` and `arm64/v8` across stages of a single build (apple/container#1542). Omit the redundant `v8` variant for arm64 so equal platforms always describe as `linux/arm64`, matching how Docker and containerd display it. Only the rendered description changes; the stored variant and Codable encoding are untouched, so OCI content digests remain stable. --- Sources/ContainerizationOCI/Platform.swift | 16 +++++++-- .../OCIPlatformTests.swift | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Sources/ContainerizationOCI/Platform.swift b/Sources/ContainerizationOCI/Platform.swift index 7455e011..63edf598 100644 --- a/Sources/ContainerizationOCI/Platform.swift +++ b/Sources/ContainerizationOCI/Platform.swift @@ -47,15 +47,27 @@ public struct Platform: Sendable, Equatable { return .init(arch: normalized.arch, os: "linux", variant: normalized.variant) } - /// The computed description, for example, `linux/arm64/v8`. + /// The computed description, for example, `linux/amd64` or `linux/arm/v7`. + /// + /// `arm64`'s only defined variant is `v8`, which `==` and `hash` already treat as + /// equivalent to a `nil` variant. The redundant `v8` is therefore omitted so that + /// two equal arm64 platforms (one with `variant == nil`, one with `"v8"`) describe + /// identically as `linux/arm64`, rather than drifting between `arm64` and + /// `arm64/v8`. See apple/container#1542. public var description: String { let architecture = architecture - if let variant = variant { + if let variant, !Self.isRedundantVariant(variant, for: architecture) { return "\(os)/\(architecture)/\(variant)" } return "\(os)/\(architecture)" } + /// Whether `variant` is the canonical default for `architecture` and can be omitted + /// from the rendered description without losing information. + private static func isRedundantVariant(_ variant: String, for architecture: String) -> Bool { + architecture == "arm64" && variant == "v8" + } + /// The CPU architecture, for example, `amd64` or `arm64`. public var architecture: String { Self.normalizeArch(_rawArch).arch diff --git a/Tests/ContainerizationOCITests/OCIPlatformTests.swift b/Tests/ContainerizationOCITests/OCIPlatformTests.swift index 7ffebedc..5a9c5aa7 100644 --- a/Tests/ContainerizationOCITests/OCIPlatformTests.swift +++ b/Tests/ContainerizationOCITests/OCIPlatformTests.swift @@ -81,4 +81,40 @@ struct OCIPlatformTests { set.insert(withoutVariant) #expect(set.contains(withV8), "arm64/v8 must be found in a Set that contains arm64 with nil variant") } + + // MARK: - description consistency (issue apple/container#1542) + + @Test func arm64_nilAndV8_sameDescription() { + let withoutVariant = Platform(arch: "arm64", os: "linux", variant: nil) + let withV8 = Platform(arch: "arm64", os: "linux", variant: "v8") + #expect( + withoutVariant.description == withV8.description, + "equal arm64 platforms must produce the same description" + ) + } + + @Test func arm64_descriptionDropsRedundantV8() { + let withV8 = Platform(arch: "arm64", os: "linux", variant: "v8") + #expect(withV8.description == "linux/arm64", "arm64/v8 is canonical arm64, rendered without the redundant variant") + } + + @Test func arm64_nilVariantDescription() { + let withoutVariant = Platform(arch: "arm64", os: "linux", variant: nil) + #expect(withoutVariant.description == "linux/arm64") + } + + @Test func arm64_fromStringWithV8DescriptionIsCanonical() throws { + let parsed = try Platform(from: "linux/arm64/v8") + #expect(parsed.description == "linux/arm64", "parsing arm64/v8 then describing must yield the canonical short form") + } + + @Test func arm_v7_descriptionKeepsVariant() { + let armv7 = Platform(arch: "arm", os: "linux", variant: "v7") + #expect(armv7.description == "linux/arm/v7", "non-redundant variants such as arm/v7 must be preserved") + } + + @Test func amd64_descriptionUnaffected() { + let amd64 = Platform(arch: "amd64", os: "linux") + #expect(amd64.description == "linux/amd64") + } }