diff --git a/CMakeLists.txt b/CMakeLists.txt index 5df248c..19efa08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,9 +350,6 @@ if(FPC_BUILD_GUI) "${FPC_QT_PREFIX}/bin/Qt6ShaderTools.dll" "${FPC_DIST_DIR}/") endif() # FpvRelay QML 模块目录(生成在构建目录,拷贝到 dist) - list(APPEND FPC_GUI_STAGE - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_BINARY_DIR}/FpvRelay" "${FPC_DIST_DIR}/FpvRelay") # qt.conf:让 Qt 从程序目录找 QML file(WRITE "${CMAKE_BINARY_DIR}/fpc_qt.conf" "[Paths]\nPrefix=.\nQmlImportsPath=qml\n") list(APPEND FPC_GUI_STAGE @@ -370,6 +367,10 @@ if(FPC_BUILD_GUI) endforeach() endif() endif() + # 所有平台都把 FpvRelay QML 模块目录拷到 dist,保证从 dist 直接运行能找到模块 + list(APPEND FPC_GUI_STAGE + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_BINARY_DIR}/FpvRelay" "${FPC_DIST_DIR}/FpvRelay") # 依赖 DLL / 配置 / 密钥的汇总也挂到 GUI 目标,避免只构建 GUI 时缺失 add_custom_command(TARGET fpv-relay-gui POST_BUILD ${FPC_STAGE_CMDS} ${FPC_GUI_STAGE}) endif() diff --git a/packaging/build-deb.sh b/packaging/build-deb.sh old mode 100644 new mode 100755 index c1c3dbf..3d0f3b6 --- a/packaging/build-deb.sh +++ b/packaging/build-deb.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # ============================================================ -# 打包 C++ 项目为 .deb(自包含:可执行文件 + 运行库,装到 /opt/fpv-relay) +# 打包为 .deb(安装到 /opt/fpv-relay) +# - CLI 的运行库通过 Depends 声明(由 apt 安装) +# - GUI 自带的 Qt6 运行库一并放进 /opt/fpv-relay/lib # 用法: # ./packaging/build-deb.sh # 仅命令行版 # ./packaging/build-deb.sh --with-gui # 含 GUI(需 QT_PREFIX) @@ -12,40 +14,50 @@ REPO="$(cd "$(dirname "$0")/.." && pwd)" WITH_GUI=0 [ "$1" = "--with-gui" ] && WITH_GUI=1 -VERSION="$(cd "$REPO" && git describe --tags --always 2>/dev/null | tr -d 'v' || date +%Y%m%d)" +VERSION="$(cd "$REPO" && git describe --tags --always 2>/dev/null | tr -d 'v' || true)" [ -z "$VERSION" ] && VERSION="$(date +%Y%m%d)" +case "$VERSION" in + [0-9]*) ;; + *) VERSION="0.0.0+git$(date +%Y%m%d).${VERSION}" ;; +esac DEB_VER="${VERSION}-1" PKG="fpv-relay" ARCH="amd64" -DIST="${REPO}/dist" +OUT="${REPO}/dist" ROOT="$(mktemp -d)" APP="${ROOT}/opt/fpv-relay" echo "==> 构建(CLI)" cd "${REPO}" -cmake --preset cli >/dev/null -cmake --build --preset cli -j"$(nproc)" >/dev/null +cmake --preset linux-cli >/dev/null +cmake --build --preset linux-cli -j"$(nproc)" >/dev/null +GUI_DIST="" if [ "$WITH_GUI" = "1" ]; then - : "${QT_PREFIX:?--with-gui 需要设置 QT_PREFIX}" + : "${QT_PREFIX:?--with-gui 需要设置 QT_PREFIX 指向 Qt 安装目录}" echo "==> 构建(GUI)" - cmake --preset gui >/dev/null - cmake --build --preset gui -j"$(nproc)" >/dev/null + cmake --preset linux-gui -DFPC_QT_PREFIX="${QT_PREFIX}" >/dev/null + cmake --build --preset linux-gui -j"$(nproc)" >/dev/null + GUI_DIST="${REPO}/build-gui/dist" fi echo "==> 准备 ${APP}" mkdir -p "${APP}/bin" "${APP}/lib" "${APP}/tools" "${ROOT}/DEBIAN" "${ROOT}/usr/bin" -cp "${REPO}/build/fpv-relay" "${APP}/bin/" -cp "${REPO}/tools/play-udp.sh" "${APP}/tools/" 2>/dev/null || true -cp "${REPO}/config.example.yaml" "${APP}/" +cp "${REPO}/build/dist/fpv-relay" "${APP}/bin/" +cp "${REPO}/config.example.yaml" "${APP}/" 2>/dev/null || true +[ -f "${REPO}/ass/gs.key" ] && cp "${REPO}/ass/gs.key" "${APP}/gs.key" +cp -r "${REPO}/tools/." "${APP}/tools/" 2>/dev/null || true cp "${REPO}/README.md" "${APP}/" 2>/dev/null || true +# 只打包这些非系统库(Qt 自带;其余系统库由 Depends 提供) bundle_libs() { - ldd "$1" 2>/dev/null | awk '{print $3}' | grep '^/' | while read -r lib; do + local lp="${LD_LIBRARY_PATH}" + [ -n "${QT_PREFIX:-}" ] && lp="${QT_PREFIX}/lib:${lp}" + LD_LIBRARY_PATH="$lp" ldd "$1" 2>/dev/null | awk '{print $3}' | grep '^/' | while read -r lib; do base="$(basename "$lib")" case "$base" in - libc.so*|libm.so*|libpthread*|librt*|libdl*|libgcc_s*|libstdc++*|ld-linux*|libnsl*|libresolv*) continue ;; - libgst*|libglib-2.0*|libgobject-2.0*|libgmodule-2.0*|libgio-2.0*|libgthread-2.0*|liborc-0.4*|libav*|libswscale*|libswresample*|libpostproc*|libx264*|libx265*|libvpx*) continue ;; + libQt6*|libicu*) ;; + *) continue ;; esac if [ ! -e "${APP}/lib/${base}" ]; then cp -L "$lib" "${APP}/lib/${base}" @@ -53,29 +65,50 @@ bundle_libs() { fi done } -echo "==> 收集运行库" -bundle_libs "${APP}/bin/fpv-relay" + +# CLI 运行库一律用系统包,不打包 +DEPS="libc6, libstdc++6, libgcc-s1, libpcap0.8 | libpcap0.8t64, libsodium23, libnl-3-200, libnl-route-3-200, libcap2, libgcrypt20, libgpg-error0, libibverbs1, liblz4-1, liblzma5, libzstd1, libsystemd0, libdbus-1-3, iw, ffmpeg" if [ "$WITH_GUI" = "1" ]; then echo "==> 打包 GUI 与 Qt 运行环境" - cp "${REPO}/build-gui/fpv-relay-gui" "${APP}/bin/" - bundle_libs "${APP}/bin/fpv-relay-gui" - cp -L "${QT_PREFIX}/lib/"libQt6*.so* "${APP}/lib/" 2>/dev/null || true - for l in "${APP}"/lib/libQt6*.so*; do - [ -e "$l" ] && bundle_libs "$l" - done - cp -rL "${REPO}/build-gui/FpvRelay" "${APP}/bin/" 2>/dev/null || true - mkdir -p "${APP}/plugins/platforms" "${APP}/plugins/xcbglintegrations" "${APP}/qml" + cp "${GUI_DIST}/fpv-relay-gui" "${APP}/bin/" + cp -rL "${GUI_DIST}/FpvRelay" "${APP}/bin/" 2>/dev/null || true + + mkdir -p "${APP}/plugins/platforms" "${APP}/plugins/xcbglintegrations" \ + "${APP}/plugins/imageformats" "${APP}/plugins/iconengines" "${APP}/qml" for p in libqxcb.so libqminimal.so libqoffscreen.so; do [ -f "${QT_PREFIX}/plugins/platforms/${p}" ] && cp -L "${QT_PREFIX}/plugins/platforms/${p}" "${APP}/plugins/platforms/" done cp -L "${QT_PREFIX}/plugins/xcbglintegrations/"*.so "${APP}/plugins/xcbglintegrations/" 2>/dev/null || true - for m in QtQml QtQuick Qt Qt5Compat; do - [ -d "${QT_PREFIX}/qml/${m}" ] && cp -rL "${QT_PREFIX}/qml/${m}" "${APP}/qml/" + cp -L "${QT_PREFIX}/plugins/imageformats/"*.so "${APP}/plugins/imageformats/" 2>/dev/null || true + cp -L "${QT_PREFIX}/plugins/iconengines/"*.so "${APP}/plugins/iconengines/" 2>/dev/null || true + + # 只拷 FluentUI/本项目用到的 QML 模块(避免把整个 Qt qml 目录塞进去) + cp -rL "${QT_PREFIX}/qml/QtQml" "${APP}/qml/" + mkdir -p "${APP}/qml/Qt/labs" "${APP}/qml/Qt5Compat" "${APP}/qml/QtQuick" + cp -rL "${QT_PREFIX}/qml/Qt/labs/qmlmodels" "${APP}/qml/Qt/labs/" 2>/dev/null || true + cp -rL "${QT_PREFIX}/qml/Qt/labs/folderlistmodel" "${APP}/qml/Qt/labs/" 2>/dev/null || true + cp -rL "${QT_PREFIX}/qml/Qt5Compat/GraphicalEffects" "${APP}/qml/Qt5Compat/" 2>/dev/null || true + find "${QT_PREFIX}/qml/QtQuick" -maxdepth 1 -type f -exec cp -L {} "${APP}/qml/QtQuick/" \; + for sub in Controls Dialogs Layouts Shapes Templates Window; do + [ -d "${QT_PREFIX}/qml/QtQuick/${sub}" ] && cp -rL "${QT_PREFIX}/qml/QtQuick/${sub}" "${APP}/qml/QtQuick/" done + + # 按实际依赖收集 Qt6/ICU 库 + bundle_libs "${APP}/bin/fpv-relay-gui" find "${APP}/qml" "${APP}/plugins" -name "*.so" 2>/dev/null | while read -r so; do bundle_libs "$so" done + + # qt.conf:让 Qt 从安装目录找插件与 QML + cat > "${APP}/bin/qt.conf" <<'EOF' +[Paths] +Prefix=.. +Plugins=plugins +QmlImports=qml +EOF + + DEPS="${DEPS}, libglib2.0-0 | libglib2.0-0t64, libgl1, libegl1, libglx0, libopengl0, libglvnd0, libx11-6, libxcb1, libxau6, libxdmcp6, libxkbcommon0, libxkbcommon-x11-0, libfontconfig1, libfreetype6, libexpat1, libpng16-16 | libpng16-16t64, zlib1g, libbz2-1.0, libbrotli1, libpcre2-8-0, libgssapi-krb5-2, libkrb5-3, libk5crypto3, libkrb5support0, libkeyutils1, libcom-err2, libbsd0, libmd0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render-util0, libxcb-shape0, libxcb-xinerama0, libxcb-xkb1" fi cat > "${APP}/run.sh" <<'EOF' @@ -122,16 +155,15 @@ Architecture: ${ARCH} Maintainer: fpv-relay Section: net Priority: optional -Depends: libc6, iw, ffmpeg +Depends: ${DEPS} Description: Multi-input FPV video relay (capture + low-latency forward) - RTL8812AU capture (wfb-ng), low-latency UDP forwarding, DVR recording, - RTSP output, mavlink uplink/downlink. Self-contained build with bundled - runtime libraries under /opt/fpv-relay. + RTL8812AU capture (wfb-ng / devourer), low-latency UDP forwarding, DVR + recording, RTSP output, mavlink uplink/downlink.${WITH_GUI:+ Includes Qt GUI with bundled Qt runtime.} EOF -mkdir -p "${DIST}" -dpkg-deb --build --root-owner-group "${ROOT}" "${DIST}/${PKG}_${DEB_VER}_${ARCH}.deb" +mkdir -p "${OUT}" +dpkg-deb --build --root-owner-group "${ROOT}" "${OUT}/${PKG}_${DEB_VER}_${ARCH}.deb" rm -rf "${ROOT}" echo -echo "完成: ${DIST}/${PKG}_${DEB_VER}_${ARCH}.deb" -du -sh "${DIST}/${PKG}_${DEB_VER}_${ARCH}.deb" +echo "完成: ${OUT}/${PKG}_${DEB_VER}_${ARCH}.deb" +du -sh "${OUT}/${PKG}_${DEB_VER}_${ARCH}.deb" diff --git a/packaging/build-release.sh b/packaging/build-release.sh old mode 100644 new mode 100755 index 95a97f8..958001e --- a/packaging/build-release.sh +++ b/packaging/build-release.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # ============================================================ -# 打包 C++ 项目为 tar.gz(自包含:可执行文件 + 运行库) +# 打包为 tar.gz(解压即用:可执行文件 + 关键运行库 + Qt) +# - 打包 Qt6/ICU 与 libpcap/libsodium(其余系统库由发行版提供) # 用法: # ./packaging/build-release.sh # 仅命令行版 # ./packaging/build-release.sh --with-gui # 含 GUI(需 QT_PREFIX) @@ -12,40 +13,50 @@ REPO="$(cd "$(dirname "$0")/.." && pwd)" WITH_GUI=0 [ "$1" = "--with-gui" ] && WITH_GUI=1 -VERSION="$(cd "$REPO" && git describe --tags --always 2>/dev/null || date +%Y%m%d)" +VERSION="$(cd "$REPO" && git describe --tags --always 2>/dev/null | tr -d 'v' || true)" +[ -z "$VERSION" ] && VERSION="$(date +%Y%m%d)" +case "$VERSION" in + [0-9]*) ;; + *) VERSION="0.0.0+git$(date +%Y%m%d).${VERSION}" ;; +esac NAME="fpv-relay-${VERSION}-linux-x86_64" -DIST="${REPO}/dist" -STAGE="${DIST}/${NAME}" +OUT="${REPO}/dist" +STAGE="${OUT}/${NAME}" LIBDIR="${STAGE}/lib" echo "==> 构建(CLI)" cd "${REPO}" -cmake --preset cli >/dev/null -cmake --build --preset cli -j"$(nproc)" >/dev/null +cmake --preset linux-cli >/dev/null +cmake --build --preset linux-cli -j"$(nproc)" >/dev/null +GUI_DIST="" if [ "$WITH_GUI" = "1" ]; then : "${QT_PREFIX:?--with-gui 需要设置 QT_PREFIX 指向 Qt 安装目录}" echo "==> 构建(GUI)" - cmake --preset gui >/dev/null - cmake --build --preset gui -j"$(nproc)" >/dev/null + cmake --preset linux-gui -DFPC_QT_PREFIX="${QT_PREFIX}" >/dev/null + cmake --build --preset linux-gui -j"$(nproc)" >/dev/null + GUI_DIST="${REPO}/build-gui/dist" fi echo "==> 准备目录 ${STAGE}" rm -rf "${STAGE}" mkdir -p "${STAGE}/bin" "${LIBDIR}" "${STAGE}/tools" -cp "${REPO}/build/fpv-relay" "${STAGE}/bin/" -cp "${REPO}/tools/play-udp.sh" "${STAGE}/tools/" 2>/dev/null || true -cp "${REPO}/config.example.yaml" "${STAGE}/" +cp "${REPO}/build/dist/fpv-relay" "${STAGE}/bin/" +cp "${REPO}/config.example.yaml" "${STAGE}/" 2>/dev/null || true +[ -f "${REPO}/ass/gs.key" ] && cp "${REPO}/ass/gs.key" "${STAGE}/gs.key" +cp -r "${REPO}/tools/." "${STAGE}/tools/" 2>/dev/null || true cp "${REPO}/README.md" "${STAGE}/" 2>/dev/null || true -# 收集并复制非系统共享库(递归) +# 只打包这些库(Qt6/ICU/libpcap/libsodium),其余系统库依赖目标机提供 bundle_libs() { - ldd "$1" 2>/dev/null | awk '{print $3}' | grep '^/' | while read -r lib; do + local lp="${LD_LIBRARY_PATH}" + [ -n "${QT_PREFIX:-}" ] && lp="${QT_PREFIX}/lib:${lp}" + LD_LIBRARY_PATH="$lp" ldd "$1" 2>/dev/null | awk '{print $3}' | grep '^/' | while read -r lib; do base="$(basename "$lib")" case "$base" in - libc.so*|libm.so*|libpthread*|librt*|libdl*|libgcc_s*|libstdc++*|ld-linux*|libnsl*|libresolv*) continue ;; - libgst*|libglib-2.0*|libgobject-2.0*|libgmodule-2.0*|libgio-2.0*|libgthread-2.0*|liborc-0.4*|libav*|libswscale*|libswresample*|libpostproc*|libx264*|libx265*|libvpx*) continue ;; + libQt6*|libicu*|libpcap*|libsodium*) ;; + *) continue ;; esac if [ ! -e "${LIBDIR}/${base}" ]; then cp -L "$lib" "${LIBDIR}/${base}" @@ -59,32 +70,31 @@ bundle_libs "${STAGE}/bin/fpv-relay" if [ "$WITH_GUI" = "1" ]; then echo "==> 打包 GUI 与 Qt 运行环境" - cp "${REPO}/build-gui/fpv-relay-gui" "${STAGE}/bin/" - bundle_libs "${STAGE}/bin/fpv-relay-gui" + cp "${GUI_DIST}/fpv-relay-gui" "${STAGE}/bin/" + cp -rL "${GUI_DIST}/FpvRelay" "${STAGE}/bin/" 2>/dev/null || true - # 复制全部 Qt6 运行库(含 QML 插件会用到的),并补齐它们的依赖 - cp -L "${QT_PREFIX}/lib/"libQt6*.so* "${LIBDIR}/" 2>/dev/null || true - for l in "${LIBDIR}"/libQt6*.so*; do - [ -e "$l" ] && bundle_libs "$l" - done - - # QML 模块的 qmldir(依赖“程序目录”作为导入路径) - cp -rL "${REPO}/build-gui/FpvRelay" "${STAGE}/bin/" 2>/dev/null || true - - # Qt 插件(平台/GL/图像格式) - mkdir -p "${STAGE}/plugins/platforms" "${STAGE}/plugins/xcbglintegrations" + mkdir -p "${STAGE}/plugins/platforms" "${STAGE}/plugins/xcbglintegrations" \ + "${STAGE}/plugins/imageformats" "${STAGE}/plugins/iconengines" "${STAGE}/qml" for p in libqxcb.so libqminimal.so libqoffscreen.so; do [ -f "${QT_PREFIX}/plugins/platforms/${p}" ] && cp -L "${QT_PREFIX}/plugins/platforms/${p}" "${STAGE}/plugins/platforms/" done cp -L "${QT_PREFIX}/plugins/xcbglintegrations/"*.so "${STAGE}/plugins/xcbglintegrations/" 2>/dev/null || true + cp -L "${QT_PREFIX}/plugins/imageformats/"*.so "${STAGE}/plugins/imageformats/" 2>/dev/null || true + cp -L "${QT_PREFIX}/plugins/iconengines/"*.so "${STAGE}/plugins/iconengines/" 2>/dev/null || true - # 必要 QML 模块 - mkdir -p "${STAGE}/qml" - for m in QtQml QtQuick Qt Qt5Compat; do - [ -d "${QT_PREFIX}/qml/${m}" ] && cp -rL "${QT_PREFIX}/qml/${m}" "${STAGE}/qml/" + # 只拷 FluentUI/本项目用到的 QML 模块(避免把整个 Qt qml 目录塞进去) + cp -rL "${QT_PREFIX}/qml/QtQml" "${STAGE}/qml/" + mkdir -p "${STAGE}/qml/Qt/labs" "${STAGE}/qml/Qt5Compat" "${STAGE}/qml/QtQuick" + cp -rL "${QT_PREFIX}/qml/Qt/labs/qmlmodels" "${STAGE}/qml/Qt/labs/" 2>/dev/null || true + cp -rL "${QT_PREFIX}/qml/Qt/labs/folderlistmodel" "${STAGE}/qml/Qt/labs/" 2>/dev/null || true + cp -rL "${QT_PREFIX}/qml/Qt5Compat/GraphicalEffects" "${STAGE}/qml/Qt5Compat/" 2>/dev/null || true + find "${QT_PREFIX}/qml/QtQuick" -maxdepth 1 -type f -exec cp -L {} "${STAGE}/qml/QtQuick/" \; + for sub in Controls Dialogs Layouts Shapes Templates Window; do + [ -d "${QT_PREFIX}/qml/QtQuick/${sub}" ] && cp -rL "${QT_PREFIX}/qml/QtQuick/${sub}" "${STAGE}/qml/QtQuick/" done - # 打包 QML 插件与 Qt 插件自身的依赖 + # 按实际依赖收集 Qt6/ICU 库 + bundle_libs "${STAGE}/bin/fpv-relay-gui" find "${STAGE}/qml" "${STAGE}/plugins" -name "*.so" 2>/dev/null | while read -r so; do bundle_libs "$so" done @@ -107,13 +117,16 @@ export QT_PLUGIN_PATH="$DIR/plugins" export QT_QPA_PLATFORM_PLUGIN_PATH="$DIR/plugins/platforms" export QML2_IMPORT_PATH="$DIR/qml" export QML_IMPORT_PATH="$DIR/qml" +if ! command -v ffmpeg >/dev/null 2>&1; then + echo "警告: 未找到 ffmpeg,视频预览不可用(apt install ffmpeg)" >&2 +fi exec "$DIR/bin/fpv-relay-gui" "$@" EOF chmod +x "${STAGE}/run-gui.sh" fi echo "==> 生成 tar.gz" -tar -C "${DIST}" -czf "${DIST}/${NAME}.tar.gz" "${NAME}" +tar -C "${OUT}" -czf "${OUT}/${NAME}.tar.gz" "${NAME}" echo -echo "完成: ${DIST}/${NAME}.tar.gz" -du -sh "${DIST}/${NAME}.tar.gz" +echo "完成: ${OUT}/${NAME}.tar.gz" +du -sh "${OUT}/${NAME}.tar.gz"