From bcded5afa3f27474b18aee00ebad95ad1a5c9812 Mon Sep 17 00:00:00 2001 From: Pool Camacho Date: Mon, 20 Apr 2026 07:05:04 -0600 Subject: [PATCH] refactor: migrate to Swift 6 language mode and bump CI to macos-26 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flips SWIFT_VERSION from 5.0 to 6.0 on every build config (Maple, MapleTests, MapleUITests × Debug / Release) and upgrades the toolchain holistically — CI runners and README requirements — so the project tracks Swift 6.3 idioms instead of shimming around older stable features. Code changes for strict concurrency: - DiffParser: enum marked nonisolated. Parsing is pure value-to-value work and is consumed by GitService (an actor) from its own isolation domain — forcing it onto MainActor (via the project-wide SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor) was the source of the errors. - DiffFile: struct marked nonisolated for the same reason; its flattened view and patch builders now run wherever the parser does. - FileWatcher: keeps MainActor isolation (it feeds @Observable state the UI reads) and switches to isolated deinit so deinit can still call stop() to cancel the dispatch sources and close the FDs. This is stable on Swift 6.3; the feature shipped with Xcode 26. Tooling: - .github/workflows/ci.yml, codeql.yml, release.yml: runs-on bumped from macos-15 (Xcode 16.x, Swift 6.0 where isolated deinit is still gated behind an experimental flag) to macos-26 (Xcode 26.x, Swift 6.3 stable). - README: Swift badge 5 → 6, Requirements line updated to Xcode 26+ with a short note explaining why. Deployment target stays macOS 14.0; isolated deinit is a compile-time feature and does not raise the runtime floor. Build clean, swiftlint --strict clean. --- .github/workflows/ci.yml | 4 ++-- .github/workflows/codeql.yml | 2 +- .github/workflows/release.yml | 2 +- Maple.xcodeproj/project.pbxproj | 12 ++++++------ Maple/Models/GitModels.swift | 2 +- Maple/Services/DiffParser.swift | 5 +++-- Maple/Services/FileWatcher.swift | 4 ++-- README.md | 4 ++-- 8 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93896fb..e9ca4ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ env: jobs: build: name: Build & Analyze - runs-on: macos-15 + runs-on: macos-26 steps: - uses: actions/checkout@v6 @@ -51,7 +51,7 @@ jobs: lint: name: SwiftLint - runs-on: macos-15 + runs-on: macos-26 steps: - uses: actions/checkout@v6 - name: Install SwiftLint diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c6f2859..c7bbf16 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,7 +25,7 @@ env: jobs: analyze: name: Analyze (Swift) - runs-on: macos-15 + runs-on: macos-26 permissions: actions: read contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34e3879..000e718 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ env: jobs: build-release: name: Build unsigned .app - runs-on: macos-15 + runs-on: macos-26 steps: - uses: actions/checkout@v6 diff --git a/Maple.xcodeproj/project.pbxproj b/Maple.xcodeproj/project.pbxproj index 0ac28b7..287fe4c 100644 --- a/Maple.xcodeproj/project.pbxproj +++ b/Maple.xcodeproj/project.pbxproj @@ -420,7 +420,7 @@ SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -456,7 +456,7 @@ SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Release; }; @@ -476,7 +476,7 @@ SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Maple.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Maple"; }; name = Debug; @@ -497,7 +497,7 @@ SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Maple.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Maple"; }; name = Release; @@ -516,7 +516,7 @@ SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_TARGET_NAME = Maple; }; name = Debug; @@ -535,7 +535,7 @@ SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_TARGET_NAME = Maple; }; name = Release; diff --git a/Maple/Models/GitModels.swift b/Maple/Models/GitModels.swift index 7c1c119..c68b8d9 100644 --- a/Maple/Models/GitModels.swift +++ b/Maple/Models/GitModels.swift @@ -133,7 +133,7 @@ struct DiffHunk: Identifiable, Sendable { /// One file's diff: the raw preamble lines (`diff --git`, `index`, `---`, `+++`) /// grouped together with that file's hunks. Preserving the preamble is what /// lets us round-trip a subset of hunks back into `git apply --cached`. -struct DiffFile: Identifiable, Sendable { +nonisolated struct DiffFile: Identifiable, Sendable { let id = UUID() let path: String? let preamble: [String] diff --git a/Maple/Services/DiffParser.swift b/Maple/Services/DiffParser.swift index 8311241..55c93e9 100644 --- a/Maple/Services/DiffParser.swift +++ b/Maple/Services/DiffParser.swift @@ -8,8 +8,9 @@ import Foundation /// Parses raw `git diff` / `git show` output into structured `DiffFile` / -/// `DiffLine` representations. -enum DiffParser { +/// `DiffLine` representations. Nonisolated because parsing is pure +/// value-to-value work that must run in any actor's isolation domain. +nonisolated enum DiffParser { /// Structured view: one entry per file, each with its preamble preserved so /// a patch can be reconstructed for a subset of hunks later. diff --git a/Maple/Services/FileWatcher.swift b/Maple/Services/FileWatcher.swift index 861818f..3ff66bf 100644 --- a/Maple/Services/FileWatcher.swift +++ b/Maple/Services/FileWatcher.swift @@ -11,7 +11,7 @@ import Observation @Observable final class FileWatcher { - /// Callback fired (on the main thread) when watched directory changes. + /// Callback fired on the main actor when the watched directory changes. var onChange: (() -> Void)? private var sources: [DispatchSourceFileSystemObject] = [] @@ -108,7 +108,7 @@ final class FileWatcher { ) } - deinit { + isolated deinit { stop() } } diff --git a/README.md b/README.md index ecd65ef..a08302d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![CI](https://github.com/poolcamacho/Maple/actions/workflows/ci.yml/badge.svg)](https://github.com/poolcamacho/Maple/actions/workflows/ci.yml) [![Platform](https://img.shields.io/badge/platform-macOS%2014%2B-lightgrey)](https://www.apple.com/macos/) -[![Swift](https://img.shields.io/badge/Swift-5-orange)](https://swift.org) +[![Swift](https://img.shields.io/badge/Swift-6-orange)](https://swift.org) [![License: MIT](https://img.shields.io/github/license/poolcamacho/Maple)](LICENSE) [![Stars](https://img.shields.io/github/stars/poolcamacho/Maple?style=flat)](https://github.com/poolcamacho/Maple/stargazers) [![Forks](https://img.shields.io/github/forks/poolcamacho/Maple?style=flat)](https://github.com/poolcamacho/Maple/network/members) @@ -46,7 +46,7 @@ Most Git GUIs on macOS are Electron based, locked behind a subscription, or over ## Requirements - macOS 14.0+ (Apple Silicon recommended, Intel supported) -- Xcode 16+ +- Xcode 26+ (Swift 6.3 toolchain; required for `isolated deinit`) - Git installed (ships with Xcode Command Line Tools) ## Getting started