Files
MyGoNavi/.github/workflows/dev-build.yml
Syngnat 6c36bd0a08 🔧 chore(ci): 补齐 Wails 前端构建前置资源目录
- 为 dev 和 release workflow 增加 frontend/dist 初始化
- 保证 Wails module 生成阶段可通过资源嵌入检查
2026-05-16 13:44:44 +08:00

933 lines
35 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Dev Build
on:
push:
branches:
- dev
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
frontend:
name: Build frontend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Cache frontend node_modules
uses: actions/cache@v4
with:
path: frontend/node_modules
key: ${{ runner.os }}-node20-frontend-${{ hashFiles('frontend/package-lock.json') }}
- name: Install Wails
run: go install -v github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
- name: Build frontend dist
shell: bash
run: |
set -euo pipefail
mkdir -p frontend/dist
printf '<!doctype html><title>GoNavi</title>\n' > frontend/dist/index.html
wails generate module
node frontend/scripts/wails-frontend-install.mjs
npm --prefix frontend run build
- name: Pack frontend dist
shell: bash
run: tar -cf frontend-dist.tar -C frontend/dist .
- name: Upload frontend dist
uses: actions/upload-artifact@v4
with:
name: frontend-dist
path: frontend-dist.tar
if-no-files-found: error
retention-days: 1
driver_agents:
name: Detect changed driver agents
runs-on: ubuntu-latest
outputs:
drivers: ${{ steps.detect.outputs.drivers }}
has_changes: ${{ steps.detect.outputs.has_changes }}
release_source: ${{ steps.detect.outputs.release_source }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect changed driver agents
id: detect
shell: bash
run: |
set -euo pipefail
BASE_REF="${{ github.event.before }}"
if [[ -z "$BASE_REF" || "$BASE_REF" =~ ^0+$ ]]; then
if BASE_REF="$(git rev-parse HEAD^ 2>/dev/null)"; then
:
else
BASE_REF="all"
fi
fi
DRIVERS="$(bash ./tools/detect-changed-driver-agents.sh --base "$BASE_REF" --head "$GITHUB_SHA")"
echo "drivers=${DRIVERS}" >> "$GITHUB_OUTPUT"
if [ -n "$DRIVERS" ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "🧭 Changed driver agents: $DRIVERS"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "🧭 No driver-agent changes detected"
fi
echo "release_source=dev-latest" >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.platform }}
needs:
- frontend
- driver_agents
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
platform: darwin/amd64
os_name: MacOS
arch_name: Amd64
build_name: gonavi-build-darwin-amd64
wails_tags: ""
artifact_suffix: ""
build_optional_agents: true
linux_webkit: ""
- os: macos-latest
platform: darwin/arm64
os_name: MacOS
arch_name: Arm64
build_name: gonavi-build-darwin-arm64
wails_tags: ""
artifact_suffix: ""
build_optional_agents: true
linux_webkit: ""
- os: windows-latest
platform: windows/amd64
os_name: Windows
arch_name: Amd64
build_name: gonavi-build-windows-amd64
wails_tags: ""
artifact_suffix: ""
build_optional_agents: true
linux_webkit: ""
- os: windows-latest
platform: windows/arm64
os_name: Windows
arch_name: Arm64
build_name: gonavi-build-windows-arm64
wails_tags: ""
artifact_suffix: ""
build_optional_agents: true
linux_webkit: ""
- os: ubuntu-22.04
platform: linux/amd64
os_name: Linux
arch_name: Amd64
build_name: gonavi-build-linux-amd64
wails_tags: ""
artifact_suffix: ""
build_optional_agents: true
linux_webkit: "4.0"
- os: ubuntu-24.04
platform: linux/amd64
os_name: Linux
arch_name: Amd64
build_name: gonavi-build-linux-amd64-webkit41
wails_tags: "webkit2_41"
artifact_suffix: "-WebKit41"
build_optional_agents: false
linux_webkit: "4.1"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Download frontend dist
uses: actions/download-artifact@v4
with:
name: frontend-dist
path: .
- name: Extract frontend dist
shell: bash
run: |
set -euo pipefail
mkdir -p frontend/dist
tar -xf frontend-dist.tar -C frontend/dist
test -s frontend/dist/index.html
- name: Install UPX (Windows)
if: matrix.platform == 'windows/amd64'
shell: pwsh
run: |
$UPX_VERSION = "4.2.4"
$url = "https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-win64.zip"
$zipPath = "$env:RUNNER_TEMP\upx.zip"
$extractPath = "$env:RUNNER_TEMP\upx"
Write-Host "📥 从 GitHub Releases 下载 UPX v${UPX_VERSION} ..."
Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
$upxDir = Get-ChildItem -Path $extractPath -Directory | Select-Object -First 1
"$($upxDir.FullName)" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
$upxCmd = Join-Path $upxDir.FullName "upx.exe"
if (!(Test-Path $upxCmd)) {
Write-Error "❌ 未检测到 upx无法保证 Windows 产物经过压缩"
exit 1
}
& $upxCmd --version
- name: Install Linux Dependencies
if: contains(matrix.platform, 'linux')
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev
if [ "${{ matrix.linux_webkit }}" = "4.1" ]; then
sudo apt-get install -y libwebkit2gtk-4.1-dev libsoup-3.0-dev
else
sudo apt-get install -y libwebkit2gtk-4.0-dev
fi
sudo apt-get install -y upx-ucl || sudo apt-get install -y upx
upx --version
sudo apt-get install -y libfuse2 || sudo apt-get install -y libfuse2t64 || true
LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
PLUGIN_URL="https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/releases/download/continuous/linuxdeploy-plugin-gtk-x86_64.AppImage"
echo "📥 下载 linuxdeploy..."
wget --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 --tries=3 \
-O /tmp/linuxdeploy "$LINUXDEPLOY_URL" || {
echo "⚠️ linuxdeploy 下载失败AppImage 打包将跳过"
touch /tmp/skip-appimage
}
echo "📥 下载 linuxdeploy-plugin-gtk..."
wget --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 --tries=3 \
-O /tmp/linuxdeploy-plugin-gtk "$PLUGIN_URL" || {
echo "⚠️ linuxdeploy-plugin-gtk 下载失败AppImage 打包将跳过"
touch /tmp/skip-appimage
}
if [ ! -f /tmp/skip-appimage ]; then
chmod +x /tmp/linuxdeploy /tmp/linuxdeploy-plugin-gtk
echo "✅ AppImage 工具准备完成"
fi
- name: Install Wails
run: go install -v github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
- name: Setup MSYS2 Toolchain For DuckDB (Windows AMD64)
id: msys2_duckdb
if: ${{ matrix.build_optional_agents && matrix.platform == 'windows/amd64' && contains(format(',{0},', needs.driver_agents.outputs.drivers), ',duckdb,') }}
continue-on-error: true
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: >-
mingw-w64-ucrt-x86_64-gcc
- name: Configure DuckDB CGO Toolchain (Windows AMD64)
if: ${{ matrix.build_optional_agents && matrix.platform == 'windows/amd64' && contains(format(',{0},', needs.driver_agents.outputs.drivers), ',duckdb,') }}
shell: pwsh
run: |
function Find-MingwBin([string[]]$candidates) {
foreach ($bin in $candidates) {
if ([string]::IsNullOrWhiteSpace($bin)) {
continue
}
$gcc = Join-Path $bin 'gcc.exe'
$gxx = Join-Path $bin 'g++.exe'
if ((Test-Path $gcc) -and (Test-Path $gxx)) {
return $bin
}
}
return $null
}
$msys2Outcome = "${{ steps.msys2_duckdb.outcome }}"
$msys2Location = "${{ steps.msys2_duckdb.outputs['msys2-location'] }}"
$candidateBins = @()
if (-not [string]::IsNullOrWhiteSpace($msys2Location)) {
$candidateBins += Join-Path $msys2Location 'ucrt64\bin'
}
$candidateBins += @(
'C:\msys64\ucrt64\bin',
'D:\a\_temp\msys64\ucrt64\bin'
)
$candidateBins = @($candidateBins | Select-Object -Unique)
$mingwBin = Find-MingwBin $candidateBins
if (-not $mingwBin) {
if ($msys2Outcome -ne 'success') {
Write-Warning "⚠️ MSYS2 安装步骤结果为 $msys2Outcome回退到 UCRT64 本机路径探测"
} else {
Write-Warning "⚠️ MSYS2 已执行,但未找到 UCRT64 gcc/g++,回退到本机路径探测"
}
$mingwBin = Find-MingwBin $candidateBins
}
if (-not $mingwBin) {
Write-Error "❌ 未找到可用的 DuckDB UCRT64 编译器。已检查:$($candidateBins -join ', ')"
exit 1
}
$gcc = (Join-Path $mingwBin 'gcc.exe')
$gxx = (Join-Path $mingwBin 'g++.exe')
if (!(Test-Path $gcc) -or !(Test-Path $gxx)) {
Write-Error "❌ DuckDB 编译器缺失gcc=$gcc g++=$gxx"
exit 1
}
"$mingwBin" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
"CC=$gcc" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"CXX=$gxx" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "✅ 已配置 DuckDB cgo 编译器: gcc=$gcc g++=$gxx"
- name: Verify DuckDB CGO Toolchain (Windows AMD64)
if: ${{ matrix.build_optional_agents && matrix.platform == 'windows/amd64' && contains(format(',{0},', needs.driver_agents.outputs.drivers), ',duckdb,') }}
shell: pwsh
run: |
& "$env:CC" --version
& "$env:CXX" --version
# ---- 生成 dev 版本号 ----
- name: Generate Dev Version
id: version
shell: bash
run: |
SHORT_SHA=$(git rev-parse --short HEAD)
DEV_VERSION="dev-${SHORT_SHA}"
echo "version=${DEV_VERSION}" >> "$GITHUB_OUTPUT"
echo "📌 Dev 版本号: ${DEV_VERSION}"
- name: Build
shell: bash
env:
CHANGED_DRIVER_AGENTS: ${{ needs.driver_agents.outputs.drivers }}
run: |
set -euo pipefail
DEV_VERSION="${{ steps.version.outputs.version }}"
if [ -n "$CHANGED_DRIVER_AGENTS" ]; then
./tools/generate-driver-agent-revisions.sh --platform "${{ matrix.platform }}" --drivers "$CHANGED_DRIVER_AGENTS"
else
echo "🧭 No driver-agent changes; keeping committed driver revisions"
fi
if [ -n "${{ matrix.wails_tags }}" ]; then
wails build -s -skipbindings -platform ${{ matrix.platform }} -clean -o ${{ matrix.build_name }} -tags "${{ matrix.wails_tags }}" -ldflags "-s -w -X GoNavi-Wails/internal/app.AppVersion=${DEV_VERSION}"
else
wails build -s -skipbindings -platform ${{ matrix.platform }} -clean -o ${{ matrix.build_name }} -ldflags "-s -w -X GoNavi-Wails/internal/app.AppVersion=${DEV_VERSION}"
fi
- name: Build Optional Driver Agents
if: ${{ matrix.build_optional_agents && needs.driver_agents.outputs.has_changes == 'true' }}
shell: bash
env:
CHANGED_DRIVER_AGENTS: ${{ needs.driver_agents.outputs.drivers }}
run: |
set -euo pipefail
TARGET_PLATFORM="${{ matrix.platform }}"
GOOS="${TARGET_PLATFORM%%/*}"
GOARCH="${TARGET_PLATFORM##*/}"
IFS=',' read -r -a DRIVERS <<< "$CHANGED_DRIVER_AGENTS"
OUTDIR="drivers/${{ matrix.os_name }}"
mkdir -p "$OUTDIR"
DUCKDB_WINDOWS_LIBRARY_VERSION="v1.4.4"
DUCKDB_WINDOWS_LIBRARY_URL="https://github.com/duckdb/duckdb/releases/download/${DUCKDB_WINDOWS_LIBRARY_VERSION}/libduckdb-windows-amd64.zip"
prepare_duckdb_windows_library() {
local lib_dir="$RUNNER_TEMP/duckdb-windows-${DUCKDB_WINDOWS_LIBRARY_VERSION}"
local zip_path="$RUNNER_TEMP/libduckdb-windows-amd64.zip"
if [ -f "$lib_dir/duckdb.dll" ] && [ -f "$lib_dir/duckdb.lib" ]; then
echo "$lib_dir"
return 0
fi
mkdir -p "$lib_dir"
curl -fsSL "$DUCKDB_WINDOWS_LIBRARY_URL" -o "$zip_path"
unzip -qo "$zip_path" -d "$lib_dir"
cp "$lib_dir/duckdb.lib" "$lib_dir/libduckdb.dll.a"
cp "$lib_dir/duckdb.lib" "$lib_dir/libduckdb.a"
echo "$lib_dir"
}
for DRIVER in "${DRIVERS[@]}"; do
BUILD_DRIVER="$DRIVER"
if [ "$DRIVER" = "doris" ]; then
BUILD_DRIVER="diros"
fi
if [ "$DRIVER" = "duckdb" ] && [ "$GOOS" = "windows" ] && [ "$GOARCH" != "amd64" ]; then
echo "⚠️ 跳过 DuckDB driver当前平台 ${GOOS}/${GOARCH} 不受支持,仅支持 windows/amd64"
continue
fi
TAG="gonavi_${BUILD_DRIVER}_driver"
BUILD_TAGS="$TAG"
OUTPUT="${DRIVER}-driver-agent-${GOOS}-${GOARCH}"
if [ "$GOOS" = "windows" ]; then
OUTPUT="${OUTPUT}.exe"
fi
OUTPUT_PATH="${OUTDIR}/${OUTPUT}"
DUCKDB_LIB_DIR=""
if [ "$DRIVER" = "duckdb" ] && [ "$GOOS" = "windows" ] && [ "$GOARCH" = "amd64" ]; then
DUCKDB_LIB_DIR="$(prepare_duckdb_windows_library)"
BUILD_TAGS="${BUILD_TAGS} duckdb_use_lib"
fi
echo "🔧 构建 ${OUTPUT_PATH} (tags=${BUILD_TAGS})"
if [ "$DRIVER" = "duckdb" ]; then
if [ -n "$DUCKDB_LIB_DIR" ]; then
CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" CGO_LDFLAGS="-L${DUCKDB_LIB_DIR} -lduckdb" PATH="${DUCKDB_LIB_DIR}:$PATH" go build \
-tags "${BUILD_TAGS}" \
-trimpath \
-ldflags "-s -w" \
-o "${OUTPUT_PATH}" \
./cmd/optional-driver-agent
cp "$DUCKDB_LIB_DIR/duckdb.dll" "$OUTDIR/duckdb.dll"
bash ./tools/compress-driver-artifact.sh "$OUTDIR/duckdb.dll" "$TARGET_PLATFORM" "${{ matrix.os_name }}/duckdb.dll"
else
CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build \
-tags "${BUILD_TAGS}" \
-trimpath \
-ldflags "-s -w" \
-o "${OUTPUT_PATH}" \
./cmd/optional-driver-agent
fi
else
CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build \
-tags "${BUILD_TAGS}" \
-trimpath \
-ldflags "-s -w" \
-o "${OUTPUT_PATH}" \
./cmd/optional-driver-agent
fi
bash ./tools/compress-driver-artifact.sh "${OUTPUT_PATH}" "$TARGET_PLATFORM" "${{ matrix.os_name }}/${OUTPUT}"
done
# macOS Packaging
- name: Package macOS DMG
if: contains(matrix.platform, 'darwin')
run: |
brew install create-dmg
VERSION="${{ steps.version.outputs.version }}"
cd build/bin
APP_PATH=$(find . -maxdepth 1 -name "*.app" | head -n 1)
if [ -z "$APP_PATH" ]; then
echo "❌ 未找到 .app 应用包!"
exit 1
fi
APP_NAME=$(basename "$APP_PATH")
APP_BIN=$(find "$APP_PATH/Contents/MacOS" -maxdepth 1 -type f | head -n 1)
if [ -z "$APP_BIN" ]; then
echo "❌ 未找到 macOS 应用主程序!"
exit 1
fi
echo " macOS 产物不执行 UPX 压缩,保留原始主程序。"
echo "🔏 正在进行 Ad-hoc 签名..."
if command -v xattr >/dev/null 2>&1; then
xattr -cr "$APP_NAME" || true
fi
codesign --force --deep --sign - "$APP_NAME"
DMG_NAME="${{ matrix.build_name }}.dmg"
FINAL_NAME="GoNavi-${VERSION}-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.dmg"
echo "📦 正在生成 DMG: $DMG_NAME..."
create-dmg \
--volname "GoNavi Dev Build" \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "$APP_NAME" 200 190 \
--hide-extension "$APP_NAME" \
--app-drop-link 600 185 \
"$DMG_NAME" \
"$APP_NAME"
VERIFY_MOUNT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gonavi-dev-verify.XXXXXX")
hdiutil attach -nobrowse -readonly -mountpoint "$VERIFY_MOUNT_DIR" "$DMG_NAME" >/dev/null
PACKAGED_APP=$(find "$VERIFY_MOUNT_DIR" -maxdepth 1 -name "*.app" | head -n 1)
if [ -z "$PACKAGED_APP" ]; then
echo "❌ DMG 内未找到 .app 应用包!"
hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true
exit 1
fi
codesign --verify --deep --strict --verbose=4 "$PACKAGED_APP"
hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true
mv "$DMG_NAME" "../../$FINAL_NAME"
# Windows Packaging
- name: Package Windows EXE
if: contains(matrix.platform, 'windows')
shell: pwsh
run: |
Set-Location build/bin
$version = "${{ steps.version.outputs.version }}"
$target = "${{ matrix.build_name }}"
$finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe"
if (Test-Path "$target.exe") {
$finalExe = "$target.exe"
} elseif (Test-Path "$target") {
Rename-Item -Path "$target" -NewName "$target.exe"
$finalExe = "$target.exe"
} else {
Write-Error "❌ 未找到构建产物 '$target'!"
exit 1
}
$isArm64Target = "${{ matrix.arch_name }}".ToLowerInvariant() -eq "arm64"
if ($isArm64Target) {
Write-Warning "⚠️ UPX 当前不支持 win64/arm64跳过压缩并保留原始 EXE。"
$LASTEXITCODE = 0
} else {
$upxCmd = Get-Command upx -ErrorAction SilentlyContinue
if ($null -eq $upxCmd) {
Write-Error "❌ 未找到 upx无法保证 Windows 产物经过压缩"
exit 1
}
$beforeBytes = (Get-Item -LiteralPath $finalExe).Length
Write-Host "🗜️ 使用 UPX 压缩 $finalExe ..."
& upx --best --lzma --force $finalExe | Out-Host
if ($LASTEXITCODE -ne 0) {
Write-Error "❌ UPX 压缩失败($LASTEXITCODE"
exit 1
}
& upx -t $finalExe | Out-Host
if ($LASTEXITCODE -ne 0) {
Write-Error "❌ UPX 校验失败($LASTEXITCODE"
exit 1
}
$afterBytes = (Get-Item -LiteralPath $finalExe).Length
if ($afterBytes -lt $beforeBytes) {
$savedBytes = $beforeBytes - $afterBytes
Write-Host ("✅ UPX 压缩完成:{0:N2}MB -> {1:N2}MB减少 {2:N2}MB" -f ($beforeBytes / 1MB), ($afterBytes / 1MB), ($savedBytes / 1MB))
} else {
Write-Host (" UPX 压缩完成:{0:N2}MB -> {1:N2}MB" -f ($beforeBytes / 1MB), ($afterBytes / 1MB))
}
}
Write-Host "📦 输出 Windows 可执行文件 $finalExeName..."
Copy-Item -LiteralPath $finalExe -Destination "..\\..\\$finalExeName" -Force
# Linux Packaging
- name: Package Linux
if: contains(matrix.platform, 'linux')
run: |
VERSION="${{ steps.version.outputs.version }}"
cd build/bin
TARGET="${{ matrix.build_name }}"
TAR_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.tar.gz"
APPIMAGE_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.AppImage"
if [ ! -f "$TARGET" ]; then
echo "❌ 未找到构建产物 '$TARGET'!"
exit 1
fi
chmod +x "$TARGET"
BEFORE_BYTES=$(wc -c <"$TARGET" | tr -d '[:space:]')
echo "🗜️ 正在使用 UPX 压缩 Linux 可执行文件: $TARGET ..."
upx --best --lzma --force "$TARGET"
upx -t "$TARGET"
AFTER_BYTES=$(wc -c <"$TARGET" | tr -d '[:space:]')
if [ "$AFTER_BYTES" -lt "$BEFORE_BYTES" ]; then
SAVED_BYTES=$((BEFORE_BYTES - AFTER_BYTES))
awk -v b="$BEFORE_BYTES" -v a="$AFTER_BYTES" -v s="$SAVED_BYTES" 'BEGIN { printf "✅ Linux UPX 压缩完成:%.2fMB -> %.2fMB,减少 %.2fMB\n", b/1024/1024, a/1024/1024, s/1024/1024 }'
else
awk -v b="$BEFORE_BYTES" -v a="$AFTER_BYTES" 'BEGIN { printf " Linux UPX 压缩完成:%.2fMB -> %.2fMB\n", b/1024/1024, a/1024/1024 }'
fi
echo "📦 正在打包 $TAR_NAME..."
tar -czvf "$TAR_NAME" "$TARGET"
mv "$TAR_NAME" ../../
if [ -f /tmp/skip-appimage ]; then
echo "⚠️ 跳过 AppImage 打包"
exit 0
fi
echo "📦 正在生成 AppImage..."
mkdir -p AppDir/usr/bin
mkdir -p AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
cp "$TARGET" AppDir/usr/bin/gonavi
printf '%s\n' \
'[Desktop Entry]' \
'Name=GoNavi' \
'Exec=gonavi' \
'Icon=gonavi' \
'Type=Application' \
'Categories=Development;Database;' \
'Comment=Database Management Tool' \
> AppDir/usr/share/applications/gonavi.desktop
cp AppDir/usr/share/applications/gonavi.desktop AppDir/gonavi.desktop
if [ -f "../../build/appicon.png" ]; then
cp "../../build/appicon.png" AppDir/usr/share/icons/hicolor/256x256/apps/gonavi.png
cp "../../build/appicon.png" AppDir/gonavi.png
else
convert -size 256x256 xc:#336791 -fill white -gravity center -pointsize 48 -annotate 0 "GoNavi" AppDir/gonavi.png || \
wget -q "https://via.placeholder.com/256/336791/FFFFFF?text=GoNavi" -O AppDir/gonavi.png || \
touch AppDir/gonavi.png
cp AppDir/gonavi.png AppDir/usr/share/icons/hicolor/256x256/apps/gonavi.png
fi
export DEPLOY_GTK_VERSION=3
/tmp/linuxdeploy --appdir AppDir --plugin gtk --output appimage || {
echo "⚠️ AppImage 生成失败,但 tar.gz 已成功生成"
exit 0
}
mv GoNavi*.AppImage "$APPIMAGE_NAME" 2>/dev/null || {
echo "⚠️ AppImage 重命名失败"
exit 0
}
if [ -f "$APPIMAGE_NAME" ]; then
mv "$APPIMAGE_NAME" ../../
echo "✅ AppImage 生成成功"
fi
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: dev-build-artifacts-${{ strategy.job-index }}
path: |
GoNavi-*.dmg
GoNavi-*.exe
GoNavi-*.tar.gz
GoNavi-*.AppImage
drivers/**
retention-days: 7
# 汇总所有产物并发布为 Pre-release
release:
name: Publish Dev Pre-release
needs:
- build
- driver_agents
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
path: release-assets
pattern: dev-build-artifacts-*
merge-multiple: true
- name: List Assets
run: ls -R release-assets
- name: Complete Driver Agent Assets
if: needs.driver_agents.outputs.has_changes == 'true'
env:
DRIVER_RELEASE_TOKEN: ${{ secrets.DRIVER_RELEASE_TOKEN }}
run: |
python3 tools/complete-driver-release-assets.py \
--assets-dir release-assets \
--source "${{ needs.driver_agents.outputs.release_source }}" \
--require-complete
- name: Package Driver Agents Bundle
id: driver_assets
shell: bash
run: |
set -euo pipefail
cd release-assets
if [ ! -d drivers ]; then
echo "⚠️ 未找到 drivers 目录,跳过驱动总包打包"
echo "has_driver_assets=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ -z "$(find drivers -type f 2>/dev/null | head -n 1)" ]; then
echo "⚠️ drivers 目录为空,跳过驱动总包打包"
rm -rf drivers
echo "has_driver_assets=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "📦 打包驱动总包GoNavi-DriverAgents.zip"
python3 - <<'PY'
import json
import os
import shutil
import zipfile
from pathlib import Path
out_name = "GoNavi-DriverAgents.zip"
index_name = "GoNavi-DriverAgents-Index.json"
base = Path("drivers")
driver_release_dir = Path("../driver-release-assets")
if driver_release_dir.exists():
shutil.rmtree(driver_release_dir)
driver_release_dir.mkdir(parents=True, exist_ok=True)
out_path = driver_release_dir / out_name
index_path = driver_release_dir / index_name
if out_path.exists():
out_path.unlink()
if index_path.exists():
index_path.unlink()
size_index = {}
standalone_assets = []
with zipfile.ZipFile(out_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for p in sorted(base.rglob("*")):
if not p.is_file():
continue
arcname = p.relative_to(base).as_posix()
if p.name in size_index:
raise RuntimeError(f"driver asset name conflict: {p.name}")
zf.write(p, arcname)
size_index[p.name] = p.stat().st_size
standalone_path = driver_release_dir / p.name
if standalone_path.exists():
raise RuntimeError(f"release asset already exists: {standalone_path}")
shutil.copy2(p, standalone_path)
standalone_assets.append(standalone_path.name)
index_path.write_text(
json.dumps({"assets": size_index}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"created {out_name} size={out_path.stat().st_size} bytes")
print(f"created {index_name} entries={len(size_index)}")
print(f"published standalone driver assets={len(standalone_assets)}")
PY
rm -rf drivers
echo "has_driver_assets=true" >> "$GITHUB_OUTPUT"
- name: Generate SHA256SUMS
shell: bash
run: |
cd release-assets
FILES=()
while IFS= read -r file; do
if [ -n "$file" ]; then
FILES+=("$file")
fi
done < <(find . -maxdepth 1 -type f ! -name SHA256SUMS -exec basename {} \; | sort)
if [ ${#FILES[@]} -eq 0 ]; then
echo "⚠️ 未找到可签名资产,生成空 SHA256SUMS"
: > SHA256SUMS
else
sha256sum "${FILES[@]}" > SHA256SUMS
fi
- name: Generate Driver SHA256SUMS
if: steps.driver_assets.outputs.has_driver_assets == 'true'
shell: bash
run: |
cd driver-release-assets
FILES=()
while IFS= read -r file; do
if [ -n "$file" ]; then
FILES+=("$file")
fi
done < <(find . -maxdepth 1 -type f ! -name SHA256SUMS -exec basename {} \; | sort)
if [ ${#FILES[@]} -eq 0 ]; then
echo "❌ 未找到驱动发布资产"
exit 1
fi
sha256sum "${FILES[@]}" > SHA256SUMS
- name: Generate Dev Version
id: version
run: |
SHORT_SHA="${GITHUB_SHA:0:7}"
DEV_VERSION="dev-${SHORT_SHA}"
echo "version=${DEV_VERSION}" >> "$GITHUB_OUTPUT"
- name: Format Build Time
id: build_time
shell: bash
run: |
python3 - <<'PY' >> "$GITHUB_OUTPUT"
from datetime import datetime, timezone, timedelta
raw = "${{ github.event.head_commit.timestamp }}"
dt = datetime.fromisoformat(raw)
china_tz = timezone(timedelta(hours=8))
formatted = dt.astimezone(china_tz).strftime("%Y-%m-%d %H:%M:%S")
print(f"display={formatted}")
PY
# 删除旧的 dev pre-release保持只有最新一个
- name: Reset Previous Dev Release
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tag = 'dev-latest';
const ref = `tags/${tag}`;
const { owner, repo } = context.repo;
const releases = await github.paginate(github.rest.repos.listReleases, {
owner,
repo,
per_page: 100,
});
const matchedReleases = releases.filter((release) => release.tag_name === tag);
if (matchedReleases.length === 0) {
core.info(`No existing releases found for tag ${tag}`);
} else {
for (const release of matchedReleases) {
core.info(`Deleting release ${release.id} (${release.name || 'unnamed'}) for tag ${tag}`);
await github.rest.repos.deleteRelease({
owner,
repo,
release_id: release.id,
});
}
}
try {
await github.rest.git.deleteRef({
owner,
repo,
ref,
});
core.info(`Deleted ref ${ref}`);
} catch (error) {
const message = String(error.response?.data?.message || error.message || '');
if (error.status === 404 || (error.status === 422 && message.includes('Reference does not exist'))) {
core.info(`No existing ref found for ${ref}`);
} else {
throw error;
}
}
- name: Reset Previous Driver Dev Release
if: steps.driver_assets.outputs.has_driver_assets == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DRIVER_RELEASE_TOKEN }}
script: |
const tag = 'dev-latest';
const ref = `tags/${tag}`;
const [owner, repo] = 'Syngnat/GoNavi-DriverAgents'.split('/');
const releases = await github.paginate(github.rest.repos.listReleases, {
owner,
repo,
per_page: 100,
});
const matchedReleases = releases.filter((release) => release.tag_name === tag);
if (matchedReleases.length === 0) {
core.info(`No existing driver releases found for tag ${tag}`);
} else {
for (const release of matchedReleases) {
core.info(`Deleting driver release ${release.id} (${release.name || 'unnamed'}) for tag ${tag}`);
await github.rest.repos.deleteRelease({
owner,
repo,
release_id: release.id,
});
}
}
try {
await github.rest.git.deleteRef({
owner,
repo,
ref,
});
core.info(`Deleted driver ref ${ref}`);
} catch (error) {
const message = String(error.response?.data?.message || error.message || '');
if (error.status === 404 || (error.status === 422 && message.includes('Reference does not exist'))) {
core.info(`No existing driver ref found for ${ref}`);
} else {
throw error;
}
}
- name: Create Dev Driver Agents Pre-release
if: steps.driver_assets.outputs.has_driver_assets == 'true'
uses: softprops/action-gh-release@v2
with:
repository: Syngnat/GoNavi-DriverAgents
tag_name: dev-latest
name: "GoNavi Driver Agents (${{ steps.version.outputs.version }})"
files: driver-release-assets/*
fail_on_unmatched_files: true
prerelease: true
draft: false
make_latest: false
body: |
GoNavi dev driver-agent assets.
**版本**: `${{ steps.version.outputs.version }}`
**来源仓库**: `${{ github.repository }}`
**提交**: [`${{ github.sha }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})
token: ${{ secrets.DRIVER_RELEASE_TOKEN }}
- name: Create Dev Pre-release
uses: softprops/action-gh-release@v2
with:
tag_name: dev-latest
name: "🧪 Dev Build (${{ steps.version.outputs.version }})"
target_commitish: ${{ github.sha }}
files: release-assets/*
prerelease: true
draft: false
body: |
## 🧪 测试版本 (Dev Build)
**版本**: `${{ steps.version.outputs.version }}`
**分支**: `dev`
**提交**: [`${{ github.sha }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }})
**构建时间**: ${{ steps.build_time.outputs.display }}
> ⚠️ 这是开发测试版本,仅供内部测试使用,不建议用于生产环境。
> 每次 push 到 `dev` 分支会自动覆盖此 release。
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}