Skip to content

Commit 65e524d

Browse files
committed
Reformulate how operatingSystemVersionString is constructed - now always includes at minimum the OS name (if known), and looks for a distro name on Linux. Slightly improve the test case.
1 parent c526129 commit 65e524d

File tree

2 files changed

+66
-32
lines changed

2 files changed

+66
-32
lines changed

Sources/Foundation/ProcessInfo.swift

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -100,52 +100,71 @@ open class ProcessInfo: NSObject {
100100
return osVersionInfo
101101
}
102102
#endif
103-
104-
open var operatingSystemVersionString: String {
105-
let fallback = "Unknown"
106-
#if os(Linux)
103+
104+
internal lazy var _operatingSystemVersionString: String = {
105+
#if canImport(Darwin)
106+
// Just use CoreFoundation on Darwin
107+
return CFCopySystemVersionString()?._swiftObject ?? "Darwin"
108+
#elseif os(Linux)
109+
// Try to parse a `PRETTY_NAME` out of `/etc/os-release`.
110+
if let osReleaseContents = try? String(contentsOf: URL(fileURLWithPath: "/etc/os-release", isDirectory: false)),
111+
let name = osReleaseContents.split(separator: "\n").first(where: { $0.hasPrefix("PRETTY_NAME=") })
112+
{
113+
// This is extremely simplistic but manages to work for all known cases.
114+
return String(name.dropFirst("PRETTY_NAME=".count).trimmingCharacters(in: .init(charactersIn: "\"")))
115+
}
116+
117+
// Okay, we can't get a distro name, so try for generic info.
118+
var versionString = "Linux"
119+
120+
// Try to get a release version number from `uname -r`.
107121
var utsNameBuffer = utsname()
108-
guard uname(&utsNameBuffer) == 0 else {
109-
return fallback
122+
if uname(&utsNameBuffer) == 0 {
123+
let release = withUnsafePointer(to: &utsNameBuffer.release.0) { String(cString: $0) }
124+
if !release.isEmpty {
125+
versionString += " \(release)"
126+
}
110127
}
111-
let release = withUnsafePointer(to: &utsNameBuffer.release.0) { String(cString: $0) }
112-
113-
return release
128+
129+
return versionString
114130
#elseif os(Windows)
131+
var versionString = "Windows"
132+
115133
guard let osVersionInfo = self._rawOperatingSystemVersionInfo else {
116-
return fallback
134+
return versionString
117135
}
118136

119137
// Windows has no canonical way to turn the fairly complex `RTL_OSVERSIONINFOW` version info into a string. We
120138
// do our best here to construct something consistent. Unfortunately, to provide a useful result, this requires
121139
// hardcoding several of the somewhat ambiguous values in the table provided here:
122140
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw#remarks
123-
var versionString = ""
124141
switch (osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion) {
125-
case (5, 0): versionString += "Windows 2000"
126-
case (5, 1): versionString += "Windows XP"
127-
case (5, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows XP Professional x64"
128-
case (5, 2) where osVersionInfo.wSuiteMask == VER_SUITE_WH_SERVER: versionString += "Windows Home Server"
129-
case (5, 2): versionString += "Windows Server 2003"
130-
case (6, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows Vista"
131-
case (6, 0): versionString += "Windows Server 2008"
132-
case (6, 1) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 7"
133-
case (6, 1): versionString += "Windows Server 2008 R2"
134-
case (6, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 8"
135-
case (6, 2): versionString += "Windows Server 2012"
136-
case (6, 3) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 8.1"
137-
case (6, 3): versionString += "Windows Server 2012 R2" // We assume the "10,0" numbers in the table for this are a typo
138-
case (10, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += "Windows 10"
139-
case (10, 0): versionString += "Windows Server 2019" // The table gives identical values for 2016 and 2019, so we just assume 2019 here
140-
default: return fallback
142+
case (5, 0): versionString += " 2000"
143+
case (5, 1): versionString += " XP"
144+
case (5, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " XP Professional x64"
145+
case (5, 2) where osVersionInfo.wSuiteMask == VER_SUITE_WH_SERVER: versionString += " Home Server"
146+
case (5, 2): versionString += " Server 2003"
147+
case (6, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " Vista"
148+
case (6, 0): versionString += " Server 2008"
149+
case (6, 1) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 7"
150+
case (6, 1): versionString += " Server 2008 R2"
151+
case (6, 2) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 8"
152+
case (6, 2): versionString += " Server 2012"
153+
case (6, 3) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 8.1"
154+
case (6, 3): versionString += " Server 2012 R2" // We assume the "10,0" numbers in the table for this are a typo
155+
case (10, 0) where osVersionInfo.wProductType == VER_NT_WORKSTATION: versionString += " 10"
156+
case (10, 0): versionString += " Server 2019" // The table gives identical values for 2016 and 2019, so we just assume 2019 here
157+
case let (maj, min): versionString += " \(maj).\(min)" // If all else fails, just give the raw version number
141158
}
142159
versionString += " (build \(osVersionInfo.dwBuildNumber))"
143160
// For now we ignore the `szCSDVersion`, `wServicePackMajor`, and `wServicePackMinor` values.
144161
return versionString
145162
#else
146-
return CFCopySystemVersionString()?._swiftObject ?? fallback
163+
// On other systems at least return something.
164+
return "Unknown"
147165
#endif
148-
}
166+
}()
167+
open var operatingSystemVersionString: String { return _operatingSystemVersionString }
149168

150169
open var operatingSystemVersion: OperatingSystemVersion {
151170
// The following fallback values match Darwin Foundation
@@ -174,7 +193,7 @@ open class ProcessInfo: NSObject {
174193
minorVersion: Int(osVersionInfo.dwMinorVersion),
175194
patchVersion: Int(osVersionInfo.dwBuildNumber)
176195
)
177-
#else
196+
#elseif os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)
178197
var utsNameBuffer = utsname()
179198
guard uname(&utsNameBuffer) == 0 else {
180199
return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
@@ -184,6 +203,8 @@ open class ProcessInfo: NSObject {
184203
}
185204
let idx = release.firstIndex(of: "-") ?? release.endIndex
186205
versionString = String(release[..<idx])
206+
#else
207+
return OperatingSystemVersion(majorVersion: fallbackMajor, minorVersion: fallbackMinor, patchVersion: fallbackPatch)
187208
#endif
188209
let versionComponents = versionString.split(separator: ".").map(String.init).compactMap({ Int($0) })
189210
let majorVersion = versionComponents.dropFirst(0).first ?? fallbackMajor

Tests/Foundation/Tests/TestProcessInfo.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,24 @@ class TestProcessInfo : XCTestCase {
2222
let processInfo = ProcessInfo.processInfo
2323
let versionString = processInfo.operatingSystemVersionString
2424
XCTAssertFalse(versionString.isEmpty)
25-
25+
26+
#if os(Linux)
27+
// Since the list of supported distros tends to change, at least check that it used os-release (if it's there).
28+
if let distroId = try? String(contentsOf: URL(fileURLWithPath: "/etc/os-release", isDirectory: false)),
29+
distroId.contains("PRETTY_NAME")
30+
{
31+
XCTAssertTrue(distroId.contains(versionString))
32+
} else {
33+
XCTAssertTrue(versionString.contains("Linux"))
34+
}
35+
#elseif os(Windows)
36+
XCTAssertTrue(versionString.hasPrefix("Windows"))
37+
#endif
38+
2639
let version = processInfo.operatingSystemVersion
2740
XCTAssert(version.majorVersion != 0)
2841

29-
#if os(Linux) || canImport(Darwin)
42+
#if canImport(Darwin) || os(Linux) || os(Windows)
3043
let minVersion = OperatingSystemVersion(majorVersion: 1, minorVersion: 0, patchVersion: 0)
3144
XCTAssertTrue(processInfo.isOperatingSystemAtLeast(minVersion))
3245
#endif

0 commit comments

Comments
 (0)