From 14c4be6bb89cab68e64083ee77a60ce392c2536c Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Sat, 26 Apr 2025 10:52:30 -0400 Subject: [PATCH 1/6] Android support in Package.swift and CI testing --- .github/workflows/tests.yml | 12 +++++++++++- .swiftformat | 2 +- Package@swift-5.9.swift | 34 ++++++++++++++++++++++++---------- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20d4ceeed..7f831e281 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,4 +36,14 @@ jobs: # steps: # - uses: actions/checkout@v3 # - name: Run tests - # run: CI=1 ./scripts/all-tests.sh "Unix" \ No newline at end of file + # run: CI=1 ./scripts/all-tests.sh "Unix" + + +linux: + name: "Test (Android)" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Test Swift Package on Android" + uses: skiptools/swift-android-action@v2 + diff --git a/.swiftformat b/.swiftformat index 93777b0da..ef7da1df4 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,3 +1,3 @@ --ifdef no-indent --wrap-arguments before-first ---exclude Sources/AllTestz/main.swift \ No newline at end of file +--exclude Sources/AllTestz/main.swift,RxTest/HotObservable.swift,Tests/RxSwiftTests/BagTest.swift \ No newline at end of file diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index d3ec95b02..d9795a302 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -1,7 +1,21 @@ // swift-tools-version:5.9 +import Foundation import PackageDescription +func targetsDarwin() -> Bool { + if (ProcessInfo.processInfo.environment["TARGET_OS_ANDROID"] ?? "0") != "0" { + // we are building for Android, and so Cocoa is not available + return false + } + + #if !canImport(Darwin) + return false // Linux, Windows, etc. + #else + return true // macOS, iOS, etc. + #endif +} + let buildTests = false extension Product { @@ -26,19 +40,19 @@ extension Target { extension Target { static func rxCocoa() -> [Target] { - #if os(Linux) - return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] - #else - return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] - #endif + if !targetsDarwin() { + [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] + } else { + [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] + } } static func rxCocoaRuntime() -> [Target] { - #if os(Linux) - return [] - #else - return [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] - #endif + if !targetsDarwin() { + [] + } else { + [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] + } } static func allTests() -> [Target] { From 8cd2d57116adb8dfc7e96733e25481fba3a3502b Mon Sep 17 00:00:00 2001 From: Shai Mishali Date: Wed, 21 Jan 2026 14:02:07 +0200 Subject: [PATCH 2/6] Update package.swift --- .github/workflows/tests.yml | 18 ++++++++---------- Package.swift | 4 ++-- Package@swift-5.9.swift | 34 ++++++++++------------------------ 3 files changed, 20 insertions(+), 36 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7f831e281..af8963858 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,11 +24,18 @@ jobs: - name: Run Tests run: CI=1 ./scripts/all-tests.sh "${{ matrix.environment }}" + linux: + name: "Test (Android)" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Test Swift Package on Android" + uses: skiptools/swift-android-action@v2 + # We're having some issues with the Linux tests, so we're disabling them for now. # Hopefully we'll be able to fix and re-enable them soon. # Even more hopefully that I won't git blame this comment in the future and see it was 5 years ago :) # Some more info on part of the breakage is here: https://forums.swift.org/t/swift-6-0-regression-cannot-inherit-from-some-foundation-classes-on-linux-because-it-has-overridable-members-that-could-not-be-loaded/74794 - # linux: # name: "Test (Linux)" # runs-on: ubuntu-latest @@ -38,12 +45,3 @@ jobs: # - name: Run tests # run: CI=1 ./scripts/all-tests.sh "Unix" - -linux: - name: "Test (Android)" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: "Test Swift Package on Android" - uses: skiptools/swift-android-action@v2 - diff --git a/Package.swift b/Package.swift index ebc51712f..239217daa 100644 --- a/Package.swift +++ b/Package.swift @@ -26,7 +26,7 @@ extension Target { extension Target { static func rxCocoa() -> [Target] { - #if os(Linux) + #if !canImport(Darwin) return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] #else return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] @@ -34,7 +34,7 @@ extension Target { } static func rxCocoaRuntime() -> [Target] { - #if os(Linux) + #if !canImport(Darwin) return [] #else return [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index d9795a302..87ecf710d 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -1,21 +1,7 @@ // swift-tools-version:5.9 -import Foundation import PackageDescription -func targetsDarwin() -> Bool { - if (ProcessInfo.processInfo.environment["TARGET_OS_ANDROID"] ?? "0") != "0" { - // we are building for Android, and so Cocoa is not available - return false - } - - #if !canImport(Darwin) - return false // Linux, Windows, etc. - #else - return true // macOS, iOS, etc. - #endif -} - let buildTests = false extension Product { @@ -40,19 +26,19 @@ extension Target { extension Target { static func rxCocoa() -> [Target] { - if !targetsDarwin() { - [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] - } else { - [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] - } + #if !canImport(Darwin) + return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] + #else + return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] + #endif } static func rxCocoaRuntime() -> [Target] { - if !targetsDarwin() { - [] - } else { - [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] - } + #if !canImport(Darwin) + return [] + #else + return [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] + #endif } static func allTests() -> [Target] { From 7e5c23655b036fe71ce931ac9b6fd2b785b8c97d Mon Sep 17 00:00:00 2001 From: Shai Mishali Date: Wed, 21 Jan 2026 14:32:56 +0200 Subject: [PATCH 3/6] Fix darwin imports --- Platform/Platform.Darwin.swift | 2 +- Platform/Platform.Linux.swift | 2 +- RxBlocking/RunLoopLock.swift | 4 ++-- RxCocoa/Foundation/URLSession+Rx.swift | 2 +- .../Traits/SharedSequence/SchedulerType+SharedSequence.swift | 4 ++-- RxSwift/RxMutableBox.swift | 4 ++-- RxSwift/Schedulers/CurrentThreadScheduler.swift | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Platform/Platform.Darwin.swift b/Platform/Platform.Darwin.swift index 370b73065..26771ab7e 100644 --- a/Platform/Platform.Darwin.swift +++ b/Platform/Platform.Darwin.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS) +#if canImport(Darwin) import Darwin import Foundation diff --git a/Platform/Platform.Linux.swift b/Platform/Platform.Linux.swift index 379a45a98..7a9856629 100644 --- a/Platform/Platform.Linux.swift +++ b/Platform/Platform.Linux.swift @@ -6,7 +6,7 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // -#if os(Linux) +#if !canImport(Darwin) import Foundation diff --git a/RxBlocking/RunLoopLock.swift b/RxBlocking/RunLoopLock.swift index 0943f623e..dbe747c8f 100644 --- a/RxBlocking/RunLoopLock.swift +++ b/RxBlocking/RunLoopLock.swift @@ -10,7 +10,7 @@ import CoreFoundation import Foundation import RxSwift -#if os(Linux) +#if !canImport(Darwin) import Foundation let runLoopMode: RunLoop.Mode = .default @@ -61,7 +61,7 @@ final class RunLoopLock { fatalError("Run can be only called once") } if let timeout { - #if os(Linux) + #if !canImport(Darwin) let runLoopResult = CFRunLoopRunInMode(runLoopModeRaw, timeout, false) #else let runLoopResult = CFRunLoopRunInMode(runLoopMode, timeout, false) diff --git a/RxCocoa/Foundation/URLSession+Rx.swift b/RxCocoa/Foundation/URLSession+Rx.swift index 5cbbb3ce6..2e7b48d49 100644 --- a/RxCocoa/Foundation/URLSession+Rx.swift +++ b/RxCocoa/Foundation/URLSession+Rx.swift @@ -122,7 +122,7 @@ public extension Reactive where Base: URLSession { if URLSession.rx.shouldLogRequest(request) { let interval = Date().timeIntervalSince(d ?? Date()) print(convertURLRequestToCurlCommand(request)) - #if os(Linux) + #if !canImport(Darwin) print(convertResponseToString(response, error.flatMap { $0 as NSError }, interval)) #else print(convertResponseToString(response, error.map { $0 as NSError }, interval)) diff --git a/RxCocoa/Traits/SharedSequence/SchedulerType+SharedSequence.swift b/RxCocoa/Traits/SharedSequence/SchedulerType+SharedSequence.swift index d6ac33ad6..0bbc8de48 100644 --- a/RxCocoa/Traits/SharedSequence/SchedulerType+SharedSequence.swift +++ b/RxCocoa/Traits/SharedSequence/SchedulerType+SharedSequence.swift @@ -43,7 +43,7 @@ public enum SharingScheduler { } } -#if os(Linux) +#if !canImport(Darwin) import Glibc #else import Foundation @@ -51,7 +51,7 @@ import Foundation func _forceCompilerToStopDoingInsaneOptimizationsThatBreakCode(_ scheduler: () -> SchedulerType) { let a: Int32 = 1 - #if os(Linux) + #if !canImport(Darwin) let b = 314 + Int32(Glibc.random() & 1) #else let b = 314 + Int32(arc4random() & 1) diff --git a/RxSwift/RxMutableBox.swift b/RxSwift/RxMutableBox.swift index 4e07a7ce0..47551b89b 100644 --- a/RxSwift/RxMutableBox.swift +++ b/RxSwift/RxMutableBox.swift @@ -6,12 +6,12 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // -#if os(Linux) +#if !canImport(Darwin) /// As Swift 5 was released, A patch to `Thread` for Linux /// changed `threadDictionary` to a `NSMutableDictionary` instead of /// a `Dictionary`: https://github.com/apple/swift-corelibs-foundation/pull/1762/files /// -/// This means that on Linux specifically, `RxMutableBox` must be a `NSObject` +/// This means that on non-Darwin platforms (Linux, Android, etc.), `RxMutableBox` must be a `NSObject` /// or it won't be possible to store it in `Thread.threadDictionary`. /// /// For more information, read the discussion at: diff --git a/RxSwift/Schedulers/CurrentThreadScheduler.swift b/RxSwift/Schedulers/CurrentThreadScheduler.swift index 81ac93148..8d526e216 100644 --- a/RxSwift/Schedulers/CurrentThreadScheduler.swift +++ b/RxSwift/Schedulers/CurrentThreadScheduler.swift @@ -9,7 +9,7 @@ import Dispatch import Foundation -#if os(Linux) +#if !canImport(Darwin) fileprivate enum CurrentThreadSchedulerQueueKey { fileprivate static let instance = "RxSwift.CurrentThreadScheduler.Queue" } From ce1e8552c1972cf055f677bba768ae8d1a1d8c7f Mon Sep 17 00:00:00 2001 From: Shai Mishali Date: Wed, 21 Jan 2026 16:52:25 +0200 Subject: [PATCH 4/6] Exclude RxCocoa/RxCocoaRuntime from Android builds with conditional dependencies - Use environment variable detection for Android builds (ANDROID_DATA/ANDROID_ROOT) - RxCocoa and RxCocoaRuntime targets/products are excluded on non-Darwin platforms - Use conditional dependencies to specify RxCocoaRuntime only on specific Darwin platforms --- Package.swift | 66 +++++++++++++++++++++++++++++++++-------- Package@swift-5.9.swift | 66 +++++++++++++++++++++++++++++++++-------- 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/Package.swift b/Package.swift index 239217daa..71f81745d 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,24 @@ // swift-tools-version:5.5 +import Foundation import PackageDescription +func isTargetingDarwin() -> Bool { + // Check if building for Android or other non-Darwin platforms + if (ProcessInfo.processInfo.environment["ANDROID_DATA"] != nil) || + (ProcessInfo.processInfo.environment["ANDROID_ROOT"] != nil) { + return false + } + + #if canImport(Darwin) + return true + #else + return false + #endif +} + let buildTests = false +let targetsDarwin = isTargetingDarwin() extension Product { static func allTests() -> [Product] { @@ -12,6 +28,17 @@ extension Product { [] } } + + static func rxCocoaProducts() -> [Product] { + if targetsDarwin { + return [ + .library(name: "RxCocoa", targets: ["RxCocoa"]), + .library(name: "RxCocoa-Dynamic", type: .dynamic, targets: ["RxCocoa"]), + ] + } else { + return [] + } + } } extension Target { @@ -26,19 +53,35 @@ extension Target { extension Target { static func rxCocoa() -> [Target] { - #if !canImport(Darwin) - return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] - #else - return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] - #endif + if !targetsDarwin { + return [] + } else { + return [ + .target( + name: "RxCocoa", + dependencies: [ + "RxSwift", + "RxRelay", + .target(name: "RxCocoaRuntime", condition: .when(platforms: [.iOS, .macOS, .tvOS, .watchOS])) + ], + resources: [.copy("PrivacyInfo.xcprivacy")] + ) + ] + } } static func rxCocoaRuntime() -> [Target] { - #if !canImport(Darwin) - return [] - #else - return [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] - #endif + if !targetsDarwin { + return [] + } else { + return [ + .target( + name: "RxCocoaRuntime", + dependencies: ["RxSwift"], + resources: [.copy("PrivacyInfo.xcprivacy")] + ) + ] + } } static func allTests() -> [Target] { @@ -56,16 +99,15 @@ let package = Package( products: ([ [ .library(name: "RxSwift", targets: ["RxSwift"]), - .library(name: "RxCocoa", targets: ["RxCocoa"]), .library(name: "RxRelay", targets: ["RxRelay"]), .library(name: "RxBlocking", targets: ["RxBlocking"]), .library(name: "RxTest", targets: ["RxTest"]), .library(name: "RxSwift-Dynamic", type: .dynamic, targets: ["RxSwift"]), - .library(name: "RxCocoa-Dynamic", type: .dynamic, targets: ["RxCocoa"]), .library(name: "RxRelay-Dynamic", type: .dynamic, targets: ["RxRelay"]), .library(name: "RxBlocking-Dynamic", type: .dynamic, targets: ["RxBlocking"]), .library(name: "RxTest-Dynamic", type: .dynamic, targets: ["RxTest"]), ], + Product.rxCocoaProducts(), Product.allTests(), ] as [[Product]]).flatMap(\.self), targets: ([ diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index 87ecf710d..a4914ee34 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -1,8 +1,24 @@ // swift-tools-version:5.9 +import Foundation import PackageDescription +func isTargetingDarwin() -> Bool { + // Check if building for Android or other non-Darwin platforms + if (ProcessInfo.processInfo.environment["ANDROID_DATA"] != nil) || + (ProcessInfo.processInfo.environment["ANDROID_ROOT"] != nil) { + return false + } + + #if canImport(Darwin) + return true + #else + return false + #endif +} + let buildTests = false +let targetsDarwin = isTargetingDarwin() extension Product { static func allTests() -> [Product] { @@ -12,6 +28,17 @@ extension Product { [] } } + + static func rxCocoaProducts() -> [Product] { + if targetsDarwin { + return [ + .library(name: "RxCocoa", targets: ["RxCocoa"]), + .library(name: "RxCocoa-Dynamic", type: .dynamic, targets: ["RxCocoa"]), + ] + } else { + return [] + } + } } extension Target { @@ -26,19 +53,35 @@ extension Target { extension Target { static func rxCocoa() -> [Target] { - #if !canImport(Darwin) - return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay"])] - #else - return [.rxTarget(name: "RxCocoa", dependencies: ["RxSwift", "RxRelay", "RxCocoaRuntime"])] - #endif + if !targetsDarwin { + return [] + } else { + return [ + .target( + name: "RxCocoa", + dependencies: [ + "RxSwift", + "RxRelay", + .target(name: "RxCocoaRuntime", condition: .when(platforms: [.iOS, .macOS, .tvOS, .watchOS, .visionOS])) + ], + resources: [.copy("PrivacyInfo.xcprivacy")] + ) + ] + } } static func rxCocoaRuntime() -> [Target] { - #if !canImport(Darwin) - return [] - #else - return [.rxTarget(name: "RxCocoaRuntime", dependencies: ["RxSwift"])] - #endif + if !targetsDarwin { + return [] + } else { + return [ + .target( + name: "RxCocoaRuntime", + dependencies: ["RxSwift"], + resources: [.copy("PrivacyInfo.xcprivacy")] + ) + ] + } } static func allTests() -> [Target] { @@ -56,16 +99,15 @@ let package = Package( products: ([ [ .library(name: "RxSwift", targets: ["RxSwift"]), - .library(name: "RxCocoa", targets: ["RxCocoa"]), .library(name: "RxRelay", targets: ["RxRelay"]), .library(name: "RxBlocking", targets: ["RxBlocking"]), .library(name: "RxTest", targets: ["RxTest"]), .library(name: "RxSwift-Dynamic", type: .dynamic, targets: ["RxSwift"]), - .library(name: "RxCocoa-Dynamic", type: .dynamic, targets: ["RxCocoa"]), .library(name: "RxRelay-Dynamic", type: .dynamic, targets: ["RxRelay"]), .library(name: "RxBlocking-Dynamic", type: .dynamic, targets: ["RxBlocking"]), .library(name: "RxTest-Dynamic", type: .dynamic, targets: ["RxTest"]), ], + Product.rxCocoaProducts(), Product.allTests(), ] as [[Product]]).flatMap(\.self), targets: ([ From 4b0eb1824c0349f759ee33ca62b5cb8eae067451 Mon Sep 17 00:00:00 2001 From: Shai Mishali Date: Wed, 21 Jan 2026 16:59:37 +0200 Subject: [PATCH 5/6] Remove incorrect @retroactive attributes from same-package Equatable conformances The @retroactive attribute is only needed when adding protocol conformance to types from different modules. CompletableEvent, Event, and MaybeEvent are all in the same package, so @retroactive should not be used. --- RxTest/Event+Equatable.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RxTest/Event+Equatable.swift b/RxTest/Event+Equatable.swift index eabf8910d..cda88f276 100644 --- a/RxTest/Event+Equatable.swift +++ b/RxTest/Event+Equatable.swift @@ -88,7 +88,7 @@ func equals(lhs: MaybeEvent, rhs: MaybeEvent Bool { switch (lhs, rhs) { case (.completed, .completed): return true @@ -108,13 +108,13 @@ extension CompletableEvent: @retroactive Equatable { } } -extension Event: @retroactive Equatable where Element: Equatable { +extension Event: Equatable where Element: Equatable { public static func == (lhs: Event, rhs: Event) -> Bool { equals(lhs: lhs, rhs: rhs) } } -extension MaybeEvent: @retroactive Equatable where Element: Equatable { +extension MaybeEvent: Equatable where Element: Equatable { public static func == (lhs: MaybeEvent, rhs: MaybeEvent) -> Bool { equals(lhs: lhs, rhs: rhs) } From b2a2db6e59abba5cc6a23bb2b3fb3a043d4f448f Mon Sep 17 00:00:00 2001 From: Adrian Schoenig Date: Mon, 2 Feb 2026 20:09:21 +1100 Subject: [PATCH 6/6] macCatalyst compile fix --- Package@swift-5.9.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index a4914ee34..9a5fc1659 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -62,7 +62,7 @@ extension Target { dependencies: [ "RxSwift", "RxRelay", - .target(name: "RxCocoaRuntime", condition: .when(platforms: [.iOS, .macOS, .tvOS, .watchOS, .visionOS])) + .target(name: "RxCocoaRuntime", condition: .when(platforms: [.iOS, .macCatalyst, .macOS, .tvOS, .watchOS, .visionOS])) ], resources: [.copy("PrivacyInfo.xcprivacy")] )