diff --git a/.woodpecker/verify.yml b/.woodpecker/verify.yml index 8ee56eb..60963a8 100644 --- a/.woodpecker/verify.yml +++ b/.woodpecker/verify.yml @@ -23,4 +23,5 @@ steps: environment: NUGET_PACKAGES: /home/justme/.nuget/packages commands: - - cd backend && dotnet test --solution MoShiro.slnx -c Release + # 增量编译 + 测试(把后端源码同步到固定目录,只重编译变了的项目),见 ci/verify-backend.sh + - bash ci/verify-backend.sh diff --git a/.woodpecker/web-build.yml b/.woodpecker/web-build.yml index 761fdaa..35d7bb4 100644 --- a/.woodpecker/web-build.yml +++ b/.woodpecker/web-build.yml @@ -13,15 +13,12 @@ when: - event: push branch: main -depends_on: - - verify - +# 和 verify 并行:前端构建不依赖后端测试,两个同时跑 steps: - name: web-build image: bash environment: NPM_CONFIG_CACHE: /home/justme/.npm commands: - - npm ci --prefix web --prefer-offline - - npm run build --prefix web - - npm run lint:css --prefix web + # node_modules 按锁文件哈希缓存,展示端和管理端并行构建,再检查 CSS,见 ci/web-build.sh + - bash ci/web-build.sh diff --git a/ci/verify-backend.sh b/ci/verify-backend.sh new file mode 100644 index 0000000..bbfdfb1 --- /dev/null +++ b/ci/verify-backend.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# 后端测试,带增量编译:把后端源码同步到一个固定目录,在那里 dotnet build / test。 +# +# 为什么:每条流水线的工作区都是新克隆的(路径、文件时间都是新的),MSBuild 没法增量,每次从零编译整个解决方案要两分钟。 +# 固定目录里 obj/bin 留着,同步时只更新内容真的变了的文件(按内容比较,没变的文件时间不动),MSBuild 就只重编译受影响的项目; +# 改动小的时候整个测试 20–30 秒。 +# +# 同一时间只允许一条流水线用这份缓存(flock);增量编译失败(缓存坏了)就清掉缓存从头编译一次,所以最坏也只是回到原来的速度。 +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cache="${MOSHIRO_CI_CACHE:-/home/justme/woodpecker/cache/backend-tests}" +src="$cache/src" +mkdir -p "$cache" + +# .NET 第一次运行的欢迎信息、遥测、开发证书,在每条流水线的临时 HOME 里都要白做一遍 +export DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1 DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 DOTNET_GENERATE_ASPNET_CERTIFICATE=false + +exec 9> "$cache/.lock" +flock -w 900 9 || { echo "could not get the backend test cache lock" >&2; exit 1; } + +sync_sources() { + mkdir -p "$src" + local list + list="$(mktemp)" + # 构建要用的都在 backend/ 和根目录的几个文件里(Directory.Packages.props、global.json、NuGet 配置) + (cd "$root" && git ls-files -z -- backend Directory.Packages.props global.json 'NuGet.*' 'nuget.*') | tr '\0' '\n' | sort -u > "$list" + local changed=0 file + while IFS= read -r file; do + [[ -f "$root/$file" ]] || continue + if ! cmp -s "$root/$file" "$src/$file" 2>/dev/null; then + mkdir -p "$(dirname "$src/$file")" + cp "$root/$file" "$src/$file" + changed=$((changed + 1)) + fi + done < "$list" + # 仓库里已经删掉的文件,缓存里也要删(构建产物 artifacts 不动) + local stale=0 + while IFS= read -r file; do + [[ -n "$file" ]] || continue + rm -f "$src/$file" + stale=$((stale + 1)) + done < <(cd "$src" && find backend -type f -not -path 'backend/artifacts/*' -not -path '*/bin/*' -not -path '*/obj/*' 2>/dev/null | sort | comm -23 - <(grep '^backend/' "$list")) + rm -f "$list" + echo "synced sources: $changed changed, $stale removed" +} + +build() { (cd "$src/backend" && dotnet build MoShiro.slnx -c Release --nologo -v:q); } + +sync_sources +if ! build; then + echo "incremental build failed; wiping the cache and building from scratch" >&2 + rm -rf "$src" + sync_sources + build +fi + +# 测试在缓存目录里跑完整的解决方案(不再重复编译) +(cd "$src/backend" && dotnet test --solution MoShiro.slnx -c Release --no-build) diff --git a/ci/web-build.sh b/ci/web-build.sh new file mode 100644 index 0000000..c0cfbb3 --- /dev/null +++ b/ci/web-build.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# 前端构建和 CSS 检查,带依赖缓存:node_modules 按「锁文件 + package.json」的哈希缓存起来,同样的依赖不再 npm ci(约 20 秒); +# 展示端和管理端并行构建。缓存里放的是硬链接副本(cp -al),恢复几乎是瞬间;依赖变了才重新安装并更新缓存,只留最近 3 份。 +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cache="${MOSHIRO_CI_CACHE:-/home/justme/woodpecker/cache/web-node-modules}" +export NPM_CONFIG_CACHE="${NPM_CONFIG_CACHE:-/home/justme/.npm}" +web="$root/web" +dirs=(node_modules display/node_modules admin/node_modules) +mkdir -p "$cache" + +key="$(cat "$web/package-lock.json" "$web/package.json" "$web/display/package.json" "$web/admin/package.json" | sha256sum | cut -c1-16)" + +restore() { + [[ -d "$cache/$key/node_modules" ]] || return 1 + local d + for d in "${dirs[@]}"; do + [[ -d "$cache/$key/$d" ]] || continue + mkdir -p "$(dirname "$web/$d")" + cp -al "$cache/$key/$d" "$web/$d" 2>/dev/null || cp -a "$cache/$key/$d" "$web/$d" + done +} + +save() { + local tmp="$cache/$key.tmp.$$" d + rm -rf "$tmp" + for d in "${dirs[@]}"; do + [[ -d "$web/$d" ]] || continue + mkdir -p "$(dirname "$tmp/$d")" + cp -al "$web/$d" "$tmp/$d" 2>/dev/null || cp -a "$web/$d" "$tmp/$d" + done + rm -rf "$cache/$key" + mv "$tmp" "$cache/$key" + # 只留最近用过的 3 份 + (cd "$cache" && ls -1t | grep -v '\.tmp\.' | tail -n +4 | xargs -r rm -rf) +} + +if restore; then + echo "restored node_modules from the cache ($key)" +else + echo "no cache for $key; running npm ci" + rm -rf "$web/node_modules" "$web/display/node_modules" "$web/admin/node_modules" + npm ci --prefix "$web" --prefer-offline + save || echo "could not save the node_modules cache" >&2 +fi + +# 展示端和管理端并行构建,再检查 CSS +pids=() +(cd "$web" && timeout 600 npm run build -w display) & pids+=($!) +(cd "$web" && timeout 600 npm run build -w admin) & pids+=($!) +failed=0 +for pid in "${pids[@]}"; do wait "$pid" || failed=1; done +[[ "$failed" == 0 ]] || { echo "web build failed" >&2; exit 1; } +npm run lint:css --prefix "$web"