Files
Kuddev bf2ed9af9b feat(mobile): integrate remote terminal, SSH and reader workflows (#343)
Integrate the Android client with native desktop pairing, remote terminal and SSH/SFTP sessions, keyboard controls, file reading and Agent conversations. Preserve authenticated connection ownership and per-device input permissions.

Validation: all ten required GitHub checks passed, including five native test platforms and both macOS release workspace checks.
2026-09-29 20:09:00 +08:00

745 lines
32 KiB
YAML

name: Pebrel stable release
run-name: >-
Pebrel ${{ inputs.version || github.ref_name }} stable release
on:
push:
tags: ["v*.*.*"]
workflow_dispatch:
inputs:
version:
description: Stable Cargo package version to build and publish
required: true
type: string
publish:
description: Create the public GitHub Release after every package check passes
required: true
default: false
type: boolean
windows_only:
description: Build only the missing Windows package, retaining other platform artifacts
type: boolean
default: false
source_commit:
description: Exact previously tested source commit for Windows-only packaging
type: string
default: ""
validated_run:
description: Existing stable or Full native tests run whose Windows tests passed for source_commit
type: string
default: ""
macos_signing:
description: macOS signing mode; Developer ID also requires notarization secrets
required: true
type: choice
options: [adhoc, developer-id]
default: adhoc
concurrency:
group: stable-release-${{ github.ref }}-${{ inputs.windows_only || false }}
cancel-in-progress: false
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: "1"
RUST_TOOLCHAIN: "1.97.1"
PYTHONUTF8: "1"
MACOS_SIGNING: ${{ inputs.macos_signing || 'adhoc' }}
jobs:
prepare:
name: Validate stable release inputs
runs-on: ubuntu-22.04
outputs:
version: ${{ steps.metadata.outputs.version }}
asset_version: ${{ steps.metadata.outputs.asset_version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Resolve and validate package metadata
id: metadata
env:
REQUESTED_VERSION: ${{ inputs.version }}
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
run: |
version="$(python3 scripts/preview_release.py --print-package-version nebula_app/Cargo.toml)"
if [[ -n "$REQUESTED_VERSION" && "$REQUESTED_VERSION" != "$version" ]]; then
echo "requested version $REQUESTED_VERSION does not match Cargo package version $version" >&2
exit 1
fi
if [[ "$EVENT_NAME" == push && "$REF_NAME" != "v$version" ]]; then
echo "release tag $REF_NAME does not match Cargo package version $version" >&2
exit 1
fi
VERSION="$version" python3 - <<'PY'
import os
from scripts.stable_release import validate_version
validate_version(os.environ["VERSION"])
if "-" in os.environ["VERSION"] or "+" in os.environ["VERSION"]:
raise SystemExit("stable releases require a plain semver without prerelease or build suffix")
PY
python3 - <<'PY'
from pathlib import Path
from scripts.stable_release import validate_changelog, validate_notes
import subprocess
version = subprocess.check_output(
["python3", "scripts/preview_release.py", "--print-package-version", "nebula_app/Cargo.toml"],
text=True,
).strip()
validate_notes(Path(f"docs/release-notes/v{version}.md"), version)
validate_changelog(Path("CHANGELOG.md"), version)
PY
{
echo "version=$version"
echo "asset_version=$version"
} >>"$GITHUB_OUTPUT"
- name: Check architecture and release helper contracts
run: |
python3 scripts/check_architecture.py
python3 scripts/check_platform_cfg.py
python3 -m unittest \
scripts.tests.test_stable_release \
scripts.tests.test_android_release \
scripts.tests.test_ci_native_tests \
scripts.tests.test_preview_release \
scripts.tests.test_macos_dmg \
scripts.tests.test_ai_session_probe \
scripts.tests.test_platform_cfg \
scripts.tests.test_windows_resources \
scripts.tests.test_pebrel_branding \
scripts.tests.test_app_icons \
scripts.tests.test_git_attributes \
scripts.tests.test_xplat
bash -n scripts/package-linux.sh scripts/package-macos.sh
python3 -m py_compile scripts/stable_release.py scripts/preview_release.py
linux:
name: Linux x86_64 stable package
if: ${{ !inputs.windows_only }}
needs: prepare
runs-on: ubuntu-22.04
timeout-minutes: 120
env:
PACKAGE_VERSION: ${{ needs.prepare.outputs.version }}
LINUXDEPLOY_VERSION: 1-alpha-20251107-1
LINUXDEPLOY_SHA256: c20cd71e3a4e3b80c3483cef793cda3f4e990aca14014d23c544ca3ce1270b4d
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install build, package, and headless runtime dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
appstream build-essential cmake curl desktop-file-utils dpkg-dev \
imagemagick libasound2-dev libdbus-1-dev libegl1-mesa-dev \
libfontconfig1-dev libfreetype6-dev libgl1-mesa-dev libudev-dev \
libvulkan1 libwayland-dev libx11-dev libx11-xcb-dev \
libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
libxcb-xinput-dev libxcb-xkb-dev libxcb1-dev libxi-dev \
libxkbcommon-dev libxkbcommon-x11-dev mesa-vulkan-drivers \
patchelf pkg-config weston xauth xvfb zsh
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: ./.github/actions/rust-cache
id: rust-cache
with:
key: release
target: target/release
legacy-key: linux
- name: Build GPUI release binary
run: cargo build --locked --release -p nebula --bin pebrel --features gpui-shell --timings
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: target/release
key: ${{ steps.rust-cache.outputs.cache-key }}
- name: Download pinned linuxdeploy
env:
LINUXDEPLOY: ${{ runner.temp }}/linuxdeploy-x86_64.AppImage
run: |
curl --fail --location --retry 3 --retry-all-errors --show-error \
--output "$LINUXDEPLOY" \
"https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VERSION/linuxdeploy-x86_64.AppImage"
echo "$LINUXDEPLOY_SHA256 $LINUXDEPLOY" | sha256sum --check --strict
chmod 0755 "$LINUXDEPLOY"
echo "LINUXDEPLOY=$LINUXDEPLOY" >>"$GITHUB_ENV"
- name: Package stable Linux assets
run: |
SOURCE_DATE_EPOCH="$(git show -s --format=%ct "$GITHUB_SHA")"
export SOURCE_DATE_EPOCH
bash scripts/package-linux.sh \
--channel stable \
--binary target/release/pebrel \
--version "$PACKAGE_VERSION" \
--output-directory dist \
--linuxdeploy "$LINUXDEPLOY" \
--force
- name: Run AppImage conformance under X11
env:
NEBULA_RESIZE_TRACE: "1"
run: |
xvfb-run -a -s "-screen 0 1600x1000x24" \
python3 scripts/conformance/run.py \
--app "dist/Pebrel-v$PACKAGE_VERSION-linux-x64.AppImage" \
--platform linux-x86_64-appimage \
--output dist/conformance/linux-appimage-report.json
- name: Run AppImage conformance under Wayland
env:
NEBULA_RESIZE_TRACE: "1"
run: |
xvfb-run -a -s "-screen 0 1600x1000x24" bash -euo pipefail <<'WAYLAND_SESSION'
runtime_directory="$RUNNER_TEMP/pebrel-wayland-runtime"
mkdir -p "$runtime_directory" dist/conformance
chmod 0700 "$runtime_directory"
XDG_RUNTIME_DIR="$runtime_directory" weston --backend=x11-backend.so \
--use-pixman --no-config --socket=wayland-pebrel --width=1600 --height=1000 \
--idle-time=0 --log=dist/conformance/weston.log &
weston_pid=$!
trap 'kill "$weston_pid" >/dev/null 2>&1 || true; wait "$weston_pid" >/dev/null 2>&1 || true' EXIT
for _ in $(seq 1 100); do
[[ -S "$runtime_directory/wayland-pebrel" ]] && break
kill -0 "$weston_pid" >/dev/null 2>&1 || exit 1
sleep 0.1
done
test -S "$runtime_directory/wayland-pebrel"
DISPLAY='' XDG_RUNTIME_DIR="$runtime_directory" WAYLAND_DISPLAY=wayland-pebrel \
python3 scripts/conformance/run.py \
--app "dist/Pebrel-v$PACKAGE_VERSION-linux-x64.AppImage" \
--platform linux-x86_64-appimage-wayland \
--output dist/conformance/linux-appimage-wayland-report.json
WAYLAND_SESSION
- name: Install and run Debian conformance
env:
NEBULA_RESIZE_TRACE: "1"
run: |
sudo apt-get install -y "./dist/Pebrel-v$PACKAGE_VERSION-linux-x64.deb"
test "$(dpkg-query -W -f='${Status}' pebrel)" = "install ok installed"
xvfb-run -a -s "-screen 0 1600x1000x24" \
python3 scripts/conformance/run.py --app /usr/bin/pebrel \
--platform linux-x86_64-deb --output dist/conformance/linux-deb-report.json
- name: Upload Linux stable assets
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-package-linux-x64
path: |
dist/*.AppImage
dist/*.deb
dist/*.tar.gz
if-no-files-found: error
retention-days: 14
- name: Upload Linux conformance evidence
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-evidence-linux-x64
path: dist/conformance
if-no-files-found: warn
retention-days: 14
macos:
name: macOS ${{ matrix.architecture }} stable package
if: ${{ !inputs.windows_only }}
needs: prepare
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
include:
- runner: macos-26
architecture: aarch64
native_architecture: arm64
asset_architecture: arm64
- runner: macos-26-intel
architecture: x86_64
native_architecture: x86_64
asset_architecture: x64
runs-on: ${{ matrix.runner }}
env:
PACKAGE_VERSION: ${{ needs.prepare.outputs.version }}
MACOSX_DEPLOYMENT_TARGET: "14.0"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Verify native runner architecture
run: test "$(uname -m)" = "${{ matrix.native_architecture }}"
- name: Require the modern AppKit SDK for native window controls
run: |
sdk_version="$(xcrun --sdk macosx --show-sdk-version)"
test "${sdk_version%%.*}" -ge 26
- name: Test native DMG capacity and mounted contents
run: python3 -m unittest scripts.tests.test_macos_dmg -v
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: ./.github/actions/rust-cache
id: rust-cache
with:
key: release
target: target/release
legacy-key: macos
- name: Build GPUI release binary
run: cargo build --locked --release -p nebula --bin pebrel --features gpui-shell --timings
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: target/release
key: ${{ steps.rust-cache.outputs.cache-key }}
- name: Import Developer ID signing credentials
if: ${{ env.MACOS_SIGNING == 'developer-id' }}
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: |
export SIGNING_DIRECTORY="$RUNNER_TEMP/pebrel-signing"
mkdir -m 0700 "$SIGNING_DIRECTORY"
python3 - <<'PY'
import base64
import os
from pathlib import Path
required = ["APPLE_CERTIFICATE_BASE64", "APPLE_CERTIFICATE_PASSWORD", "APPLE_SIGNING_IDENTITY",
"APPLE_API_KEY_BASE64", "APPLE_API_KEY_ID", "APPLE_API_ISSUER"]
if any(not os.environ.get(name) for name in required):
raise SystemExit("missing required Developer ID signing secret")
root = Path(os.environ["SIGNING_DIRECTORY"])
for name, filename in [("APPLE_CERTIFICATE_BASE64", "certificate.p12"), ("APPLE_API_KEY_BASE64", "AuthKey.p8")]:
path = root / filename
path.write_bytes(base64.b64decode(os.environ[name], validate=True))
path.chmod(0o600)
PY
keychain="$SIGNING_DIRECTORY/release.keychain-db"
password="$(openssl rand -hex 32)"
echo "::add-mask::$password"
security create-keychain -p "$password" "$keychain"
security set-keychain-settings -lut 21600 "$keychain"
security unlock-keychain -p "$password" "$keychain"
security import "$SIGNING_DIRECTORY/certificate.p12" -k "$keychain" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$password" "$keychain"
xcrun notarytool store-credentials pebrel-release --keychain "$keychain" \
--key "$SIGNING_DIRECTORY/AuthKey.p8" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER"
echo "PEBREL_SIGNING_KEYCHAIN=$keychain" >>"$GITHUB_ENV"
- name: Package stable macOS DMG
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
signing_args=()
if [[ "$MACOS_SIGNING" == developer-id ]]; then
test -n "$PEBREL_SIGNING_KEYCHAIN"
signing_args=(--sign-identity "$APPLE_SIGNING_IDENTITY" --notary-profile pebrel-release --signing-keychain "$PEBREL_SIGNING_KEYCHAIN")
fi
bash scripts/package-macos.sh \
--channel stable \
--binary target/release/pebrel \
--version "$PACKAGE_VERSION" \
--architecture "${{ matrix.architecture }}" \
--build-number "$GITHUB_RUN_NUMBER" \
--output-directory dist --force "${signing_args[@]}"
- name: Mount DMG and run conformance
env:
NEBULA_RESIZE_TRACE: "1"
run: |
dmg="dist/Pebrel-v$PACKAGE_VERSION-macos-${{ matrix.asset_architecture }}.dmg"
mount_point="$RUNNER_TEMP/pebrel-stable-${{ matrix.architecture }}"
mkdir -p "$mount_point"
hdiutil attach "$dmg" -nobrowse -readonly -mountpoint "$mount_point"
trap 'hdiutil detach "$mount_point" >/dev/null 2>&1 || hdiutil detach "$mount_point" -force >/dev/null 2>&1 || true' EXIT
app="$mount_point/Pebrel.app"
codesign --verify --deep --strict --verbose=2 "$app"
plutil -lint "$app/Contents/Info.plist"
test "$(plutil -extract CFBundleIdentifier raw -o - "$app/Contents/Info.plist")" = "io.github.kuddev.pebrel"
test "$(lipo -archs "$app/Contents/MacOS/pebrel")" = "${{ matrix.native_architecture }}"
python3 scripts/conformance/run.py --app "$app" --platform "macos-${{ matrix.architecture }}" \
--output "dist/conformance/macos-${{ matrix.architecture }}-report.json"
python3 scripts/conformance/macos_launch.py --app "$app" \
--output "dist/conformance/macos-${{ matrix.architecture }}-launch.json"
- name: Remove temporary signing credentials
if: ${{ always() && env.MACOS_SIGNING == 'developer-id' }}
run: |
if [[ -f "$RUNNER_TEMP/pebrel-signing/release.keychain-db" ]]; then
security delete-keychain "$RUNNER_TEMP/pebrel-signing/release.keychain-db"
fi
rm -rf -- "$RUNNER_TEMP/pebrel-signing"
- name: Upload macOS stable asset
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-package-macos-${{ matrix.asset_architecture }}
path: dist/*.dmg
if-no-files-found: error
retention-days: 14
- name: Upload macOS conformance evidence
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-evidence-macos-${{ matrix.architecture }}
path: dist/conformance
if-no-files-found: warn
retention-days: 14
windows:
name: Windows x64 stable package
needs: prepare
runs-on: windows-2022
timeout-minutes: 120
permissions:
contents: read
actions: read
env:
PACKAGE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Retain runtime preparation tool for the selected source
shell: pwsh
run: |
Copy-Item scripts/prepare-windows-runtime.ps1 "$env:RUNNER_TEMP/prepare-windows-runtime.ps1"
Copy-Item scripts/conformance/windows_standard_user.py "$env:RUNNER_TEMP/windows-standard-user.py"
New-Item -ItemType Directory -Path "$env:RUNNER_TEMP/runtime-tests" -Force | Out-Null
Copy-Item scripts/tests/windows-runtime.tests.ps1 "$env:RUNNER_TEMP/runtime-tests/windows-runtime.tests.ps1"
- name: Verify the previous Windows test result
if: ${{ inputs.source_commit != '' }}
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
SELECTED_COMMIT: ${{ inputs.source_commit }}
VALIDATED_RUN: ${{ inputs.validated_run }}
WINDOWS_ONLY: ${{ inputs.windows_only }}
run: |
if ($env:WINDOWS_ONLY -ne 'true' -or $env:SELECTED_COMMIT -notmatch '^[0-9a-f]{40}$' -or $env:VALIDATED_RUN -notmatch '^[0-9]+$') {
throw 'Selecting old source requires Windows-only mode, an exact commit, and a validated run.'
}
$headers = @{ Authorization = "Bearer $env:GH_TOKEN"; Accept = 'application/vnd.github+json'; 'X-GitHub-Api-Version' = '2022-11-28' }
$run = Invoke-RestMethod -Uri "https://api.github.com/repos/$env:GITHUB_REPOSITORY/actions/runs/$env:VALIDATED_RUN" -Headers $headers -ErrorAction Stop
if ($run.head_sha -ne $env:SELECTED_COMMIT) { throw 'Validated run source differs.' }
$jobs = Invoke-RestMethod -Uri "https://api.github.com/repos/$env:GITHUB_REPOSITORY/actions/runs/$env:VALIDATED_RUN/jobs?per_page=100" -Headers $headers -ErrorAction Stop
$windows = @($jobs.jobs | Where-Object { $_.name -in @('Windows x64 stable package', 'Native tests (windows-x64)') -or $_.name -match 'Tests \(windows-2022\)$' })
$tests = @($windows.steps | Where-Object { $_.name -in @('Test workspace and native harness', 'Test complete workspace and native harness') -and $_.conclusion -eq 'success' })
if ($tests.Count -ne 1) { throw 'Prior Windows native tests did not pass.' }
- uses: actions/checkout@v4
if: ${{ inputs.source_commit != '' }}
with:
ref: ${{ inputs.source_commit }}
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: ./.github/actions/rust-cache
id: rust-cache
with:
key: release
target: target/release
legacy-key: windows
if: ${{ inputs.source_commit == '' }}
- uses: Swatinem/rust-cache@v2
if: ${{ inputs.source_commit != '' }}
with:
cache-on-failure: true
- name: Prepare pinned Windows console runtime
shell: pwsh
run: |
& "$env:RUNNER_TEMP/prepare-windows-runtime.ps1" -Destination assets/windows/conhost
& "$env:RUNNER_TEMP/runtime-tests/windows-runtime.tests.ps1" -ArchivePath (Join-Path ([IO.Path]::GetTempPath()) 'pebrel-conpty-source-v1.5.0.zip')
- name: Test workspace and native harness
if: ${{ inputs.windows_only && inputs.source_commit == '' }}
run: python scripts/ci_native_tests.py
- name: Build GPUI release binary
shell: pwsh
run: |
if (Test-Path scripts/build-windows-product.ps1) {
./scripts/build-windows-product.ps1 -Configuration release -TargetDirectory target
} else {
cargo build --locked --release -p nebula --bin pebrel -p nebula_hook --bin pebrel-hook --features nebula/gpui-shell
if ($LASTEXITCODE -ne 0) { throw 'Windows product build failed' }
}
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: target/release
key: ${{ steps.rust-cache.outputs.cache-key }}
- name: Retain built Windows runtime before packaging
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-runtime-windows-x64
path: |
target/release/pebrel.exe
target/release/pebrel-hook.exe
target/release/conpty.dll
target/release/OpenConsole.exe
if-no-files-found: error
retention-days: 30
- name: Test Windows packaging scripts
shell: pwsh
run: |
./scripts/tests/installer.tests.ps1 -TargetDirectory target
./scripts/tests/package-release.tests.ps1 -TargetDirectory target
- name: Package ZIP and installer
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
./scripts/package-release.ps1 -Version $env:PACKAGE_VERSION -PackageBrand Pebrel -OutputDirectory dist -TargetDirectory target -Force
./scripts/build-installer.ps1 -Version $env:PACKAGE_VERSION -PackageBrand Pebrel -OutputDirectory dist -TargetDirectory target -Force
- name: Run Windows conformance
shell: pwsh
env:
NEBULA_RESIZE_TRACE: "1"
SELECTED_COMMIT: ${{ inputs.source_commit || github.sha }}
run: |
$actual = git rev-parse HEAD
if ($actual -ne $env:SELECTED_COMMIT) { throw 'Checked-out source differs from the requested build.' }
$env:GITHUB_SHA = $actual
python "$env:RUNNER_TEMP/windows-standard-user.py" scripts/conformance/run.py --app target/release/pebrel.exe --platform windows-x86_64 --output dist/conformance/windows-report.json
exit $LASTEXITCODE
- name: Upload Windows stable assets
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-package-windows-x64
path: |
dist/Pebrel-v${{ needs.prepare.outputs.version }}-windows-x64.zip
dist/Pebrel-v${{ needs.prepare.outputs.version }}-windows-x64-setup.exe
if-no-files-found: error
retention-days: 14
- name: Upload Windows conformance evidence
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-evidence-windows-x64
path: dist/conformance
if-no-files-found: warn
retention-days: 14
windows-arm64:
name: Windows ARM64 stable package
if: ${{ !inputs.windows_only }}
needs: prepare
runs-on: windows-11-arm
timeout-minutes: 120
env:
PACKAGE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Require native Windows ARM64
shell: pwsh
run: |
if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -ne 'Arm64') {
throw 'ARM64 packaging requires an ARM64 host.'
}
$compiler = rustc -vV
if ($LASTEXITCODE -ne 0 -or $compiler -notcontains 'host: aarch64-pc-windows-msvc') {
throw 'ARM64 packaging requires a native ARM64 compiler.'
}
- uses: ./.github/actions/rust-cache
id: rust-cache
with:
key: release
target: target/release
- name: Prepare and verify pinned ARM64 console runtime
shell: pwsh
run: |
./scripts/prepare-windows-runtime.ps1 -Destination assets/windows/conhost -Architecture arm64
./scripts/tests/windows-runtime.tests.ps1 -Architecture arm64
./scripts/tests/windows-package-architecture.tests.ps1
- name: Build native ARM64 application and hook
shell: pwsh
run: ./scripts/build-windows-product.ps1 -Configuration release -TargetDirectory target
- name: Save compiled workload
if: ${{ always() && steps.rust-cache.outcome == 'success' && steps.rust-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v4
with:
path: target/release
key: ${{ steps.rust-cache.outputs.cache-key }}
- name: Package native ARM64 assets
shell: pwsh
run: |
./scripts/package-release.ps1 -Version $env:PACKAGE_VERSION -Architecture arm64 -OutputDirectory dist -TargetDirectory target
./scripts/build-installer.ps1 -Version $env:PACKAGE_VERSION -Architecture arm64 -OutputDirectory dist -TargetDirectory target -Force
- name: Keep Windows PowerShell alive on the ARM64 runner image
shell: pwsh
run: ./scripts/conformance/windows_arm64_module_path.ps1
- name: Run ARM64 Windows conformance
shell: pwsh
env:
NEBULA_RESIZE_TRACE: "1"
NEBULA_BOOT_TRACE: "1"
run: |
python scripts/conformance/windows_standard_user.py scripts/conformance/run.py --app target/release/pebrel.exe --platform windows-aarch64 --output dist/conformance/windows-arm64-report.json
exit $LASTEXITCODE
- name: Upload Windows ARM64 stable asset
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-package-windows-arm64
path: |
dist/Pebrel-v${{ needs.prepare.outputs.version }}-windows-arm64.zip
dist/Pebrel-v${{ needs.prepare.outputs.version }}-windows-arm64-setup.exe
if-no-files-found: error
retention-days: 14
- name: Upload Windows ARM64 conformance evidence
if: always()
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: stable-evidence-windows-arm64
path: dist/conformance
if-no-files-found: warn
retention-days: 14
android:
name: Android Preview
if: ${{ !inputs.windows_only }}
needs: prepare
uses: ./.github/workflows/android-release.yml
with:
version: ${{ needs.prepare.outputs.version }}
aggregate:
name: Verify stable assets and upload complete set
if: ${{ !inputs.windows_only }}
needs: [prepare, linux, macos, windows, windows-arm64, android]
runs-on: ubuntu-22.04
env:
PACKAGE_VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: actions/download-artifact@v4
with:
pattern: stable-package-*
merge-multiple: true
path: release-dist
- uses: actions/download-artifact@v4
with:
pattern: stable-evidence-*
merge-multiple: true
path: stable-evidence
- name: Validate conformance evidence and stable manifest
env:
SOURCE_COMMIT: ${{ github.sha }}
run: |
python3 - <<'PY'
import os
from pathlib import Path
from scripts.preview_release import validate_evidence
validate_evidence(Path("stable-evidence"), os.environ["SOURCE_COMMIT"], windows_arm64=True)
PY
python3 scripts/android_release.py --version "$PACKAGE_VERSION" --commit "$SOURCE_COMMIT" \
--output release-dist --report stable-evidence/android-report.json --verify-evidence
python3 scripts/stable_release.py release-dist \
--version "$PACKAGE_VERSION" \
--mark-platform-previews \
--notes "docs/release-notes/v$PACKAGE_VERSION.md" \
--changelog CHANGELOG.md \
--write-checksums release-dist/SHA256SUMS \
--write-notes release-dist/RELEASE_NOTES.md
- name: Upload complete stable release set
uses: actions/upload-artifact@v4
with:
compression-level: 0
name: Pebrel-v${{ needs.prepare.outputs.version }}
path: release-dist
if-no-files-found: error
retention-days: 30
publish:
name: Publish verified GitHub Release
if: ${{ !inputs.windows_only && (github.event_name == 'push' || inputs.publish == true) }}
needs: [prepare, aggregate]
runs-on: ubuntu-22.04
permissions:
contents: write
env:
PACKAGE_VERSION: ${{ needs.prepare.outputs.version }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: actions/download-artifact@v4
with:
name: Pebrel-v${{ needs.prepare.outputs.version }}
path: release-dist
- name: Create public stable Release
run: |
tag="v$PACKAGE_VERSION"
if gh release view "$tag" >/dev/null 2>&1; then
echo "refusing to replace existing Release: $tag" >&2
exit 1
fi
existing_tag_response="$RUNNER_TEMP/stable-existing-tag.json"
existing_tag_error="$RUNNER_TEMP/stable-existing-tag-error.log"
if gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" >"$existing_tag_response" 2>"$existing_tag_error"; then
existing_tag_ref="$(jq -er '.object | select(.type == "tag" or .type == "commit") | .type + " " + .sha' "$existing_tag_response")"
elif jq -e '(.status | tostring) == "404"' "$existing_tag_response" >/dev/null; then
existing_tag_ref=""
else
cat "$existing_tag_error" >&2
exit 1
fi
existing_tag_commit=""
if [[ -n "$existing_tag_ref" ]]; then
read -r existing_tag_type existing_tag_object <<<"$existing_tag_ref"
if [[ "$existing_tag_type" == tag ]]; then
existing_tag_commit="$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$existing_tag_object" --jq .object.sha)"
else
existing_tag_commit="$existing_tag_object"
fi
fi
if [[ -n "$existing_tag_commit" && "$existing_tag_commit" != "$GITHUB_SHA" ]]; then
echo "tag $tag already points to $existing_tag_commit, expected $GITHUB_SHA" >&2
exit 1
fi
gh release create "$tag" \
release-dist/*.AppImage release-dist/*.deb release-dist/*.dmg \
release-dist/*.tar.gz release-dist/*.zip release-dist/*-setup.exe \
release-dist/*.apk release-dist/SHA256SUMS \
--target "$GITHUB_SHA" --title "Pebrel $PACKAGE_VERSION" \
--notes-file release-dist/RELEASE_NOTES.md
gh api "repos/$GITHUB_REPOSITORY/releases/tags/$tag" >"$RUNNER_TEMP/stable-release.json"
tag_ref="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag" --jq '.object.type + " " + .object.sha')"
read -r tag_type tag_object <<<"$tag_ref"
if [[ "$tag_type" == tag ]]; then
tag_commit="$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$tag_object" --jq .object.sha)"
else
tag_commit="$tag_object"
fi
python3 scripts/stable_release.py release-dist --version "$PACKAGE_VERSION" \
--verify-release "$RUNNER_TEMP/stable-release.json" \
--commit "$GITHUB_SHA" --tag-commit "$tag_commit"