github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/sysinfo/sysinfo_darwin.go (about) 1 package sysinfo 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/fs" 8 "os" 9 "os/exec" 10 "regexp" 11 "strconv" 12 13 "github.com/ActiveState/cli/internal/errs" 14 "github.com/ActiveState/cli/internal/fileutils" 15 "github.com/ActiveState/cli/internal/locale" 16 "github.com/ActiveState/cli/internal/logging" 17 "github.com/patrickmn/go-cache" 18 ) 19 20 // OS returns the system's OS 21 func OS() OsInfo { 22 return Mac 23 } 24 25 var ( 26 plistVersionRegex = regexp.MustCompile(`(?s)ProductVersion.*?([\d\.]+)`) 27 ) 28 29 // OSVersion returns the system's OS version. 30 func OSVersion() (*OSVersionInfo, error) { 31 if cached, found := sysinfoCache.Get(osVersionInfoCacheKey); found { 32 return cached.(*OSVersionInfo), nil 33 } 34 35 if v := os.Getenv(VersionOverrideEnvVar); v != "" { 36 vInfo, err := parseVersionInfo(v) 37 if err != nil { 38 return nil, fmt.Errorf("Could not parse version info: %w", err) 39 } 40 return &OSVersionInfo{vInfo, "spoofed"}, nil 41 } 42 43 // Fetch OS version. 44 version, err := getDarwinProductVersion() 45 if err != nil { 46 return nil, fmt.Errorf("Unable to determine OS version: %v", err) 47 } 48 49 vInfo, err := parseVersionInfo(version) 50 if err != nil { 51 return nil, fmt.Errorf("Unable to parse OS version: %w", err) 52 } 53 54 // Fetch OS name. 55 name, err := exec.Command("sw_vers", "-productName").Output() 56 if err != nil { 57 return nil, fmt.Errorf("Unable to fetch OS name: %s", err) 58 } 59 60 info := &OSVersionInfo{vInfo, string(name)} 61 sysinfoCache.Set(osVersionInfoCacheKey, info, cache.NoExpiration) 62 return info, nil 63 } 64 65 // Libc returns the system's C library. 66 func Libc() (*LibcInfo, error) { 67 if cached, found := sysinfoCache.Get(libcInfoCacheKey); found { 68 return cached.(*LibcInfo), nil 69 } 70 71 version, err := exec.Command("clang", "--version").Output() 72 if err != nil { 73 return nil, fmt.Errorf("Unable to fetch libc version: %s", err) 74 } 75 regex := regexp.MustCompile(`(\d+)\D(\d+)`) 76 parts := regex.FindStringSubmatch(string(version)) 77 if len(parts) != 3 { 78 return nil, fmt.Errorf("Unable to parse libc string '%s'", version) 79 } 80 for i := 1; i < len(parts); i++ { 81 if _, err := strconv.Atoi(parts[i]); err != nil { 82 return nil, fmt.Errorf("Unable to parse part '%s' of libc string '%s'", parts[i], version) 83 } 84 } 85 major, _ := strconv.Atoi(parts[1]) 86 minor, _ := strconv.Atoi(parts[2]) 87 info := &LibcInfo{BsdLibc, major, minor} 88 sysinfoCache.Set(libcInfoCacheKey, info, cache.NoExpiration) 89 return info, nil 90 } 91 92 // Compilers returns the system's available compilers. 93 func Compilers() ([]*CompilerInfo, error) { 94 if cached, found := sysinfoCache.Get(compilersCacheKey); found { 95 return cached.([]*CompilerInfo), nil 96 } 97 98 compilers := []*CompilerInfo{} 99 100 // Map of compiler commands to CompilerNameInfos. 101 var compilerMap = map[string]CompilerNameInfo{ 102 "clang": Clang, 103 } 104 for command, nameInfo := range compilerMap { 105 major, minor, err := getCompilerVersion([]string{command, "--version"}) 106 if err != nil { 107 return compilers, err 108 } else if major > 0 { 109 compilers = append(compilers, &CompilerInfo{nameInfo, major, minor}) 110 } 111 } 112 113 sysinfoCache.Set(compilersCacheKey, compilers, cache.NoExpiration) 114 return compilers, nil 115 } 116 117 func getDarwinProductVersion() (string, error) { 118 v, err := getDarwinProductVersionFromFS() 119 if err != nil && !errors.Is(err, fs.ErrNotExist) { 120 logging.Warning("Unable to fetch OS version from filesystem: %v", errs.JoinMessage(err)) 121 } else if err == nil { 122 return v, nil 123 } 124 125 version, err := exec.Command("sw_vers", "-productVersion").Output() 126 if err != nil { 127 return "", locale.WrapError(err, "Could not detect your OS version, error received: %s", err.Error()) 128 } 129 return string(bytes.TrimSpace(version)), nil 130 } 131 132 func getDarwinProductVersionFromFS() (string, error) { 133 fpath := "/System/Library/CoreServices/SystemVersion.plist" 134 if !fileutils.TargetExists(fpath) { 135 return "", fs.ErrNotExist 136 } 137 138 b, err := fileutils.ReadFile(fpath) 139 if err != nil { 140 return "", errs.Wrap(err, "Could not read %s", fpath) 141 } 142 143 match := plistVersionRegex.FindSubmatch(b) 144 if len(match) != 2 { 145 return "", errs.Wrap(err, "SystemVersion.plist does not contain a valid ProductVersion, match result: %v, xml:\n%s", match, string(b)) 146 } 147 148 return string(match[1]), nil 149 }