Skip to content

Commit 0cd4c9f

Browse files
authored
ci(build): route x86 variants through BuildBox remote execution (#1227)
* ci(build): route x86 variants through BuildBox remote execution with fail-closed verification * refactor(ci): slim the config generator since the checker owns shape assertions
1 parent b307333 commit 0cd4c9f

12 files changed

Lines changed: 490 additions & 331 deletions

File tree

Lines changed: 71 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
name: Generate BuildStream CI config
2-
description: Write BuildStream CI config for the runner-based Dakota workflow.
2+
description: Write BuildStream CI config for remote builds and local artifact consumers.
33

44
inputs:
55
enable-remote-execution:
6-
description: Retained for compatibility; Dakota uses the GitHub runner for builds and cache.projectbluefin.io for artifact/source caches.
6+
description: Route build actions and CAS storage through cache.projectbluefin.io.
77
required: true
88
enable-push:
9-
description: Push artifacts and sources to the remote cache. Set false for build jobs.
9+
description: Publish built artifacts and fetched sources during the BuildStream build.
1010
required: false
1111
default: 'true'
1212

@@ -23,13 +23,40 @@ runs:
2323
WORKSPACE_ROOT="${GITHUB_WORKSPACE:-$PWD}"
2424
mkdir -p "$WORKSPACE_ROOT/logs"
2525
26-
if [[ "${ENABLE_REMOTE_EXECUTION:-false}" == "true" ]]; then
27-
echo "::error::Dakota CI uses the GitHub runner for the BuildStream build; remote execution is intentionally disabled."
26+
if [[ "${ENABLE_REMOTE_EXECUTION:-false}" == "true" ]] &&
27+
{ [[ -z "${CASD_CLIENT_CERT:-}" ]] || [[ -z "${CASD_CLIENT_KEY:-}" ]]; }; then
28+
echo "::error::remote execution was requested but CASD client credentials are missing"
2829
exit 1
2930
fi
3031
3132
printf '%s\n' "${CASD_CLIENT_CERT:-}" > "$WORKSPACE_ROOT/client.crt"
3233
printf '%s\n' "${CASD_CLIENT_KEY:-}" > "$WORKSPACE_ROOT/client.key"
34+
chmod 0600 "$WORKSPACE_ROOT/client.crt" "$WORKSPACE_ROOT/client.key"
35+
36+
# Every remote service entry shares one endpoint tuning and mTLS
37+
# identity; the stanzas expand inside the unquoted heredocs below.
38+
CONNECTION_CONFIG=$(cat <<'EOF'
39+
connection-config:
40+
keepalive-time: 180
41+
retry-limit: 5
42+
retry-delay: 1000
43+
request-timeout: 180
44+
EOF
45+
)
46+
CLIENT_AUTH=$(cat <<'EOF'
47+
auth:
48+
client-key: /src/client.key
49+
client-cert: /src/client.crt
50+
EOF
51+
)
52+
53+
if [[ "${ENABLE_REMOTE_EXECUTION:-false}" == "true" ]]; then
54+
# Four BuildBox action slots × four jobs per action is the initial
55+
# conservative ceiling on the 32-thread, 128 GiB executor.
56+
MAX_JOBS=4
57+
else
58+
MAX_JOBS=1
59+
fi
3360
3461
cat > "$WORKSPACE_ROOT/buildstream-ci.conf" <<'BSTCONF'
3562
scheduler:
@@ -46,10 +73,10 @@ runs:
4673
logdir: /src/logs
4774
BSTCONF
4875
49-
cat >> "$WORKSPACE_ROOT/buildstream-ci.conf" <<'BSTCONFBUILD'
76+
cat >> "$WORKSPACE_ROOT/buildstream-ci.conf" <<BSTCONFBUILD
5077
build:
5178
retry-failed: True
52-
max-jobs: 1
79+
max-jobs: ${MAX_JOBS}
5380
BSTCONFBUILD
5481
5582
if [[ -n "${CASD_CLIENT_CERT:-}" ]] && [[ -n "${CASD_CLIENT_KEY:-}" ]]; then
@@ -60,55 +87,27 @@ runs:
6087
servers:
6188
- url: https://cache.projectbluefin.io:11002
6289
push: ${PUSH_FLAG}
63-
connection-config:
64-
keepalive-time: 180
65-
retry-limit: 5
66-
retry-delay: 1000
67-
request-timeout: 180
68-
auth:
69-
client-key: /src/client.key
70-
client-cert: /src/client.crt
90+
${CONNECTION_CONFIG}
91+
${CLIENT_AUTH}
7192
- url: https://gbm.gnome.org:11003
7293
push: false
73-
connection-config:
74-
keepalive-time: 180
75-
retry-limit: 5
76-
retry-delay: 1000
77-
request-timeout: 180
94+
${CONNECTION_CONFIG}
7895
- url: https://cache.freedesktop-sdk.io:11001
7996
push: false
80-
connection-config:
81-
keepalive-time: 180
82-
retry-limit: 5
83-
retry-delay: 1000
84-
request-timeout: 180
97+
${CONNECTION_CONFIG}
8598
8699
source-caches:
87100
servers:
88101
- url: https://cache.projectbluefin.io:11002
89102
push: ${PUSH_FLAG}
90-
connection-config:
91-
keepalive-time: 180
92-
retry-limit: 5
93-
retry-delay: 1000
94-
request-timeout: 180
95-
auth:
96-
client-key: /src/client.key
97-
client-cert: /src/client.crt
103+
${CONNECTION_CONFIG}
104+
${CLIENT_AUTH}
98105
- url: https://gbm.gnome.org:11003
99106
push: false
100-
connection-config:
101-
keepalive-time: 180
102-
retry-limit: 5
103-
retry-delay: 1000
104-
request-timeout: 180
107+
${CONNECTION_CONFIG}
105108
- url: https://cache.freedesktop-sdk.io:11001
106109
push: false
107-
connection-config:
108-
keepalive-time: 180
109-
retry-limit: 5
110-
retry-delay: 1000
111-
request-timeout: 180
110+
${CONNECTION_CONFIG}
112111
BSTCONFCACHE
113112
fi
114113
@@ -118,67 +117,37 @@ runs:
118117
cache-buildtrees: never
119118
BSTCONFCACHEBLOCK
120119
121-
if ! grep -Eq '^[[:space:]]*max-jobs:[[:space:]]*1[[:space:]]*$' "$WORKSPACE_ROOT/buildstream-ci.conf"; then
122-
echo "::error::buildstream-ci.conf is missing build.max-jobs: 1; the runner-based Dakota build requires this cap."
123-
exit 1
120+
if [[ "${ENABLE_REMOTE_EXECUTION:-false}" == "true" ]]; then
121+
# The top-level storage service makes the runner's local casd a
122+
# remote-backed cache, so artifact payloads stay on the BuildBox host.
123+
# Keep this out of fetch-only publish configs: export must materialize
124+
# the final artifact on the runner.
125+
cat >> "$WORKSPACE_ROOT/buildstream-ci.conf" <<BSTCONFREMOTE
126+
storage-service:
127+
url: https://cache.projectbluefin.io:11002
128+
${CONNECTION_CONFIG}
129+
${CLIENT_AUTH}
130+
131+
remote-execution:
132+
execution-service:
133+
url: https://cache.projectbluefin.io:11002
134+
${CONNECTION_CONFIG}
135+
${CLIENT_AUTH}
136+
storage-service:
137+
url: https://cache.projectbluefin.io:11002
138+
${CONNECTION_CONFIG}
139+
${CLIENT_AUTH}
140+
action-cache-service:
141+
url: https://cache.projectbluefin.io:11002
142+
push: true
143+
${CONNECTION_CONFIG}
144+
${CLIENT_AUTH}
145+
BSTCONFREMOTE
146+
echo "Remote execution enabled: yes"
147+
else
148+
echo "Remote execution enabled: no"
124149
fi
125150
126151
cp "$WORKSPACE_ROOT/buildstream-ci.conf" "$WORKSPACE_ROOT/logs/buildstream-ci.conf"
127-
echo "Remote execution enabled: no"
128152
echo "=== BuildStream CI config ==="
129153
cat "$WORKSPACE_ROOT/buildstream-ci.conf"
130-
131-
if [[ -n "${CASD_CLIENT_CERT:-}" ]] && [[ -n "${CASD_CLIENT_KEY:-}" ]]; then
132-
cat > "$WORKSPACE_ROOT/buildstream-push.conf" <<'BSTCONFPUSH'
133-
scheduler:
134-
on-error: continue
135-
fetchers: 16
136-
builders: 2
137-
network-retries: 3
138-
139-
logging:
140-
message-format: '[%{wallclock}][%{elapsed}][%{key}][%{element}] %{action} %{message}'
141-
error-lines: 80
142-
143-
cachedir: /root/.cache/buildstream
144-
logdir: /src/logs
145-
146-
build:
147-
retry-failed: True
148-
max-jobs: 1
149-
150-
cache:
151-
cache-buildtrees: never
152-
153-
artifacts:
154-
servers:
155-
- url: https://cache.projectbluefin.io:11002
156-
push: true
157-
connection-config:
158-
keepalive-time: 180
159-
retry-limit: 5
160-
retry-delay: 1000
161-
request-timeout: 180
162-
auth:
163-
client-key: /src/client.key
164-
client-cert: /src/client.crt
165-
- url: https://gbm.gnome.org:11003
166-
push: false
167-
connection-config:
168-
keepalive-time: 180
169-
retry-limit: 5
170-
retry-delay: 1000
171-
request-timeout: 180
172-
- url: https://cache.freedesktop-sdk.io:11001
173-
push: false
174-
connection-config:
175-
keepalive-time: 180
176-
retry-limit: 5
177-
retry-delay: 1000
178-
request-timeout: 180
179-
180-
BSTCONFPUSH
181-
cp "$WORKSPACE_ROOT/buildstream-push.conf" "$WORKSPACE_ROOT/logs/buildstream-push.conf"
182-
echo "=== BuildStream push config ==="
183-
cat "$WORKSPACE_ROOT/buildstream-push.conf"
184-
fi

.github/copilot-instructions.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,13 @@ re-run the pre-flight until it is clean. Only then proceed.
170170
- "The stale build is for a different branch, it won't interfere" → **It uses the same runners and CAS. Cancel it.**
171171
- "I already cancelled one build, that's enough" → **Cancel ALL of them. Run the pre-flight again.**
172172

173-
Concurrent BST builds share the same `ubuntu-24.04` runner pool and the same remote CAS
174-
write bandwidth at `cache.projectbluefin.io:11002`. Two concurrent builds do not halve
175-
wall time — they more than double it and risk 6-hour timeouts with
176-
`Cached elements after warm: 0`.
173+
Independent BST **workflow runs** share the same remote executor and CAS. Never
174+
start a second build workflow while one is active. The four x86_64 matrix jobs inside
175+
one `build.yml` run are the intentional exception: they start together and are bounded
176+
by the BuildBox backend's four global action slots. Do not cancel matrix siblings or
177+
serialize them; their CAS payloads stay remote and BuildBox enforces capacity.
177178

178-
**One build. Field clear first. No exceptions.**
179+
**One build workflow run, with its four coordinated variants. Field clear first.**
179180

180181
**Pre-flight is atomic:** cancel → verify clear → push/dispatch must complete in one
181182
uninterrupted sequence. Cancelling the active build and then not pushing the
@@ -191,8 +192,8 @@ lesson (2026-07-09).
191192
## CI overview
192193

193194
- **Schedule:** nightly at 13:00 UTC (after gnome-build-meta nightly ~08:00 UTC finish)
194-
- **Publish triggers:** `merge_group`, `schedule`, `workflow_dispatch` (not `pull_request`)
195-
- **Remote cache:** `cache.projectbluefin.io:11002` (mTLS — `CASD_CLIENT_CERT` + `CASD_CLIENT_KEY`)
195+
- **Build triggers:** BST-affecting `push: testing`, daily schedule, or `workflow_dispatch` (not PR/merge-group)
196+
- **Remote build:** BuildBox execution + remote CAS at `cache.projectbluefin.io:11002` (mTLS — `CASD_CLIENT_CERT` + `CASD_CLIENT_KEY`)
196197
- **Image:** `ghcr.io/projectbluefin/dakota:{testing,stable,next,btw}` and `:<sha>` (`:latest` is never published)
197198

198199
## Key architecture

.github/workflows/build.yml

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ jobs:
4848
if: (!cancelled()) && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'push')
4949
runs-on: ubuntu-24.04
5050
timeout-minutes: 350
51+
# The BuildBox backend has four action slots. Start every image variant
52+
# together; the backend remains the global execution-capacity limit.
5153
strategy:
5254
fail-fast: false
53-
max-parallel: 1
55+
max-parallel: 4
5456
matrix:
5557
include:
5658
- variant: default
@@ -101,17 +103,11 @@ jobs:
101103
CASD_CLIENT_KEY: ${{ secrets.CASD_CLIENT_KEY }}
102104
uses: ./.github/actions/generate-bst-ci-config
103105
with:
104-
enable-remote-execution: "false"
105-
enable-push: "false"
106-
107-
# Warm-up only; the build step pulls missing artifacts on demand.
108-
- name: Pull prebuilt artifacts from remote CAS
109-
continue-on-error: true
110-
env:
111-
BST_FLAGS: -o x86_64_v3 false --no-interactive --config /src/buildstream-ci.conf
112-
run: |
113-
just bst artifact pull --deps all ${{ matrix.element }}
114-
timeout-minutes: 60
106+
enable-remote-execution: "true"
107+
# BuildStream adds artifact/source push queues to the build when the
108+
# configured remotes are writable. RE and the caches share one CAS,
109+
# so payloads remain on the BuildBox host and are deduplicated there.
110+
enable-push: "true"
115111

