Skip to content

Put the commit first in the build stamp, and compare only that #1083

Put the commit first in the build stamp, and compare only that

Put the commit first in the build stamp, and compare only that #1083

Workflow file for this run

name: Core
on:
workflow_dispatch:
push:
branches: [ master ]
tags: [ v* ]
pull_request:
merge_group:
jobs:
build:
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
echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties
- name: Setup ninja
uses: seanmiddleditch/gha-setup-ninja@v6
with:
version: 1.12.1
- 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
# --- 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