add CI
This commit is contained in:
parent
6e6043cbda
commit
255f23928a
1 changed files with 256 additions and 0 deletions
256
.forgejo/workflows/build.yaml
Normal file
256
.forgejo/workflows/build.yaml
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
# Build, test, sign and publish Tally.
|
||||
#
|
||||
# Every push, any branch: the engine and both terminal frontends are built and tested,
|
||||
# the JNI layer is proved against a real JVM, the Android libraries are audited, and the
|
||||
# app is built and unit tested. Artifacts are attached to the run.
|
||||
#
|
||||
# Pushes to master also sign and publish, signing with action-hsm-sign:
|
||||
# - the APK, signed (schemes v2 and v3) by the HSM
|
||||
# - every release file, with a detached signature logged to sigstore's public
|
||||
# transparency log
|
||||
# - all of it to the Forgejo generic package registry as `tally`, under the short SHA
|
||||
# and under `latest`
|
||||
#
|
||||
# Secrets: HSM_USER_PIN (signing), PACKAGE_PUSH (registry), NTFY_* (notifications).
|
||||
# Runners: ubuntu-latest, and ubuntu-latest-with-hsm, which has the HSM on a smart USB hub
|
||||
# (per-port power switching) and runs one job at a time.
|
||||
name: Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
env:
|
||||
RELEASE_OPTIMIZATION: ReleaseSafe
|
||||
# Pinned and checked, because CI should not take whatever was published today. The
|
||||
# same versions as .mise.toml, which is what a developer machine uses.
|
||||
ZLINT_VERSION: v0.9.0
|
||||
ZLINT_SHA256: 2485f4f744345e4b7c23a6023e96f0a22306d832f64ba3aeba08d35c90f81a71
|
||||
GRADLE_VERSION: 8.14.5
|
||||
GRADLE_SHA256: 6f74b601422d6d6fc4e1f9a1ab6522f642c2fdcbc15ae33ebd30ba3d7198e854
|
||||
jobs:
|
||||
engine:
|
||||
name: Engine, CLI and TUI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Zig
|
||||
# No version given on purpose: setup-zig resolves it from minimum_zig_version
|
||||
# in build.zig.zon, so the toolchain cannot drift from what the package declares.
|
||||
uses: https://github.com/mlugg/setup-zig@v2.2.1
|
||||
- name: Check formatting
|
||||
# Before the build, which fetches dependencies into zig-pkg/ - not ours to format.
|
||||
run: zig fmt --check build.zig build.zig.zon build engine src
|
||||
- name: Lint
|
||||
run: |
|
||||
curl -fsSLo "$RUNNER_TEMP/zlint" \
|
||||
"https://github.com/DonIsaac/zlint/releases/download/${ZLINT_VERSION}/zlint-linux-x86_64"
|
||||
echo "${ZLINT_SHA256} $RUNNER_TEMP/zlint" | sha256sum -c -
|
||||
chmod 755 "$RUNNER_TEMP/zlint"
|
||||
"$RUNNER_TEMP/zlint" --deny-warnings
|
||||
- name: Build project
|
||||
run: zig build --summary all
|
||||
- name: Run tests
|
||||
# Engine, CLI, TUI, the C ABI from Zig, the JNI layer against a synthetic
|
||||
# function table, and the C ABI from C through the installed header.
|
||||
run: zig build test --summary all
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
- name: Prove the JNI table against a real JVM
|
||||
run: zig build jvm-test --summary all
|
||||
- name: Package the CLI and TUI
|
||||
# One binary holds both. Static musl on Linux, so it runs on any distribution.
|
||||
run: |
|
||||
mkdir -p dist
|
||||
for target in x86_64-linux-musl aarch64-linux-musl aarch64-macos; do
|
||||
zig build -Dtarget="$target" -Doptimize="$RELEASE_OPTIMIZATION" --prefix "out/$target"
|
||||
name="tally-${target%-musl}"
|
||||
cp "out/$target/bin/tally" "dist/$name"
|
||||
done
|
||||
ls -la dist
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tally-cli
|
||||
path: dist/
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always() && env.GITEA_ACTIONS == 'true'
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
status: ${{ job.status }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
||||
|
||||
android:
|
||||
name: Android
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Zig
|
||||
uses: https://github.com/mlugg/setup-zig@v2.2.1
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
- name: Setup Android SDK
|
||||
# The packages `mise run android-sdk` installs. No NDK: Zig builds the native
|
||||
# library without one.
|
||||
uses: https://github.com/android-actions/setup-android@v3
|
||||
with:
|
||||
packages: 'platform-tools platforms;android-35 build-tools;35.0.0'
|
||||
log-accepted-android-sdk-licenses: 'false'
|
||||
- name: Setup Gradle
|
||||
# No wrapper is committed (android/README.md), so the version comes from here,
|
||||
# as it comes from .mise.toml locally.
|
||||
run: |
|
||||
curl -fsSLo "$RUNNER_TEMP/gradle.zip" \
|
||||
"https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip"
|
||||
echo "${GRADLE_SHA256} $RUNNER_TEMP/gradle.zip" | sha256sum -c -
|
||||
unzip -q "$RUNNER_TEMP/gradle.zip" -d "$RUNNER_TEMP"
|
||||
echo "$RUNNER_TEMP/gradle-${GRADLE_VERSION}/bin" >> "$GITHUB_PATH"
|
||||
- name: Build the native libraries
|
||||
# Gradle never invokes Zig, so this comes first or the APK packages nothing.
|
||||
run: zig build android --summary all
|
||||
- name: Audit the native libraries
|
||||
# No undefined symbols, no TLS, no DT_NEEDED: each one loads on a desktop and
|
||||
# fails in dlopen on a phone.
|
||||
run: |
|
||||
command -v readelf >/dev/null || { sudo apt-get update && sudo apt-get install -y binutils; }
|
||||
android/audit-libs.sh zig-out/android
|
||||
- name: Unit test and build the app
|
||||
working-directory: android
|
||||
# The release APK is unsigned here; signing happens on the HSM runner. Lint's
|
||||
# release checks run as part of assembleRelease.
|
||||
run: gradle --no-daemon testDebugUnitTest assembleDebug assembleRelease
|
||||
- name: Upload the unsigned release APK
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tally-apk-unsigned
|
||||
path: android/app/build/outputs/apk/release/app-release-unsigned.apk
|
||||
- name: Upload the debug APK
|
||||
# Signed with this runner's throwaway debug key, so it installs only where no
|
||||
# other build of the app is installed. For trying a branch, not for keeping.
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tally-apk-debug
|
||||
path: android/app/build/outputs/apk/debug/app-debug.apk
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: android-test-results
|
||||
path: android/app/build/test-results/
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always() && env.GITEA_ACTIONS == 'true'
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
status: ${{ job.status }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
||||
|
||||
sign:
|
||||
name: Sign
|
||||
# Only what is published is signed: the HSM is hardware on a hub, not something to
|
||||
# cycle for every branch push.
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest-with-hsm
|
||||
needs: [engine, android]
|
||||
steps:
|
||||
- name: Download the binaries
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: tally-cli
|
||||
path: dist
|
||||
- name: Download the unsigned APK
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: tally-apk-unsigned
|
||||
path: unsigned
|
||||
- name: Sign the APK and every release file
|
||||
# The APK first, then a detached signature of every file in dist/, the signed APK
|
||||
# included, each logged to sigstore. One power cycle of the HSM for all of it.
|
||||
# The APK's signature is the app's identity on every device that installs it, for
|
||||
# good: the key is the HSM's, and its certificate is the one `make-cert` wrote to
|
||||
# the token (README, "Signing").
|
||||
id: sign
|
||||
uses: https://git.lerch.org/lobo/action-hsm-sign@v3
|
||||
with:
|
||||
pin: ${{ secrets.HSM_USER_PIN }}
|
||||
apk: unsigned/app-release-unsigned.apk
|
||||
apk_output: dist/tally.apk
|
||||
files: dist/*
|
||||
public_key: 'https://emil.lerch.org/serverpublic.pem'
|
||||
uhub_control: 'true'
|
||||
- name: Transparency log entries
|
||||
run: |
|
||||
echo "${{ steps.sign.outputs.SOURCE_0 }}: ${{ steps.sign.outputs.URL_0 }}"
|
||||
echo "${{ steps.sign.outputs.SOURCE_1 }}: ${{ steps.sign.outputs.URL_1 }}"
|
||||
echo "${{ steps.sign.outputs.SOURCE_2 }}: ${{ steps.sign.outputs.URL_2 }}"
|
||||
echo "${{ steps.sign.outputs.SOURCE_3 }}: ${{ steps.sign.outputs.URL_3 }}"
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: tally-release
|
||||
path: dist/
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always()
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
status: ${{ job.status }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
needs: sign
|
||||
steps:
|
||||
- name: Download the signed release
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: tally-release
|
||||
path: dist
|
||||
- name: Publish to the Forgejo generic package registry
|
||||
env:
|
||||
PACKAGE_PUSH: ${{ secrets.PACKAGE_PUSH }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -eu
|
||||
shortsha="$(printf '%s' "$SHA" | cut -c1-7)"
|
||||
base="https://git.lerch.org/api/packages/${OWNER}/generic/tally"
|
||||
cd dist
|
||||
for version in "$shortsha" latest; do
|
||||
for file in *; do
|
||||
url="${base}/${version}/${file}"
|
||||
# The registry refuses a second PUT of the same file (409), so `latest`
|
||||
# is replaced by delete-then-put. A 404 for a new short SHA is harmless.
|
||||
curl -sS -o /dev/null -w "DELETE %{http_code} ${url}\n" \
|
||||
-u "${ACTOR}:${PACKAGE_PUSH}" -X DELETE "${url}" || true
|
||||
curl -sSf -u "${ACTOR}:${PACKAGE_PUSH}" --upload-file "${file}" "${url}"
|
||||
echo "uploaded ${url}"
|
||||
done
|
||||
done
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always()
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
status: ${{ job.status }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
||||
Loading…
Add table
Reference in a new issue