Skip to content

Commit 69f8226

Browse files
committed
fix(index): refresh official xim index for toolchains
1 parent f8ff845 commit 69f8226

4 files changed

Lines changed: 117 additions & 23 deletions

File tree

.agents/docs/2026-05-31-index-refresh-cache-labels-plan.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,22 @@ uses only the xlings NDJSON interface path for regular xpkg installs, while
132132
`mcpp.xlings::install_with_progress()` already documents the direct CLI path
133133
as more reliable for large package installs.
134134

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+
135151
## Implementation Plan
136152

137153
- [x] Add focused regression coverage for default-index refresh quietness.
@@ -145,6 +161,8 @@ as more reliable for large package installs.
145161
- [x] Add a direct `xlings install <target> -y` fallback after interface
146162
install failure, preserving visible direct-install output for CI
147163
diagnostics.
164+
- [x] Track official `xim-pkgindex` freshness independently and refresh it
165+
before auto-installing `xim:` toolchain packages.
148166
- [x] Validate with the local xlings checkout using the new mcpp binary.
149167
- [x] Push a draft PR and use it as the multi-commit checkpoint.
150168

@@ -198,3 +216,20 @@ as more reliable for large package installs.
198216
`src/pm/package_fetcher.cppm`.
199217
- Re-ran local `mcpp build --no-cache`, `mcpp test --
200218
--gtest_filter=XlingsIndexFreshness.*`, and the three related e2e tests.
219+
- The fallback made the hidden root cause visible on the second run:
220+
the main sandbox could not find `xim:musl-gcc@15.1.0` because the official
221+
`xim-pkgindex` data was missing/stale even though mcpp's default
222+
`mcpplibs` marker was fresh.
223+
- Added `is_official_index_fresh()` / `ensure_official_index_fresh()` and
224+
call it before `xim:` auto-installs.
225+
- Added unit coverage for a fresh default index with a missing official
226+
`xim-pkgindex`.
227+
- Local verification after this fix:
228+
- `mcpp build --no-cache` passed.
229+
- `mcpp test -- --gtest_filter=XlingsIndexFreshness.*` passed with 6
230+
matching `test_xlings` cases.
231+
- e2e `49_bmi_cache_nested_custom_index.sh`,
232+
`52_local_path_namespaced_index.sh`, and `53_namespaced_cache_label.sh`
233+
passed.
234+
- `/tmp/mcpp-fresh-codex clean && /tmp/mcpp-fresh-codex build --target
235+
x86_64-linux-musl` passed and resolved `gcc@15.1.0-musl`.

