diff --git a/.gitea/workflows/build-linux-release.yml b/.gitea/workflows/build-linux-release.yml new file mode 100644 index 0000000..4d318d2 --- /dev/null +++ b/.gitea/workflows/build-linux-release.yml @@ -0,0 +1,49 @@ +name: Build Linux Release + +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + build-linux: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install host tools + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + bzip2 \ + tar \ + file + + - name: Build release package + run: | + chmod +x scripts/*.sh + ./scripts/package-linux-release.sh + + - name: Verify package + run: | + test -s dist-linux/chengnan-linux-x86_64.tar.gz + sha256sum -c dist-linux/chengnan-linux-x86_64.tar.gz.sha256 + tar -tzf dist-linux/chengnan-linux-x86_64.tar.gz | grep -E '^chengnan-linux-x86_64/(bin/chengnan|run-chengnan.sh)$' + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: chengnan-linux-x86_64 + path: | + dist-linux/chengnan-linux-x86_64.tar.gz + dist-linux/chengnan-linux-x86_64.tar.gz.sha256 + if-no-files-found: error diff --git a/README.md b/README.md index 835669e..f101d2e 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,29 @@ cd /root/chengnan build-linux/chengnan ``` +### 生成发布包 + +如需生成可分发的 Linux x86_64 发布包,执行: + +```bash +cd /root/chengnan +./scripts/package-linux-release.sh +``` + +脚本会先执行 Release 构建,再将程序、项目内 Qt/OpenCV 运行时库、Qt 插件和启动脚本打包到: + +```text +dist-linux/chengnan-linux-x86_64.tar.gz +dist-linux/chengnan-linux-x86_64.tar.gz.sha256 +``` + +解压后在有 X11 / Wayland 图形会话的 Linux 桌面环境中运行: + +```bash +tar -xzf chengnan-linux-x86_64.tar.gz +./chengnan-linux-x86_64/run-chengnan.sh +``` + ### 分步构建 如果希望先准备依赖,再单独构建: diff --git a/scripts/package-linux-release.sh b/scripts/package-linux-release.sh new file mode 100755 index 0000000..d4c6378 --- /dev/null +++ b/scripts/package-linux-release.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_DIR="$ROOT_DIR/tools/linux-build-env" +BUILD_DIR="$ROOT_DIR/build-linux" +DIST_DIR="$ROOT_DIR/dist-linux" +PACKAGE_ROOT="$DIST_DIR/chengnan-linux-x86_64" +APP_BIN="$PACKAGE_ROOT/bin/chengnan" +LIB_DIR="$PACKAGE_ROOT/lib" +PLUGIN_DIR="$PACKAGE_ROOT/plugins" +QT_CONF="$PACKAGE_ROOT/bin/qt.conf" +ARCHIVE="$DIST_DIR/chengnan-linux-x86_64.tar.gz" + +"$ROOT_DIR/scripts/build-linux-binary.sh" + +rm -rf "$PACKAGE_ROOT" "$ARCHIVE" +mkdir -p "$PACKAGE_ROOT/bin" "$LIB_DIR" "$PLUGIN_DIR" + +"$ENV_DIR/bin/cmake" --install "$BUILD_DIR" --prefix "$PACKAGE_ROOT" + +if [[ ! -x "$APP_BIN" ]]; then + echo "[package] 未找到可执行文件:$APP_BIN" >&2 + exit 1 +fi + +copy_lib() { + local lib="$1" + if [[ -f "$lib" ]]; then + cp -L -n "$lib" "$LIB_DIR/" + fi +} + +# 只打包项目内二进制环境中的依赖,避免把系统 /lib、/usr/lib 复制进制品。 +while IFS= read -r lib; do + case "$lib" in + "$ENV_DIR"/*) copy_lib "$lib" ;; + esac +done < <(LD_LIBRARY_PATH="$ENV_DIR/lib:${LD_LIBRARY_PATH:-}" ldd "$APP_BIN" | awk '/=>/ {print $3}' | sort -u) + +# Qt GUI 程序运行需要平台插件;同时带上常用图片格式插件,保证 jpg/png 等格式可打开。 +for subdir in platforms imageformats xcbglintegrations platformthemes styles iconengines; do + if [[ -d "$ENV_DIR/plugins/$subdir" ]]; then + mkdir -p "$PLUGIN_DIR/$subdir" + cp -L "$ENV_DIR/plugins/$subdir"/*.so "$PLUGIN_DIR/$subdir/" 2>/dev/null || true + fi +done + +# 平台插件自身还有二级依赖,继续收集项目内环境中的 .so。 +if [[ -d "$PLUGIN_DIR" ]]; then + while IFS= read -r plugin; do + while IFS= read -r lib; do + case "$lib" in + "$ENV_DIR"/*) copy_lib "$lib" ;; + esac + done < <(LD_LIBRARY_PATH="$ENV_DIR/lib:$LIB_DIR:${LD_LIBRARY_PATH:-}" ldd "$plugin" | awk '/=>/ {print $3}' | sort -u || true) + done < <(find "$PLUGIN_DIR" -type f -name '*.so' | sort) +fi + +cat > "$QT_CONF" <<'QTCFG' +[Paths] +Plugins = ../plugins +QTCFG + +cat > "$PACKAGE_ROOT/run-chengnan.sh" <<'RUNNER' +#!/usr/bin/env bash +set -euo pipefail +APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +export LD_LIBRARY_PATH="$APP_DIR/lib:${LD_LIBRARY_PATH:-}" +export QT_PLUGIN_PATH="$APP_DIR/plugins" +exec "$APP_DIR/bin/chengnan" "$@" +RUNNER +chmod +x "$PACKAGE_ROOT/run-chengnan.sh" + +if command -v patchelf >/dev/null 2>&1; then + patchelf --set-rpath '$ORIGIN/../lib' "$APP_BIN" || true +elif [[ -x "$ENV_DIR/bin/patchelf" ]]; then + "$ENV_DIR/bin/patchelf" --set-rpath '$ORIGIN/../lib' "$APP_BIN" || true +else + echo "[package] 未找到 patchelf,制品可通过 run-chengnan.sh 设置 LD_LIBRARY_PATH 运行。" +fi + +find "$PACKAGE_ROOT" -type f -name '*.so*' -exec chmod 0644 {} + +chmod +x "$APP_BIN" + +tar -C "$DIST_DIR" -czf "$ARCHIVE" "$(basename "$PACKAGE_ROOT")" + +if [[ ! -s "$ARCHIVE" ]]; then + echo "[package] 打包失败:$ARCHIVE 为空或不存在" >&2 + exit 1 +fi + +sha256sum "$ARCHIVE" > "$ARCHIVE.sha256" + +echo "[package] 发布包:$ARCHIVE" +echo "[package] 校验文件:$ARCHIVE.sha256" diff --git a/scripts/setup-linux-binary-deps.sh b/scripts/setup-linux-binary-deps.sh index d150b92..c582faf 100755 --- a/scripts/setup-linux-binary-deps.sh +++ b/scripts/setup-linux-binary-deps.sh @@ -20,7 +20,7 @@ fi if [[ ! -x "$ENV_DIR/bin/cmake" || ! -x "$ENV_DIR/bin/ninja" || ! -x "$ENV_DIR/bin/clang++" ]]; then echo "[setup] 在项目目录内创建 Linux 二进制构建环境..." "$MICROMAMBA_BIN" create -y -p "$ENV_DIR" -c conda-forge \ - cmake make clangxx qt-main opencv pkg-config + cmake ninja make clangxx qt-main opencv pkg-config patchelf fi # README 中约定的路径,使用软链接指向隔离环境,便于 CMake 查找。