scythe/.github/workflows/release.yml
spearzificial e069a4576f fix(packaging): build the RPM with rpmbuild and derive its requirements
The v0.2.20 release failed at the RPM step. rpm --addrequires does not
exist in the rpm that Ubuntu 24.04 ships, so the attempt to add the
missing libva requirement back to the package aborted the job.

The RPM is now built with rpmbuild from a generated spec instead of
through alien, which gives exact control over the requirements. They are
derived from the dynamic section of the binary that was actually built,
so the package always describes its own linkage rather than a hand
maintained list, and libva is added explicitly because the FFmpeg VAAPI
device opens it at runtime. The step fails if either the libva or the
FFmpeg requirement is missing from the finished package.

alien is no longer used and has been dropped from the runner packages.
2026-10-21 17:18:00 +05:30

335 lines
14 KiB
YAML

name: Release & Packaging
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build-linux:
name: Build Linux Packages (.tar.gz, .deb, .rpm, .AppImage)
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config clang libva-dev \
libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev \
libavfilter-dev libavdevice-dev \
libpipewire-0.3-dev libgtk-3-dev libgtk-layer-shell-dev libxcb1-dev libasound2-dev \
rpm wget patchelf
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build Release Binaries
run: cargo build --release
- name: Package Linux Tarball (Unified Binary)
run: |
mkdir -p dist/scythe-linux-x86_64
cp target/release/scythe dist/scythe-linux-x86_64/
ln -sf scythe dist/scythe-linux-x86_64/scythe-daemon
ln -sf scythe dist/scythe-linux-x86_64/scythe-ui
ln -sf scythe dist/scythe-linux-x86_64/vrec-daemon
ln -sf scythe dist/scythe-linux-x86_64/vrec-ui
ln -sf scythe dist/scythe-linux-x86_64/vrec
cp packaging/scythe.desktop dist/scythe-linux-x86_64/
tar -czvf dist/scythe-linux-x86_64.tar.gz -C dist scythe-linux-x86_64
- name: Build Debian Package (.deb)
run: |
VERSION="${GITHUB_REF_NAME#v}"
if [ -z "$VERSION" ] || [ "$VERSION" = "main" ]; then
VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2)
fi
echo "Packaging Debian version: ${VERSION}"
mkdir -p "deb/scythe_${VERSION}/DEBIAN" "deb/scythe_${VERSION}/usr/bin" "deb/scythe_${VERSION}/usr/share/applications"
cat > "deb/scythe_${VERSION}/DEBIAN/control" << EOF
Package: scythe
Provides: vrec
Version: ${VERSION}
Section: video
Priority: optional
Architecture: amd64
Maintainer: oiupoyt
Description: High-performance GPU hardware screen recorder and instant replay overlay
Depends: libavcodec60, libavformat60, libavutil58, libswresample4, libswscale7, libpipewire-0.3-0, libxcb1, libasound2, libva2
EOF
cp target/release/scythe "deb/scythe_${VERSION}/usr/bin/scythe"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/scythe-daemon"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/scythe-ui"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec-daemon"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec-ui"
ln -sf scythe "deb/scythe_${VERSION}/usr/bin/vrec"
cp packaging/scythe.desktop "deb/scythe_${VERSION}/usr/share/applications/"
dpkg-deb --build "deb/scythe_${VERSION}" "dist/scythe_${VERSION}_amd64.deb"
- name: Build RPM Package (.rpm)
run: |
VERSION="${GITHUB_REF_NAME#v}"
if [ -z "$VERSION" ] || [ "$VERSION" = "main" ]; then
VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2)
fi
echo "Packaging RPM version: ${VERSION}"
# The requirements are derived from the binary that was actually
# built, so the package always describes its own linkage. Building
# through alien instead dropped libva2, because it has no Debian to
# RPM name rule for it, which would have left hardware encoding
# unavailable on an RPM install.
REQUIRES=""
for soname in $(objdump -p target/release/scythe | awk '/NEEDED/ {print $2}' | sort -u); do
case "$soname" in
libc.so.6|libm.so.6|libgcc_s.so.1|ld-linux*.so.2) continue ;;
esac
REQUIRES="${REQUIRES}Requires: ${soname}()(64bit)\n"
done
# libva is opened at runtime by the FFmpeg VAAPI device, so it is
# not a link time dependency but is still needed for hardware
# encoding to work.
REQUIRES="${REQUIRES}Requires: libva.so.2()(64bit)\n"
mkdir -p rpmbuild
cat > rpmbuild/scythe.spec << 'SPEC'
Name: scythe
Version: RPM_VERSION
Release: 1%{?dist}
Summary: High-performance GPU hardware screen recorder and instant replay overlay
License: MIT
URL: https://github.com/oiupoyt/scythe
BuildArch: x86_64
Provides: vrec
RPM_REQUIRES
%description
High-performance GPU hardware screen recorder and instant replay overlay for Wayland and X11.
%prep
%build
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_datadir}/applications
install -m 0755 %{_binary_path} %{buildroot}%{_bindir}/scythe
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/scythe-daemon
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/scythe-ui
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec-daemon
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec-ui
ln -sf %{_bindir}/scythe %{buildroot}%{_bindir}/vrec
install -m 0644 %{_desktop_path} %{buildroot}%{_datadir}/applications/scythe.desktop
ln -sf %{_datadir}/applications/scythe.desktop %{buildroot}%{_datadir}/applications/vrec.desktop
%files
%{_bindir}/scythe
%{_bindir}/scythe-daemon
%{_bindir}/scythe-ui
%{_bindir}/vrec-daemon
%{_bindir}/vrec-ui
%{_bindir}/vrec
%{_datadir}/applications/scythe.desktop
%{_datadir}/applications/vrec.desktop
%changelog
* Tue Oct 21 2026 oiupoyt - RPM_VERSION-1
- Unified multicall binary with the compatibility symlinks
- Dropped the retired GTK 3 requirements
- Runtime dependencies taken from the built binary
SPEC
sed -i "s|RPM_VERSION|${VERSION}|g" rpmbuild/scythe.spec
# The literal \n sequences are expanded by sed itself. Passing real
# newlines instead would split the s command across lines and sed
# would reject it as unterminated.
sed -i "s|RPM_REQUIRES|${REQUIRES}|" rpmbuild/scythe.spec
rpmbuild --define "_topdir ${PWD}/rpmbuild" \
--define "_binary_path ${PWD}/target/release/scythe" \
--define "_desktop_path ${PWD}/packaging/scythe.desktop" \
-bb rpmbuild/scythe.spec
mv rpmbuild/RPMS/x86_64/*.rpm dist/
rpm -qp --requires dist/*.rpm | grep -q "libva.so.2" \
|| { echo "::error::libva requirement missing from the RPM"; exit 1; }
rpm -qp --requires dist/*.rpm | grep -q "libavcodec" \
|| { echo "::error::FFmpeg requirement missing from the RPM"; exit 1; }
- name: Package Standalone AppImage
run: |
mkdir -p AppDir/usr/bin AppDir/usr/lib AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/scalable/apps
cp target/release/scythe AppDir/usr/bin/
ln -sf scythe AppDir/usr/bin/scythe-ui
ln -sf scythe AppDir/usr/bin/scythe-daemon
ln -sf scythe AppDir/usr/bin/vrec-ui
ln -sf scythe AppDir/usr/bin/vrec-daemon
ln -sf scythe AppDir/usr/bin/vrec
cp packaging/scythe.desktop AppDir/usr/share/applications/
# Bundle the shared library closure. Without this the image only
# runs on a host that happens to carry the exact FFmpeg build the
# binary was linked against, which defeats the point of an
# AppImage. ldd already resolves the full transitive closure.
ldd target/release/scythe | awk '/=>/ {print $3}' | sort -u | while read -r lib; do
[ -e "$lib" ] || continue
base=$(basename "$lib")
# The C runtime stays on the host: bundling a different glibc
# than the kernel and the other system libraries expect breaks
# the image in ways that are hard to diagnose.
case "$base" in
libc.so.6|libm.so.6|libgcc_s.so.1|ld-linux*.so.2|libpthread.so.0|libdl.so.2|librt.so.1) continue ;;
esac
cp -L "$lib" "AppDir/usr/lib/$base"
done
# libva is opened at runtime by the FFmpeg VAAPI device, so it is
# not part of the link time closure and ldd never reports it.
va_path=$(ldconfig -p | awk '$1=="libva.so.2" {print $NF; exit}')
if [ -n "$va_path" ]; then
cp -L "$va_path" AppDir/usr/lib/libva.so.2
else
echo "::error::libva.so.2 not found, hardware encoding would not work in the image"
exit 1
fi
# Prefer the bundled copies over anything the host provides.
patchelf --set-rpath '$ORIGIN/../lib' AppDir/usr/bin/scythe
patchelf --print-rpath AppDir/usr/bin/scythe
cat > AppDir/AppRun << 'EOF'
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
export LD_LIBRARY_PATH="${HERE}/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
exec "${HERE}/usr/bin/scythe" "$@"
EOF
chmod +x AppDir/AppRun
touch AppDir/usr/share/icons/hicolor/scalable/apps/media-record.svg
touch AppDir/media-record.svg
ln -s usr/share/applications/scythe.desktop AppDir/scythe.desktop
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O appimagetool
chmod +x appimagetool
./appimagetool --appimage-extract-and-run AppDir dist/scythe-x86_64.AppImage || true
# The image is only useful if the libraries actually made it in.
lib_count=$(ls -1 AppDir/usr/lib | wc -l)
echo "bundled libraries: ${lib_count}"
if [ "${lib_count}" -lt 8 ]; then
echo "::error::AppImage bundled only ${lib_count} libraries, expected the FFmpeg closure"
exit 1
fi
if [ ! -f dist/scythe-x86_64.AppImage ]; then
echo "::error::AppImage was not produced"
exit 1
fi
- name: Upload Linux Artifacts
uses: actions/upload-artifact@v4
with:
name: linux-packages
path: dist/*
build-windows:
name: Build Windows Binaries (.exe)
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
path-type: minimal
install: >-
mingw-w64-x86_64-toolchain
mingw-w64-x86_64-rust
mingw-w64-x86_64-ffmpeg
mingw-w64-x86_64-pkg-config
mingw-w64-x86_64-clang
mingw-w64-x86_64-ntldd
python3
- name: Build Release Binaries (Windows)
shell: msys2 {0}
env:
LIBCLANG_PATH: /mingw64/bin
BINDGEN_EXTRA_CLANG_ARGS: "--target=x86_64-w64-windows-gnu"
run: |
cargo build --release
mkdir -p dist/bundle
cp target/release/scythe.exe dist/bundle/
python3 packaging/windows/bundle_deps.py dist/bundle
strip dist/bundle/*.exe || true
strip --strip-unneeded dist/bundle/*.dll || true
- name: Package Windows Standalone (.exe), Installer, and Zip
shell: pwsh
run: |
if (!(Get-Command makensis -ErrorAction SilentlyContinue)) {
if (Test-Path "C:\Program Files (x86)\NSIS\makensis.exe") {
$env:Path += ";C:\Program Files (x86)\NSIS"
} else {
choco install nsis -y --no-progress
$env:Path += ";C:\Program Files (x86)\NSIS"
}
}
makensis packaging/windows/scythe-installer.nsi
makensis packaging/windows/scythe-standalone.nsi
Compress-Archive -Path dist/bundle/* -DestinationPath dist/scythe-windows-x86_64.zip -Force
- name: Upload Windows Artifacts
uses: actions/upload-artifact@v4
with:
name: windows-packages
path: |
dist/scythe-setup.exe
dist/scythe.exe
dist/scythe-windows-x86_64.zip
publish-release:
name: Publish GitHub Release
needs: [build-linux, build-windows]
if: always() && !cancelled() && (needs.build-linux.result == 'success' || needs.build-windows.result == 'success')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all built artifacts
uses: actions/download-artifact@v4
continue-on-error: true
with:
path: artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: "Scythe ${{ github.ref_name }}"
body: |
High-performance GPU hardware screen recorder and instant replay overlay for Linux and Windows.
### Packages
- **Windows**: `scythe-setup.exe` (Official installer with Start Menu & Desktop shortcuts)
- **Windows Portable**: `scythe.exe` (Standalone portable launcher)
- **Windows Archive**: `scythe-windows-x86_64.zip` (Portable zip archive)
- **Universal Linux**: `scythe-x86_64.AppImage`
- **Debian / Ubuntu / Mint**: `scythe_*_amd64.deb`
- **Fedora / RHEL / openSUSE**: `*.rpm`
- **Generic Linux**: `scythe-linux-x86_64.tar.gz`
files: |
artifacts/linux-packages/*
artifacts/windows-packages/*
draft: false
prerelease: false
generate_release_notes: false