refactor: 统一构建产物输出目录

This commit is contained in:
2026-05-20 10:59:16 +08:00
parent 6e0abac30b
commit fcac708814
13 changed files with 181 additions and 133 deletions
-3
View File
@@ -18,14 +18,11 @@ dist
uploads uploads
CapacityReportData CapacityReportData
auth.ini auth.ini
Configure.json
*.log *.log
*.tmp *.tmp
*.temp *.temp
*.bak *.bak
build/temp
build/pyinstaller
target target
src-tauri/target src-tauri/target
src-tauri/binaries src-tauri/binaries
-2
View File
@@ -35,8 +35,6 @@ dist/
cache/ cache/
logs/ logs/
uploads/ uploads/
build/temp/
build/pyinstaller/
src-tauri/target/ src-tauri/target/
src-tauri/binaries/ src-tauri/binaries/
CapacityReportData/ CapacityReportData/
+16 -8
View File
@@ -37,7 +37,8 @@ CapaReport/
├─ frontend/ # Vue 前端 ├─ frontend/ # Vue 前端
├─ src-tauri/ # Tauri 桌面壳 ├─ src-tauri/ # Tauri 桌面壳
├─ scripts/ # 一键构建脚本 ├─ scripts/ # 一键构建脚本
├─ build/ # Docker 和 PyInstaller 配置 ├─ packaging/ # Docker、Compose、PyInstaller 配置
├─ dist/ # 一键构建后的最终产物
├─ docs/project_context.md # 项目维护记录 ├─ docs/project_context.md # 项目维护记录
├─ Configure.json # 应用配置 ├─ Configure.json # 应用配置
├─ ReportScript.sql # SQL 处理脚本 ├─ ReportScript.sql # SQL 处理脚本
@@ -117,6 +118,8 @@ scripts\build.bat docker -SkipDockerBuild
scripts\build.bat all -SkipDesktopBuild scripts\build.bat all -SkipDesktopBuild
``` ```
构建完成后只保留 `dist/` 下的最终产物;`dist/.tmp`、`frontend/dist`、`src-tauri/target`、`src-tauri/binaries` 等中间产物会自动清理。
### Server Portable ### Server Portable
```bat ```bat
@@ -126,8 +129,8 @@ scripts\build.bat server
输出: 输出:
```text ```text
dist\packages\CapacityReport-Server-windows-x64\ dist\server\CapacityReport-Server-windows-x64\
dist\packages\CapacityReport-Server-windows-x64.zip dist\server\CapacityReport-Server-windows-x64.zip
``` ```
便携版内包含后端可执行文件、前端构建产物、`Configure.json`、`ReportScript.sql`、`cache/`、`logs/` 和启动脚本。默认监听端口为 `9081`。 便携版内包含后端可执行文件、前端构建产物、`Configure.json`、`ReportScript.sql`、`cache/`、`logs/` 和启动脚本。默认监听端口为 `9081`。
@@ -141,8 +144,8 @@ scripts\build.bat desktop
输出: 输出:
```text ```text
src-tauri\target\release\bundle\msi\*.msi dist\desktop\*.msi
src-tauri\target\release\bundle\nsis\*-setup.exe dist\desktop\*-setup.exe
``` ```
桌面版使用 Tauri 启动 Python sidecar。sidecar 默认监听 `127.0.0.1:19082`,运行数据写入系统 app data 目录,不写入安装目录。 桌面版使用 Tauri 启动 Python sidecar。sidecar 默认监听 `127.0.0.1:19082`,运行数据写入系统 app data 目录,不写入安装目录。
@@ -159,16 +162,21 @@ cargo install tauri-cli --locked
scripts\build.bat docker scripts\build.bat docker
``` ```
输出镜像: 输出:
```text ```text
capacity-report-app:latest capacity-report-app:latest
dist\docker\capacity-report-app-latest.tar
dist\docker\docker-compose.yml
dist\docker\Configure.json
dist\docker\ReportScript.sql
``` ```
启动: 启动:
```bat ```bat
docker compose -f build\docker-compose.yml up -d docker load -i dist\docker\capacity-report-app-latest.tar
docker compose -f dist\docker\docker-compose.yml up -d
``` ```
访问: 访问:
@@ -180,7 +188,7 @@ http://localhost:19081
停止: 停止:
```bat ```bat
docker compose -f build\docker-compose.yml down docker compose -f dist\docker\docker-compose.yml down
``` ```
## Linux / macOS 构建 ## Linux / macOS 构建
-52
View File
@@ -1,52 +0,0 @@
# Git
.git
.gitignore
# Python cache and virtual environments
__pycache__/
*.py[cod]
.venv/
venv/
ENV/
env/
# Frontend local artifacts
frontend/node_modules/
frontend/dist/
frontend/.vite/
# Runtime and package output
cache/
logs/
dist/
uploads/
CapacityReportData/
error.log
*.log
# Build helper output
build/temp/
# Local-only files
.env
.env.local
auth.ini
# Deployment files copied separately by build.py
docker-compose*.yml
Dockerfile*
.dockerignore
# MySQL data
mysql/
# Temporary and OS files
*.tmp
*.temp
.temp/
.DS_Store
Thumbs.db
# Documentation is not required in the runtime image.
*.md
!README.md
-26
View File
@@ -1,26 +0,0 @@
# Build Notes
Use the root scripts for release builds:
```powershell
scripts\build.bat server
scripts\build.bat docker
scripts\build.bat desktop
```
On Linux or macOS:
```bash
sh scripts/build.sh server
sh scripts/build.sh docker
sh scripts/build.sh desktop
```
## Targets
- `server`: builds a portable server package for the current OS.
- `docker`: builds the Docker image and validates the compose file.
- `desktop`: builds a Tauri desktop package with the Python server sidecar.
- `all`: runs all targets.
Native server and desktop packages must be built on the target OS. Build Windows packages on Windows, Linux packages on Linux, and macOS packages on macOS. Docker images can be built from the Windows development host.
+9 -2
View File
@@ -387,7 +387,7 @@
- `app/config.py` supports `CAPAREPORT_BASE_DIR`; frozen PyInstaller server builds default `BASE_DIR` to the executable directory. The Tauri sidecar sets `CAPAREPORT_BASE_DIR` to the app data directory so runtime files are not written into the install directory. - `app/config.py` supports `CAPAREPORT_BASE_DIR`; frozen PyInstaller server builds default `BASE_DIR` to the executable directory. The Tauri sidecar sets `CAPAREPORT_BASE_DIR` to the app data directory so runtime files are not written into the install directory.
- Tauri startup creates app-data `cache/` and `logs/`, then copies bundled `Configure.json` and `ReportScript.sql` on first run. Resource lookup supports both normal Tauri resources and the `_up_` directory generated for bundled `../` resources. - Tauri startup creates app-data `cache/` and `logs/`, then copies bundled `Configure.json` and `ReportScript.sql` on first run. Resource lookup supports both normal Tauri resources and the `_up_` directory generated for bundled `../` resources.
- Windows desktop shutdown uses `taskkill /F /T /PID` before killing the shell child, which avoids PyInstaller one-file sidecar process leftovers. - Windows desktop shutdown uses `taskkill /F /T /PID` before killing the shell child, which avoids PyInstaller one-file sidecar process leftovers.
- Docker build now copies only required app files and frontend build output. `.dockerignore` excludes local dependencies, caches, build output, and local config; `build/docker-compose.yml` mounts paths relative to the `build/` directory. - Docker build now copies only required app files and frontend build output. `.dockerignore` excludes local dependencies, caches, and generated output; deployment compose files are emitted under `dist/docker/`.
- `app/main.py` accepts `--host` and `--port` and exposes `run_server()` so portable launchers, Docker, and the Tauri sidecar share the same backend entry point. - `app/main.py` accepts `--host` and `--port` and exposes `run_server()` so portable launchers, Docker, and the Tauri sidecar share the same backend entry point.
- Windows verification completed: `scripts\build.bat server -NoArchive`, `scripts\build.bat server`, `scripts\build.bat docker`, and `scripts\build.bat desktop`. Server portable `/health`, Docker container `/health`, and desktop sidecar `/health` all returned HTTP 200; desktop first run also copied config and SQL into app data. - Windows verification completed: `scripts\build.bat server -NoArchive`, `scripts\build.bat server`, `scripts\build.bat docker`, and `scripts\build.bat desktop`. Server portable `/health`, Docker container `/health`, and desktop sidecar `/health` all returned HTTP 200; desktop first run also copied config and SQL into app data.
- Cleanup verification completed: `.venv\Scripts\python.exe -m compileall app`, `npm run build`, PowerShell AST parse for `scripts/build.ps1`, Docker-hosted `sh -n scripts/build.sh`, and `cargo check --manifest-path src-tauri\Cargo.toml` with a temporary sidecar placeholder all passed. Linux/macOS native server and desktop packages still need native OS verification. - Cleanup verification completed: `.venv\Scripts\python.exe -m compileall app`, `npm run build`, PowerShell AST parse for `scripts/build.ps1`, Docker-hosted `sh -n scripts/build.sh`, and `cargo check --manifest-path src-tauri\Cargo.toml` with a temporary sidecar placeholder all passed. Linux/macOS native server and desktop packages still need native OS verification.
@@ -397,6 +397,13 @@
## 2026-05-20: Tauri desktop console and installer language ## 2026-05-20: Tauri desktop console and installer language
- `src-tauri/src/main.rs` uses `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` so Windows release builds use the GUI subsystem and do not open an extra console window when launched from Explorer or the installer shortcut. - `src-tauri/src/main.rs` uses `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` so Windows release builds use the GUI subsystem and do not open an extra console window when launched from Explorer or the installer shortcut.
- `build/capareport-server.spec` keeps Server Portable in console mode, but desktop one-file sidecar builds (`CAPAREPORT_ONEFILE=1`) use PyInstaller `console=False`; PyInstaller should select `runw.exe` for the sidecar so it also stays hidden behind the Tauri window. - `packaging/capareport-server.spec` keeps Server Portable in console mode, but desktop one-file sidecar builds (`CAPAREPORT_ONEFILE=1`) use PyInstaller `console=False`; PyInstaller should select `runw.exe` for the sidecar so it also stays hidden behind the Tauri window.
- `src-tauri/tauri.conf.json` sets Windows installer localization through `bundle.windows.wix.language = "zh-CN"` and `bundle.windows.nsis.languages = ["SimpChinese"]`. NSIS language keys must use NSIS names such as `SimpChinese`, while WiX/MSI uses locale names such as `zh-CN`. - `src-tauri/tauri.conf.json` sets Windows installer localization through `bundle.windows.wix.language = "zh-CN"` and `bundle.windows.nsis.languages = ["SimpChinese"]`. NSIS language keys must use NSIS names such as `SimpChinese`, while WiX/MSI uses locale names such as `zh-CN`.
- Verification on Windows: `cargo check --manifest-path src-tauri\Cargo.toml` passed, `scripts\build.bat desktop` generated the MSI and NSIS bundles, PE subsystem checks reported `Windows GUI` for both `capacity-report-desktop.exe` and `capareport-server.exe`, and the generated NSIS script included `MUI_LANGUAGE "SimpChinese"`. - Verification on Windows: `cargo check --manifest-path src-tauri\Cargo.toml` passed, `scripts\build.bat desktop` generated the MSI and NSIS bundles, PE subsystem checks reported `Windows GUI` for both `capacity-report-desktop.exe` and `capareport-server.exe`, and the generated NSIS script included `MUI_LANGUAGE "SimpChinese"`.
## 2026-05-20: Unified release output under dist
- The former `build/` packaging source directory was renamed to `packaging/` to avoid confusing source-side packaging recipes with generated output. `packaging/` now holds `Dockerfile`, `docker-compose.yml`, the PyInstaller spec, and MySQL container config.
- `scripts/build.ps1` and `scripts/build.sh` now use `dist/.tmp/` for PyInstaller work output and copy final deliverables to `dist/server/`, `dist/desktop/`, and `dist/docker/`. Successful builds remove `dist/.tmp`, `frontend/dist`, `src-tauri/target`, and `src-tauri/binaries`.
- Docker builds now include `Configure.json` in the image and also create a deployable `dist/docker/` bundle containing `capacity-report-app-latest.tar`, `docker-compose.yml`, `Configure.json`, `ReportScript.sql`, `mysql/`, `cache/`, and `logs/`.
- Server Portable still includes `Configure.json` and `ReportScript.sql` inside `dist/server/CapacityReport-Server-<platform>-x64/`. Tauri desktop still bundles both files through `src-tauri/tauri.conf.json` resources and copies them to app data on first run.
+1 -1
View File
@@ -31,7 +31,7 @@ RUN pip install --no-cache-dir -r requirements.txt
RUN mkdir -p /var/log/supervisor /var/run /app/cache RUN mkdir -p /var/log/supervisor /var/run /app/cache
COPY app ./app COPY app ./app
COPY ReportScript.sql start.sh supervisord.conf ./ COPY Configure.json ReportScript.sql start.sh supervisord.conf ./
COPY --from=frontend-build /frontend/dist /app/frontend/dist COPY --from=frontend-build /frontend/dist /app/frontend/dist
RUN chmod +x /app/start.sh 2>/dev/null || true RUN chmod +x /app/start.sh 2>/dev/null || true
@@ -6,10 +6,10 @@ services:
ports: ports:
- "19081:9081" - "19081:9081"
volumes: volumes:
- ../cache:/app/cache - ./cache:/app/cache
- ../Configure.json:/app/Configure.json - ./Configure.json:/app/Configure.json
- ../ReportScript.sql:/app/ReportScript.sql - ./ReportScript.sql:/app/ReportScript.sql
- ../logs:/var/log/supervisor - ./logs:/var/log/supervisor
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
+86 -20
View File
@@ -17,13 +17,16 @@ $ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue" $ProgressPreference = "SilentlyContinue"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$Root = Resolve-Path (Join-Path $ScriptDir "..") $Root = (Resolve-Path (Join-Path $ScriptDir "..")).Path
$DistDir = Join-Path $Root "dist" $DistDir = Join-Path $Root "dist"
$PackageDir = Join-Path $DistDir "packages" $ServerDistDir = Join-Path $DistDir "server"
$PyInstallerDir = Join-Path $Root "build\pyinstaller" $DesktopDistDir = Join-Path $DistDir "desktop"
$DockerDistDir = Join-Path $DistDir "docker"
$TempDir = Join-Path $DistDir ".tmp"
$PyInstallerDir = Join-Path $TempDir "pyinstaller"
$FrontendDir = Join-Path $Root "frontend" $FrontendDir = Join-Path $Root "frontend"
$PackagingDir = Join-Path $Root "packaging"
$ServerName = "capareport-server" $ServerName = "capareport-server"
$AppVersion = "2.0.2"
$DesktopApiBase = "http://127.0.0.1:19082" $DesktopApiBase = "http://127.0.0.1:19082"
function Write-Step($Message) { function Write-Step($Message) {
@@ -46,6 +49,21 @@ function Show-Usage {
Write-Host " -SkipDesktopBuild" Write-Host " -SkipDesktopBuild"
} }
function Assert-InWorkspace($Path) {
$resolved = (Resolve-Path -LiteralPath $Path).Path
if (!$resolved.StartsWith($Root, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to operate outside workspace: $resolved"
}
return $resolved
}
function Remove-Tree($Path) {
if (Test-Path -LiteralPath $Path) {
$resolved = Assert-InWorkspace $Path
Remove-Item -LiteralPath $resolved -Recurse -Force
}
}
function Invoke-Checked($Command, $WorkingDirectory = $Root) { function Invoke-Checked($Command, $WorkingDirectory = $Root) {
Write-Info $Command Write-Info $Command
Push-Location $WorkingDirectory Push-Location $WorkingDirectory
@@ -136,9 +154,15 @@ function Build-Frontend($ApiBase) {
function Build-ServerBinary([switch]$OneFile) { function Build-ServerBinary([switch]$OneFile) {
Ensure-PythonEnvironment Ensure-PythonEnvironment
Write-Step "Building server binary" Write-Step "Building server binary"
Remove-Item -LiteralPath $PyInstallerDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Tree $PyInstallerDir
New-Item -ItemType Directory -Force -Path $PyInstallerDir | Out-Null
$python = Join-Path $Root ".venv\Scripts\python.exe" $python = Join-Path $Root ".venv\Scripts\python.exe"
$workPath = Join-Path $PyInstallerDir "work"
$distPath = Join-Path $PyInstallerDir "dist"
$specPath = Join-Path $PackagingDir "capareport-server.spec"
$previousOneFile = $env:CAPAREPORT_ONEFILE $previousOneFile = $env:CAPAREPORT_ONEFILE
if ($OneFile) { if ($OneFile) {
$env:CAPAREPORT_ONEFILE = "1" $env:CAPAREPORT_ONEFILE = "1"
} }
@@ -147,7 +171,7 @@ function Build-ServerBinary([switch]$OneFile) {
} }
try { try {
Invoke-Checked "`"$python`" -m PyInstaller --clean --noconfirm --workpath build\pyinstaller\work --distpath build\pyinstaller\dist build\capareport-server.spec" $Root Invoke-Checked "`"$python`" -m PyInstaller --clean --noconfirm --workpath `"$workPath`" --distpath `"$distPath`" `"$specPath`"" $Root
} }
finally { finally {
if ($null -ne $previousOneFile) { if ($null -ne $previousOneFile) {
@@ -159,14 +183,14 @@ function Build-ServerBinary([switch]$OneFile) {
} }
if ($OneFile) { if ($OneFile) {
$binaryDir = Join-Path $PyInstallerDir "dist" $binaryDir = $distPath
$binaryPath = Join-Path $binaryDir (Get-ExecutableName (Get-CurrentPlatform)) $binaryPath = Join-Path $binaryDir (Get-ExecutableName (Get-CurrentPlatform))
if (!(Test-Path $binaryPath)) { if (!(Test-Path $binaryPath)) {
throw "PyInstaller output not found: $binaryPath" throw "PyInstaller output not found: $binaryPath"
} }
} }
else { else {
$binaryDir = Join-Path $PyInstallerDir "dist\$ServerName" $binaryDir = Join-Path $distPath $ServerName
if (!(Test-Path $binaryDir)) { if (!(Test-Path $binaryDir)) {
throw "PyInstaller output not found: $binaryDir" throw "PyInstaller output not found: $binaryDir"
} }
@@ -237,11 +261,14 @@ function Build-ServerPortable {
$platformName = "windows" $platformName = "windows"
} }
$outputDir = Join-Path $ServerDistDir "CapacityReport-Server-$platformName-x64"
$archive = "$outputDir.zip"
Remove-Tree $outputDir
Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
Build-Frontend $null Build-Frontend $null
$binaryDir = Build-ServerBinary $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 New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
Copy-Item -Path (Join-Path $binaryDir "*") -Destination $outputDir -Recurse -Force Copy-Item -Path (Join-Path $binaryDir "*") -Destination $outputDir -Recurse -Force
@@ -249,8 +276,6 @@ function Build-ServerPortable {
Write-ServerLaunchers $outputDir $platformName Write-ServerLaunchers $outputDir $platformName
if (!$NoArchive) { if (!$NoArchive) {
$archive = "$outputDir.zip"
Remove-Item -LiteralPath $archive -Force -ErrorAction SilentlyContinue
Compress-Archive -Path (Join-Path $outputDir "*") -DestinationPath $archive -Force Compress-Archive -Path (Join-Path $outputDir "*") -DestinationPath $archive -Force
Write-Info "Archive: $archive" Write-Info "Archive: $archive"
} }
@@ -258,15 +283,36 @@ function Build-ServerPortable {
Write-Info "Server package: $outputDir" Write-Info "Server package: $outputDir"
} }
function Copy-DockerBundle {
Remove-Tree $DockerDistDir
New-Item -ItemType Directory -Force -Path $DockerDistDir | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $DockerDistDir "cache") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $DockerDistDir "logs") | Out-Null
Copy-Item -Path (Join-Path $PackagingDir "docker-compose.yml") -Destination (Join-Path $DockerDistDir "docker-compose.yml") -Force
Copy-Item -Path (Join-Path $PackagingDir "mysql") -Destination (Join-Path $DockerDistDir "mysql") -Recurse -Force
Copy-Item -Path (Join-Path $Root "Configure.json") -Destination (Join-Path $DockerDistDir "Configure.json") -Force
Copy-Item -Path (Join-Path $Root "ReportScript.sql") -Destination (Join-Path $DockerDistDir "ReportScript.sql") -Force
}
function Build-DockerImage { function Build-DockerImage {
Remove-Tree $DockerDistDir
if ($SkipDockerBuild) { if ($SkipDockerBuild) {
Write-Host "Docker build skipped." Write-Host "Docker image build skipped."
Copy-DockerBundle
Invoke-Checked "docker compose -f `"$DockerDistDir\docker-compose.yml`" config" $Root
Write-Info "Docker package: $DockerDistDir"
return return
} }
Write-Step "Building Docker image" Write-Step "Building Docker image"
Invoke-Checked "docker build --progress=plain -f build\Dockerfile -t capacity-report-app:latest ." $Root Invoke-Checked "docker build --progress=plain -f `"$PackagingDir\Dockerfile`" -t capacity-report-app:latest ." $Root
Invoke-Checked "docker compose -f build\docker-compose.yml config" $Root
Write-Step "Packaging Docker output"
Copy-DockerBundle
Invoke-Checked "docker save -o `"$DockerDistDir\capacity-report-app-latest.tar`" capacity-report-app:latest" $Root
Invoke-Checked "docker compose -f `"$DockerDistDir\docker-compose.yml`" config" $Root
Write-Info "Docker package: $DockerDistDir"
} }
function Build-DesktopPackage { function Build-DesktopPackage {
@@ -279,6 +325,8 @@ function Build-DesktopPackage {
throw "Rust is required for the Tauri desktop build" throw "Rust is required for the Tauri desktop build"
} }
Remove-Tree $DesktopDistDir
Build-Frontend $DesktopApiBase Build-Frontend $DesktopApiBase
$binaryDir = Build-ServerBinary -OneFile $binaryDir = Build-ServerBinary -OneFile
$platformName = Get-CurrentPlatform $platformName = Get-CurrentPlatform
@@ -294,8 +342,23 @@ function Build-DesktopPackage {
Invoke-Checked "cargo install tauri-cli --locked" $Root Invoke-Checked "cargo install tauri-cli --locked" $Root
} }
Remove-Tree (Join-Path $Root "src-tauri\target\release\bundle")
Write-Step "Building Tauri desktop package" Write-Step "Building Tauri desktop package"
Invoke-Checked "cargo tauri build" (Join-Path $Root "src-tauri") Invoke-Checked "cargo tauri build" (Join-Path $Root "src-tauri")
New-Item -ItemType Directory -Force -Path $DesktopDistDir | Out-Null
$bundleDir = Join-Path $Root "src-tauri\target\release\bundle"
Get-ChildItem -Path $bundleDir -Recurse -File -Include "*.msi", "*.exe" |
Copy-Item -Destination $DesktopDistDir -Force
Write-Info "Desktop package: $DesktopDistDir"
}
function Clean-Intermediates {
Write-Step "Cleaning intermediate output"
Remove-Tree $TempDir
Remove-Tree (Join-Path $Root "frontend\dist")
Remove-Tree (Join-Path $Root "src-tauri\binaries")
Remove-Tree (Join-Path $Root "src-tauri\target")
} }
if ($Help) { if ($Help) {
@@ -304,13 +367,14 @@ if ($Help) {
} }
if ($Clean) { if ($Clean) {
Write-Step "Cleaning build output" Write-Step "Cleaning dist output"
Remove-Item -LiteralPath $DistDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Tree $DistDir
Remove-Item -LiteralPath $PyInstallerDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Tree (Join-Path $Root "frontend\dist")
Remove-Item -LiteralPath (Join-Path $Root "src-tauri\binaries") -Recurse -Force -ErrorAction SilentlyContinue Remove-Tree (Join-Path $Root "src-tauri\binaries")
Remove-Tree (Join-Path $Root "src-tauri\target")
} }
New-Item -ItemType Directory -Force -Path $PackageDir | Out-Null New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
switch ($Target) { switch ($Target) {
"server" { Build-ServerPortable } "server" { Build-ServerPortable }
@@ -323,4 +387,6 @@ switch ($Target) {
} }
} }
Clean-Intermediates
Write-Step "Build finished" Write-Step "Build finished"
+65 -15
View File
@@ -4,7 +4,15 @@ set -eu
TARGET="${1:-server}" TARGET="${1:-server}"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)" ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
DIST_DIR="$ROOT/dist"
SERVER_DIST_DIR="$DIST_DIR/server"
DESKTOP_DIST_DIR="$DIST_DIR/desktop"
DOCKER_DIST_DIR="$DIST_DIR/docker"
TMP_DIR="$DIST_DIR/.tmp"
PYINSTALLER_DIR="$TMP_DIR/pyinstaller"
PACKAGING_DIR="$ROOT/packaging"
DESKTOP_API_BASE="http://127.0.0.1:19082" DESKTOP_API_BASE="http://127.0.0.1:19082"
SERVER_NAME="capareport-server"
cd "$ROOT" cd "$ROOT"
log() { log() {
@@ -24,6 +32,18 @@ platform_name() {
esac esac
} }
server_exe_name() {
case "$(platform_name)" in
windows) printf '%s.exe' "$SERVER_NAME" ;;
*) printf '%s' "$SERVER_NAME" ;;
esac
}
clean_intermediates() {
log "Cleaning intermediate output"
rm -rf "$TMP_DIR" "$ROOT/frontend/dist" "$ROOT/src-tauri/binaries" "$ROOT/src-tauri/target"
}
ensure_python() { ensure_python() {
log "Preparing Python environment" log "Preparing Python environment"
if [ ! -x ".venv/bin/python" ]; then if [ ! -x ".venv/bin/python" ]; then
@@ -49,20 +69,22 @@ build_server_binary() {
mode="${1:-onedir}" mode="${1:-onedir}"
ensure_python ensure_python
log "Building server binary" log "Building server binary"
rm -rf build/pyinstaller rm -rf "$PYINSTALLER_DIR"
mkdir -p "$PYINSTALLER_DIR"
if [ "$mode" = "onefile" ]; then if [ "$mode" = "onefile" ]; then
CAPAREPORT_ONEFILE=1 CAPAREPORT_ONEFILE=1
export CAPAREPORT_ONEFILE export CAPAREPORT_ONEFILE
run .venv/bin/python -m PyInstaller --clean --noconfirm \ run .venv/bin/python -m PyInstaller --clean --noconfirm \
--workpath build/pyinstaller/work \ --workpath "$PYINSTALLER_DIR/work" \
--distpath build/pyinstaller/dist \ --distpath "$PYINSTALLER_DIR/dist" \
build/capareport-server.spec "$PACKAGING_DIR/capareport-server.spec"
unset CAPAREPORT_ONEFILE unset CAPAREPORT_ONEFILE
else else
run .venv/bin/python -m PyInstaller --clean --noconfirm \ run .venv/bin/python -m PyInstaller --clean --noconfirm \
--workpath build/pyinstaller/work \ --workpath "$PYINSTALLER_DIR/work" \
--distpath build/pyinstaller/dist \ --distpath "$PYINSTALLER_DIR/dist" \
build/capareport-server.spec "$PACKAGING_DIR/capareport-server.spec"
fi fi
} }
@@ -85,38 +107,64 @@ EOF
build_server() { build_server() {
platform="$(platform_name)" platform="$(platform_name)"
exe_name="$(server_exe_name)"
out_dir="$SERVER_DIST_DIR/CapacityReport-Server-$platform-x64"
rm -rf "$out_dir" "$SERVER_DIST_DIR/$(basename "$out_dir").tar.gz"
build_frontend server build_frontend server
build_server_binary onedir 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" mkdir -p "$out_dir/cache" "$out_dir/logs" "$out_dir/frontend"
cp -R build/pyinstaller/dist/capareport-server/. "$out_dir/" cp -R "$PYINSTALLER_DIR/dist/$SERVER_NAME/." "$out_dir/"
cp Configure.json ReportScript.sql "$out_dir/" cp Configure.json ReportScript.sql "$out_dir/"
cp -R frontend/dist "$out_dir/frontend/" cp -R frontend/dist "$out_dir/frontend/"
write_launchers "$out_dir" "capareport-server" write_launchers "$out_dir" "$exe_name"
(cd "$out_dir/.." && tar -czf "$(basename "$out_dir").tar.gz" "$(basename "$out_dir")") (cd "$SERVER_DIST_DIR" && tar -czf "$(basename "$out_dir").tar.gz" "$(basename "$out_dir")")
log "Server package: $out_dir" log "Server package: $out_dir"
} }
copy_docker_bundle() {
rm -rf "$DOCKER_DIST_DIR"
mkdir -p "$DOCKER_DIST_DIR/cache" "$DOCKER_DIST_DIR/logs"
cp "$PACKAGING_DIR/docker-compose.yml" "$DOCKER_DIST_DIR/docker-compose.yml"
cp Configure.json ReportScript.sql "$DOCKER_DIST_DIR/"
cp -R "$PACKAGING_DIR/mysql" "$DOCKER_DIST_DIR/mysql"
}
build_docker() { build_docker() {
rm -rf "$DOCKER_DIST_DIR"
log "Building Docker image" log "Building Docker image"
run docker build --progress=plain -f build/Dockerfile -t capacity-report-app:latest . run docker build --progress=plain -f "$PACKAGING_DIR/Dockerfile" -t capacity-report-app:latest .
run docker compose -f build/docker-compose.yml config log "Packaging Docker output"
copy_docker_bundle
run docker save -o "$DOCKER_DIST_DIR/capacity-report-app-latest.tar" capacity-report-app:latest
run docker compose -f "$DOCKER_DIST_DIR/docker-compose.yml" config
log "Docker package: $DOCKER_DIST_DIR"
} }
build_desktop() { build_desktop() {
rm -rf "$DESKTOP_DIST_DIR"
build_frontend desktop build_frontend desktop
build_server_binary onefile build_server_binary onefile
triple="$(rustc -vV | awk '/^host:/ {print $2}')" triple="$(rustc -vV | awk '/^host:/ {print $2}')"
mkdir -p src-tauri/binaries mkdir -p src-tauri/binaries
cp build/pyinstaller/dist/capareport-server "src-tauri/binaries/capareport-server-$triple" cp "$PYINSTALLER_DIR/dist/$(server_exe_name)" "src-tauri/binaries/$SERVER_NAME-$triple"
if ! cargo tauri --version >/dev/null 2>&1; then if ! cargo tauri --version >/dev/null 2>&1; then
run cargo install tauri-cli --locked run cargo install tauri-cli --locked
fi fi
rm -rf src-tauri/target/release/bundle
(cd src-tauri && run cargo tauri build) (cd src-tauri && run cargo tauri build)
rm -rf "$DESKTOP_DIST_DIR"
mkdir -p "$DESKTOP_DIST_DIR"
find src-tauri/target/release/bundle -type f \( -name '*.msi' -o -name '*.exe' \) -exec cp {} "$DESKTOP_DIST_DIR/" \;
log "Desktop package: $DESKTOP_DIST_DIR"
} }
mkdir -p "$DIST_DIR"
case "$TARGET" in case "$TARGET" in
server) build_server ;; server) build_server ;;
docker) build_docker ;; docker) build_docker ;;
@@ -125,4 +173,6 @@ case "$TARGET" in
*) echo "Usage: scripts/build.sh [server|docker|desktop|all]" >&2; exit 1 ;; *) echo "Usage: scripts/build.sh [server|docker|desktop|all]" >&2; exit 1 ;;
esac esac
clean_intermediates
log "Build finished" log "Build finished"