ci: run Swift core tests on available Docker runner
Some checks failed
macoc CI (Docker) / swift-core (push) Failing after 3s

This commit is contained in:
root 2026-10-26 10:04:29 +03:00
parent 009c7e5a33
commit 1e50fae6c8
7 changed files with 206 additions and 90 deletions

73
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,73 @@
name: macoc CI (Docker)
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
jobs:
swift-core:
# This is the ONLY currently available runner label.
runs-on: docker
# The runner may have a Node/Alpine default image. Override it with
# official Swift on Linux so we can run the real platform-neutral XCTest.
container:
image: swift:6.2-noble
steps:
- name: Check out commit without Node.js actions
env:
MACOC_SERVER_URL: "${{ forgejo.server_url }}"
MACOC_REPOSITORY: "${{ forgejo.repository }}"
MACOC_SHA: "${{ forgejo.sha }}"
MACOC_TOKEN: "${{ forgejo.token }}"
run: |
set -eu
test "$MACOC_REPOSITORY" = "catsona/macoc"
case "$MACOC_SHA" in
''|*[!0-9a-f]*) echo "Invalid Forgejo commit SHA" >&2; exit 1 ;;
esac
# Checkout actions depend on Node.js, which the official Swift
# image does not provide. Forgejo's own archive API returns an
# archive for the exact triggering commit (also for private repos).
workspace="${FORGEJO_WORKSPACE:-${GITHUB_WORKSPACE:-$PWD}}"
mkdir -p "$workspace"
archive="$(mktemp)"
config="$(mktemp)"
chmod 600 "$config"
trap 'rm -f "$archive" "$config"' EXIT
printf 'header = "Authorization: token %s"\n' "$MACOC_TOKEN" > "$config"
curl --config "$config" --fail --silent --show-error \
"$MACOC_SERVER_URL/api/v1/repos/$MACOC_REPOSITORY/archive/$MACOC_SHA.tar.gz" \
--output "$archive"
tar -xzf "$archive" --strip-components=1 -C "$workspace"
cd "$workspace"
# The API tarball has no .git directory. Create a temporary local
# index so git diff --cached --check still catches whitespace errors.
git init -q
git config --global --add safe.directory "$workspace"
git add -A
- name: Lint and run Linux SwiftCore XCTest
env:
MACOC_REQUIRE_SWIFT: '1'
run: |
swift --version
bash scripts/verify.sh
- name: Verify release tag matches Info.plist
if: startsWith(forgejo.ref, 'refs/tags/v')
env:
MACOC_TAG: "${{ forgejo.ref_name }}"
run: |
set -eu
version="$(sed -n '/<key>CFBundleShortVersionString<\/key>/{n;s/.*<string>\([^<]*\)<\/string>.*/\1/p;}' Resources/Info.plist)"
if [ -z "$version" ] || [ "$MACOC_TAG" != "v$version" ]; then
echo "Tag $MACOC_TAG does not match app version v$version" >&2
exit 1
fi
echo "Validated source tag $MACOC_TAG (no macOS binary built in Docker)."

View file

@ -1,37 +0,0 @@
name: macoc macOS
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
jobs:
build:
# A dedicated macOS Forgejo runner is required; register label macos:host.
# It must have Command Line Tools. There is no Linux/macOS cross-build.
runs-on: macos
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
with:
persist-credentials: false
- name: Run tests and static checks
run: bash scripts/verify.sh
- name: Build and package macOS app
run: bash scripts/package.sh
env:
MACOC_CODESIGN_IDENTITY: "${{ vars.MACOC_CODESIGN_IDENTITY }}"
- name: Publish Forgejo release on version tags
if: startsWith(forgejo.ref, 'refs/tags/v')
env:
FORGEJO_TOKEN: "${{ secrets.FORGEJO_TOKEN }}"
FORGEJO_SERVER_URL: "${{ forgejo.server_url }}"
FORGEJO_REPOSITORY: "${{ forgejo.repository }}"
FORGEJO_REF_NAME: "${{ forgejo.ref_name }}"
run: bash scripts/publish-release.sh

View file

