ci: add Linux build image workflow

This commit is contained in:
dijunkun
2026-07-21 21:54:03 +08:00
parent 59f77c2820
commit 7ff6c8a4ad
12 changed files with 518 additions and 28 deletions
+153
View File
@@ -0,0 +1,153 @@
# syntax=docker/dockerfile:1.7
ARG BASE_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu22.04
FROM ${BASE_IMAGE}
ARG TARGETARCH
ARG XMAKE_VERSION=3.0.9
ARG RUST_VERSION=1.92.0
ARG RUSTUP_VERSION=1.28.2
LABEL org.opencontainers.image.title="CrossDesk Linux build environment" \
org.opencontainers.image.description="Ubuntu 22.04 build environment for CrossDesk" \
org.opencontainers.image.source="https://github.com/kunkundi/crossdesk" \
org.opencontainers.image.licenses="GPL-3.0-only"
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
RUSTUP_HOME=/opt/rustup \
CARGO_HOME=/opt/cargo \
XMAKE_GLOBALDIR=/data \
XMAKE_ROOT=y \
CUDA_PATH=/usr/local/cuda \
PATH=/root/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
autoconf \
automake \
binutils \
bison \
build-essential \
ca-certificates \
ccache \
cmake \
curl \
dbus \
dpkg-dev \
file \
flex \
gettext \
git \
libasound2-dev \
libdbus-1-dev \
libdrm-dev \
libegl1-mesa-dev \
libffi-dev \
libfontconfig1-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libmount-dev \
libpulse-dev \
libreadline-dev \
libsndio-dev \
libssl-dev \
libtool \
libx11-dev \
libxcb-xfixes0-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-shm0-dev \
libxcb-xinerama0-dev \
libxcb-xkb-dev \
libxcb-xtest0-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxft-dev \
libxi-dev \
libxinerama-dev \
libxkbcommon-dev \
libxrandr-dev \
libxrender-dev \
libxtst-dev \
libxv-dev \
make \
nasm \
ninja-build \
patch \
patchelf \
perl \
pkg-config \
python3 \
python3-pip \
unzip \
xutils-dev \
xz-utils \
yasm \
zip \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
RUN export GITHUB_ACTIONS=true \
&& curl -fsSL https://xmake.io/shget.text \
| bash -s -- "v${XMAKE_VERSION}" \
&& source ~/.xmake/profile \
&& xmake --version | grep -F "v${XMAKE_VERSION}"
RUN case "${TARGETARCH}" in \
amd64) \
rustup_arch="x86_64-unknown-linux-gnu"; \
rustup_sha256="20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c" \
;; \
arm64) \
rustup_arch="aarch64-unknown-linux-gnu"; \
rustup_sha256="e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c" \
;; \
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac \
&& curl --proto '=https' --tlsv1.2 --fail --show-error --location \
"https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustup_arch}/rustup-init" \
-o /tmp/rustup-init \
&& echo "${rustup_sha256} /tmp/rustup-init" | sha256sum --check - \
&& chmod 0755 /tmp/rustup-init \
&& /tmp/rustup-init -y --no-modify-path --profile minimal \
--default-toolchain "${RUST_VERSION}" \
&& rm -f /tmp/rustup-init \
&& rustc --version \
&& cargo --version
WORKDIR /tmp/crossdesk-dependencies
COPY xmake.lua ./xmake.lua
COPY xmake ./xmake
COPY thirdparty ./thirdparty
COPY submodules/xmake.lua ./submodules/xmake.lua
COPY submodules/minirtc/xmake.lua ./submodules/minirtc/xmake.lua
COPY submodules/minirtc/thirdparty ./submodules/minirtc/thirdparty
# Configure the real project manifests so the image cache stays aligned with
# CrossDesk's exact dependency graph. Source files are intentionally excluded
# from the Docker context; source-only changes therefore reuse this layer.
RUN xmake repo -u --root \
&& xmake f -m release --USE_CUDA=true --root -y \
&& rm -rf /data/.xmake/cache /tmp/crossdesk-dependencies \
&& mkdir -p /workspace
WORKDIR /workspace
COPY docker/linux-build/verify-build-image.sh \
/usr/local/bin/verify-crossdesk-build-image
COPY docker/linux-build/verify-glibc-baseline.sh \
/usr/local/bin/verify-crossdesk-glibc-baseline
RUN chmod 0755 \
/usr/local/bin/verify-crossdesk-build-image \
/usr/local/bin/verify-crossdesk-glibc-baseline \
&& verify-crossdesk-build-image
CMD ["bash"]
+41
View File
@@ -0,0 +1,41 @@
# CrossDesk Linux 构建镜像
这个目录维护 CrossDesk 专用的 Ubuntu 22.04 构建环境。镜像包含 xmake、
Rust、Linux 开发库以及根据项目 `xmake.lua` 提前编译好的依赖。
## 发布方式
推送影响镜像的文件到 `ci/linux-build-image` 分支时,
`build-linux-image.yml` 会在 GitHub 原生 amd64/arm64 runner 上分别构建和验证,
全部通过后再合并为同一个 multi-arch 镜像:
```text
crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
```
Docker 会根据运行机器自动选择正确架构。amd64 变体以
`nvidia/cuda:12.6.3-devel-ubuntu22.04` 为基础;arm64 变体不包含当前构建
用不到的 CUDA SDK。两个变体都使用 Ubuntu 22.04/glibc 2.35。
因此该镜像生成的 Linux 安装包最低支持 Ubuntu 22.04,不再保证兼容
Ubuntu 20.04。
每次成功发布还会生成 `sha-<完整提交 SHA>`,用于精确回滚;`latest` 和上面的
工具链版本 tag 只会在两个架构都验证成功后更新。
## GitHub 配置
在仓库的 Actions secrets 中配置:
- `DOCKERHUB_USERNAME`:有权推送 `crossdesk/ubuntu22.04` 的 Docker Hub 用户名
- `DOCKERHUB_TOKEN`:该用户的 Docker Hub access token
工作流文件进入默认分支后,也可以从 Actions 页面手动运行。为了避免首次发布
新 tag 时应用构建先于镜像完成,常规 `build.yml` 不响应
`ci/linux-build-image` 分支的 push。第一次启用时,应先推送该分支并等待镜像
发布成功,再把应用构建工作流的修改合并到 `main`
## 更新依赖
修改 Dockerfile、xmake 清单、本地包配方或子模块中的包配方后,推送该分支即可。
升级工具链时应同时修改 Dockerfile 的版本参数、工作流中的 `STACK_TAG`,以及
常规构建工作流引用的镜像 tag。
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_ARCH="${EXPECTED_ARCH:-}"
EXPECTED_XMAKE_VERSION="${EXPECTED_XMAKE_VERSION:-3.0.9}"
EXPECTED_RUST_VERSION="${EXPECTED_RUST_VERSION:-1.92.0}"
# shellcheck disable=SC1091
source /etc/os-release
if [[ "${ID}" != "ubuntu" || "${VERSION_ID}" != "22.04" ]]; then
echo "Expected Ubuntu 22.04, found ${ID:-unknown} ${VERSION_ID:-unknown}" >&2
exit 1
fi
GLIBC_VERSION="$(getconf GNU_LIBC_VERSION | awk '{print $2}')"
if [[ "${GLIBC_VERSION}" != "2.35" ]]; then
echo "Expected glibc 2.35, found ${GLIBC_VERSION}" >&2
exit 1
fi
ACTUAL_ARCH="$(dpkg --print-architecture)"
if [[ -n "${EXPECTED_ARCH}" && "${ACTUAL_ARCH}" != "${EXPECTED_ARCH}" ]]; then
echo "Expected architecture ${EXPECTED_ARCH}, found ${ACTUAL_ARCH}" >&2
exit 1
fi
XMAKE_VERSION_OUTPUT="$(xmake --version 2>&1)"
grep -F "v${EXPECTED_XMAKE_VERSION}" <<<"${XMAKE_VERSION_OUTPUT}" >/dev/null
rustc --version | grep -F "rustc ${EXPECTED_RUST_VERSION}" >/dev/null
cargo --version >/dev/null
cmake --version >/dev/null
g++ --version >/dev/null
pkg-config --exists xft
for package_path in \
"s/slint" \
"l/libdatachannel" \
"o/openssl3" \
"s/spdlog"; do
if [[ ! -d "${XMAKE_GLOBALDIR}/.xmake/packages/${package_path}" ]]; then
echo "Prebuilt xmake package is missing: ${package_path}" >&2
exit 1
fi
done
if ! find "${XMAKE_GLOBALDIR}/.xmake/packages/s/slint" \
-type f -name 'slint-compiler*' -print -quit | grep -q .; then
echo "The prebuilt Slint compiler is missing" >&2
exit 1
fi
if [[ "${ACTUAL_ARCH}" == "amd64" && ! -f "${CUDA_PATH}/include/cuda.h" ]]; then
echo "The amd64 image is missing CUDA headers under ${CUDA_PATH}" >&2
exit 1
fi
echo "Verified CrossDesk build image: Ubuntu ${VERSION_ID}, ${ACTUAL_ARCH}, glibc ${GLIBC_VERSION}"
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "Usage: $0 <ELF-file-or-directory> [maximum-glibc-version]" >&2
exit 2
fi
INPUT_PATH="$1"
MAXIMUM_VERSION="${2:-2.35}"
verify_elf() {
local elf_file="$1"
local version=""
local highest_version="none"
local -a required_versions=()
mapfile -t required_versions < <(
readelf --version-info "${elf_file}" \
| grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' \
| sed 's/^GLIBC_//' \
| sort -Vu \
|| true
)
for version in "${required_versions[@]}"; do
if dpkg --compare-versions "${version}" gt "${MAXIMUM_VERSION}"; then
echo "${elf_file} requires GLIBC_${version}; maximum allowed is GLIBC_${MAXIMUM_VERSION}" >&2
return 1
fi
done
if [[ ${#required_versions[@]} -gt 0 ]]; then
highest_version="${required_versions[-1]}"
fi
echo "Verified ${elf_file}: highest required glibc version is ${highest_version}"
}
if [[ -f "${INPUT_PATH}" ]]; then
if ! readelf -h "${INPUT_PATH}" >/dev/null 2>&1; then
echo "Not an ELF file: ${INPUT_PATH}" >&2
exit 1
fi
verify_elf "${INPUT_PATH}"
exit 0
fi
if [[ ! -d "${INPUT_PATH}" ]]; then
echo "File or directory does not exist: ${INPUT_PATH}" >&2
exit 1
fi
ELF_COUNT=0
while IFS= read -r -d '' candidate; do
if readelf -h "${candidate}" >/dev/null 2>&1; then
verify_elf "${candidate}"
ELF_COUNT=$((ELF_COUNT + 1))
fi
done < <(find "${INPUT_PATH}" -type f -print0)
if [[ ${ELF_COUNT} -eq 0 ]]; then
echo "No ELF files found under ${INPUT_PATH}" >&2
exit 1
fi