116112
- name: Count elements for progress tracking
117113
id: count
@@ -139,16 +135,19 @@ jobs:
139135
ELEMENT_TOTAL: ${{ steps.count.outputs.total }}
140136
run: |
141137
set -o pipefail
138+
CONSOLE_LOG="logs/bst-console-${{ matrix.variant }}.log"
142139
just bst build --deps none ${{ matrix.element }} 2>&1 \
140+
| tee "$CONSOLE_LOG" \
143141
| python3 files/scripts/bst-progress.py
144-
timeout-minutes: 330
145142
146-
- name: Push OCI artifact to remote CAS
147-
env:
148-
BST_FLAGS: -o x86_64_v3 false --no-interactive --config /src/buildstream-push.conf
149-
run: |
150-
just bst artifact push --deps run ${{ matrix.element }}
151-
timeout-minutes: 120
143+
# Fail closed: a green cache hit is not proof that BuildStream loaded
144+
# the remote executor. The startup banner is emitted whenever the
145+
# remote-execution block is active, even if no action needs rebuilding.
146+
if ! grep -Fq "Remote Execution Configuration" "$CONSOLE_LOG"; then
147+
echo "::error::BuildStream did not load the remote execution configuration"
148+
exit 1
149+
fi
150+
timeout-minutes: 330
152151

