Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ final class OSLoggerPlatformProvider: ILoggerPlatformProvider {
static let deviceManufacturer = "Apple"
static let unknown = "unknown"
static let osBuildName = "kern.osversion"
static let xcodeVersionInfoKey = "DTXcode"
static let xcodeVersionAttribute = "xcode_version"
static let xcodeBuildInfoKey = "DTXcodeBuild"
static let xcodeBuildAttribute = "xcode_build"
static let minimumOSVersionInfoKey = "MinimumOSVersion"
static let minimumOSVersionAttribute = "minimum_os_version"
static let minimumMacOSVersionInfoKey = "LSMinimumSystemVersion"
static let minimumMacOSVersionAttribute = "minimum_macos_version"
static let disabledLogLevel = "NONE"
static let crashDirectoryComponents = ["onesignal", "logger", "crashes"]

Expand Down Expand Up @@ -107,15 +115,17 @@ final class OSLoggerPlatformProvider: ILoggerPlatformProvider {
let osBuildId = OSLoggerPlatformProvider.systemValue(named: Constants.osBuildName)
let sdkWrapper = OneSignalWrapper.sdkType
let sdkWrapperVersion = OneSignalWrapper.sdkVersion
/// Nil on iOS: this describes the *host app's* Kotlin stack, and an iOS app is
/// not a Kotlin host. The shared module's own provenance rides on
/// `ossdk.kmp_version` instead.
let kotlinVersion: String? = nil

/// Nil on iOS: there is no runtime API for the Swift language version, and anything
/// derivable would just re-encode `xcode_version` less precisely.
let swiftVersion: String? = nil
Comment thread
abdulraqeeb33 marked this conversation as resolved.
let additionalVersionAttributes: [String: String] = {
#if targetEnvironment(macCatalyst)
return ["apple_platform": "mac_catalyst"]
#else
return [:]
#endif
}()

let additionalVersionAttributes: [String: String] =
OSLoggerPlatformProvider.hostBuildAttributes()
var enabledFeatureFlags: [String] {
featureFlagsProvider()
}
Expand Down Expand Up @@ -189,6 +199,69 @@ final class OSLoggerPlatformProvider: ILoggerPlatformProvider {

let apiBaseUrl = OS_API_SERVER_URL.trimmingCharacters(in: CharacterSet(charactersIn: "/"))

/// Toolchain and deployment target of the running executable, read from its
/// `Info.plist`. Read at runtime rather than with `#if swift(...)` because we ship a
/// prebuilt XCFramework — a compile-time check would describe OneSignal's build
/// machine, identically for every customer.
///
/// `minimum_os_version` is the iOS deployment target the host *commits to*, which
/// is the fact that gates raising our own target. Catalyst's macOS floor is a
/// different namespace (`LSMinimumSystemVersion` → `minimum_macos_version`) and
/// is not mixed in. The OS actually running is separate (`os.name` / `os.version`
/// / `os.build_id`) and routinely diverges from both.
static func hostBuildAttributes(
infoDictionary: [String: Any]? = Bundle.main.infoDictionary
) -> [String: String] {
var attributes: [String: String] = [:]
#if targetEnvironment(macCatalyst)
attributes["apple_platform"] = "mac_catalyst"
#endif
if let xcodeVersion = hostXcodeVersion(infoDictionary: infoDictionary) {
attributes[Constants.xcodeVersionAttribute] = xcodeVersion
}
if let xcodeBuild = infoValue(Constants.xcodeBuildInfoKey, in: infoDictionary) {
attributes[Constants.xcodeBuildAttribute] = xcodeBuild
}
if let minimumOSVersion = infoValue(Constants.minimumOSVersionInfoKey, in: infoDictionary) {
attributes[Constants.minimumOSVersionAttribute] = minimumOSVersion
}
// macOS version — do not fold into minimum_os_version
if let minimumMacOSVersion = infoValue(
Constants.minimumMacOSVersionInfoKey,
in: infoDictionary
) {
attributes[Constants.minimumMacOSVersionAttribute] = minimumMacOSVersion
}
return attributes
}

private static func infoValue(_ key: String, in infoDictionary: [String: Any]?) -> String? {
guard let value = infoDictionary?[key] as? String, !value.isEmpty else {
return nil
}
return value
}

static func hostXcodeVersion(
infoDictionary: [String: Any]? = Bundle.main.infoDictionary
) -> String? {
guard let raw = infoDictionary?[Constants.xcodeVersionInfoKey] as? String else {
return nil
}
return decodeXcodeVersion(raw)
}

/// `DTXcode` packs major/minor/patch into digits: `"2620"` is 26.2, `"0900"` is 9.0.
static func decodeXcodeVersion(_ raw: String) -> String? {
guard let packed = Int(raw.trimmingCharacters(in: .whitespaces)), packed > 0 else {
return nil
}
let major = packed / 100
let minor = (packed / 10) % 10
let patch = packed % 10
return patch == 0 ? "\(major).\(minor)" : "\(major).\(minor).\(patch)"
}

private static func systemValue(named name: String) -> String {
var size = 0
guard sysctlbyname(name, nil, &size, nil, 0) == 0, size > 0 else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,70 @@ final class OSLoggerAdaptersTests: XCTestCase {
}
}

/// Host build metadata read from the app's `Info.plist`, kept separate from the
/// adapter tests above so neither class outgrows the lint limit.
final class OSLoggerHostBuildAttributesTests: XCTestCase {
func testDecodeXcodeVersionUnpacksDTXcodeDigits() {
XCTAssertEqual(OSLoggerPlatformProvider.decodeXcodeVersion("2620"), "26.2")
XCTAssertEqual(OSLoggerPlatformProvider.decodeXcodeVersion("1600"), "16.0")
XCTAssertEqual(OSLoggerPlatformProvider.decodeXcodeVersion("0900"), "9.0")
XCTAssertEqual(OSLoggerPlatformProvider.decodeXcodeVersion("1632"), "16.3.2")
XCTAssertNil(OSLoggerPlatformProvider.decodeXcodeVersion(""))
XCTAssertNil(OSLoggerPlatformProvider.decodeXcodeVersion("not-a-number"))
}

func testHostBuildAttributesMapsInfoPlistKeys() {
let attributes = OSLoggerPlatformProvider.hostBuildAttributes(infoDictionary: [
"DTXcode": "2620",
"DTXcodeBuild": "17C52",
"MinimumOSVersion": "12.0",
// Present in every built Info.plist but deliberately not emitted: each is
// either constant across all apps or derivable from the fields above.
"DTCompiler": "com.apple.compilers.llvm.clang.1_0",
"DTPlatformName": "iphonesimulator",
"DTPlatformVersion": "26.2",
"DTPlatformBuild": "23C53",
"DTSDKName": "iphonesimulator26.2",
"DTSDKBuild": "23C53"
])

XCTAssertEqual(attributes["xcode_version"], "26.2")
XCTAssertEqual(attributes["xcode_build"], "17C52")
// The host's declared deployment target, not the OS it happens to run on.
XCTAssertEqual(attributes["minimum_os_version"], "12.0")
XCTAssertEqual(attributes.count, 3)
}

func testHostBuildAttributesEmitsCatalystFloorAsMacOSVersion() {
let attributes = OSLoggerPlatformProvider.hostBuildAttributes(infoDictionary: [
"LSMinimumSystemVersion": "13.1"
])

XCTAssertEqual(attributes["minimum_macos_version"], "13.1")
XCTAssertNil(attributes["minimum_os_version"])
}

func testHostBuildAttributesEmitsIOSAndMacOSFloorsIndependently() {
let attributes = OSLoggerPlatformProvider.hostBuildAttributes(infoDictionary: [
"MinimumOSVersion": "12.0",
"LSMinimumSystemVersion": "13.1"
])

XCTAssertEqual(attributes["minimum_os_version"], "12.0")
XCTAssertEqual(attributes["minimum_macos_version"], "13.1")
}

func testHostBuildAttributesOmitsBlankAndMissingValues() {
let attributes = OSLoggerPlatformProvider.hostBuildAttributes(infoDictionary: [
"DTXcodeBuild": "",
"MinimumOSVersion": "",
"LSMinimumSystemVersion": ""
])

XCTAssertTrue(attributes.isEmpty)
}
}

private final class LoggerAdapterListener: NSObject, OSLogListener {
var levels: [ONE_S_LOG_LEVEL] = []

Expand Down
Loading