authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-04 12:43:04-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-04 19:59:06-07:00
log407dfe55dbe42cb807256f899650e1042bc2c8e4
tree2aa91617a06be1e158fb58b97ec0a0fafffebb99
parentd0b4d9575d4f0d808a7c00d9ff90846b5721ee3f
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

chore: CI checks out in workspaces/ci inside the checkout that holds the repo

A nested workspace finds that checkout through its `.jj/repo` file, so ci.py from any workspace shares one CI workspace and the main checkout's toolchains. Assisted-by: claude-opus-5.5

3 files changed, 17 insertions(+), 11 deletions(-)

.gitignore+2
......@@ -10,6 +10,8 @@ __pycache__/
1010/soak/
1111/tools/w7/payload/vendor/
1212/resources/
13# jj workspaces for CI, releases and agents, inside the checkout that holds the repo.
14/workspaces/
1315/.env
1416
1517# The global ignore excludes AGENTS.md; this repo keeps its own.
tools/TESTING.md+6-6
......@@ -3,11 +3,11 @@
33## The CI gate
44
55`python3 tools/ci.py` gates `main` (or `--rev REV`, or `--working-copy` for this
6checkout's `@`) in the jj workspace `../snowbound-ci`: it points that
6checkout's `@`) in the jj workspace `workspaces/ci`: it points that
77workspace's own commit, a child of `main`, at the revision's files, so other
88checkouts' edits in progress never reach the result, and a working copy is
99frozen as it was when the run started. Its build cache is
10`../snowbound-ci/target`, apart from every agent's `target/`. Runs wait for one
10`workspaces/ci/target`, apart from every agent's `target/`. Runs wait for one
1111another, and the exit status is the result.
1212
1313| Lane | Runs |
......@@ -31,17 +31,17 @@ python3 tools/ci.py --rev xyz --lanes test windows # `windows` names both wind
3131Lanes run four at a time (`--jobs`), each under its own time limit
3232(`--timeout MINUTES` overrides them all). The table it prints names each
3333failure's first errors with their files and lines, to tell whose edit broke
34it; `../snowbound-ci/target/ci/runs/TIME/` keeps every lane's log and
34it; `workspaces/ci/target/ci/runs/TIME/` keeps every lane's log and
3535`summary.json` (status, seconds, errors with files, each test executable's
36time), for the last 20 runs. After a run over `--budget` (80 GB), it deletes
36time), for the last 20 runs. After a run over `--budget` (40 GB), it deletes
3737the build units this run didn't use, least recently used first, which keeps
3838`deps/` small for the font tests that scan it. Windows needs llvm-mingw from
39`platform/windows/toolchain.sh` in this checkout's `target/windows`, or
39`platform/windows/toolchain.sh` in the main checkout's `target/windows`, or
4040`LLVM_MINGW`; Linux needs `zig`. `release.py` runs the gate on the commit it
4141publishes.
4242
4343The workspace is made on first use; to drop it, `jj workspace forget ci` and
44delete `../snowbound-ci`.
44delete `workspaces/ci`.
4545
4646## Public fixtures
4747
tools/ci.py+9-5
......@@ -1,6 +1,6 @@
11#!/usr/bin/env python3
22"""Gates a revision (main by default) on formatting, Clippy, the tests, the Python suite and
3every platform's build, checked out in the jj workspace ../snowbound-ci so that edits in
3every platform's build, checked out in the jj workspace workspaces/ci so that edits in
44progress elsewhere never reach it. See tools/TESTING.md."""
55import argparse
66from concurrent.futures import ThreadPoolExecutor
......@@ -18,7 +18,10 @@ import threading
1818import time
1919
2020ROOT = Path(__file__).resolve().parents[1]
21CI = ROOT.parent / 'snowbound-ci'
21# The checkout holding the jj repository, which a nested workspace's `.jj/repo` file names.
22STORE = ROOT / '.jj/repo'
23MAIN = (STORE.parent / STORE.read_text()).resolve().parents[1] if STORE.is_file() else ROOT
24CI = MAIN / 'workspaces/ci'
2225TARGET = CI / 'target'
2326RUNS = TARGET / 'ci'
2427# Changes here rebuild every package.
......@@ -43,8 +46,8 @@ def lanes():
4346 '--', '-D', 'warnings']
4447 windows = CI / 'platform/windows/cargo.sh'
4548 linux = ROOT / 'platform/linux/cargo.sh'
46 mingw = os.environ.get('LLVM_MINGW') or ROOT / 'target/windows/llvm-mingw'
47 sdk = Path(os.environ.get('SNOW_LEOPARD_SDK') or ROOT / 'target/snow-leopard/MacOSX10.6.sdk')
49 mingw = os.environ.get('LLVM_MINGW') or MAIN / 'target/windows/llvm-mingw'
50 sdk = Path(os.environ.get('SNOW_LEOPARD_SDK') or MAIN / 'target/snow-leopard/MacOSX10.6.sdk')
4851 nightly = subprocess.run(['rustup', 'component', 'list', '--installed', '--toolchain', 'nightly'],
4952 capture_output=True, text=True).stdout.split()
5053 result = [
......@@ -122,6 +125,7 @@ def checkout(rev):
122125 if len(commits) != 1:
123126 sys.exit(f'{rev} names {len(commits)} revisions, not one.')
124127 if not CI.exists():
128 CI.parent.mkdir(exist_ok=True)
125129 jj('workspace', 'add', '--name', 'ci', '-r', 'main', str(CI))
126130 jj('workspace', 'update-stale', cwd=CI)
127131 jj('rebase', '-r', '@', '-o', 'main', cwd=CI)
......@@ -376,7 +380,7 @@ def main():
376380 parser.add_argument('--test-jobs', type=int, default=4, help='Test executables at once (default 4)')
377381 parser.add_argument('--timeout', type=float, metavar='MINUTES', help="Each lane's limit, overriding its own")
378382 parser.add_argument('--budget', type=float, default=40, metavar='GB',
379 help='Prune ../snowbound-ci/target to this size after the run (default 40)')
383 help='Prune workspaces/ci/target to this size after the run (default 40)')
380384 args = parser.parse_args()
381385 if ROOT == CI:
382386 sys.exit(f'Run ci.py from another checkout; {CI} is its own.')