chore: speed up local build workflow

This commit is contained in:
boos
2026-06-06 18:10:16 +08:00
parent 25c6fdbd6c
commit def8d7a204
3 changed files with 200 additions and 23 deletions
+6 -5
View File
@@ -1,18 +1,19 @@
# Cargo build acceleration. # Cargo build acceleration.
# #
# The biggest single speedup is the `mold` linker (~3-5x faster link # The biggest single speedup is the `mold` linker (~3-5x faster link
# time on debug builds). It isn't installed by default on this host # time on debug builds). `build-and-run.sh` enables mold/lld automatically
# (no apt-get, see build-and-run.sh notes), so the mold block below is # when available, so the block below is kept as an optional manual setup for
# commented out — uncomment it after running: # direct `cargo build` / `cargo run` usage:
# #
# sudo apt-get install -y mold # sudo apt-get install -y mold
# #
# (or build it from source: https://github.com/rui314/mold) # (or build it from source: https://github.com/rui314/mold)
# #
# Without mold, the [profile.dev] tweaks in Cargo.toml still give a # Without mold/lld, the [profile.dev] tweaks in Cargo.toml still give a
# meaningful speedup (line-tables-only debuginfo + bumped codegen-units). # meaningful speedup (line-tables-only debuginfo + bumped codegen-units).
# Uncomment once mold is on PATH: # Optional: uncomment once mold is on PATH if you want direct cargo commands
# to use it without going through build-and-run.sh.
# [target.x86_64-unknown-linux-gnu] # [target.x86_64-unknown-linux-gnu]
# linker = "clang" # linker = "clang"
# rustflags = ["-C", "link-arg=-fuse-ld=mold"] # rustflags = ["-C", "link-arg=-fuse-ld=mold"]
+19 -4
View File
@@ -410,11 +410,20 @@ BOOSAPI_COMFYUI__API_KEY=your-workflow-backend-key
### 后端(Rust) ### 后端(Rust)
```bash ```bash
# 编译并运行 # 最快验证:只检查 Rust 后端,不构建前端、不启动服务
cargo run --release ./build-and-run.sh --check
# 仅检查编译 # 日常启动:复用前端缓存,后端 dev profile 编译后启动
cargo check ./build-and-run.sh
# 只重启/编译后端,不碰 Vite/Next
./build-and-run.sh --backend-only
# 接近生产性能但比 release 快
./build-and-run.sh --fast-release
# 完整生产包:构建并嵌入前端/Workflow UI,只输出 boosapi + config.toml
./build-and-run.sh --production
# 运行测试 # 运行测试
cargo test cargo test
@@ -428,6 +437,12 @@ export BOOSAPI_UPSTREAM_BASE=https://api.boos.lat/v1
export BOOSAPI_UPSTREAM_KEY=sk-your-upstream-key export BOOSAPI_UPSTREAM_KEY=sk-your-upstream-key
``` ```
构建加速说明:
- 脚本会在 Linux 下自动使用 `mold` / `lld` 快速链接器;可用 `BOOSAPI_FAST_LINKER=off` 关闭。
- `BOOSAPI_CARGO_JOBS=8 ./build-and-run.sh --check` 可限制 Cargo 并发,避免把开发机打满。
- 生产性能验证优先用 `--fast-release`,真正发布再用 `--production` 或 `--release`。
### 前端(React SPA) ### 前端(React SPA)
前端使用 Vite 构建,产物由 Rust 后端静态 serve: 前端使用 Vite 构建,产物由 Rust 后端静态 serve:
+175 -14
View File
@@ -17,13 +17,17 @@ fail() { echo "[$(date '+%H:%M:%S')] ✗ $*" >&2; exit 1; }
BUILD_PROFILE="${BOOSAPI_BUILD_PROFILE:-dev}" BUILD_PROFILE="${BOOSAPI_BUILD_PROFILE:-dev}"
FRONTEND_ONLY=0 FRONTEND_ONLY=0
BACKEND_ONLY=0 BACKEND_ONLY=0
CHECK_ONLY=0
NO_RUN=0 NO_RUN=0
FORCE_FRONTEND=0 FORCE_FRONTEND=0
SKIP_FRONTEND=0 SKIP_FRONTEND=0
PRODUCTION_BUNDLE=0 PRODUCTION_BUNDLE=0
PRODUCTION_DIR="${BOOSAPI_PRODUCTION_DIR:-$ROOT_DIR/dist/production}" PRODUCTION_DIR="${BOOSAPI_PRODUCTION_DIR:-$ROOT_DIR/dist/production}"
CARGO_FEATURES="${BOOSAPI_CARGO_FEATURES:-}" CARGO_FEATURES="${BOOSAPI_CARGO_FEATURES:-}"
CARGO_JOBS="${BOOSAPI_CARGO_JOBS:-}"
FAST_LINKER="${BOOSAPI_FAST_LINKER:-auto}"
FRONTEND_PM="${BOOSAPI_FRONTEND_PM:-auto}" FRONTEND_PM="${BOOSAPI_FRONTEND_PM:-auto}"
BUILD_TIMINGS=()
usage() { usage() {
cat <<'EOF' cat <<'EOF'
@@ -38,6 +42,8 @@ usage() {
--prod 同 --production --prod 同 --production
--production-dir <dir> --production-dir <dir>
指定生产产物目录(默认:dist/production) 指定生产产物目录(默认:dist/production)
--check 只执行 cargo check,不构建前端、不启动服务(最快验证)
--backend-check 同 --check
--frontend-only 只构建前端 --frontend-only 只构建前端
--backend-only 只构建后端 --backend-only 只构建后端
--force-frontend 强制重建前端,忽略缓存 --force-frontend 强制重建前端,忽略缓存
@@ -46,6 +52,8 @@ usage() {
--no-run 构建后不启动服务 --no-run 构建后不启动服务
BOOSAPI_BUILD_PROFILE 可设置默认 profile(dev / release / fast-release) BOOSAPI_BUILD_PROFILE 可设置默认 profile(dev / release / fast-release)
BOOSAPI_CARGO_FEATURES 可覆盖后端 Cargo features(默认:空) BOOSAPI_CARGO_FEATURES 可覆盖后端 Cargo features(默认:空)
BOOSAPI_CARGO_JOBS 可设置 cargo 并发任务数,例如 8
BOOSAPI_FAST_LINKER 可设置 auto / off / mold / lld(默认:auto,Linux 下优先 mold)
BOOSAPI_FRONTEND_PM 可设置主前端构建器:auto / npm / bun(默认:auto) BOOSAPI_FRONTEND_PM 可设置主前端构建器:auto / npm / bun(默认:auto)
BOOSAPI_PRODUCTION_DIR 可设置 --production 默认产物目录 BOOSAPI_PRODUCTION_DIR 可设置 --production 默认产物目录
-h, --help 显示帮助 -h, --help 显示帮助
@@ -53,6 +61,7 @@ usage() {
示例: 示例:
./build-and-run.sh --release ./build-and-run.sh --release
./build-and-run.sh --fast-release ./build-and-run.sh --fast-release
./build-and-run.sh --check
./build-and-run.sh --bun ./build-and-run.sh --bun
./build-and-run.sh --production ./build-and-run.sh --production
./build-and-run.sh --production --production-dir /opt/boosapi-release ./build-and-run.sh --production --production-dir /opt/boosapi-release
@@ -75,6 +84,11 @@ while [ $# -gt 0 ]; do
[ $# -gt 0 ] || fail "--production-dir 需要目录参数" [ $# -gt 0 ] || fail "--production-dir 需要目录参数"
PRODUCTION_DIR="$1" PRODUCTION_DIR="$1"
;; ;;
--check|--backend-check)
CHECK_ONLY=1
BACKEND_ONLY=1
NO_RUN=1
;;
--profile) --profile)
shift shift
[ $# -gt 0 ] || fail "--profile 需要参数:dev / release / fast-release" [ $# -gt 0 ] || fail "--profile 需要参数:dev / release / fast-release"
@@ -105,12 +119,114 @@ if [ "$PRODUCTION_BUNDLE" -eq 1 ] && [ "$BACKEND_ONLY" -eq 1 ]; then
fail "--production 需要先构建前端以嵌入 web,不能和 --backend-only 同时使用" fail "--production 需要先构建前端以嵌入 web,不能和 --backend-only 同时使用"
fi fi
if [ "$PRODUCTION_BUNDLE" -eq 1 ] && [ "$CHECK_ONLY" -eq 1 ]; then
fail "--production 不能和 --check 同时使用"
fi
case "$BUILD_PROFILE" in case "$BUILD_PROFILE" in
dev|debug) BUILD_PROFILE="dev" ;; dev|debug) BUILD_PROFILE="dev" ;;
release|fast-release) ;; release|fast-release) ;;
*) fail "未知构建 profile: $BUILD_PROFILE(可选:dev / release / fast-release)" ;; *) fail "未知构建 profile: $BUILD_PROFILE(可选:dev / release / fast-release)" ;;
esac esac
case "$FAST_LINKER" in
auto|off|none|mold|lld) ;;
*) fail "未知 BOOSAPI_FAST_LINKER: $FAST_LINKER(可选:auto / off / mold / lld)" ;;
esac
timer_start() {
date +%s
}
record_timing() {
local label="$1"
local started="$2"
local ended
ended="$(date +%s)"
BUILD_TIMINGS+=("${label}: $((ended - started))s")
}
print_timing_summary() {
[ "${#BUILD_TIMINGS[@]}" -gt 0 ] || return 0
log "构建耗时:"
local item
for item in "${BUILD_TIMINGS[@]}"; do
log " - $item"
done
}
append_rustflags() {
local flags="$1"
if [[ " ${RUSTFLAGS:-} " != *" $flags "* ]]; then
export RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }$flags"
fi
}
build_cargo_args() {
CARGO_BASE_ARGS=()
CARGO_FEATURE_ARGS=()
if [ -n "$CARGO_JOBS" ]; then
CARGO_BASE_ARGS+=(--jobs "$CARGO_JOBS")
fi
if [ -n "$CARGO_FEATURES" ]; then
CARGO_FEATURE_ARGS=(--features "$CARGO_FEATURES")
fi
}
configure_fast_linker() {
local mode="$FAST_LINKER"
case "$(uname -s 2>/dev/null || true)" in
Linux) ;;
*)
[ "$mode" = "auto" ] && return 0
fail "BOOSAPI_FAST_LINKER=$mode 目前只在 Linux 构建环境自动配置"
;;
esac
if [ "$mode" = "off" ] || [ "$mode" = "none" ]; then
return 0
fi
if [ "$mode" = "auto" ]; then
if command -v mold >/dev/null 2>&1; then
mode="mold"
elif command -v ld.lld >/dev/null 2>&1 || command -v lld >/dev/null 2>&1; then
mode="lld"
else
log "提示: 安装 mold 或 lld 可显著缩短 Rust 链接时间;当前使用系统默认链接器"
return 0
fi
fi
case "$mode" in
mold)
command -v mold >/dev/null 2>&1 || fail "BOOSAPI_FAST_LINKER=mold 但未找到 mold"
if command -v clang >/dev/null 2>&1; then
append_rustflags "-C linker=clang"
elif ! command -v gcc >/dev/null 2>&1 && [ -z "${RUSTC_LINKER:-}" ]; then
fail "启用 mold 需要 clang 或 gcc 作为 linker driver"
fi
append_rustflags "-C link-arg=-fuse-ld=mold"
log "Rust 快速链接器: mold(BOOSAPI_FAST_LINKER=$FAST_LINKER)"
;;
lld)
if ! command -v ld.lld >/dev/null 2>&1 && ! command -v lld >/dev/null 2>&1; then
fail "BOOSAPI_FAST_LINKER=lld 但未找到 ld.lld/lld"
fi
if command -v clang >/dev/null 2>&1; then
append_rustflags "-C linker=clang"
elif ! command -v gcc >/dev/null 2>&1 && [ -z "${RUSTC_LINKER:-}" ]; then
fail "启用 lld 需要 clang 或 gcc 作为 linker driver"
fi
append_rustflags "-C link-arg=-fuse-ld=lld"
log "Rust 快速链接器: lld(BOOSAPI_FAST_LINKER=$FAST_LINKER)"
;;
esac
}
server_port() { server_port() {
if [ -n "${BOOSAPI_SERVER__PORT:-}" ]; then if [ -n "${BOOSAPI_SERVER__PORT:-}" ]; then
printf '%s\n' "$BOOSAPI_SERVER__PORT" printf '%s\n' "$BOOSAPI_SERVER__PORT"
@@ -224,8 +340,12 @@ install_frontend_dependencies() {
} }
build_frontend() { build_frontend() {
local started
started="$(timer_start)"
if [ "$SKIP_FRONTEND" -eq 1 ]; then if [ "$SKIP_FRONTEND" -eq 1 ]; then
log "跳过前端构建(--skip-frontend)" log "跳过前端构建(--skip-frontend)"
record_timing "前端检查" "$started"
return 0 return 0
fi fi
@@ -267,6 +387,7 @@ build_frontend() {
&& [ -f "$ROOT_DIR/dist/web/index.html" ] \ && [ -f "$ROOT_DIR/dist/web/index.html" ] \
&& [ "$(cat "$fingerprint_file" 2>/dev/null || true)" = "$fingerprint" ]; then && [ "$(cat "$fingerprint_file" 2>/dev/null || true)" = "$fingerprint" ]; then
ok "前端无变化,跳过 Vite 构建 → dist/web/" ok "前端无变化,跳过 Vite 构建 → dist/web/"
record_timing "前端检查" "$started"
return 0 return 0
fi fi
@@ -280,6 +401,7 @@ build_frontend() {
mkdir -p "$ROOT_DIR/dist/web" mkdir -p "$ROOT_DIR/dist/web"
printf '%s\n' "$fingerprint" > "$fingerprint_file" printf '%s\n' "$fingerprint" > "$fingerprint_file"
ok "前端构建完成 → dist/web/" ok "前端构建完成 → dist/web/"
record_timing "前端构建" "$started"
} }
install_website_dependencies() { install_website_dependencies() {
@@ -291,8 +413,12 @@ install_website_dependencies() {
} }
build_public_website() { build_public_website() {
local started
started="$(timer_start)"
if [ "$SKIP_FRONTEND" -eq 1 ]; then if [ "$SKIP_FRONTEND" -eq 1 ]; then
log "跳过官网构建(--skip-frontend)" log "跳过官网构建(--skip-frontend)"
record_timing "官网检查" "$started"
return 0 return 0
fi fi
@@ -319,6 +445,7 @@ build_public_website() {
&& [ -f "$ROOT_DIR/dist/website/index.html" ] \ && [ -f "$ROOT_DIR/dist/website/index.html" ] \
&& [ "$(cat "$fingerprint_file" 2>/dev/null || true)" = "$fingerprint" ]; then && [ "$(cat "$fingerprint_file" 2>/dev/null || true)" = "$fingerprint" ]; then
ok "官网无变化,跳过 Next 构建 → dist/website/" ok "官网无变化,跳过 Next 构建 → dist/website/"
record_timing "官网检查" "$started"
return 0 return 0
fi fi
@@ -329,11 +456,16 @@ build_public_website() {
cp -R "$ROOT_DIR/website/out/." "$ROOT_DIR/dist/website/" cp -R "$ROOT_DIR/website/out/." "$ROOT_DIR/dist/website/"
printf '%s\n' "$fingerprint" > "$fingerprint_file" printf '%s\n' "$fingerprint" > "$fingerprint_file"
ok "官网构建完成 → dist/website/" ok "官网构建完成 → dist/website/"
record_timing "官网构建" "$started"
} }
ensure_workflow_ui_for_embedding() { ensure_workflow_ui_for_embedding() {
local started
started="$(timer_start)"
if [ "$SKIP_FRONTEND" -eq 1 ]; then if [ "$SKIP_FRONTEND" -eq 1 ]; then
log "跳过 Workflow UI 构建检查(--skip-frontend)" log "跳过 Workflow UI 构建检查(--skip-frontend)"
record_timing "Workflow UI 检查" "$started"
return 0 return 0
fi fi
@@ -342,6 +474,7 @@ ensure_workflow_ui_for_embedding() {
&& [ -f "$ROOT_DIR/dist/workflow-ui/index.html" ] \ && [ -f "$ROOT_DIR/dist/workflow-ui/index.html" ] \
&& [ "$FORCE_FRONTEND" -eq 0 ]; then && [ "$FORCE_FRONTEND" -eq 0 ]; then
ok "Workflow UI 已存在,跳过构建 → dist/workflow-ui-{external,cloud}/" ok "Workflow UI 已存在,跳过构建 → dist/workflow-ui-{external,cloud}/"
record_timing "Workflow UI 检查" "$started"
return 0 return 0
fi fi
@@ -352,43 +485,60 @@ ensure_workflow_ui_for_embedding() {
log "构建 Workflow UI 双前端(external + cloud,用于嵌入二进制)..." log "构建 Workflow UI 双前端(external + cloud,用于嵌入二进制)..."
pnpm run workflow-ui:install pnpm run workflow-ui:install
ok "Workflow UI 构建完成 → dist/workflow-ui-external/ dist/workflow-ui-cloud/" ok "Workflow UI 构建完成 → dist/workflow-ui-external/ dist/workflow-ui-cloud/"
record_timing "Workflow UI 构建" "$started"
} }
build_backend() { build_backend() {
local started
started="$(timer_start)"
if ! command -v cargo >/dev/null 2>&1; then if ! command -v cargo >/dev/null 2>&1; then
fail "未找到 cargo,请先安装 Rust 工具链" fail "未找到 cargo,请先安装 Rust 工具链"
fi fi
# 提示一次:如果链接器走默认 ld 而 mold 不在 PATH,给出一条性能建议。 configure_fast_linker
# mold 安装后还需在 .cargo/config.toml 取消 `linker = "clang"` 那段注释。 build_cargo_args
if ! command -v mold >/dev/null 2>&1; then
log "提示: 安装 mold 后取消 .cargo/config.toml 中的相应注释,链接将快 3-5x"
fi
CARGO_FEATURE_ARGS=() if [ "$CHECK_ONLY" -eq 1 ]; then
if [ -n "$CARGO_FEATURES" ]; then if [ "$BUILD_PROFILE" = "release" ]; then
CARGO_FEATURE_ARGS=(--features "$CARGO_FEATURES") log "检查后端 (cargo check --release ${CARGO_BASE_ARGS[*]} ${CARGO_FEATURE_ARGS[*]})..."
cargo check "${CARGO_BASE_ARGS[@]}" --release "${CARGO_FEATURE_ARGS[@]}"
elif [ "$BUILD_PROFILE" = "fast-release" ]; then
log "检查后端 (cargo check --profile fast-release ${CARGO_BASE_ARGS[*]} ${CARGO_FEATURE_ARGS[*]})..."
cargo check "${CARGO_BASE_ARGS[@]}" --profile fast-release "${CARGO_FEATURE_ARGS[@]}"
else
log "检查后端 (cargo check ${CARGO_BASE_ARGS[*]} ${CARGO_FEATURE_ARGS[*]})..."
cargo check "${CARGO_BASE_ARGS[@]}" "${CARGO_FEATURE_ARGS[@]}"
fi
ok "后端检查完成"
record_timing "后端检查" "$started"
return 0
fi fi
if [ "$BUILD_PROFILE" = "release" ]; then if [ "$BUILD_PROFILE" = "release" ]; then
log "构建后端 (cargo build --release ${CARGO_FEATURE_ARGS[*]})..." log "构建后端 (cargo build --release ${CARGO_BASE_ARGS[*]} ${CARGO_FEATURE_ARGS[*]})..."
cargo build --release "${CARGO_FEATURE_ARGS[@]}" cargo build "${CARGO_BASE_ARGS[@]}" --release "${CARGO_FEATURE_ARGS[@]}"
BINARY="$ROOT_DIR/target/release/boosapi" BINARY="$ROOT_DIR/target/release/boosapi"
ok "后端构建完成 → target/release/boosapi" ok "后端构建完成 → target/release/boosapi"
elif [ "$BUILD_PROFILE" = "fast-release" ]; then elif [ "$BUILD_PROFILE" = "fast-release" ]; then
log "构建后端 (cargo build --profile fast-release ${CARGO_FEATURE_ARGS[*]})..." log "构建后端 (cargo build --profile fast-release ${CARGO_BASE_ARGS[*]} ${CARGO_FEATURE_ARGS[*]})..."
cargo build --profile fast-release "${CARGO_FEATURE_ARGS[@]}" cargo build "${CARGO_BASE_ARGS[@]}" --profile fast-release "${CARGO_FEATURE_ARGS[@]}"
BINARY="$ROOT_DIR/target/fast-release/boosapi" BINARY="$ROOT_DIR/target/fast-release/boosapi"
ok "后端构建完成 → target/fast-release/boosapi" ok "后端构建完成 → target/fast-release/boosapi"
else else
log "构建后端 (cargo build ${CARGO_FEATURE_ARGS[*]})..." log "构建后端 (cargo build ${CARGO_BASE_ARGS[*]} ${CARGO_FEATURE_ARGS[*]})..."
cargo build "${CARGO_FEATURE_ARGS[@]}" cargo build "${CARGO_BASE_ARGS[@]}" "${CARGO_FEATURE_ARGS[@]}"
BINARY="$ROOT_DIR/target/debug/boosapi" BINARY="$ROOT_DIR/target/debug/boosapi"
ok "后端构建完成 → target/debug/boosapi" ok "后端构建完成 → target/debug/boosapi"
fi fi
record_timing "后端构建" "$started"
} }
build_production_bundle() { build_production_bundle() {
local started
started="$(timer_start)"
[ -n "${BINARY:-}" ] || fail "生产打包失败:后端二进制路径为空" [ -n "${BINARY:-}" ] || fail "生产打包失败:后端二进制路径为空"
[ -f "$BINARY" ] || fail "生产打包失败:未找到二进制 $BINARY" [ -f "$BINARY" ] || fail "生产打包失败:未找到二进制 $BINARY"
[ -f "$ROOT_DIR/config.toml" ] || fail "生产打包失败:未找到 config.toml" [ -f "$ROOT_DIR/config.toml" ] || fail "生产打包失败:未找到 config.toml"
@@ -409,6 +559,7 @@ build_production_bundle() {
log "产物内容:" log "产物内容:"
(cd "$PRODUCTION_DIR" && ls -lh boosapi config.toml) (cd "$PRODUCTION_DIR" && ls -lh boosapi config.toml)
log "部署时只需要复制该目录内的 boosapi 和 config.toml;主 Web 前端已嵌入二进制。" log "部署时只需要复制该目录内的 boosapi 和 config.toml;主 Web 前端已嵌入二进制。"
record_timing "生产打包" "$started"
} }
cleanup_started_server() { cleanup_started_server() {
@@ -424,6 +575,7 @@ cleanup_started_server() {
if [ "$FRONTEND_ONLY" -eq 1 ]; then if [ "$FRONTEND_ONLY" -eq 1 ]; then
build_frontend build_frontend
build_public_website build_public_website
print_timing_summary
log "跳过启动(--frontend-only)" log "跳过启动(--frontend-only)"
exit 0 exit 0
fi fi
@@ -438,16 +590,25 @@ fi
build_backend build_backend
if [ "$CHECK_ONLY" -eq 1 ]; then
print_timing_summary
log "跳过启动(--check)"
exit 0
fi
if [ "$PRODUCTION_BUNDLE" -eq 1 ]; then if [ "$PRODUCTION_BUNDLE" -eq 1 ]; then
build_production_bundle build_production_bundle
print_timing_summary
exit 0 exit 0
fi fi
if [ "$NO_RUN" -eq 1 ]; then if [ "$NO_RUN" -eq 1 ]; then
print_timing_summary
log "跳过启动(--no-run)" log "跳过启动(--no-run)"
exit 0 exit 0
fi fi
print_timing_summary
stop_previous_server stop_previous_server
log "启动服务..." log "启动服务..."