name: Build distributable binaries
on:
push:
branches:
- main
tags:
- "v*"
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
checks:
name: Format, lint, and test
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Check Nix formatting
run: |
nix fmt -- --check .
- name: Check Rust formatting
run: |
nix develop --command cargo fmt --all -- --check
- name: Run Clippy
run: |
nix develop --command cargo clippy --workspace --all-targets -- -D warnings
- name: Run tests
run: |
nix develop --command cargo test --workspace
linux:
name: Linux CLI x86_64
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build Linux CLI archive
run: |
nix build .#dist-cli-linux-x86_64 --out-link result-linux-cli
- name: Stage Linux artifact
run: |
set -euo pipefail
mkdir -p dist
cp -L result-linux-cli dist/rlru-cli-linux-x86_64.tar.gz
- name: Upload Linux artifact
uses: actions/upload-artifact@v4
with:
name: rlru-cli-linux-x86_64
path: dist/rlru-cli-linux-x86_64.tar.gz
if-no-files-found: error
windows:
name: Windows CLI x86_64
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build Windows CLI archive
run: |
nix build .#dist-cli-windows-x86_64 --out-link result-windows-cli
- name: Stage Windows artifact
run: |
set -euo pipefail
mkdir -p dist
cp -L result-windows-cli dist/rlru-cli-windows-x86_64.zip
- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: rlru-cli-windows-x86_64
path: dist/rlru-cli-windows-x86_64.zip
if-no-files-found: error
linux-appimage:
name: Linux Desktop AppImage x86_64
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Build Linux desktop AppImage
run: |
nix build .#rlru-dioxus-appimage --out-link result-appimage
- name: Stage AppImage artifact
run: |
set -euo pipefail
mkdir -p dist
if [[ -d result-appimage ]]; then
appimage="$(find result-appimage -maxdepth 1 -type f -name '*.AppImage' -print -quit)"
if [[ -z "$appimage" ]]; then
echo "no AppImage file found in result-appimage" >&2
find result-appimage -maxdepth 2 -type f -print >&2
exit 1
fi
cp -L "$appimage" dist/rlru-dioxus-linux-x86_64.AppImage
else
cp -L result-appimage dist/rlru-dioxus-linux-x86_64.AppImage
fi
chmod +x dist/rlru-dioxus-linux-x86_64.AppImage
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: rlru-dioxus-linux-x86_64-appimage
path: dist/rlru-dioxus-linux-x86_64.AppImage
if-no-files-found: error
android:
name: Android app release
runs-on: ubuntu-latest
timeout-minutes: 90
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
- name: Configure Nix cache
uses: cachix/cachix-action@v15
with:
name: nix-community
skipPush: true
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-rlru-dioxus-android-gradle-${{ hashFiles('flake.lock', 'Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rlru-dioxus-android-gradle-
- name: Build Android release APK and AAB
env:
ANDROID_SIGNING_KEYSTORE_BASE64: ${{ secrets.ANDROID_SIGNING_KEYSTORE_BASE64 }}
ANDROID_SIGNING_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEYSTORE_PASSWORD }}
ANDROID_SIGNING_KEY_ALIAS: ${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}
ANDROID_SIGNING_KEY_PASSWORD: ${{ secrets.ANDROID_SIGNING_KEY_PASSWORD }}
run: nix run .#dioxus-android-release
- name: Stage Android artifacts
shell: bash
run: |
set -euo pipefail
mkdir -p dist
mapfile -t signed_apks < <(
find target/dx/rlru-dioxus/release/android \
-path '*/build/outputs/apk/release/*-signed.apk' \
-type f \
| sort
)
if [[ "${#signed_apks[@]}" -eq 0 ]]; then
echo "No signed Android APK was produced" >&2
exit 1
fi
mapfile -t artifacts < <(
find target/dx/rlru-dioxus/release/android \
\( -path '*/build/outputs/apk/release/*-signed.apk' -o -path '*/build/outputs/bundle/release/*.aab' \) \
-type f \
| sort
)
if [[ "${#artifacts[@]}" -eq 0 ]]; then
echo "No Android APK/AAB artifacts were produced" >&2
exit 1
fi
for artifact in "${artifacts[@]}"; do
cp -v "$artifact" "dist/rlru-dioxus-android-$(basename "$artifact")"
done
ls -lh dist
- name: Upload Android artifacts
uses: actions/upload-artifact@v4
with:
name: rlru-dioxus-android-release
path: dist/*
if-no-files-found: error
cargo-package:
name: Cargo package check
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Check Cargo packages
run: |
nix develop --command cargo package -p psynet --locked
echo "rlru is packaged by cargo publish after psynet is visible on crates.io"
cargo-publish:
name: Publish Cargo packages
runs-on: ubuntu-latest
needs:
- cargo-package
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Validate release tag
run: |
set -euo pipefail
version="$(nix develop --command cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "rlru") | .version')"
psynet_version="$(nix develop --command cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "psynet") | .version')"
if [[ "$GITHUB_REF_NAME" != "v$version" ]]; then
echo "release tag $GITHUB_REF_NAME does not match rlru version $version" >&2
exit 1
fi
if [[ "$psynet_version" != "$version" ]]; then
echo "psynet version $psynet_version does not match rlru version $version" >&2
exit 1
fi
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
if [[ -z "${CARGO_REGISTRY_TOKEN:-}" ]]; then
echo "CARGO_REGISTRY_TOKEN secret is not configured; skipping crates.io publishing"
exit 0
fi
crate_version() {
nix develop --command cargo metadata --no-deps --format-version 1 \
| jq -r ".packages[] | select(.name == \"$1\") | .version"
}
crate_exists() {
local crate="$1"
local version="$2"
curl -fsS -A "rlru-ci/1.0 (https://github.com/rlrml/rlru)" \
"https://crates.io/api/v1/crates/$crate/$version" >/dev/null 2>&1
}
cargo_publish_with_retries() {
local crate="$1"
for attempt in 1 2 3; do
local output
output=$(nix develop --command cargo publish -p "$crate" --locked 2>&1) && return 0
if echo "$output" | grep -q "already exists"; then
echo "$crate is already published; skipping"
return 0
fi
echo "$output" >&2
if [[ "$attempt" -eq 3 ]]; then
return 1
fi
sleep_seconds=$((attempt * 20))
echo "cargo publish failed for $crate on attempt $attempt; retrying in ${sleep_seconds}s..."
sleep "$sleep_seconds"
done
}
publish_crate() {
local crate="$1"
local version
version="$(crate_version "$crate")"
if crate_exists "$crate" "$version"; then
echo "$crate $version is already published; skipping"
return
fi
cargo_publish_with_retries "$crate"
}
wait_for_crate() {
local crate="$1"
local version
version="$(crate_version "$crate")"
for _ in {1..30}; do
if crate_exists "$crate" "$version"; then
return
fi
sleep 10
done
echo "$crate $version did not become visible on crates.io in time" >&2
exit 1
}
publish_crate psynet
wait_for_crate psynet
publish_crate rlru
github-release:
name: Publish GitHub release
runs-on: ubuntu-latest
needs:
- checks
- linux
- windows
- linux-appimage
- android
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Validate release tag
run: |
set -euo pipefail
version="$(sed -n '0,/^version = "\(.*\)"$/s//\1/p' Cargo.toml)"
if [[ "$GITHUB_REF_NAME" != "v$version" ]]; then
echo "release tag $GITHUB_REF_NAME does not match rlru version $version" >&2
exit 1
fi
- name: Download release artifacts
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true
- name: Publish release assets
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
set -euo pipefail
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" dist/* --clobber
else
gh release create "$GITHUB_REF_NAME" dist/* \
--title "$GITHUB_REF_NAME" \
--generate-notes \
--verify-tag
fi