Skip to content

Commit 99bce6a

Browse files
authored
fix(index): quiet refresh and canonical cache labels
* fix(index): quiet refresh and canonical cache labels Closes #90 * fix(fetcher): retry direct xlings install after interface failure * fix(index): refresh official xim index for toolchains * fix(index): refresh missing xim package metadata * fix(index): reject stale xlings cache paths * docs(index): record PR gate status
1 parent 5423b21 commit 99bce6a

6 files changed

Lines changed: 778 additions & 24 deletions

File tree

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
# Index Refresh And Dependency Cache Label Fix
2+
3+
> Date: 2026-05-31 | Status: awaiting required review | Branch:
4+
> `codex/quiet-index-refresh-cache-labels`
5+
6+
Tracking issue: https://github.com/mcpp-community/mcpp/issues/90
7+
8+
## Scope
9+
10+
This document tracks the mcpp-side fixes found while validating `mcpp build`
11+
against the `xlings` project.
12+
13+
The current work is intentionally limited to build-tool behavior:
14+
15+
- make automatic package-index refresh quiet from the mcpp user's perspective;
16+
- make index freshness use a reliable mcpp-owned timestamp marker;
17+
- make dependency cache status use canonical dependency identities;
18+
- make large xpkg/toolchain installs retry through direct `xlings install`
19+
when the NDJSON interface path fails;
20+
- keep local xlings validation as the integration proof.
21+
22+
## Findings
23+
24+
### 1. Auto-refresh leaks xlings update output
25+
26+
`mcpp build` calls `ensure_index_fresh()` when a project has version-source
27+
dependencies. That path currently calls `xlings update` with `quiet=false`.
28+
29+
Observed output:
30+
31+
```text
32+
Updating package index (auto-refresh)
33+
[1/7] awesome::/home/speak/.mcpp/registry/data/xim-index-repos/...
34+
...
35+
index updated
36+
```
37+
38+
The `[N/M] awesome::...` lines are xlings internals. mcpp users should only see
39+
the mcpp-level status line unless they explicitly run a verbose/manual index
40+
update command.
41+
42+
### 2. Freshness check uses an unstable directory mtime
43+
44+
`is_index_fresh()` currently checks:
45+
46+
```text
47+
~/.mcpp/registry/data/mcpplibs/pkgs
48+
```
49+
50+
Local evidence showed:
51+
52+
```text
53+
mcpplibs/pkgs 2026-05-31 01:55:40
54+
mcpplibs/.git/FETCH_HEAD 2026-05-31 04:31:24
55+
xim-indexrepos.json 2026-05-31 04:31:27
56+
```
57+
58+
So `xlings update` can refresh the repository without updating the `pkgs/`
59+
directory mtime. Once the TTL expires, every full `prepare_build()` can decide
60+
that the index is stale again.
61+
62+
### 3. Dependency status labels can say `Compiling` for cached deps
63+
64+
For xlings:
65+
66+
```toml
67+
[dependencies.mcpplibs]
68+
xpkg = "0.0.41"
69+
tinyhttps = "0.2.3"
70+
```
71+
72+
The UI prints dependency names as `mcpplibs.xpkg` and `mcpplibs.tinyhttps`.
73+
The cache hit label is currently built from the dependency package's own
74+
`mcpp.toml` name. For tinyhttps that manifest uses:
75+
76+
```toml
77+
namespace = "mcpplibs"
78+
name = "tinyhttps"
79+
version = "0.2.3"
80+
```
81+
82+
The cache directory is therefore:
83+
84+
```text
85+
~/.mcpp/bmi/<fingerprint>/deps/mcpplibs/tinyhttps@0.2.3
86+
```
87+
88+
The UI compares `tinyhttps` against `mcpplibs.tinyhttps`, so it can print
89+
`Compiling mcpplibs.tinyhttps` even when the cache entry exists.
90+
91+
### 4. libxpkg 0.0.41 has stale embedded mcpp metadata
92+
93+
The installed `mcpplibs.xpkg@0.0.41` archive contains:
94+
95+
```toml
96+
name = "xpkg"
97+
version = "0.0.39"
98+
```
99+
100+
This causes mcpp to cache the resolved package as:
101+
102+
```text
103+
deps/mcpplibs/xpkg@0.0.39
104+
```
105+
106+
while the consumer dependency remains `mcpplibs.xpkg v0.0.41`.
107+
108+
### 5. Cold Linux CI can fail musl-gcc via xlings interface install
109+
110+
PR #91 inherited a main-branch Linux CI failure in:
111+
112+
```text
113+
Toolchain: musl-gcc - build mcpp (--target)
114+
```
115+
116+
The failing command was:
117+
118+
```text
119+
mcpp build --target x86_64-linux-musl
120+
```
121+
122+
The visible error was only:
123+
124+
```text
125+
xlings install of 'xim:musl-gcc@15.1.0' failed (exit 1)
126+
```
127+
128+
The same workflow's e2e suite had already proven that fresh-home musl-gcc
129+
installation can work, and recent CI history showed this as a cold-cache
130+
failure on main too. The weak point is that `Fetcher::resolve_xpkg_path()`
131+
uses only the xlings NDJSON interface path for regular xpkg installs, while
132+
`mcpp.xlings::install_with_progress()` already documents the direct CLI path
133+
as more reliable for large package installs.
134+
135+
### 6. Fresh mcpplibs marker does not imply fresh official xim index
136+
137+
The direct-install fallback exposed the next failing layer in Linux CI:
138+
139+
```text
140+
[error] package 'xim:musl-gcc@15.1.0' not found
141+
error: toolchain 'gcc@15.1.0-musl': xlings install of 'xim:musl-gcc@15.1.0' failed (interface exit 1, direct exit 1)
142+
```
143+
144+
The same workflow's earlier e2e suite could install/build with musl-gcc in an
145+
isolated fresh home, so the package resource was available. The failing later
146+
step used the main mcpp sandbox, where `mcpplibs/.mcpp-index-updated` can be
147+
fresh while `xim-pkgindex/pkgs` is missing or stale. Toolchains are resolved
148+
from xlings' official `xim-pkgindex`, so mcpp must track that index separately
149+
from the default modular-library `mcpplibs` index.
150+
151+
### 7. Fresh official index marker does not imply the target package exists
152+
153+
The third PR checkpoint still failed in the same Linux musl step. The package
154+
lookup error remained:
155+
156+
```text
157+
[error] package 'xim:musl-gcc@15.1.0' not found
158+
```
159+
160+
That means a restored CI cache can still satisfy the coarse check
161+
`xim-pkgindex/pkgs + .mcpp-index-updated` while missing the package file added
162+
later:
163+
164+
```text
165+
xim-pkgindex/pkgs/m/musl-gcc.lua
166+
```
167+
168+
For `xim:` auto-installs, the freshness guard must therefore check the target
169+
package's index file, not just the official index directory. If the package
170+
file is absent, mcpp should silently run `xlings update` before calling either
171+
the NDJSON interface install path or the direct CLI fallback.
172+
173+
### 8. xlings index cache can be poisoned by temp-home symlinked indexes
174+
175+
The fourth PR checkpoint still failed in the same Linux musl step. Local
176+
inspection revealed an additional xlings cache layer:
177+
178+
```text
179+
xim-pkgindex/.xlings-index-cache.json
180+
```
181+
182+
That cache stores absolute `path` values for each xpkg file. mcpp e2e tests
183+
inherit the global `xim-pkgindex` into temp `MCPP_HOME` directories, often via
184+
symlink. When xlings rebuilds the cache from that temp home, it can write paths
185+
like:
186+
187+
```text
188+
/tmp/tmp.X.../mcpp-home/registry/data/xim-pkgindex/pkgs/m/musl-gcc.lua
189+
```
190+
191+
into the shared global index directory. Later, the main sandbox loads that
192+
cache and `PackageCatalog::build_match_()` calls `load_package()` on the stale
193+
temp path. The load fails, the match is discarded, and the user-facing error is
194+
still:
195+
196+
```text
197+
package 'xim:musl-gcc@15.1.0' not found
198+
```
199+
200+
So the mcpp guard must also reject a package cache whose target package entry
201+
does not point at the current sandbox's package file.
202+
203+
## Implementation Plan
204+
205+
- [x] Add focused regression coverage for default-index refresh quietness.
206+
- [x] Add a reliable mcpp-owned index freshness marker after successful update.
207+
- [x] Make automatic refresh call `update_index(..., quiet=true)`.
208+
- [x] Keep explicit/manual index update output available for diagnostics.
209+
- [x] Canonicalize dependency identity used for cache-hit labels.
210+
- [x] Avoid using stale embedded package version as the user-facing resolved
211+
dependency version when the index resolution already knows the requested
212+
version.
213+
- [x] Add a direct `xlings install <target> -y` fallback after interface
214+
install failure, preserving visible direct-install output for CI
215+
diagnostics.
216+
- [x] Track official `xim-pkgindex` freshness independently and refresh it
217+
before auto-installing `xim:` toolchain packages.
218+
- [x] Require the target package file to exist before treating an official
219+
`xim:` index as fresh.
220+
- [x] Reject xlings index caches whose target package path points at a foreign
221+
temp/home directory.
222+
- [x] Validate with the local xlings checkout using the new mcpp binary.
223+
- [x] Push a draft PR and use it as the multi-commit checkpoint.
224+
225+
## Verification Plan
226+
227+
- [x] Run targeted unit/e2e coverage in mcpp.
228+
- [x] Build mcpp itself.
229+
- [x] Run `mcpp build` in `/home/speak/test/tmp/xlings` with the new binary.
230+
- [x] Confirm auto-refresh no longer prints xlings `[N/M] index::path` lines.
231+
- [x] Confirm repeated xlings full prepare does not refresh the index again
232+
while the marker is inside TTL.
233+
- [x] Confirm cached dependencies display as `Cached` when their artifacts are
234+
staged.
235+
- [x] Record CI status on the PR after the final checkpoint commit.
236+
237+
## Dynamic Notes
238+
239+
- `mcpp build` has a 0.01s fast path when `build.ninja` is newer than source
240+
files; the noisy path happens when full `prepare_build()` runs.
241+
- A no-change `ninja -C target/... -n` in the local xlings checkout reported
242+
`ninja: no work to do`, so the status-line issue is partly UI/cache identity
243+
rather than always an actual compiler invocation.
244+
- Added regression tests:
245+
- `tests/unit/test_xlings.cpp` now requires `.mcpp-index-updated` for a
246+
fresh default index.
247+
- `tests/e2e/53_namespaced_cache_label.sh` verifies quiet auto-refresh,
248+
marker creation, and canonical `Cached compat.widget v1.0.0` output.
249+
- Local mcpp verification:
250+
- `mcpp test -- --gtest_filter=XlingsIndexFreshness.*` passed; the command
251+
built and ran all 16 test binaries, with `test_xlings` covering the filter.
252+
- `mcpp build --no-cache` passed and produced
253+
`target/x86_64-linux-gnu/4d24c8b57fdbbbb4/bin/mcpp`.
254+
- `MCPP=.../bin/mcpp bash tests/e2e/49_bmi_cache_nested_custom_index.sh`
255+
passed.
256+
- `MCPP=.../bin/mcpp bash tests/e2e/52_local_path_namespaced_index.sh`
257+
passed.
258+
- `MCPP=.../bin/mcpp bash tests/e2e/53_namespaced_cache_label.sh` passed.
259+
- Local xlings verification with the new mcpp binary:
260+
- First `build --print-fingerprint`: exit `0`, displayed only
261+
`Updating package index (auto-refresh)` with no xlings `[N/M]` lines.
262+
- Second `build --print-fingerprint`: exit `0`, no index refresh line, and
263+
`mcpplibs.tinyhttps` / `mcpplibs.xpkg` displayed as `Cached`.
264+
- Marker was written at
265+
`~/.mcpp/registry/data/mcpplibs/.mcpp-index-updated`.
266+
- CI follow-up:
267+
- Linux CI on PR #91 failed in `Toolchain: musl-gcc - build mcpp
268+
(--target)`.
269+
- The same step had already failed on `origin/main` run `26691717542`, so
270+
this is not introduced solely by PR #91.
271+
- Added direct-install fallback in `src/xlings.cppm` and
272+
`src/pm/package_fetcher.cppm`.
273+
- Re-ran local `mcpp build --no-cache`, `mcpp test --
274+
--gtest_filter=XlingsIndexFreshness.*`, and the three related e2e tests.
275+
- The fallback made the hidden root cause visible on the second run:
276+
the main sandbox could not find `xim:musl-gcc@15.1.0` because the official
277+
`xim-pkgindex` data was missing/stale even though mcpp's default
278+
`mcpplibs` marker was fresh.
279+
- Added `is_official_index_fresh()` / `ensure_official_index_fresh()` and
280+
call it before `xim:` auto-installs.
281+
- Added unit coverage for a fresh default index with a missing official
282+
`xim-pkgindex`.
283+
- Local verification after this fix:
284+
- `mcpp build --no-cache` passed.
285+
- `mcpp test -- --gtest_filter=XlingsIndexFreshness.*` passed with 6
286+
matching `test_xlings` cases.
287+
- e2e `49_bmi_cache_nested_custom_index.sh`,
288+
`52_local_path_namespaced_index.sh`, and `53_namespaced_cache_label.sh`
289+
passed.
290+
- `/tmp/mcpp-fresh-codex clean && /tmp/mcpp-fresh-codex build --target
291+
x86_64-linux-musl` passed and resolved `gcc@15.1.0-musl`.
292+
- The next CI run still failed at the same musl step, proving the remaining
293+
stale-cache shape is target-package-level, not just official-index-level.
294+
- Added `is_official_package_index_fresh()` /
295+
`ensure_official_package_index_fresh()` and wired `xim:` auto-installs to
296+
check the concrete package file (for example `pkgs/m/musl-gcc.lua`).
297+
- Added two more unit tests for a fresh official index marker with a missing
298+
target package file.
299+
- Local verification after this package-file fix:
300+
- `mcpp test -- --gtest_filter=XlingsIndexFreshness.*` passed with 8
301+
matching `test_xlings` cases.
302+
- `mcpp build --no-cache` passed.
303+
- e2e `49_bmi_cache_nested_custom_index.sh`,
304+
`52_local_path_namespaced_index.sh`, and `53_namespaced_cache_label.sh`
305+
passed.
306+
- `/tmp/mcpp-fresh-codex clean && /tmp/mcpp-fresh-codex build --target
307+
x86_64-linux-musl` passed and resolved `gcc@15.1.0-musl`.
308+
- The next CI run still failed at the same musl step. The remaining issue is
309+
xlings' `.xlings-index-cache.json` using absolute package-file paths that
310+
can be written from e2e temp homes into the shared index directory.
311+
- Added cache-path validation for the target package file in
312+
`is_official_package_index_fresh()`.
313+
- Added two unit tests for foreign/current package paths inside
314+
`.xlings-index-cache.json`.
315+
- Local verification after this cache-path fix:
316+
- `mcpp test -- --gtest_filter=XlingsIndexFreshness.*` passed with 10
317+
matching `test_xlings` cases.
318+
- `mcpp build --no-cache` passed.
319+
- e2e `49_bmi_cache_nested_custom_index.sh`,
320+
`52_local_path_namespaced_index.sh`, and `53_namespaced_cache_label.sh`
321+
passed.
322+
- `/tmp/mcpp-fresh-codex clean && /tmp/mcpp-fresh-codex build --target
323+
x86_64-linux-musl` passed and resolved `gcc@15.1.0-musl`.
324+
- Final CI on PR #91 at commit
325+
`f93f3179331ad87434d6643a88ed526677b1e4e5` passed on all required
326+
platforms:
327+
- Linux `ci-linux`, run `26697058042`, job `78683273931`.
328+
- macOS `ci-macos`, run `26697058049`, job `78683273992`.
329+
- Windows `ci-windows`, run `26697058053`, job `78683274017`.
330+
- PR #91 is ready for review and mergeable, but branch protection currently
331+
reports `reviewDecision=REVIEW_REQUIRED`. A normal squash merge was
332+
rejected by base-branch policy, and repository auto-merge is disabled. Do
333+
not use `--admin`; wait for maintainer approval before squash merging.

0 commit comments

Comments
 (0)