Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .claude/rules/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ The required check names are in `AGENTS.md` (CI). Reading a red check: `.claude/

## Release Build (`release-build.yml`)

- Nightly at 19:00 UTC on main, push to `release/**`, and dispatch; never a PR. Never cancelled. Not a required check.
- `Changes (Release)` outputs `run`. A dispatch always builds. A nightly runs the `release` detector against the head of the last nightly that ended green, read with `gh api`, so a quiet day builds nothing. A `release/**` push diffs `github.event.before`, and builds when the push created the branch.
- `Release Build` runs on the self-hosted Mac, or on `xcode-27` when a dispatch picks `runner: github` or, for a push or nightly, the repository variable `RELEASE_BUILD_RUNNER` is `github`. It has a 150-minute limit and VERSION 0.0.0-dev and BUILD 9999999, so build.sh passes the version settings a tag passes.
- While the Mac is offline a run waits for it, and GitHub cancels a job still queued after 24 hours. The tag gate refuses a commit with any red run, and a `runner: github` dispatch on the same commit does not replace it: `apple/RELEASING.md`, When a run fails.
- Nightly at 19:00 UTC on main and dispatch; never a PR or a push. Never cancelled. Not a required check, and no tag waits for it.
- `Changes (Release)` outputs `run`. A dispatch always builds. A nightly runs the `release` detector against the head of the last nightly that ended green, read with `gh api`, so a quiet day builds nothing.
- `Release Build` runs on the self-hosted Mac, or on `xcode-27` when a dispatch picks `runner: github` or, for a nightly, the repository variable `RELEASE_BUILD_RUNNER` is `github`. It has a 150-minute limit and VERSION 0.0.0-dev and BUILD 9999999, so build.sh passes the version settings a tag passes.
- While the Mac is offline a run waits for it, and GitHub cancels a job still queued after 24 hours. `gh variable set RELEASE_BUILD_RUNNER --body github` sends nightlies to `xcode-27` until `gh variable delete RELEASE_BUILD_RUNNER`.
- Setup as macOS Tests up to the SPM restore, which it never saves; then Go, `build-dameng.sh both` and `build-hana.sh both`.
- `apple/scripts/release/build.sh app --unsigned` archives the `TablePro` scheme; `build.sh plugins --unsigned` builds `RegistryPlugins` and copies the publish set, the targets `apple/scripts/release/registry.py targets` reads from `.github/plugin-registry.json`, to `apple/build/release/plugins`. Both build universal. They run `lipo -verify_arch` once per arch (arm64, x86_64), since Xcode 27's `lipo` takes one arch per call, then `verify-plugin-loads.sh`.
- `apple/scripts/release/package.sh app --unsigned` writes the release assets: the DMG, the `.tar.xz` (extracted and compared with the app) and the dSYMs zip; `package.sh plugins` writes one zip per plugin and the plugin dSYMs zip.
Expand Down Expand Up @@ -117,21 +117,23 @@ The required check names are in `AGENTS.md` (CI). Reading a red check: `.claude/

## Change detector (`scripts/ci/detect-changed-paths.sh`)

- `detect-changed-paths.sh [--base <sha>] macos|plugins|ios|oracle|hana|release` prints `true` or `false`. An unknown suite or flag exits 2.
- A PR passes its base SHA, a push `github.event.before`. No base prints true. The all-zero SHA of a new ref diffs against `git merge-base HEAD origin/main`, so a branch cut from main skips what main already ran; with no merge base it prints true.
- `detect-changed-paths.sh [--base <sha>] [--head <rev>] [--ref <git ref>] <suite>` prints `true` or `false` for `macos`, `plugins`, `ios`, `oracle`, `hana`, `release`, `shipped` or `linux`. An unknown suite or flag exits 2.
- A PR passes its base SHA, a push `github.event.before`; `--head` defaults to `HEAD`. No base prints true. The all-zero SHA of a new ref diffs against `git merge-base <head> origin/main`, so a branch cut from main skips what main already ran; with no merge base it prints true.
- macOS Tests, iOS and Package Tests and Repo Hygiene pass `--ref` with `github.ref`, and `refs/heads/release/*` prints true for every suite: a tag's `verify` needs the line head's own runs, and a release PR changes only the changelog.
- `macos` watches the app, test, plugin, package, native, config and libs folders, `apple/TestPlans/`, `apple/project.yml`, the app's `Package.resolved`, the build scripts and CI scripts it runs, the setup actions and its workflow. `apple/Packages/` covers TableProMacCore.
- `plugins` watches `apple/Plugins/`, `apple/Packages/TableProCore/`, `apple/Packages/TableProOracle/`, `apple/Native/`, `apple/Libs/`, `apple/Configs/`, `apple/project.yml`, the app's `Package.resolved`, the project, libs and bridge scripts, `verify-plugin-loads.sh`, `apple/scripts/check-pluginkit-abi.sh` and the setup actions.
- `ios` watches `apple/TableProMobile/`, `apple/Libs/`, `apple/Plugins/TableProPluginKit/` (the package target is a symlink to it), and every `../` path it reads out of `apple/TableProMobile/project.yml`.
- `oracle` watches `apple/Packages/TableProOracle/`; `hana` watches `apple/Native/HanaBridge/`. Each suite also watches its own workflow.
- `release` watches what the Release build reads: the app, plugin, package, native, config and libs folders, `apple/project.yml`, the app's `Package.resolved`, `.github/plugin-registry.json`, the release scripts it runs (`build.sh`, `package.sh`, `registry.py`, `version.py`, `notes.py`), the build and CI scripts it runs and the setup actions. It skips `apple/Plugins/*Tests/` and `apple/Packages/*/Tests/`.
- `shipped` is `release` without its `.github/workflows/` and `.github/actions/` entries. `version.py check` reads it for a stable's diff from its newest beta (`apple/RELEASING.md`, What a tag runs).
- `linux` watches `linux/`, the detector itself and the three Linux workflows.
- A path a suite newly reads joins that suite's pattern in the same change, or the suite skips on it.
- `apple/CHANGELOG.md` and `apple/changelog.d/` are on no suite's pattern: the What's New phase writes nothing at `0.0.0-dev`, and Repo Hygiene renders every fragment and section instead.

## What runs where

- A pull request: both changes jobs; macOS Tests and iOS and Package Tests unless their detector says false (Oracle only when its detector is not false); SwiftLint; Repo Hygiene; PR title. `AllPlugins` only when the `plugins` detector is not false.
- A push to main or `release/**`: the same minus PR title; never cancelled, the SPM cache saved on a miss. Every code push runs both plans in full; a docs-only or changelog-only push skips the suites through the detector. A `release/**` push also runs Release Build; main gets it nightly.
- A push to main or `release/**`: the same minus PR title; never cancelled, the SPM cache saved on a miss. Every code push runs both plans in full. On main a docs-only or changelog-only push skips the suites through the detector; a `release/**` push runs every suite (Change detector). Release Build runs nightly on main and by dispatch only.
- A `v*` tag runs only `release.yml`, and a `linux-v*` tag only `release-linux.yml`, which calls `linux-packages.yml`. The test workflows never run on a tag.
- `workflow_dispatch` exists on every workflow except `pr-title.yml` and `release.yml`.
- `build-linux.yml` builds and tests the Linux app, and `linux-packages.yml` builds its packages (`linux/docs/testing.md`, CI). What starts each is in its `on:` and job `if:` lines. Flatpak Devel and Minimum Rust version never run on a PR or push, and `linux-packages.yml` never on a push.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Before any decision, gather:

The days, the gate and the patch bar live in `apple/RELEASING.md`, Schedule.

- **Promote to X.Y.0**: Monday, when the gate passes: the line's age, the newest beta's age and nothing but changelog changes after it (section 2), no open `regression` issue, and the storage check (section 4).
- **Promote to X.Y.0**: Monday, when the gate passes: the line's age, the newest beta's age and no shipped source after it (section 2), no open `regression` issue, and the storage check (section 4).
- **beta.1 of X.(Y+1)**: the same Monday, after X.Y.0's forward-port merges (section 9). The tag refuses to build until `main` has `## [X.Y.0]`.
- **beta.N**: Tuesday to Thursday, when the line has picks since its last beta.
- **Patch X.Y.Z**: Tuesday to Thursday, when the stable line has a fix past the patch bar.
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/ios-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ jobs:
id: decide
env:
BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
REF: ${{ github.ref }}
run: |
set -euo pipefail
run=$(scripts/ci/detect-changed-paths.sh --base "$BASE" ios)
oracle=$(scripts/ci/detect-changed-paths.sh --base "$BASE" oracle)
run=$(scripts/ci/detect-changed-paths.sh --base "$BASE" --ref "$REF" ios)
oracle=$(scripts/ci/detect-changed-paths.sh --base "$BASE" --ref "$REF" oracle)
{ echo "run=$run"; echo "oracle=$oracle"; } >> "$GITHUB_OUTPUT"

# The required check, with no gate job: see macos-tests.yml. TableProCore and TableProOracle run
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/macos-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ jobs:
id: decide
env:
BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
REF: ${{ github.ref }}
run: |
set -euo pipefail
run=$(scripts/ci/detect-changed-paths.sh --base "$BASE" macos)
plugins=$(scripts/ci/detect-changed-paths.sh --base "$BASE" plugins)
run=$(scripts/ci/detect-changed-paths.sh --base "$BASE" --ref "$REF" macos)
plugins=$(scripts/ci/detect-changed-paths.sh --base "$BASE" --ref "$REF" plugins)
{ echo "run=$run"; echo "plugins=$plugins"; } >> "$GITHUB_OUTPUT"

# The required check. A job skipped by its if: reports success, so no gate job is needed. The
Expand Down
36 changes: 10 additions & 26 deletions .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
name: Release Build

# The Release configuration of the app and every registry plugin, universal and unsigned, packaged
# the way a tag packages it, so a packaging failure shows before the tag: nightly on main, on every
# release branch push, and on demand.
# the way a tag packages it, so a packaging failure shows before the tag: nightly on main and on demand.

on:
push:
branches: ['release/**']
schedule:
- cron: '0 19 * * *'
workflow_dispatch:
Expand Down Expand Up @@ -36,42 +33,29 @@ jobs:
fetch-depth: 0

# A nightly diffs against the last nightly that ended green, built or skipped: main up to
# that commit is covered. A push that creates a release branch builds the cut commit, which
# the merge-base rule would skip.
# that commit is covered.
- name: Decide whether the Release build runs
id: decide
env:
EVENT: ${{ github.event_name }}
CREATED: ${{ github.event.created }}
BEFORE: ${{ github.event.before }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
case "$EVENT" in
workflow_dispatch)
run=true
;;
schedule)
base=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/release-build.yml/runs?event=schedule&status=success&per_page=1" \
--jq '.workflow_runs[0].head_sha // ""')
run=$(scripts/ci/detect-changed-paths.sh --base "$base" release)
;;
*)
if [ "$CREATED" = true ]; then
run=true
else
run=$(scripts/ci/detect-changed-paths.sh --base "$BEFORE" release)
fi
;;
esac
if [ "$EVENT" = workflow_dispatch ]; then
run=true
else
base=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/release-build.yml/runs?event=schedule&status=success&per_page=1" \
--jq '.workflow_runs[0].head_sha // ""')
run=$(scripts/ci/detect-changed-paths.sh --base "$base" release)
fi
echo "run=$run" >> "$GITHUB_OUTPUT"

# Reads "not false" so a failed changes job builds instead of passing.
build:
name: Release Build
needs: changes
if: ${{ !cancelled() && needs.changes.outputs.run != 'false' }}
# A push or nightly has no input, so it reads the RELEASE_BUILD_RUNNER repository variable; unset is the Mac.
# A nightly has no input, so it reads the RELEASE_BUILD_RUNNER repository variable; unset is the Mac.
runs-on: ${{ (inputs.runner || vars.RELEASE_BUILD_RUNNER) == 'github' && 'xcode-27' || fromJSON('["self-hosted", "macOS", "ARM64", "tablepro-release"]') }}
timeout-minutes: 150
# The tag's version settings run here first; 9999999 is the largest build at MAJOR 0 (apple/RELEASING.md, Versions).
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
verify:
name: Verify
runs-on: ubuntu-latest
timeout-minutes: 240
timeout-minutes: 10
permissions:
contents: read
actions: read
Expand All @@ -28,6 +28,7 @@ jobs:
env:
TAG: ${{ github.ref_name }}
GH_TOKEN: ${{ github.token }}
PYTHONUNBUFFERED: "1"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/repo-hygiene.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@ jobs:
id: native
env:
BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
REF: ${{ github.ref }}
run: |
set -euo pipefail
hana=$(scripts/ci/detect-changed-paths.sh --base "$BASE" hana)
hana=$(scripts/ci/detect-changed-paths.sh --base "$BASE" --ref "$REF" hana)
echo "hana=$hana" >> "$GITHUB_OUTPUT"

- name: Set up Go
Expand Down
Loading
Loading