From ea6d2619ef03a7c2b02b998879c4271121239b3a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 15:06:59 +0000 Subject: [PATCH 1/4] ci: pin Xcode 26.6, run the consumer checks and an iOS build CI and Docs ran on macos-latest with whatever Xcode was newest, so an image update could change the compiler under a green commit. Both now run on macos-26 with Xcode 26.6 via DEVELOPER_DIR, as the apps do. The test job also runs tools/consumer-check, tools/readme-consumer and an iOS Simulator build of the package (Package.swift declares iOS 18), and the lint job's checkout moves off the Node 20 major. Fixes mac4DSTEM/CuboKit#17 Done: CI pins its toolchain and runs the consumer checks and the iOS build. Next: Paul re-runs Docs for the newest tag once this is merged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VkKZEWRjvbRGe75ep8jhBn --- .github/workflows/ci.yml | 26 ++++++++++++++++++-------- .github/workflows/docs.yml | 11 +++++++---- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed08032..6e93d1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,8 @@ -# CuboKit CI: the test run (tools/test.sh = `swift test -c release`) and, on a tag, the CHANGELOG version check. +# CuboKit CI: the test run (tools/test.sh = `swift test -c release`), the consumer check, the README consumer, an iOS +# Simulator build and, on a tag, the CHANGELOG version check. # -# Actions must be enabled on the repository. The package needs macOS 15 or later and a Swift 6.2 or later toolchain -# (Package.swift: `.macOS(.v15)`, tools 6.2); `macos-latest` with its newest Xcode carries both. +# The package needs macOS 15 / iOS 18 or later and a Swift 6.2 or later toolchain (Package.swift: tools 6.2); the +# macos-26 image with Xcode 26.6, pinned below, carries both. # No real data in CI: RealDataTests skip by name when ~/Library/Caches/CuboKit/real-data is absent, so tools/test.sh is # green without it. No libhdf5 and no References/ (CuboKit has no package dependencies). # Never `swift build --build-tests -c release` (fails on this toolchain's Swift Build backend); tools/test.sh is the gate. @@ -18,19 +19,28 @@ concurrency: group: ci-${{ github.ref }} cancel-in-progress: true +env: + # Pinned like the apps: the macos-26 image's Xcode 26.6 (Swift 6.3, macOS 26 SDK). Bump on purpose. + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer + jobs: test: - runs-on: macos-latest + runs-on: macos-26 timeout-minutes: 90 steps: - uses: actions/checkout@v7 - - name: Select the newest Xcode + - name: Toolchain on record run: | - xcode=$(ls -d /Applications/Xcode*.app | sort -V | tail -1) - sudo xcode-select -s "$xcode" + xcode-select -p swift --version - name: Gate (tools/test.sh, release) run: tools/test.sh + - name: Consumer check (a separate package, the public API only) + run: tools/consumer-check/run.sh + - name: README consumer (a separate package compiling exactly the README's program, output checked) + run: tools/readme-consumer/run.sh + - name: Build the libraries for iOS (Simulator, not run) + run: xcodebuild -scheme CuboKit-Package -destination 'generic/platform=iOS Simulator' -quiet build version: name: CHANGELOG version matches the tag @@ -59,7 +69,7 @@ jobs: name: Lint (shared SwiftLint and SwiftFormat config) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: SwiftLint run: docker run --rm -v "$PWD:/work" -w /work --entrypoint swiftlint ghcr.io/realm/swiftlint:0.65.1 lint --strict --quiet - name: SwiftFormat diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7cba055..b5433fb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,16 +23,19 @@ concurrency: group: docs-${{ github.ref }} cancel-in-progress: true +env: + # Pinned like the apps: the macos-26 image's Xcode 26.6 (Swift 6.3, macOS 26 SDK). Bump on purpose. + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer + jobs: build: - runs-on: macos-latest + runs-on: macos-26 timeout-minutes: 60 steps: - uses: actions/checkout@v7 - - name: Select the newest Xcode + - name: Toolchain on record run: | - xcode=$(ls -d /Applications/Xcode*.app | sort -V | tail -1) - sudo xcode-select -s "$xcode" + xcode-select -p xcodebuild -version - name: Build the site run: tools/build-docs.sh .docs-site /CuboKit From 47a0ac359724fc9541a764ced8712d31c9653ccd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 16:28:23 +0000 Subject: [PATCH 2/4] ci: run the extra macOS builds after merge, not on every pull request The free plan's macOS runners are scarce. The required test job keeps its name and still runs on every pull request; the added consumer, iOS and URL-resolution builds move to a job that runs on push, tag and by hand. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VkKZEWRjvbRGe75ep8jhBn --- .github/workflows/ci.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e93d1a..86f707d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,5 @@ -# CuboKit CI: the test run (tools/test.sh = `swift test -c release`), the consumer check, the README consumer, an iOS -# Simulator build and, on a tag, the CHANGELOG version check. +# CuboKit CI: the test run (tools/test.sh = `swift test -c release`) on every pull request; after merge the consumer check, +# the README consumer and an iOS Simulator build; on a tag, the CHANGELOG version check. # # The package needs macOS 15 / iOS 18 or later and a Swift 6.2 or later toolchain (Package.swift: tools 6.2); the # macos-26 image with Xcode 26.6, pinned below, carries both. @@ -35,6 +35,15 @@ jobs: swift --version - name: Gate (tools/test.sh, release) run: tools/test.sh + + post-merge: + name: Consumer checks and iOS build (after merge) + # Not on pull requests: the free plan's macOS runners are scarce, so the extra builds run on main, tags and by hand. + if: github.event_name != 'pull_request' + runs-on: macos-26 + timeout-minutes: 90 + steps: + - uses: actions/checkout@v7 - name: Consumer check (a separate package, the public API only) run: tools/consumer-check/run.sh - name: README consumer (a separate package compiling exactly the README's program, output checked) From effed861b970e5f50a79d93648cc0580fc262b79 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 17:06:36 +0000 Subject: [PATCH 3/4] test: give the consumer check's two slow closures explicit types Xcode 26.6's type checker gives up on the hpFrame and positions closures ("unable to type-check this expression in reasonable time"), so the consumer check never built on the pinned toolchain. Explicit parameter and result types; the values are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VkKZEWRjvbRGe75ep8jhBn --- tools/consumer-check/Sources/consumer-check/main.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/consumer-check/Sources/consumer-check/main.swift b/tools/consumer-check/Sources/consumer-check/main.swift index b39a0f1..5bcecba 100644 --- a/tools/consumer-check/Sources/consumer-check/main.swift +++ b/tools/consumer-check/Sources/consumer-check/main.swift @@ -475,7 +475,7 @@ check("CuboVirtualError.invalidInput", CuboVirtualError.invalidInput("x") == .in // HotPixelFilter.Replacer: the reusable in-place replacement plan (equals the one-frame `replacing`). do { let hpMask = (0..<20).map { $0 % 7 == 3 } - let hpFrame = (0..<20).map { Float(($0 * 37) % 11) + (($0 == 3) ? 50 : 0) } + let hpFrame: [Float] = (0..<20).map { (i: Int) -> Float in Float((i * 37) % 11) + (i == 3 ? 50 : 0) } var hpPlanned = hpFrame let replacer = try HotPixelFilter.Replacer(rows: 4, columns: 5, mask: hpMask) replacer.apply(to: &hpPlanned) @@ -659,7 +659,9 @@ do { let probe = try PtychographyArray(rows: d, columns: d, real: probeRe, imaginary: [Double](repeating: 0, count: d * d)) let object = try PtychographyArray(rows: 24, columns: 24, real: [Double](repeating: 1, count: 576), imaginary: [Double](repeating: 0, count: 576)) let amps = [Double](repeating: 1, count: count * d * d) - let positions = (0.. PtychographyPosition in + PtychographyPosition(row: Double(8 + 3 * i), column: Double(8 + 2 * i)) + } nonisolated(unsafe) var ptychoProgress: [Int] = [] let single = try SingleslicePtychography.reconstruct(amplitudes: amps, positions: positions, object: object, probe: probe, iterations: 2, precision: .single, progress: { done, _ in ptychoProgress.append(done) }) From 4b8dad66bde7414d74eb2f90978bd95095a3f7ea Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 17:44:24 +0000 Subject: [PATCH 4/4] test: give one #expect closure an explicit type for Xcode 26.6 The same shape (#expect(values == (0.. Claude-Session: https://claude.ai/code/session_01VkKZEWRjvbRGe75ep8jhBn --- Tests/CuboVirtualTests/TruthTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CuboVirtualTests/TruthTests.swift b/Tests/CuboVirtualTests/TruthTests.swift index 8349d08..68307a1 100644 --- a/Tests/CuboVirtualTests/TruthTests.swift +++ b/Tests/CuboVirtualTests/TruthTests.swift @@ -117,7 +117,7 @@ import CuboVirtual for (row, column) in [(1, 5), (3, 2), (4, 0), (0, 6), (2, 4)] { for path in paths { let image = try await VirtualImaging.image(of: source, geometry: .point(row: row, column: column), path: path) - #expect(image.values == (0..<6).map { Double($0 * 10000 + row * 100 + column) }, "(\(row), \(column)) \(path)") + #expect(image.values == (0..<6).map { (i: Int) -> Double in Double(i * 10000 + row * 100 + column) }, "(\(row), \(column)) \(path)") } } }