From 82b371ecad90e44995d77f1d8912d8c681a5b721 Mon Sep 17 00:00:00 2001 From: George Barnett Date: Fri, 28 Mar 2025 10:11:44 +0000 Subject: [PATCH] Remove platforms from manifest Motivation: Adding or raising the deployment platforms in the package manifest is a SemVer major breaking change as consumers must also add or raise their deployment platforms. This is a known limitation of SwiftPM. Unforunately this means that it's very difficult for non-leaf packages to adopt packages which declare their platforms in the manifest. Doing so puts the brakes on adoption and ecosystem growth. For 'core' packages like this one availability constraints should be expressed on declarations rather than in the manifest. Modifications: - Remove platforms from the package manifest - Add availability annotations to types which require it Result: This package can be more widely adopted --- Package.swift | 8 +------- .../AsyncCancelOnGracefulShutdownSequence.swift | 4 ++++ .../AsyncGracefulShutdownSequence.swift | 1 + Sources/ServiceLifecycle/CancellationWaiter.swift | 1 + Sources/ServiceLifecycle/GracefulShutdown.swift | 12 ++++++++++++ Sources/ServiceLifecycle/Service.swift | 1 + Sources/ServiceLifecycle/ServiceGroup.swift | 2 ++ .../ServiceLifecycle/ServiceGroupConfiguration.swift | 1 + .../ServiceLifecycleTestKit/GracefulShutdown.swift | 2 ++ Sources/UnixSignals/UnixSignalsSequence.swift | 3 +++ .../GracefulShutdownTests.swift | 5 +++++ 11 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index 8ce3f41..2f6b553 100644 --- a/Package.swift +++ b/Package.swift @@ -3,12 +3,6 @@ import PackageDescription let package = Package( name: "swift-service-lifecycle", - platforms: [ - .macOS(.v10_15), - .iOS(.v13), - .watchOS(.v6), - .tvOS(.v13), - ], products: [ .library( name: "ServiceLifecycle", @@ -30,7 +24,7 @@ let package = Package( ), .package( url: "https://github.com/apple/swift-async-algorithms.git", - from: "1.0.0" + from: "1.0.4" ), ], targets: [ diff --git a/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift b/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift index 47fef51..0144264 100644 --- a/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift +++ b/Sources/ServiceLifecycle/AsyncCancelOnGracefulShutdownSequence.swift @@ -14,6 +14,7 @@ import AsyncAlgorithms +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension AsyncSequence where Self: Sendable, Element: Sendable { /// Creates an asynchronous sequence that is cancelled once graceful shutdown has triggered. /// @@ -24,6 +25,7 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { } /// An asynchronous sequence that is cancelled once graceful shutdown has triggered. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public struct AsyncCancelOnGracefulShutdownSequence: AsyncSequence, Sendable where Base.Element: Sendable { @usableFromInline @@ -101,6 +103,7 @@ where Base.Element: Sendable { /// This is just a helper extension and sequence to allow us to get the `nil` value as an element of the sequence. /// We need this since merge is only finishing when both upstreams are finished but we need to finish when either is done. /// In the future, we should move to something in async algorithms if it exists. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension AsyncSequence where Self: Sendable, Element: Sendable { @inlinable func mapNil() -> AsyncMapNilSequence { @@ -109,6 +112,7 @@ extension AsyncSequence where Self: Sendable, Element: Sendable { } @usableFromInline +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) struct AsyncMapNilSequence: AsyncSequence, Sendable where Base.Element: Sendable { @usableFromInline enum ElementOrEnd: Sendable { diff --git a/Sources/ServiceLifecycle/AsyncGracefulShutdownSequence.swift b/Sources/ServiceLifecycle/AsyncGracefulShutdownSequence.swift index 209776c..24ce9de 100644 --- a/Sources/ServiceLifecycle/AsyncGracefulShutdownSequence.swift +++ b/Sources/ServiceLifecycle/AsyncGracefulShutdownSequence.swift @@ -18,6 +18,7 @@ /// /// - Note: This sequence respects cancellation and thus is `throwing`. @usableFromInline +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) struct AsyncGracefulShutdownSequence: AsyncSequence, Sendable { @usableFromInline typealias Element = CancellationWaiter.Reason diff --git a/Sources/ServiceLifecycle/CancellationWaiter.swift b/Sources/ServiceLifecycle/CancellationWaiter.swift index 1476fec..565fc95 100644 --- a/Sources/ServiceLifecycle/CancellationWaiter.swift +++ b/Sources/ServiceLifecycle/CancellationWaiter.swift @@ -14,6 +14,7 @@ /// An actor that provides a function to wait on cancellation/graceful shutdown. @usableFromInline +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) actor CancellationWaiter { @usableFromInline enum Reason: Sendable { diff --git a/Sources/ServiceLifecycle/GracefulShutdown.swift b/Sources/ServiceLifecycle/GracefulShutdown.swift index 56daca0..7d210ce 100644 --- a/Sources/ServiceLifecycle/GracefulShutdown.swift +++ b/Sources/ServiceLifecycle/GracefulShutdown.swift @@ -37,6 +37,7 @@ import ConcurrencyHelpers /// - handler: The handler which is invoked once graceful shutdown has been triggered. // Unsafely inheriting the executor is safe to do here since we are not calling any other async method // except the operation. This makes sure no other executor hops would occur here. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func withGracefulShutdownHandler( isolation: isolated (any Actor)? = #isolation, operation: () async throws -> T, @@ -58,6 +59,7 @@ public func withGracefulShutdownHandler( } @available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @_disfavoredOverload public func withGracefulShutdownHandler( operation: () async throws -> T, @@ -83,6 +85,7 @@ public func withGracefulShutdownHandler( // Swift versions since the semantics changed. @_disfavoredOverload @_unsafeInheritExecutor +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func withGracefulShutdownHandler( operation: () async throws -> T, onGracefulShutdown handler: @Sendable @escaping () -> Void @@ -127,6 +130,7 @@ public func withGracefulShutdownHandler( /// - handler: The handler which is invoked once graceful shutdown or task cancellation has been triggered. // Unsafely inheriting the executor is safe to do here since we are not calling any other async method // except the operation. This makes sure no other executor hops would occur here. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func withTaskCancellationOrGracefulShutdownHandler( isolation: isolated (any Actor)? = #isolation, operation: () async throws -> T, @@ -139,6 +143,7 @@ public func withTaskCancellationOrGracefulShutdownHandler( } } @available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @_disfavoredOverload public func withTaskCancellationOrGracefulShutdownHandler( operation: () async throws -> T, @@ -152,6 +157,7 @@ public func withTaskCancellationOrGracefulShutdownHandler( } #else @available(*, deprecated, message: "Use the method with the isolation parameter instead.") +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @_disfavoredOverload @_unsafeInheritExecutor public func withTaskCancellationOrGracefulShutdownHandler( @@ -172,6 +178,7 @@ public func withTaskCancellationOrGracefulShutdownHandler( /// graceful shutdown is triggered then this method will throw a `CancellationError`. /// /// - Throws: `CancellationError` if the task is cancelled. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func gracefulShutdown() async throws { switch await AsyncGracefulShutdownSequence().first(where: { _ in true }) { case .cancelled: @@ -193,6 +200,7 @@ enum ValueOrGracefulShutdown: Sendable { /// Cancels the closure when a graceful shutdown was triggered. /// /// - Parameter operation: The actual operation. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func cancelWhenGracefulShutdown( _ operation: @Sendable @escaping () async throws -> T ) async rethrows -> T { @@ -243,12 +251,14 @@ public func cancelWhenGracefulShutdown( // renamed pattern has been shown to cause compiler crashes in 5.x compilers. @available(*, deprecated, message: "renamed to cancelWhenGracefulShutdown") #endif +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func cancelOnGracefulShutdown( _ operation: @Sendable @escaping () async throws -> T ) async rethrows -> T? { return try await cancelWhenGracefulShutdown(operation) } +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension Task where Success == Never, Failure == Never { /// A Boolean value that indicates whether the task is gracefully shutting down /// @@ -263,6 +273,7 @@ extension Task where Success == Never, Failure == Never { } @_spi(TestKit) +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public enum TaskLocals { @TaskLocal @_spi(TestKit) @@ -270,6 +281,7 @@ public enum TaskLocals { } @_spi(TestKit) +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public final class GracefulShutdownManager: @unchecked Sendable { struct Handler { /// The id of the handler. diff --git a/Sources/ServiceLifecycle/Service.swift b/Sources/ServiceLifecycle/Service.swift index 000303f..8c5a1b6 100644 --- a/Sources/ServiceLifecycle/Service.swift +++ b/Sources/ServiceLifecycle/Service.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// /// This is the basic protocol that a service has to implement. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public protocol Service: Sendable { /// This method is called when the ``ServiceGroup`` is starting all the services. /// diff --git a/Sources/ServiceLifecycle/ServiceGroup.swift b/Sources/ServiceLifecycle/ServiceGroup.swift index 580313a..48e882d 100644 --- a/Sources/ServiceLifecycle/ServiceGroup.swift +++ b/Sources/ServiceLifecycle/ServiceGroup.swift @@ -17,6 +17,7 @@ import UnixSignals import AsyncAlgorithms /// A ``ServiceGroup`` is responsible for running a number of services, setting up signal handling and signalling graceful shutdown to the services. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public actor ServiceGroup: Sendable, Service { /// The internal state of the ``ServiceGroup``. private enum State { @@ -893,6 +894,7 @@ public actor ServiceGroup: Sendable, Service { } // This should be removed once we support Swift 5.9+ +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension AsyncStream { fileprivate static func makeStream( of elementType: Element.Type = Element.self, diff --git a/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift b/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift index 30b21ff..c1beef8 100644 --- a/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift +++ b/Sources/ServiceLifecycle/ServiceGroupConfiguration.swift @@ -18,6 +18,7 @@ import UnixSignals let deprecatedLoggerLabel = "service-lifecycle-deprecated-method-logger" /// The configuration for the ``ServiceGroup``. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public struct ServiceGroupConfiguration: Sendable { /// The group's logging configuration. public struct LoggingConfiguration: Sendable { diff --git a/Sources/ServiceLifecycleTestKit/GracefulShutdown.swift b/Sources/ServiceLifecycleTestKit/GracefulShutdown.swift index e37c0c5..bfdf247 100644 --- a/Sources/ServiceLifecycleTestKit/GracefulShutdown.swift +++ b/Sources/ServiceLifecycleTestKit/GracefulShutdown.swift @@ -18,6 +18,7 @@ /// /// It is passed to the `operation` closure of the ``testGracefulShutdown(operation:)`` method and allows /// to trigger the graceful shutdown for testing purposes. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public struct GracefulShutdownTestTrigger: Sendable { private let gracefulShutdownManager: GracefulShutdownManager @@ -35,6 +36,7 @@ public struct GracefulShutdownTestTrigger: Sendable { /// /// Call the code that you want to test inside the `operation` closure and trigger the graceful shutdown by calling ``GracefulShutdownTestTrigger/triggerGracefulShutdown()`` /// on the ``GracefulShutdownTestTrigger`` that is passed to the `operation` closure. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public func testGracefulShutdown(operation: (GracefulShutdownTestTrigger) async throws -> T) async rethrows -> T { let gracefulShutdownManager = GracefulShutdownManager() return try await TaskLocals.$gracefulShutdownManager.withValue(gracefulShutdownManager) { diff --git a/Sources/UnixSignals/UnixSignalsSequence.swift b/Sources/UnixSignals/UnixSignalsSequence.swift index 49d0e81..4627792 100644 --- a/Sources/UnixSignals/UnixSignalsSequence.swift +++ b/Sources/UnixSignals/UnixSignalsSequence.swift @@ -33,6 +33,7 @@ import ConcurrencyHelpers /// /// - Important: There can only be a single signal handler for a signal installed. So you should avoid creating multiple handlers /// for the same signal. +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public struct UnixSignalsSequence: AsyncSequence, Sendable { private static let queue = DispatchQueue(label: "com.service-lifecycle.unix-signals") @@ -74,6 +75,7 @@ public struct UnixSignalsSequence: AsyncSequence, Sendable { } } +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension UnixSignalsSequence { fileprivate final class Storage: @unchecked Sendable { private let stateMachine: LockedValueBox @@ -169,6 +171,7 @@ extension UnixSignalsSequence { } } +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) extension UnixSignalsSequence { fileprivate struct StateMachine { private enum State { diff --git a/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift b/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift index a330a92..6569087 100644 --- a/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift +++ b/Tests/ServiceLifecycleTests/GracefulShutdownTests.swift @@ -16,6 +16,7 @@ import ServiceLifecycle import ServiceLifecycleTestKit import XCTest +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) final class GracefulShutdownTests: XCTestCase { func testWithGracefulShutdownHandler() async { var cont: AsyncStream.Continuation! @@ -255,6 +256,7 @@ final class GracefulShutdownTests: XCTestCase { } } + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func testWaitForGracefulShutdown() async throws { try await testGracefulShutdown { gracefulShutdownTestTrigger in try await withThrowingTaskGroup(of: Void.self) { group in @@ -274,6 +276,7 @@ final class GracefulShutdownTests: XCTestCase { } } + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func testWaitForGracefulShutdown_WhenAlreadyShutdown() async throws { try await testGracefulShutdown { gracefulShutdownTestTrigger in gracefulShutdownTestTrigger.triggerGracefulShutdown() @@ -355,6 +358,7 @@ final class GracefulShutdownTests: XCTestCase { } } + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func testCancelWhenGracefulShutdownSurvivesCancellation() async throws { await withTaskGroup(of: Void.self) { group in group.addTask { @@ -375,6 +379,7 @@ final class GracefulShutdownTests: XCTestCase { } } + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) func testCancelWhenGracefulShutdownSurvivesErrorThrown() async throws { struct MyError: Error, Equatable {}