From 49cc5e750668d806c2895dceac2af59e2ff09372 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 22:55:11 +1000 Subject: [PATCH 01/12] Replace `Lock` and friends with a better quality backport of `Mutex` --- Sources/Vexil/Pole.swift | 3 +- Sources/Vexil/Snapshots/SnapshotBuilder.swift | 4 +- .../Sources/FlagValueSourceCoordinator.swift | 8 +- Sources/Vexil/Utilities/Lock.swift | 251 ++++++++++++++++++ Sources/Vexil/Utilities/Locks.swift | 18 -- Sources/Vexil/Utilities/POSIXLocks.swift | 179 ------------- Sources/Vexil/Utilities/UnfairLocks.swift | 190 ------------- 7 files changed, 257 insertions(+), 396 deletions(-) create mode 100644 Sources/Vexil/Utilities/Lock.swift delete mode 100644 Sources/Vexil/Utilities/Locks.swift delete mode 100644 Sources/Vexil/Utilities/POSIXLocks.swift delete mode 100644 Sources/Vexil/Utilities/UnfairLocks.swift diff --git a/Sources/Vexil/Pole.swift b/Sources/Vexil/Pole.swift index 5ac10e31..7f5e43db 100644 --- a/Sources/Vexil/Pole.swift +++ b/Sources/Vexil/Pole.swift @@ -127,7 +127,6 @@ public final class FlagPole: Sendable where RootGroup: FlagContainer for task in manager.tasks { task.1.cancel() } - manager.stream?.finish() } } @@ -225,7 +224,7 @@ public final class FlagPole: Sendable where RootGroup: FlagContainer .prepend(rootGroup) } - private let _snapshotPublisher = UnfairLock]>, AsyncCompactMapSequence?>>, Snapshot>>>>, CurrentValueSubject]>, AsyncCompactMapSequence?>>, Snapshot>>.Element, Never>>>?>(uncheckedState: nil) + private let _snapshotPublisher = Lock]>, AsyncCompactMapSequence?>>, Snapshot>>>>, CurrentValueSubject]>, AsyncCompactMapSequence?>>, Snapshot>>.Element, Never>>>?>(uncheckedState: nil) /// A `Publisher` that will emit a snapshot of the flag pole every time flag values have changed. /// diff --git a/Sources/Vexil/Snapshots/SnapshotBuilder.swift b/Sources/Vexil/Snapshots/SnapshotBuilder.swift index 9658f1c7..259c96c0 100644 --- a/Sources/Vexil/Snapshots/SnapshotBuilder.swift +++ b/Sources/Vexil/Snapshots/SnapshotBuilder.swift @@ -77,9 +77,7 @@ extension Snapshot.Builder: FlagLookup { } var changes: FlagChangeStream { - AsyncStream { - $0.finish() - } + FlagChangeStream(allocation: nil) } } diff --git a/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift b/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift index bfc71ce6..79f63cbe 100644 --- a/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift +++ b/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift @@ -49,25 +49,25 @@ public final class FlagValueSourceCoordinator: Sendable where Source: No extension FlagValueSourceCoordinator: FlagValueSource { public var flagValueSourceID: String { - source.withLock { + source.withLockUnchecked { $0.flagValueSourceID } } public var flagValueSourceName: String { - source.withLock { + source.withLockUnchecked { $0.flagValueSourceName } } public func flagValue(key: String) -> Value? where Value: FlagValue { - source.withLock { + source.withLockUnchecked { $0.flagValue(key: key) } } public func setFlagValue(_ value: (some FlagValue)?, key: String) throws { - try source.withLock { + try source.withLockUnchecked { try $0.setFlagValue(value, key: key) } } diff --git a/Sources/Vexil/Utilities/Lock.swift b/Sources/Vexil/Utilities/Lock.swift new file mode 100644 index 00000000..1e313e4f --- /dev/null +++ b/Sources/Vexil/Utilities/Lock.swift @@ -0,0 +1,251 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Vexil open source project +// +// Copyright (c) 2025 Unsigned Apps and the open source contributors. +// Licensed under the MIT license +// +// See LICENSE for license information +// +// SPDX-License-Identifier: MIT +// +//===----------------------------------------------------------------------===// + +import Foundation + +#if canImport(os) +import os.lock +#endif + +struct Mutex: ~Copyable, Sendable { + + // Mutex isn't supposed to use an allocation, but the tools we would need to + // *avoid* an allocation are not available to us (we'd need to import `Builtin`, + // which is forbidden) + // + // Using an allocation here makes us a reference type, but since Mutex is not + // Copyable, it's pretty hard to *observe* that in practice. + private let platformLock: PlatformLock + + init(_ initialValue: consuming sending Value) { + self.platformLock = PlatformLock(initialValue) + } + + borrowing func withLock( + _ body: (inout sending Value) throws -> sending Result + ) rethrows -> sending Result { + try platformLock.withLock(body) + } + + } + +/// This is a lock that will use the most appropriate platform lock under the hood. On Apple platforms +/// it is effectively a wrapper around `OSAllocatedUnfairLock`. On non-Apple platforms it'll +/// use `pthread_lock` and friends. +struct Lock: Sendable { + + private let platformLock: PlatformLock + + init(uncheckedState: State) { + nonisolated(unsafe) let initialState = uncheckedState + self.platformLock = PlatformLock(initialState) + } + + init(initialState: State) where State: Sendable { + self.platformLock = PlatformLock(initialState) + } + + init(_ initialState: State) where State: Sendable { + self.platformLock = PlatformLock(initialState) + } + + func withLockUnchecked(_ body: (inout State) throws -> R) rethrows -> R { + try platformLock.withLock { + var state = $0 + do { + let result = try body(&state) + $0 = state + return result + } catch { + $0 = state + throw error + } + } + } + + func withLock(_ body: @Sendable (inout State) throws -> R) rethrows -> R { + try withLockUnchecked(body) + } + +} + +// MARK: - Platform-specific Locking Implementations + +struct PlatformLock { + +#if canImport(os) + + private typealias ActualPlatformLock = os_unfair_lock + + private static func createPlatformLock(_ lockPointer: UnsafeMutablePointer) { + lockPointer.initialize(to: os_unfair_lock()) + } + + private static func destroyPlatformLock(_ lockPointer: UnsafeMutablePointer) { + // os_unfair_lock doesn't have a destructor (which also means that it can't check that + // it's not locked when it's destroyed, which seems like a bad thing... + lockPointer.deinitialize(count: 1) + } + + private static func withPlatformLock( + _ lockPointer: UnsafeMutablePointer, + _ valuePointer: UnsafeMutablePointer>, + _ body: (inout sending Value) throws(E) -> sending R + ) throws(E) -> sending R { + os_unfair_lock_lock(lockPointer) + defer { + os_unfair_lock_unlock(lockPointer) + } + return try valuePointer.pointee.withValue(body) + } + +#else + + private typealias ActualPlatformLock = pthread_mutex_t + + private static func createPlatformLock(_ lockPointer: UnsafeMutablePointer) { + // can't explicitly manipulate the initialized/deinititalized state of + // the memory, when using pthread_mutex_init. That should be conceptual + // and a no-op, but if a debug layer ever makes it count for something, + // this might break. I have no idea how to fix it in that case, though... + let error = pthread_mutex_init(lockPointer, nil) + // pthread_mutex_init can only fail with ENOMEM, which we don't generally + // expect to recover from, so we can explicitly crash here. + precondition(error == 0, "Error \(error) creating pthread_mutex") + } + + private static func destroyPlatformLock(_ lockPointer: UnsafeMutablePointer) { + // can't explicitly manipulate the initialized/deinititalized state of + // the memory, when using pthread_mutex_destroy. That should be conceptual + // and a no-op, but if a debug layer ever makes it count for something, + // this might break. I have no idea how to fix it in that case, though... + pthread_mutex_destroy(lockPointer) + } + + private static func withPlatformLock( + _ lockPointer: UnsafeMutablePointer, + _ valuePointer: UnsafeMutablePointer>, + _ body: (inout sending Value) throws(E) -> sending R + ) throws(E) -> sending R { + let error = pthread_mutex_lock(lockPointer) + // pthread_mutex_lock can only fail with EDEADLK, which the os_unfair_lock + // variants crash for, so we can and should crash too. + precondition(error == 0, "Error \(error) locking pthread_mutex") + defer { + let error = pthread_mutex_unlock(lockPointer) + // pthread_mutex_unlock can only fail with EPERM, and only if this thread + // doesn't already hold the lock. Since everything here is synchronous, + // and we only have this closure-based locking function, that should + // be impossible. + precondition(error == 0, "Error \(error) unlocking pthread_mutex") + } + return try valuePointer.pointee.withValue(body) + } + +#endif + + // ManagedBuffer is frankly insane, but it's a good way to ensure we have a single heap + // allocation containing both the lock and the state, and that we can hook into deinit + // to ensure that the PlatformLock and State both get correctly destroyed. + // + // It's horrible to use, but without it, we either have to accept a second allocation, + // or drop down to runtime magic. + private final class LockBuffer: ManagedBuffer> { + + deinit { + withUnsafeMutablePointers { lockPointer, statePointer in + PlatformLock.destroyPlatformLock(lockPointer) + statePointer.deinitialize(count: 1) + } + } + + } + + private let lockBuffer: ManagedBuffer> + + init(_ initialValue: consuming sending Value) { + var initialValue = Disconnected(Optional(initialValue)) + self.lockBuffer = LockBuffer.create(minimumCapacity: 1, makingHeaderWith: { buffer in + buffer.withUnsafeMutablePointers { lockPointer, valuePointer in + PlatformLock.createPlatformLock(lockPointer) + valuePointer.initialize(to: Disconnected(initialValue.take()!)) + // this is gross, we can't really create pthread_mutex by value, + // since there's no particular guarantee that a *moved* pthread_mutex + // is still valid, but ManagedBuffer requires us to return a value + // here. So we initialize the pthread_mutex in-place, then return + // the same value from this closure, which ManagedBuffer then uses + // to overwrite the value we set (with the same bytes, for no change). + // Ugh. + return lockPointer.pointee + } + }) + } + + func withLock( + _ body: (inout sending Value) throws(E) -> sending R + ) throws(E) -> sending R { + // `withUnsafeMutablePointers` doesn't handle `sending` result types, so we + // transfer the result inside a `Disconnected` to appease the compiler. + try lockBuffer.withUnsafeMutablePointers { lockPointer, valuePointer throws(E) in + try Disconnected(PlatformLock.withPlatformLock(lockPointer, valuePointer, body)) + } + .consume() + } + +} + +// Safe to be `Sendable` because `state` is accessible +// only via the `withLock` method, which wraps the access +// in `withPlatformLock`. +extension PlatformLock: @unchecked Sendable {} + +// MARK: - Disconnected + +private struct Disconnected: ~Copyable, @unchecked Sendable { + private nonisolated(unsafe) var value: Value + + init(_ value: consuming sending Value) { + self.value = value + } + +#if compiler(>=6.2) + /// Swift 6.2 may miscompile `consume()` when inlined. + @inline(never) +#endif + consuming func consume() -> sending Value { + value + } + + mutating func swap(_ other: consuming sending Value) -> sending Value { + let result = value + self = Disconnected(other) + return result + } + +#if compiler(>=6.2) + mutating func take() -> sending Value where Value: ExpressibleByNilLiteral & SendableMetatype { + swap(nil) + } +#else + mutating func take() -> sending Value where Value: ExpressibleByNilLiteral { + swap(nil) + } +#endif + + mutating func withValue( + _ work: (inout sending Value) throws(E) -> sending R + ) throws(E) -> sending R { + try work(&value) + } + +} diff --git a/Sources/Vexil/Utilities/Locks.swift b/Sources/Vexil/Utilities/Locks.swift deleted file mode 100644 index f5889d37..00000000 --- a/Sources/Vexil/Utilities/Locks.swift +++ /dev/null @@ -1,18 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Vexil open source project -// -// Copyright (c) 2024 Unsigned Apps and the open source contributors. -// Licensed under the MIT license -// -// See LICENSE for license information -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -#if canImport(os) -typealias Lock = UnfairLock -#else -typealias Lock = POSIXThreadLock -#endif diff --git a/Sources/Vexil/Utilities/POSIXLocks.swift b/Sources/Vexil/Utilities/POSIXLocks.swift deleted file mode 100644 index 600cbafd..00000000 --- a/Sources/Vexil/Utilities/POSIXLocks.swift +++ /dev/null @@ -1,179 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Vexil open source project -// -// Copyright (c) 2024 Unsigned Apps and the open source contributors. -// Licensed under the MIT license -// -// See LICENSE for license information -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -import Foundation - -/// A type of lock or mutex that can be used to synchronise access or -/// execution of code by wrapping `pthread_mutex_lock` and `pthread_mutex_unlock` -/// -/// This lock must be unlocked from the same thread that locked it, attempts to -/// unlock from a different thread will cause an assertion aborting the process. -/// -/// - Important: If you're using async/await or Structured Concurrency consider -/// using an `actor` instead of these locks. -/// -struct POSIXThreadLock: Sendable { - - private let mutexValue: POSIXMutex - - /// Initialise the Mutex with a non-sendable lock-protected `initialState`. - /// - /// By initialising with a non-sendable type, the owner of this structure - /// must ensure the Sendable contract is upheld manually. - /// Non-sendable content from `State` should not be allowed - /// to escape from the lock. - /// - /// - Parameter - /// - initialState: An initial value to store that will be protected under the lock. - /// - init(uncheckedState initialState: State) { - self.mutexValue = .init(uncheckedState: initialState) { mutex in - // can't explicitly manipulate the initialized/deinititalized state of - // the memory, when using pthread_mutex_init. That should be conceptual - // and a no-op, but if a debug layer ever makes it count for something, - // this might break. I have no idea how to fix it in that case, though... - let error = pthread_mutex_init(mutex, nil) - - // pthread_mutex_init can only fail with ENOMEM, which we don't generally - // expect to recover from, so we can explicitly crash here. - precondition(error == 0, "Could not initialise a pthread_mutex, this usually indicates a serious problem with system resources") - } - } - - /// Initialise the Mutex with a lock-protected sendable `initialState`. - /// - /// - Parameter - /// - initialState: An initial value to store that will be protected under the lock. - /// - init(initialState: State) where State: Sendable { - self.init(uncheckedState: initialState) - } - - /// Perform a closure while holding this lock. - /// - /// This method does not enforce sendability requirement on closure body and its return type. - /// The caller of this method is responsible for ensuring references to non-sendables from closure - /// uphold the Sendability contract. - /// - /// - Parameters: - /// - closure: A closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLockUnchecked(_ closure: (inout State) throws -> R) rethrows -> R { - try mutexValue.withLockUnchecked(closure) - } - - /// Perform a sendable closure while holding this lock. - /// - /// - Parameters: - /// - closure: A sendable closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLock(_ closure: @Sendable (inout State) throws -> R) rethrows -> R where R: Sendable { - try withLockUnchecked(closure) - } - - /// Attempt to acquire the lock, if successful, perform a closure while holding the lock. - /// - /// This method does not enforce sendability requirement on closure body and its return type. - /// The caller of this method is responsible for ensuring references to non-sendables from closure - /// uphold the Sendability contract. - /// - /// - Parameters: - /// - closure: A sendable closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLockIfAvailableUnchecked(_ closure: (inout State) throws -> R) rethrows -> R? { - try mutexValue.withLockIfAvailableUnchecked(closure) - } - - /// Attempt to acquire the lock, if successful, perform a sendable closure while - /// holding the lock. - /// - /// - Parameters: - /// - closure: A sendable closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLockIfAvailable(_ closure: @Sendable (inout State) throws -> R) rethrows -> R? where R: Sendable { - try withLockIfAvailableUnchecked(closure) - } - -} - -// MARK: - POSIX mutex - -private final class POSIXMutex: @unchecked Sendable { - - let buffer: ManagedBuffer - - init( - uncheckedState initialState: State, - mutexInitializer: (UnsafeMutablePointer) -> Void - ) { - self.buffer = .create(minimumCapacity: 1) { buffer in - buffer.withUnsafeMutablePointers { mutex, state in - state.initialize(to: initialState) - mutexInitializer(mutex) - return mutex.pointee - } - } - } - - deinit { - buffer.withUnsafeMutablePointers { mutex, state in - state.deinitialize(count: 1) - - // can't explicitly manipulate the initialized/deinititalized state of - // the memory, when using pthread_mutex_destroy. That should be conceptual - // and a no-op, but if a debug layer ever makes it count for something, - // this might break. I have no idea how to fix it in that case, though... - pthread_mutex_destroy(mutex) - } - } - - func withLockUnchecked(_ closure: (inout State) throws -> R) rethrows -> R { - try buffer.withUnsafeMutablePointers { mutex, state in - let result = pthread_mutex_lock(mutex) - precondition(result == 0, "Error \(result) locking pthread_mutex") - - defer { - let result = pthread_mutex_unlock(mutex) - precondition(result == 0, "Error \(result) unlocking pthread_mutex") - } - - return try closure(&state.pointee) - } - } - - func withLockIfAvailableUnchecked(_ closure: (inout State) throws -> R) rethrows -> R? { - try buffer.withUnsafeMutablePointers { mutex, state in - let result = pthread_mutex_trylock(mutex) - precondition(result == 0 || result == EBUSY, "Error \(result) trying to lock pthread_mutex") - guard result == 0 else { - return nil - } - - defer { - let result = pthread_mutex_unlock(mutex) - precondition(result == 0, "Error \(result) unlocking pthread_mutex") - } - - return try closure(&state.pointee) - } - } - -} diff --git a/Sources/Vexil/Utilities/UnfairLocks.swift b/Sources/Vexil/Utilities/UnfairLocks.swift deleted file mode 100644 index 6de521fe..00000000 --- a/Sources/Vexil/Utilities/UnfairLocks.swift +++ /dev/null @@ -1,190 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Vexil open source project -// -// Copyright (c) 2024 Unsigned Apps and the open source contributors. -// Licensed under the MIT license -// -// See LICENSE for license information -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -#if canImport(os) - -import Foundation -import os.lock - -/// A type of lock or mutex that can be used to synchronise access -/// or execution of code by wrapping `OSAllocatedUnfairLock` (iOS 16+) or -/// `os_unfair_lock` (iOS <16). -/// -/// This lock must be unlocked from the same thread that locked it, attempts to -/// unlock from a different thread will cause an assertion aborting the process. -/// -/// This lock must not be accessed from multiple processes or threads via shared -/// or multiply-mapped memory, the lock implementation relies on the address of -/// the lock value and owning process. -/// -struct UnfairLock { - - // MARK: - Properties - - private var mutexValue: any UnfairMutex - - // MARK: - Initialisation - - /// Initialise an `UnfairLock` with a non-sendable lock-protected `initialState`. - /// - /// By initialising with a non-sendable type, the owner of this structure - /// must ensure the Sendable contract is upheld manually. - /// Non-sendable content from `State` should not be allowed - /// to escape from the lock. - /// - /// - Parameter - /// - initialState: An initial value to store that will be protected under the lock. - /// - init(uncheckedState initialState: State) { - if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) { - mutexValue = OSAllocatedUnfairLock(uncheckedState: initialState) - } else { - self.mutexValue = LegacyUnfairLock.create(initialState: initialState) - } - } - - /// Initialise the Mutex with a lock-protected sendable `initialState`. - /// - /// - Parameter - /// - initialState: An initial value to store that will be protected under the lock. - /// - init(initialState: State) where State: Sendable { - self.init(uncheckedState: initialState) - } - - /// Perform a closure while holding this lock. - /// - /// This method does not enforce sendability requirement on closure body and its return type. - /// The caller of this method is responsible for ensuring references to non-sendables from closure - /// uphold the Sendability contract. - /// - /// - Parameters: - /// - closure: A closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLockUnchecked(_ closure: (inout State) throws -> R) rethrows -> R { - try mutexValue.withLockUnchecked(closure) - } - - /// Perform a sendable closure while holding this lock. - /// - /// - Parameters: - /// - closure: A sendable closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLock(_ closure: @Sendable (inout State) throws -> R) rethrows -> R where R: Sendable { - try withLockUnchecked(closure) - } - - /// Attempt to acquire the lock, if successful, perform a closure while holding the lock. - /// - /// This method does not enforce sendability requirement on closure body and its return type. - /// The caller of this method is responsible for ensuring references to non-sendables from closure - /// uphold the Sendability contract. - /// - /// - Parameters: - /// - closure: A sendable closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLockIfAvailableUnchecked(_ closure: (inout State) throws -> R) rethrows -> R? { - try mutexValue.withLockIfAvailableUnchecked(closure) - } - - /// Attempt to acquire the lock, if successful, perform a sendable closure while - /// holding the lock. - /// - /// - Parameters: - /// - closure: A sendable closure to invoke while holding this lock. - /// - Returns: The return value of `closure`. - /// - Throws: Anything thrown by `closure`. - /// - func withLockIfAvailable(_ closure: @Sendable (inout State) throws -> R) rethrows -> R? where R: Sendable { - try withLockIfAvailableUnchecked(closure) - } - -} - -// MARK: - Unfair Mutex - -/// A private protocol that lets us work with both `OSAllocatedUnfairLock` and -/// `os_unfair_lock` depending on an #available check. -/// -/// This can be removed when we drop support for iOS 15 and macOS 12, etc -/// -private protocol UnfairMutex: Sendable { - - associatedtype UnfairState - func withLockUnchecked(_ closure: (inout UnfairState) throws -> R) rethrows -> R - func withLockIfAvailableUnchecked(_ closure: (inout UnfairState) throws -> R) rethrows -> R? - -} - -// swiftlint:disable unchecked_sendable -// -// `LegacyUnfairLock` exists to help ensure thread-safety, so asserting that is Sendable here is appropriate - -private final class LegacyUnfairLock: ManagedBuffer, UnfairMutex, @unchecked Sendable { - - typealias UnfairState = State - - static func create(initialState: State) -> Self { - create(minimumCapacity: 1) { buffer in - buffer.withUnsafeMutablePointers { lockPointer, statePointer in - lockPointer.initialize(to: os_unfair_lock()) - statePointer.initialize(to: initialState) - return lockPointer.pointee - } - // not sure why a non-final class wouldn't return Self here - } as! Self // swiftlint:disable:this force_cast - } - - deinit { - withUnsafeMutablePointers { mutex, state in - mutex.deinitialize(count: 1) - state.deinitialize(count: 1) - } - } - - func withLockUnchecked(_ closure: (inout UnfairState) throws -> R) rethrows -> R { - try withUnsafeMutablePointers { mutex, state in - os_unfair_lock_lock(mutex) - defer { - os_unfair_lock_unlock(mutex) - } - return try closure(&state.pointee) - } - } - - func withLockIfAvailableUnchecked(_ closure: (inout UnfairState) throws -> R) rethrows -> R? { - try withUnsafeMutablePointers { mutex, state in - guard os_unfair_lock_trylock(mutex) else { - return nil - } - defer { - os_unfair_lock_unlock(mutex) - } - return try closure(&state.pointee) - } - } - -} - -@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) -extension OSAllocatedUnfairLock: UnfairMutex { - typealias UnfairState = State -} - -#endif From 2a51cd8e2ff92af659bd6686d77f8f9561989f67 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 22:59:35 +1000 Subject: [PATCH 02/12] Replaced reliance on AsyncStream's support for multi-subscribers with custom solution --- Sources/Vexil/Observability/Observing.swift | 3 - Sources/Vexil/StreamManager.swift | 19 ++-- .../Vexil/Utilities/AsyncCurrentValue.swift | 90 +++++++++++++++++++ .../Vexil/Utilities/FlagChangeStream.swift | 84 +++++++++++++++++ 4 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 Sources/Vexil/Utilities/AsyncCurrentValue.swift create mode 100644 Sources/Vexil/Utilities/FlagChangeStream.swift diff --git a/Sources/Vexil/Observability/Observing.swift b/Sources/Vexil/Observability/Observing.swift index 6b1f3a3d..fd8e21ef 100644 --- a/Sources/Vexil/Observability/Observing.swift +++ b/Sources/Vexil/Observability/Observing.swift @@ -25,9 +25,6 @@ public enum FlagChange: Sendable, Equatable { } -public typealias FlagChangeStream = AsyncStream - - // MARK: - Filtered Change Stream public struct FilteredFlagChangeStream: AsyncSequence, Sendable { diff --git a/Sources/Vexil/StreamManager.swift b/Sources/Vexil/StreamManager.swift index b830487e..eadc54dd 100644 --- a/Sources/Vexil/StreamManager.swift +++ b/Sources/Vexil/StreamManager.swift @@ -134,23 +134,22 @@ extension StreamManager { /// is deinited, which doesn't happen often. /// struct Stream { - var stream: AsyncStream - var continuation: AsyncStream.Continuation + let currentValue: AsyncCurrentValue let keyPathMapper: @Sendable (String) -> FlagKeyPath - init(keyPathMapper: @Sendable @escaping (String) -> FlagKeyPath) { - let (stream, continuation) = AsyncStream.makeStream() - self.stream = stream - self.continuation = continuation - self.keyPathMapper = keyPathMapper + var stream: FlagChangeStream { + currentValue.stream } - func finish() { - continuation.finish() + init(keyPathMapper: @Sendable @escaping (String) -> FlagKeyPath) { + self.currentValue = AsyncCurrentValue(.all) + self.keyPathMapper = keyPathMapper } func send(_ change: FlagChange) { - continuation.yield(change) + currentValue.update { + $0 = change + } } func send(keys: Set) { diff --git a/Sources/Vexil/Utilities/AsyncCurrentValue.swift b/Sources/Vexil/Utilities/AsyncCurrentValue.swift new file mode 100644 index 00000000..f12eda29 --- /dev/null +++ b/Sources/Vexil/Utilities/AsyncCurrentValue.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Vexil open source project +// +// Copyright (c) 2025 Unsigned Apps and the open source contributors. +// Licensed under the MIT license +// +// See LICENSE for license information +// +// SPDX-License-Identifier: MIT +// +//===----------------------------------------------------------------------===// + +import Foundation + +struct AsyncCurrentValue: Sendable { + + struct State { + // iterators start with generation = 0, so our initial value + // has generation 1, so even that will be delivered. + var generation = 1 + var wrappedValue: Wrapped { + didSet { + generation += 1 + for (_, continuation) in pendingContinuations { + continuation.resume(returning: (generation, wrappedValue)) + } + pendingContinuations = [] + } + } + var pendingContinuations = [(UUID, CheckedContinuation<(Int, Wrapped)?, Never>)]() + } + + final class Allocation: Sendable { + let mutex: Mutex + + init(state: sending State) { + self.mutex = Mutex(state) + } + } + + // MARK: - Properties + + let allocation: Allocation + + // get-only; providing set would encourage `currentValue += 1` + // which is a race (lock taken twice). Use + // `$currentValue.update { $0 += 1 }` instead. + + /// Access to the current value. + var value: Wrapped { + allocation.mutex.withLock { $0.wrappedValue } + } + + // MARK: - Initialisation + + /// Creates a `CurrentValue` with an initial value + init(_ initialValue: sending Wrapped) { + self.allocation = .init(state: State(wrappedValue: initialValue)) + } + + // MARK: - Mutation + + /// Updates the current state using the supplied closure. + /// + /// - Parameters: + /// - body: A closure that passes the current value as an in-out parameter that you can mutate. + /// When the closure returns the mutated value is saved as the current value and is sent to all subscribers. + /// + func update(_ body: (inout sending Wrapped) throws -> R) rethrows -> R { + try allocation.mutex.withLock { state in + var wrappedValue = state.wrappedValue + do { + let result = try body(&wrappedValue) + state.wrappedValue = wrappedValue + return result + } catch { + state.wrappedValue = wrappedValue + throw error + } + } + } + +} + +extension AsyncCurrentValue { + var stream: FlagChangeStream { + FlagChangeStream(allocation: allocation) + } +} diff --git a/Sources/Vexil/Utilities/FlagChangeStream.swift b/Sources/Vexil/Utilities/FlagChangeStream.swift new file mode 100644 index 00000000..0f663d13 --- /dev/null +++ b/Sources/Vexil/Utilities/FlagChangeStream.swift @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Vexil open source project +// +// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Licensed under the MIT license +// +// See LICENSE for license information +// +// SPDX-License-Identifier: MIT +// +//===----------------------------------------------------------------------===// + +import Foundation + +public struct FlagChangeStream: AsyncSequence, Sendable { + + public typealias Element = FlagChange + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) + public typealias Failure = Never + + public func makeAsyncIterator() -> AsyncIterator { + AsyncIterator(allocation: allocation) + } + + weak var allocation: AsyncCurrentValue.Allocation? + +} + + +// MARK: - AsyncIterator + +public extension FlagChangeStream { + + struct AsyncIterator: AsyncIteratorProtocol { + + weak var allocation: AsyncCurrentValue.Allocation? + var generation = 0 + + public mutating func next() async -> Element? { + // is `#isolation` or `nil` better here? + await next(isolation: #isolation) + } + + public mutating func next(isolation actor: isolated (any Actor)?) async -> Element? { + guard let allocation else { + return nil + } + let uuid = UUID() + let generationAndResult = await withTaskCancellationHandler { + await withCheckedContinuation { (continuation: CheckedContinuation<(Int, Element)?, Never>) in + allocation.mutex.withLock { + if Task.isCancelled { + // the iterating task is already cancelled, just return nil + continuation.resume(returning: nil) + } else if $0.generation > generation { + // the `CurrentValue` already has a newer value, just return it + continuation.resume(returning: ($0.generation, $0.wrappedValue)) + } else { + // wait for the `CurrentValue` to be updated + $0.pendingContinuations.append((uuid, continuation)) + } + } + } + } onCancel: { + allocation.mutex.withLock { + if let index = $0.pendingContinuations.firstIndex(where: { $0.0 == uuid }) { + $0.pendingContinuations.remove(at: index).1.resume(returning: nil) + } else { + // onCancel: was called before operation: + // operation: will discover that Task.isCancelled + } + } + } + guard let generationAndResult else { + return nil + } + // ensure we don't return a duplicate value next time + generation = generationAndResult.0 + return generationAndResult.1 + } + } + +} From 874b69122be16b1c87707035408f008f9288a4fc Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 22:59:41 +1000 Subject: [PATCH 03/12] Cleanup --- Package.swift | 79 ++++++++------------------------ Sources/VexilMacros/Plugin.swift | 7 --- 2 files changed, 18 insertions(+), 68 deletions(-) diff --git a/Package.swift b/Package.swift index 530c085b..937a3b5f 100644 --- a/Package.swift +++ b/Package.swift @@ -20,39 +20,33 @@ let package = Package( // .library(name: "Vexillographer", targets: [ "Vexillographer" ]), ], - dependencies: .init { - Package.Dependency.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0") - Package.Dependency.package(url: "https://github.com/nicklockwood/SwiftFormat.git", from: "0.54.1") - Package.Dependency.package(url: "https://github.com/swiftlang/swift-syntax.git", .upToNextMajor(from: "600.0.1")) - }, + dependencies: [ + .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), + .package(url: "https://github.com/nicklockwood/SwiftFormat.git", from: "0.54.1"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", "600.0.0"..<"603.0.0"), + ], - targets: .init { + targets: [ // Vexil - Target.target( + .target( name: "Vexil", dependencies: [ .target(name: "VexilMacros"), .product(name: "AsyncAlgorithms", package: "swift-async-algorithms"), - ], - swiftSettings: [ - .swiftLanguageMode(.v6), ] - ) - Target.testTarget( + ), + .testTarget( name: "VexilTests", - dependencies: .init { - Target.Dependency.target(name: "Vexil") - }, - swiftSettings: [ - .swiftLanguageMode(.v6), + dependencies: [ + .target(name: "Vexil") ] - ) + ), // Vexillographer -// Target.target( +// .target( // name: "Vexillographer", // dependencies: [ // .target(name: "Vexil"), @@ -61,66 +55,29 @@ let package = Package( // Macros - Target.macro( + .macro( name: "VexilMacros", dependencies: [ .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - ], - swiftSettings: [ - .swiftLanguageMode(.v6), ] - ) - -#if !os(Linux) + ), // We can't disable macro validation using `swift test` so these are guaranteed to fail on Linux - Target.testTarget( + .testTarget( name: "VexilMacroTests", dependencies: [ .target(name: "VexilMacros"), .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), - ], - swiftSettings: [ - .swiftLanguageMode(.v6), ] - ) - -#endif + ), - }, + ], swiftLanguageModes: [ .v6, ] ) - -// MARK: - Helpers - -@resultBuilder -enum CollectionBuilder { - - typealias Component = [Element] - - static func buildExpression(_ expression: Element) -> Component { - [expression] - } - - static func buildBlock(_ components: Component...) -> Component { - components.flatMap { $0 } - } - - static func buildLimitedAvailability(_ components: [Element]) -> Component { - components - } - -} - -extension Array { - init(@CollectionBuilder collecting: () -> [Element]) { - self = collecting() - } -} diff --git a/Sources/VexilMacros/Plugin.swift b/Sources/VexilMacros/Plugin.swift index 00043046..c277920c 100644 --- a/Sources/VexilMacros/Plugin.swift +++ b/Sources/VexilMacros/Plugin.swift @@ -11,13 +11,6 @@ // //===----------------------------------------------------------------------===// -// -// Plugin.swift -// Vexil: VexilMacros -// -// Created by Rob Amos on 11/6/2023. -// - import SwiftCompilerPlugin import SwiftSyntaxMacros From 253a87cc634fa8ef9aa56bb6ecec7d859dd84973 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:00:00 +1000 Subject: [PATCH 04/12] Update unit test workflows to latest supported by GitHub --- .github/workflows/ios-tests.yml | 6 +++--- .github/workflows/linux-tests.yml | 2 +- .github/workflows/macos-tests.yml | 4 ++-- .github/workflows/tvos-tests.yml | 4 ++-- .github/workflows/visionos-tests.yml | 4 ++-- .github/workflows/watchos-tests.yml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ios-tests.yml b/.github/workflows/ios-tests.yml index db362d3a..1dab3c92 100644 --- a/.github/workflows/ios-tests.yml +++ b/.github/workflows/ios-tests.yml @@ -30,8 +30,8 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.0", "16.1" ] - os: [ macos-14, macos-15 ] + xcode: [ "16.2", "16.3", "16.4", "26.0" ] + os: [ macos-15 ] runs-on: ${{ matrix.os }} env: @@ -44,7 +44,7 @@ jobs: run: | set -o pipefail && \ NSUnbufferedIO=YES \ - xcrun xcodebuild test -workspace . -scheme Vexil -skipMacroValidation -destination "platform=iOS Simulator,name=iPhone 16" \ + xcrun xcodebuild test -workspace . -scheme Vexil -skipMacroValidation -destination "platform=iOS Simulator,name=iPhone 16e" \ | xcbeautify --renderer github-actions build-ios: diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index bf2b874a..063e8c6b 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - swift: [ "swift:6.0.2" ] + swift: [ "swift:6.0", "swift:6.1", "swift:6.2" ] os: [ amazonlinux2, focal, jammy, rhel-ubi9, noble ] container: diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index f734717e..760586c7 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -30,8 +30,8 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.0", "16.1" ] - os: [ macos-14, macos-15 ] + xcode: [ "16.2", "16.3", "16.4", "26.0" ] + os: [ macos-15 ] runs-on: ${{ matrix.os }} env: diff --git a/.github/workflows/tvos-tests.yml b/.github/workflows/tvos-tests.yml index 182bf1ae..e0775d30 100644 --- a/.github/workflows/tvos-tests.yml +++ b/.github/workflows/tvos-tests.yml @@ -30,8 +30,8 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.0", "16.1" ] - os: [ macos-14, macos-15 ] + xcode: [ "16.2", "16.3", "16.4", "26.0" ] + os: [ macos-15 ] runs-on: ${{ matrix.os }} env: diff --git a/.github/workflows/visionos-tests.yml b/.github/workflows/visionos-tests.yml index d24a62c1..1e0670b8 100644 --- a/.github/workflows/visionos-tests.yml +++ b/.github/workflows/visionos-tests.yml @@ -30,8 +30,8 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.0", "16.1" ] - os: [ macos-14, macos-15 ] + xcode: [ "16.2", "16.3", "16.4", "26.0" ] + os: [ macos-15 ] runs-on: ${{ matrix.os }} env: diff --git a/.github/workflows/watchos-tests.yml b/.github/workflows/watchos-tests.yml index a404357d..7bd82048 100644 --- a/.github/workflows/watchos-tests.yml +++ b/.github/workflows/watchos-tests.yml @@ -30,8 +30,8 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.0", "16.1" ] - os: [ macos-14, macos-15 ] + xcode: [ "16.2", "16.3", "16.4", "26.0" ] + os: [ macos-15 ] runs-on: ${{ matrix.os }} env: From 09745569721c59a270a6e94a77765b479e615d22 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:14:25 +1000 Subject: [PATCH 05/12] Macro tests still need to be disabled on Linux --- Package.swift | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index 937a3b5f..9981c998 100644 --- a/Package.swift +++ b/Package.swift @@ -65,14 +65,6 @@ let package = Package( ] ), - // We can't disable macro validation using `swift test` so these are guaranteed to fail on Linux - .testTarget( - name: "VexilMacroTests", - dependencies: [ - .target(name: "VexilMacros"), - .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), - ] - ), ], @@ -81,3 +73,18 @@ let package = Package( ] ) + +#if !os(Linux) + +// We can't disable macro validation using `swift test` so these are guaranteed to fail on Linux +package.targets.append( + .testTarget( + name: "VexilMacroTests", + dependencies: [ + .target(name: "VexilMacros"), + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ] + ) +) + +#endif From 552b1c181175b8facd7b6f0f5ca04fb998f6df99 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:16:02 +1000 Subject: [PATCH 06/12] Remove focal from list of supported linux variants --- .github/workflows/linux-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 063e8c6b..741204b7 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -36,7 +36,7 @@ jobs: fail-fast: false matrix: swift: [ "swift:6.0", "swift:6.1", "swift:6.2" ] - os: [ amazonlinux2, focal, jammy, rhel-ubi9, noble ] + os: [ amazonlinux2, jammy, rhel-ubi9, noble ] container: image: ${{ matrix.swift }}-${{ matrix.os }} From ee06c98dc69559eaf3e4cbce73518df3a0a3e6b7 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:31:45 +1000 Subject: [PATCH 07/12] Fixed incompatible Xcode versions, sigh --- .github/workflows/ios-tests.yml | 2 +- .github/workflows/macos-tests.yml | 2 +- .github/workflows/tvos-tests.yml | 2 +- .github/workflows/visionos-tests.yml | 2 +- .github/workflows/watchos-tests.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ios-tests.yml b/.github/workflows/ios-tests.yml index 1dab3c92..6881b68b 100644 --- a/.github/workflows/ios-tests.yml +++ b/.github/workflows/ios-tests.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.2", "16.3", "16.4", "26.0" ] + xcode: [ "16.3", "16.4" ] # "26.0" is broken with xcodebuild test os: [ macos-15 ] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index 760586c7..3fd48b5e 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.2", "16.3", "16.4", "26.0" ] + xcode: [ "16.3", "16.4", "26.0" ] os: [ macos-15 ] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/tvos-tests.yml b/.github/workflows/tvos-tests.yml index e0775d30..bd5b61ca 100644 --- a/.github/workflows/tvos-tests.yml +++ b/.github/workflows/tvos-tests.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.2", "16.3", "16.4", "26.0" ] + xcode: [ "16.3", "16.4" ] # "26.0" is broken with xcodebuild test os: [ macos-15 ] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/visionos-tests.yml b/.github/workflows/visionos-tests.yml index 1e0670b8..ef312290 100644 --- a/.github/workflows/visionos-tests.yml +++ b/.github/workflows/visionos-tests.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.2", "16.3", "16.4", "26.0" ] + xcode: [ "16.3", "16.4" ] # "26.0" is broken with xcodebuild test os: [ macos-15 ] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/watchos-tests.yml b/.github/workflows/watchos-tests.yml index 7bd82048..efe0c2d6 100644 --- a/.github/workflows/watchos-tests.yml +++ b/.github/workflows/watchos-tests.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - xcode: [ "16.2", "16.3", "16.4", "26.0" ] + xcode: [ "16.3", "16.4" ] # "26.0" is broken with xcodebuild test os: [ macos-15 ] runs-on: ${{ matrix.os }} From 2dff472ad07c0e2a54ec6cc9630e76342b48b9e4 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:39:35 +1000 Subject: [PATCH 08/12] Formatting --- Sources/Vexil/Configuration.swift | 2 +- Sources/Vexil/Container.swift | 2 +- Sources/Vexil/DisplayOptions.swift | 2 +- Sources/Vexil/Flag.swift | 2 +- Sources/Vexil/Group.swift | 2 +- Sources/Vexil/KeyPath.swift | 2 +- Sources/Vexil/Lookup.swift | 2 +- .../Vexil/Observability/FlagGroupWigwag.swift | 2 +- Sources/Vexil/Observability/FlagWigwag.swift | 2 +- Sources/Vexil/Observability/Observing.swift | 2 +- Sources/Vexil/Pole+Observability.swift | 2 +- Sources/Vexil/Pole.swift | 2 +- .../Snapshots/MutableFlagContainer.swift | 2 +- .../Vexil/Snapshots/Snapshot+Extensions.swift | 2 +- .../Snapshots/Snapshot+FlagValueSource.swift | 2 +- Sources/Vexil/Snapshots/Snapshot+Lookup.swift | 2 +- Sources/Vexil/Snapshots/Snapshot.swift | 2 +- Sources/Vexil/Snapshots/SnapshotBuilder.swift | 2 +- .../Sources/BoxedFlagValue+NSObject.swift | 2 +- .../FlagValueDictionary+FlagValueSource.swift | 2 +- .../Vexil/Sources/FlagValueDictionary.swift | 2 +- Sources/Vexil/Sources/FlagValueSource.swift | 2 +- .../Sources/FlagValueSourceCoordinator.swift | 2 +- ...quitousKeyValueStore+FlagValueSource.swift | 2 +- .../Sources/NonSendableFlagValueSource.swift | 2 +- .../UserDefaults+FlagValueSource.swift | 2 +- Sources/Vexil/StreamManager.swift | 2 +- .../Vexil/Utilities/AsyncCurrentValue.swift | 1 + .../Utilities/BoxedFlagValue+Codable.swift | 2 +- .../CollectionDifference.Change+Element.swift | 2 +- .../Vexil/Utilities/FlagChangeStream.swift | 2 +- Sources/Vexil/Utilities/Lock.swift | 32 +++++++++---------- Sources/Vexil/Value.swift | 2 +- Sources/Vexil/Visitor.swift | 2 +- Sources/Vexil/Visitors/FlagDescriber.swift | 2 +- Sources/Vexil/Visitors/FlagRemover.swift | 2 +- Sources/Vexil/Visitors/FlagSetter.swift | 2 +- Sources/VexilMacros/FlagContainerMacro.swift | 2 +- Sources/VexilMacros/FlagGroupMacro.swift | 2 +- Sources/VexilMacros/FlagMacro.swift | 2 +- Sources/VexilMacros/Plugin.swift | 2 +- .../Utilities/AttributeArgument.swift | 2 +- .../VexilMacros/Utilities/DisplayName.swift | 2 +- .../Utilities/PatternBindingSyntax.swift | 2 +- .../Utilities/SimpleVariables.swift | 2 +- .../Utilities/String+Snakecase.swift | 2 +- Sources/Vexillographer/Bindings/Binding.swift | 2 +- .../Bindings/EditableBoxedFlagValues.swift | 2 +- .../Bindings/LosslessStringTransformer.swift | 2 +- .../Bindings/OptionalTransformer.swift | 2 +- .../Bindings/PassthroughTransformer.swift | 2 +- Sources/Vexillographer/CopyButton.swift | 2 +- Sources/Vexillographer/DetailButton.swift | 2 +- .../Extensions/NSApplication+Sidebar.swift | 2 +- .../BooleanFlagControl.swift | 6 ++-- .../CaseIterableFlagControl.swift | 4 +-- .../OptionalCaseIterableFlagControl.swift | 4 +-- .../StringFlagControl.swift | 6 ++-- .../Vexillographer/FlagDetailSection.swift | 2 +- Sources/Vexillographer/FlagDetailView.swift | 2 +- .../Vexillographer/FlagDisplayValueView.swift | 2 +- Sources/Vexillographer/FlagGroupView.swift | 2 +- Sources/Vexillographer/FlagSectionView.swift | 2 +- Sources/Vexillographer/FlagValueManager.swift | 2 +- Sources/Vexillographer/FlagView.swift | 2 +- .../Vexillographer/Unfurling/Unfurlable.swift | 4 +-- .../Unfurling/UnfurledFlag.swift | 2 +- .../Unfurling/UnfurledFlagGroup.swift | 2 +- .../Unfurling/UnfurledFlagInfo.swift | 2 +- .../Unfurling/UnfurledFlagItem.swift | 2 +- .../Vexillographer/Utilities/AnyView.swift | 2 +- .../Utilities/DisplayName.swift | 2 +- .../Utilities/OptionalFlagValues.swift | 2 +- .../Vexillographer/Utilities/Pasteboard.swift | 2 +- Sources/Vexillographer/Vexillographer.swift | 2 +- .../EquatableFlagContainerMacroTests.swift | 2 +- .../FlagContainerMacroTests.swift | 2 +- .../VexilMacroTests/FlagGroupMacroTests.swift | 2 +- Tests/VexilMacroTests/FlagMacroTests.swift | 2 +- .../BoxedFlagValueDecodingTests.swift | 2 +- .../BoxedFlagValueEncodingTests.swift | 2 +- Tests/VexilTests/EquatableTests.swift | 2 +- Tests/VexilTests/FlagDetailTests.swift | 2 +- Tests/VexilTests/FlagPoleTests.swift | 2 +- Tests/VexilTests/FlagValueBoxingTests.swift | 2 +- .../FlagValueCompilationTests.swift | 4 +-- .../VexilTests/FlagValueDictionaryTests.swift | 2 +- Tests/VexilTests/FlagValueSourceTests.swift | 2 +- Tests/VexilTests/FlagValueUnboxingTests.swift | 4 +-- Tests/VexilTests/KeyEncodingTests.swift | 2 +- Tests/VexilTests/PublisherTests.swift | 2 +- Tests/VexilTests/SnapshotTests.swift | 2 +- .../UserDefaultPublisherTests.swift | 2 +- .../UserDefaultsDecodingTests.swift | 4 +-- .../UserDefaultsEncodingTests.swift | 2 +- Tests/VexilTests/Utilities/Tags.swift | 2 +- Tests/VexilTests/Utilities/TestRunner.swift | 2 +- Tests/VexilTests/VisitorTests.swift | 2 +- 98 files changed, 123 insertions(+), 122 deletions(-) diff --git a/Sources/Vexil/Configuration.swift b/Sources/Vexil/Configuration.swift index 5ee99346..19fb601c 100644 --- a/Sources/Vexil/Configuration.swift +++ b/Sources/Vexil/Configuration.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Container.swift b/Sources/Vexil/Container.swift index 7f064bf9..1aa979a4 100644 --- a/Sources/Vexil/Container.swift +++ b/Sources/Vexil/Container.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/DisplayOptions.swift b/Sources/Vexil/DisplayOptions.swift index d96b30e9..3b9385ad 100644 --- a/Sources/Vexil/DisplayOptions.swift +++ b/Sources/Vexil/DisplayOptions.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Flag.swift b/Sources/Vexil/Flag.swift index 90616eec..ec58a4d6 100644 --- a/Sources/Vexil/Flag.swift +++ b/Sources/Vexil/Flag.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Group.swift b/Sources/Vexil/Group.swift index 5cc30c68..1c8f8c22 100644 --- a/Sources/Vexil/Group.swift +++ b/Sources/Vexil/Group.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/KeyPath.swift b/Sources/Vexil/KeyPath.swift index ad09b740..c3b06ff3 100644 --- a/Sources/Vexil/KeyPath.swift +++ b/Sources/Vexil/KeyPath.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Lookup.swift b/Sources/Vexil/Lookup.swift index 773aa2b3..75675979 100644 --- a/Sources/Vexil/Lookup.swift +++ b/Sources/Vexil/Lookup.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Observability/FlagGroupWigwag.swift b/Sources/Vexil/Observability/FlagGroupWigwag.swift index 4950c20c..c52e8f2e 100644 --- a/Sources/Vexil/Observability/FlagGroupWigwag.swift +++ b/Sources/Vexil/Observability/FlagGroupWigwag.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Observability/FlagWigwag.swift b/Sources/Vexil/Observability/FlagWigwag.swift index a747e3fd..0683cb7c 100644 --- a/Sources/Vexil/Observability/FlagWigwag.swift +++ b/Sources/Vexil/Observability/FlagWigwag.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Observability/Observing.swift b/Sources/Vexil/Observability/Observing.swift index fd8e21ef..95d51469 100644 --- a/Sources/Vexil/Observability/Observing.swift +++ b/Sources/Vexil/Observability/Observing.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Pole+Observability.swift b/Sources/Vexil/Pole+Observability.swift index 85f700d4..a8fbc6f2 100644 --- a/Sources/Vexil/Pole+Observability.swift +++ b/Sources/Vexil/Pole+Observability.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Pole.swift b/Sources/Vexil/Pole.swift index 7f5e43db..84ddeec9 100644 --- a/Sources/Vexil/Pole.swift +++ b/Sources/Vexil/Pole.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Snapshots/MutableFlagContainer.swift b/Sources/Vexil/Snapshots/MutableFlagContainer.swift index bf486d08..87f867d0 100644 --- a/Sources/Vexil/Snapshots/MutableFlagContainer.swift +++ b/Sources/Vexil/Snapshots/MutableFlagContainer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Snapshots/Snapshot+Extensions.swift b/Sources/Vexil/Snapshots/Snapshot+Extensions.swift index 694312e3..2dea4c3e 100644 --- a/Sources/Vexil/Snapshots/Snapshot+Extensions.swift +++ b/Sources/Vexil/Snapshots/Snapshot+Extensions.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Snapshots/Snapshot+FlagValueSource.swift b/Sources/Vexil/Snapshots/Snapshot+FlagValueSource.swift index 7b3dc5cd..6bd8241e 100644 --- a/Sources/Vexil/Snapshots/Snapshot+FlagValueSource.swift +++ b/Sources/Vexil/Snapshots/Snapshot+FlagValueSource.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Snapshots/Snapshot+Lookup.swift b/Sources/Vexil/Snapshots/Snapshot+Lookup.swift index 3201f8f4..b25a82b7 100644 --- a/Sources/Vexil/Snapshots/Snapshot+Lookup.swift +++ b/Sources/Vexil/Snapshots/Snapshot+Lookup.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Snapshots/Snapshot.swift b/Sources/Vexil/Snapshots/Snapshot.swift index 38f31189..34e33d2a 100644 --- a/Sources/Vexil/Snapshots/Snapshot.swift +++ b/Sources/Vexil/Snapshots/Snapshot.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Snapshots/SnapshotBuilder.swift b/Sources/Vexil/Snapshots/SnapshotBuilder.swift index 259c96c0..0a8de63b 100644 --- a/Sources/Vexil/Snapshots/SnapshotBuilder.swift +++ b/Sources/Vexil/Snapshots/SnapshotBuilder.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/BoxedFlagValue+NSObject.swift b/Sources/Vexil/Sources/BoxedFlagValue+NSObject.swift index 256003d9..50213b6d 100644 --- a/Sources/Vexil/Sources/BoxedFlagValue+NSObject.swift +++ b/Sources/Vexil/Sources/BoxedFlagValue+NSObject.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/FlagValueDictionary+FlagValueSource.swift b/Sources/Vexil/Sources/FlagValueDictionary+FlagValueSource.swift index cbfc676b..ca9091aa 100644 --- a/Sources/Vexil/Sources/FlagValueDictionary+FlagValueSource.swift +++ b/Sources/Vexil/Sources/FlagValueDictionary+FlagValueSource.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/FlagValueDictionary.swift b/Sources/Vexil/Sources/FlagValueDictionary.swift index 607db0b6..81e2a026 100644 --- a/Sources/Vexil/Sources/FlagValueDictionary.swift +++ b/Sources/Vexil/Sources/FlagValueDictionary.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/FlagValueSource.swift b/Sources/Vexil/Sources/FlagValueSource.swift index 2e61cdc6..cf269713 100644 --- a/Sources/Vexil/Sources/FlagValueSource.swift +++ b/Sources/Vexil/Sources/FlagValueSource.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift b/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift index 79f63cbe..37d52351 100644 --- a/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift +++ b/Sources/Vexil/Sources/FlagValueSourceCoordinator.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/NSUbiquitousKeyValueStore+FlagValueSource.swift b/Sources/Vexil/Sources/NSUbiquitousKeyValueStore+FlagValueSource.swift index 8cebd6ee..74ebc59f 100644 --- a/Sources/Vexil/Sources/NSUbiquitousKeyValueStore+FlagValueSource.swift +++ b/Sources/Vexil/Sources/NSUbiquitousKeyValueStore+FlagValueSource.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/NonSendableFlagValueSource.swift b/Sources/Vexil/Sources/NonSendableFlagValueSource.swift index 70801870..a79494e7 100644 --- a/Sources/Vexil/Sources/NonSendableFlagValueSource.swift +++ b/Sources/Vexil/Sources/NonSendableFlagValueSource.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Sources/UserDefaults+FlagValueSource.swift b/Sources/Vexil/Sources/UserDefaults+FlagValueSource.swift index 279d4ed8..6bf310d7 100644 --- a/Sources/Vexil/Sources/UserDefaults+FlagValueSource.swift +++ b/Sources/Vexil/Sources/UserDefaults+FlagValueSource.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/StreamManager.swift b/Sources/Vexil/StreamManager.swift index eadc54dd..74ab5532 100644 --- a/Sources/Vexil/StreamManager.swift +++ b/Sources/Vexil/StreamManager.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Utilities/AsyncCurrentValue.swift b/Sources/Vexil/Utilities/AsyncCurrentValue.swift index f12eda29..ff436e00 100644 --- a/Sources/Vexil/Utilities/AsyncCurrentValue.swift +++ b/Sources/Vexil/Utilities/AsyncCurrentValue.swift @@ -28,6 +28,7 @@ struct AsyncCurrentValue: Sendable { pendingContinuations = [] } } + var pendingContinuations = [(UUID, CheckedContinuation<(Int, Wrapped)?, Never>)]() } diff --git a/Sources/Vexil/Utilities/BoxedFlagValue+Codable.swift b/Sources/Vexil/Utilities/BoxedFlagValue+Codable.swift index 119af722..4f2e3222 100644 --- a/Sources/Vexil/Utilities/BoxedFlagValue+Codable.swift +++ b/Sources/Vexil/Utilities/BoxedFlagValue+Codable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Utilities/CollectionDifference.Change+Element.swift b/Sources/Vexil/Utilities/CollectionDifference.Change+Element.swift index ad6cdad6..cc099119 100644 --- a/Sources/Vexil/Utilities/CollectionDifference.Change+Element.swift +++ b/Sources/Vexil/Utilities/CollectionDifference.Change+Element.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Utilities/FlagChangeStream.swift b/Sources/Vexil/Utilities/FlagChangeStream.swift index 0f663d13..eb092c6e 100644 --- a/Sources/Vexil/Utilities/FlagChangeStream.swift +++ b/Sources/Vexil/Utilities/FlagChangeStream.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Utilities/Lock.swift b/Sources/Vexil/Utilities/Lock.swift index 1e313e4f..b86f15d9 100644 --- a/Sources/Vexil/Utilities/Lock.swift +++ b/Sources/Vexil/Utilities/Lock.swift @@ -19,26 +19,26 @@ import os.lock struct Mutex: ~Copyable, Sendable { - // Mutex isn't supposed to use an allocation, but the tools we would need to - // *avoid* an allocation are not available to us (we'd need to import `Builtin`, - // which is forbidden) - // - // Using an allocation here makes us a reference type, but since Mutex is not - // Copyable, it's pretty hard to *observe* that in practice. - private let platformLock: PlatformLock - - init(_ initialValue: consuming sending Value) { - self.platformLock = PlatformLock(initialValue) - } + // Mutex isn't supposed to use an allocation, but the tools we would need to + // *avoid* an allocation are not available to us (we'd need to import `Builtin`, + // which is forbidden) + // + // Using an allocation here makes us a reference type, but since Mutex is not + // Copyable, it's pretty hard to *observe* that in practice. + private let platformLock: PlatformLock - borrowing func withLock( - _ body: (inout sending Value) throws -> sending Result - ) rethrows -> sending Result { - try platformLock.withLock(body) - } + init(_ initialValue: consuming sending Value) { + self.platformLock = PlatformLock(initialValue) + } + borrowing func withLock( + _ body: (inout sending Value) throws -> sending Result + ) rethrows -> sending Result { + try platformLock.withLock(body) } +} + /// This is a lock that will use the most appropriate platform lock under the hood. On Apple platforms /// it is effectively a wrapper around `OSAllocatedUnfairLock`. On non-Apple platforms it'll /// use `pthread_lock` and friends. diff --git a/Sources/Vexil/Value.swift b/Sources/Vexil/Value.swift index ff2919e5..23f713e9 100644 --- a/Sources/Vexil/Value.swift +++ b/Sources/Vexil/Value.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Visitor.swift b/Sources/Vexil/Visitor.swift index c55deb91..69146e7d 100644 --- a/Sources/Vexil/Visitor.swift +++ b/Sources/Vexil/Visitor.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Visitors/FlagDescriber.swift b/Sources/Vexil/Visitors/FlagDescriber.swift index ea82ff53..6c1212c5 100644 --- a/Sources/Vexil/Visitors/FlagDescriber.swift +++ b/Sources/Vexil/Visitors/FlagDescriber.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Visitors/FlagRemover.swift b/Sources/Vexil/Visitors/FlagRemover.swift index eaa08aff..bdb14541 100644 --- a/Sources/Vexil/Visitors/FlagRemover.swift +++ b/Sources/Vexil/Visitors/FlagRemover.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexil/Visitors/FlagSetter.swift b/Sources/Vexil/Visitors/FlagSetter.swift index c2a0341d..f2faecf9 100644 --- a/Sources/Vexil/Visitors/FlagSetter.swift +++ b/Sources/Vexil/Visitors/FlagSetter.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/FlagContainerMacro.swift b/Sources/VexilMacros/FlagContainerMacro.swift index 32feff51..209e4f7d 100644 --- a/Sources/VexilMacros/FlagContainerMacro.swift +++ b/Sources/VexilMacros/FlagContainerMacro.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/FlagGroupMacro.swift b/Sources/VexilMacros/FlagGroupMacro.swift index a943ab02..605a29ff 100644 --- a/Sources/VexilMacros/FlagGroupMacro.swift +++ b/Sources/VexilMacros/FlagGroupMacro.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/FlagMacro.swift b/Sources/VexilMacros/FlagMacro.swift index 4e32d73f..0c04d0a9 100644 --- a/Sources/VexilMacros/FlagMacro.swift +++ b/Sources/VexilMacros/FlagMacro.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/Plugin.swift b/Sources/VexilMacros/Plugin.swift index c277920c..74231f4c 100644 --- a/Sources/VexilMacros/Plugin.swift +++ b/Sources/VexilMacros/Plugin.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/Utilities/AttributeArgument.swift b/Sources/VexilMacros/Utilities/AttributeArgument.swift index 0a8886bf..6302b369 100644 --- a/Sources/VexilMacros/Utilities/AttributeArgument.swift +++ b/Sources/VexilMacros/Utilities/AttributeArgument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/Utilities/DisplayName.swift b/Sources/VexilMacros/Utilities/DisplayName.swift index a55f3a6d..a3350a8a 100644 --- a/Sources/VexilMacros/Utilities/DisplayName.swift +++ b/Sources/VexilMacros/Utilities/DisplayName.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/Utilities/PatternBindingSyntax.swift b/Sources/VexilMacros/Utilities/PatternBindingSyntax.swift index 4b84370a..a1fb8351 100644 --- a/Sources/VexilMacros/Utilities/PatternBindingSyntax.swift +++ b/Sources/VexilMacros/Utilities/PatternBindingSyntax.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/Utilities/SimpleVariables.swift b/Sources/VexilMacros/Utilities/SimpleVariables.swift index f5d9f7a7..e05f2bad 100644 --- a/Sources/VexilMacros/Utilities/SimpleVariables.swift +++ b/Sources/VexilMacros/Utilities/SimpleVariables.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/VexilMacros/Utilities/String+Snakecase.swift b/Sources/VexilMacros/Utilities/String+Snakecase.swift index aa50067d..d763f8c6 100644 --- a/Sources/VexilMacros/Utilities/String+Snakecase.swift +++ b/Sources/VexilMacros/Utilities/String+Snakecase.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Bindings/Binding.swift b/Sources/Vexillographer/Bindings/Binding.swift index 89d60338..921ca43d 100644 --- a/Sources/Vexillographer/Bindings/Binding.swift +++ b/Sources/Vexillographer/Bindings/Binding.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Bindings/EditableBoxedFlagValues.swift b/Sources/Vexillographer/Bindings/EditableBoxedFlagValues.swift index d78616d9..c03897ee 100644 --- a/Sources/Vexillographer/Bindings/EditableBoxedFlagValues.swift +++ b/Sources/Vexillographer/Bindings/EditableBoxedFlagValues.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Bindings/LosslessStringTransformer.swift b/Sources/Vexillographer/Bindings/LosslessStringTransformer.swift index fcd83ce4..2bae3826 100644 --- a/Sources/Vexillographer/Bindings/LosslessStringTransformer.swift +++ b/Sources/Vexillographer/Bindings/LosslessStringTransformer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Bindings/OptionalTransformer.swift b/Sources/Vexillographer/Bindings/OptionalTransformer.swift index 358116b5..07e83f86 100644 --- a/Sources/Vexillographer/Bindings/OptionalTransformer.swift +++ b/Sources/Vexillographer/Bindings/OptionalTransformer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Bindings/PassthroughTransformer.swift b/Sources/Vexillographer/Bindings/PassthroughTransformer.swift index 7bc7f222..37f506d0 100644 --- a/Sources/Vexillographer/Bindings/PassthroughTransformer.swift +++ b/Sources/Vexillographer/Bindings/PassthroughTransformer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/CopyButton.swift b/Sources/Vexillographer/CopyButton.swift index f06ecaf7..1668d0cd 100644 --- a/Sources/Vexillographer/CopyButton.swift +++ b/Sources/Vexillographer/CopyButton.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/DetailButton.swift b/Sources/Vexillographer/DetailButton.swift index c0dec6bf..8fbea775 100644 --- a/Sources/Vexillographer/DetailButton.swift +++ b/Sources/Vexillographer/DetailButton.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Extensions/NSApplication+Sidebar.swift b/Sources/Vexillographer/Extensions/NSApplication+Sidebar.swift index b2f8c7d3..4304cb22 100644 --- a/Sources/Vexillographer/Extensions/NSApplication+Sidebar.swift +++ b/Sources/Vexillographer/Extensions/NSApplication+Sidebar.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Flag Value Controls/BooleanFlagControl.swift b/Sources/Vexillographer/Flag Value Controls/BooleanFlagControl.swift index ce3eabd8..2a9f398b 100644 --- a/Sources/Vexillographer/Flag Value Controls/BooleanFlagControl.swift +++ b/Sources/Vexillographer/Flag Value Controls/BooleanFlagControl.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -63,7 +63,7 @@ struct BooleanFlagControl: View { /// @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol BooleanEditableFlag { - func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView where RootGroup: FlagContainer + func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) @@ -91,7 +91,7 @@ extension UnfurledFlag: BooleanEditableFlag where Value.BoxedValueType == Bool { /// @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol OptionalBooleanEditableFlag { - func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView where RootGroup: FlagContainer + func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) diff --git a/Sources/Vexillographer/Flag Value Controls/CaseIterableFlagControl.swift b/Sources/Vexillographer/Flag Value Controls/CaseIterableFlagControl.swift index 93da15eb..c84ec5f3 100644 --- a/Sources/Vexillographer/Flag Value Controls/CaseIterableFlagControl.swift +++ b/Sources/Vexillographer/Flag Value Controls/CaseIterableFlagControl.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -152,7 +152,7 @@ struct CaseIterableFlagControl: View where Value: FlagValue, Value: CaseI @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol CaseIterableEditableFlag { - func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView where RootGroup: FlagContainer + func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) diff --git a/Sources/Vexillographer/Flag Value Controls/OptionalCaseIterableFlagControl.swift b/Sources/Vexillographer/Flag Value Controls/OptionalCaseIterableFlagControl.swift index 11b36246..b02e139e 100644 --- a/Sources/Vexillographer/Flag Value Controls/OptionalCaseIterableFlagControl.swift +++ b/Sources/Vexillographer/Flag Value Controls/OptionalCaseIterableFlagControl.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -148,7 +148,7 @@ struct OptionalCaseIterableFlagControl: View @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol OptionalCaseIterableEditableFlag { - func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView where RootGroup: FlagContainer + func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) diff --git a/Sources/Vexillographer/Flag Value Controls/StringFlagControl.swift b/Sources/Vexillographer/Flag Value Controls/StringFlagControl.swift index 78a060e1..4cf97e2d 100644 --- a/Sources/Vexillographer/Flag Value Controls/StringFlagControl.swift +++ b/Sources/Vexillographer/Flag Value Controls/StringFlagControl.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -57,7 +57,7 @@ struct StringFlagControl: View { @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol StringEditableFlag { - func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView where RootGroup: FlagContainer + func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) @@ -87,7 +87,7 @@ extension UnfurledFlag: StringEditableFlag where Value.BoxedValueType: LosslessS @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol OptionalStringEditableFlag { - func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView where RootGroup: FlagContainer + func control(label: String, manager: FlagValueManager, showDetail: Binding) -> AnyView } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) diff --git a/Sources/Vexillographer/FlagDetailSection.swift b/Sources/Vexillographer/FlagDetailSection.swift index 79e64bfb..de53bb16 100644 --- a/Sources/Vexillographer/FlagDetailSection.swift +++ b/Sources/Vexillographer/FlagDetailSection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/FlagDetailView.swift b/Sources/Vexillographer/FlagDetailView.swift index f89ff3d7..d5f0b662 100644 --- a/Sources/Vexillographer/FlagDetailView.swift +++ b/Sources/Vexillographer/FlagDetailView.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/FlagDisplayValueView.swift b/Sources/Vexillographer/FlagDisplayValueView.swift index cdead560..35d21621 100644 --- a/Sources/Vexillographer/FlagDisplayValueView.swift +++ b/Sources/Vexillographer/FlagDisplayValueView.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/FlagGroupView.swift b/Sources/Vexillographer/FlagGroupView.swift index 680df665..53a2f880 100644 --- a/Sources/Vexillographer/FlagGroupView.swift +++ b/Sources/Vexillographer/FlagGroupView.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/FlagSectionView.swift b/Sources/Vexillographer/FlagSectionView.swift index 2f18dc97..1c1ff8f0 100644 --- a/Sources/Vexillographer/FlagSectionView.swift +++ b/Sources/Vexillographer/FlagSectionView.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/FlagValueManager.swift b/Sources/Vexillographer/FlagValueManager.swift index 662c1536..90cdebbd 100644 --- a/Sources/Vexillographer/FlagValueManager.swift +++ b/Sources/Vexillographer/FlagValueManager.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/FlagView.swift b/Sources/Vexillographer/FlagView.swift index 1b300f4e..d60670fc 100644 --- a/Sources/Vexillographer/FlagView.swift +++ b/Sources/Vexillographer/FlagView.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Unfurling/Unfurlable.swift b/Sources/Vexillographer/Unfurling/Unfurlable.swift index 016ba882..e9e1be2c 100644 --- a/Sources/Vexillographer/Unfurling/Unfurlable.swift +++ b/Sources/Vexillographer/Unfurling/Unfurlable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -23,7 +23,7 @@ import Vexil /// @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) protocol Unfurlable { - func unfurl(label: String, manager: FlagValueManager) -> UnfurledFlagItem? where RootGroup: FlagContainer + func unfurl(label: String, manager: FlagValueManager) -> UnfurledFlagItem? } @available(OSX 11.0, iOS 13.0, watchOS 7.0, tvOS 13.0, *) diff --git a/Sources/Vexillographer/Unfurling/UnfurledFlag.swift b/Sources/Vexillographer/Unfurling/UnfurledFlag.swift index f7c0060c..ce57c603 100644 --- a/Sources/Vexillographer/Unfurling/UnfurledFlag.swift +++ b/Sources/Vexillographer/Unfurling/UnfurledFlag.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Unfurling/UnfurledFlagGroup.swift b/Sources/Vexillographer/Unfurling/UnfurledFlagGroup.swift index 29d456fd..c23b4e8d 100644 --- a/Sources/Vexillographer/Unfurling/UnfurledFlagGroup.swift +++ b/Sources/Vexillographer/Unfurling/UnfurledFlagGroup.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Unfurling/UnfurledFlagInfo.swift b/Sources/Vexillographer/Unfurling/UnfurledFlagInfo.swift index e574e784..447d57e2 100644 --- a/Sources/Vexillographer/Unfurling/UnfurledFlagInfo.swift +++ b/Sources/Vexillographer/Unfurling/UnfurledFlagInfo.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Unfurling/UnfurledFlagItem.swift b/Sources/Vexillographer/Unfurling/UnfurledFlagItem.swift index f5b957b7..4f5cdd67 100644 --- a/Sources/Vexillographer/Unfurling/UnfurledFlagItem.swift +++ b/Sources/Vexillographer/Unfurling/UnfurledFlagItem.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Utilities/AnyView.swift b/Sources/Vexillographer/Utilities/AnyView.swift index 14f1dbc5..6820df46 100644 --- a/Sources/Vexillographer/Utilities/AnyView.swift +++ b/Sources/Vexillographer/Utilities/AnyView.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Utilities/DisplayName.swift b/Sources/Vexillographer/Utilities/DisplayName.swift index a2111bf2..c6cb5b70 100644 --- a/Sources/Vexillographer/Utilities/DisplayName.swift +++ b/Sources/Vexillographer/Utilities/DisplayName.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Utilities/OptionalFlagValues.swift b/Sources/Vexillographer/Utilities/OptionalFlagValues.swift index d02b1dcf..ace14a5b 100644 --- a/Sources/Vexillographer/Utilities/OptionalFlagValues.swift +++ b/Sources/Vexillographer/Utilities/OptionalFlagValues.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Utilities/Pasteboard.swift b/Sources/Vexillographer/Utilities/Pasteboard.swift index 48698821..540889ce 100644 --- a/Sources/Vexillographer/Utilities/Pasteboard.swift +++ b/Sources/Vexillographer/Utilities/Pasteboard.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Sources/Vexillographer/Vexillographer.swift b/Sources/Vexillographer/Vexillographer.swift index 16b5d86d..f247bfc2 100644 --- a/Sources/Vexillographer/Vexillographer.swift +++ b/Sources/Vexillographer/Vexillographer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift b/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift index 223aaf8e..4975607e 100644 --- a/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift +++ b/Tests/VexilMacroTests/EquatableFlagContainerMacroTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilMacroTests/FlagContainerMacroTests.swift b/Tests/VexilMacroTests/FlagContainerMacroTests.swift index b5537ac9..99706a4f 100644 --- a/Tests/VexilMacroTests/FlagContainerMacroTests.swift +++ b/Tests/VexilMacroTests/FlagContainerMacroTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilMacroTests/FlagGroupMacroTests.swift b/Tests/VexilMacroTests/FlagGroupMacroTests.swift index c6cd3bd2..52061e65 100644 --- a/Tests/VexilMacroTests/FlagGroupMacroTests.swift +++ b/Tests/VexilMacroTests/FlagGroupMacroTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilMacroTests/FlagMacroTests.swift b/Tests/VexilMacroTests/FlagMacroTests.swift index 31a1755e..153d4905 100644 --- a/Tests/VexilMacroTests/FlagMacroTests.swift +++ b/Tests/VexilMacroTests/FlagMacroTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/BoxedFlagValueDecodingTests.swift b/Tests/VexilTests/BoxedFlagValueDecodingTests.swift index 8066df16..d498eabf 100644 --- a/Tests/VexilTests/BoxedFlagValueDecodingTests.swift +++ b/Tests/VexilTests/BoxedFlagValueDecodingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/BoxedFlagValueEncodingTests.swift b/Tests/VexilTests/BoxedFlagValueEncodingTests.swift index 6b2d7e65..e7135d83 100644 --- a/Tests/VexilTests/BoxedFlagValueEncodingTests.swift +++ b/Tests/VexilTests/BoxedFlagValueEncodingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/EquatableTests.swift b/Tests/VexilTests/EquatableTests.swift index 2fd4b701..0025f911 100644 --- a/Tests/VexilTests/EquatableTests.swift +++ b/Tests/VexilTests/EquatableTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/FlagDetailTests.swift b/Tests/VexilTests/FlagDetailTests.swift index 026ae1e5..ca98da94 100644 --- a/Tests/VexilTests/FlagDetailTests.swift +++ b/Tests/VexilTests/FlagDetailTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/FlagPoleTests.swift b/Tests/VexilTests/FlagPoleTests.swift index 8d7e953c..58b40ea1 100644 --- a/Tests/VexilTests/FlagPoleTests.swift +++ b/Tests/VexilTests/FlagPoleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/FlagValueBoxingTests.swift b/Tests/VexilTests/FlagValueBoxingTests.swift index c15d946c..fbf375d9 100644 --- a/Tests/VexilTests/FlagValueBoxingTests.swift +++ b/Tests/VexilTests/FlagValueBoxingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/FlagValueCompilationTests.swift b/Tests/VexilTests/FlagValueCompilationTests.swift index 3ce78fdf..13c5195f 100644 --- a/Tests/VexilTests/FlagValueCompilationTests.swift +++ b/Tests/VexilTests/FlagValueCompilationTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -121,7 +121,7 @@ struct FlagValueCompilationTests { } @Test("Compiles unsigned integer") - func testUIntFlagValue() { + func uIntFlagValue() { let pole = FlagPole(hoist: IntTestFlags.self, sources: []) #expect(pole.flag == 123) } diff --git a/Tests/VexilTests/FlagValueDictionaryTests.swift b/Tests/VexilTests/FlagValueDictionaryTests.swift index c0760c76..f008b0da 100644 --- a/Tests/VexilTests/FlagValueDictionaryTests.swift +++ b/Tests/VexilTests/FlagValueDictionaryTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/FlagValueSourceTests.swift b/Tests/VexilTests/FlagValueSourceTests.swift index 4915286d..999b7b5d 100644 --- a/Tests/VexilTests/FlagValueSourceTests.swift +++ b/Tests/VexilTests/FlagValueSourceTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/FlagValueUnboxingTests.swift b/Tests/VexilTests/FlagValueUnboxingTests.swift index 8d0a2126..f7b774c9 100644 --- a/Tests/VexilTests/FlagValueUnboxingTests.swift +++ b/Tests/VexilTests/FlagValueUnboxingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -220,7 +220,7 @@ struct FlagValueUnboxingTests { // MARK: - Codable Types @Test("Unboxes codable") - private func testCodableFlagValue() throws { + private func codableFlagValue() throws { let expected = TestCodable() let data = try JSONEncoder().encode(Wrapper(wrapped: expected)) diff --git a/Tests/VexilTests/KeyEncodingTests.swift b/Tests/VexilTests/KeyEncodingTests.swift index d0c89eeb..cfd42090 100644 --- a/Tests/VexilTests/KeyEncodingTests.swift +++ b/Tests/VexilTests/KeyEncodingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/PublisherTests.swift b/Tests/VexilTests/PublisherTests.swift index c1fe0fb3..6380e80a 100644 --- a/Tests/VexilTests/PublisherTests.swift +++ b/Tests/VexilTests/PublisherTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/SnapshotTests.swift b/Tests/VexilTests/SnapshotTests.swift index 4400eb62..fa571a53 100644 --- a/Tests/VexilTests/SnapshotTests.swift +++ b/Tests/VexilTests/SnapshotTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/UserDefaultPublisherTests.swift b/Tests/VexilTests/UserDefaultPublisherTests.swift index d92cfbb6..b5cb1e91 100644 --- a/Tests/VexilTests/UserDefaultPublisherTests.swift +++ b/Tests/VexilTests/UserDefaultPublisherTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/UserDefaultsDecodingTests.swift b/Tests/VexilTests/UserDefaultsDecodingTests.swift index ca4827fc..872222da 100644 --- a/Tests/VexilTests/UserDefaultsDecodingTests.swift +++ b/Tests/VexilTests/UserDefaultsDecodingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information @@ -288,7 +288,7 @@ final class UserDefaultsDecodingTests { } @Test("Decodes string as optional boolean") - func testOptionalBoolString() { + func optionalBoolString() { withUserDefaults(#function) { defaults in defaults.set("t", forKey: "test") defaults.synchronize() diff --git a/Tests/VexilTests/UserDefaultsEncodingTests.swift b/Tests/VexilTests/UserDefaultsEncodingTests.swift index 79ffd622..ae4d5d5c 100644 --- a/Tests/VexilTests/UserDefaultsEncodingTests.swift +++ b/Tests/VexilTests/UserDefaultsEncodingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/Utilities/Tags.swift b/Tests/VexilTests/Utilities/Tags.swift index d2d33511..2266a968 100644 --- a/Tests/VexilTests/Utilities/Tags.swift +++ b/Tests/VexilTests/Utilities/Tags.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/Utilities/TestRunner.swift b/Tests/VexilTests/Utilities/TestRunner.swift index 99c3ed23..711ee6bc 100644 --- a/Tests/VexilTests/Utilities/TestRunner.swift +++ b/Tests/VexilTests/Utilities/TestRunner.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information diff --git a/Tests/VexilTests/VisitorTests.swift b/Tests/VexilTests/VisitorTests.swift index a4859c99..c4041701 100644 --- a/Tests/VexilTests/VisitorTests.swift +++ b/Tests/VexilTests/VisitorTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Vexil open source project // -// Copyright (c) 2024 Unsigned Apps and the open source contributors. +// Copyright (c) 2025 Unsigned Apps and the open source contributors. // Licensed under the MIT license // // See LICENSE for license information From a98c5865d0747f1c66e17f6083c5df0ec70714ca Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:40:18 +1000 Subject: [PATCH 09/12] Raise minimum supported Swift version to 6.1 to work around issues with locking on Linux. --- .github/workflows/linux-tests.yml | 2 +- Package.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 741204b7..19044b76 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - swift: [ "swift:6.0", "swift:6.1", "swift:6.2" ] + swift: [ "swift:6.1", "swift:6.2" ] os: [ amazonlinux2, jammy, rhel-ubi9, noble ] container: diff --git a/Package.swift b/Package.swift index 9981c998..9f4527dc 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.0 +// swift-tools-version:6.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import CompilerPluginSupport @@ -23,7 +23,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"), .package(url: "https://github.com/nicklockwood/SwiftFormat.git", from: "0.54.1"), - .package(url: "https://github.com/swiftlang/swift-syntax.git", "600.0.0"..<"603.0.0"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", "600.0.0" ..< "603.0.0"), ], targets: [ @@ -40,7 +40,7 @@ let package = Package( .testTarget( name: "VexilTests", dependencies: [ - .target(name: "Vexil") + .target(name: "Vexil"), ] ), From 03323c323d4d7c63ea76606f35a5d34b8605cad0 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:40:21 +1000 Subject: [PATCH 10/12] CI cleanup --- .github/workflows/ios-tests.yml | 6 ++---- .github/workflows/linux-tests.yml | 6 ++---- .github/workflows/macos-tests.yml | 6 ++---- .github/workflows/tvos-tests.yml | 6 ++---- .github/workflows/visionos-tests.yml | 6 ++---- .github/workflows/watchos-tests.yml | 6 ++---- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ios-tests.yml b/.github/workflows/ios-tests.yml index 6881b68b..de9ed1af 100644 --- a/.github/workflows/ios-tests.yml +++ b/.github/workflows/ios-tests.yml @@ -1,8 +1,6 @@ name: iOS Tests on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -50,8 +48,8 @@ jobs: build-ios: runs-on: ubuntu-latest name: iOS Tests - if: ${{ github.event_name == 'push' || needs.check-changes.outputs.changed == 'true' }} - needs: build-ios-matrix + if: ${{ needs.check-changes.outputs.changed == 'true' }} + needs: [ build-ios-matrix, check-changes ] steps: - name: Check build matrix status if: ${{ needs.build-ios-matrix.result != 'success' }} diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 19044b76..e626a31b 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -1,8 +1,6 @@ name: Linux Tests on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -50,8 +48,8 @@ jobs: test: runs-on: ubuntu-latest name: Linux Tests - if: ${{ github.event_name == 'push' || needs.check-changes.outputs.changed == 'true' }} - needs: matrix + if: ${{ needs.check-changes.outputs.changed == 'true' }} + needs: [ matrix, check-changes ] steps: - name: Check build matrix status if: ${{ needs.matrix.result != 'success' }} diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index 3fd48b5e..9c68d89f 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -1,8 +1,6 @@ name: macOS Tests on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -50,8 +48,8 @@ jobs: build-macos: runs-on: ubuntu-latest name: macOS Tests - if: ${{ github.event_name == 'push' || needs.check-changes.outputs.changed == 'true' }} - needs: build-macos-matrix + if: ${{ needs.check-changes.outputs.changed == 'true' }} + needs: [ build-macos-matrix, check-changes ] steps: - name: Check build matrix status if: ${{ needs.build-macos-matrix.result != 'success' }} diff --git a/.github/workflows/tvos-tests.yml b/.github/workflows/tvos-tests.yml index bd5b61ca..f8e5febb 100644 --- a/.github/workflows/tvos-tests.yml +++ b/.github/workflows/tvos-tests.yml @@ -1,8 +1,6 @@ name: tvOS Tests on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -50,8 +48,8 @@ jobs: build-tvos: runs-on: ubuntu-latest name: tvOS Tests - if: ${{ github.event_name == 'push' || needs.check-changes.outputs.changed == 'true' }} - needs: build-tvos-matrix + if: ${{ needs.check-changes.outputs.changed == 'true' }} + needs: [ build-tvos-matrix, check-changes ] steps: - name: Check build matrix status if: ${{ needs.build-tvos-matrix.result != 'success' }} diff --git a/.github/workflows/visionos-tests.yml b/.github/workflows/visionos-tests.yml index ef312290..4e4d167d 100644 --- a/.github/workflows/visionos-tests.yml +++ b/.github/workflows/visionos-tests.yml @@ -1,8 +1,6 @@ name: visionOS Tests on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -50,8 +48,8 @@ jobs: build-visionos: runs-on: ubuntu-latest name: visionOS Tests - if: ${{ github.event_name == 'push' || needs.check-changes.outputs.changed == 'true' }} - needs: build-visionos-matrix + if: ${{ needs.check-changes.outputs.changed == 'true' }} + needs: [ build-visionos-matrix, check-changes ] steps: - name: Check build matrix status if: ${{ needs.build-visionos-matrix.result != 'success' }} diff --git a/.github/workflows/watchos-tests.yml b/.github/workflows/watchos-tests.yml index efe0c2d6..616b9e08 100644 --- a/.github/workflows/watchos-tests.yml +++ b/.github/workflows/watchos-tests.yml @@ -1,8 +1,6 @@ name: watchOS Build Tests on: - push: - branches: [ main ] pull_request: branches: [ main ] @@ -50,8 +48,8 @@ jobs: build-watchos: runs-on: ubuntu-latest name: watchOS Tests - if: ${{ github.event_name == 'push' || needs.check-changes.outputs.changed == 'true' }} - needs: build-watchos-matrix + if: ${{ needs.check-changes.outputs.changed == 'true' }} + needs: [ build-watchos-matrix, check-changes ] steps: - name: Check build matrix status if: ${{ needs.build-watchos-matrix.result != 'success' }} From 6dfa98bee3ea961edf79c9141bdf2e7e605f5dc0 Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Sun, 28 Sep 2025 23:49:56 +1000 Subject: [PATCH 11/12] Try avoiding Xcode device matching problems --- .github/workflows/visionos-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/visionos-tests.yml b/.github/workflows/visionos-tests.yml index 4e4d167d..3adfeb40 100644 --- a/.github/workflows/visionos-tests.yml +++ b/.github/workflows/visionos-tests.yml @@ -42,7 +42,7 @@ jobs: run: | set -o pipefail && \ NSUnbufferedIO=YES \ - xcrun xcodebuild test -workspace . -scheme Vexil -skipMacroValidation -destination "platform=visionOS Simulator,name=Apple Vision Pro" \ + xcrun xcodebuild test -workspace . -scheme Vexil -skipMacroValidation -destination "platform=visionOS Simulator,name=Apple Vision Pro,os=2.5" \ | xcbeautify --renderer github-actions build-visionos: From 27f63e13c9dee96e9efcbf4ffb6e7f8bce1cfbbd Mon Sep 17 00:00:00 2001 From: Rob Amos Date: Mon, 29 Sep 2025 00:00:12 +1000 Subject: [PATCH 12/12] More cleanup --- .github/workflows/ios-tests.yml | 2 +- .github/workflows/linux-tests.yml | 2 +- .github/workflows/macos-tests.yml | 2 +- .github/workflows/tvos-tests.yml | 2 +- .github/workflows/visionos-tests.yml | 2 +- .github/workflows/watchos-tests.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ios-tests.yml b/.github/workflows/ios-tests.yml index de9ed1af..36d0d9d1 100644 --- a/.github/workflows/ios-tests.yml +++ b/.github/workflows/ios-tests.yml @@ -48,7 +48,7 @@ jobs: build-ios: runs-on: ubuntu-latest name: iOS Tests - if: ${{ needs.check-changes.outputs.changed == 'true' }} + if: ${{ always() && needs.check-changes.outputs.changed == 'true' }} needs: [ build-ios-matrix, check-changes ] steps: - name: Check build matrix status diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index e626a31b..4219ac24 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -48,7 +48,7 @@ jobs: test: runs-on: ubuntu-latest name: Linux Tests - if: ${{ needs.check-changes.outputs.changed == 'true' }} + if: ${{ always() && needs.check-changes.outputs.changed == 'true' }} needs: [ matrix, check-changes ] steps: - name: Check build matrix status diff --git a/.github/workflows/macos-tests.yml b/.github/workflows/macos-tests.yml index 9c68d89f..f6632342 100644 --- a/.github/workflows/macos-tests.yml +++ b/.github/workflows/macos-tests.yml @@ -48,7 +48,7 @@ jobs: build-macos: runs-on: ubuntu-latest name: macOS Tests - if: ${{ needs.check-changes.outputs.changed == 'true' }} + if: ${{ always() && needs.check-changes.outputs.changed == 'true' }} needs: [ build-macos-matrix, check-changes ] steps: - name: Check build matrix status diff --git a/.github/workflows/tvos-tests.yml b/.github/workflows/tvos-tests.yml index f8e5febb..f1be5ef1 100644 --- a/.github/workflows/tvos-tests.yml +++ b/.github/workflows/tvos-tests.yml @@ -48,7 +48,7 @@ jobs: build-tvos: runs-on: ubuntu-latest name: tvOS Tests - if: ${{ needs.check-changes.outputs.changed == 'true' }} + if: ${{ always() && needs.check-changes.outputs.changed == 'true' }} needs: [ build-tvos-matrix, check-changes ] steps: - name: Check build matrix status diff --git a/.github/workflows/visionos-tests.yml b/.github/workflows/visionos-tests.yml index 3adfeb40..86008ad7 100644 --- a/.github/workflows/visionos-tests.yml +++ b/.github/workflows/visionos-tests.yml @@ -48,7 +48,7 @@ jobs: build-visionos: runs-on: ubuntu-latest name: visionOS Tests - if: ${{ needs.check-changes.outputs.changed == 'true' }} + if: ${{ always() && needs.check-changes.outputs.changed == 'true' }} needs: [ build-visionos-matrix, check-changes ] steps: - name: Check build matrix status diff --git a/.github/workflows/watchos-tests.yml b/.github/workflows/watchos-tests.yml index 616b9e08..b0eebe04 100644 --- a/.github/workflows/watchos-tests.yml +++ b/.github/workflows/watchos-tests.yml @@ -48,7 +48,7 @@ jobs: build-watchos: runs-on: ubuntu-latest name: watchOS Tests - if: ${{ needs.check-changes.outputs.changed == 'true' }} + if: ${{ always() && needs.check-changes.outputs.changed == 'true' }} needs: [ build-watchos-matrix, check-changes ] steps: - name: Check build matrix status