mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-08-28 09:06:56 +00:00
Drm deb in release workflow (#15776)
* docs(agents): add a comment-length rule Comments were growing to document rejected alternatives, past bugs and measurements. That belongs in the commit message, not the source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci(drm): build the unattended-wayland deb in the release workflow The deb was built by a separate drm-capture workflow on a plain runner, so it diverged from every other Linux deb: different base, different vcpkg/ffmpeg, different toolchain. Move it into flutter-build.yml as build-rustdesk-linux-drm, mirroring build-rustdesk-linux's x86_64 path -- same ubuntu18.04 container, same vcpkg install, same rust and flutter. libdrmtap is built on the runner first and handed to the container via DRMTAP_PREBUILT_DIR, because bionic's meson is too old to build it. The job is ungated, so the --drm packaging path is exercised on every PR; only publishing stays gated on upload-artifact. drm-capture.yml is deleted along with docs/DRM_CAPTURE_SECURITY.md -- the 29 drm unit tests that workflow ran are no longer executed by CI. Three bugs the move exposed: - build.py anchored the libdrmtap paths on abspath(__file__), which is only cwd-independent on Python >= 3.9 (bpo-20443). The packaging container runs 3.6 and chdir's into flutter/, so the ABI-gate cross-check resolved one directory off and every --drm packaging run would have died with FileNotFoundError. Captured as REPO_ROOT at import instead. - DRMTAP_PREBUILT_DIR no longer needs DRMTAP_ALLOW_UNPINNED. A prebuilt dir inside the repo's own third_party/libdrmtap at the pinned sha is the pinned object, not an override, and is now verified as such. - The variant's Depends carried a bare libdrm2. libdrmtap needs drmModeGetFB2, so it is libdrm2 (>= 2.4.95); below that the package installed and could never capture. The loader also logs the dlerror now instead of discarding it, so a soname or glibc mismatch is named rather than surfacing as a generic "libdrmtap not available". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(drm): declare the unattended-wayland deb's real libc6 and libdrm floors libdrmtap is built on the ubuntu-22.04 runner while the rest of the deb comes from the ubuntu18.04 container, so the package has a mixed glibc floor and declared neither half. It installed happily on Ubuntu 20.04 / Debian 11 (glibc 2.31), then dlopen failed on GLIBC_2.34 and capture degraded to the PipeWire portal -- the one thing this variant exists to avoid. Measure the floor off the staged objects and put it in Depends, so apt refuses with a reason instead of handing over a package that can never capture. Measured rather than written down: the number moves whenever either base does, and it lands exactly on RHEL/Rocky 9 (glibc 2.34), where one off-by-one decides whether that whole family can install. drmModeGetFB2 landed in libdrm 2.4.101, not 2.4.95 -- checked against the libdrm tags, xf86drmMode.h first declares it in 2.4.101. The old floor admitted Debian 10 (2.4.97), where the .so is linked -z now and dies on an undefined symbol at dlopen. libdrmtap's own meson.build carries the same wrong number. Upload the deb on always(): the run that fails the drm check is the one whose artifact is most worth downloading. Publish stays gated on success, so an unverified build still cannot reach a release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ddad47925c
commit
9a81c8a138
@@ -1,449 +0,0 @@
|
||||
name: DRM capture (opt-in drm feature)
|
||||
|
||||
# Least-privilege GITHUB_TOKEN. Every job here only checks out, builds and tests; the artifact
|
||||
# up/download used by the deb job authenticates with the runtime token, not this one. Declared at
|
||||
# the workflow level so the reusable bridge workflow called below inherits the same bound.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Supersede a stale run when a PR is pushed again; never cancel a master run, whose whole job is to
|
||||
# record that a given commit on master was verified.
|
||||
concurrency:
|
||||
group: drm-capture-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Everything CI-side about the opt-in `drm` backend lives here, so the stock CI and release workflows
|
||||
# stay byte-identical to a build with the feature off. Nothing in this file runs unless a drm-related
|
||||
# path changes (or someone dispatches it by hand), so a PR that does not touch the backend pays nothing.
|
||||
#
|
||||
# The stock `CI` workflow deliberately does NOT compile with `--features drm`: the shipped default is
|
||||
# the drm-off configuration and that stays the primary verified one.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- "libs/scrap/src/common/drm_reader.rs"
|
||||
- "libs/scrap/src/common/drm_render.rs"
|
||||
- "libs/scrap/src/common/drmtap_dl.rs"
|
||||
- "libs/scrap/src/common/mod.rs"
|
||||
- "libs/scrap/Cargo.toml"
|
||||
# The ROOT manifest is where the top-level `drm` feature is DEFINED, so a PR that changes
|
||||
# what `drm` pulls in must not skip this workflow. `Cargo.lock` is deliberately NOT here:
|
||||
# measured over the last 100 commits, it alone would have fired this workflow 13 times and
|
||||
# the pair 24 times, each run costing about two job-hours for a full vcpkg + flutter release
|
||||
# build, almost always for a dependency the drm path never touches. A lockfile bump that
|
||||
# does affect it arrives with a manifest or source change, which is triggered above.
|
||||
- "Cargo.toml"
|
||||
- "src/ipc.rs"
|
||||
- "src/ipc/**"
|
||||
- "src/server/drm_capturer.rs"
|
||||
- "src/server/wayland.rs"
|
||||
- "src/server/display_service.rs"
|
||||
# These three carry DRM wiring too (warm_availability, the cursor path in run_cursor, and the
|
||||
# producer start + get_cursor/get_cursor_data), so a PR touching only them must not skip the
|
||||
# whole drm verification.
|
||||
- "src/server.rs"
|
||||
- "src/server/input_service.rs"
|
||||
- "src/platform/linux.rs"
|
||||
- "build.py"
|
||||
- ".github/workflows/drm-capture.yml"
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# Deliberately the SAME list as the pull_request trigger above: a shorter one here means a push
|
||||
# that touches only the missing paths (a squash merge, a direct push) skips re-verification.
|
||||
paths:
|
||||
- "libs/scrap/src/common/drm_reader.rs"
|
||||
- "libs/scrap/src/common/drm_render.rs"
|
||||
- "libs/scrap/src/common/drmtap_dl.rs"
|
||||
- "libs/scrap/src/common/mod.rs"
|
||||
- "libs/scrap/Cargo.toml"
|
||||
# The ROOT manifest is where the top-level `drm` feature is DEFINED, so a PR that changes
|
||||
# what `drm` pulls in must not skip this workflow. `Cargo.lock` is deliberately NOT here:
|
||||
# measured over the last 100 commits, it alone would have fired this workflow 13 times and
|
||||
# the pair 24 times, each run costing about two job-hours for a full vcpkg + flutter release
|
||||
# build, almost always for a dependency the drm path never touches. A lockfile bump that
|
||||
# does affect it arrives with a manifest or source change, which is triggered above.
|
||||
- "Cargo.toml"
|
||||
- "src/ipc.rs"
|
||||
- "src/ipc/**"
|
||||
- "src/server/drm_capturer.rs"
|
||||
- "src/server/wayland.rs"
|
||||
- "src/server/display_service.rs"
|
||||
# These three carry DRM wiring too (warm_availability, the cursor path in run_cursor, and the
|
||||
# producer start + get_cursor/get_cursor_data), so a PR touching only them must not skip the
|
||||
# whole drm verification.
|
||||
- "src/server.rs"
|
||||
- "src/server/input_service.rs"
|
||||
- "src/platform/linux.rs"
|
||||
- "build.py"
|
||||
- ".github/workflows/drm-capture.yml"
|
||||
|
||||
env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
FLUTTER_VERSION: "3.24.5"
|
||||
|
||||
jobs:
|
||||
drm-tests:
|
||||
name: drm unit tests (linux)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
|
||||
with:
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: false
|
||||
swap-storage: false
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get install -y \
|
||||
clang cmake curl gcc git g++ \
|
||||
libpam0g-dev libasound2-dev libunwind-dev \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
libgtk-3-dev libpulse-dev libva-dev libvdpau-dev \
|
||||
libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
|
||||
libxdo-dev libxfixes-dev nasm wget
|
||||
|
||||
- name: Setup vcpkg with Github Actions binary cache
|
||||
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
|
||||
with:
|
||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
shell: bash
|
||||
run: $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
|
||||
# The whole rustdesk-crate test set with the feature ON, not just the `_drm` ones by name: a name
|
||||
# filter would skip the sibling asserts that also matter in this configuration, notably the one
|
||||
# bounding `size_of::<Data>()`, which the new DmabufDesc variant grows.
|
||||
# The two skips are the same ones the stock CI applies: both need a real display server and fail
|
||||
# on a headless runner regardless of this feature.
|
||||
- name: Run rustdesk crate tests with the drm feature
|
||||
shell: bash
|
||||
run: |
|
||||
cargo test --locked --target x86_64-unknown-linux-gnu -p rustdesk --features drm \
|
||||
--no-fail-fast -- --skip test_get_cursor_pos --skip test_get_key_state
|
||||
|
||||
# The capture backend itself lives in the scrap crate, so its unit tests are a separate
|
||||
# package. `--lib` keeps this to unit tests; none of them touch a device or a display server.
|
||||
- name: Run scrap crate tests with the drm feature
|
||||
shell: bash
|
||||
run: |
|
||||
cargo test --locked --target x86_64-unknown-linux-gnu -p scrap --features drm --lib
|
||||
|
||||
libdrmtap:
|
||||
name: libdrmtap pin, build and .so contract
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install libdrmtap build deps
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get install -y meson ninja-build pkg-config libdrm-dev \
|
||||
libegl1-mesa-dev libgles2-mesa-dev
|
||||
|
||||
# Exercises the real fetch-and-build path in build.py, which pins the commit by sha, so a bad or
|
||||
# moved pin fails here rather than in a release job.
|
||||
- name: Fetch the pinned libdrmtap and build the .so
|
||||
shell: bash
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import importlib.util, sys
|
||||
spec = importlib.util.spec_from_file_location("b", "build.py")
|
||||
b = importlib.util.module_from_spec(spec)
|
||||
sys.argv = ["build.py"]
|
||||
spec.loader.exec_module(b)
|
||||
so = b.build_libdrmtap_so()
|
||||
print(f"::notice::built {so}")
|
||||
open("so_path", "w").write(so)
|
||||
PY
|
||||
|
||||
# The shipped hot path is the EGL detile. libdrmtap degrades to a CPU-only stub when the egl or
|
||||
# glesv2 pkg-config files are missing on the build host, and nothing else in the pipeline notices,
|
||||
# so assert here that the object we would ship really carries EGL and really exports every symbol
|
||||
# the runtime loader resolves.
|
||||
- name: Assert the .so contract (EGL enabled, loader symbols present)
|
||||
shell: bash
|
||||
run: |
|
||||
# Strict mode is load-bearing here: without it the trailing ::notice echo would return 0
|
||||
# and mask the `test "$missing" -eq 0` assertion, so the step would pass with a missing
|
||||
# loader symbol or a CPU-only stub. (pipefail also keeps the grep -c pipelines honest.)
|
||||
set -euo pipefail
|
||||
SO="$(cat so_path)"
|
||||
echo "checking $SO"
|
||||
missing=0
|
||||
# Every symbol drmtap_dl.rs resolves, derived from the loader itself so the two cannot
|
||||
# drift. The character class allows digits (a drmtap_grab_desc2 would otherwise be
|
||||
# silently dropped from the loop), and the count is asserted below so a refactor of the
|
||||
# loader away from b"..." literals cannot quietly turn this whole check into a no-op that
|
||||
# iterates zero times and passes.
|
||||
# `|| true` on the extraction pipelines: under set -e/pipefail a zero-match grep would
|
||||
# abort the script before the explicit ::error guard below can say WHY it failed; the
|
||||
# guard on nsyms is the intended reporter for that case.
|
||||
syms=$(grep -oE 'b"drmtap_[a-z0-9_]+"' libs/scrap/src/common/drmtap_dl.rs \
|
||||
| sed 's/^b"//; s/"$//' | sort -u || true)
|
||||
nsyms=$(echo "$syms" | grep -c . || true)
|
||||
if [ "$nsyms" -lt 13 ]; then
|
||||
echo "::error::extracted only $nsyms loader symbols from drmtap_dl.rs (expected >= 13); the extraction pattern no longer matches the loader"
|
||||
missing=1
|
||||
fi
|
||||
# Inspect the object ONCE into a variable, then match with bash's own pattern operator --
|
||||
# NO PIPE ANYWHERE IN THESE CHECKS. `anything | grep -q` under `set -o pipefail` reports a
|
||||
# FALSE FAILURE as soon as the producer outruns the 64 KB pipe buffer: grep -q exits at the
|
||||
# first match, the producer dies on SIGPIPE (141), and pipefail makes that the pipeline's
|
||||
# status, so a library that HAS the symbol is reported as missing it. Measured on a real
|
||||
# EGL-enabled .so (101 KB of `strings`, both markers present): the piped form reported both
|
||||
# missing and failed the step. Note the obvious repair does NOT work -- materializing the
|
||||
# output and then doing `printf '%s\n' "$var" | grep -q` keeps the pipe and just swaps the
|
||||
# producer, and it fails identically (measured). Today's release-sized .so happens to fit in
|
||||
# the buffer, which is the only reason this has not fired yet.
|
||||
exported="$(nm -D --defined-only "$SO")"
|
||||
strs="$(strings "$SO")"
|
||||
for sym in $syms; do
|
||||
# Line-anchored: wrap in newlines so the pattern can require a whole line, the same
|
||||
# thing `grep " T $sym$"` was expressing.
|
||||
if [[ $'\n'"$exported"$'\n' != *$'\n'*" T $sym"$'\n'* ]]; then
|
||||
echo "::error::libdrmtap does not export $sym, which the runtime loader resolves"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
# EGL is reached by lazy dlopen, on purpose, so that the privileged process never links the
|
||||
# vendor GL stack. That means there is NO DT_NEEDED entry and no undefined egl* symbol to look
|
||||
# for: the naive ELF check reports "no EGL" on a perfectly good library. What a CPU-only stub
|
||||
# build really lacks is the dlopen target name and the import call itself.
|
||||
for s in "libEGL.so.1" "eglCreateImageKHR"; do
|
||||
if [[ "$strs" != *"$s"* ]]; then
|
||||
echo "::error::libdrmtap looks like a CPU-only stub (no $s): the EGL detile hot path is missing"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
test "$missing" -eq 0
|
||||
echo "::notice::libdrmtap .so contract ok ($nsyms loader symbols, EGL detile present)"
|
||||
|
||||
# The bridge generator is a reusable workflow, so this calls the stock one instead of duplicating it.
|
||||
generate-bridge:
|
||||
uses: ./.github/workflows/bridge.yml
|
||||
|
||||
drm-deb:
|
||||
name: unattended-wayland deb (verification build)
|
||||
needs: generate-bridge
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
|
||||
with:
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: false
|
||||
swap-storage: false
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- name: Restore bridge files
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: bridge-artifact
|
||||
path: ./
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
# Same list the stock linux job needs, plus the flutter desktop toolchain and the three
|
||||
# libdrmtap build deps (libdrm and the mesa-specific EGL/GLES dev packages).
|
||||
sudo apt-get install -y \
|
||||
clang cmake curl gcc git g++ ninja-build meson pkg-config \
|
||||
libpam0g-dev libasound2-dev libunwind-dev liblzma-dev \
|
||||
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
libgtk-3-dev libpulse-dev libva-dev libvdpau-dev \
|
||||
libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
|
||||
libxdo-dev libxfixes-dev nasm wget \
|
||||
libdrm-dev libegl1-mesa-dev libgles2-mesa-dev
|
||||
|
||||
- name: Setup vcpkg with Github Actions binary cache
|
||||
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
|
||||
with:
|
||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
shell: bash
|
||||
run: $VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
|
||||
- name: Setup flutter
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
- name: Patch flutter
|
||||
shell: bash
|
||||
run: |
|
||||
cd $(dirname $(dirname $(which flutter)))
|
||||
# `[[ ... ]] && cmd` as the last line makes the STEP fail once FLUTTER_VERSION moves off
|
||||
# the pinned value, because the failed test becomes the script's exit status. An explicit
|
||||
# if/else skips instead. Reading the values from the environment rather than interpolating
|
||||
# github expressions into the script also keeps this off zizmor's template-injection list.
|
||||
# (spelled out in prose: a literal expression marker here, even in a comment, is parsed by
|
||||
# actionlint and breaks workflow linting.)
|
||||
if [[ "$FLUTTER_VERSION" == "3.24.5" ]]; then
|
||||
git apply "$GITHUB_WORKSPACE/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff"
|
||||
else
|
||||
echo "::notice::flutter $FLUTTER_VERSION is not 3.24.5; skipping the dropdown patch"
|
||||
fi
|
||||
|
||||
- name: Build the unattended-wayland deb
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The features have to be on the cargo line HERE, because the packaging line below passes
|
||||
# --skip-cargo and never rebuilds: whatever this compiles is what ships. ASK build.py for
|
||||
# the list rather than repeating it -- get_features() is the single definition of what
|
||||
# these flags mean, and a hardcoded copy silently ships something other than what
|
||||
# `build.py --drm` produces the moment that function changes. The flags must be the same
|
||||
# on both lines for that to hold, so keep them in one variable.
|
||||
DRM_BUILD_FLAGS=(--flutter --drm --hwcodec --unix-file-copy-paste)
|
||||
FEATURES="$(python3 ./build.py "${DRM_BUILD_FLAGS[@]}" --print-features)"
|
||||
echo "features from build.py: $FEATURES"
|
||||
# Assert rather than trust: an empty or error-shaped value would otherwise become a cargo
|
||||
# line that builds a stock binary, which only the staged-binary marker check would catch.
|
||||
# Match whole comma-separated TOKENS, one feature at a time. A substring test would depend
|
||||
# on the order get_features happens to append them (failing a correct build the day they
|
||||
# are reordered) and would also match a future feature that merely contains "drm", the same
|
||||
# trap build.py avoids by splitting on commas rather than testing a substring.
|
||||
for want in drm drm-wake; do
|
||||
case ",$FEATURES," in
|
||||
*",$want,"*) ;;
|
||||
*) echo "::error::build.py --print-features returned no '$want' feature: $FEATURES"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
cargo build --locked --lib --release --features "$FEATURES"
|
||||
python3 ./build.py "${DRM_BUILD_FLAGS[@]}" --skip-cargo
|
||||
|
||||
# build.py exits 0 on some inner failures, so assert the artifact instead of trusting the status,
|
||||
# and assert the two things that make it the drm variant at all.
|
||||
- name: Assert the deb is a real drm build
|
||||
shell: bash
|
||||
run: |
|
||||
# Strict mode so the mid-script checks can fail the step (without it only the LAST
|
||||
# command's status counts and the greps above it are decorative).
|
||||
set -euo pipefail
|
||||
# Glob into an array and assert the COUNT. `deb="$(ls ...)"` aborted on zero matches
|
||||
# before its own `test -n` could report, and on several matches produced a multi-line
|
||||
# value whose `mv` failed with something unrelated to the real problem.
|
||||
shopt -s nullglob
|
||||
debs=(rustdesk-unattended-wayland-*.deb)
|
||||
if [ "${#debs[@]}" -ne 1 ]; then
|
||||
echo "::error::expected exactly one rustdesk-unattended-wayland-*.deb, found ${#debs[@]}: ${debs[*]-none}"
|
||||
exit 1
|
||||
fi
|
||||
deb="${debs[0]}"
|
||||
echo "::notice::built $deb ($(stat -c %s "$deb") bytes)"
|
||||
# Pipe-free for the same reason as the .so contract step above (see the comment there:
|
||||
# a producer feeding a grep that can exit early is a SIGPIPE reported as a failure under
|
||||
# pipefail). `grep -E` without -q reads to EOF so these two happen to be safe, but the
|
||||
# shape is the hazard and the next `-q` added here would inherit it silently.
|
||||
contents="$(dpkg -c "$deb")"
|
||||
if [[ ! "$contents" =~ usr/lib/rustdesk/libdrmtap\.so\.0\.[0-9]+\.[0-9]+ ]]; then
|
||||
echo "::error::the deb does not contain a versioned libdrmtap.so.0.x.y"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$contents" != *"usr/lib/rustdesk/libdrmtap.so.0 ->"* ]]; then
|
||||
echo "::error::the deb does not contain the libdrmtap.so.0 soname symlink"
|
||||
exit 1
|
||||
fi
|
||||
# The library alone does not make this a drm build: build.py stages it whenever --drm is
|
||||
# passed, independently of what was compiled, and the deb name is what tells a user this
|
||||
# is the consent-bypass variant. Assert the BINARY too, by the absolute dlopen path that
|
||||
# only exists when the feature is compiled in -- otherwise a stock binary could ship
|
||||
# under the unattended-wayland name with a library it can never reach.
|
||||
rm -rf /tmp/debassert && dpkg-deb -R "$deb" /tmp/debassert
|
||||
if ! grep -qF /usr/lib/rustdesk/libdrmtap.so.0 /tmp/debassert/usr/share/rustdesk/lib/librustdesk.so; then
|
||||
echo "::error::the packaged librustdesk.so has no libdrmtap dlopen path; this is not a drm build"
|
||||
exit 1
|
||||
fi
|
||||
mv "$deb" "${deb%.deb}-x86_64.deb"
|
||||
|
||||
# MEASURE the glibc floor rather than describing it. This job builds on the runner instead of the
|
||||
# ubuntu18.04 container the stock release debs use, so the artifact only runs on a host at least
|
||||
# as new as the runner -- and that number belongs in the artifact NAME, because a comment in this
|
||||
# file is not visible to whoever downloads it from the Actions UI.
|
||||
- name: Measure the deb glibc floor
|
||||
id: floor
|
||||
shell: bash
|
||||
run: |
|
||||
# Strict mode for the same reason as the assert step above. The floor extraction gets an
|
||||
# explicit rescue so a no-match grep reaches the `test -n` reporter instead of dying as a
|
||||
# bare pipeline failure.
|
||||
set -euo pipefail
|
||||
# Same nullglob array + count assertion as the assert step above, for the same two
|
||||
# reasons: under set -e a zero-match `ls` aborts before anything can report WHY, and
|
||||
# several matches make `deb` multi-line so dpkg-deb fails with an unrelated error. (This
|
||||
# was the sibling left behind when that one was fixed.)
|
||||
shopt -s nullglob
|
||||
debs=(rustdesk-unattended-wayland-*-x86_64.deb)
|
||||
if [ "${#debs[@]}" -ne 1 ]; then
|
||||
echo "::error::expected exactly one renamed deb to measure, found ${#debs[@]}: ${debs[*]-none}"
|
||||
exit 1
|
||||
fi
|
||||
deb="${debs[0]}"
|
||||
rm -rf /tmp/debfloor && dpkg-deb -R "$deb" /tmp/debfloor
|
||||
floor="$(objdump -T /tmp/debfloor/usr/share/rustdesk/lib/librustdesk.so \
|
||||
| grep -oE 'GLIBC_2\.[0-9]+' | sort -uV | tail -1 || true)"
|
||||
test -n "$floor"
|
||||
echo "floor=${floor#GLIBC_}" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::deb requires ${floor} or newer (built on the runner, not the ubuntu18.04 release container)"
|
||||
|
||||
# Verification artifact, deliberately NOT a release deliverable. The consent-free variant stays
|
||||
# out of the published release either way; the name states the floor so nobody installs it on an
|
||||
# older distro and hits a bare loader error.
|
||||
- name: Upload the deb
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: rustdesk-unattended-wayland-x86_64-verification-glibc${{ steps.floor.outputs.floor }}.deb
|
||||
path: rustdesk-unattended-wayland-*-x86_64.deb
|
||||
@@ -1749,6 +1749,276 @@ jobs:
|
||||
files: |
|
||||
res/rustdesk-${{ env.VERSION }}*.zst
|
||||
|
||||
# Same build as build-rustdesk-linux x86_64 -- same vcpkg/ffmpeg, same ubuntu18.04 container, same
|
||||
# rust and flutter -- only with the drm feature on, so it ships as the separate
|
||||
# rustdesk-unattended-wayland deb. libdrmtap is built on the runner because bionic's meson is too
|
||||
# old for it. A separate job rather than a matrix entry of build-rustdesk-linux: appimage and
|
||||
# flatpak need that job, and a failure here must not skip them.
|
||||
build-rustdesk-linux-drm:
|
||||
needs: [generate-bridge]
|
||||
name: build rustdesk linux drm x86_64
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Maximize build space
|
||||
run: |
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y nasm
|
||||
sudo apt-get install -y qemu-user-static
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0
|
||||
with:
|
||||
swap-size-gb: 12
|
||||
|
||||
- name: Free Space
|
||||
run: |
|
||||
df -h
|
||||
free -m
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
components: "rustfmt"
|
||||
|
||||
- name: Save Rust toolchain version
|
||||
run: |
|
||||
RUST_TOOLCHAIN_VERSION=$(cargo --version | awk '{print $2}')
|
||||
echo "RUST_TOOLCHAIN_VERSION=$RUST_TOOLCHAIN_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Disable rust bridge build
|
||||
run: |
|
||||
# only build cdylib
|
||||
sed -i "s/\[\"cdylib\", \"staticlib\", \"rlib\"\]/\[\"cdylib\"\]/g" Cargo.toml
|
||||
|
||||
- name: Restore bridge files
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: bridge-artifact
|
||||
path: ./
|
||||
|
||||
- name: Setup vcpkg with Github Actions binary cache
|
||||
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
|
||||
with:
|
||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||
doNotCache: false
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: |
|
||||
sudo apt install -y libva-dev && apt show libva-dev
|
||||
if ! $VCPKG_ROOT/vcpkg \
|
||||
install \
|
||||
--triplet x64-linux \
|
||||
--x-install-root="$VCPKG_ROOT/installed"; then
|
||||
find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do
|
||||
echo "$_1:"
|
||||
echo "======"
|
||||
cat "$_1"
|
||||
echo "======"
|
||||
echo ""
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-x64-linux-rel-out.log" || true
|
||||
shell: bash
|
||||
|
||||
# The container's meson is too old to build libdrmtap, so build it here from the pin in
|
||||
# build.py and hand the .so to the container below via DRMTAP_PREBUILT_DIR.
|
||||
- name: Build libdrmtap
|
||||
run: |
|
||||
sudo apt-get install -y meson ninja-build pkg-config \
|
||||
libdrm-dev libegl1-mesa-dev libgles2-mesa-dev
|
||||
python3 - <<'PY'
|
||||
import importlib.util, sys
|
||||
spec = importlib.util.spec_from_file_location("b", "build.py")
|
||||
b = importlib.util.module_from_spec(spec)
|
||||
sys.argv = ["build.py"]
|
||||
spec.loader.exec_module(b)
|
||||
print(f"::notice::built {b.build_libdrmtap_so()}")
|
||||
PY
|
||||
shell: bash
|
||||
|
||||
- uses: rustdesk-org/run-on-arch-action@d3fcfbb632b84cf7f6bc772bfaaa2c2f4f8789a8 # no release tag; commit 2026-05-26
|
||||
name: Build rustdesk
|
||||
id: vcpkg
|
||||
with:
|
||||
arch: x86_64
|
||||
distro: ubuntu18.04
|
||||
githubToken: ${{ github.token }}
|
||||
setup: |
|
||||
ls -l "${PWD}"
|
||||
ls -l /opt/artifacts/vcpkg/installed
|
||||
dockerRunArgs: |
|
||||
--volume "${PWD}:/workspace"
|
||||
--volume "/opt/artifacts:/opt/artifacts"
|
||||
shell: /bin/bash
|
||||
install: |
|
||||
apt-get update -y
|
||||
echo -e "installing deps"
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
gcc \
|
||||
git \
|
||||
g++ \
|
||||
libayatana-appindicator3-dev \
|
||||
libasound2-dev \
|
||||
libclang-10-dev \
|
||||
libgstreamer1.0-dev \
|
||||
libgstreamer-plugins-base1.0-dev \
|
||||
libgtk-3-dev \
|
||||
libpam0g-dev \
|
||||
libpulse-dev \
|
||||
libva-dev \
|
||||
libxcb-randr0-dev \
|
||||
libxcb-shape0-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libxdo-dev \
|
||||
libxfixes-dev \
|
||||
llvm-10-dev \
|
||||
nasm \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
tree \
|
||||
python3 \
|
||||
rpm \
|
||||
unzip \
|
||||
wget \
|
||||
xz-utils \
|
||||
libssl-dev
|
||||
# we have libopus compiled by us.
|
||||
apt-get remove -y libopus-dev || true
|
||||
# output devs
|
||||
ls -l ./
|
||||
tree -L 3 /opt/artifacts/vcpkg/installed
|
||||
run: |
|
||||
# disable git safe.directory
|
||||
git config --global --add safe.directory "*"
|
||||
# rust
|
||||
pushd /opt
|
||||
# do not use rustup, because memory overflow in qemu
|
||||
wget -O rust.tar.gz https://static.rust-lang.org/dist/rust-${{env.RUST_TOOLCHAIN_VERSION}}-x86_64-unknown-linux-gnu.tar.gz
|
||||
tar -zxvf rust.tar.gz > /dev/null && rm rust.tar.gz
|
||||
cd rust-${{env.RUST_TOOLCHAIN_VERSION}}-x86_64-unknown-linux-gnu && ./install.sh
|
||||
rm -rf rust-${{env.RUST_TOOLCHAIN_VERSION}}-x86_64-unknown-linux-gnu
|
||||
# edit config
|
||||
mkdir -p ~/.cargo/
|
||||
echo """
|
||||
[source.crates-io]
|
||||
registry = 'https://github.com/rust-lang/crates.io-index'
|
||||
""" > ~/.cargo/config
|
||||
cat ~/.cargo/config
|
||||
# start build
|
||||
pushd /workspace
|
||||
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
||||
# use the .so built on the runner; build.py checks it is the pinned checkout
|
||||
export DRMTAP_PREBUILT_DIR=/workspace/third_party/libdrmtap/build-pkg
|
||||
# ask build.py for the features so this line and the packaging line cannot drift
|
||||
FEATURES=$(python3 ./build.py --flutter --drm --hwcodec --unix-file-copy-paste --print-features)
|
||||
# an empty or error-shaped value would silently build a stock binary
|
||||
for want in drm drm-wake; do
|
||||
case ",$FEATURES," in
|
||||
*",$want,"*) ;;
|
||||
*) echo "::error::build.py returned no '$want' feature: $FEATURES"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
cargo build --locked --lib --features "$FEATURES" --release
|
||||
rm -rf target/release/deps target/release/build
|
||||
rm -rf ~/.cargo
|
||||
|
||||
# Setup Flutter
|
||||
# disable git safe.directory
|
||||
git config --global --add safe.directory "*"
|
||||
export PATH=/opt/flutter/bin:$PATH
|
||||
pushd /opt
|
||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz
|
||||
tar xf flutter_linux_${{ env.FLUTTER_VERSION }}-stable.tar.xz
|
||||
flutter doctor -v
|
||||
|
||||
if [[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]]; then
|
||||
pushd /opt/flutter
|
||||
git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||
popd
|
||||
fi
|
||||
|
||||
# build flutter
|
||||
pushd /workspace
|
||||
export CARGO_INCREMENTAL=0
|
||||
export DEB_ARCH=amd64
|
||||
python3 ./build.py --flutter --drm --hwcodec --unix-file-copy-paste --skip-cargo
|
||||
for name in rustdesk*??.deb; do
|
||||
mv "$name" "${name%%.deb}-x86_64.deb"
|
||||
done
|
||||
|
||||
# build.py can exit 0 on some inner failures, so check the artifact rather than the status.
|
||||
# The package name is the informed consent for consent-free capture, so a stock binary must
|
||||
# never ship under it: assert the bundled library AND the dlopen path in the binary.
|
||||
- name: Check the deb is a drm build
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Resolve by glob, not from env.VERSION: build.py names the deb from Cargo.toml, so a
|
||||
# hardcoded name fails with a bare exit 1 the first time those two drift.
|
||||
shopt -s nullglob
|
||||
debs=(rustdesk-unattended-wayland-*-x86_64.deb)
|
||||
if [ "${#debs[@]}" -ne 1 ]; then
|
||||
echo "::error::expected one rustdesk-unattended-wayland-*-x86_64.deb, found ${#debs[@]}: ${debs[*]-none}"
|
||||
exit 1
|
||||
fi
|
||||
deb="${debs[0]}"
|
||||
echo "DRM_DEB=$deb" >> "$GITHUB_ENV"
|
||||
contents="$(dpkg -c "$deb")"
|
||||
if [[ ! "$contents" =~ usr/lib/rustdesk/libdrmtap\.so\.0\.[0-9]+\.[0-9]+ ]]; then
|
||||
echo "::error::$deb has no versioned libdrmtap.so.0.x.y"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$contents" != *"usr/lib/rustdesk/libdrmtap.so.0 ->"* ]]; then
|
||||
echo "::error::$deb has no libdrmtap.so.0 soname symlink"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf /tmp/deb && dpkg-deb -R "$deb" /tmp/deb
|
||||
if ! grep -qF /usr/lib/rustdesk/libdrmtap.so.0 /tmp/deb/usr/share/rustdesk/lib/librustdesk.so; then
|
||||
echo "::error::$deb was not built with the drm feature"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Publish debian package
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
files: |
|
||||
${{ env.DRM_DEB }}
|
||||
|
||||
# No UPLOAD_ARTIFACT gate: on a PR this is the only way to get at the deb that was just built.
|
||||
# always(), because a deb that failed the check above is the one most worth downloading.
|
||||
- name: Upload deb
|
||||
if: always() && env.DRM_DEB != ''
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ env.DRM_DEB }}
|
||||
path: ${{ env.DRM_DEB }}
|
||||
|
||||
build-rustdesk-linux-sciter:
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
runs-on: ${{ matrix.job.on }}
|
||||
|
||||
Reference in New Issue
Block a user