github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/utils/platform/platform.go (about)

     1  // Copyright © 2022 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package platform
    16  
    17  import (
    18  	"path"
    19  	"regexp"
    20  	"runtime"
    21  	"strings"
    22  
    23  	"github.com/pkg/errors"
    24  	v1 "github.com/sealerio/sealer/types/api/v1"
    25  )
    26  
    27  var (
    28  	specifierRe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
    29  )
    30  
    31  func ParsePlatforms(v string) ([]*v1.Platform, error) {
    32  	var pp []*v1.Platform
    33  	for _, v := range strings.Split(v, ",") {
    34  		p, err := Parse(v)
    35  		if err != nil {
    36  			return nil, errors.Wrapf(err, "failed to parse target platform %s", v)
    37  		}
    38  		p = Normalize(p)
    39  		pp = append(pp, &p)
    40  	}
    41  
    42  	return pp, nil
    43  }
    44  
    45  func Normalize(platform v1.Platform) v1.Platform {
    46  	platform.OS = normalizeOS(platform.OS)
    47  	platform.Architecture, platform.Variant = NormalizeArch(platform.Architecture, platform.Variant)
    48  
    49  	return platform
    50  }
    51  
    52  func Parse(specifier string) (v1.Platform, error) {
    53  	if strings.Contains(specifier, "*") {
    54  		return v1.Platform{}, errors.Wrapf(ErrInvalidArgument, "%q: wildcards not yet supported", specifier)
    55  	}
    56  
    57  	parts := strings.Split(specifier, "/")
    58  
    59  	for _, part := range parts {
    60  		if !specifierRe.MatchString(part) {
    61  			return v1.Platform{}, errors.Wrapf(ErrInvalidArgument, "%q is an invalid component of %q: platform specifier component must match %q", part, specifier, specifierRe.String())
    62  		}
    63  	}
    64  
    65  	var p v1.Platform
    66  	switch len(parts) {
    67  	case 1:
    68  		// in this case, we will test that the value might be an OS, then look
    69  		// it up. If it is not known, we'll treat it as an architecture. Since
    70  		// we have very little information about the platform here, we are
    71  		// going to be a little stricter if we don't know about the argument
    72  		// value.
    73  		p.OS = normalizeOS(parts[0])
    74  		if isKnownOS(p.OS) {
    75  			// picks a default architecture
    76  			p.Architecture = runtime.GOARCH
    77  			if p.Architecture == ARM && cpuVariant() != "v7" {
    78  				p.Variant = cpuVariant()
    79  			}
    80  
    81  			return p, nil
    82  		}
    83  
    84  		p.Architecture, p.Variant = NormalizeArch(parts[0], "")
    85  		if p.Architecture == ARM && p.Variant == "v7" {
    86  			p.Variant = ""
    87  		}
    88  		if isKnownArch(p.Architecture) {
    89  			p.OS = runtime.GOOS
    90  			return p, nil
    91  		}
    92  
    93  		return v1.Platform{}, errors.Wrapf(ErrInvalidArgument, "%q: unknown operating system or architecture", specifier)
    94  	case 2:
    95  		// In this case, we treat as a regular os/arch pair. We don't care
    96  		// about whether we know of the platform.
    97  		p.OS = normalizeOS(parts[0])
    98  		p.Architecture, p.Variant = NormalizeArch(parts[1], "")
    99  		if p.Architecture == ARM && p.Variant == "v7" {
   100  			p.Variant = ""
   101  		}
   102  
   103  		return p, nil
   104  	case 3:
   105  		// we have a fully specified variant, this is rare
   106  		p.OS = normalizeOS(parts[0])
   107  		p.Architecture, p.Variant = NormalizeArch(parts[1], parts[2])
   108  		if p.Architecture == ARM64 && p.Variant == "" {
   109  			p.Variant = "v8"
   110  		}
   111  
   112  		return p, nil
   113  	}
   114  
   115  	return v1.Platform{}, errors.Wrapf(ErrInvalidArgument, "%q: cannot parse platform specifier", specifier)
   116  }
   117  
   118  func GetDefaultPlatform() v1.Platform {
   119  	return v1.Platform{
   120  		OS:           runtime.GOOS,
   121  		Architecture: runtime.GOARCH,
   122  		// The Variant field will be empty if arch != ARM.
   123  		Variant: cpuVariant(),
   124  	}
   125  }
   126  
   127  // Format returns a string specifier from the provided platform specification.
   128  func Format(platform v1.Platform) string {
   129  	if platform.OS == "" {
   130  		return "unknown"
   131  	}
   132  
   133  	return path.Join(platform.OS, platform.Architecture, platform.Variant)
   134  }
   135  
   136  // Matched check if src == dest
   137  func Matched(src, dest v1.Platform) bool {
   138  	if src.OS == dest.OS &&
   139  		src.Architecture == ARM64 && dest.Architecture == ARM64 {
   140  		return true
   141  	}
   142  
   143  	return src.OS == dest.OS &&
   144  		src.Architecture == dest.Architecture &&
   145  		src.Variant == dest.Variant
   146  }
   147  
   148  func isLinuxOS(os string) bool {
   149  	return os == "linux"
   150  }
   151  
   152  // NormalizeArch normalizes the architecture.
   153  func NormalizeArch(arch, variant string) (string, string) {
   154  	arch, variant = strings.ToLower(arch), strings.ToLower(variant)
   155  	switch arch {
   156  	case "i386":
   157  		arch = "386"
   158  		variant = ""
   159  	case "x86_64", "x86-64":
   160  		arch = "amd64"
   161  		variant = ""
   162  	//nolint
   163  	case "aarch64", "arm64":
   164  		arch = "arm64"
   165  		variant = "v8"
   166  	case "armhf":
   167  		//nolint
   168  		arch = "arm"
   169  		variant = "v7"
   170  	case "armel":
   171  		arch = "arm"
   172  		variant = "v6"
   173  	case "arm":
   174  		switch variant {
   175  		case "", "7":
   176  			variant = "v7"
   177  		case "5", "6", "8":
   178  			variant = "v" + variant
   179  		}
   180  	}
   181  
   182  	return arch, variant
   183  }
   184  
   185  func isKnownOS(os string) bool {
   186  	switch os {
   187  	case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js",
   188  		"linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos":
   189  		return true
   190  	}
   191  	return false
   192  }
   193  
   194  func isArmArch(arch string) bool {
   195  	switch arch {
   196  	case "arm", "arm64":
   197  		return true
   198  	}
   199  	return false
   200  }
   201  
   202  func isKnownArch(arch string) bool {
   203  	switch arch {
   204  	case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be",
   205  		"ppc64", "ppc64le", "loong64", "mips", "mipsle", "mips64", "mips64le", "mips64p32",
   206  		"mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm":
   207  		return true
   208  	}
   209  	return false
   210  }
   211  
   212  func normalizeOS(os string) string {
   213  	if os == "" {
   214  		return runtime.GOOS
   215  	}
   216  	os = strings.ToLower(os)
   217  
   218  	switch os {
   219  	case "macos":
   220  		os = "darwin"
   221  	}
   222  	return os
   223  }