Merge pull request #7636 from fangn2/fix-cpu-architecture-detection-issue-on-arm
Fix cpu architecture detection issue on linux/arm
This commit is contained in:
commit
c0c3546de4
@ -17,14 +17,9 @@
|
|||||||
package platforms
|
package platforms
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,108 +32,12 @@ var cpuVariantOnce sync.Once
|
|||||||
func cpuVariant() string {
|
func cpuVariant() string {
|
||||||
cpuVariantOnce.Do(func() {
|
cpuVariantOnce.Do(func() {
|
||||||
if isArmArch(runtime.GOARCH) {
|
if isArmArch(runtime.GOARCH) {
|
||||||
cpuVariantValue = getCPUVariant()
|
var err error
|
||||||
|
cpuVariantValue, err = getCPUVariant()
|
||||||
|
if err != nil {
|
||||||
|
log.L.Errorf("Error getCPUVariant for OS %s: %v", runtime.GOOS, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return cpuVariantValue
|
return cpuVariantValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Linux, the kernel has already detected the ABI, ISA and Features.
|
|
||||||
// So we don't need to access the ARM registers to detect platform information
|
|
||||||
// by ourselves. We can just parse these information from /proc/cpuinfo
|
|
||||||
func getCPUInfo(pattern string) (info string, err error) {
|
|
||||||
if !isLinuxOS(runtime.GOOS) {
|
|
||||||
return "", fmt.Errorf("getCPUInfo for OS %s: %w", runtime.GOOS, errdefs.ErrNotImplemented)
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuinfo, err := os.Open("/proc/cpuinfo")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer cpuinfo.Close()
|
|
||||||
|
|
||||||
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
|
|
||||||
// the first core is enough.
|
|
||||||
scanner := bufio.NewScanner(cpuinfo)
|
|
||||||
for scanner.Scan() {
|
|
||||||
newline := scanner.Text()
|
|
||||||
list := strings.Split(newline, ":")
|
|
||||||
|
|
||||||
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
|
|
||||||
return strings.TrimSpace(list[1]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether the scanner encountered errors
|
|
||||||
err = scanner.Err()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("getCPUInfo for pattern: %s: %w", pattern, errdefs.ErrNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCPUVariant() string {
|
|
||||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
|
||||||
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
|
||||||
// runtime.GOARCH to determine the variants
|
|
||||||
var variant string
|
|
||||||
switch runtime.GOARCH {
|
|
||||||
case "arm64":
|
|
||||||
variant = "v8"
|
|
||||||
case "arm":
|
|
||||||
variant = "v7"
|
|
||||||
default:
|
|
||||||
variant = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return variant
|
|
||||||
}
|
|
||||||
if runtime.GOOS == "freebsd" {
|
|
||||||
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
|
||||||
// detecting those variants is currently unimplemented
|
|
||||||
var variant string
|
|
||||||
switch runtime.GOARCH {
|
|
||||||
case "arm64":
|
|
||||||
variant = "v8"
|
|
||||||
default:
|
|
||||||
variant = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return variant
|
|
||||||
}
|
|
||||||
|
|
||||||
variant, err := getCPUInfo("Cpu architecture")
|
|
||||||
if err != nil {
|
|
||||||
log.L.WithError(err).Error("failure getting variant")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
|
|
||||||
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
|
|
||||||
if runtime.GOARCH == "arm" && variant == "7" {
|
|
||||||
model, err := getCPUInfo("model name")
|
|
||||||
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
|
|
||||||
variant = "6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(variant) {
|
|
||||||
case "8", "aarch64":
|
|
||||||
variant = "v8"
|
|
||||||
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
|
|
||||||
variant = "v7"
|
|
||||||
case "6", "6tej":
|
|
||||||
variant = "v6"
|
|
||||||
case "5", "5t", "5te", "5tej":
|
|
||||||
variant = "v5"
|
|
||||||
case "4", "4t":
|
|
||||||
variant = "v4"
|
|
||||||
case "3":
|
|
||||||
variant = "v3"
|
|
||||||
default:
|
|
||||||
variant = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
return variant
|
|
||||||
}
|
|
||||||
|
161
platforms/cpuinfo_linux.go
Normal file
161
platforms/cpuinfo_linux.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package platforms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getMachineArch retrieves the machine architecture through system call
|
||||||
|
func getMachineArch() (string, error) {
|
||||||
|
var uname unix.Utsname
|
||||||
|
err := unix.Uname(&uname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
arch := string(uname.Machine[:bytes.IndexByte(uname.Machine[:], 0)])
|
||||||
|
|
||||||
|
return arch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Linux, the kernel has already detected the ABI, ISA and Features.
|
||||||
|
// So we don't need to access the ARM registers to detect platform information
|
||||||
|
// by ourselves. We can just parse these information from /proc/cpuinfo
|
||||||
|
func getCPUInfo(pattern string) (info string, err error) {
|
||||||
|
|
||||||
|
cpuinfo, err := os.Open("/proc/cpuinfo")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer cpuinfo.Close()
|
||||||
|
|
||||||
|
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
|
||||||
|
// the first core is enough.
|
||||||
|
scanner := bufio.NewScanner(cpuinfo)
|
||||||
|
for scanner.Scan() {
|
||||||
|
newline := scanner.Text()
|
||||||
|
list := strings.Split(newline, ":")
|
||||||
|
|
||||||
|
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
|
||||||
|
return strings.TrimSpace(list[1]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the scanner encountered errors
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("getCPUInfo for pattern %s: %w", pattern, errdefs.ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCPUVariantFromArch get CPU variant from arch through a system call
|
||||||
|
func getCPUVariantFromArch(arch string) (string, error) {
|
||||||
|
|
||||||
|
var variant string
|
||||||
|
|
||||||
|
arch = strings.ToLower(arch)
|
||||||
|
|
||||||
|
if arch == "aarch64" {
|
||||||
|
variant = "8"
|
||||||
|
} else if arch[0:4] == "armv" && len(arch) >= 5 {
|
||||||
|
//Valid arch format is in form of armvXx
|
||||||
|
switch arch[3:5] {
|
||||||
|
case "v8":
|
||||||
|
variant = "8"
|
||||||
|
case "v7":
|
||||||
|
variant = "7"
|
||||||
|
case "v6":
|
||||||
|
variant = "6"
|
||||||
|
case "v5":
|
||||||
|
variant = "5"
|
||||||
|
case "v4":
|
||||||
|
variant = "4"
|
||||||
|
case "v3":
|
||||||
|
variant = "3"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("getCPUVariantFromArch invalid arch: %s, %w", arch, errdefs.ErrInvalidArgument)
|
||||||
|
}
|
||||||
|
return variant, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCPUVariant 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) {
|
||||||
|
|
||||||
|
variant, err := getCPUInfo("Cpu architecture")
|
||||||
|
if err != nil {
|
||||||
|
if errdefs.IsNotFound(err) {
|
||||||
|
//Let's try getting CPU variant from machine architecture
|
||||||
|
arch, err := getMachineArch()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failure getting machine architecture: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
variant, err = getCPUVariantFromArch(arch)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failure getting CPU variant from machine architecture: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("failure getting CPU variant: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
|
||||||
|
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
|
||||||
|
if runtime.GOARCH == "arm" && variant == "7" {
|
||||||
|
model, err := getCPUInfo("model name")
|
||||||
|
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
|
||||||
|
variant = "6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(variant) {
|
||||||
|
case "8", "aarch64":
|
||||||
|
variant = "v8"
|
||||||
|
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
|
||||||
|
variant = "v7"
|
||||||
|
case "6", "6tej":
|
||||||
|
variant = "v6"
|
||||||
|
case "5", "5t", "5te", "5tej":
|
||||||
|
variant = "v5"
|
||||||
|
case "4", "4t":
|
||||||
|
variant = "v4"
|
||||||
|
case "3":
|
||||||
|
variant = "v3"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return variant, nil
|
||||||
|
}
|
141
platforms/cpuinfo_linux_test.go
Normal file
141
platforms/cpuinfo_linux_test.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package platforms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCPUVariant(t *testing.T) {
|
||||||
|
if !isArmArch(runtime.GOARCH) {
|
||||||
|
t.Skip("only relevant on linux/arm")
|
||||||
|
}
|
||||||
|
|
||||||
|
variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"}
|
||||||
|
|
||||||
|
p, err := getCPUVariant()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error getting CPU variant: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, variant := range variants {
|
||||||
|
if p == variant {
|
||||||
|
t.Logf("got valid variant as expected: %#v = %#v", p, variant)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatalf("could not get valid variant as expected: %v", variants)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCPUVariantFromArch(t *testing.T) {
|
||||||
|
|
||||||
|
for _, testcase := range []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
output string
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test aarch64",
|
||||||
|
input: "aarch64",
|
||||||
|
output: "8",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test Armv8 with capital",
|
||||||
|
input: "Armv8",
|
||||||
|
output: "8",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test armv7",
|
||||||
|
input: "armv7",
|
||||||
|
output: "7",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test armv6",
|
||||||
|
input: "armv6",
|
||||||
|
output: "6",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test armv5",
|
||||||
|
input: "armv5",
|
||||||
|
output: "5",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test armv4",
|
||||||
|
input: "armv4",
|
||||||
|
output: "4",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test armv3",
|
||||||
|
input: "armv3",
|
||||||
|
output: "3",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test unknown input",
|
||||||
|
input: "armv9",
|
||||||
|
output: "unknown",
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test invalid input which doesn't start with armv",
|
||||||
|
input: "armxxxx",
|
||||||
|
output: "",
|
||||||
|
expectedErr: errdefs.ErrInvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test invalid input whose length is less than 5",
|
||||||
|
input: "armv",
|
||||||
|
output: "",
|
||||||
|
expectedErr: errdefs.ErrInvalidArgument,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
t.Logf("input: %v", testcase.input)
|
||||||
|
|
||||||
|
variant, err := getCPUVariantFromArch(testcase.input)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if testcase.expectedErr != nil {
|
||||||
|
t.Fatalf("Expect to get error: %v, however no error got", testcase.expectedErr)
|
||||||
|
} else {
|
||||||
|
if variant != testcase.output {
|
||||||
|
t.Fatalf("Expect to get variant: %v, however %v returned", testcase.output, variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if !errors.Is(err, testcase.expectedErr) {
|
||||||
|
t.Fatalf("Expect to get error: %v, however error %v returned", testcase.expectedErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
60
platforms/cpuinfo_other.go
Normal file
60
platforms/cpuinfo_other.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package platforms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCPUVariant() (string, error) {
|
||||||
|
|
||||||
|
var variant string
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||||
|
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
|
||||||
|
// runtime.GOARCH to determine the variants
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "arm64":
|
||||||
|
variant = "v8"
|
||||||
|
case "arm":
|
||||||
|
variant = "v7"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
} else if runtime.GOOS == "freebsd" {
|
||||||
|
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
|
||||||
|
// detecting those variants is currently unimplemented
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "arm64":
|
||||||
|
variant = "v8"
|
||||||
|
default:
|
||||||
|
variant = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errdefs.ErrNotImplemented)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return variant, nil
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright The containerd Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package platforms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCPUVariant(t *testing.T) {
|
|
||||||
if !isArmArch(runtime.GOARCH) || !isLinuxOS(runtime.GOOS) {
|
|
||||||
t.Skip("only relevant on linux/arm")
|
|
||||||
}
|
|
||||||
|
|
||||||
variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"}
|
|
||||||
|
|
||||||
p := getCPUVariant()
|
|
||||||
for _, variant := range variants {
|
|
||||||
if p == variant {
|
|
||||||
t.Logf("got valid variant as expected: %#v = %#v\n", p, variant)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Fatalf("could not get valid variant as expected: %v\n", variants)
|
|
||||||
}
|
|
@ -21,13 +21,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isLinuxOS returns true if the operating system is Linux.
|
|
||||||
//
|
|
||||||
// The OS value should be normalized before calling this function.
|
|
||||||
func isLinuxOS(os string) bool {
|
|
||||||
return os == "linux"
|
|
||||||
}
|
|
||||||
|
|
||||||
// These function are generated from https://golang.org/src/go/build/syslist.go.
|
// These function are generated from https://golang.org/src/go/build/syslist.go.
|
||||||
//
|
//
|
||||||
// We use switch statements because they are slightly faster than map lookups
|
// We use switch statements because they are slightly faster than map lookups
|
||||||
|
Loading…
Reference in New Issue
Block a user