From b4b993d0f3e7a992ca136fc6d224016d978104dd Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:57:29 +0200 Subject: [PATCH 1/2] resolver: preserve OS version in worker platforms Use the full platform string when deduplicating platforms reported by BuildKit workers so Windows platforms that differ only by OS version remain distinct during node resolution. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- build/resolver/driver.go | 13 ++++-------- util/platformutil/parse.go | 2 +- util/platformutil/parse_test.go | 37 +++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 util/platformutil/parse_test.go diff --git a/build/resolver/driver.go b/build/resolver/driver.go index 671b14c5bfe9..7044713b528e 100644 --- a/build/resolver/driver.go +++ b/build/resolver/driver.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/platforms" "github.com/docker/buildx/builder" "github.com/docker/buildx/driver" + "github.com/docker/buildx/util/platformutil" "github.com/docker/buildx/util/progress" "github.com/moby/buildkit/client" gateway "github.com/moby/buildkit/frontend/gateway/client" @@ -147,17 +148,11 @@ func (r *nodeResolver) Resolve(ctx context.Context, optPlatforms map[string][]oc if err != nil { return errors.Wrap(err, "listing workers") } - - ps := make(map[string]ocispecs.Platform, len(ww)) + var ps []ocispecs.Platform for _, w := range ww { - for _, p := range w.Platforms { - pk := platforms.Format(platforms.Normalize(p)) - ps[pk] = p - } - } - for _, p := range ps { - workers[i] = append(workers[i], p) + ps = append(ps, w.Platforms...) } + workers[i] = platformutil.Dedupe(ps) return nil }) } diff --git a/util/platformutil/parse.go b/util/platformutil/parse.go index b95855ffab6a..dfc2721467b3 100644 --- a/util/platformutil/parse.go +++ b/util/platformutil/parse.go @@ -43,7 +43,7 @@ func Dedupe(in []ocispecs.Platform) []ocispecs.Platform { out := make([]ocispecs.Platform, 0, len(in)) for _, p := range in { p := platforms.Normalize(p) - key := platforms.Format(p) + key := platforms.FormatAll(p) if _, ok := m[key]; ok { continue } diff --git a/util/platformutil/parse_test.go b/util/platformutil/parse_test.go new file mode 100644 index 000000000000..0b05bd11452b --- /dev/null +++ b/util/platformutil/parse_test.go @@ -0,0 +1,37 @@ +package platformutil + +import ( + "testing" + + "github.com/containerd/platforms" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/require" +) + +func TestDedupePreservesOSVersionAndFeatures(t *testing.T) { + t.Parallel() + + got := Dedupe([]ocispecs.Platform{ + platforms.MustParse("windows(10.0.17763)/amd64"), + platforms.MustParse("windows(10.0.20348)/amd64"), + platforms.MustParse("windows(10.0.20348+win32k)/amd64"), + platforms.MustParse("windows(10.0.17763)/amd64"), + platforms.MustParse("linux/x86_64"), + platforms.MustParse("linux/amd64"), + }) + + require.Equal(t, []string{ + "windows(10.0.17763)/amd64", + "windows(10.0.20348)/amd64", + "windows(10.0.20348+win32k)/amd64", + "linux/amd64", + }, formatAll(got)) +} + +func formatAll(pp []ocispecs.Platform) []string { + out := make([]string, 0, len(pp)) + for _, p := range pp { + out = append(out, platforms.FormatAll(p)) + } + return out +} From 8e507756dbd3a7b7a10dfaef511f8e82a649a9fd Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:20:02 +0200 Subject: [PATCH 2/2] platforms: preserve OS version in store and display Use the full normalized platform string when filtering node platforms so append operations keep Windows variants that differ by OS version or feature. Use the same full platform formatting for grouped platform output so buildx ls and inspect report the distinct entries. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- store/nodegroup.go | 4 ++-- store/nodegroup_test.go | 24 ++++++++++++++++++++++++ util/platformutil/parse.go | 8 +++----- util/platformutil/parse_test.go | 24 ++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/store/nodegroup.go b/store/nodegroup.go index 8c78acc1c94c..d19a30161234 100644 --- a/store/nodegroup.go +++ b/store/nodegroup.go @@ -176,7 +176,7 @@ func (ng *NodeGroup) validateDuplicates(ep string, idx int) error { m := map[string]struct{}{} for _, p := range ng.Nodes[idx].Platforms { - m[platforms.Format(p)] = struct{}{} + m[platforms.FormatAll(platforms.Normalize(p))] = struct{}{} } for i := range ng.Nodes { @@ -213,7 +213,7 @@ func (ng *NodeGroup) nextNodeName() string { func filterPlatforms(in []ocispecs.Platform, m map[string]struct{}) []ocispecs.Platform { out := make([]ocispecs.Platform, 0, len(in)) for _, p := range in { - if _, ok := m[platforms.Format(p)]; !ok { + if _, ok := m[platforms.FormatAll(platforms.Normalize(p))]; !ok { out = append(out, p) } } diff --git a/store/nodegroup_test.go b/store/nodegroup_test.go index 5a55703bafa7..ece584c21e92 100644 --- a/store/nodegroup_test.go +++ b/store/nodegroup_test.go @@ -3,7 +3,9 @@ package store import ( "testing" + "github.com/containerd/platforms" "github.com/docker/buildx/util/platformutil" + ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" ) @@ -42,3 +44,25 @@ func TestNodeGroupUpdate(t *testing.T) { require.Equal(t, 1, len(ng.Nodes)) require.Equal(t, []string{"linux/arm64"}, platformutil.Format(ng.Nodes[0].Platforms)) } + +func TestNodeGroupUpdateFiltersOnlyMatchingPlatforms(t *testing.T) { + t.Parallel() + + ng := &NodeGroup{} + err := ng.Update("n1", "ctx-a", []string{"windows(10.0.17763)/amd64", "linux/amd64"}, true, false, nil, "", nil) + require.NoError(t, err) + + err = ng.Update("n2", "ctx-b", []string{"windows(10.0.20348)/amd64", "linux/x86_64"}, true, true, nil, "", nil) + require.NoError(t, err) + + require.Equal(t, []string{"windows(10.0.17763)/amd64"}, formatAll(ng.Nodes[0].Platforms)) + require.Equal(t, []string{"windows(10.0.20348)/amd64", "linux/amd64"}, formatAll(ng.Nodes[1].Platforms)) +} + +func formatAll(pp []ocispecs.Platform) []string { + out := make([]string, 0, len(pp)) + for _, p := range pp { + out = append(out, platforms.FormatAll(p)) + } + return out +} diff --git a/util/platformutil/parse.go b/util/platformutil/parse.go index dfc2721467b3..e8dfeee8cfbd 100644 --- a/util/platformutil/parse.go +++ b/util/platformutil/parse.go @@ -58,13 +58,11 @@ func FormatInGroups(gg ...[]ocispecs.Platform) []string { out := make([]string, 0, len(gg)) for i, g := range gg { for _, p := range g { - p := platforms.Normalize(p) - key := platforms.Format(p) - if _, ok := m[key]; ok { + v := platforms.FormatAll(platforms.Normalize(p)) + if _, ok := m[v]; ok { continue } - m[key] = struct{}{} - v := platforms.Format(p) + m[v] = struct{}{} if i == 0 { v += "*" } diff --git a/util/platformutil/parse_test.go b/util/platformutil/parse_test.go index 0b05bd11452b..1be4b6787c10 100644 --- a/util/platformutil/parse_test.go +++ b/util/platformutil/parse_test.go @@ -28,6 +28,30 @@ func TestDedupePreservesOSVersionAndFeatures(t *testing.T) { }, formatAll(got)) } +func TestFormatInGroupsPreservesOSVersionAndFeatures(t *testing.T) { + t.Parallel() + + got := FormatInGroups( + []ocispecs.Platform{ + platforms.MustParse("windows(10.0.17763)/amd64"), + platforms.MustParse("windows(10.0.20348)/amd64"), + }, + []ocispecs.Platform{ + platforms.MustParse("windows(10.0.20348)/amd64"), + platforms.MustParse("windows(10.0.20348+win32k)/amd64"), + platforms.MustParse("linux/x86_64"), + platforms.MustParse("linux/amd64"), + }, + ) + + require.Equal(t, []string{ + "windows(10.0.17763)/amd64*", + "windows(10.0.20348)/amd64*", + "windows(10.0.20348+win32k)/amd64", + "linux/amd64", + }, got) +} + func formatAll(pp []ocispecs.Platform) []string { out := make([]string, 0, len(pp)) for _, p := range pp {