From 407dfe55dbe42cb807256f899650e1042bc2c8e4 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Sun, 4 Oct 2026 12:43:04 -0700 Subject: [PATCH] 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 --- .gitignore | 2 ++ tools/TESTING.md | 12 ++++++------ tools/ci.py | 14 +++++++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 041c96f85ab60600de067dc38782b727224a9f69..adca899aee0659936f757c57f94247afead18fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ __pycache__/ /soak/ /tools/w7/payload/vendor/ /resources/ +# jj workspaces for CI, releases and agents, inside the checkout that holds the repo. +/workspaces/ /.env # The global ignore excludes AGENTS.md; this repo keeps its own. diff --git a/tools/TESTING.md b/tools/TESTING.md index 6bb601c5efe5a96de6eb12b61f95bbe4a2881618..e5c19b193d5e6930e2ca7da68b3798e17bef654e 100644 --- a/tools/TESTING.md +++ b/tools/TESTING.md @@ -3,11 +3,11 @@ ## The CI gate `python3 tools/ci.py` gates `main` (or `--rev REV`, or `--working-copy` for this -checkout's `@`) in the jj workspace `../snowbound-ci`: it points that +checkout's `@`) in the jj workspace `workspaces/ci`: it points that workspace's own commit, a child of `main`, at the revision's files, so other checkouts' edits in progress never reach the result, and a working copy is frozen as it was when the run started. Its build cache is -`../snowbound-ci/target`, apart from every agent's `target/`. Runs wait for one +`workspaces/ci/target`, apart from every agent's `target/`. Runs wait for one another, and the exit status is the result. | Lane | Runs | @@ -31,17 +31,17 @@ python3 tools/ci.py --rev xyz --lanes test windows # `windows` names both wind Lanes run four at a time (`--jobs`), each under its own time limit (`--timeout MINUTES` overrides them all). The table it prints names each failure's first errors with their files and lines, to tell whose edit broke -it; `../snowbound-ci/target/ci/runs/TIME/` keeps every lane's log and +it; `workspaces/ci/target/ci/runs/TIME/` keeps every lane's log and `summary.json` (status, seconds, errors with files, each test executable's -time), for the last 20 runs. After a run over `--budget` (80 GB), it deletes +time), for the last 20 runs. After a run over `--budget` (40 GB), it deletes the build units this run didn't use, least recently used first, which keeps `deps/` small for the font tests that scan it. Windows needs llvm-mingw from -`platform/windows/toolchain.sh` in this checkout's `target/windows`, or +`platform/windows/toolchain.sh` in the main checkout's `target/windows`, or `LLVM_MINGW`; Linux needs `zig`. `release.py` runs the gate on the commit it publishes. The workspace is made on first use; to drop it, `jj workspace forget ci` and -delete `../snowbound-ci`. +delete `workspaces/ci`. ## Public fixtures diff --git a/tools/ci.py b/tools/ci.py index 739dd9d93a8db76c91767af07014eefd07864893..60cfbb1f633ef25114e8348277ca7a79ca830e27 100644 --- a/tools/ci.py +++ b/tools/ci.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """Gates a revision (main by default) on formatting, Clippy, the tests, the Python suite and -every platform's build, checked out in the jj workspace ../snowbound-ci so that edits in +every platform's build, checked out in the jj workspace workspaces/ci so that edits in progress elsewhere never reach it. See tools/TESTING.md.""" import argparse from concurrent.futures import ThreadPoolExecutor @@ -18,7 +18,10 @@ import threading import time ROOT = Path(__file__).resolve().parents[1] -CI = ROOT.parent / 'snowbound-ci' +# The checkout holding the jj repository, which a nested workspace's `.jj/repo` file names. +STORE = ROOT / '.jj/repo' +MAIN = (STORE.parent / STORE.read_text()).resolve().parents[1] if STORE.is_file() else ROOT +CI = MAIN / 'workspaces/ci' TARGET = CI / 'target' RUNS = TARGET / 'ci' # Changes here rebuild every package. @@ -43,8 +46,8 @@ def lanes(): '--', '-D', 'warnings'] windows = CI / 'platform/windows/cargo.sh' linux = ROOT / 'platform/linux/cargo.sh' - mingw = os.environ.get('LLVM_MINGW') or ROOT / 'target/windows/llvm-mingw' - sdk = Path(os.environ.get('SNOW_LEOPARD_SDK') or ROOT / 'target/snow-leopard/MacOSX10.6.sdk') + mingw = os.environ.get('LLVM_MINGW') or MAIN / 'target/windows/llvm-mingw' + sdk = Path(os.environ.get('SNOW_LEOPARD_SDK') or MAIN / 'target/snow-leopard/MacOSX10.6.sdk') nightly = subprocess.run(['rustup', 'component', 'list', '--installed', '--toolchain', 'nightly'], capture_output=True, text=True).stdout.split() result = [ @@ -122,6 +125,7 @@ def checkout(rev): if len(commits) != 1: sys.exit(f'{rev} names {len(commits)} revisions, not one.') if not CI.exists(): + CI.parent.mkdir(exist_ok=True) jj('workspace', 'add', '--name', 'ci', '-r', 'main', str(CI)) jj('workspace', 'update-stale', cwd=CI) jj('rebase', '-r', '@', '-o', 'main', cwd=CI) @@ -376,7 +380,7 @@ def main(): parser.add_argument('--test-jobs', type=int, default=4, help='Test executables at once (default 4)') parser.add_argument('--timeout', type=float, metavar='MINUTES', help="Each lane's limit, overriding its own") parser.add_argument('--budget', type=float, default=40, metavar='GB', - help='Prune ../snowbound-ci/target to this size after the run (default 40)') + help='Prune workspaces/ci/target to this size after the run (default 40)') args = parser.parse_args() if ROOT == CI: sys.exit(f'Run ci.py from another checkout; {CI} is its own.') -- 2.54.0