526 lines
13 KiB
Bash
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 "临时文件已清理"
|