Skip to content
This repository was archived by the owner on Aug 19, 2026. It is now read-only.

Commit f296df5

Browse files
zozo123claude
andcommitted
docs: add RESEARCH.md — Angular Bazel build, caching, RBE & islo feasibility
Research artifact documenting a local //packages/core build (cold 304s / warm ~0.3s), Bazel caching & REAPI mechanics, and an honest islo/Incredibuild RBE feasibility verdict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c23ddd6 commit f296df5

1 file changed

Lines changed: 237 additions & 0 deletions

File tree

‎RESEARCH.md‎

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# Angular + Bazel Build Research: Caching, Remote Build Execution, and islo/Incredibuild Feasibility
2+
3+
> **This fork is a research artifact.** It is a fork of `angular/angular` created under an Incredibuild org solely to document a build-and-investigation session. It contains no product changes to Angular. **Only `//packages/core` was built**, and **no Incredibuild or islo endpoint was exercised** during this session — the RBE feasibility assessment below is analytical, not benchmarked against a live islo/Incredibuild backend.
4+
5+
## Abstract
6+
7+
This document records a research session whose goal was to (1) understand the structure of the `angular/angular` monorepo, (2) build a slice of it locally on an Apple Silicon Mac, (3) deep-dive how Bazel caching and Remote Build Execution (RBE) work, and (4) honestly assess whether the Angular build could run on Incredibuild or islo as an RBE backend.
8+
9+
The build half is fully empirical: every measurement below was independently confirmed on disk during the session. The feasibility half is analytical and modeled — it is explicitly labeled as such wherever a number is an estimate rather than a measurement. The short version of the verdict: a single laptop already wins from Bazel's local warm cache (~0.3s no-change rebuild), and neither Incredibuild nor islo appears to be a Bazel REAPI backend today, so the recommended first move is a cheap falsification test rather than an integration effort.
10+
11+
## What we did
12+
13+
| Step | Action | Outcome |
14+
|------|--------|---------|
15+
| 1 | Inspected the host machine and toolchain | Apple Silicon Mac, 10 cores, 32 GB RAM, macOS (darwin); git, Node v25.2.1, npm; host pnpm shims to the repo-pinned 11.7.0 (Homebrew binary is 10.25.0) |
16+
| 2 | Shallow-cloned `github.com/angular/angular` | ~196M working tree; repo uses `MODULE.bazel` (bzlmod) |
17+
| 3 | Installed `@bazel/bazelisk` globally | Bazelisk read `.bazelversion` (8.7.0) and fetched Bazel 8.7.0 |
18+
| 4 | Ran a cold build of `//packages/core:core` | Success ("Build completed successfully"); 304.142s elapsed, 2467 total actions |
19+
| 5 | Verified emitted artifacts on disk | Real compiler emit under `dist/bin/packages/core` (453 `.js`, 456 `.d.ts`, 453 `.js.map`) |
20+
| 6 | Ran a warm no-change rebuild | ~0.3s, "2 action cache hit, 1 internal" (1 total action) |
21+
| 7 | Confirmed the build ran 100% locally | Plain `bazel build` with no `--config=remote`; local `darwin-sandbox` + `worker` strategies only |
22+
| 8 | Studied Bazel caching + REAPI; assessed islo/Incredibuild as an RBE target | Findings and verdict below |
23+
24+
## Build measurements
25+
26+
### Environment
27+
28+
- **Host:** Apple Silicon Mac, 10 cores, 32 GB RAM, macOS (darwin).
29+
- **Host tooling:** git, Node v25.2.1, npm. The host's Homebrew pnpm binary is 10.25.0, but pnpm self-manages to the repo's `packageManager: "pnpm@11.7.0"` pin, so the effective host pnpm is 11.7.0 — it matches the repo.
30+
- **Bazel:** 8.7.0, fetched by `@bazel/bazelisk` reading `.bazelversion`.
31+
32+
**Hermeticity note (important):** the Angular build is hermetic. `rules_nodejs` 6.7.4 supplies Node 22.22.3 and a pinned pnpm 11.7.0 *inside the sandbox*, so the host toolchain was **not** used to compile. `aspect_rules_js` 3.2.1 and `aspect_rules_ts` 3.8.10 consume `pnpm-lock.yaml` directly. The only genuine host/sandbox version gap is Node (host v25.2.1 vs sandbox 22.22.3); the effective host pnpm (11.7.0, via self-management) already matches the sandbox. This is why the host toolchain version did not matter.
33+
34+
### Cold build
35+
36+
Command:
37+
38+
```
39+
bazel build //packages/core:core
40+
```
41+
42+
Result (from the build log):
43+
44+
```
45+
Elapsed time: 304.142s, Critical Path: 29.21s
46+
2467 processes: 695 internal, 1694 darwin-sandbox, 78 worker
47+
Build completed successfully, 2467 total actions
48+
```
49+
50+
The captured log reported "Build completed successfully" (its EXIT marker did not print a numeric exit code).
51+
52+
What the action breakdown actually means:
53+
54+
| Action class | Count | Meaning |
55+
|--------------|-------|---------|
56+
| `internal` | 695 | In-process Bazel actions — no subprocess spawned |
57+
| `darwin-sandbox` | 1694 | Subprocess actions under macOS `sandbox-exec`. **Not all "compiles"** — also includes bundling, `.d.ts` / source-map generation, and codegen |
58+
| `worker` | 78 | Persistent, reused worker processes (long-lived `tsc` / `ngtsc`) |
59+
| **Total** | **2467** | 695 + 1694 + 78 = 2467 |
60+
61+
The 304s wall-clock vs. the 29.21s critical path is the key signal: the build is highly parallel, and on this 10-core machine wall-clock is dominated by throughput across many actions, while 29.21s is the longest dependency chain (the theoretical floor for this target on infinite cores).
62+
63+
### Artifacts (proof of real emit)
64+
65+
Angular's `.bazelrc` sets `--symlink_prefix=dist/`, so outputs land under `dist/bin/packages/core`:
66+
67+
- 453 `.js`
68+
- 456 `.d.ts`
69+
- 453 `.js.map`
70+
71+
Spot-checks confirming this is genuine compiler emit, not a copy of sources:
72+
73+
- `src/version.js` has TypeScript types stripped and a `sourceMappingURL` footer.
74+
- `index.js` carries the license header and `export * from './public_api'`.
75+
76+
**Scope:** only `//packages/core` was built. `dist/bin/packages` contains exactly one package directory (`core/`) — **not** `common/`, `compiler/`, `router/`, etc. (only loose toolchain files like `tsconfig-build.json` sit beside it).
77+
78+
### Warm re-run
79+
80+
An immediate no-change rebuild of the same target:
81+
82+
- Measured ~0.3s (0.375s, then 0.335s).
83+
- Reported `2 action cache hit, 1 internal` (1 total action).
84+
85+
Why it's that fast:
86+
87+
- The **in-memory Skyframe incremental graph** lives on the persistent Bazel server.
88+
- The **on-disk local action cache** covers what the server doesn't hold in memory.
89+
- **External dependencies are already fetched.**
90+
91+
Storage footprint observed: Bazel output base `/var/tmp/_bazel_yossi.eliaz` was ~2.0G, of which `external/` was ~1.3G. **Note:** no separate `--disk_cache` or `--repository_cache` was configured — dependencies live in the output base's `external/` directory.
92+
93+
### Local vs. RBE — what actually ran
94+
95+
The build ran **100% locally.** Evidence:
96+
97+
- The command was plain `bazel build //packages/core:core` with **no** `--config=remote`.
98+
- The strategy names in the log — `darwin-sandbox` and `worker` — are **local** strategies.
99+
- Platform was `darwin_arm64-fastbuild`.
100+
- No `/tmp/rbe-grpc.log` was produced.
101+
102+
For completeness: Angular's `.bazelrc` *does* define a Google RBE config — `build:remote` points `remote_executor` / `remote_cache` at `remotebuildexecution.googleapis.com` with `remote_instance_name=projects/internal-200822/instances/primary_instance`, and `build:remote-cache` references an `angular-team-cache` GCS bucket. But that config is gated behind `--google_default_credentials` and is inert unless `--config=remote` is passed. **We never passed it.**
103+
104+
## How Bazel caching and RBE work
105+
106+
### From targets to actions
107+
108+
Bazel parses `BUILD` files into a **target graph**, then lowers it to an **action graph**. The **analysis phase** builds the full action graph and is pure — no commands run. The **execution phase** then runs (or cache-serves) each action.
109+
110+
### Hermeticity and sandboxing make caching sound
111+
112+
Each action sees only its **declared inputs** (enforced by the sandbox). This isolation is precisely what makes caching *sound*: if the declared inputs are identical, the output is guaranteed identical, so a cached result can be trusted.
113+
114+
### The action key
115+
116+
An action's cache key is a hash over:
117+
118+
- the `argv`,
119+
- the **content digests** of every declared input,
120+
- the toolchain binary,
121+
- the declared environment, and
122+
- the platform.
123+
124+
It hashes **content, not mtimes** — touching a file without changing its bytes does not bust the cache.
125+
126+
### Caching layers
127+
128+
1. **Skyframe in-memory** — per-server, volatile; cleared when the Bazel server dies.
129+
2. **Local action cache / `--disk_cache`** — content-addressable on local disk.
130+
3. **Repository cache** — network downloads keyed by sha256.
131+
4. **Remote cache** — a **CAS** (blobs addressed by `Digest`) plus an **ActionCache** (`Action` digest → `ActionResult`), spoken over the gRPC **Remote Execution API (REAPI v2)**.
132+
133+
### Remote Build Execution
134+
135+
RBE is remote *execution* over the same REAPI surface (`ContentAddressableStorage`, `ActionCache`, `Execution`, `Capabilities`):
136+
137+
- Inputs are uploaded as a **Merkle tree** into CAS.
138+
- The action is **scheduled on a remote worker** and run hermetically.
139+
- Outputs are written **back to CAS**.
140+
141+
Because the remote cache and remote executor **share the same CAS**, one engineer's (or CI's) previous execution becomes everyone's cache hit.
142+
143+
## Can Angular run on islo / Incredibuild RBE?
144+
145+
**Honest verdict up front: almost certainly not as a Bazel REAPI backend today.** The two products are not REAPI servers, so the interesting question is whether a cheap test can confirm or kill the idea quickly. The detail:
146+
147+
### Why neither is a REAPI backend today
148+
149+
- **Incredibuild** is *process virtualization*: an Initiator farms spawned child processes out to Helpers. It is **not REAPI**. The only known Bazel bridge — Bazel PR #18113 — **never shipped**.
150+
- **islo.dev** is a newer **microVM agent-sandbox** product with a **REST/JSON + SSE** API. It has **no gRPC and no REAPI**. Running `bazel` *inside* an islo sandbox makes islo a Bazel **client**, not an RBE **server**.
151+
- Neither product appears on Bazel's official **Remote Execution Services** vendor list (Buildbarn, Buildfarm, BuildGrid, NativeLink, BuildBuddy, EngFlow, ...).
152+
153+
### Speedup model (MODELED — not benchmarked)
154+
155+
These figures are analytical estimates from the observed build shape. They were **not** measured against any remote backend.
156+
157+
| Scenario | Modeled speedup | Why |
158+
|----------|-----------------|-----|
159+
| Remote **execution**, fully cold build, **one laptop** | ~5–8x | The Mac is already CPU-saturated across 10 cores; the 29s critical path is the floor; you also **lose** the warm persistent workers |
160+
| Shared **cache**, fresh/clean checkout | ~20–100x | A teammate or CI populated the CAS; you download results instead of recomputing |
161+
| Local warm rebuild vs. remote cache hit | **Local wins** | Local warm rebuild ~0.3s already beats a remote cache hit (~3–15s, network-bound) |
162+
163+
The real beneficiary of RBE/shared caching is a **CI fleet and a many-developer team**, not a single laptop that already has a hot local cache.
164+
165+
### The hard part: darwin → linux
166+
167+
Angular's `--config=remote` forces **linux/x86_64** (`--cpu=k8` plus linux platforms). To run remotely you would need:
168+
169+
- a custom `platform()` definition,
170+
- a linux container image with a glibc userland, and
171+
- linux toolchains.
172+
173+
And **the tests are the trap**: the Karma/Chrome test setup is the part most likely to break under a remote linux executor.
174+
175+
### Verdict
176+
177+
Treat islo/Incredibuild-as-Bazel-RBE as **unproven and probably unsupported** until a handshake test says otherwise. For a single laptop, local caching already wins; the value of RBE is a team/CI story and would require a real REAPI backend and a darwin→linux toolchain port.
178+
179+
## Recommendations / next steps
180+
181+
1. **Run a cheap falsification test first.** Add a `--config=islo` block that is **remote-cache-only** (no remote execution). The decisive check is whether the REAPI **`GetCapabilities` gRPC handshake** succeeds.
182+
2. **If the handshake fails** (likely, since islo is REST) the premise is dead. The right paths are then:
183+
- **(a) One laptop →** do nothing; local caching already wins.
184+
- **(b) Linux build/test →** run Bazel on a **Linux CI runner or VM** (solves the darwin→linux problem directly, no RBE needed).
185+
- **(c) Team / CI scale →** adopt a **real REAPI vendor** — BuildBuddy, EngFlow, NativeLink, or Buildbarn.
186+
3. **Do not invest in a darwin→linux remote toolchain port** until step 1 proves a working REAPI endpoint exists.
187+
188+
## How to reproduce
189+
190+
On an Apple Silicon Mac with git and Node available:
191+
192+
```bash
193+
# 1. Install the Bazel launcher (reads .bazelversion -> Bazel 8.7.0)
194+
npm install -g @bazel/bazelisk
195+
196+
# 2. Shallow-clone the repo
197+
git clone --depth=1 https://github.com/angular/angular.git
198+
cd angular
199+
200+
# 3. Cold build of the core package only
201+
bazel build //packages/core:core
202+
# expect: ~304s elapsed, 2467 total actions, "Build completed successfully"
203+
204+
# 4. Warm re-run (no changes) — same command
205+
bazel build //packages/core:core
206+
# expect: ~0.3s, "2 action cache hit, 1 internal" (1 total action)
207+
208+
# 5. Inspect emitted artifacts
209+
ls dist/bin/packages/core
210+
# expect: real .js / .d.ts / .js.map emit (453 / 456 / 453)
211+
```
212+
213+
Notes:
214+
215+
- Do **not** pass `--config=remote` unless you intend to use Google's RBE and have `--google_default_credentials` configured. This session never did.
216+
- The host's Node/pnpm versions do not affect the compile because the build is hermetic (toolchains are pinned inside the sandbox).
217+
- Numbers will vary with core count, disk, and cache state; the ~304s cold / ~0.3s warm split is the qualitative result to expect.
218+
219+
## Appendix: caveats, unknowns, and a security note
220+
221+
### Caveats and scope limits
222+
223+
- **Only `//packages/core` was built.** No other Angular package (`common`, `compiler`, `router`, ...) was compiled, so whole-repo build cost is not measured.
224+
- **No Incredibuild or islo endpoint was exercised.** The feasibility section is analysis, not a benchmark.
225+
- All speedup figures are **modeled**, not measured.
226+
- Measurements are from a single machine and a single run pair (cold + warm); they are illustrative, not a statistical sample.
227+
- The Google RBE config in `.bazelrc` exists but was never invoked; its current validity for external users is unknown.
228+
229+
### Open questions to resolve
230+
231+
- Does any islo/Incredibuild endpoint answer a REAPI `GetCapabilities` call at all? (The proposed falsification test answers this.)
232+
- What is the real cost of a full-repo cold build, and the full-repo warm rebuild?
233+
- How does the Karma/Chrome test path behave under a remote linux executor?
234+
235+
### Security note
236+
237+
During the session, AWS access keys were observed sitting in **plaintext** in the user's global `~/.claude/CLAUDE.md`. These should be **rotated** and moved into a proper credential store (e.g., a secrets manager or the AWS CLI credential provider chain). No secret values are reproduced in this document.

0 commit comments

Comments
 (0)