diff --git a/.github/workflows/desktop-linux.yml b/.github/workflows/desktop-linux.yml new file mode 100644 index 0000000..4f026d4 --- /dev/null +++ b/.github/workflows/desktop-linux.yml @@ -0,0 +1,88 @@ +name: Desktop Linux + +on: + workflow_dispatch: + workflow_call: + pull_request: + paths: + - ".github/workflows/desktop-linux.yml" + - ".github/workflows/release.yml" + - "versions.env" + - "scripts/build-desktop-cli.sh" + - "scripts/common.sh" + - "scripts/fetch-sources.sh" + - "scripts/verify-desktop-cli.sh" + - "patches/x264/**" + - "tests/desktop_cli/**" + +permissions: + contents: read + +env: + FFMPEG_WORKSPACE: ${{ github.workspace }}-build + +jobs: + build: + name: Build and verify ${{ matrix.arch }} + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + runner: ubuntu-22.04 + image: quay.io/pypa/manylinux_2_28_x86_64@sha256:39df0042d5cc900b085aa25a0659368b42a0006c54c474299b785b44c1b4ff82 + - arch: arm64 + runner: ubuntu-22.04-arm + image: quay.io/pypa/manylinux_2_28_aarch64@sha256:f5f03dedf61b47d69d0742d36047db78adeee3bdb7a9910ff52304750ecf2514 + runs-on: ${{ matrix.runner }} + timeout-minutes: 120 + + steps: + - uses: actions/checkout@v4 + + - name: Install verification tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends binutils python3 ripgrep + + - name: Build desktop CLI + run: | + mkdir -p "$FFMPEG_WORKSPACE" + docker run --rm \ + -v "$PWD:/src:ro" -v "$FFMPEG_WORKSPACE:/build" \ + -w /src -e FFMPEG_WORKSPACE=/build \ + "${{ matrix.image }}" bash -ec ' + dnf upgrade -y --security + dnf install -y meson ninja-build nasm ripgrep xz-devel + export PATH="/opt/python/cp312-cp312/bin:$PATH" + git config --global --add safe.directory /src + scripts/build-desktop-cli.sh desktop-linux-${{ matrix.arch }} + ' + + - name: Verify on Ubuntu + run: | + mkdir -p artifact + tar -xzf "$FFMPEG_WORKSPACE/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz" -C artifact + WORK_ROOT="$RUNNER_TEMP/ffmpeg-verify" scripts/verify-desktop-cli.sh "$PWD/artifact" + + - name: Verify on glibc 2.28 + run: | + docker run --rm -v "$PWD:/src:ro" \ + almalinux:8@sha256:8b469a3a78515e8a18ea8fc727e6a3679e1d0c5ba6f58d5b30be3d0d9b86cffe \ + bash -ec ' + dnf upgrade -y --security + dnf install -y python3.12 libstdc++ zlib xz-libs + python3.12 /src/tests/desktop_cli/verify_media.py \ + --ffmpeg /src/artifact/ffmpeg --ffprobe /src/artifact/ffprobe \ + --work-dir /tmp/verify --target linux-${{ matrix.arch }} + ' + + - name: Upload desktop assets + uses: actions/upload-artifact@v4 + with: + name: release-desktop-linux-${{ matrix.arch }} + path: | + ${{ env.FFMPEG_WORKSPACE }}/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz + ${{ env.FFMPEG_WORKSPACE }}/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz.sha256 + ${{ env.FFMPEG_WORKSPACE }}/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz.manifest.env + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0df134..d294f0f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,44 +117,8 @@ jobs: if-no-files-found: error desktop-linux: - name: Desktop Linux ${{ matrix.arch }} - strategy: - fail-fast: false - matrix: - include: - - { arch: x64, runner: ubuntu-22.04 } - - { arch: arm64, runner: ubuntu-22.04-arm } - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - - steps: - - uses: actions/checkout@v4 - - - name: Install host tools and libraries - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - autoconf automake binutils curl g++ git libtool make nasm pkg-config ripgrep \ - meson ninja-build python3 xz-utils zlib1g-dev - - - name: Build desktop CLI - run: scripts/build-desktop-cli.sh desktop-linux-${{ matrix.arch }} - - - name: Verify desktop CLI - run: | - mkdir -p artifact - tar -xzf "$FFMPEG_WORKSPACE/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz" -C artifact - scripts/verify-desktop-cli.sh "$PWD/artifact" - - - name: Upload desktop assets - uses: actions/upload-artifact@v4 - with: - name: release-desktop-linux-${{ matrix.arch }} - path: | - ${{ env.FFMPEG_WORKSPACE }}/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz - ${{ env.FFMPEG_WORKSPACE }}/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz.sha256 - ${{ env.FFMPEG_WORKSPACE }}/dist/desktop/linux-${{ matrix.arch }}/ffmpeg.tar.gz.manifest.env - if-no-files-found: error + name: Desktop Linux + uses: ./.github/workflows/desktop-linux.yml desktop-macos-universal: name: Desktop macOS universal diff --git a/scripts/build-desktop-cli.sh b/scripts/build-desktop-cli.sh index 2203a03..45281b5 100755 --- a/scripts/build-desktop-cli.sh +++ b/scripts/build-desktop-cli.sh @@ -78,6 +78,18 @@ build_one_desktop() { local extra_cflags="" local extra_ldflags="" local ffmpeg_cross_flags=() + if [ "$DESKTOP_OS" = "linux" ]; then + local bzip2_src="$target_root/bzip2-$BZIP2_VERSION" + local bzip2_archive="$DOWNLOADS_ROOT/bzip2-$BZIP2_VERSION.tar.gz" + curl -fL "https://sourceware.org/pub/bzip2/bzip2-$BZIP2_VERSION.tar.gz" -o "$bzip2_archive" + verify_sha256 "$bzip2_archive" "$BZIP2_SHA256" + tar -xf "$bzip2_archive" -C "$target_root" + extra_cflags="-fstack-protector-strong -D_FORTIFY_SOURCE=2 -I$bzip2_src" + extra_ldflags="-L$bzip2_src -Wl,-z,relro,-z,now" + make -C "$bzip2_src" -j"$JOBS" libbz2.a \ + CC="$cc" AR="$ar" RANLIB="$ranlib" CFLAGS="-O2 -fPIC $extra_cflags" + ffmpeg_cross_flags+=(--enable-pic --extra-ldexeflags=-pie --enable-bzlib --enable-lzma --disable-xlib) + fi if [ "$DESKTOP_OS" = "darwin" ]; then export MACOSX_DEPLOYMENT_TARGET="$DESKTOP_MACOS_MIN_VERSION" extra_cflags="-arch $DESKTOP_ARCH" @@ -263,10 +275,18 @@ case "$target" in ;; *) archive="$out/ffmpeg.tar.gz" - tar -C "$out" -czf "$archive" ffmpeg ffprobe dav1d-COPYING + files=(ffmpeg ffprobe dav1d-COPYING) + if [[ "$target" == desktop-linux-* ]]; then + cp "$BUILD_ROOT/$target/bzip2-$BZIP2_VERSION/LICENSE" "$out/bzip2-LICENSE" + files+=(bzip2-LICENSE) + fi + tar -C "$out" -czf "$archive" "${files[@]}" ;; esac sha256_file "$archive" > "$archive.sha256" write_manifest "$archive.manifest.env" "TARGET=$target" "ARTIFACT=$archive" \ "DAV1D_VERSION=$DAV1D_VERSION" "DAV1D_SHA256=$DAV1D_SHA256" +if [[ "$target" == desktop-linux-* ]]; then + printf 'BZIP2_VERSION=%s\nBZIP2_SHA256=%s\n' "$BZIP2_VERSION" "$BZIP2_SHA256" >> "$archive.manifest.env" +fi log "built desktop artifact $archive" diff --git a/scripts/verify-desktop-cli.sh b/scripts/verify-desktop-cli.sh index ed32e08..1979e30 100755 --- a/scripts/verify-desktop-cli.sh +++ b/scripts/verify-desktop-cli.sh @@ -55,37 +55,31 @@ fi if command -v ldd >/dev/null 2>&1 && [ "$(uname -s)" = "Linux" ]; then ldd "$ffmpeg" > "$WORK_ROOT/desktop-ffmpeg-ldd.txt" - if rg -q 'lib(x264|zimg|dav1d)' "$WORK_ROOT/desktop-ffmpeg-ldd.txt"; then - die "desktop ffmpeg must link pinned x264/zimg/dav1d statically" + if rg -q 'lib(x264|zimg|dav1d|bz2)' "$WORK_ROOT/desktop-ffmpeg-ldd.txt"; then + die "desktop ffmpeg must link pinned x264/zimg/dav1d/bzip2 statically" fi fi -if command -v objdump >/dev/null 2>&1 && [ "$(uname -s)" = "Linux" ]; then +if [ "$(uname -s)" = "Linux" ]; then + require_cmd readelf for binary in "$ffmpeg" "$ffprobe"; do - glibc_report="$WORK_ROOT/$(basename "$binary")-glibc-symbols.txt" - objdump -T "$binary" | rg -o 'GLIBC_[0-9]+\.[0-9]+' | sort -Vu > "$glibc_report" || true - python3 - "$binary" "$glibc_report" "$DESKTOP_LINUX_MAX_GLIBC" <<'PY' + report="$WORK_ROOT/$(basename "$binary")-elf.txt" + LC_ALL=C readelf --wide --version-info "$binary" > "$report" + python3 - "$binary" "$report" "$DESKTOP_LINUX_MAX_GLIBC" <<'PYTHON' +import re import sys +from pathlib import Path -binary, report, max_allowed = sys.argv[1:] +binary, report, maximum = sys.argv[1:] +text = Path(report).read_text(encoding="utf-8") -def parse(version): - major, minor = version.split(".", 1) - return int(major), int(minor) +def numeric(value): + return tuple(map(int, value.split("."))) -with open(report, encoding="utf-8") as handle: - versions = sorted( - {line.strip().removeprefix("GLIBC_") for line in handle if line.strip()}, - key=parse, - ) -if not versions: - raise SystemExit(0) -highest = versions[-1] -if parse(highest) > parse(max_allowed): - raise SystemExit( - f"{binary} requires GLIBC_{highest}; max allowed is GLIBC_{max_allowed}" - ) -PY +versions = re.findall(r"\bGLIBC_([0-9.]+)", text) +if not versions or max(map(numeric, versions)) > numeric(maximum): + raise SystemExit(f"{binary}: missing or unsupported GLIBC versions; maximum is {maximum}") +PYTHON done fi diff --git a/versions.env b/versions.env index d9273c5..8c86d07 100644 --- a/versions.env +++ b/versions.env @@ -15,11 +15,14 @@ DAV1D_VERSION=1.5.4 DAV1D_URL=https://downloads.videolan.org/pub/videolan/dav1d/1.5.4/dav1d-1.5.4.tar.xz DAV1D_SHA256=686616b7c69eb88d44459391ab25cac13b6647a3b288835c5784e71c1514a5c5 +BZIP2_VERSION=1.0.8 +BZIP2_SHA256=ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269 + ANDROID_API=26 ANDROID_NDK_VERSION=28.2.13676358 IOS_MIN_VERSION=14.0 XCODE_VERSION=26.2 -DESKTOP_LINUX_MAX_GLIBC=2.35 +DESKTOP_LINUX_MAX_GLIBC=2.28 DESKTOP_MACOS_MIN_VERSION=13.3 MOBILE_TARGETS="android-arm64 android-armv7 ios-device-arm64 ios-sim-arm64"