Benchmarks / CPU and I/O (per commit) (push) Successful in 5m18s
Benchmarks / Frame budget (on demand) (push) Skipped
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Desktop (Linux) (push) Successful in 47m36s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / windows-image (push) Successful in 1s
Build and test / Layer separation (push) Successful in 32s
Traceability / Requirement traces (push) Successful in 1m37s
Build and test / Android (aarch64) (push) Successful in 30m39s
Build and test / Windows (x86_64, cross) (push) Successful in 35m42s
Build and test / Publish the release (push) Skipped
The single CI runner has one 99 GB disk shared with its container images. The desktop job reached 92 GB used (2.1 GB free) on v0.18.0's run, and on v0.18.1's the release build died with "No space left on device", so that tag has no release page. At rest the runner holds about 23 GB; the restored target cache, the models and the dependency build bring a job to about 78 GB before a test runs, and tests plus the release build take the rest. The test executables and examples in target/debug are the largest part of that, are relinked whenever a source changes, and are not what the cache exists for — the dependency rlibs are. Deleting them after the Test step, before the release build, frees several GB at the moment the job is fullest without costing the next run anything the cache would have saved. The step prints the disk afterwards, beside the existing Disk before/after lines.
604 lines
28 KiB
YAML
604 lines
28 KiB
YAML
name: Build and test
|
|
|
|
# Desktop and Android are built on every push, per the v0.1 decision to carry
|
|
# both platforms from the first commit. An Android break is then caught the day
|
|
# it lands rather than at a porting milestone.
|
|
|
|
on:
|
|
push:
|
|
branches: [main, master, develop]
|
|
# A release tag builds again and publishes what it built (the `release`
|
|
# job at the end). The master push of the same commit has usually filled
|
|
# the caches, so the second run is the warm one.
|
|
tags: ['v*']
|
|
pull_request:
|
|
branches: [main, master, develop]
|
|
|
|
jobs:
|
|
# The Android job runs inside an image that this repo builds. Ensure it is in
|
|
# the registry before anything tries to pull it — see android-image.yml for
|
|
# why this is a job rather than a documented manual step. It is a no-op of a
|
|
# few seconds unless docker/android actually changed.
|
|
android-image:
|
|
uses: ./.gitea/workflows/android-image.yml
|
|
|
|
desktop:
|
|
runs-on: linux/amd64
|
|
name: Desktop (Linux)
|
|
# actions/checkout and actions/cache are JavaScript actions: the runner
|
|
# executes them with Node from inside this container. The bare runner image
|
|
# has none, so the job failed at checkout before reaching any build step.
|
|
container:
|
|
image: catthehacker/ubuntu:act-latest
|
|
|
|
# This job filled the runner's disk and died mid-link with "No space left
|
|
# on device" — LLVM reporting an IO failure on its output stream, which
|
|
# reads like a compiler crash and is not one.
|
|
#
|
|
# `target/debug` was 24 GB against `target/release`'s 2.6 GB: 15 GB of it
|
|
# debug info in `debug/deps`, 3.6 GB incremental state. Neither earns its
|
|
# space here. Nothing attaches a debugger to a CI run, and incremental
|
|
# compilation exists to make the *second* build in a working tree fast,
|
|
# which is not a thing a fresh checkout has. Turning both off is the
|
|
# standard CI setting rather than a trick.
|
|
#
|
|
# Measured on this workspace: the same `cargo test --workspace --no-run`
|
|
# tree goes from 24 GB to 3.3 GB, `debug/deps` from 15 GB to 2.8 GB.
|
|
#
|
|
# Backtraces still name functions without debug info; they lose file and
|
|
# line numbers. If a test failure ever needs those, drop DEBUG to 1
|
|
# (line-tables-only) rather than back to 2.
|
|
#
|
|
# This is a mitigation, not a fix. If the runner is full of anything other
|
|
# than this job's own output, it will still be full afterwards.
|
|
env:
|
|
CARGO_INCREMENTAL: 0
|
|
CARGO_PROFILE_DEV_DEBUG: 0
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# The model, which is in LFS and is not optional.
|
|
#
|
|
# `models/**/*.onnx` is tracked in LFS (.gitattributes), so a
|
|
# plain checkout writes a ~130-byte pointer where 11 MB should be, and
|
|
# `dr-segment`'s build script panics by design rather than embedding a
|
|
# pointer and failing at inference. That failure reads like a broken build
|
|
# instead of a missing fetch, which is how it went unnoticed.
|
|
#
|
|
# Not `lfs: true` on the checkout above, and no `Authorization` header
|
|
# here either. Both install a blanket header for every request to this
|
|
# host, and the object download is the one request that already carries
|
|
# one: `git lfs pull` asks `/info/lfs/objects/batch` first, and Gitea
|
|
# answers with a short-lived `Bearer` JWT scoped to that object. git-lfs
|
|
# then sends the JWT *and* the configured header, and two `Authorization`
|
|
# headers is a 400 from Gitea — reported as
|
|
# LFS: Client error: .../info/lfs/objects/<oid>
|
|
# one step after the batch call that had just succeeded, which reads like
|
|
# a rejected credential rather than a duplicated one. A lone token header
|
|
# is understood fine; it is only the collision that fails.
|
|
#
|
|
# So: strip the headers and hand the token to git-lfs as an ordinary
|
|
# credential instead. It authenticates the batch call and leaves the
|
|
# per-object JWT untouched. Gitea authenticates on the password, so the
|
|
# username is a placeholder. Nothing later in this job talks to the
|
|
# remote, so dropping checkout's header costs us nothing.
|
|
#
|
|
# `continue-on-error` deliberately: if this cannot authenticate, the build
|
|
# below still runs and fails with the build script's own message, which
|
|
# names the real problem. A checkout that dies here says nothing.
|
|
- name: Fetch the segmentation model
|
|
continue-on-error: true
|
|
env:
|
|
LFS_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
|
|
run: |
|
|
set -e
|
|
git lfs install --local
|
|
git config --local --get-regexp '^http\..*extraheader$' \
|
|
| cut -d' ' -f1 | sort -u \
|
|
| while read -r key; do git config --local --unset-all "$key"; done || true
|
|
git config --local lfs.url \
|
|
"https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs"
|
|
# The manual's pictures too: the APK carries the manual, and
|
|
# assemble-apk.sh refuses a pointer where a picture should be.
|
|
git lfs pull --exclude="fixtures/**"
|
|
ls -lR models/
|
|
|
|
- name: Cache cargo
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: desktop-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
# Slint and winit need these at build time; the runner image is minimal.
|
|
- name: Build dependencies
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq pkg-config libfontconfig1-dev libxkbcommon-dev
|
|
|
|
# The act image ships Node but no Rust. Pinned to the workspace
|
|
# rust-version so CI, the Android image, and local builds agree — a
|
|
# floating toolchain turns an unrelated push into a mystery failure.
|
|
#
|
|
# The component list mirrors rust-toolchain.toml's, rust-analyzer
|
|
# included, even though nothing in this job runs it. rustup reconciles
|
|
# that file against the installed toolchain on the first cargo call in
|
|
# the work tree and fetches whatever is missing — so leaving it out does
|
|
# not save the download, it only moves it into the middle of a build
|
|
# step where it is nobody's line item. Naming it here keeps every fetch
|
|
# inside the step whose name says it is installing things.
|
|
- name: Install Rust 1.92.0
|
|
run: |
|
|
set -e
|
|
curl -fsSL https://sh.rustup.rs | sh -s -- \
|
|
-y --no-modify-path --profile minimal \
|
|
--default-toolchain 1.92.0 --component rustfmt,clippy,rust-analyzer
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
|
|
# Free space before and after the expensive steps, so a repeat of the
|
|
# disk exhaustion above is one line to diagnose instead of a puzzling
|
|
# LLVM error.
|
|
- name: Disk before
|
|
run: df -h /workspace 2>/dev/null || df -h .
|
|
|
|
- name: Format
|
|
run: cargo fmt --all -- --check
|
|
|
|
- name: Clippy
|
|
run: cargo clippy --workspace --all-targets -- -D warnings
|
|
|
|
# GPU tests skip themselves where no adapter is present rather than
|
|
# failing — CI runners generally have none, and a test that cannot run is
|
|
# not evidence either way.
|
|
- name: Test
|
|
run: cargo test --workspace
|
|
|
|
# The runner has one 99 GB disk shared with its images, and this job
|
|
# ended at 92 GB (2.1 GB free) on v0.18.0's run; v0.18.1's release
|
|
# build then died with "No space left on device". The test executables
|
|
# and examples in target/debug are the largest things in it, are never
|
|
# reused (a changed source relinks them) and are not what the cache is
|
|
# for — the dependency rlibs are — so they go before the release build
|
|
# rather than competing with it.
|
|
- name: Free the test binaries before the release build
|
|
run: |
|
|
find target/debug/deps -maxdepth 1 -type f -executable -delete
|
|
rm -rf target/debug/examples target/debug/incremental
|
|
df -h /workspace 2>/dev/null || df -h .
|
|
|
|
- name: Build
|
|
run: cargo build --workspace --release
|
|
|
|
# Only on a release tag: the binary is 150 MB and nothing but the
|
|
# release job wants it.
|
|
- name: Upload the desktop binary
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: darkroom-desktop-x86_64-linux
|
|
path: target/release/darkroom-desktop
|
|
if-no-files-found: error
|
|
|
|
- name: Disk after
|
|
if: always()
|
|
run: df -h /workspace 2>/dev/null || df -h .
|
|
|
|
android:
|
|
runs-on: linux/amd64
|
|
name: Android (aarch64)
|
|
# Waits for the image build. Without this the pull races the push and the
|
|
# job dies with "manifest unknown" before its first step, which is the
|
|
# failure mode this ordering exists to remove.
|
|
needs: android-image
|
|
container:
|
|
image: gitea.tourolle.paris/dtourolle/darkroom-android:latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# The model, which is in LFS and is not optional.
|
|
#
|
|
# `models/**/*.onnx` is tracked in LFS (.gitattributes), so a
|
|
# plain checkout writes a ~130-byte pointer where 11 MB should be, and
|
|
# `dr-segment`'s build script panics by design rather than embedding a
|
|
# pointer and failing at inference. That failure reads like a broken build
|
|
# instead of a missing fetch, which is how it went unnoticed.
|
|
#
|
|
# Not `lfs: true` on the checkout above, and no `Authorization` header
|
|
# here either. Both install a blanket header for every request to this
|
|
# host, and the object download is the one request that already carries
|
|
# one: `git lfs pull` asks `/info/lfs/objects/batch` first, and Gitea
|
|
# answers with a short-lived `Bearer` JWT scoped to that object. git-lfs
|
|
# then sends the JWT *and* the configured header, and two `Authorization`
|
|
# headers is a 400 from Gitea — reported as
|
|
# LFS: Client error: .../info/lfs/objects/<oid>
|
|
# one step after the batch call that had just succeeded, which reads like
|
|
# a rejected credential rather than a duplicated one. A lone token header
|
|
# is understood fine; it is only the collision that fails.
|
|
#
|
|
# So: strip the headers and hand the token to git-lfs as an ordinary
|
|
# credential instead. It authenticates the batch call and leaves the
|
|
# per-object JWT untouched. Gitea authenticates on the password, so the
|
|
# username is a placeholder. Nothing later in this job talks to the
|
|
# remote, so dropping checkout's header costs us nothing.
|
|
#
|
|
# `continue-on-error` deliberately: if this cannot authenticate, the build
|
|
# below still runs and fails with the build script's own message, which
|
|
# names the real problem. A checkout that dies here says nothing.
|
|
- name: Fetch the segmentation model
|
|
continue-on-error: true
|
|
env:
|
|
LFS_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
|
|
run: |
|
|
set -e
|
|
git lfs install --local
|
|
git config --local --get-regexp '^http\..*extraheader$' \
|
|
| cut -d' ' -f1 | sort -u \
|
|
| while read -r key; do git config --local --unset-all "$key"; done || true
|
|
git config --local lfs.url \
|
|
"https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs"
|
|
# The manual's pictures too: the APK carries the manual, and
|
|
# assemble-apk.sh refuses a pointer where a picture should be.
|
|
git lfs pull --exclude="fixtures/**"
|
|
ls -lR models/
|
|
|
|
- name: Cache cargo
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
/opt/cargo/registry
|
|
target-android
|
|
key: android-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
# A fast gate on the crates most likely to break the cross-compile, run
|
|
# before the expensive part. It is `cargo check`, so it type-checks
|
|
# without linking and returns in a fraction of the time the step below
|
|
# takes.
|
|
#
|
|
# Not a statement that only these crates cross-compile — `darkroom-android`
|
|
# and the whole UI stack beneath it build for aarch64 too, which is what
|
|
# the API-level step below does. This one exists to fail fast and name a
|
|
# smaller suspect when it does.
|
|
- name: Cross-compile core
|
|
env:
|
|
CARGO_TARGET_DIR: target-android
|
|
run: cargo check -p dr-types -p dr-gpu -p dr-sync --target aarch64-linux-android
|
|
|
|
# The linker targets MIN_API, not the compile SDK. cargo-ndk otherwise
|
|
# defaults to API 21, far below the Vulkan floor this app needs — and the
|
|
# mismatch is invisible until a device refuses to install.
|
|
#
|
|
# Look under the target triple, and fail on a mismatch. Searching the
|
|
# whole target dir for the first `*.so` found the host proc-macro
|
|
# libraries in target-android/debug/deps instead — x86-64 objects built
|
|
# by the runner's gcc, whose .comment section says nothing about Android
|
|
# and can never contradict the expected API. The step passed regardless
|
|
# of what the linker actually did, which is the one thing it exists to
|
|
# rule out.
|
|
- name: Verify minimum API level
|
|
env:
|
|
CARGO_TARGET_DIR: target-android
|
|
run: |
|
|
set -e
|
|
# `darkroom-android`, not a core crate: this step reads the API level
|
|
# out of a *linked* object, and only that crate produces one. It is
|
|
# the workspace's single `crate-type = ["cdylib"]`; a library crate
|
|
# builds an rlib, which is an archive of object files that no linker
|
|
# has yet touched and that `file` therefore has nothing to say about.
|
|
# Asking for `-p dr-gpu` here could only ever reach the "no aarch64
|
|
# .so was produced" branch below, whatever the linker did.
|
|
#
|
|
# It is also the honest artefact to check: the .so this names is the
|
|
# one that ships in the APK, so the API level verified here is the
|
|
# API level a device will refuse to install against.
|
|
cargo ndk -t arm64-v8a -o target-android/jniLibs \
|
|
build -p darkroom-android --release
|
|
MIN_API=$(sed -n 's/^ARG MIN_API=\([0-9]*\).*/\1/p' docker/android/Dockerfile)
|
|
# Empty on both sides would compare equal and pass, so neither side
|
|
# is allowed to be the result of a failed parse.
|
|
if [ -z "$MIN_API" ]; then
|
|
echo "no ARG MIN_API= in docker/android/Dockerfile"
|
|
exit 1
|
|
fi
|
|
SO=$(find target-android/aarch64-linux-android/release -maxdepth 1 -name '*.so' | head -1)
|
|
if [ -z "$SO" ]; then
|
|
echo "no aarch64 .so was produced"
|
|
exit 1
|
|
fi
|
|
echo "checking $SO"
|
|
# `file` is kept for the log — it names the NDK that built this — but
|
|
# the check no longer depends on it.
|
|
file "$SO" || true
|
|
# The API level is the first word of the `.note.android.ident` ELF
|
|
# note, little-endian. Read the note rather than asking `file` for it:
|
|
# `file` only prints "for Android 28" when its magic database is new
|
|
# enough to decode that note, and this image's is not. The parse then
|
|
# produced nothing, `${API:-unknown}` reported "unknown", and every
|
|
# push failed here for weeks on a .so that was linked perfectly
|
|
# correctly. A note read straight out of the ELF cannot go stale that
|
|
# way.
|
|
readelf -n "$SO" | sed -n '/android.ident/,+3p'
|
|
HEX=$(readelf -n "$SO" 2>/dev/null \
|
|
| awk '/description data:/ { print $6 $5 $4 $3; exit }')
|
|
if [ -z "$HEX" ]; then
|
|
echo "FAIL: no .note.android.ident in $SO — nothing states an API level"
|
|
exit 1
|
|
fi
|
|
API=$(( 0x$HEX ))
|
|
if [ "$API" != "$MIN_API" ]; then
|
|
echo "FAIL: linked for Android $API, expected $MIN_API"
|
|
exit 1
|
|
fi
|
|
echo "OK: linked for Android $API"
|
|
|
|
# The APK itself, so a run leaves something installable behind rather
|
|
# than only the knowledge that it would have linked. The assembly is
|
|
# `docker/android/assemble-apk.sh`, shared with `package.sh` so the file
|
|
# a device gets from `package.sh --install` and the file published here
|
|
# are built by the same code — see that script's header.
|
|
#
|
|
# `KEYSTORE` deliberately points at a throwaway directory instead of its
|
|
# default under `target-android`: that directory is what `actions/cache`
|
|
# restores and saves, and a signing key has no business in a build cache
|
|
# or in anything this job uploads. A fresh debug key per run is the right
|
|
# trade for an artefact whose purpose is getting the app onto a test
|
|
# device; nothing upgrades in place over it, which is the one thing a
|
|
# stable key would buy.
|
|
- name: Package the APK
|
|
env:
|
|
CARGO_TARGET_DIR: target-android
|
|
# Absent secrets mean a debug signature, which is what a fork or a
|
|
# branch build should get. Set all three (see docs/dev/android-signing.md)
|
|
# and the same job produces a release-signed APK instead.
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
KEY_PASS: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
run: |
|
|
set -e
|
|
KEYDIR="$(mktemp -d)"
|
|
chmod 700 "$KEYDIR"
|
|
trap 'rm -rf "$KEYDIR"' EXIT
|
|
|
|
if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
|
|
# The keystore reaches the runner base64-encoded because a secret
|
|
# is a string. It is written under a 0700 mktemp directory, never
|
|
# into the workspace: `target-android` is what actions/cache saves,
|
|
# and the upload step globs the workspace.
|
|
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$KEYDIR/release.keystore"
|
|
export KEYSTORE="$KEYDIR/release.keystore"
|
|
else
|
|
# Not an error. Unset the rest so assemble-apk.sh takes its debug
|
|
# path cleanly rather than seeing a half-configured release one.
|
|
export KEYSTORE="$KEYDIR/debug.keystore"
|
|
unset KEYSTORE_PASS KEY_PASS KEY_ALIAS
|
|
fi
|
|
|
|
REPO="$PWD" TARGET_DIR="$PWD/target-android" \
|
|
bash docker/android/assemble-apk.sh
|
|
|
|
# v3, not v4. v4 is untested against this Gitea and its runner; v3 is
|
|
# what JellyTau uploads its APK with on this same runner, so it is the
|
|
# version known to work here rather than the version that ought to.
|
|
#
|
|
# `if-no-files-found: error` because the failure this guards against is
|
|
# a green run with an empty artefact list, which reads as success until
|
|
# somebody goes looking for the file.
|
|
- name: Upload the APK
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: darkroom-arm64-v8a-apk
|
|
path: target-android/apk/darkroom.apk
|
|
if-no-files-found: error
|
|
|
|
windows-image:
|
|
uses: ./.gitea/workflows/windows-image.yml
|
|
|
|
# TRACES: FR-PLAT-WIN-3
|
|
# The Windows executable and its installer, cross-built from Linux
|
|
# (docs/dev/windows.md §7). No Windows machine anywhere in this job: what it
|
|
# can prove is that the binary links, is a Windows executable with no
|
|
# MinGW runtime imports, starts under Wine, and that the installer installs
|
|
# and uninstalls under Wine. What it cannot prove — a Vulkan device, a
|
|
# render, the secret store — is a release step on a real machine (§6).
|
|
windows:
|
|
runs-on: linux/amd64
|
|
name: Windows (x86_64, cross)
|
|
needs: windows-image
|
|
container:
|
|
image: gitea.tourolle.paris/dtourolle/darkroom-windows:latest
|
|
env:
|
|
CARGO_INCREMENTAL: 0
|
|
CARGO_PROFILE_DEV_DEBUG: 0
|
|
CARGO_TARGET_DIR: target-windows
|
|
# Wine keeps its prefix under $HOME, which the image points at a
|
|
# directory that does not exist in a fresh container.
|
|
HOME: /tmp/home
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# Same step as the desktop leg: the models are LFS objects and the
|
|
# packager refuses pointers.
|
|
- name: Fetch the models
|
|
env:
|
|
LFS_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
|
|
run: |
|
|
set -e
|
|
git lfs install --local
|
|
git config --local --get-regexp '^http\..*extraheader$' \
|
|
| cut -d' ' -f1 | sort -u \
|
|
| while read -r key; do git config --local --unset-all "$key"; done || true
|
|
git config --local lfs.url \
|
|
"https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs"
|
|
# The manual's pictures too: the installer carries the manual, and
|
|
# package.sh refuses a pointer where a picture should be.
|
|
git lfs pull --exclude="fixtures/**"
|
|
ls -l models/face models/scene
|
|
|
|
- name: Cache cargo
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
/opt/cargo/registry
|
|
target-windows
|
|
key: windows-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
# The cfg(windows) branches are linted here and nowhere else: the
|
|
# desktop leg's clippy never compiles them.
|
|
- name: Clippy for the target
|
|
run: cargo clippy --release --target x86_64-pc-windows-gnu -p darkroom-desktop -- -D warnings
|
|
|
|
- name: Build
|
|
run: cargo build --release --target x86_64-pc-windows-gnu -p darkroom-desktop
|
|
|
|
- name: Smoke-test the executable
|
|
run: |
|
|
set -e
|
|
mkdir -p "$HOME"
|
|
EXE=target-windows/x86_64-pc-windows-gnu/release/darkroom-desktop.exe
|
|
file "$EXE"
|
|
file "$EXE" | grep -q 'PE32+' || { echo "FAIL: not a PE32+ executable"; exit 1; }
|
|
file "$EXE" | grep -q '(GUI)' || { echo "FAIL: not a GUI-subsystem executable"; exit 1; }
|
|
if x86_64-w64-mingw32-objdump -p "$EXE" | grep -iE 'libwinpthread|libgcc|libstdc'; then
|
|
echo "FAIL: the executable imports a MinGW runtime DLL"
|
|
exit 1
|
|
fi
|
|
x86_64-w64-mingw32-objdump -p "$EXE" | grep 'DLL Name' | sort -u
|
|
wineboot --init >/dev/null 2>&1 || true
|
|
OUT=$(wine "$EXE" --version 2>/dev/null)
|
|
echo "wine: $OUT"
|
|
echo "$OUT" | grep -q '^darkroom-desktop ' || { echo "FAIL: --version did not answer under Wine"; exit 1; }
|
|
|
|
- name: Package the installer
|
|
run: bash docker/windows/package.sh
|
|
|
|
- name: Smoke-test the installer
|
|
run: |
|
|
set -e
|
|
SETUP=$(ls target-windows/installer/DarkRoom-*-x86_64-setup.exe)
|
|
file "$SETUP" | grep -q 'PE32+' || { echo "FAIL: the installer is not 64-bit"; exit 1; }
|
|
wine "$SETUP" /S 2>/dev/null
|
|
INST=$(echo "$HOME"/.wine/drive_c/users/*/AppData/Local/Programs/DarkRoom)
|
|
ls "$INST"
|
|
# As many files as package.sh stages: everything but the READMEs in
|
|
# the directories it copies. A literal here went stale the first
|
|
# time a model was added.
|
|
WANT=$(find models/face models/scene models/inpaint -maxdepth 1 -type f ! -name README.md | wc -l)
|
|
GOT=$(ls "$INST/models" | wc -l)
|
|
[ "$GOT" = "$WANT" ] || { echo "FAIL: expected $WANT model files, installed $GOT"; exit 1; }
|
|
# The manual, and every picture it shows, counted the same way.
|
|
[ -f "$INST/manual/index.html" ] || { echo "FAIL: no manual installed"; exit 1; }
|
|
WANT=$(ls docs/manual/media | wc -l)
|
|
GOT=$(ls "$INST/manual/media" | wc -l)
|
|
[ "$GOT" = "$WANT" ] || { echo "FAIL: expected $WANT manual pictures, installed $GOT"; exit 1; }
|
|
wine reg query 'HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\DarkRoom' 2>/dev/null \
|
|
| grep -q DisplayVersion || { echo "FAIL: no uninstall registry key"; exit 1; }
|
|
wine "$INST/darkroom.exe" --version 2>/dev/null | grep -q '^darkroom-desktop ' \
|
|
|| { echo "FAIL: the installed executable does not run"; exit 1; }
|
|
wine "$INST/uninstall.exe" /S 2>/dev/null
|
|
sleep 3
|
|
[ ! -e "$INST" ] || { echo "FAIL: uninstall left $INST behind"; ls -R "$INST"; exit 1; }
|
|
echo "OK: installed and uninstalled under Wine"
|
|
|
|
- name: Upload the installer
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: darkroom-windows-x86_64-setup
|
|
path: target-windows/installer/DarkRoom-*-x86_64-setup.exe
|
|
if-no-files-found: error
|
|
|
|
layering:
|
|
runs-on: linux/amd64
|
|
name: Layer separation
|
|
# Node for the JS actions, as above. cargo comes from rustup below.
|
|
container:
|
|
image: catthehacker/ubuntu:act-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# `cargo tree` resolves the dependency graph, so it needs the registry
|
|
# index but no system libraries — this job builds nothing.
|
|
#
|
|
# rust-analyzer is named for the reason given in the desktop job: rustup
|
|
# installs rust-toolchain.toml's components on the first cargo call
|
|
# whether or not this step asks for them, and an unasked-for download is
|
|
# the one nobody can find in the log.
|
|
- name: Install Rust 1.92.0
|
|
run: |
|
|
set -e
|
|
curl -fsSL https://sh.rustup.rs | sh -s -- \
|
|
-y --no-modify-path --profile minimal --default-toolchain 1.92.0 \
|
|
--component rust-analyzer
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
|
|
# ARCH §6.5a: no core/ crate may depend on the UI toolkit. One stray
|
|
# `use slint::` costs headless golden-image testing and the
|
|
# one-operation-two-presentations property together, and nothing else
|
|
# would notice.
|
|
- name: Core crates must not depend on the UI
|
|
run: |
|
|
set -e
|
|
FAILED=0
|
|
for crate in dr-types dr-gpu dr-sync; do
|
|
if cargo tree -p "$crate" -e normal 2>/dev/null | grep -qE '\bslint\b|\bi-slint'; then
|
|
echo "FAIL: $crate depends on Slint (ARCH §6.5a)"
|
|
FAILED=1
|
|
else
|
|
echo "ok: $crate"
|
|
fi
|
|
done
|
|
exit $FAILED
|
|
|
|
# A v* tag becomes a Gitea Release carrying the three builds and their
|
|
# SHA256SUMS, titled and described by the tag's message. Until this job
|
|
# existed every release was made by hand, and most tags never got one.
|
|
#
|
|
# It needs all three platform jobs, so a tag whose tests fail publishes
|
|
# nothing; re-run the failed job and this one follows. The work is
|
|
# tools/publish-release.sh, which is also how a release is finished by hand.
|
|
release:
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
needs: [desktop, android, windows]
|
|
runs-on: linux/amd64
|
|
name: Publish the release
|
|
container:
|
|
image: catthehacker/ubuntu:act-latest
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Fetch the builds
|
|
uses: actions/download-artifact@v3
|
|
with:
|
|
path: dist
|
|
|
|
# Named for the download page, with the version in each name the way
|
|
# the hand-made releases had them. The installer already carries its
|
|
# version from package.sh.
|
|
- name: Publish
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN || github.token }}
|
|
TAG: ${{ github.ref_name }}
|
|
run: |
|
|
set -e
|
|
V="${TAG#v}"
|
|
ls -lR dist
|
|
mkdir -p out
|
|
cp dist/darkroom-arm64-v8a-apk/darkroom.apk "out/darkroom-${V}-arm64-v8a.apk"
|
|
cp dist/darkroom-desktop-x86_64-linux/darkroom-desktop "out/darkroom-desktop-${V}-x86_64-linux"
|
|
chmod +x "out/darkroom-desktop-${V}-x86_64-linux"
|
|
cp dist/darkroom-windows-x86_64-setup/DarkRoom-${V}-x86_64-setup.exe out/
|
|
bash tools/publish-release.sh "$TAG" out/*
|