Skip to content

Commit a278857

Browse files
committed
feat: namespace-aware install_path + design doc
Extends PR #23 with: 1. Design doc (.agents/docs/2026-05-11-namespace-field-design.md) covering the full namespace scheme: mcpplibs / mcpplibs.capi / compat, with xpkgs directory naming, migration path, and deprecation timeline. 2. package_fetcher.cppm: install_path() now supports namespace-aware directory lookup. Priority order: a. New: <namespace>-x-<shortName> (e.g. "mcpplibs-x-cmdline") b. Old: <defaultIndex>-x-<fullName> (e.g. "mcpp-index-x-mcpplibs.cmdline") c. Old short: <defaultIndex>-x-<name> (e.g. "mcpp-index-x-gtest") d. Fallback scan: any directory matching *-x-<name> This ensures both new installs (namespace-aware xpkgs paths) and pre-0.0.6 cached installs (index-prefixed paths) resolve correctly, enabling a smooth migration with no forced cache invalidation.
1 parent ad24f61 commit a278857

2 files changed

Lines changed: 199 additions & 14 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Namespace Field Design — mcpp 0.0.6
2+
3+
> 方案设计文档,指导 namespace 字段的实现和生态迁移。
4+
5+
## 1. 动机
6+
7+
mcpp 生态向 C++23 模块化方向发展。包索引中存在两类库:
8+
9+
- **模块化库**(mcpplibs 生态):原生 `export module`,用 `import` 消费
10+
- **非模块化库**(compat):传统 C/C++ 库,通过 Form B 描述文件 + `#include` 消费
11+
12+
需要在 namespace 层面区分这两类,让用户一眼看出某个依赖是否是模块化的,
13+
同时为未来"非模块化库迁移到模块化"提供清晰的升级路径。
14+
15+
## 2. 命名空间划分
16+
17+
| namespace | 含义 | 示例包 |
18+
|---|---|---|
19+
| `mcpplibs` | mcpplibs 生态的模块化 C++23 库 | cmdline, tinyhttps, llmapi, xpkg, templates |
20+
| `mcpplibs.capi` | mcpplibs 的 C API 模块化封装子集 | lua (封装 Lua C API 为 C++23 module) |
21+
| `compat` | 非模块化的第三方 C/C++ 库(兼容性支持,不鼓励直接使用) | gtest, mbedtls, lua(上游 C 库), ftxui |
22+
23+
### 2.1 默认 namespace
24+
25+
由于 xlings 的 `defaultNamespace = repo.name`(硬编码为索引仓库名 `"mcpp-index"`),
26+
我们采用**每个包显式指定 namespace** 的方案,不依赖默认值。
27+
28+
### 2.2 用户 mcpp.toml 写法
29+
30+
```toml
31+
# 模块化库
32+
[dependencies.mcpplibs]
33+
cmdline = "0.0.2"
34+
tinyhttps = "0.2.2"
35+
llmapi = "0.2.5"
36+
37+
# C API 封装
38+
[dependencies.mcpplibs.capi]
39+
lua = "0.0.3"
40+
41+
# 非模块化兼容库
42+
[dependencies.compat]
43+
gtest = "1.15.2"
44+
mbedtls = "3.6.1"
45+
ftxui = "6.1.9"
46+
lua = "5.4.7" # 上游 C 库(和 mcpplibs.capi.lua 是不同的包)
47+
```
48+
49+
### 2.3 迁移路径
50+
51+
当某个 compat 库完成模块化封装后:
52+
1. 在 mcpplibs 或 mcpplibs.capi 下发布新包
53+
2. compat 版本标记 deprecated(保留一段时间)
54+
3. 用户改一行依赖声明即可迁移
55+
56+
## 3. 索引文件布局
57+
58+
### 3.1 描述文件命名
59+
60+
文件名使用 `<namespace>.<name>.lua` 格式:
61+
62+
```
63+
pkgs/
64+
c/compat.gtest.lua namespace="compat", name="gtest"
65+
c/compat.mbedtls.lua namespace="compat", name="mbedtls"
66+
c/compat.lua.lua namespace="compat", name="lua"
67+
c/compat.ftxui.lua namespace="compat", name="ftxui"
68+
m/mcpplibs.cmdline.lua namespace="mcpplibs", name="cmdline"
69+
m/mcpplibs.tinyhttps.lua namespace="mcpplibs", name="tinyhttps"
70+
m/mcpplibs.llmapi.lua namespace="mcpplibs", name="llmapi"
71+
m/mcpplibs.xpkg.lua namespace="mcpplibs", name="xpkg"
72+
m/mcpplibs.templates.lua namespace="mcpplibs", name="templates"
73+
m/mcpplibs.capi.lua.lua namespace="mcpplibs.capi", name="lua"
74+
```
75+
76+
### 3.2 描述文件格式
77+
78+
```lua
79+
package = {
80+
spec = "1",
81+
namespace = "compat", -- 显式 namespace(0.0.6+)
82+
name = "gtest", -- 短名
83+
...
84+
}
85+
```
86+
87+
### 3.3 xpkgs 安装目录
88+
89+
```
90+
<namespace>-x-<name>/<version>/
91+
92+
compat-x-gtest/1.15.2/
93+
compat-x-mbedtls/3.6.1/
94+
compat-x-lua/5.4.7/
95+
compat-x-ftxui/6.1.9/
96+
mcpplibs-x-cmdline/0.0.2/
97+
mcpplibs-x-tinyhttps/0.2.2/
98+
mcpplibs.capi-x-lua/0.0.3/
99+
```
100+
101+
## 4. mcpp 实现清单
102+
103+
### 4.1 src/pm/compat.cppm (已完成 PR #23)
104+
105+
- `resolve_package_name(name, ns)` — 显式 ns 优先 > 点号拆分 > 默认
106+
- `qualified_name(ns, short)` — 重建完整名
107+
- `xpkg_dir_name(index, ns, short)` — xpkgs 目录名
108+
109+
### 4.2 src/manifest.cppm (已完成 PR #23)
110+
111+
- `Package.namespace_` 字段
112+
- TOML `[package].namespace` 解析
113+
- `extract_xpkg_namespace()` — 从 xpkg lua 读 namespace
114+
115+
### 4.3 src/pm/package_fetcher.cppm (待更新)
116+
117+
`install_path()` 查找逻辑需要同时支持:
118+
- 新路径: `<namespace>-x-<name>`(如 `compat-x-gtest`)
119+
- 老路径: `<defaultIndex>-x-<qualifiedName>`(如 `mcpp-index-x-gtest`)
120+
121+
### 4.4 src/cli.cppm (已完成 PR #23)
122+
123+
- dep 名称匹配走 compat 模块
124+
- lua namespace 传播到 manifest
125+
126+
## 5. 向后兼容
127+
128+
### 5.1 compat.cppm 的三条规则
129+
130+
1.`namespace` 字段 → 直接用(新路径)
131+
2. `name` 带点号 → 按首个点拆分(老路径,deprecated in 1.0.0)
132+
3. 纯短名 → 走 `install_path` 的 fallback 扫描
133+
134+
### 5.2 install_path 双路查找
135+
136+
```
137+
查 <xpkgs>/<namespace>-x-<name>/<version>/ ← 新路径
138+
查 <xpkgs>/<defaultIndex>-x-<qualifiedName>/<version>/ ← 老路径 fallback
139+
```
140+
141+
先找到哪个用哪个。新安装的包走新路径,老缓存继续能用。
142+
143+
## 6. 弃用时间线
144+
145+
| 版本 | 变化 |
146+
|---|---|
147+
| 0.0.6 | namespace 字段支持 + 双路 install_path |
148+
| 0.1.0 | mcpp-index 全面迁移到显式 namespace |
149+
| 1.0.0 | 移除 name 嵌点的 compat 拆分逻辑 |