153152
- name: Upload build logs
154153
if: always()

Justfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ bst *ARGS:
7878
[group('dev')]
7979
check-publish-workflow:
8080
python3 scripts/check_publish_workflow.py
81+
python3 -m unittest scripts.test_check_publish_workflow
8182

8283
[group('dev')]
8384
monitor-pipeline BUILD_RUN_ID="":

docs/ci.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
|---|---|---|
77
| `validate` | `pull_request` | `bst show` — graph + patch check (~15 min) |
88
| `e2e` | `pull_request` when `elements/`, `files/`, `patches/`, `Justfile`, or `project.conf` changed | Smoke test in QEMU via projectbluefin/testsuite |
9-
| `build` | `push: testing/next` (paths-ignore: `.github/workflows/**`, `docs/**`, `**.md`, `AGENTS.md`), `merge_group`, `workflow_dispatch`, `schedule: daily 13:00 UTC` — skips on `pull_request` | Full OCI build (~60–90 min) |
9+
| `build` | `push: testing` (BST-affecting paths), `workflow_dispatch`, `schedule: daily 13:00 UTC` — skips on `pull_request`/`merge_group` | Four x86 variants concurrently through remote BuildBox execution; artifacts land in the remote CAS |
1010
| `build-aarch64` | `push: testing/main` (BST-affecting paths only), `workflow_run` from `publish.yml` on `testing`, `workflow_dispatch` | ARM64 — fully decoupled, never blocks release |
1111

