Skip to content

Commit 078631d

Browse files
authored
fix: xpkg parse 的 Form A 输出补回已计算的 versions (#489) (#490)
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
1 parent b54f4a6 commit 078631d

4 files changed

Lines changed: 94 additions & 19 deletions

File tree

docs/11-machine-output.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ mcpp xpkg parse <file.lua> --format json
222222

223223
`data` is the same document `--json` prints bare.
224224

225+
A descriptor whose `mcpp` field is an inline table yields the full document:
226+
`namespace`, `name`, `versions`, `standard`, `import_std`, `sources`,
227+
`include_dirs`, `generated_files`, `generated_contents`, `targets`,
228+
`unknown_keys`. A descriptor without an inline table reports `"form": "A"`
229+
in place of the build information. Both forms carry `versions` — the per-OS
230+
version keys of the descriptor's `xpm` tables.
231+
225232
### `mcpp.cache` — the global build cache
226233

227234
```

docs/zh/11-machine-output.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ mcpp xpkg parse <file.lua> --format json
193193

194194
`data` 就是 `--json` 裸打印的那份文档。
195195

196+
`mcpp` 字段为内联表的描述符产出完整文档:`namespace``name``versions`
197+
`standard``import_std``sources``include_dirs``generated_files`
198+
`generated_contents``targets``unknown_keys`。没有内联表的描述符以
199+
`"form": "A"` 代替构建信息。两种形态都携带 `versions` —— 描述符各平台
200+
`xpm` 表的版本键。
201+
196202
### `mcpp.cache` —— 全局构建缓存
197203

198204
```

src/cli/cmd_xpkg.cppm

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,32 @@ std::string json_array(const std::vector<std::string>& v) {
7777
return out + "]";
7878
}
7979

80+
// The per-OS xpm version lists, in the payload shape both forms share:
81+
// {"linux":[...],"macosx":[...],"windows":[...]}. Form A emits it too —
82+
// the tables are computed before the form branch either way, and dropping
83+
// them there is what made envelope consumers read Form A descriptors as
84+
// version-less (mcpp-vscode#8).
85+
std::string versions_json(
86+
const std::map<std::string, std::vector<std::string>>& versions) {
87+
std::string out = "{";
88+
bool first = true;
89+
for (auto& [plat, v] : versions) {
90+
if (!first) out += ",";
91+
first = false;
92+
out += "\"" + plat + "\":" + json_array(v);
93+
}
94+
return out + "}";
95+
}
96+
97+
std::string joined_versions(const std::vector<std::string>& v) {
98+
std::string joined;
99+
for (std::size_t i = 0; i < v.size(); ++i) {
100+
if (i) joined += ", ";
101+
joined += v[i];
102+
}
103+
return joined;
104+
}
105+
80106
} // namespace
81107