@ -102,17 +102,23 @@ assigned its normal modifier role rather than No Action.
## Forgejo CI and releases
The workflow at .forgejo/workflows/macos.yml builds and tests on pushes to
main. A pushed tag such as v0.3.0 also creates a Forgejo release with a
macOS .app ZIP and SHA-256 checksum, through the built-in scoped token.
CI workflow: .forgejo/workflows/ci.yml. It uses the **existing Docker
runner** and the official Linux Swift image. Pushes to main run shell
validation and **actual XCTest for pure Swift core logic**. On version
tags it also checks the tag against the application version.
**A real Mac Forgejo runner labelled macos is required.** The Linux server
cannot build AppKit or IOKit. See [CI setup](docs/ci.md). Until a Mac runner
is registered and its job succeeds, no release build has been produced.
**Linux Docker cannot compile macOS .app binaries** because Apple AppKit,
IOKit and macOS SDK are unavailable there. Docker CI does not publish
nonexistent macOS binaries.
A release archive signed ad-hoc is intended for local evaluation.
To distribute broadly without Gatekeeper warnings, use Developer ID
signing and Apple's notarization service (not yet configured here).
For a release archive, check out the tag on a real Mac and run
bash scripts/package.sh (or task package), then execute
bash scripts/publish-release.sh with a repository-scoped Forgejo token.
See [CI and release instructions](docs/ci.md). Fully automated macOS
binary releases require a real Mac runner or another Mac build service.
Ad-hoc artifacts are suited to local evaluation; public distribution
requires Developer ID signing and Apple notarization.
## Diagnostics and recovery

View file

@ -1,48 +1,66 @@
# Forgejo Actions CI and macOS releases
# Forgejo Actions CI: existing Docker runner
The workflow is in .forgejo/workflows/macos.yml.
The only registered Forgejo runner has the **docker** label. Workflow:
.forgejo/workflows/ci.yml. It uses runs-on: docker with the official
swift:6.2-noble Linux container image.
- Push to main: Swift tests, static checks, macOS app build and ZIP archive.
- Push a version tag like v0.3.0: same checks plus a Forgejo release with
macoc-vX.Y.Z-macos-<arch>.zip and a SHA-256 checksum file.
- Manual workflow run: test and build only.
## CI checks
Artifacts are for the runner's CPU architecture, not universal.
- Main branch / manual dispatch: obtain the exact triggering commit, validate
source and shell scripts, and run the **real** XCTest suite for the pure
Swift CapswitchCore module.
- Tags v*: same tests plus Info.plist-version/tag consistency checking.
The v0.3.0 tag must match CFBundleShortVersionString 0.3.0.
## One-time macOS runner setup
scripts/test-linux-core.sh creates a temporary SwiftPM package containing
the actual Sources/Capswitch source files and Tests/CapswitchCoreTests suite.
The standard Package.swift is macOS-specific and cannot be compiled on Linux.
1. Enable Actions under repository Settings -> Units.
2. Register a dedicated runner on a real Mac with Command Line Tools, Swift,
iconutil, sips, codesign, ditto, curl, Git, Python 3 and Node for checkout.
3. Under Settings -> Actions -> Runners obtain the registration token.
4. Configure the runner label macos:host; workflow uses runs-on: macos.
5. Push a main-branch commit to check the workflow. Then push an exact version
tag matching Resources/Info.plist when the build is green.
## Why Docker CI does not produce a macOS binary
Host runners have no isolation; allow only trusted contributors to edit
the workflow, protect release tags, and avoid running arbitrary PR code.
Linux Docker does not include the Apple macOS SDK, AppKit, Carbon or IOKit.
It cannot generate a signed functional macoc.app. This workflow does not run
scripts/package.sh on Linux and does not create a misleading binary release.
Official documentation:
https://forgejo.org/docs/latest/user/actions/overview/
For a release with the current infrastructure:
1. Push tag v0.3.0 (or the version in Info.plist); verify Docker CI passed.
2. On a real Mac with Command Line Tools, check out that precise tag.
3. Run bash scripts/package.sh (equivalent to task package). This creates
dist/macoc-v0.3.0-macos-<arch>.zip and the SHA-256 checksum file.
4. Set FORGEJO_SERVER_URL, FORGEJO_REPOSITORY=catsona/macoc,
FORGEJO_REF_NAME=v0.3.0, and FORGEJO_TOKEN with repository release write
access; then run bash scripts/publish-release.sh to publish the Mac
generated ZIP/checksum to Forgejo.
That is a manual macOS packaging step. For fully automated builds of
macOS binaries, a real Mac runner or a trusted Mac build service is needed.
Code signing / Developer ID notarization is separate; ad-hoc builds may
require explicit Gatekeeper approval.
## No Node checkout dependency
Forgejo Runner's default Docker image might supply Node, but the official
Swift image does not promise Node.js, so actions/checkout is not used.
The workflow obtains an authenticated archive at the triggering SHA using
Forgejo's own API, with the short-lived repository-scoped forgejo.token.
The token is only placed in a mode-0600 temporary curl configuration,
not in clone URLs, source files or process arguments.
As tar archives have no Git index, the job creates a local Git index for
git diff --cached --check. No CI commit or push occurs.
## Prerequisites
The registered runner must advertise the docker label to this repository.
It must be allowed to pull swift:6.2-noble from Docker Hub and reach the
Forgejo API. No Docker socket inside the job is necessary.
Forgejo documentation:
https://forgejo.org/docs/latest/admin/actions/configuration/
https://forgejo.org/docs/latest/user/actions/basic-concepts/
https://forgejo.org/docs/latest/user/actions/reference/
https://forgejo.org/docs/latest/user/actions/security/
## Signing and publishing
The optional Forgejo repository variable MACOC_CODESIGN_IDENTITY selects
a code-signing identity already available in the macOS runner keychain.
Otherwise builds use ad-hoc signing. Ad-hoc releases are useful for local
testing but are not notarized and may trigger Gatekeeper warnings.
For public distribution use Developer ID Application signing plus Apple
notarization. Do not commit private keys or notarization credentials.
Notarization has not been automated in this version.
scripts/package.sh builds the archive and SHA-256 checksum.
scripts/publish-release.sh creates the release and uploads artifacts over
Forgejo REST using the workflow's short-lived FORGEJO_TOKEN. No long-lived
access token is stored in this repository.
The workflow requires a separately provisioned macOS runner. It cannot
be completed with the current Linux development host alone, and merely
committing it does not prove that a Mac build/release has run.
Swift Docker:
https://www.swift.org/install/linux/docker/

