409 lines
11 KiB
Bash
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}')"
|
|
}
|