GrabBag/GrabBagPrj/project_pkg_common.sh

409 lines
11 KiB
Bash

#!/bin/bash
# 通用打包函数(由 project_pkg_desktop.sh 和 project_pkg_service.sh 引用)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
source "${SCRIPT_DIR}/project_registry.sh"
# ===== 打包常量 =====
PKG_ARCH="arm64"
QT_PKG_PATH="/opt/firefly_qt5.15_arm64_20.04"
QT_LIB_PATH="/opt/sysroot/firefly-arm64-sysroot-20.04/lib/aarch64-linux-gnu"
DEFAULT_OPENCV_PROFILE="opencv320"
OPENCV320_LIB_GLOB="SDK/OpenCV320/Arm/aarch64/libopencv_*.so*"
OPENCV480_LIB_GLOB="SDK/OpenCV480/Arm/aarch64/lib/libopencv_*.so*"
FIXED_PACKAGE_MAINTAINER="visionrobot"
FIXED_QT_REMOVE_WEBENGINE=true
FIXED_QT_REMOVE_NFC=true
# ===== postinst/postrm 中复用的代码片段 =====
DETECT_CURRENT_USER_SNIPPET=$(cat <<'_SNIPPET_'
if [ -n "${SUDO_USER:-}" ]; then
current_user="${SUDO_USER}"
current_home=$(getent passwd "${current_user}" | cut -d: -f6)
else
current_user=$(whoami)
current_home="${HOME}"
fi
_SNIPPET_
)
RESOLVE_DESKTOP_DIR_SNIPPET=$(cat <<'_SNIPPET_'
resolve_desktop_dir() {
local home_dir=$1
local candidate=""
if command -v xdg-user-dir >/dev/null 2>&1; then
candidate=$(xdg-user-dir DESKTOP 2>/dev/null || true)
if [ -n "${candidate}" ] && [ -d "${candidate}" ]; then
printf '%s\n' "${candidate}"
return 0
fi
fi
if [ -f "${home_dir}/.config/user-dirs.dirs" ]; then
candidate=$(grep '^XDG_DESKTOP_DIR=' "${home_dir}/.config/user-dirs.dirs" | head -n 1 | cut -d= -f2- | tr -d '"')
candidate=${candidate//\$HOME/${home_dir}}
if [ -n "${candidate}" ] && [ -d "${candidate}" ]; then
printf '%s\n' "${candidate}"
return 0
fi
fi
for dir_name in Desktop $'\346\241\214\351\235\242' desktop; do
candidate="${home_dir}/${dir_name}"
if [ -d "${candidate}" ]; then
printf '%s\n' "${candidate}"
return 0
fi
done
return 1
}
_SNIPPET_
)
# ===== 通用函数 =====
log_info() {
printf '[项目打包] %s\n' "$*"
}
show_pkg_help() {
local kind=$1
local script_name="project_pkg_${kind}.sh"
cat <<EOF
用法:
./${script_name} <ProjectName>
./${script_name} --list
可打包项目:
EOF
list_projects_by_kind "${kind}" | sed 's/^/ - /'
}
copy_glob_items() {
local src_pattern=$1
local dest_rel=$2
local copy_mode=$3
local required=$4
local matches=()
shopt -s nullglob
matches=( ${REPO_DIR}/${src_pattern} )
shopt -u nullglob
if [ ${#matches[@]} -eq 0 ]; then
if [ "$required" = true ]; then
echo "缺少文件: ${src_pattern}" >&2
exit 1
fi
return 0
fi
mkdir -p "${PKG_PATH}/${dest_rel}"
case "$copy_mode" in
globa)
cp -a "${matches[@]}" "${PKG_PATH}/${dest_rel}/"
;;
glob)
cp "${matches[@]}" "${PKG_PATH}/${dest_rel}/"
;;
*)
echo "不支持的 glob 复制模式: ${copy_mode}" >&2
exit 1
;;
esac
}
copy_file_item() {
local src_rel=$1
local dest_rel=$2
local required=$3
local src_abs="${REPO_DIR}/${src_rel}"
if [ ! -f "${src_abs}" ]; then
if [ "$required" = true ]; then
echo "缺少文件: ${src_rel}" >&2
exit 1
fi
return 0
fi
mkdir -p "$(dirname "${PKG_PATH}/${dest_rel}")"
cp "${src_abs}" "${PKG_PATH}/${dest_rel}"
}
copy_directory_item() {
local src_rel=$1
local dest_rel=$2
local required=$3
local src_abs="${REPO_DIR}/${src_rel}"
if [ ! -d "${src_abs}" ]; then
if [ "$required" = true ]; then
echo "缺少目录: ${src_rel}" >&2
exit 1
fi
return 0
fi
mkdir -p "${PKG_PATH}/${dest_rel}"
cp -a "${src_abs}/." "${PKG_PATH}/${dest_rel}/"
}
copy_from_spec() {
local required=$1
local spec=$2
local kind=""
local src=""
local dest=""
IFS='|' read -r kind src dest <<< "${spec}"
case "$kind" in
glob|globa)
copy_glob_items "${src}" "${dest}" "${kind}" "${required}"
;;
file)
copy_file_item "${src}" "${dest}" "${required}"
;;
dir)
copy_directory_item "${src}" "${dest}" "${required}"
;;
*)
echo "不支持的复制规则: ${spec}" >&2
exit 1
;;
esac
}
process_specs() {
local required=$1
shift
local spec=""
for spec in "$@"; do
copy_from_spec "${required}" "${spec}"
done
}
apply_package_defaults() {
PACKAGE_SLUG=$(printf '%s' "${PACKAGE_NAME}" | tr '[:upper:]' '[:lower:]')
PACKAGE_SECTION="${PACKAGE_SLUG}app"
[ "${PACKAGE_KIND}" = "service" ] && PACKAGE_SECTION="${PACKAGE_SLUG}"
PACKAGE_MAINTAINER="${FIXED_PACKAGE_MAINTAINER}"
: "${PACKAGE_DESCRIPTION:=${PACKAGE_NAME} application}"
: "${PACKAGE_COMMENT:=${PACKAGE_NAME} App}"
: "${PACKAGE_OPENCV_PROFILE:=${DEFAULT_OPENCV_PROFILE}}"
}
prepare_package_layout() {
rm -rf "${PKG_PATH}"
mkdir -p "${PKG_PATH}/DEBIAN"
mkdir -p "${PKG_PATH}/etc/profile.d"
mkdir -p "${PKG_PATH}/opt/sysroot/lib"
mkdir -p "${PKG_PATH}/usr/lib"
if [ "${PACKAGE_KIND}" = "desktop" ]; then
mkdir -p "${PKG_PATH}/etc/xdg/autostart"
mkdir -p "${PKG_PATH}/usr/local/bin"
mkdir -p "${PKG_PATH}/usr/share/applications"
mkdir -p "${PKG_PATH}/usr/share/pixmaps"
fi
}
copy_qt_runtime() {
cp -rfd "${QT_PKG_PATH}/ext" "${PKG_PATH}/opt/firefly_qt5.15"
cp "${QT_PKG_PATH}/target_qtEnv.sh" "${PKG_PATH}/etc/profile.d/"
local libfile=""
local filename=""
for libfile in "${QT_LIB_PATH}"/*.so*; do
filename=$(basename "${libfile}")
if [[ "${filename}" == *icu* ]]; then
cp -rfd "${libfile}" "${PKG_PATH}/opt/sysroot/lib/"
fi
done
}
cleanup_qt_runtime() {
if [ "${FIXED_QT_REMOVE_WEBENGINE}" = true ]; then
rm -f "${PKG_PATH}/opt/firefly_qt5.15/lib/libQt5WebEngine"*
rm -rf "${PKG_PATH}/opt/firefly_qt5.15/libexec/QtWebEngineProcess"
rm -rf "${PKG_PATH}/opt/firefly_qt5.15/resources/qtwebengine"*
rm -rf "${PKG_PATH}/opt/firefly_qt5.15/translations/qtwebengine"*
fi
if [ "${FIXED_QT_REMOVE_NFC}" = true ]; then
rm -rf "${PKG_PATH}/opt/firefly_qt5.15/translations/Nfc"*
fi
}
copy_opencv_runtime() {
local src_pattern=""
case "${PACKAGE_OPENCV_PROFILE}" in
opencv320)
src_pattern="${OPENCV320_LIB_GLOB}"
;;
opencv480)
src_pattern="${OPENCV480_LIB_GLOB}"
;;
none)
return 0
;;
*)
echo "不支持的 OpenCV 配置: ${PACKAGE_OPENCV_PROFILE}" >&2
exit 1
;;
esac
log_info "复制 OpenCV 运行库 (${PACKAGE_OPENCV_PROFILE})"
copy_glob_items "${src_pattern}" "usr/lib" "globa" true
}
copy_required_files() {
copy_opencv_runtime
process_specs true "${PACKAGE_COPY_SPECS[@]}"
process_specs false "${PACKAGE_OPTIONAL_COPY_SPECS[@]}"
copy_file_item "${PACKAGE_BINARY_SOURCE_REL}" "${PACKAGE_BINARY_DEST_REL}" true
if [ -n "${PACKAGE_ICON_SOURCE_REL}" ] && [ -n "${PACKAGE_ICON_DEST_REL}" ]; then
copy_file_item "${PACKAGE_ICON_SOURCE_REL}" "${PACKAGE_ICON_DEST_REL}" true
fi
if [ -n "${PACKAGE_SERVICE_SOURCE_REL}" ] && [ -n "${PACKAGE_SERVICE_DEST_REL}" ]; then
copy_file_item "${PACKAGE_SERVICE_SOURCE_REL}" "${PACKAGE_SERVICE_DEST_REL}" true
fi
}
write_control_file() {
cat > "${PKG_PATH}/DEBIAN/control" <<EOF
Package: ${PACKAGE_NAME}
Version: ${PKG_RELEASE_VERSION}
Section: ${PACKAGE_SECTION}
Architecture: ${PKG_ARCH}
Priority: optional
Maintainer: ${PACKAGE_MAINTAINER}
Description: ${PACKAGE_DESCRIPTION}
EOF
}
set_permissions() {
local target=""
for target in "${PKG_PATH}/usr" "${PKG_PATH}/etc" "${PKG_PATH}/opt"; do
if [ -d "${target}" ]; then
chmod -R 755 "${target}"
fi
done
}
# ===== 流程编排函数 =====
# 加载打包上下文:参数解析 + meta 加载 + 派生变量计算
# 用法: load_package_context "$@" "desktop" 或 load_package_context "$@" "service"
load_package_context() {
local expected_kind="${!#}" # 最后一个参数是期望的 kind
local args=("${@:1:$#-1}") # 去掉最后一个参数
echo "开始加载 ${expected_kind} 上下文"
if [ ${#args[@]} -eq 1 ] && [ "${args[0]}" = "--list" ]; then
list_projects_by_kind "${expected_kind}"
exit 0
fi
if [ ${#args[@]} -eq 1 ] && { [ "${args[0]}" = "-h" ] || [ "${args[0]}" = "--help" ]; }; then
show_pkg_help "${expected_kind}"
exit 0
fi
if [ ${#args[@]} -ne 1 ]; then
show_pkg_help "${expected_kind}"
exit 1
fi
PACKAGE_TARGET="${args[0]}"
echo "加载项目 ${PACKAGE_TARGET} 上下文"
if ! load_package_project_meta "${PACKAGE_TARGET}"; then
echo "未知项目: ${PACKAGE_TARGET}" >&2
echo >&2
show_pkg_help "${expected_kind}" >&2
exit 1
fi
if [ "${PACKAGE_KIND}" != "${expected_kind}" ]; then
echo "错误: ${PACKAGE_TARGET} 不是 ${expected_kind} 类型项目" >&2
exit 1
fi
apply_package_defaults
echo "读取 ${PACKAGE_TARGET} 版本信息"
VERSION_FILE="${REPO_DIR}/${PACKAGE_VERSION_FILE_REL}"
read_project_version "${VERSION_FILE}" "${PACKAGE_VERSION_MACRO}"
echo "生成 ${expected_kind} 打包名称"
PKG_VERSION="${PROJECT_VERSION}"
BUILD_NUMBER="${PROJECT_BUILD}"
PKG_RELEASE_VERSION="${PKG_VERSION}.${BUILD_NUMBER}"
PKG_PATH="${HOME}/${PACKAGE_NAME}Pkg"
RELEASE_PATH="${REPO_DIR}/Publish"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
DEB_FILENAME="${RELEASE_PATH}/${PACKAGE_NAME}_${PKG_VERSION}_${BUILD_NUMBER}_${PKG_ARCH}_${TIMESTAMP}.deb"
DESKTOP_FILE_NAME="${PACKAGE_SLUG}.desktop"
EXEC_INSTALL_PATH="/${PACKAGE_BINARY_DEST_REL}"
ICON_INSTALL_PATH="/${PACKAGE_ICON_DEST_REL}"
SERVICE_INSTALL_DIR="/opt/${PACKAGE_SLUG}"
echo "加载项目 ${PACKAGE_TARGET} 上下文完成"
if [ -n "${PACKAGE_SERVICE_DEST_REL}" ]; then
SERVICE_INSTALL_PATH="/${PACKAGE_SERVICE_DEST_REL}"
SERVICE_UNIT_NAME="$(basename "${PACKAGE_SERVICE_DEST_REL}")"
else
SERVICE_INSTALL_PATH=""
SERVICE_UNIT_NAME=""
fi
mkdir -p "${RELEASE_PATH}"
log_info "开始打包 ${PACKAGE_NAME} ${PKG_RELEASE_VERSION}"
}
# 通用打包步骤
run_common_packaging_steps() {
log_info "准备打包目录"
prepare_package_layout
log_info "复制 Qt 运行库"
copy_qt_runtime
log_info "清理 Qt 冗余文件"
cleanup_qt_runtime
log_info "复制应用文件"
copy_required_files
log_info "写入软件包元数据"
write_control_file
}
# 最终打包
finalize_package() {
log_info "设置文件权限"
set_permissions
log_info "生成 DEB 安装包"
fakeroot dpkg -b "${PKG_PATH}" "${DEB_FILENAME}" >/dev/null
log_info "安装包已生成: ${DEB_FILENAME}"
log_info "安装包大小: $(ls -lh "${DEB_FILENAME}" | awk '{print $5}')"
}