From 0d1f81ec11a879508ace10f753b7f01960bd4db0 Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Wed, 28 Oct 2026 16:11:41 +0100 Subject: [PATCH] ci: pinned images and Xcode, --parallel, Pages permissions on the deploy job, no iOS claim (#17) Both CI jobs and the docs build name their image and Xcode (DEVELOPER_DIR: 26.6 on macos-26, 26.0 on macos-15) instead of picking the newest with sort -V, which can select a beta. The gate runs tools/test.sh --parallel; it already exits with swift test's status and fails on any warning, so the tee and the second warnings step go, and with them the CIScriptTests check that parsed ci.yml as text. Pages write and id-token move from the workflow to the deploy job. Package.swift drops .iOS(.v18): SpectroKit has never built for iOS (FormatoKit's readers use macOS-only XMLDocument, FormatoKit#27). Done: #17's toolchain, permissions and iOS parts; lint (#26), Dependabot and the reference scan (#25) land in their own PRs. Next: Paul enables Pages for the DocC site. BREAKING CHANGE: Package.swift no longer declares iOS; it never built there. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 39 ++++++++--------------- .github/workflows/docs.yml | 22 ++++++------- Package.swift | 2 +- Tests/SpectroKitTests/CIScriptTests.swift | 17 ---------- 4 files changed, 26 insertions(+), 54 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1e9f2b..0b4ecbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,7 @@ -# SpectroKit CI: the gate (tools/test.sh = `swift test -c release`), the consumer check, and the tag check. -# -# Written 2026-10-14; enabling it (Actions on the repository) is the owner's. The package declares macOS 15 / iOS 18 and a -# Swift 6.2 toolchain (Package.swift: `.macOS(.v15)`, tools 6.2). -# Two jobs (0.5.1): `test` on the `macos-26` image (Swift 6.3.3, newest Xcode, printed by the job) and `test-macos15` on macos-15 -# with Xcode 26.0, the floor the package now declares (macOS 15, iOS 18; FormatoKit 0.2.1's dylibs are built for 15). -# Since 0.4.0 the readers are FormatoKit's, a package dependency fetched from github.com/mac4DSTEM/FormatoKit, which ships -# libhdf5 inside FormatoHDF5: no libhdf5 step. FormatoKit is public (2026-10-15), so the runner fetches it with no token. +# SpectroKit CI: the gate (tools/test.sh: `swift test -c release --parallel`, failing on any warning), the consumer check, and +# the tag check. Two macOS jobs, each on a pinned image and Xcode: `test` on macos-26 with Xcode 26.6 (Swift 6.3), and +# `test-macos15` on macos-15 with Xcode 26.0 (Swift 6.2), the floor Package.swift declares (macOS 15, tools 6.2). +# The dependencies (FormatoKit, ImagoKit) are public packages the runner resolves with no token. # Never `swift build --build-tests -c release` (fails on this toolchain's Swift Build backend); tools/test.sh is the gate. name: CI @@ -24,24 +20,17 @@ jobs: test: runs-on: macos-26 timeout-minutes: 90 + env: + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer steps: - uses: actions/checkout@v4 - - name: Select the newest Xcode + - name: Toolchain run: | - xcode=$(ls -d /Applications/Xcode*.app | sort -V | tail -1) - sudo xcode-select -s "$xcode" xcodebuild -version swift --version + # tools/test.sh exits with swift test's status and fails on any warning; its log is .build/test-results/test.log. - name: Gate (tools/test.sh, release) - shell: bash - run: tools/test.sh 2>&1 | tee test.log - - name: No warnings - run: | - # tools/test.sh already fails on any warning but the three FormatoKit's signed dylibs cause; this step is kept as - # a second look at the log. - if sed 's/warning:/\nwarning:/g' test.log | grep "warning:" | grep -v "not stripping binary because it is signed" | grep -q .; then - sed 's/warning:/\nwarning:/g' test.log | grep "warning:" | head -20; exit 1 - fi + run: tools/test.sh --parallel - name: Consumer check run: tools/consumer-check/run.sh version: @@ -55,17 +44,17 @@ jobs: top=$(grep -m1 -E '^## \[?v?[0-9]+\.[0-9]+\.[0-9]+' CHANGELOG.md | sed -E 's/^## \[?v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/') tag=${GITHUB_REF_NAME#v} [ "$top" = "$tag" ] || { echo "CHANGELOG top is $top, tag is $tag"; exit 1; } - # 0.5.1: the macOS 15 floor. Xcode 26.0 (Swift 6.2) pinned the way FormatoKit's CI pins it; a macos-15 image's default Xcode may - # carry an older Swift than the manifests' tools-version 6.2. No Test.cancel anywhere (missing on this toolchain). + # The macOS 15 floor with the oldest Swift the manifest allows (tools-version 6.2). No Test.cancel anywhere (missing there). test-macos15: runs-on: macos-15 timeout-minutes: 90 + env: + DEVELOPER_DIR: /Applications/Xcode_26.0.app/Contents/Developer steps: - uses: actions/checkout@v4 - - name: Select Xcode 26.0 (Swift 6.2) + - name: Toolchain run: | - sudo xcode-select -s /Applications/Xcode_26.0.app swift --version swift --version | grep -E "Swift version 6\.([2-9]|[1-9][0-9])" || { echo "Swift 6.2 or newer needed"; exit 1; } - name: Gate (tools/test.sh, release) - run: tools/test.sh + run: tools/test.sh --parallel diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 81ced30..1449ee3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,9 +2,9 @@ # published to GitHub Pages at https://mac4dstem.github.io/SpectroKit/documentation/ on every tag. # On a pull request the site is built and uploaded as an artifact only, so a broken catalog fails before the tag. # -# No swift-docc-plugin: the kit takes no package dependencies (CLAUDE.md). `xcodebuild docbuild` builds the archives -# from the package's own schemes; `docc merge` (Xcode 16+) joins them. Needs Pages enabled with source "GitHub Actions" -# (repository Settings > Pages). Copied from CuboKit's pattern; the module list is read from the .docc catalogs. +# No swift-docc-plugin, so the docs add no dependency to the manifest: `xcodebuild docbuild` builds the archives from the +# package's own schemes and `docc merge` joins them; the module list is read from the .docc catalogs. Needs Pages enabled with +# source "GitHub Actions" (repository Settings > Pages) and the `github-pages` environment allowing tags `*.*.*`. name: Docs on: @@ -16,8 +16,6 @@ on: permissions: contents: read - pages: write - id-token: write concurrency: group: docs-${{ github.ref }} @@ -25,15 +23,14 @@ concurrency: jobs: build: - runs-on: macos-latest + runs-on: macos-26 timeout-minutes: 60 + env: + DEVELOPER_DIR: /Applications/Xcode_26.6.app/Contents/Developer steps: - uses: actions/checkout@v4 - - name: Select the newest Xcode - run: | - xcode=$(ls -d /Applications/Xcode*.app | sort -V | tail -1) - sudo xcode-select -s "$xcode" - xcodebuild -version + - name: Toolchain + run: xcodebuild -version - name: Build the site run: tools/build-docs.sh .docs-site /SpectroKit - uses: actions/upload-pages-artifact@v3 @@ -44,6 +41,9 @@ jobs: needs: build if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/Package.swift b/Package.swift index 66096eb..9731a8b 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ import PackageDescription let package = Package( name: "SpectroKit", - platforms: [.macOS(.v15), .iOS(.v18)], + platforms: [.macOS(.v15)], products: [ .library(name: "SpectroCore", targets: ["SpectroCore"]), .library(name: "SpectroEELS", targets: ["SpectroEELS"]), diff --git a/Tests/SpectroKitTests/CIScriptTests.swift b/Tests/SpectroKitTests/CIScriptTests.swift index 43877d9..bd43898 100644 --- a/Tests/SpectroKitTests/CIScriptTests.swift +++ b/Tests/SpectroKitTests/CIScriptTests.swift @@ -18,23 +18,6 @@ struct CIScriptTests { return process.terminationStatus } - @Test func testCIGatePropagatesSwiftTestFailureThroughTee() throws { - let workflow = try String(contentsOf: root.appendingPathComponent(".github/workflows/ci.yml"), encoding: .utf8) - let gate = try #require(workflow.components(separatedBy: "- name: Gate (tools/test.sh, release)").dropFirst().first) - .components(separatedBy: "- name:")[0] - let temp = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try FileManager.default.createDirectory(at: temp.appendingPathComponent("tools"), withIntermediateDirectories: true) - defer { try? FileManager.default.removeItem(at: temp) } - try "#!/bin/sh\nexit 23\n".write(to: temp.appendingPathComponent("tools/test.sh"), atomically: true, encoding: .utf8) - try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: temp.appendingPathComponent("tools/test.sh").path) - // GitHub's implicit bash shell uses -e without pipefail. An explicit bash shell adds pipefail. - let explicitBash = gate.contains("shell: bash") - let command = try #require(gate.components(separatedBy: "\n").first { $0.trimmingCharacters(in: .whitespaces).hasPrefix("run: ") }) - .trimmingCharacters(in: .whitespaces).dropFirst("run: ".count) - let script = (explicitBash ? "set -o pipefail\n" : "") + command - #expect(try run("/bin/bash", script: script, in: temp) == 23) - } - @Test func testConsumerTargetInventorySurvivesAbsentBuildDirectories() throws { let source = try String(contentsOf: root.appendingPathComponent("tools/consumer-check/run.sh"), encoding: .utf8) let start = try #require(source.range(of: "built=$("))