Files
sofmani/platform/platform.go

265 lines
6.6 KiB
Go
Executable File

package platform
import (
"fmt"
"runtime"
"slices"
"github.com/samber/lo"
)
var osValue string = runtime.GOOS // osValue stores the current operating system.
var archValue string = runtime.GOARCH // archValue stores the current architecture.
// getOS returns the current operating system. It caches the value after the first call.
func getOS() string {
if osValue == "" {
osValue = runtime.GOOS
}
return osValue
}
// SetOS overrides the detected operating system. This is primarily used for testing.
func SetOS(v string) {
osValue = v
}
// getArch returns the current architecture. It caches the value after the first call.
func getArch() string {
if archValue == "" {
archValue = runtime.GOARCH
}
return archValue
}
// SetArch overrides the detected architecture. This is primarily used for testing.
func SetArch(v string) {
archValue = v
}
// Architecture represents a CPU architecture.
type Architecture string
// Constants for supported architectures.
const (
ArchAmd64 Architecture = "amd64" // ArchAmd64 represents x86_64 architecture.
ArchArm64 Architecture = "arm64" // ArchArm64 represents ARM64 architecture.
)
// GetArch returns the current architecture (amd64 or arm64).
func GetArch() Architecture {
switch getArch() {
case "amd64", "x86_64":
return ArchAmd64
case "arm64", "aarch64":
return ArchArm64
default:
return Architecture(getArch())
}
}
// GetArchAlias returns the architecture in common alias format (x86_64 or arm64).
func GetArchAlias() string {
switch GetArch() {
case ArchAmd64:
return "x86_64"
case ArchArm64:
return "arm64"
default:
return string(GetArch())
}
}
// GetArchGnu returns the architecture in GNU/Linux format (x86_64 or aarch64).
func GetArchGnu() string {
switch GetArch() {
case ArchAmd64:
return "x86_64"
case ArchArm64:
return "aarch64"
default:
return string(GetArch())
}
}
// GetPlatform returns the current platform (macos, linux, or windows).
func GetPlatform() Platform {
switch getOS() {
case "darwin":
return PlatformMacos
case "linux":
return PlatformLinux
case "windows":
return PlatformWindows
}
panic(fmt.Sprintf("Unsupported platform %s", getOS()))
}
// Platforms defines which platforms a configuration applies to.
type Platforms struct {
// Only specifies a list of platforms where the configuration should apply.
Only *[]Platform `json:"only" yaml:"only"`
// Except specifies a list of platforms where the configuration should not apply.
Except *[]Platform `json:"except" yaml:"except"`
}
// Platform represents an operating system platform.
type Platform string
// Constants for supported platforms.
const (
PlatformMacos Platform = "macos" // PlatformMacos represents macOS.
PlatformLinux Platform = "linux" // PlatformLinux represents Linux.
PlatformWindows Platform = "windows" // PlatformWindows represents Windows.
)
// PlatformMap is a generic type that holds platform-specific values.
type PlatformMap[T any] struct {
// MacOS is the value for macOS.
MacOS *T `json:"macos" yaml:"macos"`
// Linux is the value for Linux.
Linux *T `json:"linux" yaml:"linux"`
// Windows is the value for Windows.
Windows *T `json:"windows" yaml:"windows"`
}
// Resolve returns the value for the current platform from the PlatformMap.
// It returns nil if no value is defined for the current platform.
func (p *PlatformMap[T]) Resolve() *T {
if p == nil {
return nil
}
switch getOS() {
case "darwin":
if p.MacOS != nil {
return p.MacOS
}
return nil
case "linux":
if p.Linux != nil {
return p.Linux
}
return nil
case "windows":
if p.Windows != nil {
return p.Windows
}
return nil
default:
return nil
}
}
// ResolveWithFallback returns the value for the current platform from the PlatformMap.
// If no value is defined for the current platform, it falls back to the value from the provided fallback PlatformMap.
func (o *PlatformMap[T]) ResolveWithFallback(fallback PlatformMap[T]) T {
val := o.Resolve()
if val == nil {
return *fallback.Resolve()
}
return *val
}
// ContainsPlatform checks if a slice of platforms contains a specific platform.
func ContainsPlatform(platforms *[]Platform, platform Platform) bool {
return slices.Contains(*platforms, platform)
}
// GetShouldRunOnOS determines if a configuration should run on the current operating system
// based on the Only and Except fields of the Platforms struct.
func (p *Platforms) GetShouldRunOnOS(curOS Platform) bool {
if p == nil {
return true
}
if p.Only != nil {
return ContainsPlatform(p.Only, curOS)
}
if p.Except != nil {
return !ContainsPlatform(p.Except, curOS)
}
return true
}
// DockerOSMap is a PlatformMap that defines the Docker OS for each platform.
var DockerOSMap = PlatformMap[string]{
MacOS: lo.ToPtr("linux"),
Linux: lo.ToPtr("linux"),
Windows: lo.ToPtr("windows"),
}
// ParsePlatformSingleValue creates a new PlatformMap with the value for all platforms
func ParsePlatformSingleValue[T any](value T) *PlatformMap[T] {
p := &PlatformMap[T]{}
p.MacOS = &value
p.Linux = &value
p.Windows = &value
return p
}
// ParselatformMap creates a PlatformMap from a map of platform strings to values.
func ParselatformMap[T any](values map[string]T) *PlatformMap[T] {
p := &PlatformMap[T]{}
for k, v := range values {
val := v // capture value for pointer
switch Platform(k) {
case PlatformMacos:
p.MacOS = &val
case PlatformLinux:
p.Linux = &val
case PlatformWindows:
p.Windows = &val
default:
panic(fmt.Sprintf("Unsupported platform key: %q", k))
}
}
return p
}
// NewPlatformMap creates a new PlatformMap from either a single value or a map.
func NewPlatformMap[T any](input any) *PlatformMap[T] {
switch v := input.(type) {
case nil:
return nil
case *T:
if v != nil {
return ParsePlatformSingleValue(*v)
}
return nil
case map[string]T:
return ParselatformMap(v)
case map[string]*T:
flat := make(map[string]T)
for k, ptr := range v {
if ptr != nil {
flat[k] = *ptr
}
}
return ParselatformMap(flat)
case map[any]any:
// Handle YAML unmarshaling which produces map[any]any
flat := make(map[string]T)
for k, val := range v {
if keyStr, ok := k.(string); ok {
if typedVal, ok := val.(T); ok {
flat[keyStr] = typedVal
}
}
}
return ParselatformMap(flat)
case map[string]any:
// Handle JSON unmarshaling or mixed YAML which produces map[string]interface{}
flat := make(map[string]T)
for k, val := range v {
if typedVal, ok := val.(T); ok {
flat[k] = typedVal
}
}
return ParselatformMap(flat)
case T:
return ParsePlatformSingleValue(v)
default:
panic(fmt.Sprintf("NewPlatformMap: unsupported input type %T", input))
}
}