1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# The arm64 half, and the Store screenshots.
#
# The Mac lane is an Intel Mac Mini, so the aarch64 slice is otherwise code
# nobody has run: it is built on Linux, signed on the Mini, and shipped. This
# runs it on Apple silicon and asks the window server whether a window actually
# appeared.
#
# It is also where the Store screenshots are taken. They are not taken on the
# Mini: `ship` looks through these workflows for one that declares a
# `screenshots` input and runs on macOS, dispatches it, and downloads what it
# uploaded. The Mini's job is signing and uploading, which it does over ssh.
#
# **What this cannot check, because Filebase has none of it.** The sibling
# applications open a document, so their version of this step registers the
# bundle with Launch Services, opens a document through it, and checks that the
# window is titled for the document — which is how they prove the Apple Event
# handler works. Filebase declares no document type and has no such handler: it
# is handed a folder as `argv[1]`. So the check here is that the window appears
# at all, which is the whole of what this application promises on launch.
#
# Author: David M. Anderson
# Built with AI assistance (Claude, Anthropic)
name: Apple silicon
on:
push:
pull_request:
release:
types:
workflow_dispatch:
inputs:
# Optional, because a screenshots run has no tag to give. With one the
# binary the Mac signs is built and attached to that release; without one
# this is the suite, the window and whatever screenshots were asked for.
tag:
description: 'Tag to build the universal binary for (e.g. v0.1.0)'
required: false
type: string
# A choice rather than a boolean, because there is a third thing to ask
# for: `reference` takes the one plain frame a coordinate gets measured
# off, and `full` takes the set. Every coordinate in
# packaging/macos/shots.sh is read off a reference frame rather than
# guessed, so the first run of a new set is always `reference`.
screenshots:
description: 'Store screenshots'
required: false
default: 'no'
type: choice
options:
# `write`, because the release-binary step uploads the universal binary to the
# release with `gh release upload`. It was `read` on the first tag and that step
# built a correct x86_64+arm64 binary and then failed with
# `HTTP 403: Resource not accessible by integration` — a permissions refusal
# that reads like a credentials problem. windows.yml had it right.
permissions:
contents: write
concurrency:
group: apple-silicon-${{ github.event.release.tag_name || inputs.tag || github.ref }}
cancel-in-progress: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
jobs:
arm64:
runs-on: macos-15
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}
- name: The runner is Apple silicon
run: |
set -eu
arch=$(uname -m)
echo "uname -m: ${arch}"
[ "$arch" = arm64 ] || {
echo "::error::This job exists to run the aarch64 slice and this runner is ${arch}."
exit 1
}
- name: What is installed
run: rustc -V && cargo -V && swiftc --version | head -1
- name: Build
run: cargo build
# The same rule the Linux lane checks: nothing here compiles C, and the
# check is the artefact rather than the manifest.
- name: Nothing compiled C
run: |
set -eu
if find target/debug/build -name '*.o' -print -quit 2>/dev/null | grep -q .; then
echo "::error::a build script compiled an object file"
find target/debug/build -name '*.o'
exit 1
fi
echo "no object file was produced by any build script"
- name: Test
run: cargo test
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Assemble the bundle
run: |
set -eu
target=$(cargo metadata --format-version 1 --no-deps |
sed -n 's/.*"target_directory":"\([^"]*\)".*/\1/p')
./packaging/macos/build-app.sh --binary "${target}/debug/filebase"
lipo -info dist/Filebase.app/Contents/MacOS/filebase
ls dist/Filebase.app/Contents/Resources
# The folder the window opens on, built by the committed script so that
# what CI photographs is what anyone can reproduce.
- name: A folder of containers to ask about
run: |
set -eu
cargo install slipcase --quiet
./packaging/demo-corpus.sh
./packaging/demo-corpus.sh --lang de
# A folder arrives as an argument here, not as an Apple Event: this
# application declares no document type, so there is nothing for Launch
# Services to route and nothing for a double-click to find. What is being
# checked is that the arm64 slice draws a window at all — which is the
# slice that is otherwise never run, since the Mac that signs is Intel.
- name: A window appears on arm64
run: |
set -eu
swiftc -O packaging/macos/window-probe.swift -o "${RUNNER_TEMP}/window-probe"
open -a "$PWD/dist/Filebase.app" --args "$PWD/dist/corpus"
# Cold launch, a window server, and a first frame. Polled rather than
# slept at; 30 seconds is far past anything measured by hand.
for _ in $(seq 1 15); do
sleep 2
"${RUNNER_TEMP}/window-probe" Filebase >/dev/null 2>&1 && break
done
set +e
report=$("${RUNNER_TEMP}/window-probe" Filebase)
verdict=$?
set -e
echo "$report"
case "$verdict" in
0) echo "the arm64 build drew a window for a folder handed to it" ;;
2)
# Not a failure of the application, and deliberately not a pass
# either: a check that can quietly not check is the thing this
# fleet keeps getting caught by.
echo "::error::The window server listed nothing, so this run answered nothing about the window."
exit 1 ;;
*)
echo "::error::No window appeared on arm64 for a folder given as an argument."
exit 1 ;;
esac
# For eyes, not for the verdict. The step above is what passes or fails.
- name: Photograph it, for a person
if: always()
run: |
screencapture -x "${RUNNER_TEMP}/screen.png" || true
ls -l "${RUNNER_TEMP}/screen.png" || true
- name: Keep the photograph
if: always()
uses: actions/upload-artifact@v7
with:
name: arm64-window
path: ${{ runner.temp }}/screen.png
if-no-files-found: ignore
# Built here and signed on the Mac: the Distribution certificate stays
# where it is, and the slow half happens before anybody is waiting. The
# steps are excelano/.github's, because every repository with this target
# needs the same ones.
- name: The binary the Store package is built from
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag != '')
uses: excelano/.github/.github/actions/mac-release-binary@main
with:
binary: filebase
package: filebase
tag: ${{ github.event.release.tag_name || inputs.tag }}
token: ${{ secrets.GITHUB_TOKEN }}
# Off unless asked for. A bug fix should not pay for a set of Store
# screenshots, and a set nobody asked for is a set nobody looks at.
# `shots.sh` carries the recipes and is the same command that retakes them
# by hand; everything around it is the org's.
#
# Both languages, because the listing is in both. `shots.sh` refuses a
# language it has no set for rather than photographing an English window
# under a German heading, and it opens a German corpus for the German
# set — this application's frames are almost entirely a container's flyleaf,
# so the window's language alone would not make a German frame.
- name: The Store screenshots
if: inputs.screenshots && inputs.screenshots != 'no'
uses: excelano/.github/.github/actions/mac-store-screenshots@main
with:
app: dist/Filebase.app
mode: ${{ inputs.screenshots }}
languages: en de