src/pm/package_fetcher.cppm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,12 @@ Fetcher::resolve_xpkg_path(std::string_view target,
692692

693693
// 3. Install via xlings (primary path).
694694
if (autoInstall) {
695+
if (parsed.indexName == "xim") {
696+
mcpp::xlings::Env xlEnv{ cfg_.xlingsBinary, cfg_.xlingsHome() };
697+
mcpp::xlings::ensure_official_index_fresh(
698+
xlEnv, cfg_.searchTtlSeconds, /*quiet=*/true);
699+
}
700+
695701
std::vector<std::string> targets {
696702
std::format("{}:{}@{}", parsed.indexName, parsed.packageName, parsed.version)
697703
};

src/xlings.cppm

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ void ensure_ninja(const Env& env, bool quiet,
241241
// Returns true if index is present and fresh, false otherwise.
242242
bool is_index_fresh(const Env& env, std::int64_t ttlSeconds);
243243

244+
// Check whether xlings' official xim index data exists and is fresh.
245+
// This is separate from mcpp's default mcpplibs index because xlings
246+
// toolchains live in xim-pkgindex, while modular libraries live in mcpplibs.
247+
bool is_official_index_fresh(const Env& env, std::int64_t ttlSeconds);
248+
244249
// Run `xlings update` to refresh all index repos. Streams output to stdout.
245250
// Returns the xlings exit code.
246251
int update_index(const Env& env, bool quiet = false);
@@ -250,6 +255,9 @@ int update_index(const Env& env, bool quiet = false);
250255
// when no update is needed.
251256
void ensure_index_fresh(const Env& env, std::int64_t ttlSeconds, bool quiet = false);
252257

258+
// Ensure xlings' official xim index is present and fresh.
259+
void ensure_official_index_fresh(const Env& env, std::int64_t ttlSeconds, bool quiet = false);
260+
253261
// ─── run_capture utility ────────────────────────────────────────────
254262

255263
std::expected<std::string, std::string> run_capture(const std::string& cmd);
@@ -278,21 +286,23 @@ std::filesystem::path default_index_dir(const Env& env) {
278286
return paths::index_data(env) / "mcpplibs";
279287
}
280288

281-
std::filesystem::path default_index_pkgs_dir(const Env& env) {
282-
return default_index_dir(env) / "pkgs";
289+
std::filesystem::path official_index_dir(const Env& env) {
290+
return paths::index_data(env) / "xim-pkgindex";
283291
}
284292

285-
std::filesystem::path default_index_refresh_marker(const Env& env) {
286-
return default_index_dir(env) / ".mcpp-index-updated";
293+
std::filesystem::path index_pkgs_dir(const std::filesystem::path& indexDir) {
294+
return indexDir / "pkgs";
287295
}
288296

289-
void mark_default_index_refreshed(const Env& env) {
290-
if (!env.projectDir.empty()) return;
291-
if (!std::filesystem::exists(default_index_pkgs_dir(env))) return;
297+
std::filesystem::path index_refresh_marker(const std::filesystem::path& indexDir) {
298+
return indexDir / ".mcpp-index-updated";
299+
}
292300

301+
void mark_index_refreshed(const std::filesystem::path& indexDir) {
302+
if (!std::filesystem::exists(index_pkgs_dir(indexDir))) return;
293303
std::error_code ec;
294-
std::filesystem::create_directories(default_index_dir(env), ec);
295-
auto marker = default_index_refresh_marker(env);
304+
std::filesystem::create_directories(indexDir, ec);
305+
auto marker = index_refresh_marker(indexDir);
296306
{
297307
std::ofstream os(marker, std::ios::trunc);
298308
if (!os) return;
@@ -302,6 +312,27 @@ void mark_default_index_refreshed(const Env& env) {
302312
marker, std::filesystem::file_time_type::clock::now(), ec);
303313
}
304314

315+
void mark_known_indexes_refreshed(const Env& env) {
316+
if (!env.projectDir.empty()) return;
317+
mark_index_refreshed(default_index_dir(env));
318+
mark_index_refreshed(official_index_dir(env));
319+
}
320+
321+
bool is_index_dir_fresh(const std::filesystem::path& indexDir, std::int64_t ttlSeconds) {
322+
std::error_code ec;
323+
if (!std::filesystem::exists(index_pkgs_dir(indexDir))) return false;
324+
325+
auto marker = index_refresh_marker(indexDir);
326+
if (!std::filesystem::exists(marker)) return false;
327+
328+
auto newest = std::filesystem::last_write_time(marker, ec);
329+
if (ec) return false;
330+
331+
auto now = std::filesystem::file_time_type::clock::now();
332+
auto age = std::chrono::duration_cast<std::chrono::seconds>(now - newest);
333+
return age.count() < ttlSeconds;
334+
}
335+
305336
void write_file(const std::filesystem::path& p, std::string_view content) {
306337
std::error_code ec;
307338
std::filesystem::create_directories(p.parent_path(), ec);
@@ -978,20 +1009,11 @@ void ensure_ninja(const Env& env, bool quiet,
9781009
// ─── Index freshness ────────────────────────────────────────────────
9791010

9801011
bool is_index_fresh(const Env& env, std::int64_t ttlSeconds) {
981-
std::error_code ec;
982-
auto pkgsDir = default_index_pkgs_dir(env);
983-
if (!std::filesystem::exists(pkgsDir)) return false;
984-
985-
auto marker = default_index_refresh_marker(env);
986-
if (!std::filesystem::exists(marker)) return false;
987-
988-
auto newest = std::filesystem::last_write_time(marker, ec);
989-
if (ec) return false;
1012+
return is_index_dir_fresh(default_index_dir(env), ttlSeconds);
1013+
}
9901014

991-
// Check TTL
992-
auto now = std::filesystem::file_time_type::clock::now();
993-
auto age = std::chrono::duration_cast<std::chrono::seconds>(now - newest);
994-
return age.count() < ttlSeconds;
1015+
bool is_official_index_fresh(const Env& env, std::int64_t ttlSeconds) {
1016+
return is_index_dir_fresh(official_index_dir(env), ttlSeconds);
9951017
}
9961018

9971019
int update_index(const Env& env, bool quiet) {
@@ -1000,7 +1022,7 @@ int update_index(const Env& env, bool quiet) {
10001022
[quiet](std::string_view line) {
10011023
if (!quiet) std::println("{}", line);
10021024
});
1003-
if (rc == 0) mark_default_index_refreshed(env);
1025+
if (rc == 0) mark_known_indexes_refreshed(env);
10041026
return rc;
10051027
}
10061028

@@ -1011,4 +1033,11 @@ void ensure_index_fresh(const Env& env, std::int64_t ttlSeconds, bool quiet) {
10111033
update_index(env, /*quiet=*/true);
10121034
}
10131035

1036+
void ensure_official_index_fresh(const Env& env, std::int64_t ttlSeconds, bool quiet) {
1037+
if (is_official_index_fresh(env, ttlSeconds)) return;
1038+
if (!quiet)
1039+
print_status("Updating", "package index (auto-refresh)");
1040+
update_index(env, /*quiet=*/true);
1041+
}
1042+
10141043
} // namespace mcpp::xlings

tests/unit/test_xlings.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,27 @@ TEST(XlingsIndexFreshness, RejectsStaleRefreshMarker) {
6262

6363
std::filesystem::remove_all(home);
6464
}
65+
66+
TEST(XlingsIndexFreshness, RequiresOfficialXimIndexEvenWhenDefaultIndexIsFresh) {
67+
auto home = make_tempdir("mcpp-xlings-index-freshness");
68+
std::filesystem::create_directories(home / "data" / "mcpplibs" / "pkgs");
69+
std::ofstream(home / "data" / "mcpplibs" / ".mcpp-index-updated") << "ok\n";
70+
71+
mcpp::xlings::Env env{.home = home};
72+
73+
EXPECT_FALSE(mcpp::xlings::is_official_index_fresh(env, 3600));
74+
75+
std::filesystem::remove_all(home);
76+
}
77+
78+
TEST(XlingsIndexFreshness, AcceptsFreshOfficialXimIndex) {
79+
auto home = make_tempdir("mcpp-xlings-index-freshness");
80+
std::filesystem::create_directories(home / "data" / "xim-pkgindex" / "pkgs");
81+
std::ofstream(home / "data" / "xim-pkgindex" / ".mcpp-index-updated") << "ok\n";
82+
83+
mcpp::xlings::Env env{.home = home};
84+
85+
EXPECT_TRUE(mcpp::xlings::is_official_index_fresh(env, 3600));
86+
87+
std::filesystem::remove_all(home);
88+
}

0 commit comments

Comments
 (0)