Skip to content

Commit 78e785d

Browse files
committed
fix(config): don't inject official global xim index into project scope
Project-local mode (a project with a custom `[indices]`, e.g. a local path index) force-added the official `xim` index into the project's `.xlings.json` `index_repos` (ensure_project_index_dir), intending to make `xim:*` deps resolvable in project mode. But xlings scopes by which group a repo lands in: packages from project `index_repos` are `PackageScope::Project`. So every `xim` GLOBAL tool (cmake/glibc/gcc/make/binutils/…) resolved project-scoped and installed into the project store instead of the shared registry. That broke ELF loader resolution for build-dep tools — a build-dep `xim:cmake`'s interpreter was patched to an unmaterialized project-store glibc → `cannot execute`. Any compat package running a glibc-dynamic build-dep tool in install() (e.g. a source-built CMake OpenCV) failed under `mcpp test`, universally (fresh MCPP_HOME too). global IS the default: `xim` (and its dynamically-discovered sub-indexes) are xlings global-default indices. `xim:*` resolves at global scope via the global index_repos + the registry-local xim clone, and stays visible to the project via additive. Only user-declared local custom indices are project-scoped. Remove the xim injection and the project-data-dir xim exposure; not a hardcoded allowlist — just don't inject an index the user didn't declare. Verified: opencv workspace member closes (cmake now installs to the global registry, project store holds only the local package); mcpp self-test 30/30; mcpp-index full workspace green. Design + analysis: .agents/docs/2026-07-09-project-index-scope-global-infra-fix.md Bumps 0.0.86 → 0.0.87. Claude-Session: https://claude.ai/code/session_01Y3KE7MAE8yV7BG35eXy7FV
1 parent 5764802 commit 78e785d

6 files changed

