From f439dfab3c353b315e45b9d98d695e3bb9b9b304 Mon Sep 17 00:00:00 2001 From: Zhongcheng Lao Date: Thu, 29 Feb 2024 18:55:50 +0800 Subject: [PATCH] Add AMD64 micro architecture level support This commit adds the support for AMD64 micro architecture levels on Linux. Signed-off-by: Zhongcheng Lao --- cpuinfo.go | 31 ++++++++++++++++++++++++++++--- cpuinfo_linux.go | 4 ++-- cpuinfo_linux_test.go | 2 +- cpuinfo_other.go | 4 ++-- database.go | 7 +++++++ defaults.go | 10 ++++++++++ defaults_darwin.go | 19 +++++++++++++++++++ defaults_freebsd.go | 20 ++++++++++++++++++++ defaults_unix.go | 15 +++++++++++++++ defaults_unix_test.go | 17 +++++++++++++++++ defaults_windows.go | 17 +++++++++++++++++ defaults_windows_test.go | 19 +++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ platforms_test.go | 9 +++++++++ 15 files changed, 169 insertions(+), 8 deletions(-) diff --git a/cpuinfo.go b/cpuinfo.go index 91f50e8..c0237cc 100644 --- a/cpuinfo.go +++ b/cpuinfo.go @@ -21,9 +21,11 @@ import ( "sync" "github.com/containerd/log" + amd64variant "github.com/tonistiigi/go-archvariant" ) -// Present the ARM instruction set architecture, eg: v7, v8 +// Present the instruction set architecture, eg: v7, v8 for ARM CPU, +// v3, v4 for AMD64 CPU. // Don't use this value directly; call cpuVariant() instead. var cpuVariantValue string @@ -33,11 +35,34 @@ func cpuVariant() string { cpuVariantOnce.Do(func() { if isArmArch(runtime.GOARCH) { var err error - cpuVariantValue, err = getCPUVariant() + cpuVariantValue, err = getArmCPUVariant() if err != nil { - log.L.Errorf("Error getCPUVariant for OS %s: %v", runtime.GOOS, err) + log.L.Errorf("Error getArmCPUVariant for OS %s: %v", runtime.GOOS, err) } } }) return cpuVariantValue } + +func cpuVariantMaximum() string { + cpuVariantOnce.Do(func() { + if isArmArch(runtime.GOARCH) { + var err error + cpuVariantValue, err = getArmCPUVariant() + if err != nil { + log.L.Errorf("Error getArmCPUVariant for OS %s: %v", runtime.GOOS, err) + } + } else if isAmd64Arch(runtime.GOARCH) { + var err error + cpuVariantValue, err = getAmd64MicroArchLevel() + if err != nil { + log.L.Errorf("Error getAmd64MicroArchLevel for OS %s: %v", runtime.GOOS, err) + } + } + }) + return cpuVariantValue +} + +func getAmd64MicroArchLevel() (string, error) { + return amd64variant.AMD64Variant(), nil +} diff --git a/cpuinfo_linux.go b/cpuinfo_linux.go index 98c7001..1a6b36b 100644 --- a/cpuinfo_linux.go +++ b/cpuinfo_linux.go @@ -106,12 +106,12 @@ func getCPUVariantFromArch(arch string) (string, error) { return variant, nil } -// getCPUVariant returns cpu variant for ARM +// getArmCPUVariant returns cpu variant for ARM // We first try reading "Cpu architecture" field from /proc/cpuinfo // If we can't find it, then fall back using a system call // This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo // was not present. -func getCPUVariant() (string, error) { +func getArmCPUVariant() (string, error) { variant, err := getCPUInfo("Cpu architecture") if err != nil { if errors.Is(err, errNotFound) { diff --git a/cpuinfo_linux_test.go b/cpuinfo_linux_test.go index ecb1150..290f1c0 100644 --- a/cpuinfo_linux_test.go +++ b/cpuinfo_linux_test.go @@ -29,7 +29,7 @@ func TestCPUVariant(t *testing.T) { variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"} - p, err := getCPUVariant() + p, err := getArmCPUVariant() if err != nil { t.Fatalf("Error getting CPU variant: %v", err) return diff --git a/cpuinfo_other.go b/cpuinfo_other.go index 97a1fe8..e3705d2 100644 --- a/cpuinfo_other.go +++ b/cpuinfo_other.go @@ -23,7 +23,7 @@ import ( "runtime" ) -func getCPUVariant() (string, error) { +func getArmCPUVariant() (string, error) { var variant string @@ -48,7 +48,7 @@ func getCPUVariant() (string, error) { variant = "unknown" } } else { - return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) + return "", fmt.Errorf("getArmCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) } return variant, nil diff --git a/database.go b/database.go index 2e26fd3..a48a92a 100644 --- a/database.go +++ b/database.go @@ -48,6 +48,13 @@ func isArmArch(arch string) bool { return false } +// isAmd64Arch returns true if the architecture is AMD64. +// +// The arch value should be normalized before being passed to this function. +func isAmd64Arch(arch string) bool { + return arch == "amd64" +} + // isKnownArch returns true if we know about the architecture. // // The arch value should be normalized before being passed to this function. diff --git a/defaults.go b/defaults.go index 9d898d6..1b767b1 100644 --- a/defaults.go +++ b/defaults.go @@ -27,3 +27,13 @@ func DefaultString() string { func DefaultStrict() MatchComparer { return OnlyStrict(DefaultSpec()) } + +// MaximumString returns the maximum string specifier for the platform. +func MaximumString() string { + return FormatAll(MaximumSpec()) +} + +// MaximumStrict returns strict form of Maximum. +func MaximumStrict() MatchComparer { + return OnlyStrict(MaximumSpec()) +} diff --git a/defaults_darwin.go b/defaults_darwin.go index 72355ca..173cbaf 100644 --- a/defaults_darwin.go +++ b/defaults_darwin.go @@ -42,3 +42,22 @@ func Default() MatchComparer { Architecture: runtime.GOARCH, }) } + +// MaximumSpec returns the current platform's maximum platform specification. +func MaximumSpec() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM and AMD64. + Variant: cpuVariantMaximum(), + } +} + +// Maximum returns the maximum matcher for the platform. +func Maximum() MatchComparer { + return Ordered(MaximumSpec(), specs.Platform{ + // darwin runtime also supports Linux binary via runu/LKL + OS: "linux", + Architecture: runtime.GOARCH, + }) +} diff --git a/defaults_freebsd.go b/defaults_freebsd.go index d3fe89e..86113a6 100644 --- a/defaults_freebsd.go +++ b/defaults_freebsd.go @@ -41,3 +41,23 @@ func Default() MatchComparer { Variant: cpuVariant(), }) } + +// MaximumSpec returns the current platform's maximum platform specification. +func MaximumSpec() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM and AMD64. + Variant: cpuVariantMaximum(), + } +} + +// Maximum returns the maximum matcher for the platform. +func Maximum() MatchComparer { + return Ordered(MaximumSpec(), specs.Platform{ + OS: "linux", + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM and AMD64. + Variant: cpuVariantMaximum(), + }) +} diff --git a/defaults_unix.go b/defaults_unix.go index 44acc47..94a7c50 100644 --- a/defaults_unix.go +++ b/defaults_unix.go @@ -38,3 +38,18 @@ func DefaultSpec() specs.Platform { func Default() MatchComparer { return Only(DefaultSpec()) } + +// MaximumSpec returns the current platform's maximum platform specification. +func MaximumSpec() specs.Platform { + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + // The Variant field will be empty if arch != ARM and AMD64. + Variant: cpuVariantMaximum(), + } +} + +// Maximum returns the maximum matcher for the platform. +func Maximum() MatchComparer { + return Only(MaximumSpec()) +} diff --git a/defaults_unix_test.go b/defaults_unix_test.go index a37570f..65727f3 100644 --- a/defaults_unix_test.go +++ b/defaults_unix_test.go @@ -42,3 +42,20 @@ func TestDefault(t *testing.T) { t.Fatalf("default specifier should match formatted default spec: %v != %v", s, p) } } + +func TestMaximum(t *testing.T) { + expected := specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + Variant: cpuVariantMaximum(), + } + p := MaximumSpec() + if !reflect.DeepEqual(p, expected) { + t.Fatalf("maximum platform not as expected: %#v != %#v", p, expected) + } + + s := MaximumString() + if s != FormatAll(p) { + t.Fatalf("maximum specifier should match formatted maximum spec: %v != %v", s, p) + } +} diff --git a/defaults_windows.go b/defaults_windows.go index 8bae4eb..69a060e 100644 --- a/defaults_windows.go +++ b/defaults_windows.go @@ -116,3 +116,20 @@ func prefix(v string) string { func Default() MatchComparer { return Only(DefaultSpec()) } + +// MaximumSpec returns the current platform's maximum platform specification. +func MaximumSpec() specs.Platform { + major, minor, build := windows.RtlGetNtVersionNumbers() + return specs.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + OSVersion: fmt.Sprintf("%d.%d.%d", major, minor, build), + // The Variant field will be empty if arch != ARM. + Variant: cpuVariantMaximum(), + } +} + +// Maximum returns the current platform's maximum platform specification. +func Maximum() MatchComparer { + return Only(MaximumSpec()) +} diff --git a/defaults_windows_test.go b/defaults_windows_test.go index 3e0f75f..4e9c0b2 100644 --- a/defaults_windows_test.go +++ b/defaults_windows_test.go @@ -47,6 +47,25 @@ func TestDefault(t *testing.T) { } } +func TestMaximum(t *testing.T) { + major, minor, build := windows.RtlGetNtVersionNumbers() + expected := imagespec.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + OSVersion: fmt.Sprintf("%d.%d.%d", major, minor, build), + Variant: cpuVariantMaximum(), + } + p := MaximumSpec() + if !reflect.DeepEqual(p, expected) { + t.Fatalf("maximum platform not as expected: %#v != %#v", p, expected) + } + + s := MaximumString() + if s != FormatAll(p) { + t.Fatalf("maximum specifier should match formatted maximum spec: %v != %v", s, p) + } +} + func TestDefaultMatchComparer(t *testing.T) { defaultMatcher := Default() diff --git a/go.mod b/go.mod index fb68cc1..0edc8d6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/containerd/log v0.1.0 github.com/opencontainers/image-spec v1.1.0 github.com/stretchr/testify v1.8.4 + github.com/tonistiigi/go-archvariant v1.0.0 golang.org/x/sys v0.26.0 ) diff --git a/go.sum b/go.sum index 4cdba89..256dd12 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tonistiigi/go-archvariant v1.0.0 h1:5LC1eDWiBNflnTF1prCiX09yfNHIxDC/aukdhCdTyb0= +github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/platforms_test.go b/platforms_test.go index 8a26f5c..ec3cb3e 100644 --- a/platforms_test.go +++ b/platforms_test.go @@ -263,6 +263,15 @@ func TestParseSelector(t *testing.T) { formatted: "linux/amd64", useV2Format: false, }, + { + input: "Linux/x86_64/v2", + expected: specs.Platform{ + OS: "linux", + Architecture: "amd64", + Variant: "v2", + }, + formatted: "linux/amd64/v2", + }, { input: "i386", expected: specs.Platform{