Skip to content

Latest commit

 

History

History
220 lines (144 loc) · 16 KB

File metadata and controls

220 lines (144 loc) · 16 KB

v0.0.99 批次设计 —— #243 feature 转发 + #238 xlings 升级 + #230 windows 复验

日期:2026-07-19 基线:mcpp 0.0.98(HEAD;MCPP_VERSION @ src/toolchain/fingerprint.cppm:21) 目标版本:0.0.99 覆盖 issue:#243(manifest [features] 的 dep/feat 转发)、#238(≥2 项目级 index_repo 时 xlings install_packages 静默失败)、#230([windows] mcpp test --workspace 退出 127)。 前序设计:2026-07-19-issue-243-feature-forwarding-design.md(#243 语义与用例分析)。本文 §1.3 校正其 §5 的实现路径:0.0.98 已通过 0cca9e0 落地 DependencyEdge + aggregatedRequest 单一漏斗,原设计假想的 requestedFeaturesByPkg 累加器已经存在,#243 只需注入这个既有漏斗,不再新建平行结构。 上游总账:2026-07-19-issues-230-243-batch-ledger-and-architecture-assessment.md。 所有 file:line 锚点均在 0.0.98 源码上核实。


0. 三个 issue 的现状核实(结论先行)

issue 类型 0.0.98 现状(核实) 0.0.99 需做
#243 enhancement 转发(dep/feat)完全不存在;条件依赖([feature-deps] / xpkg features.x.deps)已存在 新增转发:数据模型 featureForwards + 两文法解析 + 注入 dependencyEdges 漏斗
#238 bug 根因在 xlings(install_packages 多仓解析),mcpp 侧诊断已落地(00b66c5);xlings#374 已在上游修复并发布 0.4.67(cf9b60d56) 升级 vendored xlings 0.4.62 → 0.4.67,生态真实验证多仓安装
#230 bug(win) scanner symlink 逃逸 + 窄串转换抛异常→__fastfail→裸 127,代码已修(df985df/#231,在 HEAD)+ main.cpp 兜底 catch 复验 + 补 build.mcpp 产物 windows .exe 后缀(逃逸修复后 workspace build-mcpp 成员在 windows 才会被走到的次生面)

三者共版本 0.0.99 一次 PR/release,是 #230–#243 批次的收尾(参见 batch ledger §3 "剩余开口工作")。


1. #243 —— feature 依赖转发(Cargo dep/feat 平价)

1.1 语义与用例

opencv 模块包(源码直编形态)要提供 import opencv.dnn;:一个 feature dnn 既要拉入 compat.opencv(条件依赖,已支持),又要打开该依赖的 dnn feature(转发,新增),否则 dnn 的 +309 TU 要么对所有消费者永远全量拉、要么无法提供。

期望:opencv = { version="…", features=["dnn"] }(消费端)→ 模块包启用 dnn.cppm 源 + 依赖的 compat.opencv features=["dnn"] 参与构建。转发必须传递(链式转发继续)且在 mcpp buildmcpp test 双路径上都成立。

1.2 面向用户语法(Cargo 平价)

[features] 数组(及表格形 implies)里,/ 的 token = 转发 <depKey>/<depFeature>,不含 / = 本地 implied feature。与 Cargo [features] F = ["dep/feat", ...] 完全一致:

[features]
# dnn 激活时:本地拉 dnn.cppm 源集,并把 compat.opencv 的 dnn feature 打开
dnn = { sources = ["src/dnn.cppm"], implies = ["compat.opencv/dnn"] }
# 数组简写等价:dnn = ["compat.opencv/dnn"]
# 表格形亦可用专用键(自解释、等价):dnn = { forward = ["compat.opencv/dnn"] }

[dependencies]
compat.opencv = { version = "0.0.3", default-features = false }

depKey[dependencies] / [feature-deps] 同键空间(resolve_dependency_selector(...).stableMapKey,实为原始 selector 串;compat.opencv 保持 compat.opencv)。转发是加法:只增开依赖的 feature,不改依赖是否被拉入(那是 [feature-deps] 的职责),不受 default-features 开关影响(§1.5)。

不加 deps 内联:条件依赖的 canonical TOML 面仍是独立 [feature-deps.<name>] 段(需要完整 DependencySpec);转发只需两个字符串,内联进 [features] 是自然的。二者正交、可组合(§1.1 用例即二者同用)。

1.3 数据模型(唯一新增)

src/manifest/types.cppm(紧邻 featuresMap / featureDeps,同族)新增:

// #243: dep/feat 转发(Cargo 平价)。featureName → [(depStableKey, depFeature)]。
// 本包 feature F 激活时,把 depFeature 注入依赖 depStableKey 的请求集。
// depStableKey 与 dependencies / featureDeps 同键空间(stableMapKey == 原始 selector 串)。
std::map<std::string, std::vector<std::pair<std::string, std::string>>> featureForwards;

不新增 DependencySpec 字段、不新增 TOML 段。

1.4 共享切分 helper(两文法唯一切分点)

src/pm/dependency_selector.cppm(两文法均已 import 之)加一个纯 inline 函数:

// "compat.opencv/dnn" → {depKey:"compat.opencv", depFeature:"dnn"}。无 '/' 或空段 → nullopt(按本地 implied 处理)。
inline std::optional<std::pair<std::string,std::string>>
split_feature_forward_token(std::string_view token);

首个 / 切分(feature 名不含 /)。两解析器把 implies token 逐个过它:命中→ featureForwards,否则→ featuresMap(implies)。

  • TOML(toml.cppm:196-237):array 简写与表格 implies 收进 implied 后统一 partition;新增表格键 forward(值为 dep/feat token 数组)也走同一 helper。
  • xpkg(xpkg.cppm:1108-1195):feature 表 } 收尾后对 featuresMap[fname] 做同一 partition(与 TOML 对称)。deps 分支不变。

1.5 传播算法 —— 注入既有 aggregatedRequest 漏斗(核心)

0.0.98 的 prepare.cppm 已把 per-package 请求 feature 集收敛为唯一漏斗:

  • DependencyEdge{ consumerPackageIndex, dependencyPackageIndex, requestedFeatures, defaultFeatures }(:1849-1863),由 recordDependencyEdge(consumerDepIndex, depIdx, spec)spec.features 填充(:1948-1975)。
  • aggregatedRequest(depPkgIndex)(:2858-2871):对某 dep,union 所有入边的 requestedFeatures(菱形安全,Cargo 语义)。
  • 两个消费点都读它:激活 apply(packages[i], req, depDefaultFeatures)(:2872-2890)与 dep build.mcpp 的 bpEnv.features(:2910-2918)。
  • 解析期 mergeActiveFeatureDeps(pm, spec.features, spec.defaultFeatures)(:2646)用同一 spec.features 拉条件依赖。

转发的唯一注入点:在把某包 P 的子依赖 push 进 worklist 之前,按 P 的激活 feature 集,把 P 转发给该子依赖的 feature 追加进子依赖的 spec.features 一处注入同时覆盖两个消费点:

  1. 解析期:子依赖被处理时 mergeActiveFeatureDeps(*dep_manifest, spec.features, …) 用到的 spec.features 已含转发 feature → 该子依赖的 [feature-deps.<forwarded>] 也随之展开(转发能触发被转发包自身的条件依赖)。
  2. 激活期:recordDependencyEdge(…, spec) 把含转发 feature 的 spec.features 记到 P→D 边上 → aggregatedRequest(D) union 到它 → apply(D) 激活该 feature(发 -DMCPP_FEATURE_<F>featureDefinesfeatureSources drop/add)。apply() 内部一字不改。

注入 helper(prepare.cppm,近其它 feature lambda):

// P 的激活 feature 集 parentActive 决定哪些转发生效;把 P 转发给 childKey 的 feature
// 并入 childSpec.features(去重)。在 child push 进 worklist 前调用。
auto injectForwards = [](const Manifest& parent, const vector<string>& parentActive,
                         const string& childKey, DependencySpec& childSpec) {
    if (parent.featureForwards.empty()) return;
    for (auto& f : parentActive)
        if (auto it = parent.featureForwards.find(f); it != parent.featureForwards.end())
            for (auto& [depKey, depFeat] : it->second)
                if (depKey == childKey &&
                    find(childSpec.features.begin(), childSpec.features.end(), depFeat)
                        == childSpec.features.end())
                    childSpec.features.push_back(depFeat);
};

两处 push 点各加一次注入(push 的是增广后的 spec 拷贝,不改存储的 manifest):

  • root(:2172-2183):rootActive = feature_closure(*m, rootReq, true);对 m->dependencies 每个子依赖 injectForwards(*m, rootActive, n, reqCopy) 后 push。
  • dep(:2686-2689):depActive = feature_closure(*dep_manifests.back(), spec.features, spec.defaultFeatures)(与 :2646mergeActiveFeatureDeps 同闭包);对其子依赖同样注入后 push。