Lines changed: 156 additions & 38 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# 项目本地模式下的索引作用域:别把默认全局的官方索引(xim)注入项目组(架构分析 + 修复设计)
2+
3+
**日期**: 2026-07-09
4+
**范围**: `src/config.cppm`(`ensure_project_index_dir`)、`src/pm/index_spec.cppm``src/build/prepare.cppm`
5+
**性质**: mcpp 侧架构缺陷分析与修复设计。这是 OpenCV 收录时 `mcpp test` 沙箱里 `cmake` 不可执行故障的**唯一正解所在**
6+
## 文档族与职责边界(三份配套,全部根治设计、无 workaround)
7+
8+
| # | 仓库 / 文档 | 角色 | 与本故障的关系 |
9+
|---|---|---|---|
10+
|| mcpp-index `2026-07-09-mcpp-builddep-loader-store-split-rootcause.md` | 根因总报告(现象/实证/归责) | 先读,了解全貌 |
11+
|| **本篇** mcpp `2026-07-09-project-index-scope-global-infra-fix.md` | **本故障的唯一必需修复(根治)** | 修 ② 即可让 OpenCV 闭环 |
12+
|| openxlings/xlings `2026-07-09-scope-consistency-installed-check-and-loader-resolution.md` | xlings 侧**独立**健壮性缺口(根治) | 不由本故障触发、与 ② 互不依赖 |
13+
14+
**职责边界**:OpenCV/cmake 故障的**必需且唯一**修复是 ②(本篇)。③ 是一个**独立**的 additive loader 解析缺口(合法"项目消费者+全局 provider"场景),**不是本故障的必要条件**;两者可各自独立落地。**三份均采用根治设计,不提供也不采用任何 workaround。**
15+
16+
---
17+
18+
## 0. 结论(TL;DR)
19+
20+
- **缺陷**:mcpp 在项目本地模式下,把**官方全局基础设施索引 `xim`** 强注册为**项目作用域** index repo(`config.cppm:684-697`)。因 xlings 把"项目 index_repos 里的包"一律判 `PackageScope::Project`,导致 `xim`**全局工具**(cmake/glibc/gcc/make/binutils)被整体错误地"项目化"、装进项目 store —— 而它们本应装进全局 registry。由此引出 loader 不落项目 store、cmake interpreter 悬空、不可执行的连锁。
21+
- ****:**xlings 的作用域默认就是全局** —— 一个 repo 只有落进"项目组"(项目 `.xlings.json``index_repos` / 项目 discovered sub-index)才是 Project,其余(全局 repos、全局发现的 sub-index)恒 Global(`catalog.cppm:175-224`)。`xim` 及其 sub-index 本在全局组 → 本应 Global。**是 mcpp 主动把 `xim` 塞进了项目组**(`config.cppm:684-697` 把官方 xim append 进项目 `customRepos`),把一个默认全局的索引挪成了项目作用域。
22+
- **多余**:xlings 原生就在全局解析 `xim`(全局 index_repos + registry 本地 clone `registry/data/xim-pkgindex/pkgs`),并经 additive 让项目可见。`xim:*` 本会 Global 解析 → 装 registry。mcpp 的注入不必要。
23+
- **修复原则(不硬编码)**:**global 是默认;mcpp 只把用户在 `mcpp.toml [indices]` 声明的项目本地索引放进项目组,绝不注入用户未声明的官方 `xim`** 删掉这次注入即根治;xim 及其 sub-index 留在全局 → registry;cmake/glibc/gcc/make 同店 → interpreter 天然指 registry glibc → 可执行。**xlings 无需改动。** 不建 `is_global_infra` 白名单(违背"global 默认"、且漏 sub-index)。
24+
25+
---
26+
27+
## 1. mcpp 项目本地模式的架构(先厘清哪些是对的)
28+
29+
- **触发**:工程 `mcpp.toml` 声明自定义 `[indices]`(如本地 path 索引)→ 进入项目本地模式(`make_project_xlings_env`,`config.cppm:100-105`;`prepare.cppm:1188` `useProjectEnv = idxSpec && !idxSpec->is_builtin()`)。
30+
- **目的(正当)**:让**用户自定义索引**的包**项目作用域隔离**(不污染全局、可复现)。这本身没问题。
31+
- **工具链走全局(正确)**:默认工具链 `gcc@16.1.0`(含 glibc)经 `make_xlings_env`(无 projectDir)装进 **registry**(`prepare.cppm:805-810`),global 作用域、共享。**对。**
32+
- **一次调用挂双 store**:项目模式命令前缀同时设 `XLINGS_HOME=registry` + `XLINGS_PROJECT_DIR=<proj>/.mcpp`(`xlings.cppm:730-738`)。这本身也 OK —— xlings 支持 additive(project 叠加 global)。
33+
34+
→ 问题**不在**"用了项目本地模式",而在**把哪些索引归入了项目作用域**
35+
36+
## 2. 缺陷:把全局基础设施索引 `xim` 归进了项目作用域
37+
38+
`ensure_project_index_dir`(`config.cppm:662-767`)做了两件事:
39+
40+
1. **`config.cppm:684-697`(致命)**:把官方 `xim` 索引(`officialIndex = cfg.xlingsHome()/data/xim-pkgindex`)以名字 `xim` **append 进项目 `customRepos`**,随后 `seed_xlings_json`(`:707`)写进项目 `.mcpp/.xlings.json``index_repos`
41+
- 后果:xlings `catalog``project_index_repos()` 里的包判 `PackageScope::Project``storeRoot=project_data_dir/xpkgs`。→ **凡从 `xim` 解析到的包(cmake/glibc/gcc/make/binutils)全部项目化、装项目 store。**
42+
2. **`config.cppm:745-765`(冗余)**:把 `xim` 索引 symlink/copy 进项目 data dir(`<proj>/.mcpp/.xlings/data/xim-pkgindex`)。意图注释 `:745-748`:"让 `xim:python@latest` 之类能解析,免于回退到 remote 索引更新"。
43+
44+
**根(违背"global 默认")**:xlings 的作用域默认全局——只有落进"项目组"才是 Project(`catalog.cppm:175-224`)。`config.cppm:684-697` 这段是**无条件**把官方 `xim` 注入项目 `customRepos`(不经 `is_builtin` 判定、也不是用户声明),等于**主动把一个默认全局的索引搬进项目组**`xim` 本是 xlings 全局默认索引(`config.cppm:556-561`),连同其动态 sub-index 都应留在全局——不该被注入项目。
45+
46+
**证据(实测)**:项目 `.mcpp/.xlings.json``index_repos = [compat(本地,对), xim(官方全局,被项目化)]`;项目 store 里出现 `xim-x-cmake/gcc/glibc/make/binutils`,而 fresh registry **无 cmake**(即 cmake 只被装进了项目)。
47+
48+
## 3. 为什么"强加"既多余又有害
49+
50+
- **多余**:xlings 原生就把 `xim` auto-add 进 **globalIndexRepos_**(`config.cppm:556-561`),catalog 同时扫 project+global 两套 repo,项目模式 additive over global。registry 里已有 `xim-pkgindex` 本地 clone。→ **`xim:cmake` 本会 Global 解析 → 装 registry → 对项目可见,无需 mcpp 再把 xim 加进项目 repos。**
51+
- **有害**:一旦项目化,全局工具装进项目 store;其 loader dep glibc 又被工具链 bootstrap 装在 registry 并记录,项目安装时被"已装"复用跳过 → 项目 store 的 glibc 空 → elfpatch 把 cmake interpreter 指向空目录 → 悬空。**故障链的起点就是这次错误的作用域归属。**
52+
53+
## 4. 修复设计
54+
55+
### 设计原则:**global 是默认,项目化是显式且仅限用户声明**
56+
xlings 的作用域已由"repo 落在哪一组"决定(`catalog.cppm:175-224`):`project_index_repos()` → Project;`global_index_repos()` / discovered 全局 sub-index(`xim-indexrepos.json`)→ Global;discovered 项目 sub-index → Project。**即"不落进项目组的一切,默认全局"。** `xim` 及其 sub-index 本在全局组/全局发现 → 天然 Global。
57+
58+
因此**不建任何硬编码"全局基础设施"白名单**(既违背"global 默认",又漏掉动态发现的 sub-index)。正确原则只有一句:
59+
60+
> **mcpp 只把"用户在 `mcpp.toml [indices]` 里声明的项目本地索引"放进项目组;绝不注入用户未声明的索引(尤其官方 `xim`)。**
61+
62+
### 根治方案(单一设计;不含 workaround)
63+
1. **删除 `config.cppm:684-697`**(`ensure_project_index_dir` 里把官方 `xim` append 进项目 `customRepos` 的整段)。→ mcpp 不再把 xim 从全局组搬进项目组;xim 及其 sub-index 留在全局 → Global → registry。
64+
2. **收敛 `config.cppm:745-765`**(项目 data dir 的 xim 副本暴露)——它是"项目化 xim"的配套产物,删注入后不再需要。
65+
3. **保留 `:672` 遍历中"用户声明的本地自定义索引 → 项目组"的逻辑**(这是项目本地隔离的正当用途,不动)。
66+
4. **全局侧保证 `xim:*` 本地可解析(回应原动机、免 remote)**:确保全局 `xim` 条目/发现指向 **registry 的本地 clone**(`registry/data/xim-pkgindex`),使 `xim:*` 以 Global 解析、装 registry、不触发 remote fetch。原 `:745-748` 的"避免 remote 索引更新"顾虑,在全局侧被正确满足。
67+
68+
落地后:`xim` 的全部工具(cmake/glibc/gcc/make/binutils)及其 sub-index → Global → registry(同一全局 store);项目模式经 xlings additive 天然可见并复用;工具链(§1 已正确 global)与它们同店 → interpreter 天然自洽。**完整根治,不依赖任何 xlings 改动,且对 sub-index 机制天然正确(mcpp 一概不碰全局发现的 sub-index)。**
69+
70+
### ❌ 不要做(明确排除的 workaround)
71+
- **不建 `is_global_infra` 硬编码白名单**:硬编码 `{mcpplibs,xim,awesome,scode,d2x}` 违背"global 默认",且漏掉 xim 动态 sub-index —— 正解是"别注入",而非"枚举谁是全局"。
72+
- **不做"删注入但仍以其它方式把 xim 拉进项目"**的变体。
73+
- **不在 xlings 侧迁就**:不"把 glibc 物化进项目 store",不"改 elfpatch 指项目空目录"——那是坐实错误作用域(见文档 ③ 反模式)。**正解在本仓:别把默认全局的索引搬进项目组。**
74+
75+
## 5. 与 xlings 的关系(职责边界)
76+
- 修好本缺陷后:cmake/glibc/gcc/make 全 Global → registry(同一全局 store)→ cmake interpreter 天然指 registry glibc(存在)→ 可执行。**xlings 的 additive 模型本就支持此,无需任何改动。**
77+
- xlings 侧另有一个**相互独立**的健壮性缺口(合法"项目消费者 + 全局 loader-provider"时,elfpatch 的 provider 解析口径),记录在 xlings 仓文档里,**不是本故障的必要条件、也不由本修复触发**
78+
79+
## 6. 验证与最小复现
80+
- **修前(现状)**:含自定义 `[indices]` 的工程 + compat 包声明 build-dep `xim:cmake` 并在 install() 执行它 → 任意机器(含 fresh `MCPP_HOME`)`mcpp test``cmake: cannot execute`(项目 store 的 xim-x-glibc 只有 `.xpkg.lua`)。现成:`mcpp-index``tests/examples/opencv` + `pkgs/c/compat.opencv.lua`
81+
- **修后预期**:项目 `.xlings.json``index_repos` 只含本地 `compat`;`xim-x-cmake/glibc/gcc/make` 出现在 **registry** 而非项目 store;`cmake` 可执行;`compat.opencv``mcpp test --workspace` 三平台闭环(descriptor 与测试工程零改动)。
82+
- **回归点**:确认移除项目 xim 后,`xim:*` 依赖仍能解析(靠 global auto-add + registry 本地 clone),且不触发 remote 索引 fetch。
83+
84+
## 7. 影响
85+
- **compat.opencv**:本修复是其闭环的前置;修好后 descriptor(已是零兼容代码的 `pkginfo.build_dep` 形式)与 `tests/examples/opencv` **零改动**即通过 `mcpp test --workspace`,可进 CI 选跑、开 PR。
86+
- **面向未来**:确立"**global 默认、只项目化用户声明的本地索引、绝不注入官方索引**"这一不变式,避免后续任何"全局工具被项目化"的复发;对 xim 动态 sub-index 天然正确(mcpp 不碰全局发现)。
87+
88+
## 8. 改动点清单(单一根治设计,不硬编码)
89+
- `src/config.cppm:684-697` —— **删除**把官方 `xim` append 进项目 `customRepos` 的整段(不再把默认全局的索引注入项目组)。
90+
- `src/config.cppm:745-765` —— 收敛项目 data dir 的 xim 副本暴露(注入删除后不再需要);由全局 `xim` 指 registry 本地 clone 满足解析(免 remote fetch)。
91+
- `src/config.cppm:662-682`(`ensure_project_index_dir` 遍历)—— **保持不变**:仅项目化用户在 `[indices]` 声明的本地自定义索引(项目隔离的正当用途)。
92+
- **不改** `is_builtin()`,**不新增** `is_global_infra` 白名单(违背"global 默认"、漏 sub-index)。
93+
- e2e/工作区:`mcpp test --workspace` 覆盖"含全局 build-dep 工具的 compat 包"(opencv 即样例)+ 断言 xim 工具落在 registry 而非项目 store,防回归。

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.87] — 2026-07-09
7+
8+
### 修复
9+
10+
- **项目本地模式:不再把官方全局索引 `xim` 注入项目作用域**。此前带自定义
11+
`[indices]`(本地 path 索引)的工程进入项目本地模式时,`ensure_project_index_dir`
12+
会无条件把官方 `xim` 索引 append 进项目 `.xlings.json``index_repos`
13+
(`config.cppm`),意在让 `xim:*` 依赖在项目模式可解析。但 xlings 按"repo 落在
14+
哪一组"决定作用域——项目 `index_repos` 里的包一律 `PackageScope::Project`,于是
15+
`xim`**全局工具**(cmake/glibc/gcc/make/binutils 等)整体被错误地"项目化"、
16+
装进项目 store 而非共享 registry。由此 build-dep 工具(如 `xim:cmake`)的 ELF
17+
interpreter 被指向项目 store 里未物化的 glibc → `cannot execute`,任何在
18+
`install()` 里执行 glibc-动态 build-dep 工具的 compat 包(如从源码 CMake 构建的
19+
OpenCV)在 `mcpp test` 下必现,且与宿主历史无关(fresh `MCPP_HOME` 亦复现)。
20+
**修复**:移除该注入及配套的项目 data dir `xim` 副本暴露。`xim`(及其动态发现的
21+
sub-index)是 xlings 全局默认索引,**global 即默认作用域**——`xim:*` 经全局
22+
index_repos + registry 本地 clone 正常解析、装 registry,并经 additive 对项目
23+
可见;只有用户在 `[indices]` 声明的本地自定义索引才项目化。设计与分析见
24+
`.agents/docs/2026-07-09-project-index-scope-global-infra-fix.md`
25+
626
## [0.0.84] — 2026-07-08
727