82108
export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) {
@@ -164,14 +190,22 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) {
164190

165191
// Form A descriptors carry no `mcpp = {}` table — build info comes
166192
// from the fetched source's own mcpp.toml. Nothing further to parse.
193+
// The xpm version tables are independent of that segment and are the
194+
// resolver's source of truth, so both spellings publish them here.
167195
auto field = mcpp::manifest::extract_mcpp_field(lua);
168196
if (field.kind != mcpp::manifest::McppField::TableBody) {
169197
if (asJson) {
170198
emit_xpkg(std::format(
171-
"{{\"namespace\":\"{}\",\"name\":\"{}\",\"form\":\"A\"}}",
172-
json_escape(id.ns), json_escape(id.name)), enveloped);
199+
"{{\"namespace\":\"{}\",\"name\":\"{}\",\"versions\":{},"
200+
"\"form\":\"A\"}}",
201+
json_escape(id.ns), json_escape(id.name),
202+
versions_json(versions)), enveloped);
173203
} else {
174204
std::println("package {} (namespace '{}')", fqn, id.ns);
205+
for (auto& [plat, v] : versions) {
206+
if (v.empty()) continue;
207+
std::println("versions {:<8} {}", plat, joined_versions(v));
208+
}
175209
std::println("form A — no mcpp segment (build info from the "
176210
"source's mcpp.toml)");
177211
std::println("parse OK");
@@ -254,16 +288,6 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) {
254288
targets += "\"" + json_escape(m->targets[i].name) + "\"";
255289
}
256290
targets += "]";
257-
std::string vers = "{";
258-
{
259-
bool f2 = true;
260-
for (auto& [plat, v] : versions) {
261-
if (!f2) vers += ",";
262-
f2 = false;
263-
vers += "\"" + plat + "\":" + json_array(v);
264-
}
265-
}
266-
vers += "}";
267291
std::string genContents = "{";
268292
{
269293
bool f3 = true;
@@ -281,7 +305,8 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) {
281305
"\"include_dirs\":{},\"generated_files\":{},"
282306
"\"generated_contents\":{},\"targets\":{},"
283307
"\"unknown_keys\":{}}}",
284-
json_escape(id.ns), json_escape(id.name), vers,
308+
json_escape(id.ns), json_escape(id.name),
309+
versions_json(versions),
285310
json_escape(m->package.standard),
286311
m->language.importStd ? "true" : "false",
287312
json_array(m->modules.sources),
@@ -299,12 +324,7 @@ export int cmd_xpkg_parse(const mcpplibs::cmdline::ParsedArgs& parsed) {
299324
std::println("package {} (namespace '{}')", fqn, id.ns);
300325
for (auto& [plat, v] : versions) {
301326
if (v.empty()) continue;
302-
std::string joined;
303-
for (std::size_t i = 0; i < v.size(); ++i) {
304-
if (i) joined += ", ";
305-
joined += v[i];
306-
}
307-
std::println("versions {:<8} {}", plat, joined);
327+
std::println("versions {:<8} {}", plat, joined_versions(v));
308328
}
309329
std::println("standard {} import_std={}", m->package.standard,
310330
m->language.importStd);

tests/e2e/93_xpkg_parse.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,46 @@ if "$MCPP" xpkg parse broken.lua > /dev/null 2>&1; then
7777
echo "FAIL: broken descriptor should fail"; exit 1
7878
fi
7979

80+
# ── 5. Form A descriptor (no inline `mcpp` segment): versions still ship ──
81+
# Form A = the descriptor carries no `mcpp = {}` table (build info comes from
82+
# the fetched source's own mcpp.toml). The xpm version tables are still the
83+
# resolver's source of truth, so both spellings must publish them — dropping
84+
# already-computed versions here is what made the vscode completion layer
85+
# treat 18/81 index descriptors as version-less (mcpp-vscode#8).
86+
cat > forma.lua <<'EOF'
87+
package = {
88+
spec = "1",
89+
namespace = "demo",
90+
name = "demo.forma",
91+
xpm = {
92+
linux = { ["0.9.0"] = { url = "u", sha256 = "h" } },
93+
macosx = { ["0.9.0"] = { url = "u", sha256 = "h" },
94+
["1.0.0"] = { url = "u2", sha256 = "h2" } },
95+
windows = { ["1.0.0"] = { url = "u", sha256 = "h" } },
96+
},
97+
}
98+
EOF
99+
100+
"$MCPP" xpkg parse forma.lua | tee forma.out
101+
grep -q "form A" forma.out
102+
grep -q "versions linux" forma.out
103+
grep -q "1.0.0" forma.out
104+
grep -q "parse OK" forma.out
105+
106+
"$MCPP" xpkg parse --json forma.lua > forma.json
107+
"$MCPP" xpkg parse --format json forma.lua > forma.env.json
108+
python3 - <<'PY'
109+
import json
110+
j = json.load(open("forma.json"))
111+
assert j["namespace"] == "demo" and j["name"] == "forma", j
112+
assert j["form"] == "A", j
113+
assert sorted(j["versions"]["linux"]) == ["0.9.0"], j
114+
assert sorted(j["versions"]["macosx"]) == ["0.9.0", "1.0.0"], j
115+
assert sorted(j["versions"]["windows"]) == ["1.0.0"], j
116+
e = json.load(open("forma.env.json"))
117+
assert e["kind"] == "mcpp.xpkg", e
118+
assert sorted(e["data"]["versions"]["macosx"]) == ["0.9.0", "1.0.0"], e
119+
print("form a ok")
120+
PY
121+
80122
echo "PASS 93_xpkg_parse"

0 commit comments

Comments
 (0)