传递性:root→mid→leaf 的链,mid 处理时其 spec.features(来自 root 对 mid 的边,已含 root 的转发)决定 depActive,再把 mid 对 leaf 的转发注入 leaf 的边 → 沿 BFS 前向边天然递归。终止性:feature 集单调增且有限;feature_closureseen 挡 implies 环。

菱形局限(已知,与既有一致):若 D 有多个消费者且晚到的转发边在 D 已 resolve 之后才出现,晚到的转发不会重新触发 D 的条件依赖展开——这与 0.0.98 aggregatedRequest 注释所述"解析期按边、激活期 union"的同一历史边界一致(激活期仍会 union 到,apply 正确;只有"晚到转发再触发 D 的 [feature-deps]"这一步受限)。opencv.dnn 是 root→opencv→protobuf 的树链,不触及此边界。

1.6 与 #242(default-features = false)组合

转发是加法、default-features 是减法,二者在 feature_closure 入口并集、正交:

  • 转发注入到的是 spec.features(显式请求),不受 default-features 开关影响。
  • compat.opencv = { default-features = false } + 转发 compat.opencv/dnn → opencv 的 requested = {dnn},default seed 被跳过 → 只带 dnn(+其 implies),不带 opencv 默认 feature 集。正是"精简依赖"想要的。

1.7 校验(复用漏斗红利)

  • 转发未定义的 dep feature(opencv/nonesuch):转发 feature 经边进 aggregatedRequest,被既有"dependency does not declare requested feature"门(:2875-2883)校验 → strict 报错 / warn。无需新增校验路径。
  • 转发到未声明的 dep(depKey 不在 dependenciesfeatureDeps):新增轻量校验——在注入处,若某激活 feature 的转发 depKey 不在该包(已折入 feature-deps 的)dependencies 中,strict 报错 / 非 strict warn(与既有 feature 校验同模式)。feature 未激活则不校验(惰性)。

1.8 测试

单测(tests/unit/test_manifest.cpp,并列既有 FeatureDepsTomlSection / FeatureDepsAndImplies):

  • TEST(Manifest, FeatureForwardTomlParse):F = ["local", "compat.opencv/dnn"]featuresMap["F"]=={"local"}featureForwards["F"]=={{"compat.opencv","dnn"}}
  • TEST(Manifest, FeatureForwardTableForms):表格 implies 混入 + 专用 forward 键都分流到 featureForwards
  • TEST(SynthesizeFromXpkgLua, FeatureForwardParse):xpkg implies={"dep/x"}featureForwards

e2e(host-aware,tests/e2e/128_feature_forwarding.sh,并入 run_all.sh;模板同 126_default_features_opt_out.sh):

  • 夹具:leaf(feature extradefines=["LEAF_EXTRA=1"],不在 default);mid 依赖 leaf(default-features=false)且 [features] withextra = ["leaf/extra"];app 依赖 mid
    • app 请求 midwithextra → leaf 以 extra 编译,LEAF_EXTRA / MCPP_FEATURE_EXTRA 在 leaf 的 compile_commands.json 在场(转发经 root→mid→leaf 传递)。
    • app 不请求 withextra → leaf 无 extra,LEAF_EXTRA 在场。
    • 双路径:同断言在 mcpp test 复跑(锁 0.0.97 双路径不变量)。

2. #238 —— 升级 vendored xlings 0.4.62 → 0.4.67

2.1 根因归属(核实)

install_packages 在 ≥2 项目级 index_repos 时静默 exit 1,根因在 xlings(不在 mcpp)。已开 openxlings/xlings#374,该 issue 已 closed:上游修复 cf9b60d56 "fix(xim): surface multi-repo install failures + best-effort catalog (#374)",发布于 xlings 0.4.67(v0.4.66...v0.4.67 恰好一条该 commit)。mcpp 侧诊断(00b66c5,把裸 exit 1 变成可操作错误)已在 0.0.98。

2.2 改动

把 release/构建/e2e 三处 workflow 的 vendored xlings pin 从 0.4.62 升到 0.4.67:

  • .github/workflows/release.yml(多处 XLINGS_VERSION: '0.4.62'、aarch64 bundle URL xlings-0.4.62-linux-aarch64)。
  • .github/workflows/cross-build-test.yml(2 处)。
  • .github/workflows/ci-linux-e2e.yml(quick_install.sh v0.4.62)。