src/pm/package_fetcher.cppm

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -744,35 +744,71 @@ Fetcher::resolve_xpkg_path(std::string_view target,
744744
std::optional<std::filesystem::path>
745745
Fetcher::install_path(std::string_view name, std::string_view version) const
746746
{
747-
// M6.x: install_path now ALWAYS returns the verdir (untouched extract
748-
// root). Layout discrimination is done by the caller via the xpkg.lua's
747+
// M6.x: install_path returns the verdir (untouched extract root).
748+
// Layout discrimination is done by the caller via the xpkg.lua's
749749
// `mcpp = "<glob path>"` (Form A pointer) or `mcpp = { ... }` (Form B
750750
// inline) field — no more "find mcpp.toml subdir / unique subdir" magic.
751-
// Caller resolves further nested paths via glob.
751+
//
752+
// 0.0.6+: namespace-aware lookup. xlings stores packages at
753+
// <xpkgs>/<namespace>-x-<shortName>/<version>/
754+
// The `name` argument may be either a qualified name
755+
// ("mcpplibs.cmdline") or a short name ("cmdline"). We try
756+
// multiple candidate prefixes to handle both old and new layouts.
752757
auto base = cfg_.xlingsHome() / "data" / "xpkgs";
753758
if (!std::filesystem::exists(base)) return std::nullopt;
754759

755-
auto try_namespaced = [&](std::string_view prefix) -> std::optional<std::filesystem::path> {
756-
auto verdir = base
757-
/ std::format("{}-x-{}", prefix, name)
758-
/ std::string(version);
760+
auto try_dir = [&](std::string_view dirName) -> std::optional<std::filesystem::path> {
761+
auto verdir = base / std::string(dirName) / std::string(version);
759762
return std::filesystem::exists(verdir)
760763
? std::optional<std::filesystem::path>{verdir}
761764
: std::nullopt;
762765
};
763766

764-
// 1. Try the configured default index first (fast path).
765-
if (auto p = try_namespaced(cfg_.defaultIndex)) return *p;
767+
// Split qualified name into (namespace, shortName) for new-style lookup.
768+
// "mcpplibs.cmdline" → ("mcpplibs", "cmdline")
769+
// "gtest" → ("", "gtest")
770+
std::string ns, shortName;
771+
auto dot = name.find('.');
772+
if (dot != std::string_view::npos) {
773+
ns = std::string(name.substr(0, dot));
774+
shortName = std::string(name.substr(dot + 1));
775+
} else {
776+
shortName = std::string(name);
777+
}
778+
779+
// Priority order:
780+
// 1. New namespace-aware: <ns>-x-<shortName> (e.g. "mcpplibs-x-cmdline")
781+
// 2. Old index-prefixed: <defaultIndex>-x-<fullName> (e.g. "mcpp-index-x-mcpplibs.cmdline")
782+
// 3. Old index-prefixed short: <defaultIndex>-x-<name> (e.g. "mcpp-index-x-gtest")
783+
// 4. Fallback scan: any <*>-x-<name> or <*>-x-<shortName>
784+
785+
// 1. New namespace-aware path (0.0.6+)
786+
if (!ns.empty()) {
787+
if (auto p = try_dir(std::format("{}-x-{}", ns, shortName))) return *p;
788+
}
766789

767-
// 2. Fall back: scan every xpkgs/<index>-x-<name> for a match.
790+
// 2. Old index-prefixed path (compat with pre-0.0.6 installs)
791+
if (auto p = try_dir(std::format("{}-x-{}", cfg_.defaultIndex, name))) return *p;
792+
793+
// 3. Short name with default index (bare "gtest" → "mcpp-index-x-gtest")
794+
if (!shortName.empty() && shortName != name) {
795+
if (auto p = try_dir(std::format("{}-x-{}", cfg_.defaultIndex, shortName))) return *p;
796+
}
797+
798+
// 4. Fallback scan: walk xpkgs/ for any directory ending with -x-<name>
799+
// or -x-<shortName>.
768800
std::error_code ec;
769-
std::string suffix = std::format("-x-{}", name);
801+
std::string suffix1 = std::format("-x-{}", name);
802+
std::string suffix2 = shortName.empty() ? "" : std::format("-x-{}", shortName);
770803
for (auto& entry : std::filesystem::directory_iterator(base, ec)) {
771804
if (!entry.is_directory()) continue;
772805
auto dirname = entry.path().filename().string();
773-
if (!dirname.ends_with(suffix)) continue;
774-
auto idx = dirname.substr(0, dirname.size() - suffix.size());
775-
if (auto p = try_namespaced(idx)) return *p;
806+
if (dirname.ends_with(suffix1)) {
807+
if (auto p = try_dir(dirname)) return *p;
808+
}
809+
if (!suffix2.empty() && dirname.ends_with(suffix2)) {
810+
if (auto p = try_dir(dirname)) return *p;
811+
}
776812
}
777813
return std::nullopt;
778814
}

0 commit comments

Comments
 (0)