1212
## Publish pipeline (publish.yml)
@@ -51,11 +51,11 @@ push to testing (BST-affecting) or daily 13:00 UTC schedule
5151

5252
## Schedule
5353

54-
Build fires daily at 13:00 UTC (`schedule:` in `build.yml`), plus on every BST-affecting push to `testing` or `next`, `merge_group`, and `workflow_dispatch`.
54+
Build fires daily at 13:00 UTC, on BST-affecting pushes to `testing`, and through `workflow_dispatch`. The nightly-next dispatcher invokes the same workflow explicitly on `next`; PR and merge-queue events do not run it.
5555

56-
## Remote cache
56+
## Remote execution and cache
5757

58-
`cache.projectbluefin.io:11002` — mTLS via `CASD_CLIENT_CERT` + `CASD_CLIENT_KEY`.
58+
`cache.projectbluefin.io:11002`BuildBox 1.4.11 execution, remote CAS, artifact/source caches, and action cache behind mTLS via `CASD_CLIENT_CERT` + `CASD_CLIENT_KEY`. Build jobs fail closed; publish uses a fetch-only configuration so it can materialize images locally.
5959

6060
## Published images
6161

docs/skills/buildstream.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,25 @@ Changing a `kind: stack` dependency does not always invalidate downstream `compo
138138

139139
Dakota's `patches/` directory is still carrying a small downstream patch queue for FSDK and GNOME Build Meta behavior that has not landed in the pinned upstream release yet. Every patch should carry an `Upstream-Status` header plus an `Exit` line so the next engineer can tell whether the workaround is still pending upstream or should be dropped. This is especially important when the patch is only needed because the junction is still older than the stock GNOME OS baseline.
140140

141-
### Warm-cache builds still take 90-120 min — this is normal (2026-06-23)
141+
### Legacy runner-local warm-cache timing (2026-06-23)
142142

143-
Even with a fully warm remote CAS, a full build takes 90-120 min. Common misconception: "cache is hot = fast build." Actual breakdown:
143+
> Superseded for x86 CI by remote-backed CAS and BuildBox execution on
144+
> 2026-07-28. The 90–150 minute range described the old explicit pre-pull and
145+
> runner-local assembly path; do not use it to justify reintroducing those steps.
144146
145-
- **Pull volume:** ~1,400 elements × a few seconds each / 32 parallel fetchers = 15-30 min just for network pulls
146-
- **Two parallel jobs:** `default` and `nvidia` both run simultaneously, each hitting the same CAS endpoint, halving effective bandwidth per job
147-
- **OCI assembly is sequential:** After all elements pull/build, `oci/bluefin.bst` runs chunkify + image assembly — single-threaded, typically 20-40 min on its own
148-
- **Cold elements:** Any junction ref bump (Renovate PRs for distrobox, gnome-build-meta, etc.) invalidates those subtrees → full recompile from source adds 30-90 min
147+
Cold junction changes can still take time because actions genuinely need to run,
148+
but they run on the remote executor. Measure the new path before tuning.
149149

150-
Do not cancel a build under 120 min just because it "seems slow." Historical range for successful builds: 90-150 min.
150+
### Fetcher count needs measurement under remote-backed CAS (2026-06-23)
151151

152-
### 32 fetchers is the right setting for cache.projectbluefin.io (2026-06-23)
153-
154-
`buildstream-ci.conf` uses `fetchers: 32` (BST default is 10). With default + nvidia running simultaneously = 64 concurrent gRPC streams. The CAS server is a Hetzner AX102-U (1 Gbit/s uplink, NVMe Gen4) and can serve 64 streams comfortably. The bottleneck is network bandwidth (~125 MB/s total), not server capacity. Do not reduce fetchers without evidence of server-side saturation.
152+
`buildstream-ci.conf` retains `fetchers: 32` for the initial RE rollout. Four
153+
variant jobs can create more metadata requests than the old two-job model, while
154+
top-level remote storage avoids transferring file payloads through the runners.
155+
Change this value only from observed endpoint saturation or latency evidence.
155156

156157
### Avoid /dev/stdin redirection in remote sandboxes (2026-07-25)
157158

158-
BuildStream elements that write inline configuration files using `install -Dm644 /dev/stdin ... <<'EOF'` fail in remote execution sandboxes (like BuildBarn) where `/dev/stdin` is not available as a standard character device. Write inline files using a two-step pattern: create the destination file with `install -Dm644 /dev/null target`, then populate it with `cat > target <<'EOF'`.
159+
BuildStream elements that write inline configuration files using `install -Dm644 /dev/stdin ... <<'EOF'` fail in remote execution sandboxes (such as BuildBox) where `/dev/stdin` is not available as a standard character device. Write inline files using a two-step pattern: create the destination file with `install -Dm644 /dev/null target`, then populate it with `cat > target <<'EOF'`.
159160

160161
### overlap-whitelist required for base system file replacement
161162

0 commit comments

Comments
 (0)