Skip to content

ci(preview): force transitive Linux runtime rpath #38

ci(preview): force transitive Linux runtime rpath

ci(preview): force transitive Linux runtime rpath #38

name: 'GStreamer Runtime'
on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/gstreamer-runtime.yml'
- 'docs/bundled-gstreamer-runtime-plan.md'
- 'docs/macos-gstreamer-app-bundle-plan.md'
- 'package.json'
- 'bun.lock'
- 'scripts/setup-gstreamer.cjs'
- 'scripts/stage-gstreamer-bundle.cjs'
- 'scripts/tauri.cjs'
- 'scripts/verify-gstreamer-runtime.cjs'
- 'src-tauri/**'
push:
branches-ignore:
- release
paths:
- '.github/workflows/gstreamer-runtime.yml'
- 'docs/bundled-gstreamer-runtime-plan.md'
- 'docs/macos-gstreamer-app-bundle-plan.md'
- 'package.json'
- 'bun.lock'
- 'scripts/setup-gstreamer.cjs'
- 'scripts/stage-gstreamer-bundle.cjs'
- 'scripts/tauri.cjs'
- 'scripts/verify-gstreamer-runtime.cjs'
- 'src-tauri/**'
env:
GSTREAMER_VERSION: 1.28.2
FRAME_GSTREAMER_DOWNLOAD_BASE_URL: https://github.com/66HEX/frame-gstreamer-mirror/releases/download
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
concurrency:
group: gstreamer-runtime-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
macos-runtime:
name: macOS ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
arch: aarch64
target: aarch64-apple-darwin
- runner: macos-15-intel
arch: x86_64
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.91.1
targets: ${{ matrix.target }}
- name: Install Rust Target
run: |
rustup show active-toolchain
rustup target add ${{ matrix.target }}
rustup target list --installed
- name: Cache Bun Dependencies
uses: actions/cache@v5
with:
path: |
~/.bun/install/cache
node_modules
key: bun-${{ matrix.runner }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ matrix.runner }}-
- name: Cache Rust Build Artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: |
src-tauri -> target
key: gstreamer-runtime-${{ matrix.target }}
- name: Cache GStreamer Downloads
uses: actions/cache@v5
with:
path: .cache/gstreamer
key: gstreamer-${{ matrix.runner }}-${{ matrix.arch }}-${{ env.GSTREAMER_VERSION }}-${{ hashFiles('scripts/setup-gstreamer.cjs') }}
restore-keys: |
gstreamer-${{ matrix.runner }}-${{ matrix.arch }}-${{ env.GSTREAMER_VERSION }}-
- name: Install macOS GStreamer
run: node scripts/setup-gstreamer.cjs --platform macos --arch ${{ matrix.arch }} --mode ci --install --version ${{ env.GSTREAMER_VERSION }} --github-env
- name: Install Frontend Dependencies
run: bun install --frozen-lockfile
- name: Clean Binaries Directory
shell: bash
run: rm -rf src-tauri/binaries
- name: Cache Runtime Helper Binaries
id: cache-runtime-binaries
uses: actions/cache@v5
with:
path: src-tauri/binaries
key: runtime-binaries-${{ matrix.runner }}-${{ matrix.arch }}-${{ hashFiles('scripts/setup-ffmpeg.cjs', 'scripts/setup-upscaler.cjs') }}
restore-keys: |
runtime-binaries-${{ matrix.runner }}-${{ matrix.arch }}-
- name: Setup FFmpeg
if: steps.cache-runtime-binaries.outputs.cache-hit != 'true'
shell: bash
run: |
# macOS has unzip available by default.
node scripts/setup-ffmpeg.cjs --arch ${{ matrix.arch }}
- name: Setup Upscaler (Common)
run: node scripts/setup-upscaler.cjs --arch ${{ matrix.arch }}
- name: Check Frontend
run: bun run check
- name: Lint
run: bun run lint
- name: Build Tauri App Bundle
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bun tauri build --bundles app --target ${{ matrix.target }}
- name: Stage Bundled GStreamer Framework
run: node scripts/stage-gstreamer-bundle.cjs --app src-tauri/target/${{ matrix.target }}/release/bundle/macos/Frame.app
- name: Verify Bundled GStreamer App
run: node scripts/verify-gstreamer-runtime.cjs --platform macos --app src-tauri/target/${{ matrix.target }}/release/bundle/macos/Frame.app
- name: Smoke Test Bundled GStreamer Runtime
shell: bash
run: |
app="src-tauri/target/${{ matrix.target }}/release/bundle/macos/Frame.app"
executable_name=$(/usr/libexec/PlistBuddy -c 'Print:CFBundleExecutable' "$app/Contents/Info.plist")
env -u FRAME_GSTREAMER_MANIFEST "$app/Contents/MacOS/$executable_name" --verify-gstreamer-runtime
- name: Build macOS DMG Bundle
shell: bash
run: |
app="src-tauri/target/${{ matrix.target }}/release/bundle/macos/Frame.app"
if [ ! -d "$app" ]; then
echo "Staged macOS app bundle not found: $app"
exit 1
fi
version=$(node -p "require('./package.json').version")
case "${{ matrix.arch }}" in
aarch64) dmg_arch="aarch64" ;;
x86_64) dmg_arch="x64" ;;
*) dmg_arch="${{ matrix.arch }}" ;;
esac
dmg_dir="src-tauri/target/${{ matrix.target }}/release/bundle/dmg"
staging_dir="$RUNNER_TEMP/frame-dmg-staging-${{ matrix.arch }}"
dmg="$dmg_dir/Frame_${version}_${dmg_arch}.dmg"
rm -rf "$dmg_dir" "$staging_dir"
mkdir -p "$dmg_dir" "$staging_dir"
ditto "$app" "$staging_dir/Frame.app"
ln -s /Applications "$staging_dir/Applications"
hdiutil create -volname "Frame" -srcfolder "$staging_dir" -ov -format UDZO "$dmg"
- name: Verify macOS DMG Bundle
shell: bash
run: |
dmg=$(find "src-tauri/target/${{ matrix.target }}/release/bundle/dmg" -maxdepth 1 -name "*.dmg" -print -quit)
if [ -z "$dmg" ]; then
echo "macOS DMG bundle not found"
exit 1
fi
hdiutil verify "$dmg"
device=""
mount_dir=""
cleanup() {
if [ -n "$device" ]; then
hdiutil detach "$device" -quiet || true
elif [ -n "$mount_dir" ]; then
hdiutil detach "$mount_dir" -quiet || true
fi
}
trap cleanup EXIT
mount_root="$RUNNER_TEMP/frame-dmg-${{ matrix.arch }}"
for attempt in 1 2 3 4 5; do
mount_dir="${mount_root}-${attempt}"
rm -rf "$mount_dir"
mkdir -p "$mount_dir"
if attach_output=$(hdiutil attach "$dmg" -nobrowse -readonly -mountpoint "$mount_dir" 2>&1); then
printf '%s\n' "$attach_output"
device=$(printf '%s\n' "$attach_output" | awk '$2 == "GUID_partition_scheme" { print $1; exit }')
break
else
status=$?
printf '%s\n' "$attach_output"
hdiutil detach "$mount_dir" -quiet || true
rm -rf "$mount_dir"
if [ "$attempt" = 5 ]; then
exit "$status"
fi
sleep $((attempt * 5))
fi
done
app=$(find "$mount_dir" -maxdepth 1 -name "*.app" -print -quit)
if [ -z "$app" ]; then
echo "macOS app bundle not found inside DMG: $dmg"
exit 1
fi
node scripts/verify-gstreamer-runtime.cjs --platform macos --app "$app"
executable_name=$(/usr/libexec/PlistBuddy -c 'Print:CFBundleExecutable' "$app/Contents/Info.plist")
env -u FRAME_GSTREAMER_MANIFEST "$app/Contents/MacOS/$executable_name" --verify-gstreamer-runtime
linux-runtime:
name: Linux ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
arch: x86_64
target: x86_64-unknown-linux-gnu
- runner: ubuntu-24.04-arm
arch: aarch64
target: aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.91.1
targets: ${{ matrix.target }}
- name: Install Rust Target
run: |
rustup show active-toolchain
rustup target add ${{ matrix.target }}
rustup target list --installed
- name: Install Linux Build Dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
libssl-dev \
libwebkit2gtk-4.1-0 \
libwebkit2gtk-4.1-dev \
libjavascriptcoregtk-4.1-0 \
libjavascriptcoregtk-4.1-dev \
gir1.2-javascriptcoregtk-4.1 \
gir1.2-webkit2-4.1 \
libsoup-3.0-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
libdrm-dev \
librsvg2-dev \
patchelf \
unzip \
xdg-utils
- name: Cache Bun Dependencies
uses: actions/cache@v5
with:
path: |
~/.bun/install/cache
node_modules
key: bun-${{ matrix.runner }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ matrix.runner }}-
- name: Cache Rust Build Artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: |
src-tauri -> target
key: gstreamer-runtime-${{ matrix.target }}
- name: Cache GStreamer Downloads
uses: actions/cache@v5
with:
path: .cache/gstreamer
key: gstreamer-${{ matrix.runner }}-${{ matrix.arch }}-${{ env.GSTREAMER_VERSION }}-${{ hashFiles('scripts/setup-gstreamer.cjs') }}
restore-keys: |
gstreamer-${{ matrix.runner }}-${{ matrix.arch }}-${{ env.GSTREAMER_VERSION }}-
- name: Install Linux GStreamer
run: node scripts/setup-gstreamer.cjs --platform linux --arch ${{ matrix.arch }} --mode ci --install --version ${{ env.GSTREAMER_VERSION }} --github-env
- name: Verify Linux GStreamer pkg-config
run: |
pkg-config --print-errors --modversion gstreamer-1.0 gstreamer-app-1.0 gstreamer-video-1.0 gstreamer-pbutils-1.0
pkg-config --print-errors --libs --cflags gstreamer-video-1.0 'gstreamer-video-1.0 >= 1.14'
- name: Install Frontend Dependencies
run: bun install --frozen-lockfile
- name: Clean Binaries Directory
shell: bash
run: rm -rf src-tauri/binaries
- name: Cache Runtime Helper Binaries
id: cache-runtime-binaries-linux
uses: actions/cache@v5
with:
path: src-tauri/binaries
key: runtime-binaries-linux-${{ matrix.arch }}-${{ hashFiles('scripts/setup-ffmpeg.cjs', 'scripts/setup-upscaler.cjs') }}
restore-keys: |
runtime-binaries-linux-${{ matrix.arch }}-
- name: Setup FFmpeg
if: steps.cache-runtime-binaries-linux.outputs.cache-hit != 'true'
run: node scripts/setup-ffmpeg.cjs --platform linux --arch ${{ matrix.arch }}
- name: Setup Upscaler
if: matrix.arch == 'x86_64'
run: node scripts/setup-upscaler.cjs --platform linux --arch x86_64
- name: Build Upscaler For Linux ARM64
if: matrix.arch == 'aarch64' && steps.cache-runtime-binaries-linux.outputs.cache-hit != 'true'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends cmake libvulkan-dev glslang-dev glslang-tools libgomp1
git config --global url."https://github.com/".insteadOf "git@github.com:"
git clone --recursive https://github.com/xinntao/Real-ESRGAN-ncnn-vulkan.git
cd Real-ESRGAN-ncnn-vulkan
mkdir build
cd build
cmake ../src
make -j"$(nproc)"
cd ../..
mkdir -p src-tauri/binaries
mv Real-ESRGAN-ncnn-vulkan/build/realesrgan-ncnn-vulkan src-tauri/binaries/realesrgan-ncnn-vulkan-aarch64-unknown-linux-gnu
chmod +x src-tauri/binaries/realesrgan-ncnn-vulkan-aarch64-unknown-linux-gnu
rm -rf Real-ESRGAN-ncnn-vulkan
- name: Check Frontend
run: bun run check
- name: Lint
run: bun run lint
- name: Build Linux App Without Bundling
run: bun tauri build --no-bundle --target ${{ matrix.target }}
- name: Build Linux Smoke Test Helper
run: cargo build --manifest-path src-tauri/Cargo.toml --release --target ${{ matrix.target }} --bin gstreamer-runtime-smoke
- name: Stage Bundled Linux GStreamer Runtime
run: node scripts/stage-gstreamer-bundle.cjs --dir src-tauri/target/${{ matrix.target }}/release
- name: Verify Bundled Linux Runtime
run: |
env \
-u FRAME_GSTREAMER_MANIFEST \
-u PKG_CONFIG_PATH \
-u PKG_CONFIG_LIBDIR \
-u GST_PLUGIN_PATH_1_0 \
-u GST_PLUGIN_SYSTEM_PATH_1_0 \
-u GST_PLUGIN_SCANNER_1_0 \
-u GST_REGISTRY \
LD_LIBRARY_PATH="" \
node scripts/verify-gstreamer-runtime.cjs --platform linux --dir src-tauri/target/${{ matrix.target }}/release
- name: Smoke Test Staged Linux GStreamer Runtime
run: |
env \
-u FRAME_GSTREAMER_MANIFEST \
-u PKG_CONFIG_PATH \
-u PKG_CONFIG_LIBDIR \
-u GST_PLUGIN_PATH_1_0 \
-u GST_PLUGIN_SYSTEM_PATH_1_0 \
-u GST_PLUGIN_SCANNER_1_0 \
-u GST_REGISTRY \
LD_LIBRARY_PATH="" \
src-tauri/target/${{ matrix.target }}/release/gstreamer-runtime-smoke
windows-runtime:
name: Windows x86_64
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.91.1
targets: x86_64-pc-windows-msvc
- name: Cache Bun Dependencies
uses: actions/cache@v5
with:
path: |
~/.bun/install/cache
node_modules
key: bun-windows-latest-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-windows-latest-
- name: Cache Rust Build Artifacts
uses: Swatinem/rust-cache@v2
with:
workspaces: |
src-tauri -> target
key: gstreamer-runtime-x86_64-pc-windows-msvc
- name: Cache GStreamer Downloads
uses: actions/cache@v5
with:
path: .cache/gstreamer
key: gstreamer-windows-x86_64-${{ env.GSTREAMER_VERSION }}-${{ hashFiles('scripts/setup-gstreamer.cjs') }}
restore-keys: |
gstreamer-windows-x86_64-${{ env.GSTREAMER_VERSION }}-
- name: Install Windows GStreamer
shell: pwsh
run: node scripts/setup-gstreamer.cjs --platform windows --arch x86_64 --mode ci --install --version ${{ env.GSTREAMER_VERSION }} --github-env
- name: Install Frontend Dependencies
run: bun install --frozen-lockfile
- name: Clean Binaries Directory
shell: bash
run: rm -rf src-tauri/binaries
- name: Cache Runtime Helper Binaries
id: cache-runtime-binaries-windows
uses: actions/cache@v5
with:
path: src-tauri/binaries
key: runtime-binaries-windows-x86_64-${{ hashFiles('scripts/setup-ffmpeg.cjs', 'scripts/setup-upscaler.cjs') }}
restore-keys: |
runtime-binaries-windows-x86_64-
- name: Setup FFmpeg
if: steps.cache-runtime-binaries-windows.outputs.cache-hit != 'true'
run: node scripts/setup-ffmpeg.cjs --platform win32 --arch x86_64
- name: Setup Upscaler
run: node scripts/setup-upscaler.cjs --platform win32 --arch x86_64
- name: Check Frontend
run: bun run check
- name: Lint
run: bun run lint
- name: Build Windows App Without Bundling
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bun tauri build --no-bundle --target x86_64-pc-windows-msvc
- name: Build Windows Smoke Test Helper
run: cargo build --manifest-path src-tauri/Cargo.toml --release --target x86_64-pc-windows-msvc --bin gstreamer-runtime-smoke
- name: Stage Bundled Windows GStreamer Runtime
run: node scripts/stage-gstreamer-bundle.cjs --dir src-tauri/target/x86_64-pc-windows-msvc/release --resources src-tauri/resources/gstreamer
- name: Build Windows Installer Bundle
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: bun tauri bundle --bundles nsis --target x86_64-pc-windows-msvc
- name: Verify Bundled Windows Runtime
run: node scripts/verify-gstreamer-runtime.cjs --platform windows --dir src-tauri/target/x86_64-pc-windows-msvc/release
- name: Install Windows Bundle For Smoke Test
shell: pwsh
run: |
$setupExe = Get-ChildItem "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" -Filter "*-setup.exe" | Select-Object -First 1
if (-not $setupExe) {
throw "Windows NSIS installer not found in bundle output"
}
Write-Host "Using installer: $($setupExe.FullName)"
$installDir = Join-Path $env:RUNNER_TEMP "frame-gstreamer-smoke"
Remove-Item $installDir -Recurse -Force -ErrorAction SilentlyContinue
$process = Start-Process -FilePath $setupExe.FullName -ArgumentList @("/S", "/D=$installDir") -Wait -PassThru
if ($process.ExitCode -ne 0) {
throw "Windows NSIS installer failed with exit code $($process.ExitCode)"
}
$expectedDll = Join-Path $installDir "gstreamer-1.0-0.dll"
$deadline = (Get-Date).AddSeconds(60)
while (-not (Test-Path $expectedDll) -and (Get-Date) -lt $deadline) {
Start-Sleep -Seconds 1
}
if (-not (Test-Path $expectedDll)) {
throw "Installed bundle did not expose $expectedDll within 60 seconds"
}
- name: Verify Windows NSIS Hook
shell: pwsh
run: |
$installDir = Join-Path $env:RUNNER_TEMP "frame-gstreamer-smoke"
if (-not (Test-Path $installDir)) {
throw "Install directory not found: $installDir"
}
$hookMarker = Join-Path $installDir "gstreamer-hook-postinstall.txt"
if (-not (Test-Path $hookMarker)) {
throw "NSIS hook marker missing: $hookMarker"
}
Get-Content $hookMarker
- name: Verify Installed Windows Bundle
shell: pwsh
run: |
$installDir = Join-Path $env:RUNNER_TEMP "frame-gstreamer-smoke"
node scripts/verify-gstreamer-runtime.cjs --platform windows --dir $installDir
- name: Smoke Test Installed Windows Bundle
shell: pwsh
run: |
$installDir = Join-Path $env:RUNNER_TEMP "frame-gstreamer-smoke"
Copy-Item "src-tauri/target/x86_64-pc-windows-msvc/release/gstreamer-runtime-smoke.exe" (Join-Path $installDir "gstreamer-runtime-smoke.exe")
Remove-Item Env:FRAME_GSTREAMER_MANIFEST -ErrorAction SilentlyContinue
Remove-Item Env:PKG_CONFIG_PATH -ErrorAction SilentlyContinue
Remove-Item Env:PKG_CONFIG_LIBDIR -ErrorAction SilentlyContinue
$segments = ($env:PATH -split ';') | Where-Object { $_ -and ($_ -notmatch '\\\.cache\\gstreamer\\') }
$env:PATH = ($segments -join ';')
& (Join-Path $installDir "gstreamer-runtime-smoke.exe")