From f2dbeecf113a3dd94d9b71fa1aadab36aec936a8 Mon Sep 17 00:00:00 2001 From: wellwei Date: Sun, 23 Aug 2026 16:41:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20xpkg=20parse=20=E7=9A=84=20Form=20A=20?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E8=A1=A5=E5=9B=9E=E5=B7=B2=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E7=9A=84=20versions=20(#489)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Form A(描述符无内联 `mcpp = {}` 表,构建信息来自拉取源码自己的 mcpp.toml)在进入分支前就已经算好 linux/macosx/windows 三平台版本表, 但 --json 只吐 {"namespace","name","form":"A"},把 versions 扔掉了; 文本模式同样不打印 versions 行。消费者(vscode 补全层)因此把索引里 18/81 的 Form A 描述符当成无版本包(mcpp-vscode#8,#379 评论有核对)。 两种拼写(--json / --format json)共用同一 payload,信封只是包裹, 所以修的是 payload 本身:JSON 加 "versions" 键(Form B 同形状), 文本加与 Form B 相同的 versions 行。渲染收敛为 versions_json / joined_versions 两个辅助函数,两个分支单一来源。 xpm 表全平台缺席仍在分支前报错退出,所以 Form A 的 versions 至少一个 平台非空;个别平台为空时文本跳过该行、JSON 保持空数组(Form B 惯例)。 docs/11-machine-output.md(en+zh)补记两种形态的 data 形状。 测试:e2e 93 新增第 5 节 —— Absent 形态描述符断言文本三行 versions、 --json 的 versions 形状、--format json 信封内 data.versions。先在未 修复二进制(2026.8.21.3)上确认失败(exit=1),新二进制通过;单测 92 passed 0 failed;e2e 202 契约脚本通过。 Closes #489 Refs mcpp-community/mcpp-vscode#8 --- docs/11-machine-output.md | 7 +++++ docs/zh/11-machine-output.md | 6 ++++ src/cli/cmd_xpkg.cppm | 58 ++++++++++++++++++++++++------------ tests/e2e/93_xpkg_parse.sh | 42 ++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 19 deletions(-) diff --git a/docs/11-machine-output.md b/docs/11-machine-output.md index 76a6c2ac..ccac382d 100644 --- a/docs/11-machine-output.md +++ b/docs/11-machine-output.md @@ -222,6 +222,13 @@ mcpp xpkg parse --format json `data` is the same document `--json` prints bare. +A descriptor whose `mcpp` field is an inline table yields the full document: +`namespace`, `name`, `versions`, `standard`, `import_std`, `sources`, +`include_dirs`, `generated_files`, `generated_contents`, `targets`, +`unknown_keys`. A descriptor without an inline table reports `"form": "A"` +in place of the build information. Both forms carry `versions` — the per-OS +version keys of the descriptor's `xpm` tables. + ### `mcpp.cache` — the global build cache ``` diff --git a/docs/zh/11-machine-output.md b/docs/zh/11-machine-output.md index b12ca2c2..3accfd92 100644 --- a/docs/zh/11-machine-output.md +++ b/docs/zh/11-machine-output.md @@ -193,6 +193,12 @@ mcpp xpkg parse --format json `data` 就是 `--json` 裸打印的那份文档。 +`mcpp` 字段为内联表的描述符产出完整文档:`namespace`、`name`、`versions`、 +`standard`、`import_std`、`sources`、`include_dirs`、`generated_files`、 +`generated_contents`、`targets`、`unknown_keys`。没有内联表的描述符以 +`"form": "A"` 代替构建信息。两种形态都携带 `versions` —— 描述符各平台 +`xpm` 表的版本键。 + ### `mcpp.cache` —— 全局构建缓存 ``` diff --git a/src/cli/cmd_xpkg.cppm b/src/cli/cmd_xpkg.cppm index 3a2b4a1e..01e08ca1 100644 --- a/src/cli/cmd_xpkg.cppm +++ b/src/cli/cmd_xpkg.cppm @@ -77,6 +77,32 @@ std::string json_array(const std::vector& v) { return out + "]"; } +// The per-OS xpm version lists, in the payload shape both forms share: +// {"linux":[...],"macosx":[...],"windows":[...]}. Form A emits it too — +// the tables are computed before the form branch either way, and dropping +// them there is what made envelope consumers read Form A descriptors as +// version-less (mcpp-vscode#8). +std::string versions_json( + const std::map>& versions) { + std::string out = "{"; + bool first = true; + for (auto& [plat, v] : versions) { + if (!first) out += ","; + first = false; + out += "\"" + plat + "\":" + json_array(v); + } + return out + "}"; +} + +std::string joined_versions(const std::vector& v) { + std::string joined; + for (std::size_t i = 0; i < v.size(); ++i) { + if (i) joined += ", "; + joined += v[i]; + } + return joined; +} + } // namespace export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) { @@ -164,14 +190,22 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) { // Form A descriptors carry no `mcpp = {}` table — build info comes // from the fetched source's own mcpp.toml. Nothing further to parse. + // The xpm version tables are independent of that segment and are the + // resolver's source of truth, so both spellings publish them here. auto field = mcpp::manifest::extract_mcpp_field(lua); if (field.kind != mcpp::manifest::McppField::TableBody) { if (asJson) { emit_xpkg(std::format( - "{{\"namespace\":\"{}\",\"name\":\"{}\",\"form\":\"A\"}}", - json_escape(id.ns), json_escape(id.name)), enveloped); + "{{\"namespace\":\"{}\",\"name\":\"{}\",\"versions\":{}," + "\"form\":\"A\"}}", + json_escape(id.ns), json_escape(id.name), + versions_json(versions)), enveloped); } else { std::println("package {} (namespace '{}')", fqn, id.ns); + for (auto& [plat, v] : versions) { + if (v.empty()) continue; + std::println("versions {:<8} {}", plat, joined_versions(v)); + } std::println("form A — no mcpp segment (build info from the " "source's mcpp.toml)"); std::println("parse OK"); @@ -254,16 +288,6 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) { targets += "\"" + json_escape(m->targets[i].name) + "\""; } targets += "]"; - std::string vers = "{"; - { - bool f2 = true; - for (auto& [plat, v] : versions) { - if (!f2) vers += ","; - f2 = false; - vers += "\"" + plat + "\":" + json_array(v); - } - } - vers += "}"; std::string genContents = "{"; { bool f3 = true; @@ -281,7 +305,8 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) { "\"include_dirs\":{},\"generated_files\":{}," "\"generated_contents\":{},\"targets\":{}," "\"unknown_keys\":{}}}", - json_escape(id.ns), json_escape(id.name), vers, + json_escape(id.ns), json_escape(id.name), + versions_json(versions), json_escape(m->package.standard), m->language.importStd ? "true" : "false", json_array(m->modules.sources), @@ -299,12 +324,7 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) { std::println("package {} (namespace '{}')", fqn, id.ns); for (auto& [plat, v] : versions) { if (v.empty()) continue; - std::string joined; - for (std::size_t i = 0; i < v.size(); ++i) { - if (i) joined += ", "; - joined += v[i]; - } - std::println("versions {:<8} {}", plat, joined); + std::println("versions {:<8} {}", plat, joined_versions(v)); } std::println("standard {} import_std={}", m->package.standard, m->language.importStd); diff --git a/tests/e2e/93_xpkg_parse.sh b/tests/e2e/93_xpkg_parse.sh index 8012e596..7b8a7243 100755 --- a/tests/e2e/93_xpkg_parse.sh +++ b/tests/e2e/93_xpkg_parse.sh @@ -77,4 +77,46 @@ if "$MCPP" xpkg parse broken.lua > /dev/null 2>&1; then echo "FAIL: broken descriptor should fail"; exit 1 fi +# ── 5. Form A descriptor (no inline `mcpp` segment): versions still ship ── +# Form A = the descriptor carries no `mcpp = {}` table (build info comes from +# the fetched source's own mcpp.toml). The xpm version tables are still the +# resolver's source of truth, so both spellings must publish them — dropping +# already-computed versions here is what made the vscode completion layer +# treat 18/81 index descriptors as version-less (mcpp-vscode#8). +cat > forma.lua <<'EOF' +package = { + spec = "1", + namespace = "demo", + name = "demo.forma", + xpm = { + linux = { ["0.9.0"] = { url = "u", sha256 = "h" } }, + macosx = { ["0.9.0"] = { url = "u", sha256 = "h" }, + ["1.0.0"] = { url = "u2", sha256 = "h2" } }, + windows = { ["1.0.0"] = { url = "u", sha256 = "h" } }, + }, +} +EOF + +"$MCPP" xpkg parse forma.lua | tee forma.out +grep -q "form A" forma.out +grep -q "versions linux" forma.out +grep -q "1.0.0" forma.out +grep -q "parse OK" forma.out + +"$MCPP" xpkg parse --json forma.lua > forma.json +"$MCPP" xpkg parse --format json forma.lua > forma.env.json +python3 - <<'PY' +import json +j = json.load(open("forma.json")) +assert j["namespace"] == "demo" and j["name"] == "forma", j +assert j["form"] == "A", j +assert sorted(j["versions"]["linux"]) == ["0.9.0"], j +assert sorted(j["versions"]["macosx"]) == ["0.9.0", "1.0.0"], j +assert sorted(j["versions"]["windows"]) == ["1.0.0"], j +e = json.load(open("forma.env.json")) +assert e["kind"] == "mcpp.xpkg", e +assert sorted(e["data"]["versions"]["macosx"]) == ["0.9.0", "1.0.0"], e +print("form a ok") +PY + echo "PASS 93_xpkg_parse"