From cb069308904294cebbbf55af8c4e3a2233faaf8e Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Thu, 21 May 2026 14:08:26 -0400 Subject: [PATCH 1/4] Update package version to Swift 6.1 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index af327df1..5a2bd186 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.1 import PackageDescription let package = Package( From 29fbeb4fd43bfb999688c785339a0e224da0b828 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Thu, 21 May 2026 15:41:41 -0400 Subject: [PATCH 2/4] Add Sendable annotations and other concurrency ceremony --- Sources/SkipBuild/Commands/ADBCommand.swift | 2 +- .../SkipBuild/Commands/AndroidCommand.swift | 38 +++++++-------- .../Commands/AndroidTestCommand.swift | 8 ++-- Sources/SkipBuild/Commands/AppCommand.swift | 4 +- .../SkipBuild/Commands/CheckupCommand.swift | 4 +- .../SkipBuild/Commands/CreateCommand.swift | 4 +- .../SkipBuild/Commands/DevicesCommand.swift | 2 +- .../SkipBuild/Commands/DoctorCommand.swift | 2 +- .../SkipBuild/Commands/DumpSkipCommand.swift | 2 +- .../SkipBuild/Commands/DumpSwiftCommand.swift | 2 +- .../SkipBuild/Commands/ExportCommand.swift | 2 +- .../SkipBuild/Commands/GradleCommand.swift | 2 +- Sources/SkipBuild/Commands/IconCommand.swift | 14 +++--- Sources/SkipBuild/Commands/InfoCommand.swift | 2 +- Sources/SkipBuild/Commands/InitCommand.swift | 2 +- Sources/SkipBuild/Commands/MetaCommand.swift | 4 +- .../SkipBuild/Commands/PluginCommand.swift | 2 +- Sources/SkipBuild/Commands/SBOMCommand.swift | 8 ++-- .../SkipBuild/Commands/SkippyCommand.swift | 2 +- .../SkipBuild/Commands/SkipstoneCommand.swift | 2 +- .../SkipBuild/Commands/SnippetCommand.swift | 2 +- Sources/SkipBuild/Commands/TestCommand.swift | 2 +- .../SkipBuild/Commands/UpgradeCommand.swift | 2 +- .../SkipBuild/Commands/VerifyCommand.swift | 2 +- .../SkipBuild/Commands/WelcomeCommand.swift | 2 +- Sources/SkipBuild/ConcurrencyShims.swift | 31 ++++++++++++ Sources/SkipBuild/OutputOptions.swift | 17 +++---- Sources/SkipBuild/SkipCommand.swift | 48 +++++-------------- Sources/SkipBuild/Utilities.swift | 2 +- Sources/SkipSyntax/Bridging.swift | 2 +- Sources/SkipSyntax/HelperTypes.swift | 2 +- Sources/SkipSyntax/Indentation.swift | 2 +- .../Kotlin/KotlinBridgeTransformer.swift | 4 +- .../Kotlin/KotlinBundleTransformer.swift | 2 +- .../SkipSyntax/Kotlin/KotlinHelperTypes.swift | 6 +-- .../SkipSyntax/Kotlin/KotlinTranslator.swift | 2 +- .../Kotlin/KotlinTupleLabelTransformer.swift | 2 +- Sources/SkipSyntax/Source.swift | 4 +- Sources/SkipSyntax/SyntaxTree.swift | 2 +- Sources/SkipSyntax/Transpilation.swift | 2 +- Sources/SkipSyntax/Transpiler.swift | 2 +- Sources/SkipSyntax/TypeSignature.swift | 4 +- .../AndroidEmulatorSetupTests.swift | 2 +- .../SkipSyntaxTests/FeatureSupportTests.swift | 2 +- .../MemberDeclarationTests.swift | 2 +- .../SkipSyntaxTests/XCTestCaseAdditions.swift | 2 +- skip | 2 +- 47 files changed, 136 insertions(+), 126 deletions(-) create mode 100644 Sources/SkipBuild/ConcurrencyShims.swift diff --git a/Sources/SkipBuild/Commands/ADBCommand.swift b/Sources/SkipBuild/Commands/ADBCommand.swift index 001a7f03..37e67c9e 100644 --- a/Sources/SkipBuild/Commands/ADBCommand.swift +++ b/Sources/SkipBuild/Commands/ADBCommand.swift @@ -15,7 +15,7 @@ fileprivate let adbCommandEnabled = false @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct ADBCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "adb", abstract: "Run an adb command with error scanning", usage: """ diff --git a/Sources/SkipBuild/Commands/AndroidCommand.swift b/Sources/SkipBuild/Commands/AndroidCommand.swift index e7df0e1a..f56e4519 100644 --- a/Sources/SkipBuild/Commands/AndroidCommand.swift +++ b/Sources/SkipBuild/Commands/AndroidCommand.swift @@ -19,7 +19,7 @@ fileprivate let androidCommandEnabled = false @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "android", abstract: "Build, run, and test Swift packages for Android", discussion: """ @@ -41,7 +41,7 @@ struct AndroidCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidHomeCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "home", abstract: "Install and manage the Android SDK in ANDROID_HOME", discussion: """ @@ -55,7 +55,7 @@ struct AndroidHomeCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidHomeInstallCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "install", abstract: "Install Android SDK Command-line Tools, platform-tools, and emulator in ANDROID_HOME", usage: """ @@ -303,7 +303,7 @@ func installSDKComponents( @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidSDKCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "sdk", abstract: "Manage installation of Swift Android SDK", discussion: """ @@ -421,7 +421,7 @@ extension ToolchainOptionsCommand where Self : StreamingCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidSDKInstallCommand: MessageCommand, ToolchainOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "install", abstract: "Install the native Swift Android SDK", usage: """ @@ -482,7 +482,7 @@ struct AndroidSDKInstallCommand: MessageCommand, ToolchainOptionsCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidSDKUninstallCommand: MessageCommand, ToolchainOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "uninstall", abstract: "Uninstall the Swift Android SDK", usage: """ @@ -555,7 +555,7 @@ struct AndroidSDKUninstallCommand: MessageCommand, ToolchainOptionsCommand { struct AndroidSDKListCommand: SkipCommand, StreamingCommand, OutputOptionsCommand, ToolOptionsCommand { typealias Output = SwiftSDKOutput - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "list", abstract: "List the installed Swift Android SDKs", usage: """ @@ -624,7 +624,7 @@ struct AndroidSDKListCommand: SkipCommand, StreamingCommand, OutputOptionsComman @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidToolchainCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "toolchain", abstract: "Manage installation of Swift Android Host Toolchain", discussion: """ @@ -638,7 +638,7 @@ struct AndroidToolchainCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidToolchainVersionCommand: AndroidOperationCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "version", abstract: "Show the version of the Swift Android Host Toolchain", shouldDisplay: true) @@ -723,8 +723,8 @@ extension AndroidOperationCommand { ) { continue } - print(outputLine.line, to: &TSCBasic.stderrStream) - TSCBasic.stderrStream.flush() + print(outputLine.line, to: &skipBuildStderrStream) + skipBuildStderrStream.flush() } else { if !outputOptions.verbose && ( outputLine.line.hasPrefix("warning: Could not read SDKSettings.json for SDK") @@ -733,8 +733,8 @@ extension AndroidOperationCommand { ) { continue } - print(outputLine.line, to: &TSCBasic.stdoutStream) - TSCBasic.stdoutStream.flush() + print(outputLine.line, to: &skipBuildStdoutStream) + skipBuildStdoutStream.flush() } } #endif @@ -1433,7 +1433,7 @@ extension AndroidOperationCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidBuildCommand: AndroidOperationCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "build", abstract: "Build the native project for Android", usage: """ @@ -1475,7 +1475,7 @@ struct AndroidBuildCommand: AndroidOperationCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidRunCommand: AndroidOperationCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "run", abstract: "Run the executable target on an Android device or emulator", usage: """ @@ -1529,7 +1529,7 @@ struct AndroidRunCommand: AndroidOperationCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidEmulatorCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "emulator", abstract: "Manage Android emulators", discussion: """ @@ -1546,7 +1546,7 @@ struct AndroidEmulatorCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidEmulatorCreateCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "create", abstract: "Install and create an Android emulator image", usage: """ @@ -1640,7 +1640,7 @@ struct AndroidEmulatorCreateCommand: MessageCommand, ToolOptionsCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidEmulatorLaunchCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "launch", abstract: "Launch an Android emulator", usage: """ @@ -1787,7 +1787,7 @@ struct AndroidEmulatorLaunchCommand: MessageCommand, ToolOptionsCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidEmulatorListCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "list", abstract: "List installed Android emulator images (AVDs)", usage: """ diff --git a/Sources/SkipBuild/Commands/AndroidTestCommand.swift b/Sources/SkipBuild/Commands/AndroidTestCommand.swift index 051b49b8..3704ee1e 100644 --- a/Sources/SkipBuild/Commands/AndroidTestCommand.swift +++ b/Sources/SkipBuild/Commands/AndroidTestCommand.swift @@ -12,7 +12,7 @@ import SkipDriveExternal @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AndroidTestCommand: AndroidOperationCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "test", abstract: "Test the native project on an Android device or emulator", usage: """ @@ -490,8 +490,8 @@ fileprivate extension AndroidOperationCommand { if line.hasPrefix(instrumentationStatusPrefix) { let output = String(line.dropFirst(instrumentationStatusPrefix.count)) if let formatted = formatTestEvent(output, term: outputOptions.term) { - print(formatted, to: &TSCBasic.stdoutStream) - TSCBasic.stdoutStream.flush() + print(formatted, to: &skipBuildStdoutStream) + skipBuildStdoutStream.flush() } eventLines.append(output) } else if line.hasPrefix(instrumentationCodePrefix) { @@ -600,7 +600,7 @@ private let testFullClass = "\(testPackage).\(testClassName)" /// Package.swift for the generated Swift test harness package. /// Defines a dynamic library target that produces `libtest_harness.so` private let harnessPackageSwift: String = """ -// swift-tools-version: 6.0 +// swift-tools-version: 6.1 import PackageDescription let package = Package( diff --git a/Sources/SkipBuild/Commands/AppCommand.swift b/Sources/SkipBuild/Commands/AppCommand.swift index f54af77e..35894b85 100644 --- a/Sources/SkipBuild/Commands/AppCommand.swift +++ b/Sources/SkipBuild/Commands/AppCommand.swift @@ -9,7 +9,7 @@ import SkipSyntax @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AppCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "app", abstract: "Build, run, and manage Skip apps", discussion: """ @@ -24,7 +24,7 @@ struct AppCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct AppLaunchCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "launch", abstract: "Build and launch the Skip app on iOS simulator and/or Android emulator", usage: """ diff --git a/Sources/SkipBuild/Commands/CheckupCommand.swift b/Sources/SkipBuild/Commands/CheckupCommand.swift index 10a608e3..ee838472 100644 --- a/Sources/SkipBuild/Commands/CheckupCommand.swift +++ b/Sources/SkipBuild/Commands/CheckupCommand.swift @@ -12,8 +12,8 @@ import Glibc #endif @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) -struct CheckupCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( +struct CheckupCommand: MessageCommand, ToolOptionsCommand, @unchecked Sendable { + static let configuration = CommandConfiguration( commandName: "checkup", abstract: "Run tests to ensure Skip is in working order", usage: """ diff --git a/Sources/SkipBuild/Commands/CreateCommand.swift b/Sources/SkipBuild/Commands/CreateCommand.swift index 0ca5dbe0..a48433a7 100644 --- a/Sources/SkipBuild/Commands/CreateCommand.swift +++ b/Sources/SkipBuild/Commands/CreateCommand.swift @@ -12,7 +12,7 @@ import FoundationNetworking @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct CreateCommand: StreamingCommand, ToolchainOptionsCommand, CreateOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "create", abstract: "Create a new Skip project interactively", usage: """ @@ -54,7 +54,7 @@ struct CreateCommand: StreamingCommand, ToolchainOptionsCommand, CreateOptionsCo print(msg) } else { print(msg, terminator: "") - stdoutStream.flush() + skipBuildStdoutStream.flush() } } diff --git a/Sources/SkipBuild/Commands/DevicesCommand.swift b/Sources/SkipBuild/Commands/DevicesCommand.swift index 4978df13..1d434b26 100644 --- a/Sources/SkipBuild/Commands/DevicesCommand.swift +++ b/Sources/SkipBuild/Commands/DevicesCommand.swift @@ -11,7 +11,7 @@ import Either struct DevicesCommand: SkipCommand, StreamingCommand, OutputOptionsCommand, ToolOptionsCommand { typealias Output = DevicesOutput - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "devices", abstract: "List connected devices and emulators/simulators", usage: """ diff --git a/Sources/SkipBuild/Commands/DoctorCommand.swift b/Sources/SkipBuild/Commands/DoctorCommand.swift index 9632ac38..06f6cb82 100644 --- a/Sources/SkipBuild/Commands/DoctorCommand.swift +++ b/Sources/SkipBuild/Commands/DoctorCommand.swift @@ -13,7 +13,7 @@ import TSCUtility struct DoctorCommand: SkipCommand, StreamingCommand, ToolOptionsCommand { typealias Output = MessageBlock - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "doctor", abstract: "Evaluate and diagnose Skip development environment", usage: """ diff --git a/Sources/SkipBuild/Commands/DumpSkipCommand.swift b/Sources/SkipBuild/Commands/DumpSkipCommand.swift index f62e48fe..91736b30 100644 --- a/Sources/SkipBuild/Commands/DumpSkipCommand.swift +++ b/Sources/SkipBuild/Commands/DumpSkipCommand.swift @@ -8,7 +8,7 @@ import TSCBasic import SkipSyntax struct DumpSkipCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration(commandName: "ast-skip", abstract: "Print the Skip AST", shouldDisplay: false) + static let configuration = CommandConfiguration(commandName: "ast-skip", abstract: "Print the Skip AST", shouldDisplay: false) @Option(name: [.customShort("S")], help: ArgumentHelp("Preprocessor symbols", valueName: "file")) var symbols: [String] = [] diff --git a/Sources/SkipBuild/Commands/DumpSwiftCommand.swift b/Sources/SkipBuild/Commands/DumpSwiftCommand.swift index d89edbfc..9f6b7d9f 100644 --- a/Sources/SkipBuild/Commands/DumpSwiftCommand.swift +++ b/Sources/SkipBuild/Commands/DumpSwiftCommand.swift @@ -9,7 +9,7 @@ import SwiftParser import SkipSyntax struct DumpSwiftCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration(commandName: "ast-swift", abstract: "Print the Swift AST", shouldDisplay: false) + static let configuration = CommandConfiguration(commandName: "ast-swift", abstract: "Print the Swift AST", shouldDisplay: false) @Option(name: [.customShort("S")], help: ArgumentHelp("Preprocessor symbols", valueName: "file")) var symbols: [String] = [] diff --git a/Sources/SkipBuild/Commands/ExportCommand.swift b/Sources/SkipBuild/Commands/ExportCommand.swift index 70650d5b..f0aaa5ad 100644 --- a/Sources/SkipBuild/Commands/ExportCommand.swift +++ b/Sources/SkipBuild/Commands/ExportCommand.swift @@ -17,7 +17,7 @@ fileprivate let exportCommandEnabled = false @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct ExportCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "export", abstract: "Export the Gradle project and built artifacts", usage: """ diff --git a/Sources/SkipBuild/Commands/GradleCommand.swift b/Sources/SkipBuild/Commands/GradleCommand.swift index 5d405cd8..8310167d 100644 --- a/Sources/SkipBuild/Commands/GradleCommand.swift +++ b/Sources/SkipBuild/Commands/GradleCommand.swift @@ -17,7 +17,7 @@ fileprivate let gradleCommandEnabled = false @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct GradleCommand: SkipCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "gradle", abstract: "Launch the gradle build tool", usage: """ diff --git a/Sources/SkipBuild/Commands/IconCommand.swift b/Sources/SkipBuild/Commands/IconCommand.swift index cec8ab10..7101cbda 100644 --- a/Sources/SkipBuild/Commands/IconCommand.swift +++ b/Sources/SkipBuild/Commands/IconCommand.swift @@ -11,7 +11,7 @@ import struct UniformTypeIdentifiers.UTType @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct IconCommand: MessageCommand, StreamingCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "icon", abstract: "Create and manage app icons", usage: """ @@ -649,13 +649,13 @@ extension CGContext { @objc class CGSVGDocument: NSObject { } -private var CGSVGDocumentRetain: (@convention(c) (CGSVGDocument?) -> Unmanaged?) = loadFunction("CGSVGDocumentRetain") -private var CGSVGDocumentRelease: (@convention(c) (CGSVGDocument?) -> Void) = loadFunction("CGSVGDocumentRelease") -private var CGSVGDocumentCreateFromData: (@convention(c) (CFData?, CFDictionary?) -> Unmanaged?) = loadFunction("CGSVGDocumentCreateFromData") -private var CGContextDrawSVGDocument: (@convention(c) (CGContext?, CGSVGDocument?) -> Void) = loadFunction("CGContextDrawSVGDocument") -private var CGSVGDocumentGetCanvasSize: (@convention(c) (CGSVGDocument?) -> CGSize) = loadFunction("CGSVGDocumentGetCanvasSize") +nonisolated(unsafe) private var CGSVGDocumentRetain: (@convention(c) (CGSVGDocument?) -> Unmanaged?) = loadFunction("CGSVGDocumentRetain") +nonisolated(unsafe) private var CGSVGDocumentRelease: (@convention(c) (CGSVGDocument?) -> Void) = loadFunction("CGSVGDocumentRelease") +nonisolated(unsafe) private var CGSVGDocumentCreateFromData: (@convention(c) (CFData?, CFDictionary?) -> Unmanaged?) = loadFunction("CGSVGDocumentCreateFromData") +nonisolated(unsafe) private var CGContextDrawSVGDocument: (@convention(c) (CGContext?, CGSVGDocument?) -> Void) = loadFunction("CGContextDrawSVGDocument") +nonisolated(unsafe) private var CGSVGDocumentGetCanvasSize: (@convention(c) (CGSVGDocument?) -> CGSize) = loadFunction("CGSVGDocumentGetCanvasSize") -private let CoreSVG = dlopen("/System/Library/PrivateFrameworks/CoreSVG.framework/CoreSVG", RTLD_NOW) +nonisolated(unsafe) private let CoreSVG = dlopen("/System/Library/PrivateFrameworks/CoreSVG.framework/CoreSVG", RTLD_NOW) private func loadFunction(_ name: String) -> T { unsafeBitCast(dlsym(CoreSVG, name), to: T.self) diff --git a/Sources/SkipBuild/Commands/InfoCommand.swift b/Sources/SkipBuild/Commands/InfoCommand.swift index 3c5d292b..54f3cc31 100644 --- a/Sources/SkipBuild/Commands/InfoCommand.swift +++ b/Sources/SkipBuild/Commands/InfoCommand.swift @@ -47,7 +47,7 @@ struct InfoCommand: SingleStreamingCommand { } } - static var configuration = CommandConfiguration(commandName: "info", + static let configuration = CommandConfiguration(commandName: "info", abstract: "Print system information", shouldDisplay: false) diff --git a/Sources/SkipBuild/Commands/InitCommand.swift b/Sources/SkipBuild/Commands/InitCommand.swift index a0102ac5..03be3863 100644 --- a/Sources/SkipBuild/Commands/InitCommand.swift +++ b/Sources/SkipBuild/Commands/InitCommand.swift @@ -12,7 +12,7 @@ import FoundationNetworking @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct InitCommand: MessageCommand, CreateOptionsCommand, ProjectCommand, ToolOptionsCommand, BuildOptionsCommand, StreamingCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "init", abstract: "Initialize a new Skip project", usage: """ diff --git a/Sources/SkipBuild/Commands/MetaCommand.swift b/Sources/SkipBuild/Commands/MetaCommand.swift index 6b6a74fe..41a158ad 100644 --- a/Sources/SkipBuild/Commands/MetaCommand.swift +++ b/Sources/SkipBuild/Commands/MetaCommand.swift @@ -23,7 +23,7 @@ fileprivate let metaCommandEnabled = false @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct MetaCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "meta", abstract: "App metadata and SBOM tools", discussion: """ @@ -40,7 +40,7 @@ struct MetaCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct MetaIndexCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "index", abstract: "Generate a JSON metadata index for the app", usage: """ diff --git a/Sources/SkipBuild/Commands/PluginCommand.swift b/Sources/SkipBuild/Commands/PluginCommand.swift index 7f01bfec..95071565 100644 --- a/Sources/SkipBuild/Commands/PluginCommand.swift +++ b/Sources/SkipBuild/Commands/PluginCommand.swift @@ -11,7 +11,7 @@ import TSCBasic /// It is means to be run from an app's settings.gradle.kts to ensure that the plugin is always available. @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct PluginCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "plugin", abstract: "Set up the Skip gradle plugin", shouldDisplay: false) diff --git a/Sources/SkipBuild/Commands/SBOMCommand.swift b/Sources/SkipBuild/Commands/SBOMCommand.swift index 9149fb38..80db6e02 100644 --- a/Sources/SkipBuild/Commands/SBOMCommand.swift +++ b/Sources/SkipBuild/Commands/SBOMCommand.swift @@ -22,7 +22,7 @@ fileprivate let sbomCommandEnabled = false @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct SBOMCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "sbom", abstract: "Generate and validate SPDX SBOM files for iOS and Android", discussion: """ @@ -41,7 +41,7 @@ struct SBOMCommand: AsyncParsableCommand { @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct SBOMCreateCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "create", abstract: "Generate SPDX SBOM files for iOS and Android", usage: """ @@ -150,7 +150,7 @@ For Android, the spdx-gradle-plugin is used to analyze Gradle dependencies. @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct SBOMValidateCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "validate", abstract: "Validate existing SBOM files against the current project state", usage: """ @@ -347,7 +347,7 @@ that have been added, removed, or changed version. @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct SBOMVerifyCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "verify", abstract: "Verify SBOM dependency licenses against a policy", usage: """ diff --git a/Sources/SkipBuild/Commands/SkippyCommand.swift b/Sources/SkipBuild/Commands/SkippyCommand.swift index 41bb06d5..07809629 100644 --- a/Sources/SkipBuild/Commands/SkippyCommand.swift +++ b/Sources/SkipBuild/Commands/SkippyCommand.swift @@ -11,7 +11,7 @@ struct SkippyCommand: BuildPluginOptionsCommand { /// The "CONFIGURATION" parameter specifies whether we are to run in Skippy mode or full-transpile mode static let skippyOnly = ProcessInfo.processInfo.environment["CONFIGURATION"] == "Skippy" - static var configuration = CommandConfiguration(commandName: "skippy", abstract: "Perform transpilation preflight checks", shouldDisplay: false) + static let configuration = CommandConfiguration(commandName: "skippy", abstract: "Perform transpilation preflight checks", shouldDisplay: false) @OptionGroup(title: "Check Options") var inputOptions: SkipstoneInputOptions diff --git a/Sources/SkipBuild/Commands/SkipstoneCommand.swift b/Sources/SkipBuild/Commands/SkipstoneCommand.swift index b26c8289..fb014d96 100644 --- a/Sources/SkipBuild/Commands/SkipstoneCommand.swift +++ b/Sources/SkipBuild/Commands/SkipstoneCommand.swift @@ -13,7 +13,7 @@ let skipcodeExtension = ".skipcode.json" /// The command executed by the Skip plugin that will perform all the actions to transform a SwiftPM module into a Gradle project, including transpiling source code, building native bridges, and processing resources. struct SkipstoneCommand: BuildPluginOptionsCommand, StreamingCommand { - static var configuration = CommandConfiguration(commandName: "skipstone", abstract: "Convert Swift project to Gradle", shouldDisplay: false, aliases: ["transpile"]) + static let configuration = CommandConfiguration(commandName: "skipstone", abstract: "Convert Swift project to Gradle", shouldDisplay: false, aliases: ["transpile"]) /// The `ENABLE_PREVIEW` parameter specifies whether we are building for previews static let enablePreviews = ProcessInfo.processInfo.environment["ENABLE_PREVIEWS"] == "YES" diff --git a/Sources/SkipBuild/Commands/SnippetCommand.swift b/Sources/SkipBuild/Commands/SnippetCommand.swift index 815daf72..6a8701bd 100644 --- a/Sources/SkipBuild/Commands/SnippetCommand.swift +++ b/Sources/SkipBuild/Commands/SnippetCommand.swift @@ -11,7 +11,7 @@ import SkipSyntax fileprivate let codebaseThresholdSize = 1_000 struct SnippetCommand: SnippetOptionsCommand, StreamingCommand { - static var configuration = CommandConfiguration(commandName: "snippet", abstract: "Transpile a snippet of Swift to Kotlin", shouldDisplay: false) + static let configuration = CommandConfiguration(commandName: "snippet", abstract: "Transpile a snippet of Swift to Kotlin", shouldDisplay: false) @OptionGroup(title: "Snippet Options") var snippetOptions: SnippetOptions diff --git a/Sources/SkipBuild/Commands/TestCommand.swift b/Sources/SkipBuild/Commands/TestCommand.swift index dc5a80cd..159fe007 100644 --- a/Sources/SkipBuild/Commands/TestCommand.swift +++ b/Sources/SkipBuild/Commands/TestCommand.swift @@ -17,7 +17,7 @@ fileprivate let testCommandEnabled = false struct TestCommand: SkipCommand, StreamingCommand, ToolOptionsCommand { typealias Output = MessageBlock - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "test", abstract: "Run parity tests and generate reports", usage: """ diff --git a/Sources/SkipBuild/Commands/UpgradeCommand.swift b/Sources/SkipBuild/Commands/UpgradeCommand.swift index 595308f1..5d94b5e7 100644 --- a/Sources/SkipBuild/Commands/UpgradeCommand.swift +++ b/Sources/SkipBuild/Commands/UpgradeCommand.swift @@ -14,7 +14,7 @@ import FoundationXML // for non-Darwin @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct UpgradeCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "upgrade", abstract: "Upgrade to the latest Skip version", usage: """ diff --git a/Sources/SkipBuild/Commands/VerifyCommand.swift b/Sources/SkipBuild/Commands/VerifyCommand.swift index f1fcb1fc..c69b158e 100644 --- a/Sources/SkipBuild/Commands/VerifyCommand.swift +++ b/Sources/SkipBuild/Commands/VerifyCommand.swift @@ -11,7 +11,7 @@ import TSCBasic struct VerifyCommand: SkipCommand, StreamingCommand, ProjectCommand, ToolOptionsCommand { typealias Output = MessageBlock - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "verify", abstract: "Verify Skip project", usage: """ diff --git a/Sources/SkipBuild/Commands/WelcomeCommand.swift b/Sources/SkipBuild/Commands/WelcomeCommand.swift index 0d6d14ad..7a7c1607 100644 --- a/Sources/SkipBuild/Commands/WelcomeCommand.swift +++ b/Sources/SkipBuild/Commands/WelcomeCommand.swift @@ -10,7 +10,7 @@ import SkipSyntax struct WelcomeCommand: SkipCommand, SingleStreamingCommand { typealias Output = WelcomeInfo? - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "welcome", abstract: "Show the skip welcome message", shouldDisplay: false) diff --git a/Sources/SkipBuild/ConcurrencyShims.swift b/Sources/SkipBuild/ConcurrencyShims.swift new file mode 100644 index 00000000..877b25bd --- /dev/null +++ b/Sources/SkipBuild/ConcurrencyShims.swift @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2026 Skip +// Licensed under the GNU Affero General Public License v3.0 +// SPDX-License-Identifier: AGPL-3.0-only + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#endif +import TSCBasic + +/// Concurrency-safe stdout stream owned by SkipBuild. +/// Avoids referencing `TSCBasic.stdoutStream`, which is a nonisolated mutable global +/// that Swift 6 strict concurrency flags on read from sendable contexts. The underlying +/// `ThreadSafeOutputByteStream` is internally synchronized, so an independent instance +/// pointing at the same file descriptor is safe. +nonisolated(unsafe) var skipBuildStdoutStream: ThreadSafeOutputByteStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(filePointer: stdout, closeOnDeinit: false)) + +/// Concurrency-safe stderr stream owned by SkipBuild. See `skipBuildStdoutStream`. +nonisolated(unsafe) var skipBuildStderrStream: ThreadSafeOutputByteStream = try! ThreadSafeOutputByteStream(LocalFileOutputByteStream(filePointer: stderr, closeOnDeinit: false)) + +/// Wraps an arbitrary value in an `@unchecked Sendable` container so it can be safely +/// captured into a `Task { }` whose body Swift 6 has marked `@Sendable`. The caller is +/// responsible for ensuring that the captured value is only used in ways that respect +/// its actual thread-safety guarantees. +final class UncheckedSendableBox: @unchecked Sendable { + let value: T + init(_ value: T) { self.value = value } +} diff --git a/Sources/SkipBuild/OutputOptions.swift b/Sources/SkipBuild/OutputOptions.swift index 7edaf597..eadf3c21 100644 --- a/Sources/SkipBuild/OutputOptions.swift +++ b/Sources/SkipBuild/OutputOptions.swift @@ -14,7 +14,7 @@ public protocol OutputOptionsCommand : ParsableArguments { var outputOptions: OutputOptions { get } } -public struct OutputOptions: ParsableArguments { +public struct OutputOptions: ParsableArguments, @unchecked Sendable { @Option(name: [.customShort("o"), .long], help: ArgumentHelp("Send output to the given file (stdout: -)", valueName: "path")) var output: String? @@ -98,8 +98,8 @@ public struct OutputOptions: ParsableArguments { } internal final class OutputHandler : Decodable { - var out: WritableByteStream = stdoutStream - var err: WritableByteStream = stderrStream + var out: WritableByteStream = skipBuildStdoutStream + var err: WritableByteStream = skipBuildStderrStream var outFile: LocalFileOutputByteStream? = nil var logFile: LocalFileOutputByteStream? = nil private let logFileLock = NSLock() @@ -432,11 +432,12 @@ extension OutputOptions { /// Perform an operation with a given message handler, which will be invoked in the progress cycle with a nil result, and then a final time with the result of the block invocation /// /// If we are using a rich terminal (and not specifying plain or JSON output), outputs a progress animation while waiting for the given process to complete - @discardableResult func monitor(with messenger: MessageQueue, _ message: String, watch: Bool = false, resultHandler rhandler: MessageResultHandler? = nil, monitorAction: @escaping (_ outputHandler: @escaping (String) -> ()) async throws -> T) async -> Result { + @discardableResult func monitor(with messenger: MessageQueue, _ message: String, watch: Bool = false, resultHandler rhandler: MessageResultHandler? = nil, monitorAction: @escaping (_ outputHandler: @escaping (String) -> ()) async throws -> T) async -> Result { _ = self.streams.outputBuffer(reset: true) // reset the output line buffer let terminalWidth = TerminalController.terminalWidth() - let resultHandler = rhandler ?? defaultResultHandler(message: message) + let resultHandler = UncheckedSendableBox(rhandler ?? defaultResultHandler(message: message)) + let monitorActionBox = UncheckedSendableBox(monitorAction) let startTime = Date.now @@ -453,7 +454,7 @@ extension OutputOptions { func animatingMessageHandler(_ result: Result?) -> String { let prefix = monitorPrefix(progressSprites, for: result?.messageStatusAny, startTime: startTime) if let result = result { - let msg = resultHandler(result) + let msg = resultHandler.value(result) return msg.message.message(term: term) ?? ((prefix ?? "") + message) } else { // the progress index is based on the current time index @@ -511,7 +512,7 @@ extension OutputOptions { // Capture the async monitorAction as a Result await { do { - let result = try await monitorAction({ line in + let result = try await monitorActionBox.value({ line in logMessage(line) streams.outputBuffer(add: line) // remember the current output line }) @@ -546,7 +547,7 @@ extension OutputOptions { } // send the final message to the block - let resultHandled = resultHandler(result) + let resultHandled = resultHandler.value(result) if let msgmsg = resultHandled.message { // the result handler specifies a message to issue await postMessage(msgmsg) } else { // otherwise translate the result diff --git a/Sources/SkipBuild/SkipCommand.swift b/Sources/SkipBuild/SkipCommand.swift index c5fc6284..0f44d631 100644 --- a/Sources/SkipBuild/SkipCommand.swift +++ b/Sources/SkipBuild/SkipCommand.swift @@ -60,7 +60,7 @@ public func skipstone(_ args: [String]) async throws -> (out: String, err: Strin /// The command that is run by "SkipRunner" (aka "skip") public struct SkipRunnerExecutor: SkipCommandExecutor { - public static var configuration = CommandConfiguration( + public static let configuration = CommandConfiguration( commandName: "skip", abstract: "skip \(skipVersion)", //version: skipVersion, @@ -161,7 +161,7 @@ struct VersionCommand: SingleStreamingCommand { #endif } - static var configuration = CommandConfiguration(commandName: "version", + static let configuration = CommandConfiguration(commandName: "version", abstract: "Print the skip version", shouldDisplay: !experimental) @@ -240,31 +240,8 @@ extension AbsolutePath { } } - -// MARK: Utilities - - -/// A command that forwards itself to another command. Used for aliasing commands. -struct ForwardingCommand: ParsableCommand where Name.RawValue : StringProtocol { - static var configuration: CommandConfiguration { - var cfg = Base.configuration - cfg.commandName = Name.allCases.first?.rawValue.description - cfg.shouldDisplay = false - return cfg - } - - @OptionGroup - var command: Base - - mutating func run() throws { - try command.run() - } -} - - // MARK: Streaming command support - extension Message: MessageConvertible { /// A transpiler mesage converts warnings and errors to warn/fail public var status: MessageBlock.Status? { @@ -306,11 +283,11 @@ actor MessageQueue { return self } - @discardableResult public func yield(_ value: MessageStream.Element) -> AsyncThrowingStream.Continuation.YieldResult { + public func yield(_ value: MessageStream.Element) { if retain { elements.append(.success(value)) } - return continuation.yield(value) + _ = continuation.yield(value) } public func yield(with result: Result) { @@ -483,9 +460,10 @@ struct StreamCommandError : LocalizedError { extension StreamingCommand { fileprivate func doCommand(with out: MessageQueue) { + let selfBox = UncheckedSendableBox(self) Task.detached { do { - try await performCommand(with: out) + try await selfBox.value.performCommand(with: out) await out.finish() } catch { await out.finish(throwing: error) @@ -519,7 +497,7 @@ extension SingleStreamingCommand { /// A "message" that can be output in various ways. /// /// The default `message(term:)` must minimally be implemented for terminal messages. -public protocol MessageConvertible { +public protocol MessageConvertible: Sendable { /// Returns the message for the output with optional ANSI coloring func message(term: Term) -> String? @@ -580,7 +558,7 @@ extension Optional : MessageConvertible where Wrapped : MessageConvertible { /// A message that can optionally be highlighted with colors for rich terminal output, or a `nil` Terminal for omitting a status prefix from the message public struct MessageBlock : StringMessageEncodable { - public enum Status : String, Encodable { + public enum Status : String, Encodable, Sendable { case pass, warn, fail, skip /// The character prefix to output before the command result @@ -606,7 +584,7 @@ public struct MessageBlock : StringMessageEncodable { /// Whether to silence this message from being printed to the terminal public var squelch: Bool = false - let _message: (_ term: Term?) -> String? + let _message: @Sendable (_ term: Term?) -> String? public init(status: Status?, _ message: String) { self.status = status @@ -620,7 +598,7 @@ public struct MessageBlock : StringMessageEncodable { self.init(status: .fail, prefix + error.localizedDescription) } - public init(_ message: @escaping (_ term: Term?) -> String?) { + public init(_ message: @Sendable @escaping (_ term: Term?) -> String?) { self.status = nil self._message = message } @@ -726,7 +704,7 @@ extension ProcessInfo { /// Mock environment for testing. When set (non-nil), all environment lookups /// return values from this dictionary, allowing tests to explicitly mock the absence /// of variables by not including them. Falls back to real process environment only when nil. - public static var mockEnvironment: [String: String]? + nonisolated(unsafe) public static var mockEnvironment: [String: String]? /// Returns an environment variable from mockEnvironment if it's set (even if nil/empty), /// otherwise falls back to the real process environment. @@ -1008,7 +986,7 @@ extension ToolOptionsCommand where Self : StreamingCommand { extension ToolOptionsCommand { /// Perform a monitor check on the given URL - @discardableResult func check(_ item: T, with out: MessageQueue, title: String, handle: @escaping (T) throws -> U) async -> Result { + @discardableResult func check(_ item: T, with out: MessageQueue, title: String, handle: @escaping (T) throws -> U) async -> Result { await outputOptions.monitor(with: out, title, resultHandler: { result in return (nil, nil) as (result: Result?, message: MessageBlock?) }) { line in @@ -1177,7 +1155,7 @@ struct BuildOptions: ParsableArguments { } struct LicenseCommand: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "license", abstract: "License management (obsolete)", discussion: """ diff --git a/Sources/SkipBuild/Utilities.swift b/Sources/SkipBuild/Utilities.swift index e702d579..fd7164c8 100644 --- a/Sources/SkipBuild/Utilities.swift +++ b/Sources/SkipBuild/Utilities.swift @@ -50,7 +50,7 @@ extension BinaryInteger { } /// Terminal output information, such as how to output messages in various ANSI colors. -public struct Term { +public struct Term: Sendable { public static let plain = Term(colors: false) public static let ansi = Term(colors: true) diff --git a/Sources/SkipSyntax/Bridging.swift b/Sources/SkipSyntax/Bridging.swift index 426b2413..852bcf5f 100644 --- a/Sources/SkipSyntax/Bridging.swift +++ b/Sources/SkipSyntax/Bridging.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only /// Determines what API is bridged without being explicitly annotated. -public enum AutoBridge: Int { +public enum AutoBridge: Int, Sendable { /// Bridge only API with bridge attribute. case none /// Bridge UI code that isn't explicitly excluded. diff --git a/Sources/SkipSyntax/HelperTypes.swift b/Sources/SkipSyntax/HelperTypes.swift index 497a8c4c..d00e9412 100644 --- a/Sources/SkipSyntax/HelperTypes.swift +++ b/Sources/SkipSyntax/HelperTypes.swift @@ -133,7 +133,7 @@ enum AsyncDefaultIsolationPolicy { /// @Attributes on a declaration. /// /// - Note: `Codable` for use in `CodebaseInfo`. -struct Attributes: Hashable, PrettyPrintable, Codable { +struct Attributes: Hashable, PrettyPrintable, Codable, Sendable { var attributes: [Attribute] init(attributes: [Attribute] = []) { diff --git a/Sources/SkipSyntax/Indentation.swift b/Sources/SkipSyntax/Indentation.swift index 3f423c4f..d9a95065 100644 --- a/Sources/SkipSyntax/Indentation.swift +++ b/Sources/SkipSyntax/Indentation.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only /// Indentation helper. -public struct Indentation: ExpressibleByIntegerLiteral, CustomStringConvertible { +public struct Indentation: ExpressibleByIntegerLiteral, CustomStringConvertible, Sendable { public static let zero = Indentation(level: 0) public let level: Int diff --git a/Sources/SkipSyntax/Kotlin/KotlinBridgeTransformer.swift b/Sources/SkipSyntax/Kotlin/KotlinBridgeTransformer.swift index 51d70cbd..5a97c6a5 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinBridgeTransformer.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinBridgeTransformer.swift @@ -7,7 +7,7 @@ import Foundation /// Available bridge options. /// /// - Seealso: `JConvertibleOptions` in `SkipBridge`. -public struct KotlinBridgeOptions: OptionSet { +public struct KotlinBridgeOptions: OptionSet, Sendable { public static let kotlincompat = KotlinBridgeOptions(rawValue: 1 << 0) public let rawValue: Int @@ -33,7 +33,7 @@ public struct KotlinBridgeOptions: OptionSet { /// Generate bridging code and transformations. public final class KotlinBridgeTransformer: KotlinTransformer { - public static var testSkipAndroidBridge = false // For testing + nonisolated(unsafe) public static var testSkipAndroidBridge = false // For testing private let options: KotlinBridgeOptions diff --git a/Sources/SkipSyntax/Kotlin/KotlinBundleTransformer.swift b/Sources/SkipSyntax/Kotlin/KotlinBundleTransformer.swift index 1d77c6b7..a4025ccc 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinBundleTransformer.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinBundleTransformer.swift @@ -7,7 +7,7 @@ public final class KotlinBundleTransformer: KotlinTransformer { public static let supportFileName = "Bundle_Support.swift" private var needsModuleBundle = false - public static var testSkipAndroidBridge = false // For testing + nonisolated(unsafe) public static var testSkipAndroidBridge = false // For testing public init() { } diff --git a/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift b/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift index 5991830c..c90b6ddc 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift @@ -301,9 +301,9 @@ extension Attributes { } extension Attribute { - static let bridgeIgnored = Attribute(signature: .named("BridgeIgnored", [])) - static let bridgeToKotlin = Attribute(signature: .named("BridgeToKotlin", [])) - static let bridgeToSwift = Attribute(signature: .named("BridgeToSwift", [])) + nonisolated(unsafe) static let bridgeIgnored = Attribute(signature: .named("BridgeIgnored", [])) + nonisolated(unsafe) static let bridgeToKotlin = Attribute(signature: .named("BridgeToKotlin", [])) + nonisolated(unsafe) static let bridgeToSwift = Attribute(signature: .named("BridgeToSwift", [])) func append(to output: OutputGenerator, indentation: Indentation) { switch kind { diff --git a/Sources/SkipSyntax/Kotlin/KotlinTranslator.swift b/Sources/SkipSyntax/Kotlin/KotlinTranslator.swift index 93e50e0f..382039bf 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinTranslator.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinTranslator.swift @@ -11,7 +11,7 @@ public final class KotlinTranslator { private(set) var packageName: String? /// Module name to custom package name overrides, populated from skip.yml config and dependent module exports. - public static var packageNameOverrides: [String: String] = [:] + nonisolated(unsafe) public static var packageNameOverrides: [String: String] = [:] public init(syntaxTree: SyntaxTree) { self.syntaxTree = syntaxTree diff --git a/Sources/SkipSyntax/Kotlin/KotlinTupleLabelTransformer.swift b/Sources/SkipSyntax/Kotlin/KotlinTupleLabelTransformer.swift index f4aca67a..93e7abfa 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinTupleLabelTransformer.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinTupleLabelTransformer.swift @@ -9,7 +9,7 @@ import Foundation /// This transformer also generates errors for label conflicts, i.e. the same label being used to access different elements of the same N-tuple. final class KotlinTupleLabelTransformer: KotlinTransformer { /// Used in testing. - static var gatherLabelsFromTypeSignatures = true + nonisolated(unsafe) static var gatherLabelsFromTypeSignatures = true // Tuple arity -> Element -> Labels private typealias TupleLabels = [Int: [Int: Set]] diff --git a/Sources/SkipSyntax/Source.swift b/Sources/SkipSyntax/Source.swift index 97c72933..e26ca33b 100644 --- a/Sources/SkipSyntax/Source.swift +++ b/Sources/SkipSyntax/Source.swift @@ -6,7 +6,7 @@ import Foundation import SwiftSyntax /// Swift or Kotlin source code file. -public struct Source : Encodable { +public struct Source : Encodable, Sendable { public let file: FilePath public let content: String @@ -76,7 +76,7 @@ public struct Source : Encodable { /// A Swift source file. /// /// - Note: `Codable` for use in `CodebaseInfo`. - public struct FilePath: Hashable, Codable { + public struct FilePath: Hashable, Codable, Sendable { public private(set) var path: String /// The suffix of a file that indicates it is a bridging file public static let bridgeFileSuffix = "_Bridge.swift" diff --git a/Sources/SkipSyntax/SyntaxTree.swift b/Sources/SkipSyntax/SyntaxTree.swift index 1bfb5577..fb1d7403 100644 --- a/Sources/SkipSyntax/SyntaxTree.swift +++ b/Sources/SkipSyntax/SyntaxTree.swift @@ -6,7 +6,7 @@ import SwiftSyntax /// Representation of the Swift syntax tree. -public final class SyntaxTree: PrettyPrintable { +public final class SyntaxTree: PrettyPrintable, @unchecked Sendable { let source: Source let preprocessorSymbols: Set let root: CodeBlock = CodeBlock(statements: []) diff --git a/Sources/SkipSyntax/Transpilation.swift b/Sources/SkipSyntax/Transpilation.swift index 84bc84ac..2a1be259 100644 --- a/Sources/SkipSyntax/Transpilation.swift +++ b/Sources/SkipSyntax/Transpilation.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only /// A transpilation result. -public struct Transpilation : Encodable { // Encodable for tool JSON output option +public struct Transpilation : Encodable, @unchecked Sendable { // Encodable for tool JSON output option public var input: Source public var output: Source public var outputType: OutputType diff --git a/Sources/SkipSyntax/Transpiler.swift b/Sources/SkipSyntax/Transpiler.swift index f18bda15..de9627ef 100644 --- a/Sources/SkipSyntax/Transpiler.swift +++ b/Sources/SkipSyntax/Transpiler.swift @@ -7,7 +7,7 @@ import SwiftParser import SwiftSyntax /// Manages the transpilation process. -public struct Transpiler { +public struct Transpiler: @unchecked Sendable { private let packageName: String? private let transpileFiles: [Source.FilePath] private let bridgeFiles: [Source.FilePath] diff --git a/Sources/SkipSyntax/TypeSignature.swift b/Sources/SkipSyntax/TypeSignature.swift index d9cd9102..ce919261 100644 --- a/Sources/SkipSyntax/TypeSignature.swift +++ b/Sources/SkipSyntax/TypeSignature.swift @@ -11,7 +11,7 @@ import SwiftSyntax /// /// - Warning: Use the factory methods of this enum to extract and apply modifiers rather than attempting to create enums by hand. /// - Note: `Codable` for use in `CodebaseInfo`. -indirect enum TypeSignature: CustomStringConvertible, Hashable, Codable { +indirect enum TypeSignature: CustomStringConvertible, Hashable, Codable, Sendable { case any case anyObject case array(TypeSignature?) // Nil means the generic type has been erased @@ -2146,7 +2146,7 @@ indirect enum TypeSignature: CustomStringConvertible, Hashable, Codable { } /// A parameter in a function signature. - struct Parameter: CustomStringConvertible, Hashable, Codable { + struct Parameter: CustomStringConvertible, Hashable, Codable, Sendable { var label: String? var type: TypeSignature var isInOut = false diff --git a/Tests/SkipBuildTests/AndroidEmulatorSetupTests.swift b/Tests/SkipBuildTests/AndroidEmulatorSetupTests.swift index 4c2ec495..79b34908 100644 --- a/Tests/SkipBuildTests/AndroidEmulatorSetupTests.swift +++ b/Tests/SkipBuildTests/AndroidEmulatorSetupTests.swift @@ -14,7 +14,7 @@ import ArgumentParser /// Note: To create an instance, use `try TestMessageCommand.parse([])` in your test. @available(macOS 13, iOS 16, tvOS 16, watchOS 8, *) struct TestMessageCommand: MessageCommand, ToolOptionsCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "test-message-command", abstract: "Test command for validators", shouldDisplay: false diff --git a/Tests/SkipSyntaxTests/FeatureSupportTests.swift b/Tests/SkipSyntaxTests/FeatureSupportTests.swift index dfb9627c..0bf46af4 100644 --- a/Tests/SkipSyntaxTests/FeatureSupportTests.swift +++ b/Tests/SkipSyntaxTests/FeatureSupportTests.swift @@ -230,7 +230,7 @@ final class FeatureSupportTests: XCTestCase { } struct MaterialTheme { - static var typography = Typography() + nonisolated(unsafe) static var typography = Typography() struct Typography { let bodyLarge: Value = Value() diff --git a/Tests/SkipSyntaxTests/MemberDeclarationTests.swift b/Tests/SkipSyntaxTests/MemberDeclarationTests.swift index 274cf649..313f792f 100644 --- a/Tests/SkipSyntaxTests/MemberDeclarationTests.swift +++ b/Tests/SkipSyntaxTests/MemberDeclarationTests.swift @@ -2169,7 +2169,7 @@ final class MemberDeclarationTests: XCTestCase { } } -var sideEffectOrdering: [String] = [] +nonisolated(unsafe) var sideEffectOrdering: [String] = [] private struct MemberDeclarationTestsSideEffectsStruct { var i = 0 { diff --git a/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift b/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift index 62ffad59..d144857d 100644 --- a/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift +++ b/Tests/SkipSyntaxTests/XCTestCaseAdditions.swift @@ -32,7 +32,7 @@ extension XCTestCase { /// - swiftBridgeSupports: multiple expected bridging swift outputs /// - file: the file of the call site, expected to be `#file` /// - line: the line of the call site, expected to be `#line` - public func check(expectFailure: Bool = false, expectMessages: Bool = false, compiler: String? = ProcessInfo.processInfo.environment["KOTLINC"], dependentModules: [CodebaseInfo.ModuleExport] = [], supportingSwift: String? = nil, swift: StaticString? = nil, swiftCode: (() throws -> String?)? = nil, swiftBridge: String? = nil, kotlin: String? = nil, kotlins: [String] = [], fixup fixupKotlinBlock: ((String) -> (String)) = { $0 }, kotlinPackageSupport: String? = nil, swiftBridgeSupport: String? = nil, swiftBridgeSupports: [String] = [], bridgeDecodeLevel: DecodeLevel = .api, preprocessorSymbols: Set = [], transformers: [KotlinTransformer] = builtinKotlinTransformers(), file: StaticString = #file, line: UInt = #line) async throws { + public func check(expectFailure: Bool = false, expectMessages: Bool = false, compiler: String? = ProcessInfo.processInfo.environment["KOTLINC"], dependentModules: [CodebaseInfo.ModuleExport] = [], supportingSwift: String? = nil, swift: StaticString? = nil, swiftCode: (() throws -> String?)? = nil, swiftBridge: String? = nil, kotlin: String? = nil, kotlins: [String] = [], fixup fixupKotlinBlock: ((String) -> (String)) = { $0 }, kotlinPackageSupport: String? = nil, swiftBridgeSupport: String? = nil, swiftBridgeSupports: [String] = [], bridgeDecodeLevel: DecodeLevel = .api, preprocessorSymbols: Set = [], transformers: [KotlinTransformer] = builtinKotlinTransformers(), file: StaticString = #filePath, line: UInt = #line) async throws { func fixup(code: String) -> String { var code = fixupKotlinBlock(code) diff --git a/skip b/skip index 88a54088..24ff416a 160000 --- a/skip +++ b/skip @@ -1 +1 @@ -Subproject commit 88a540887a99625560cb09a8dad16d992d92e2ba +Subproject commit 24ff416a7b3a399ebcc129841c6d8e7105683b24 From 8a107b93d83687e0beb6e94649e01529fe0e4b18 Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Thu, 21 May 2026 16:35:28 -0400 Subject: [PATCH 3/4] Update skip submodule --- skip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skip b/skip index 24ff416a..ddc0f7e6 160000 --- a/skip +++ b/skip @@ -1 +1 @@ -Subproject commit 24ff416a7b3a399ebcc129841c6d8e7105683b24 +Subproject commit ddc0f7e641dad442af9a2bda3b5db28347576637 From 53ff52e466c6fff003241e51bcc3802be9eba7db Mon Sep 17 00:00:00 2001 From: Marc Prud'hommeaux Date: Thu, 21 May 2026 17:15:28 -0400 Subject: [PATCH 4/4] Add Sendable conformances and update build runner to macos-15 --- .github/workflows/ci.yml | 3 +-- Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift | 6 +++--- Sources/SkipSyntax/Message.swift | 4 ++-- Sources/SkipSyntax/Source.swift | 6 +++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df73e307..1f613336 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,7 @@ jobs: os: - 'ubuntu-latest' #- 'macos-latest' - # Homebrew bottle builds on macos-14, so we need to as well - - 'macos-14' + - 'macos-15-intel' steps: - name: Checkout skipstone.git uses: actions/checkout@v6 diff --git a/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift b/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift index c90b6ddc..5991830c 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinHelperTypes.swift @@ -301,9 +301,9 @@ extension Attributes { } extension Attribute { - nonisolated(unsafe) static let bridgeIgnored = Attribute(signature: .named("BridgeIgnored", [])) - nonisolated(unsafe) static let bridgeToKotlin = Attribute(signature: .named("BridgeToKotlin", [])) - nonisolated(unsafe) static let bridgeToSwift = Attribute(signature: .named("BridgeToSwift", [])) + static let bridgeIgnored = Attribute(signature: .named("BridgeIgnored", [])) + static let bridgeToKotlin = Attribute(signature: .named("BridgeToKotlin", [])) + static let bridgeToSwift = Attribute(signature: .named("BridgeToSwift", [])) func append(to output: OutputGenerator, indentation: Indentation) { switch kind { diff --git a/Sources/SkipSyntax/Message.swift b/Sources/SkipSyntax/Message.swift index efcd760f..048ec400 100644 --- a/Sources/SkipSyntax/Message.swift +++ b/Sources/SkipSyntax/Message.swift @@ -5,8 +5,8 @@ import SwiftSyntax /// An Xcode-formatted message for the user. -public struct Message: Error, Codable { - public enum Kind: String, Codable, Equatable { +public struct Message: Error, Codable, Sendable { + public enum Kind: String, Codable, Equatable, Sendable { /// A trace-level statement that will only be emitted in debug mode case trace case note // SwiftSyntax.DiagnosticSeverity.note diff --git a/Sources/SkipSyntax/Source.swift b/Sources/SkipSyntax/Source.swift index e26ca33b..36659976 100644 --- a/Sources/SkipSyntax/Source.swift +++ b/Sources/SkipSyntax/Source.swift @@ -28,7 +28,7 @@ public struct Source : Encodable, Sendable { self.lines = lines } - struct SourceLine : Encodable { + struct SourceLine : Encodable, Sendable { let offset: Int let line: String } @@ -138,7 +138,7 @@ public struct Source : Encodable, Sendable { } /// A line and column-based range in the source, appropriate for Xcode reporting. - public struct Range: Equatable, Codable { + public struct Range: Equatable, Codable, Sendable { public let start: Position public let end: Position @@ -150,7 +150,7 @@ public struct Source : Encodable, Sendable { /// A line and column-based position in the source, appropriate for Xcode reporting. /// Line and column numbers start with 1 rather than 0. - public struct Position: Equatable, Comparable, Codable { + public struct Position: Equatable, Comparable, Codable, Sendable { public let line: Int public let column: Int