-
Notifications
You must be signed in to change notification settings - Fork 81
254 lines (230 loc) · 11.4 KB
/
Copy pathupdate-homebrew-cask.yml
File metadata and controls
254 lines (230 loc) · 11.4 KB
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
name: Update Homebrew Cask
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish to the tap (e.g. v1.4.0)"
required: true
type: string
permissions:
contents: read
jobs:
update-cask:
runs-on: ubuntu-latest
# The tap configuration has LEFT this `if:`, and that is the point of the change.
# `vars.HOMEBREW_TAP_OWNER != '' && vars.HOMEBREW_TAP_REPO != ''` here made the job
# `skipped`, and a skipped job is green: every release since this workflow was
# written has "succeeded" without publishing a cask, because neither variable has
# ever existed on the repository. Same failure as #148, where publish-winget.yml
# spent eight releases green and silent for exactly this reason — see the comment
# above its own `if:`. A guard that does nothing quietly guards nothing.
#
# So the job always starts, and a step announces the missing configuration. It costs
# a runner-minute per release; it buys "nothing was published" being visible in the
# run summary instead of inferable from a tap nobody thought to look at.
if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease
env:
TAP_OWNER: ${{ vars.HOMEBREW_TAP_OWNER }}
TAP_REPO: ${{ vars.HOMEBREW_TAP_REPO }}
CASK_NAME: ${{ vars.HOMEBREW_CASK_NAME || 'openscreen' }}
# `secrets` is not a context an `if:` can read, at job level or step level — only
# `env` is. Hence this boolean-as-string, which exposes whether the token is set
# without ever exposing its value. Same trick as publish-winget.yml.
HAS_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }}
steps:
- name: Check the tap configuration
id: config
run: |
set -euo pipefail
# `homebrew-?*`, not `-n`: the fourth requirement in the warning below is a
# rule about the name itself, and a name test is the one of the four this step
# can actually apply. `getopenscreen/openscreen-tap` would clone, commit and
# push exactly like a real tap and still be untappable — a green run publishing
# to somewhere `brew tap` cannot resolve, which is the failure this whole
# workflow change exists to stop. `?*` also rejects a repository named the bare
# `homebrew-`, which the prefix alone would accept.
if [[ -n "$TAP_OWNER" && "$TAP_REPO" == homebrew-?* && "$HAS_TOKEN" == "true" ]]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
echo "Tap: ${TAP_OWNER}/${TAP_REPO}, cask ${CASK_NAME}."
exit 0
fi
echo "configured=false" >> "$GITHUB_OUTPUT"
echo "::warning title=Homebrew cask not updated::No cask was published. Needs (1) the repository variable HOMEBREW_TAP_OWNER, currently ${TAP_OWNER:-UNSET}; (2) HOMEBREW_TAP_REPO, currently ${TAP_REPO:-UNSET}; (3) the secret HOMEBREW_TAP_TOKEN, currently $([[ "$HAS_TOKEN" == "true" ]] && echo set || echo UNSET), with contents write on that repository; and (4) the tap repository itself, which must be named homebrew-<something> for Homebrew to recognise it. See https://github.com/getopenscreen/openscreen/issues/335"
- name: Resolve and validate tag
id: meta
if: steps.config.outputs.configured == 'true'
env:
GH_EVENT_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
TAG="${GH_EVENT_TAG:-$INPUT_TAG}"
if [[ -z "$TAG" ]]; then
echo "::error::No tag resolved from release event or workflow input"
exit 1
fi
# The `prerelease` filter only covers the `release` event. `workflow_dispatch`
# takes free text — no git ref rule applies to it — so a manual replay of
# `v1.9.4-rc.2` would publish an RC as THE cask, and `brew upgrade` would hand
# it to everyone on stable. Homebrew accepts that version string happily; only
# this check refuses it. The same hole was closed in aur-publish.yml, where the
# free-text input also escaped a `sed` expression; here every use is quoted, so
# what is left is the wrong-version case and the tag reaching a Ruby file that
# users execute.
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::Refusing tag '$TAG' — expected a stable vMAJOR.MINOR.PATCH tag"
exit 1
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Wait for release DMG assets
if: steps.config.outputs.configured == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.meta.outputs.tag }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
TIMEOUT_MINUTES=12
POLL_INTERVAL=30
MAX_ATTEMPTS=$(( (TIMEOUT_MINUTES * 60) / POLL_INTERVAL ))
# Match on the arch marker, not on an exact filename. build.yml names
# the DMGs `Openscreen-macOS-Apple-Silicon-<ver>.dmg` and
# `-Intel-`; older releases used `-Mac-arm64-` / `-Mac-x64-`. An
# exact-name wait would poll for the full 12 minutes and warn, on a
# release whose assets were there the whole time. These are the same
# patterns the "Find macOS DMG assets" step below already matches on,
# so the two steps cannot disagree about what counts as present.
for i in $(seq 1 $MAX_ATTEMPTS); do
NAMES=$(gh release view "$TAG" --repo "$REPO" --json assets --jq '.assets[].name' 2>/dev/null || true)
DMGS=$(echo "$NAMES" | grep -iE '\.dmg$' || true)
ARM_FOUND=$(echo "$DMGS" | grep -icE '(arm64|apple[-_. ]?silicon)' || true)
X64_FOUND=$(echo "$DMGS" | grep -icE '(x64|x86[-_]?64|intel)' || true)
if [[ "$ARM_FOUND" -ge 1 && "$X64_FOUND" -ge 1 ]]; then
echo "Both DMG assets present:"
echo "$DMGS"
exit 0
fi
echo "Waiting for DMG assets... (attempt $i/$MAX_ATTEMPTS)"
sleep $POLL_INTERVAL
done
echo "::warning::Timeout after ${TIMEOUT_MINUTES}min waiting for DMG assets. Proceeding anyway."
- name: Find macOS DMG assets
id: assets
if: steps.config.outputs.configured == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.meta.outputs.tag }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
NAMES=$(gh release view "$TAG" --repo "$REPO" --json assets --jq '.assets[].name')
# arm64 DMG: explicit "arm64" / "apple silicon" / fallback to any .dmg
# whose name does NOT contain "x64" or non-mac platform markers.
ARM_NAME=$(echo "$NAMES" | grep -iE '\.dmg$' \
| grep -iE '(arm64|apple[-_. ]?silicon)' | head -n1 || true)
if [[ -z "$ARM_NAME" ]]; then
ARM_NAME=$(echo "$NAMES" | grep -iE '\.dmg$' \
| grep -iv 'x64' | grep -iv 'linux' | grep -iv 'win' | head -n1 || true)
fi
# x64 DMG
X64_NAME=$(echo "$NAMES" | grep -iE '\.dmg$' \
| grep -iE '(x64|x86[-_]?64|intel)' | head -n1 || true)
if [[ -z "$ARM_NAME" || -z "$X64_NAME" ]]; then
echo "::error::Could not locate both arm64 and x64 DMGs in release assets"
echo "Available assets:"
echo "$NAMES"
exit 1
fi
echo "arm_name=$ARM_NAME" >> "$GITHUB_OUTPUT"
echo "x64_name=$X64_NAME" >> "$GITHUB_OUTPUT"
echo "Found arm64 asset: $ARM_NAME"
echo "Found x64 asset: $X64_NAME"
- name: Download DMGs and compute sha256
id: shas
if: steps.config.outputs.configured == 'true'
env:
REPO: ${{ github.repository }}
TAG: ${{ steps.meta.outputs.tag }}
ARM_NAME: ${{ steps.assets.outputs.arm_name }}
X64_NAME: ${{ steps.assets.outputs.x64_name }}
run: |
set -euo pipefail
BASE="https://github.com/${REPO}/releases/download/${TAG}"
curl -fsSL --retry 3 -o /tmp/arm.dmg "${BASE}/${ARM_NAME}"
curl -fsSL --retry 3 -o /tmp/x64.dmg "${BASE}/${X64_NAME}"
ARM_SHA=$(sha256sum /tmp/arm.dmg | awk '{print $1}')
X64_SHA=$(sha256sum /tmp/x64.dmg | awk '{print $1}')
echo "arm_sha=$ARM_SHA" >> "$GITHUB_OUTPUT"
echo "x64_sha=$X64_SHA" >> "$GITHUB_OUTPUT"
- name: Checkout tap
if: steps.config.outputs.configured == 'true'
uses: actions/checkout@v7
with:
repository: ${{ env.TAP_OWNER }}/${{ env.TAP_REPO }}
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: tap
- name: Write cask file
if: steps.config.outputs.configured == 'true'
env:
REPO: ${{ github.repository }}
TAG: ${{ steps.meta.outputs.tag }}
VERSION: ${{ steps.meta.outputs.version }}
ARM_NAME: ${{ steps.assets.outputs.arm_name }}
X64_NAME: ${{ steps.assets.outputs.x64_name }}
ARM_SHA: ${{ steps.shas.outputs.arm_sha }}
X64_SHA: ${{ steps.shas.outputs.x64_sha }}
run: |
set -euo pipefail
mkdir -p tap/Casks
BASE="https://github.com/${REPO}/releases/download/${TAG}"
# #{version} is Ruby interpolation written literally to the cask
# file (bash heredoc leaves "#{...}" alone). \${VERSION}, \${ARM_SHA},
# etc. are bash variables expanded by the heredoc. The literal
# #{version} fixes Homebrew's "URL is unversioned" audit warning by
# making the version string statically detectable.
cat > "tap/Casks/${CASK_NAME}.rb" <<EOF
cask "${CASK_NAME}" do
version "${VERSION}"
on_arm do
sha256 "${ARM_SHA}"
url "https://github.com/${REPO}/releases/download/v#{version}/${ARM_NAME}"
end
on_intel do
sha256 "${X64_SHA}"
url "https://github.com/${REPO}/releases/download/v#{version}/${X64_NAME}"
end
name "Openscreen"
desc "Screen recorder and video editor"
homepage "https://github.com/${REPO}"
auto_updates false
depends_on macos: ">= :big_sur"
app "Openscreen.app"
zap trash: [
"~/Library/Application Support/Openscreen",
"~/Library/Caches/com.etiennelescot.openscreen",
"~/Library/Logs/Openscreen",
"~/Library/Preferences/com.etiennelescot.openscreen.plist",
"~/Library/Saved Application State/com.etiennelescot.openscreen.savedState",
]
end
EOF
- name: Commit and push to tap
if: steps.config.outputs.configured == 'true'
working-directory: tap
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "Casks/${CASK_NAME}.rb"
if git diff --cached --quiet; then
echo "Cask already up to date for ${VERSION} — nothing to commit."
exit 0
fi
git commit -m "Bump ${CASK_NAME} to ${VERSION}"
git push