View file

@ -18,8 +18,8 @@ grep -q 'TISSelectInputSource' Sources/CapswitchApp/InputSourceManager.swift
grep -q 'IOHIDServiceClientSetProperty' Sources/CapswitchHID/CapswitchRemapping.c
grep -q '/usr/bin/open' Sources/CapswitchApp/ApplicationRelauncher.swift
grep -q 'SMAppService.mainApp' Sources/CapswitchApp/LoginItemManager.swift
test -f .forgejo/workflows/macos.yml
for name in build bundle test install doctor clean lint verify icon sign package publish-release setup-signing; do
test -f .forgejo/workflows/ci.yml
for name in build bundle test test-linux-core install doctor clean lint verify icon sign package publish-release setup-signing; do
bash -n "scripts/$name.sh"
done
echo "lint: PASS"

47
scripts/test-linux-core.sh Executable file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
source "$(dirname "$0")/common.sh"
if [[ "$IS_MACOS" == 1 ]]; then
echo "Linux-only test fixture must not be used on macOS." >&2
exit 1
fi
if ! command -v swift >/dev/null 2>&1; then
echo "Swift toolchain is required for Linux core tests." >&2
exit 1
fi
# The normal Package.swift targets AppKit, Carbon and IOKit. None of those
# frameworks exist in Linux containers. Build only the pure Swift core and
# its real XCTest suite in a temporary, independent SwiftPM package.
temporary_package="$(mktemp -d)"
trap 'rm -rf "$temporary_package"' EXIT
mkdir -p \
"$temporary_package/Sources/CapswitchCore" \
"$temporary_package/Tests/CapswitchCoreTests"
cp "$ROOT"/Sources/Capswitch/*.swift \
"$temporary_package/Sources/CapswitchCore/"
cp "$ROOT"/Tests/CapswitchCoreTests/*.swift \
"$temporary_package/Tests/CapswitchCoreTests/"
cat > "$temporary_package/Package.swift" <<'SWIFT'
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "macoc-LinuxCoreTests",
products: [
.library(name: "CapswitchCore", targets: ["CapswitchCore"]),
],
targets: [
.target(name: "CapswitchCore"),
.testTarget(
name: "CapswitchCoreTests",
dependencies: ["CapswitchCore"]
),
]
)
SWIFT
swift test --package-path "$temporary_package" --jobs 2

View file

@ -1,5 +1,14 @@
#!/usr/bin/env bash
source "$(dirname "$0")/common.sh"
macos_only
cd "$ROOT"
swift test
if [[ "$IS_MACOS" == 1 ]]; then
cd "$ROOT"
swift test
elif command -v swift >/dev/null 2>&1; then
bash "$ROOT/scripts/test-linux-core.sh"
elif [[ "${MACOC_REQUIRE_SWIFT:-0}" == 1 ]]; then
echo "ERROR: the CI requires Swift for Linux XCTest." >&2
exit 1
else
echo "SKIP: Swift is not installed on this Linux development host."
fi