feat: 增加跨平台打包脚本
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
set SCRIPT_DIR=%~dp0
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%build.ps1" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
@@ -0,0 +1,326 @@
|
||||
param(
|
||||
[ValidateSet("server", "desktop", "docker", "all")]
|
||||
[string]$Target = "all",
|
||||
|
||||
[ValidateSet("current", "windows", "linux", "macos")]
|
||||
[string]$Platform = "current",
|
||||
|
||||
[switch]$SkipDockerBuild,
|
||||
[switch]$SkipDesktopBuild,
|
||||
[switch]$NoArchive,
|
||||
[switch]$Clean,
|
||||
[Alias("h", "?")]
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Root = Resolve-Path (Join-Path $ScriptDir "..")
|
||||
$DistDir = Join-Path $Root "dist"
|
||||
$PackageDir = Join-Path $DistDir "packages"
|
||||
$PyInstallerDir = Join-Path $Root "build\pyinstaller"
|
||||
$FrontendDir = Join-Path $Root "frontend"
|
||||
$ServerName = "capareport-server"
|
||||
$AppVersion = "2.0.2"
|
||||
$DesktopApiBase = "http://127.0.0.1:19082"
|
||||
|
||||
function Write-Step($Message) {
|
||||
Write-Host ""
|
||||
Write-Host "==> $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Info($Message) {
|
||||
Write-Host " $Message"
|
||||
}
|
||||
|
||||
function Show-Usage {
|
||||
Write-Host "Usage: scripts\build.bat [server|desktop|docker|all] [options]"
|
||||
Write-Host ""
|
||||
Write-Host "Options:"
|
||||
Write-Host " -Platform current|windows|linux|macos"
|
||||
Write-Host " -NoArchive"
|
||||
Write-Host " -Clean"
|
||||
Write-Host " -SkipDockerBuild"
|
||||
Write-Host " -SkipDesktopBuild"
|
||||
}
|
||||
|
||||
function Invoke-Checked($Command, $WorkingDirectory = $Root) {
|
||||
Write-Info $Command
|
||||
Push-Location $WorkingDirectory
|
||||
try {
|
||||
cmd /c $Command
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Command failed with exit code $LASTEXITCODE`: $Command"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
function Get-CurrentPlatform {
|
||||
if ($Platform -ne "current") {
|
||||
return $Platform
|
||||
}
|
||||
if ($env:OS -eq "Windows_NT") {
|
||||
return "windows"
|
||||
}
|
||||
$uname = (uname -s 2>$null)
|
||||
if ($uname -eq "Darwin") {
|
||||
return "macos"
|
||||
}
|
||||
return "linux"
|
||||
}
|
||||
|
||||
function Get-ExecutableName($PlatformName) {
|
||||
if ($PlatformName -eq "windows") {
|
||||
return "$ServerName.exe"
|
||||
}
|
||||
return $ServerName
|
||||
}
|
||||
|
||||
function Get-RustHostTriple {
|
||||
$output = rustc -vV
|
||||
foreach ($line in $output) {
|
||||
if ($line -like "host:*") {
|
||||
return $line.Substring(5).Trim()
|
||||
}
|
||||
}
|
||||
throw "Unable to detect Rust host triple"
|
||||
}
|
||||
|
||||
function Test-TauriCli {
|
||||
cmd /c "cargo tauri --version >nul 2>nul"
|
||||
return $LASTEXITCODE -eq 0
|
||||
}
|
||||
|
||||
function Ensure-PythonEnvironment {
|
||||
Write-Step "Preparing Python environment"
|
||||
$venvPython = Join-Path $Root ".venv\Scripts\python.exe"
|
||||
if (!(Test-Path $venvPython)) {
|
||||
Invoke-Checked "uv venv" $Root
|
||||
}
|
||||
Invoke-Checked "uv pip install -r requirements.txt" $Root
|
||||
Invoke-Checked "uv pip install pyinstaller" $Root
|
||||
}
|
||||
|
||||
function Build-Frontend($ApiBase) {
|
||||
Write-Step "Building frontend"
|
||||
if (!(Test-Path (Join-Path $FrontendDir "node_modules"))) {
|
||||
Invoke-Checked "npm ci" $FrontendDir
|
||||
}
|
||||
|
||||
$previousApiBase = $env:VITE_API_BASE
|
||||
if ($ApiBase) {
|
||||
$env:VITE_API_BASE = $ApiBase
|
||||
}
|
||||
else {
|
||||
Remove-Item Env:\VITE_API_BASE -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-Checked "npm run build" $FrontendDir
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $previousApiBase) {
|
||||
$env:VITE_API_BASE = $previousApiBase
|
||||
}
|
||||
else {
|
||||
Remove-Item Env:\VITE_API_BASE -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Build-ServerBinary([switch]$OneFile) {
|
||||
Ensure-PythonEnvironment
|
||||
Write-Step "Building server binary"
|
||||
Remove-Item -LiteralPath $PyInstallerDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
$python = Join-Path $Root ".venv\Scripts\python.exe"
|
||||
$previousOneFile = $env:CAPAREPORT_ONEFILE
|
||||
if ($OneFile) {
|
||||
$env:CAPAREPORT_ONEFILE = "1"
|
||||
}
|
||||
else {
|
||||
Remove-Item Env:\CAPAREPORT_ONEFILE -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-Checked "`"$python`" -m PyInstaller --clean --noconfirm --workpath build\pyinstaller\work --distpath build\pyinstaller\dist build\capareport-server.spec" $Root
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $previousOneFile) {
|
||||
$env:CAPAREPORT_ONEFILE = $previousOneFile
|
||||
}
|
||||
else {
|
||||
Remove-Item Env:\CAPAREPORT_ONEFILE -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
if ($OneFile) {
|
||||
$binaryDir = Join-Path $PyInstallerDir "dist"
|
||||
$binaryPath = Join-Path $binaryDir (Get-ExecutableName (Get-CurrentPlatform))
|
||||
if (!(Test-Path $binaryPath)) {
|
||||
throw "PyInstaller output not found: $binaryPath"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$binaryDir = Join-Path $PyInstallerDir "dist\$ServerName"
|
||||
if (!(Test-Path $binaryDir)) {
|
||||
throw "PyInstaller output not found: $binaryDir"
|
||||
}
|
||||
}
|
||||
return $binaryDir
|
||||
}
|
||||
|
||||
function Write-TextFile($Path, $Content, [switch]$Lf) {
|
||||
$parent = Split-Path -Parent $Path
|
||||
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
||||
if ($Lf) {
|
||||
[System.IO.File]::WriteAllText($Path, $Content.Replace("`r`n", "`n"), [System.Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
else {
|
||||
[System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
}
|
||||
|
||||
function Copy-RuntimeFiles($OutputDir) {
|
||||
Copy-Item -Path (Join-Path $Root "Configure.json") -Destination (Join-Path $OutputDir "Configure.json") -Force
|
||||
Copy-Item -Path (Join-Path $Root "ReportScript.sql") -Destination (Join-Path $OutputDir "ReportScript.sql") -Force
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $OutputDir "cache") | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $OutputDir "logs") | Out-Null
|
||||
|
||||
$frontendDist = Join-Path $FrontendDir "dist"
|
||||
if (!(Test-Path $frontendDist)) {
|
||||
throw "Frontend dist not found: $frontendDist"
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $OutputDir "frontend") | Out-Null
|
||||
Copy-Item -Path $frontendDist -Destination (Join-Path $OutputDir "frontend") -Recurse -Force
|
||||
}
|
||||
|
||||
function Write-ServerLaunchers($OutputDir, $PlatformName) {
|
||||
$exe = Get-ExecutableName $PlatformName
|
||||
$bat = @"
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0"
|
||||
if not exist cache mkdir cache
|
||||
if not exist logs mkdir logs
|
||||
echo Starting CapacityReport server...
|
||||
echo URL: http://localhost:9081
|
||||
"%~dp0$exe" --host 0.0.0.0 --port 9081
|
||||
pause
|
||||
"@
|
||||
Write-TextFile (Join-Path $OutputDir "run.bat") $bat
|
||||
|
||||
$sh = @'
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
mkdir -p cache logs
|
||||
chmod +x "./__SERVER_EXE__" 2>/dev/null || true
|
||||
echo "Starting CapacityReport server..."
|
||||
echo "URL: http://localhost:9081"
|
||||
exec "./__SERVER_EXE__" --host 0.0.0.0 --port 9081
|
||||
'@
|
||||
$sh = $sh.Replace("__SERVER_EXE__", $exe)
|
||||
Write-TextFile (Join-Path $OutputDir "start.sh") $sh -Lf
|
||||
}
|
||||
|
||||
function Build-ServerPortable {
|
||||
$platformName = Get-CurrentPlatform
|
||||
if ($platformName -ne "windows" -and $env:OS -eq "Windows_NT") {
|
||||
Write-Host "Native $platformName portable packages must be built on $platformName." -ForegroundColor Yellow
|
||||
Write-Host "The Windows build will continue for the current host." -ForegroundColor Yellow
|
||||
$platformName = "windows"
|
||||
}
|
||||
|
||||
Build-Frontend $null
|
||||
$binaryDir = Build-ServerBinary
|
||||
|
||||
$outputDir = Join-Path $PackageDir "CapacityReport-Server-$platformName-x64"
|
||||
Remove-Item -LiteralPath $outputDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
|
||||
|
||||
Copy-Item -Path (Join-Path $binaryDir "*") -Destination $outputDir -Recurse -Force
|
||||
Copy-RuntimeFiles $outputDir
|
||||
Write-ServerLaunchers $outputDir $platformName
|
||||
|
||||
if (!$NoArchive) {
|
||||
$archive = "$outputDir.zip"
|
||||
Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
|
||||
Compress-Archive -Path (Join-Path $outputDir "*") -DestinationPath $archive -Force
|
||||
Write-Info "Archive: $archive"
|
||||
}
|
||||
|
||||
Write-Info "Server package: $outputDir"
|
||||
}
|
||||
|
||||
function Build-DockerImage {
|
||||
if ($SkipDockerBuild) {
|
||||
Write-Host "Docker build skipped."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Step "Building Docker image"
|
||||
Invoke-Checked "docker build --progress=plain -f build\Dockerfile -t capacity-report-app:latest ." $Root
|
||||
Invoke-Checked "docker compose -f build\docker-compose.yml config" $Root
|
||||
}
|
||||
|
||||
function Build-DesktopPackage {
|
||||
if ($SkipDesktopBuild) {
|
||||
Write-Host "Desktop build skipped."
|
||||
return
|
||||
}
|
||||
|
||||
if (!(Get-Command rustc -ErrorAction SilentlyContinue)) {
|
||||
throw "Rust is required for the Tauri desktop build"
|
||||
}
|
||||
|
||||
Build-Frontend $DesktopApiBase
|
||||
$binaryDir = Build-ServerBinary -OneFile
|
||||
$platformName = Get-CurrentPlatform
|
||||
$exe = Get-ExecutableName $platformName
|
||||
$triple = Get-RustHostTriple
|
||||
$sidecarDir = Join-Path $Root "src-tauri\binaries"
|
||||
New-Item -ItemType Directory -Force -Path $sidecarDir | Out-Null
|
||||
$sidecarName = if ($platformName -eq "windows") { "$ServerName-$triple.exe" } else { "$ServerName-$triple" }
|
||||
Copy-Item -Path (Join-Path $binaryDir $exe) -Destination (Join-Path $sidecarDir $sidecarName) -Force
|
||||
|
||||
if (!(Test-TauriCli)) {
|
||||
Write-Step "Installing Tauri CLI"
|
||||
Invoke-Checked "cargo install tauri-cli --locked" $Root
|
||||
}
|
||||
|
||||
Write-Step "Building Tauri desktop package"
|
||||
Invoke-Checked "cargo tauri build" (Join-Path $Root "src-tauri")
|
||||
}
|
||||
|
||||
if ($Help) {
|
||||
Show-Usage
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Clean) {
|
||||
Write-Step "Cleaning build output"
|
||||
Remove-Item -LiteralPath $DistDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath $PyInstallerDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath (Join-Path $Root "src-tauri\binaries") -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $PackageDir | Out-Null
|
||||
|
||||
switch ($Target) {
|
||||
"server" { Build-ServerPortable }
|
||||
"docker" { Build-DockerImage }
|
||||
"desktop" { Build-DesktopPackage }
|
||||
"all" {
|
||||
Build-ServerPortable
|
||||
Build-DockerImage
|
||||
Build-DesktopPackage
|
||||
}
|
||||
}
|
||||
|
||||
Write-Step "Build finished"
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
TARGET="${1:-server}"
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
DESKTOP_API_BASE="http://127.0.0.1:19082"
|
||||
cd "$ROOT"
|
||||
|
||||
log() {
|
||||
printf '\n==> %s\n' "$1"
|
||||
}
|
||||
|
||||
run() {
|
||||
printf ' %s\n' "$*"
|
||||
"$@"
|
||||
}
|
||||
|
||||
platform_name() {
|
||||
case "$(uname -s)" in
|
||||
Darwin) printf 'macos' ;;
|
||||
Linux) printf 'linux' ;;
|
||||
*) printf 'windows' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_python() {
|
||||
log "Preparing Python environment"
|
||||
if [ ! -x ".venv/bin/python" ]; then
|
||||
run uv venv
|
||||
fi
|
||||
run uv pip install -r requirements.txt
|
||||
run uv pip install pyinstaller
|
||||
}
|
||||
|
||||
build_frontend() {
|
||||
log "Building frontend"
|
||||
if [ ! -d "frontend/node_modules" ]; then
|
||||
(cd frontend && run npm ci)
|
||||
fi
|
||||
if [ "${1:-}" = "desktop" ]; then
|
||||
(cd frontend && VITE_API_BASE="$DESKTOP_API_BASE" run npm run build)
|
||||
else
|
||||
(cd frontend && run npm run build)
|
||||
fi
|
||||
}
|
||||
|
||||
build_server_binary() {
|
||||
mode="${1:-onedir}"
|
||||
ensure_python
|
||||
log "Building server binary"
|
||||
rm -rf build/pyinstaller
|
||||
if [ "$mode" = "onefile" ]; then
|
||||
CAPAREPORT_ONEFILE=1
|
||||
export CAPAREPORT_ONEFILE
|
||||
run .venv/bin/python -m PyInstaller --clean --noconfirm \
|
||||
--workpath build/pyinstaller/work \
|
||||
--distpath build/pyinstaller/dist \
|
||||
build/capareport-server.spec
|
||||
unset CAPAREPORT_ONEFILE
|
||||
else
|
||||
run .venv/bin/python -m PyInstaller --clean --noconfirm \
|
||||
--workpath build/pyinstaller/work \
|
||||
--distpath build/pyinstaller/dist \
|
||||
build/capareport-server.spec
|
||||
fi
|
||||
}
|
||||
|
||||
write_launchers() {
|
||||
out_dir="$1"
|
||||
exe_name="$2"
|
||||
cat > "$out_dir/start.sh" <<EOF
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
SCRIPT_DIR="\$(CDPATH= cd -- "\$(dirname -- "\$0")" && pwd)"
|
||||
cd "\$SCRIPT_DIR"
|
||||
mkdir -p cache logs
|
||||
chmod +x "./$exe_name" 2>/dev/null || true
|
||||
echo "Starting CapacityReport server..."
|
||||
echo "URL: http://localhost:9081"
|
||||
exec "./$exe_name" --host 0.0.0.0 --port 9081
|
||||
EOF
|
||||
chmod +x "$out_dir/start.sh"
|
||||
}
|
||||
|
||||
build_server() {
|
||||
platform="$(platform_name)"
|
||||
build_frontend server
|
||||
build_server_binary onedir
|
||||
|
||||
out_dir="dist/packages/CapacityReport-Server-$platform-x64"
|
||||
rm -rf "$out_dir"
|
||||
mkdir -p "$out_dir/cache" "$out_dir/logs" "$out_dir/frontend"
|
||||
cp -R build/pyinstaller/dist/capareport-server/. "$out_dir/"
|
||||
cp Configure.json ReportScript.sql "$out_dir/"
|
||||
cp -R frontend/dist "$out_dir/frontend/"
|
||||
write_launchers "$out_dir" "capareport-server"
|
||||
(cd "$out_dir/.." && tar -czf "$(basename "$out_dir").tar.gz" "$(basename "$out_dir")")
|
||||
log "Server package: $out_dir"
|
||||
}
|
||||
|
||||
build_docker() {
|
||||
log "Building Docker image"
|
||||
run docker build --progress=plain -f build/Dockerfile -t capacity-report-app:latest .
|
||||
run docker compose -f build/docker-compose.yml config
|
||||
}
|
||||
|
||||
build_desktop() {
|
||||
build_frontend desktop
|
||||
build_server_binary onefile
|
||||
triple="$(rustc -vV | awk '/^host:/ {print $2}')"
|
||||
mkdir -p src-tauri/binaries
|
||||
cp build/pyinstaller/dist/capareport-server "src-tauri/binaries/capareport-server-$triple"
|
||||
if ! cargo tauri --version >/dev/null 2>&1; then
|
||||
run cargo install tauri-cli --locked
|
||||
fi
|
||||
(cd src-tauri && run cargo tauri build)
|
||||
}
|
||||
|
||||
case "$TARGET" in
|
||||
server) build_server ;;
|
||||
docker) build_docker ;;
|
||||
desktop) build_desktop ;;
|
||||
all) build_server; build_docker; build_desktop ;;
|
||||
*) echo "Usage: scripts/build.sh [server|docker|desktop|all]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
log "Build finished"
|
||||
Reference in New Issue
Block a user