EG/build_scripts/build_linux.sh

526 lines
13 KiB
Bash

#!/bin/bash
# EG Linux 构建脚本 - 使用 Nuitka 编译 + AppImage 打包(保守白名单版)
# 用法: ./build_linux.sh [版本号] [--clean] [--skip-compile] [--deb]
set -e
# 配置
VERSION=${1:-"1.0.0"}
CLEAN=false
SKIP_COMPILE=false
BUILD_DEB=false
# 解析参数
for arg in "$@"; do
case $arg in
--clean)
CLEAN=true
shift
;;
--skip-compile)
SKIP_COMPILE=true
shift
;;
--deb)
BUILD_DEB=true
shift
;;
esac
done
# 路径配置
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DIST_DIR="$PROJECT_ROOT/dist"
BUILD_DIR="$PROJECT_ROOT/build"
NUITKA_DIR="$BUILD_DIR/nuitka"
APPDIR="$BUILD_DIR/EG_$VERSION.AppDir"
OUTPUT_NAME="EG_$VERSION"
# 应用信息
APP_NAME="EG"
COMPANY_NAME="EG Team"
APP_DESCRIPTION="EG 3D Editor and Game Engine"
# 需要包含的数据目录
DATA_DIRS=(
# 配置和资源
"config"
"icons"
"Resources"
"tex"
"templates"
# 核心模块
"core"
"gui"
"project" # 项目管理模块
"scene"
"scripts"
"ssbo_component"
"tools"
"TransformGizmo"
"ui"
# 引擎和扩展
"RenderPipelineFile"
"vr_actions"
# 项目模板
"new" # 默认空项目模板
"demo"
)
# 需要包含的单个文件
DATA_FILES=(
"imgui.ini"
)
REQUIRED_PACKAGES=(
"panda3d"
"direct"
"imgui_bundle"
"p3dimgui"
"openvr"
"RenderPipelineFile"
"rpcore"
"rplibs"
"rpplugins"
"core"
"scene"
"project"
"ui"
"gui"
"scripts"
"ssbo_component"
"tools"
"TransformGizmo"
"vr_actions"
)
OPTIONAL_PACKAGES=(
"plugins"
"gltf"
"playwright"
"PyQt5"
)
OPTIONAL_MODULES=(
"playwright.async_api"
"win32gui"
"win32con"
"PyQt5.QtCore"
"PyQt5.QtGui"
"PyQt5.QtWidgets"
)
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 辅助函数
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
local step=$1
local total=$2
local msg=$3
echo ""
echo -e "${YELLOW}[$step/$total]${NC} $msg"
}
python_importable() {
local module_name="$1"
python3 -c "import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('$module_name') else 1)" >/dev/null 2>&1
}
# ==================== 主流程 ====================
echo "========================================"
echo " EG Linux Builder v$VERSION"
echo "========================================"
echo "项目路径: $PROJECT_ROOT"
# 步骤 1: 清理
log_step 1 6 "清理旧构建..."
if [ "$CLEAN" = true ]; then
rm -rf "$BUILD_DIR"
rm -rf "$DIST_DIR"
log_success "已清理所有构建文件"
fi
mkdir -p "$BUILD_DIR"
mkdir -p "$DIST_DIR"
# 步骤 2: 环境检查
log_step 2 6 "检查构建环境..."
# 检查必要的命令
for cmd in python3 pip3 gcc g++; do
if ! command -v $cmd &> /dev/null; then
log_error "$cmd 未安装"
exit 1
fi
done
PYTHON_VERSION=$(python3 --version 2>&1)
log_success "Python: $PYTHON_VERSION"
# 检查并安装构建依赖
log_info "检查构建依赖..."
pip3 install -q nuitka pyinstaller 2>/dev/null || true
log_success "构建依赖已就绪"
# 安装项目依赖
log_info "检查项目依赖..."
pip3 install -q -r "$PROJECT_ROOT/requirements/requirements-minimal.txt" 2>/dev/null || log_warning "部分依赖可能安装失败"
log_success "项目依赖已就绪"
# 步骤 3: Nuitka 编译
if [ "$SKIP_COMPILE" = false ]; then
log_step 3 6 "使用 Nuitka 编译主程序..."
cd "$PROJECT_ROOT"
REQUIRED_PACKAGE_ARGS=()
for package in "${REQUIRED_PACKAGES[@]}"; do
REQUIRED_PACKAGE_ARGS+=("--include-package=$package")
done
OPTIONAL_PACKAGE_ARGS=()
for package in "${OPTIONAL_PACKAGES[@]}"; do
if python_importable "$package"; then
OPTIONAL_PACKAGE_ARGS+=("--include-package=$package")
log_info "检测到可选包并纳入打包: $package"
fi
done
OPTIONAL_MODULE_ARGS=()
for module in "${OPTIONAL_MODULES[@]}"; do
if python_importable "$module"; then
OPTIONAL_MODULE_ARGS+=("--include-module=$module")
log_info "检测到可选模块并纳入打包: $module"
fi
done
# 构建 Nuitka 选项
NUITKA_OPTS=(
"--standalone"
"--follow-import-to=rpcore"
"--follow-import-to=rpplugins"
"--follow-import-to=core"
"--follow-import-to=scene"
"--follow-import-to=project"
"--follow-import-to=ui"
"--nofollow-import-to=tkinter,test,unittest,setuptools,pip"
"--linux-onefile-icon=icons/app.png"
"--lto=no"
"--jobs=$(nproc)"
"--output-dir=$NUITKA_DIR"
"--remove-output"
)
NUITKA_OPTS+=("${REQUIRED_PACKAGE_ARGS[@]}")
NUITKA_OPTS+=("${OPTIONAL_PACKAGE_ARGS[@]}")
NUITKA_OPTS+=("${OPTIONAL_MODULE_ARGS[@]}")
log_info "开始编译 (可能需要几分钟)..."
python3 -m nuitka "${NUITKA_OPTS[@]}" Start_Run.py
if [ $? -ne 0 ]; then
log_error "Nuitka 编译失败"
exit 1
fi
log_success "编译成功"
else
log_step 3 6 "跳过编译 (--skip-compile)"
fi
# 步骤 4: 准备 AppDir
log_step 4 6 "准备 AppDir..."
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin"
mkdir -p "$APPDIR/usr/lib/EG"
mkdir -p "$APPDIR/usr/share/applications"
mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
mkdir -p "$APPDIR/usr/share/icons/hicolor/128x128/apps"
mkdir -p "$APPDIR/usr/share/icons/hicolor/64x64/apps"
mkdir -p "$APPDIR/usr/share/metainfo"
# 复制 Nuitka standalone 目录
DIST_SOURCE="$NUITKA_DIR/Start_Run.dist"
if [ ! -d "$DIST_SOURCE" ]; then
log_error "找不到编译后的 standalone 目录: $DIST_SOURCE"
exit 1
fi
cp -r "$DIST_SOURCE/"* "$APPDIR/usr/lib/EG/"
if [ -f "$APPDIR/usr/lib/EG/Start_Run.bin" ]; then
cp "$APPDIR/usr/lib/EG/Start_Run.bin" "$APPDIR/usr/bin/EG"
elif [ -f "$APPDIR/usr/lib/EG/Start_Run" ]; then
cp "$APPDIR/usr/lib/EG/Start_Run" "$APPDIR/usr/bin/EG"
else
EXE_SOURCE=$(find "$APPDIR/usr/lib/EG" -maxdepth 1 -type f -executable | head -1)
if [ -z "$EXE_SOURCE" ]; then
log_error "找不到可执行文件"
exit 1
fi
cp "$EXE_SOURCE" "$APPDIR/usr/bin/EG"
fi
chmod +x "$APPDIR/usr/bin/EG"
log_success "复制可执行文件和 standalone 运行时"
# 复制数据目录
COPIED=0
for dir in "${DATA_DIRS[@]}"; do
if [ -d "$PROJECT_ROOT/$dir" ]; then
cp -r "$PROJECT_ROOT/$dir" "$APPDIR/usr/lib/EG/"
# 清理 Python 缓存
find "$APPDIR/usr/lib/EG/$dir" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
find "$APPDIR/usr/lib/EG/$dir" -name "*.pyc" -delete 2>/dev/null || true
log_success "复制目录: $dir"
((COPIED++))
else
log_warning "目录不存在: $dir"
fi
done
log_success "共复制 $COPIED 个数据目录"
# 复制单个文件
for file in "${DATA_FILES[@]}"; do
if [ -f "$PROJECT_ROOT/$file" ]; then
cp "$PROJECT_ROOT/$file" "$APPDIR/usr/lib/EG/"
log_success "复制文件: $file"
fi
done
# 创建 .desktop 文件
cat > "$APPDIR/usr/share/applications/EG.desktop" << EOF
[Desktop Entry]
Name=EG
Name[zh_CN]=EG 编辑器
Comment=$APP_DESCRIPTION
Comment[zh_CN]=EG 3D 编辑器和游戏引擎
Exec=EG
Icon=EG
Type=Application
Categories=Graphics;3DGraphics;Development;Game;
MimeType=application/x-eg-project;
Terminal=false
StartupNotify=true
StartupWMClass=EG
Keywords=3D;Editor;Game;Engine;Panda3D;
EOF
# 创建 AppStream metadata (用于软件中心)
cat > "$APPDIR/usr/share/metainfo/EG.appdata.xml" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.yourcompany.EG</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>Proprietary</project_license>
<name>EG</name>
<summary>3D Editor and Game Engine</summary>
<description>
<p>EG is a powerful 3D editor and game engine based on Panda3D.</p>
</description>
<launchable type="desktop-id">EG.desktop</launchable>
<url type="homepage">https://your-website.com</url>
<provides>
<binary>EG</binary>
</provides>
</component>
EOF
# 复制图标
for size in 256 128 64; do
ICON_SRC="$PROJECT_ROOT/icons/app_${size}.png"
if [ -f "$ICON_SRC" ]; then
cp "$ICON_SRC" "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/EG.png"
elif [ -f "$PROJECT_ROOT/icons/app.png" ]; then
# 如果只有一张图,复制到所有尺寸
cp "$PROJECT_ROOT/icons/app.png" "$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/EG.png"
break
fi
done
# 创建 AppRun
cat > "$APPDIR/AppRun" << 'EOF'
#!/bin/bash
# AppRun for EG
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
# 设置环境变量
export PATH="${HERE}/usr/bin:${PATH}"
export EG_PROJECT_ROOT="${HERE}/usr/lib/EG"
export LD_LIBRARY_PATH="${HERE}/usr/lib/EG:${HERE}/usr/lib:${LD_LIBRARY_PATH}"
export PYTHONPATH="${HERE}/usr/lib/EG:${HERE}/usr/lib/EG/RenderPipelineFile:${PYTHONPATH}"
# 设置 XDG 目录
export XDG_DATA_DIRS="${HERE}/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
# 切换到项目根目录,确保相对资源路径与 Windows 打包版保持一致
cd "${EG_PROJECT_ROOT}" || exit 1
# 执行主程序
exec "${HERE}/usr/bin/EG" "$@"
EOF
chmod +x "$APPDIR/AppRun"
# 复制 .desktop 和图标到根目录
cp "$APPDIR/usr/share/applications/EG.desktop" "$APPDIR/"
if [ -f "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" ]; then
cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" "$APPDIR/EG.png"
elif [ -f "$APPDIR/usr/share/icons/hicolor/128x128/apps/EG.png" ]; then
cp "$APPDIR/usr/share/icons/hicolor/128x128/apps/EG.png" "$APPDIR/EG.png"
elif [ -f "$APPDIR/usr/share/icons/hicolor/64x64/apps/EG.png" ]; then
cp "$APPDIR/usr/share/icons/hicolor/64x64/apps/EG.png" "$APPDIR/EG.png"
fi
log_success "AppDir 准备完成"
# 步骤 5: 创建 AppImage
log_step 5 6 "创建 AppImage..."
cd "$DIST_DIR"
# 下载 appimagetool
APPIMAGETOOL="appimagetool-x86_64.AppImage"
if [ ! -f "$APPIMAGETOOL" ]; then
log_info "下载 appimagetool..."
wget -q "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O "$APPIMAGETOOL" || {
log_error "下载 appimagetool 失败"
exit 1
}
chmod +x "$APPIMAGETOOL"
log_success "下载完成"
fi
# 创建 AppImage
APPIMAGE_NAME="EG-${VERSION}-x86_64.AppImage"
log_info "构建 AppImage..."
# 使用 --appimage-extract-and-run 避免 fuse 依赖
ARCH=x86_64 ./"$APPIMAGETOOL" --appimage-extract-and-run "$APPDIR" "$APPIMAGE_NAME" 2>&1 | while read line; do
echo " $line"
done
if [ -f "$APPIMAGE_NAME" ]; then
chmod +x "$APPIMAGE_NAME"
SIZE=$(du -h "$APPIMAGE_NAME" | cut -f1)
log_success "AppImage 创建成功: $APPIMAGE_NAME ($SIZE)"
else
log_error "AppImage 创建失败"
exit 1
fi
# 可选: 创建 DEB 包
if [ "$BUILD_DEB" = true ]; then
log_step 5 6 "创建 DEB 包..."
DEB_DIR="$BUILD_DIR/eg_${VERSION}_amd64"
mkdir -p "$DEB_DIR/DEBIAN"
mkdir -p "$DEB_DIR/opt/EG"
mkdir -p "$DEB_DIR/usr/share/applications"
mkdir -p "$DEB_DIR/usr/share/icons/hicolor/256x256/apps"
mkdir -p "$DEB_DIR/usr/bin"
# 控制文件
cat > "$DEB_DIR/DEBIAN/control" << EOF
Package: eg
Version: $VERSION
Section: graphics
Priority: optional
Architecture: amd64
Depends: libgl1, libopenal1, libglu1-mesa, python3 (>= 3.8)
Maintainer: $COMPANY_NAME <support@your-website.com>
Description: EG 3D Editor and Game Engine
EG is a powerful 3D editor and game engine
based on Panda3D with advanced rendering.
EOF
# 复制文件
cp -r "$APPDIR/usr/"* "$DEB_DIR/opt/EG/"
# 创建启动器
cat > "$DEB_DIR/usr/bin/EG" << EOF
#!/bin/bash
cd /opt/EG
./bin/EG "\$@"
EOF
chmod +x "$DEB_DIR/usr/bin/EG"
# 复制 desktop 文件和图标
cp "$APPDIR/usr/share/applications/EG.desktop" "$DEB_DIR/usr/share/applications/"
if [ -f "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" ]; then
cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/EG.png" "$DEB_DIR/usr/share/icons/hicolor/256x256/apps/"
fi
# 构建 deb
dpkg-deb --build "$DEB_DIR" "$DIST_DIR/eg_${VERSION}_amd64.deb"
if [ -f "$DIST_DIR/eg_${VERSION}_amd64.deb" ]; then
SIZE=$(du -h "$DIST_DIR/eg_${VERSION}_amd64.deb" | cut -f1)
log_success "DEB 包创建成功: eg_${VERSION}_amd64.deb ($SIZE)"
fi
fi
# 步骤 6: 创建 tarball
log_step 6 6 "创建 tarball..."
cd "$BUILD_DIR"
# 创建普通 tarball
TARBALL="$DIST_DIR/EG_${VERSION}_Linux_Portable.tar.gz"
tar -czf "$TARBALL" -C "$APPDIR/usr/lib/EG" .
SIZE=$(du -h "$TARBALL" | cut -f1)
log_success "Tarball 创建成功: EG_${VERSION}_Linux_Portable.tar.gz ($SIZE)"
# ==================== 完成 ====================
echo ""
echo "========================================"
echo -e "${GREEN} 构建完成!${NC}"
echo "========================================"
echo ""
echo -e "${BLUE}输出文件:${NC}"
for file in "$DIST_DIR"/EG-*; do
if [ -f "$file" ]; then
name=$(basename "$file")
size=$(du -h "$file" | cut -f1)
echo -e " ${GREEN}${NC} $name (${size})"
fi
done
echo ""
echo -e "${BLUE}输出目录:${NC} $DIST_DIR"
echo ""
# 清理
rm -rf "$APPDIR"
log_info "临时文件已清理"