828
### 修复

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.86"
3+
version = "0.0.87"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/config.cppm

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -681,20 +681,18 @@ bool ensure_project_index_dir(
681681
customRepos.emplace_back(name, spec.url);
682682
}
683683

684-
auto officialIndex = cfg.xlingsHome() / "data" / "xim-pkgindex";
684+
// NOTE: the official global `xim` index is deliberately NOT injected into
685+
// the project's customRepos here. `xim` (and its dynamically-discovered
686+
// sub-indexes) are xlings GLOBAL-default indices — global IS the default
687+
// scope, and a repo is only project-scoped when it lands in the project's
688+
// index_repos. Injecting `xim` as a project repo made every `xim:*` tool
689+
// (cmake/glibc/gcc/make/…) resolve project-scoped and install into the
690+
// project store instead of the shared registry, which broke ELF loader
691+
// resolution for build-dep tools (cmake interpreter dangling). `xim:*`
692+
// resolves fine at global scope via xlings' global index_repos + the
693+
// registry-local clone, and stays visible to the project via additive.
694+
// See .agents/docs/2026-07-09-project-index-scope-global-infra-fix.md.
685695
std::error_code ec;
686-
if (!customRepos.empty() && std::filesystem::exists(officialIndex / "pkgs", ec)) {
687-
bool hasXim = false;
688-
for (auto const& [name, _] : customRepos) {
689-
if (name == "xim") {
690-
hasXim = true;
691-
break;
692-
}
693-
}
694-
if (!hasXim) {
695-
customRepos.emplace_back("xim", officialIndex.generic_string());
696-
}
697-
}
698696

