-
Notifications
You must be signed in to change notification settings - Fork 826
280 lines (252 loc) · 13.3 KB
/
Copy pathcore.yml
File metadata and controls
280 lines (252 loc) · 13.3 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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
name: Core
on:
workflow_dispatch:
push:
branches: [ master ]
tags: [ v* ]
pull_request:
merge_group:
jobs:
build:
# A "Release Vector …" commit is cut straight to a `v*` tag, and the tag build below is what
# publishes it — as the stable, `--latest` release. Skip the master-push run for that one commit
# so the very same code does not also go out as a canary prerelease: a release must reach users
# once, as the stable release, never also as a canary. The tag build is a separate push event
# (ref refs/tags/v*, not refs/heads/master) and is unaffected, so the release still ships.
if: ${{ !(github.event_name == 'push' && github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release Vector')) }}
runs-on: ubuntu-latest
# Needed to publish the canary prerelease below. Everything else here only reads.
permissions:
contents: write
env:
# Where this build's code came from, for the version string the manager and module.prop show.
# Both are the head of the pull request rather than GitHub's defaults, which on a pull request
# describe the run instead of the code: GITHUB_REPOSITORY is this repository even when the
# branch came from a fork, and the checked-out HEAD is an ephemeral merge commit that exists
# nowhere and cannot be looked up by anyone who reads it off a device. Both head.* values are
# null outside a pull request, where the defaults are already right.
VECTOR_BUILD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
VECTOR_BUILD_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
CCACHE_NOHASHDIR: "true"
CCACHE_HARDLINK: "true"
CCACHE_BASEDIR: "${{ github.workspace }}"
steps:
- name: Checkout
uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
# Before anything is built, because it needs nothing but Python and a translator's mistake
# should not wait twenty minutes to surface. Crowdin owns the content of these files, so what
# this catches is a bad merge or a line pasted into the wrong language -- both of which have
# happened, and neither of which any other check in this repository looks for.
- name: Check translations
run: |
python3 manager/tools/check_translations.py manager/src/main/res
python3 manager/tools/check_translations.py daemon/src/main/res
# A missing secret used to be skipped over in silence, and the build carried on to publish a
# canary or a tag signed with the debug key — which the daemon's InstallerVerifier rejects,
# and which nobody notices until someone tries to install the manager. On this repository the
# secret is always meant to be there, so its absence stops the run here. A fork has no access
# to it and cannot be given one, so a fork builds unsigned, as does any branch that is not
# master: neither publishes anything.
- name: Write key
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/master' ) || github.ref_type == 'tag' }}
env:
KEY_STORE: ${{ secrets.KEY_STORE }}
KEY_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
ALIAS: ${{ secrets.ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
IS_UPSTREAM: ${{ github.repository_owner == 'JingMatrix' }}
run: |
set -euo pipefail
if [ -z "$KEY_STORE" ]; then
if [ "$IS_UPSTREAM" = "true" ]; then
echo "::error::KEY_STORE is empty on ${{ github.ref }}. Refusing to publish an unsigned build."
exit 1
fi
echo "No signing secret on this fork; building unsigned."
exit 0
fi
# Through the environment rather than interpolated into the script: a password holding a
# quote would otherwise be pasted into a shell word and mangled, or worse, executed.
{
echo "androidStorePassword=$KEY_STORE_PASSWORD"
echo "androidKeyAlias=$ALIAS"
echo "androidKeyPassword=$KEY_PASSWORD"
echo "androidStoreFile=key.jks"
} >> gradle.properties
printf '%s' "$KEY_STORE" | base64 --decode > key.jks
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Configure Gradle properties
run: |
echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties
echo 'org.gradle.parallel=true' >> ~/.gradle/gradle.properties
echo 'org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC' >> ~/.gradle/gradle.properties
- name: Setup ninja
uses: seanmiddleditch/gha-setup-ninja@v6
with:
version: 1.13.2
- name: Setup ccache
uses: actions/cache@v6
with:
path: |
~/.ccache
${{ github.workspace }}/.ccache
key: ${{ runner.os }}-ccache-${{ hashFiles('**/build.gradle') }}-${{ hashFiles('**/CMakeLists.txt') }}
restore-keys: |
${{ runner.os }}-ccache-
- name: Setup Android SDK
uses: android-actions/setup-android@v4
- name: Remove Android's cmake
shell: bash
run: rm -rf $ANDROID_HOME/cmake
- name: Build with Gradle
run: |
./gradlew zipAll
- name: Prepare artifact
if: success()
id: prepareArtifact
run: |
zygiskReleaseName=`ls zygisk/release/Vector-v*-Release.zip | awk -F '(/|.zip)' '{print $3}'` && echo "zygiskReleaseName=$zygiskReleaseName" >> $GITHUB_OUTPUT
zygiskDebugName=`ls zygisk/release/Vector-v*-Debug.zip | awk -F '(/|.zip)' '{print $3}'` && echo "zygiskDebugName=$zygiskDebugName" >> $GITHUB_OUTPUT
versionCode=`echo "$zygiskDebugName" | awk -F '-' '{print $3}'` && echo "versionCode=$versionCode" >> $GITHUB_OUTPUT
versionName=`echo "$zygiskDebugName" | awk -F '-' '{print $2}'` && echo "versionName=$versionName" >> $GITHUB_OUTPUT
unzip zygisk/release/Vector-v*-Release.zip -d Vector-Release
unzip zygisk/release/Vector-v*-Debug.zip -d Vector-Debug
- name: Upload zygisk release
uses: actions/upload-artifact@v7
with:
name: ${{ steps.prepareArtifact.outputs.zygiskReleaseName }}
path: "./Vector-Release/*"
- name: Upload zygisk debug
uses: actions/upload-artifact@v7
with:
name: ${{ steps.prepareArtifact.outputs.zygiskDebugName }}
path: "./Vector-Debug/*"
# One entry per module that minifies. `app` was the old manager and has not existed since
# #796, so the manager's mapping — the one an obfuscated stack trace from a user actually
# needs — has been missing from every run since.
- name: Upload mappings
uses: actions/upload-artifact@v7
with:
name: mappings
path: |
zygisk/build/outputs/mapping
manager/build/outputs/mapping
daemon/build/outputs/mapping
# DEBUG_SYMBOLS_PATH is set from `layout.buildDirectory` inside the root `subprojects` block,
# so each module writes its own build/symbols and nothing ever lands in the root one.
- name: Upload symbols
uses: actions/upload-artifact@v7
with:
name: symbols
path: |
zygisk/build/symbols
daemon/build/symbols
dex2oat/build/symbols
# --- stable release --------------------------------------------------------------------
#
# A pushed `v*` tag is a stable release, not a canary. It is published here from the same
# signed zips this build produced, so cutting a release is `git tag && git push` with nothing
# hand-uploaded afterwards — the hand-assembly is exactly how v2.0's assets came to disagree
# with zygisk/update.json (#811), and an automated attach cannot drift from what was built.
#
# Marked `--latest`, unlike the canaries below: this is what `releases/latest`, and therefore
# the manager's update check, resolves to. The body is the module's own zygisk/changelog.md
# under a GitHub H1 the release page shows. The changelog itself carries no title line — the
# manager renders it beneath its own heading, so a banner there would only read twice.
- name: Publish release
if: ${{ success() && github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
# `%s` arguments, never the format string, so a `%` anywhere in the changelog is literal.
notes=$(printf '# 🎉 Vector %s 🎉\n\n%s\n' "${TAG#v}" "$(cat zygisk/changelog.md)")
# Recreated rather than edited so a re-run replaces the assets instead of appending a
# second copy. No `--cleanup-tag`: the release is rebuilt, the pushed release tag is kept.
gh release delete "$TAG" --yes 2>/dev/null || true
gh release create "$TAG" \
--latest \
--title "Vector ${TAG}" \
--notes "$notes" \
zygisk/release/Vector-v*-Release.zip \
zygisk/release/Vector-v*-Debug.zip
# --- canary distribution ---------------------------------------------------------------
#
# The zips above are also attached to a prerelease, because an Actions artifact cannot be
# downloaded without a GitHub account — `GET /actions/artifacts/<id>/zip` answers 401 to an
# anonymous caller, while a release asset answers 206. Testing a canary is the lowest-friction
# way for an ordinary user to help, so it must not require handing an OAuth grant to anyone,
# and it must work for the many users who cannot reach GitHub's login page at all.
#
# Marked prerelease so `releases/latest` — which is what update checks read — keeps pointing
# at the last stable tag.
- name: Publish canary prerelease
if: >-
success() &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/master'))
env:
GH_TOKEN: ${{ github.token }}
VERSION_CODE: ${{ steps.prepareArtifact.outputs.versionCode }}
VERSION_NAME: ${{ steps.prepareArtifact.outputs.versionName }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
COMMIT_URL: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}
run: |
set -euo pipefail
tag="canary-${VERSION_CODE}"
subject=$(git log -1 --pretty=%s)
short=$(git rev-parse --short HEAD)
notes=$(cat <<EOF
**${subject}**
Built from [\`${short}\`](${COMMIT_URL}) by [run ${{ github.run_id }}](${RUN_URL}).
This is a canary: the current state of \`master\`, tested by CI and nothing else. It is
published here rather than left as a workflow artifact so that it can be downloaded
without a GitHub account.
| Build | Use it when |
| :-- | :-- |
| \`Vector-${VERSION_NAME}-${VERSION_CODE}-Debug.zip\` | You are chasing a bug. Far more logging, and the build maintainers ask for in reports. |
| \`Vector-${VERSION_NAME}-${VERSION_CODE}-Release.zip\` | You just want the newest code. |
Only the five most recent canaries are kept.
EOF
)
# Recreated rather than edited: re-running the workflow for the same commit should
# replace that build, not append a second copy of every asset to it.
gh release delete "$tag" --yes --cleanup-tag 2>/dev/null || true
gh release create "$tag" \
--prerelease \
--target "${{ github.sha }}" \
--title "Vector ${VERSION_NAME} canary ${VERSION_CODE}" \
--notes "$notes" \
zygisk/release/Vector-v*-Release.zip \
zygisk/release/Vector-v*-Debug.zip
# Five is what a tester needs: enough to bisect a regression across a few days, few enough
# that the releases page is still mostly releases. Sorted by version code, which is the commit
# count and therefore monotonic, rather than by date, which reruns and reverts can disorder.
- name: Keep only the five most recent canaries
if: >-
success() &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/master'))
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# `grep` exits 1 when nothing matches, which under `pipefail` would fail the step on a
# repository that has no canaries yet. Collected first, then acted on.
tags=$(gh release list --limit 100 --json tagName --jq '.[].tagName' | grep '^canary-' || true)
[ -n "$tags" ] || exit 0
echo "$tags" | sort -t- -k2 -n -r | tail -n +6 | while read -r old; do
echo "Removing $old"
gh release delete "$old" --yes --cleanup-tag
done