From d599759d6eaadbd9d406e78f9a23e44a21e82f9c Mon Sep 17 00:00:00 2001 From: Andreas Bauer Date: Wed, 10 Jul 2024 13:28:22 +0200 Subject: [PATCH 1/4] Enable strict concurrency --- .swiftlint.yml | 4 -- Package.swift | 37 +++++++++++++- .../XCTRuntimeAssertionInjector.swift | 51 +++++++++++++++---- .../XCTRuntimePrecondition.swift | 2 +- 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 9afaed8..3f40bb4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -141,8 +141,6 @@ only_rules: - implicitly_unwrapped_optional # Identifiers should use inclusive language that avoids discrimination against groups of people based on race, gender, or socioeconomic status - inclusive_language - # If defer is at the end of its parent scope, it will be executed right where it is anyway. - - inert_defer # Prefer using Set.isDisjoint(with:) over Set.intersection(_:).isEmpty. - is_disjoint # Discouraged explicit usage of the default separator. @@ -329,8 +327,6 @@ only_rules: - unowned_variable_capture # Catch statements should not declare error variables without type casting. - untyped_error_in_catch - # Unused reference in a capture list should be removed. - - unused_capture_list # Unused parameter in a closure should be replaced with _. - unused_closure_parameter # Unused control flow label should be removed. diff --git a/Package.swift b/Package.swift index 97dc50c..610a2dc 100644 --- a/Package.swift +++ b/Package.swift @@ -8,8 +8,15 @@ // SPDX-License-Identifier: MIT // +import class Foundation.ProcessInfo import PackageDescription +#if swift(<6) +let swiftConcurrency: SwiftSetting = .enableExperimentalFeature("StrictConcurrency") +#else +let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("StrictConcurrency") +#endif + let package = Package( name: "XCTRuntimeAssertions", @@ -23,15 +30,41 @@ let package = Package( products: [ .library(name: "XCTRuntimeAssertions", targets: ["XCTRuntimeAssertions"]) ], + dependencies: swiftLintPackage(), targets: [ .target( - name: "XCTRuntimeAssertions" + name: "XCTRuntimeAssertions", + swiftSettings: [ + swiftConcurrency + ], + plugins: [] + swiftLintPlugin() ), .testTarget( name: "XCTRuntimeAssertionsTests", dependencies: [ .target(name: "XCTRuntimeAssertions") - ] + ], + swiftSettings: [ + swiftConcurrency + ], + plugins: [] + swiftLintPlugin() ) ] ) + +func swiftLintPlugin() -> [Target.PluginUsage] { + // Fully quit Xcode and open again with `open --env SPEZI_DEVELOPMENT_SWIFTLINT /Applications/Xcode.app` + if ProcessInfo.processInfo.environment["SPEZI_DEVELOPMENT_SWIFTLINT"] != nil { + [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")] + } else { + [] + } +} + +func swiftLintPackage() -> [PackageDescription.Package.Dependency] { + if ProcessInfo.processInfo.environment["SPEZI_DEVELOPMENT_SWIFTLINT"] != nil { + [.package(url: "https://github.com/realm/SwiftLint.git", .upToNextMinor(from: "0.55.1"))] + } else { + [] + } +} diff --git a/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift b/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift index 797fafd..87fa3fb 100644 --- a/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift +++ b/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift @@ -9,10 +9,39 @@ #if DEBUG || TEST import Foundation +private final class RuntimeInjections: Sendable { + private nonisolated(unsafe) var injected: [XCTRuntimeAssertionInjector] = [] + private let lock = NSLock() + + @inlinable var isEmpty: Bool { + injected.isEmpty + } + + var injections: [XCTRuntimeAssertionInjector] { + lock.withLock { + injected + } + } + + init() {} + + func append(_ element: XCTRuntimeAssertionInjector) { + lock.withLock { + injected.append(element) + } + } + + func removeAll(for id: UUID) { + lock.withLock { + injected.removeAll(where: { $0.id == id }) + } + } +} + class XCTRuntimeAssertionInjector { - private static var injected: [XCTRuntimeAssertionInjector] = [] - + private static let injection = RuntimeInjections() + let id: UUID private let _assert: (UUID, () -> Bool, () -> String, StaticString, UInt) -> Void @@ -65,30 +94,32 @@ class XCTRuntimeAssertionInjector { static func inject(runtimeAssertionInjector: XCTRuntimeAssertionInjector) { - injected.append(runtimeAssertionInjector) + injection.append(runtimeAssertionInjector) } static func removeRuntimeAssertionInjector(withId id: UUID) { - injected.removeAll(where: { $0.id == id }) + injection.removeAll(for: id) } - + + @inlinable static func assert(_ condition: () -> Bool, message: () -> String, file: StaticString, line: UInt) { - if injected.isEmpty { + if injection.isEmpty { Swift.assert(condition(), message(), file: file, line: line) } - for runtimeAssertionInjector in injected { + for runtimeAssertionInjector in injection.injections { runtimeAssertionInjector._assert(runtimeAssertionInjector.id, condition, message, file, line) } } - + + @inlinable static func precondition(_ condition: () -> Bool, message: () -> String, file: StaticString, line: UInt) { - if injected.isEmpty { + if injection.isEmpty { Swift.precondition(condition(), message(), file: file, line: line) } - for runtimeAssertionInjector in injected { + for runtimeAssertionInjector in injection.injections { runtimeAssertionInjector._precondition(runtimeAssertionInjector.id, condition, message, file, line) } } diff --git a/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift b/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift index c9e0c41..a7406a0 100644 --- a/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift +++ b/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift @@ -74,7 +74,7 @@ public func XCTRuntimePrecondition( _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, - _ expression: @escaping () async -> Void + _ expression: @escaping @Sendable () async -> Void ) throws { let fulfillmentCount = Counter() let xctRuntimeAssertionId = setupXCTRuntimeAssertionInjector( From 3c3392a089c09b91ca439aa31878eb88932fe99c Mon Sep 17 00:00:00 2001 From: Andreas Bauer Date: Thu, 11 Jul 2024 11:22:12 +0200 Subject: [PATCH 2/4] Try again --- Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift b/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift index 87fa3fb..3b83ebe 100644 --- a/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift +++ b/Sources/XCTRuntimeAssertions/XCTRuntimeAssertionInjector.swift @@ -9,6 +9,7 @@ #if DEBUG || TEST import Foundation + private final class RuntimeInjections: Sendable { private nonisolated(unsafe) var injected: [XCTRuntimeAssertionInjector] = [] private let lock = NSLock() From f9b0c9a47a4dc8da56058c47d609a985daebe128 Mon Sep 17 00:00:00 2001 From: Andreas Bauer Date: Thu, 11 Jul 2024 14:19:54 +0200 Subject: [PATCH 3/4] Make explicit that runtime precondition is executed on a async thread --- Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift b/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift index a7406a0..1cd91ef 100644 --- a/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift +++ b/Sources/XCTRuntimeAssertions/XCTRuntimePrecondition.swift @@ -26,7 +26,7 @@ public func XCTRuntimePrecondition( _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, - _ expression: @escaping () -> Void + _ expression: @escaping @Sendable () -> Void ) throws { let fulfillmentCount = Counter() let xctRuntimeAssertionId = setupXCTRuntimeAssertionInjector( From 8dd4833f633ceae52633991fc58601c09e34c53a Mon Sep 17 00:00:00 2001 From: Paul Schmiedmayer Date: Thu, 11 Jul 2024 17:33:57 -0700 Subject: [PATCH 4/4] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b4e1271..83fb8eb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -66,3 +66,5 @@ jobs: uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v2 with: coveragereports: XCTRuntimeAssertions-iOS.xcresult XCTRuntimeAssertions-watchOS.xcresult XCTRuntimeAssertions-visionOS.xcresult XCTRuntimeAssertions-tvOS.xcresult XCTRuntimeAssertions-macOS.xcresult + secrets: + token: ${{ secrets.CODECOV_TOKEN }}