699697
if (customRepos.empty() && penv.empty()) return false; // nothing to do
700698

@@ -742,27 +740,11 @@ bool ensure_project_index_dir(
742740
exposeLocalIndex(name, source, dotMcpp / "data");
743741
}
744742

745-
// Project-scoped xlings installs custom-index packages in an additive
746-
// project data dir. Expose the global official xim index there too, so
747-
// package deps like `xim:python@latest` can resolve without falling back
748-
// to unrelated remote index updates or system tools.
749-
if (std::filesystem::exists(officialIndex / "pkgs", ec)) {
750-
auto projectData = dotMcpp / ".xlings" / "data";
751-
auto projectOfficial = projectData / "xim-pkgindex";
752-
std::filesystem::create_directories(projectData, ec);
753-
if (!std::filesystem::exists(projectOfficial, ec)) {
754-
std::filesystem::create_directory_symlink(officialIndex, projectOfficial, ec);
755-
if (ec) {
756-
ec.clear();
757-
std::filesystem::copy(
758-
officialIndex,
759-
projectOfficial,
760-
std::filesystem::copy_options::recursive
761-
| std::filesystem::copy_options::skip_existing,
762-
ec);
763-
}
764-
}
765-
}
743+
// (removed) The official global `xim` index is no longer exposed/copied into
744+
// the project data dir: it is not a project-scoped repo (see the note above),
745+
// so `xim:*` resolves at GLOBAL scope via the registry-local xim clone, not
746+
// from a project-local copy. Only genuinely user-declared local custom
747+
// indices are exposed project-locally (the loop above).
766748
return true;
767749
}
768750

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.86";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.87";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