src/xlings.cppm:36 kXlingsVersion 是 xlings self-install 的最低 pin,与多仓解析无关,可一并同步到 0.4.67(保守:核对其消费点后再定;不影响 #238 修复,因修复来自 bundle 的 xlings 二进制行为)。

2.3 生态真实验证(§4)

release 后按发布闭环:mirror xlings-res → xim-pkgindex → 构造 ≥2 index_repo 的沙箱真装一个未缓存包(如 compat:compat.imgui),断言 exit 0(0.4.62 下为静默 exit 1);再 bump bootstrap pin。


3. #230 —— windows workspace 退出 127

3.1 根因与既有修复(核实)

mcpp test --workspace 在 windows 于成员 glob 扫描期,recursive_directory_iterator(follow_directory_symlink)(#220 d440a0b 引入)顺 #224 建的 .mcpp/.xlings/data/<index> symlink 逃逸进 vendored xim-pkgindex,其中一个 CJK 文件名触发 MSVC lexically_relative().generic_string() 宽→窄转换抛 std::system_error;0.0.95 未捕获 → std::terminate__fastfail(0xC0000409) → git-bash 报裸 127

代码已修(在 HEAD):

  • scanner.cppm:is_excluded_walk_dir prune .mcpp(:291-300,两处 walk 调用 :438-441/:531-532)+ 窄串转换 try/catch never-throw(:135-146)。
  • src/main.cpp:顶层 catch,未捕获异常→error: internal: unhandled exception + exit 70(不再裸 127)。 来源 df985df(#231),0.0.98 已含。

3.2 0.0.99 补:build.mcpp 产物 windows .exe 后缀

scanner 逃逸修复后,workspace 的 build-mcpp 成员(带 build.mcpp)在 windows 才会被真正走到——此时暴露一个次生 windows 面:

src/build/build_program.cppm:539 硬编码产物名 build.mcpp.bin(无 .exe),:591capture_exec 运行;windows 上 capture_exec 走 cmd.exe(_popen,argv[0] 原样不加引号),cmd 无法按 .bin 扩展名执行 PE(不在 PATHEXT)。

修复:产物名按平台取后缀(与 plan.cppm:201mcpp::platform::exe_suffix 同惯例):

fs::path bin = bdir / (mcpp::platform::is_windows ? "build.mcpp.exe" : "build.mcpp.bin");

非 windows 字节不变(仍 build.mcpp.bin);windows 产出并按 .exe 名执行。此改动零 schema、零跨平台行为回归(is_windows 是 constexpr)。既有 e2e 89_build_mcpp.sh / 92_build_mcpp_import.sh 在 linux 覆盖(exe_suffix 为空,无行为变化);windows 侧由 CI(ci-windows)+ mcpp-index workspace-windows 复验。

3.3 复验与关闭

0.0.99 release 后,mcpp-index 的 workspace(windows)CI pin 升到 0.0.99(从临时钉回的 0.0.94),全绿则关闭 #230。


4. 版本 / PR / 发布 / 生态验证计划

单 PR,逐 commit 绿(每 commit mcpp build self-host + mcpp test 全绿):

  1. feat(manifest):featureForwards 数据模型 + split_feature_forward_token + 两文法解析 + 解析单测。
  2. feat(build):转发注入 dependencyEdges 漏斗(root/dep 两 push 点)+ 未声明-dep 校验 + e2e 128 并入 run_all.sh
  3. fix(build.mcpp):windows .exe 后缀(#230 次生面)。
  4. chore(deps):vendored xlings 0.4.62 → 0.4.67(#238);workflow pins。
  5. chore(release):版本 0.0.99(fingerprint.cppm + mcpp.toml + CHANGELOG,仅末尾改版本号)。

发布闭环(memory release-publish-pipeline):release→四平台产物→mirror xlings-res(gh+gtc 双端)→xim-pkgindex PR→xlings install 真装 0.0.99→bump bootstrap pin(.xlings.json / workspace)。

生态真实验证(必做,三 issue 各一条闭环):

  • #243:在真实索引拉一个带转发的包(或本地夹具),import 被转发 feature 提供的模块接口,编译+运行通过。
  • #238:≥2 index_repo 沙箱真装未缓存包,exit 0。
  • #230:mcpp-index workspace(windows)CI 在 0.0.99 全绿。

架构验收:manifest 文法 / xpkg 描述符 / feature 模型三处对"转发"收敛为一数据模型 featureForwards 多文法;per-package 请求 feature 集仍是唯一漏斗 aggregatedRequest(转发只注入,不新建平行结构)。