|
1 | 1 | #!/bin/bash |
2 | 2 | # Sets up a persistent build cache for self-hosted CI runners. |
3 | | -# Creates a symlink: ./build -> <resolved scratch path>/.mfc-ci-cache/<key>/build |
| 3 | +# Creates a symlink: ./build -> <scratch>/.mfc-ci-cache/<key>/build |
4 | 4 | # |
5 | | -# This ensures that every run of the same config (cluster/device/interface) finds |
6 | | -# cached build artifacts regardless of which runner instance picks up the job. |
7 | | -# |
8 | | -# Concurrent safety: uses flock to serialize access per cache directory. If |
9 | | -# multiple PRs trigger the same config simultaneously, the second job waits |
10 | | -# for the first to finish (up to 1 hour), then gets a warm cache. If the lock |
11 | | -# times out, falls back to a local build (same as no caching). |
| 5 | +# Each runner gets its own cache keyed by (cluster, device, interface, runner). |
| 6 | +# This avoids cross-runner path issues entirely — CMake's absolute paths are |
| 7 | +# always correct because the same runner always uses the same workspace path. |
12 | 8 | # |
13 | 9 | # Usage: source .github/scripts/setup-build-cache.sh <cluster> <device> <interface> |
14 | 10 |
|
15 | 11 | _cache_cluster="${1:?Usage: setup-build-cache.sh <cluster> <device> <interface>}" |
16 | 12 | _cache_device="${2:?}" |
17 | 13 | _cache_interface="${3:-none}" |
| 14 | +_cache_runner="${RUNNER_NAME:?RUNNER_NAME not set}" |
18 | 15 |
|
19 | | -_cache_key="${_cache_cluster}-${_cache_device}-${_cache_interface}" |
| 16 | +_cache_key="${_cache_cluster}-${_cache_device}-${_cache_interface}-${_cache_runner}" |
20 | 17 | _cache_base="$HOME/scratch/.mfc-ci-cache/${_cache_key}/build" |
21 | 18 |
|
22 | | -# Create the cache dir, then resolve to a physical path (no symlinks). |
23 | | -# $HOME/scratch is typically a symlink to a scratch filesystem — resolving |
24 | | -# it ensures the build symlink target remains valid even if intermediate |
25 | | -# symlinks change. |
26 | 19 | mkdir -p "$_cache_base" |
27 | 20 | _cache_dir="$(cd "$_cache_base" && pwd -P)" |
28 | 21 |
|
29 | 22 | echo "=== Build Cache Setup ===" |
30 | 23 | echo " Cache key: $_cache_key" |
31 | 24 | echo " Cache dir: $_cache_dir" |
32 | 25 |
|
33 | | -# Acquire an exclusive lock on the cache directory to prevent concurrent |
34 | | -# builds from corrupting it. The lock is fd-based (flock on fd 9), so it |
35 | | -# auto-releases when the calling process exits — no stale locks. |
36 | | -# |
37 | | -# Timeout: 1 hour. If another build holds the lock, we wait. This is fine |
38 | | -# because the waiting job will get a warm cache when it finally acquires. |
39 | | -# If the lock can't be acquired after 1 hour, something is wrong — fall |
40 | | -# back to a local build in the workspace. |
41 | | -_cache_locked=false |
42 | | -_lock_file="$_cache_dir/.cache.lock" |
43 | | -exec 9>"$_lock_file" |
44 | | -echo " Acquiring cache lock..." |
45 | | -if flock --timeout 3600 9; then |
46 | | - _cache_locked=true |
47 | | - echo " Cache lock acquired" |
48 | | -else |
49 | | - echo " WARNING: Cache lock timeout (1h), building locally without cache" |
50 | | - exec 9>&- |
51 | | - # Remove any existing symlink to the shared cache so we don't write |
52 | | - # into it without the lock. Then create a real local directory. |
53 | | - if [ -L "build" ]; then |
54 | | - rm -f "build" |
55 | | - fi |
56 | | - mkdir -p "build" |
57 | | - echo "=========================" |
58 | | - return 0 2>/dev/null || true |
59 | | -fi |
60 | | - |
61 | | -# If build/ exists (real dir or stale symlink), remove it. |
62 | | -# rm -rf on a symlink removes the symlink, not the target — cache is safe. |
| 26 | +# Replace any existing build/ (real dir or stale symlink) with a symlink |
| 27 | +# to our runner-specific cache directory. |
63 | 28 | if [ -e "build" ] || [ -L "build" ]; then |
64 | 29 | rm -rf "build" |
65 | 30 | fi |
66 | 31 |
|
67 | 32 | ln -s "$_cache_dir" "build" |
68 | 33 |
|
69 | | -# Handle cross-runner workspace path changes. |
70 | | -# CMakeCache.txt stores absolute paths from whichever runner instance |
71 | | -# originally configured the build. If we're on a different runner, sed-replace |
72 | | -# the old workspace path with the current one so CMake can do incremental builds. |
73 | | -_workspace_marker="$_cache_dir/.workspace_path" |
74 | | -if [ -f "$_workspace_marker" ]; then |
75 | | - _old_workspace=$(cat "$_workspace_marker") |
76 | | - if [ "$_old_workspace" != "$(pwd)" ]; then |
77 | | - echo " Workspace path changed: $_old_workspace -> $(pwd)" |
78 | | - echo " Updating cached paths..." |
79 | | - # Update CMake build files in staging/ |
80 | | - find "$_cache_dir/staging" -type f \ |
81 | | - \( -name "CMakeCache.txt" -o -name "*.cmake" \ |
82 | | - -o -name "*.make" -o -name "Makefile" \ |
83 | | - -o -name "build.ninja" \) \ |
84 | | - -exec sed -i "s|${_old_workspace}|$(pwd)|g" {} + 2>/dev/null || true |
85 | | - # Compiled binaries have stale paths baked in — delete install/ |
86 | | - # so CMake rebuilds and re-installs them with correct paths. |
87 | | - echo " Clearing install/ to force rebuild of binaries..." |
88 | | - rm -rf "$_cache_dir/install" |
89 | | - fi |
90 | | -fi |
91 | | -echo "$(pwd)" > "$_workspace_marker" |
92 | | - |
93 | 34 | echo " Symlink: build -> $_cache_dir" |
94 | 35 | echo "=========================" |
0 commit comments