tests/unit/test_config.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#include <gtest/gtest.h>
2+
#include <fstream>
3+
#include <sstream>
4+
#include <string>
25

36
import std;
47
import mcpp.config;
@@ -85,7 +88,14 @@ TEST(Config, ProjectIndexJsonEscapesLocalIndexPath) {
8588
std::filesystem::remove_all(project);
8689
}
8790

88-
TEST(Config, ProjectIndexDirExposesOfficialXimIndex) {
91+
// The official global `xim` index must NOT be injected/exposed into the project
92+
// scope. `xim` (and its dynamic sub-indexes) are xlings GLOBAL-default indices —
93+
// global IS the default scope; injecting xim as a project repo made every `xim:*`
94+
// tool (cmake/glibc/gcc/make) resolve project-scoped and install into the project
95+
// store instead of the shared registry, breaking ELF loader resolution for
96+
// build-dep tools. `xim:*` resolves at global scope via the registry-local clone.
97+
// See .agents/docs/2026-07-09-project-index-scope-global-infra-fix.md.
98+
TEST(Config, ProjectIndexDirDoesNotInjectOfficialXimIndex) {
8999
auto project = make_tempdir("mcpp-config-project-xim-index");
90100
auto registry = make_tempdir("mcpp-config-registry");
91101
auto official = registry / "data" / "xim-pkgindex";
@@ -109,9 +119,22 @@ TEST(Config, ProjectIndexDirExposesOfficialXimIndex) {
109119
cfg.registryDir = registry;
110120
ASSERT_TRUE(mcpp::config::ensure_project_index_dir(cfg, project, indices));
111121

122+
// xim is NOT copied/exposed into the project data dir (it stays global).
112123
auto projectOfficial =
113124
project / ".mcpp" / ".xlings" / "data" / "xim-pkgindex";
114-
EXPECT_TRUE(std::filesystem::exists(projectOfficial / "pkgs" / "p" / "python.lua"));
125+
EXPECT_FALSE(std::filesystem::exists(projectOfficial / "pkgs" / "p" / "python.lua"));
126+
127+
// The user-declared local `compat` index IS exposed project-locally.
128+
auto projectLocal = project / ".mcpp" / ".xlings" / "data" / "compat";
129+
EXPECT_TRUE(std::filesystem::exists(projectLocal / "pkgs"));
130+
131+
// The seeded project .xlings.json index_repos must not contain `xim`.
132+
auto projJson = project / ".mcpp" / ".xlings.json";
133+
if (std::filesystem::exists(projJson)) {
134+
std::ifstream is(projJson);
135+
std::stringstream ss; ss << is.rdbuf();
136+
EXPECT_EQ(ss.str().find("\"xim\""), std::string::npos);
137+
}
115138

116139
std::filesystem::remove_all(project);
117140
std::filesystem::remove_all(registry);

0 commit comments

Comments
 (0)