Skip to content

Commit 172408a

Browse files
authored
fix(build.mcpp): 规则包能用 import std; 与 import mcpp; (2026.8.5.2) (#357)
* fix(build.mcpp): 规则包能用 import std; 与 import mcpp; (2026.8.5.2) `host-module = true` 是 2026.8.5.1 加的:一条构建规则以普通 mcpp 包分发,消费者 在 build.mcpp 里 `import` 它。机制本身成立,但两个缺陷叠在一起,使它**只能承载 手工 printf 指令的玩具规则** —— 规则一旦去用它本该用的那套 API 就编不过。 第一个真实使用者(grpc-m 的 protoc/gRPC codegen 规则)在第一分钟同时撞上两个。 e2e 189 之所以一直是绿的,是因为它的规则只 `#include <cstdio>` 再 printf,恰好 把两个缺陷都绕开了。 ── 1. 规则里的 `import std;` ──────────────────────────────────────────────── `build_program.cppm` 先编 host module、后建 std 模块,规则拿到的 `stdFlags` 是 空的,报 `module 'std' not found`。而且「是否需要 std」只扫 build.mcpp 的源码, 所以一条**只有规则**用 `import std;` 的构建根本不会去建 std 模块。 两处都修:扫描把规则接口一并计入(usesStd / usesStdCompat / usesModule 三者都 是并集),std 那一段整体挪到编译 host module **之前**,并把 stdFlags 传进 build_host_module 的 extraUseFlags。顺序是承重的,不是风格:BMI 必须先存在、 stdFlags 必须先指向它,规则才可能 import 它。 ── 2. 规则里的 `import mcpp;` ─────────────────────────────────────────────── `host-module = true` 只做了「注册这个模块」。`prepare.cppm` 从头到尾只**读** `spec.hostModule`,从没把这个包移出消费者的**普通依赖图** —— 于是同一个 `.cppm` 又被当成消费者的一个普通库编了一遍,而那次编译里内置 `mcpp` 模块并不存在: failed: obj/grpcgen/src/grpcgen.m.o fatal error: module 'mcpp' not found 一条构建规则是纯构建期的东西,本来就不该进消费者的二进制 —— Cargo 用 `[build-dependencies]` 划的是同一条界线。现在只经由 root 的 host-module 边到达 的包会被清空源码集(与 feature-gated sources 的丢弃用同一套机制),不再参与编译 与链接;解析不动,因为 lib 根要从磁盘上读。 **有护栏**:同一个包完全可以既是规则、又是别处(或 root 另一种拼法)的普通库。 只在「指向它的边全部来自 root」时才清空,否则会变成一个离现场很远的 undefined reference。 ── 测试 ───────────────────────────────────────────────────────────────────── e2e 189 扩了两条断言,并且**先验证过它们在 2026.8.5.1 上是红的**: - 一条同时 `import std;` 和 `import mcpp;` 的规则必须能建、指令必须落地; - `obj/rules/**.o` 必须不存在 —— 规则包被当普通库编译过就会留下它。 本地 e2e 全量:除 03/07/09 三条既有的环境性失败外全绿(已对released 2026.8.4.1 核对过,与本改动无关)。 ── 文档 ───────────────────────────────────────────────────────────────────── `docs/05-mcpp-toml.md` 的示例写的是 `import mcpp.rules.protobuf;`,**做不到**: mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一 原子段。顺带写明一条会咬人的约束:规则包的名字必须是合法 C++ 模块名(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示 你名字有问题。中英文档同步。 bootstrap pin 不动(仍 2026.8.4.1):它是自举起点,不随发布走。 * fix(ci): bootstrap pin 指向了索引里已经没有的版本 aarch64 fresh-install job 在 clone 出 mcpp 源码后直接死在: [error] xlings: version '2026.8.4.1' not found for 'mcpp' [error] available: 2026.8.5.1 `.xlings.json` 的 bootstrap pin 停在 2026.8.4.1,而 **xim-pkgindex 只保留一个 mcpp 版本**。2026.8.5.1 一发布,2026.8.4.1 就不再可安装 —— 于是「从已发布的 mcpp 自举」这条路当场断掉。 pin 挪到 2026.8.5.1(= 当前最新已发布版本)。 顺带把这条不变式写进 check_version_pins.sh:此前那段注释只讲了**上界** (「绝不能跑在被构建版本前面」,那是它自己踩过的坑),没讲**下界**。两边合起来 才是完整规则:**发布 N 之后,发布收尾的那个 commit 把 pin 设成 N** —— 不能超前 (check (c) 机器校验),也不能落后超过一个版本(校验不了,它需要索引,所以只会以 上面那条错误现身;注释里写清楚了下次去哪儿看)。 * fix(ci): aarch64 self-host gate 克隆的是 main,永远看不到被评审的 PR 「Self-host — build mcpp + xlings from source」这一步固定克隆上游 main。于是 这道关卡从来没有验证过 PR 本身:一个会破坏「从源码自举」的改动能顺利通过它, 只在合入之后才炸。 这次是以最刺眼的方式暴露的 —— 上一个 commit 修的正是本仓库的 bootstrap pin, 而那个修复**无法被验证**:修复在分支上,克隆的却是 main,于是 job 继续报 `version '2026.8.4.1' not found`。 PR 上改为自举**被评审的那份代码**;非 PR 场景没有 head ref,main 本来就是对的。 用 fetch-by-sha 而不是 `clone --depth 1`(后者不接受 sha)。
1 parent d1b2c11 commit 172408a

11 files changed

Lines changed: 213 additions & 31 deletions

File tree

.github/tools/check_version_pins.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ fi
140140
# what an earlier revision of this script got wrong: it sent CI to install a
141141
# version that did not exist yet, and every job died with
142142
# `package 'mcpp@<unreleased>' not found`.
143+
#
144+
# But it may lag by exactly ONE release, no more. xim-pkgindex carries a
145+
# SINGLE mcpp version — the newest — so the moment a release lands, every
146+
# older pin names something that is no longer installable:
147+
#
148+
# [error] xlings: version '2026.8.4.1' not found for 'mcpp'
149+
# [error] available: 2026.8.5.1
150+
#
151+
# which is how the aarch64 fresh-install job died after 2026.8.5.1 shipped.
152+
# The rule that satisfies both directions: after publishing release N, the
153+
# post-release commit sets this pin to N. Never ahead (check (c)), never
154+
# more than one behind (not checkable here — it needs the index — so it
155+
# surfaces as the error above, and this note is where to look when it does).
143156

144157
# (c) …and the bootstrap pin must never run AHEAD of the version being built.
145158
# Four-key numeric sort, so the date scheme orders correctly (a plain

.github/workflows/ci-aarch64-fresh-install.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,25 @@ jobs:
9999
run: |
100100
# mcpp/xlings manifests pin a glibc default toolchain; on aarch64 the
101101
# musl-static target is the published path, so build with --target.
102-
git clone --depth 1 https://github.com/mcpp-community/mcpp /tmp/mcpp-src
102+
#
103+
# On a pull_request, self-host the code UNDER REVIEW rather than
104+
# upstream main. Cloning main meant this gate never saw the PR at
105+
# all: a change that breaks the from-source build passed here and
106+
# only failed after merge, and — the way this surfaced — a fix to
107+
# the repo's own bootstrap pin was untestable, because the fix was
108+
# on the branch while the clone was of main. Outside a PR there is
109+
# no head ref and main is exactly right.
110+
ref='${{ github.event.pull_request.head.sha }}'
111+
repo='${{ github.event.pull_request.head.repo.clone_url }}'
112+
[ -n "$ref" ] || ref='${{ github.sha }}'
113+
[ -n "$repo" ] || repo='https://github.com/mcpp-community/mcpp'
114+
# fetch-by-sha rather than `clone --depth 1`, which cannot take one.
115+
git init -q /tmp/mcpp-src
103116
cd /tmp/mcpp-src
117+
git remote add origin "$repo"
118+
git fetch -q --depth 1 origin "$ref"
119+
git checkout -q FETCH_HEAD
120+
echo "self-hosting $repo @ $ref"
104121
mcpp self config --mirror GLOBAL 2>/dev/null || true
105122
mcpp build --target aarch64-linux-musl
106123
m=$(find target/aarch64-linux-musl -type f -path '*/bin/mcpp' | head -1)

.xlings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"workspace": {
3-
"mcpp": "2026.8.4.1"
3+
"mcpp": "2026.8.5.1"
44
}
55
}

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+
## [2026.8.5.2] — 2026-08-05
7+
8+
修复 `host-module = true`(规则包)的两个缺陷。二者都是 2026.8.5.1 引入的,合起来的效果是:**规则包只能写「手工 printf 指令」的玩具规则**,一旦规则要用它本该用的 API 就编不过。第一个真实使用者(`grpc-m` 的 protoc/gRPC codegen 规则)在第一分钟就同时撞上了这两个。
9+
10+
### 修复
11+
12+
- **规则里的 `import std;`** `build_program.cppm` 先编 host module、后建 std 模块,所以规则拿到的 `stdFlags` 是空的,报 `module 'std' not found`。而且「是否需要 std」只扫了 `build.mcpp` 的源码 —— 一条只有**规则**`import std;` 的构建根本不会去建 std 模块。现在两处都修:扫描把规则接口一并计入,std 那一段整体挪到编译 host module **之前**
13+
14+
这里的顺序是承重的,不是风格问题:BMI 必须先存在、`stdFlags` 必须先指向它,规则才可能 import。
15+
16+
- **规则里的 `import mcpp;`** `host-module = true` 只做了「注册这个模块」,从没把这个包移出消费者的**普通依赖图** —— 于是同一个 `.cppm` 又被当作消费者的一个普通库编译了一遍,而在那次编译里内置 `mcpp` 模块并不存在,报 `fatal error: module 'mcpp' not found`
17+
18+
一条构建规则是**纯构建期**的东西,本来就不该进消费者的二进制(Cargo 用 `[build-dependencies]` 划的是同一条界线)。现在只经由 root 的 host-module 边到达的包会被清空源码集,不再参与编译与链接;仍会被解析落盘,因为 lib 根要从那里读。
19+
20+
**有护栏**:同一个包完全可以既是规则、又是别处(或 root 另一种拼法)的普通库,此时不清空 —— 否则会变成一个离现场很远的 undefined reference。
21+
22+
### 文档
23+
24+
- `docs/05-mcpp-toml.md` 的示例此前写的是 `import mcpp.rules.protobuf;`,**做不到**:mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是**合法 C++ 模块名**(`grpcgen` 可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示你名字有问题。
25+
626
## [2026.8.5.1] — 2026-08-05
727

828
`build.mcpp` 机制的**架构地基**:把「一条指令是什么」收敛成一张表,并补上三个今天就存在的稳定性缺口。架构分析见 `.agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md`(本次实现其中的步 0 与步 1)。

docs/05-mcpp-toml.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,14 +1026,14 @@ library package and import it:
10261026

10271027
```toml
10281028
[dependencies]
1029-
"mcpp.rules.protobuf" = { version = "0.1.0", host-module = true }
1029+
protobufgen = { version = "0.1.0", host-module = true }
10301030
```
10311031

10321032
```cpp
10331033
// build.mcpp
10341034
import mcpp;
1035-
import mcpp.rules.protobuf;
1036-
int main() { mcpp::rules::protobuf::generate(/**/); }
1035+
import protobufgen;
1036+
int main() { return protobufgen::generate({"schema"}) ? 0 : 1; }
10371037
```
10381038
10391039
mcpp compiles that package's lib-root module **for the host, in the same
@@ -1045,9 +1045,25 @@ Rules are therefore versioned, testable and distributable through the package
10451045
manager you already have, written in **C++** — no second language, which is the
10461046
whole point of `build.mcpp` existing.
10471047
1048+
**The module name is the package's `name`.** mcpp registers the host module
1049+
under the dependency's bare `package.name` (not `<namespace>.<name>`), so a
1050+
rule package's name has to be a legal C++ module name: `grpcgen` works,
1051+
`grpc-rules` does not — the hyphen is fine in a package name and illegal in a
1052+
module name, and the mismatch surfaces as `module 'grpc_rules' not found`
1053+
rather than as a complaint about the name.
1054+
1055+
The lib root must be at `src/<name>.cppm` (or wherever `[lib] path` points); a
1056+
missing one is reported as *"host module 'x': no interface unit at …"*.
1057+
10481058
*Limit:* the rule interface is compiled alone, so it may import `std` and the
10491059
bundled `mcpp` module, but not a third package. A rule package is a leaf.
10501060
1061+
*Build-time only:* a `host-module = true` dependency is **not** compiled into
1062+
or linked with your target. It exists to run during `build.mcpp` and nowhere
1063+
else — the same separation Cargo draws with `[build-dependencies]`. (Before
1064+
2026.8.5.2 it was also built as an ordinary library, which made `import mcpp;`
1065+
inside a rule fail: the bundled module does not exist in that second compile.)
1066+
10511067
## Appendix A. Schema Ownership Principle (admission criteria for new fields)
10521068
10531069
> **Closed syntax, open vocabulary**: whoever owns the parsing semantics defines the keys; whoever owns the domain knowledge defines the values.

docs/zh/05-mcpp-toml.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -779,14 +779,14 @@ MCPP_TOOL_PROTOBUF_PROTOC=/usr/bin/protoc mcpp build
779779

780780
```toml
781781
[dependencies]
782-
"mcpp.rules.protobuf" = { version = "0.1.0", host-module = true }
782+
protobufgen = { version = "0.1.0", host-module = true }
783783
```
784784

785785
```cpp
786786
// build.mcpp
787787
import mcpp;
788-
import mcpp.rules.protobuf;
789-
int main() { mcpp::rules::protobuf::generate(/**/); }
788+
import protobufgen;
789+
int main() { return protobufgen::generate({"schema"}) ? 0 : 1; }
790790
```
791791
792792
mcpp 会把该包的 lib 根模块**为 host 编译,且与 `build.mcpp` 在同一条命令里** ——
@@ -796,9 +796,22 @@ mcpp 会把该包的 lib 根模块**为 host 编译,且与 `build.mcpp` 在同
796796
于是规则**有版本、能测试、能通过你已有的包管理器分发**,而且是用 **C++** 写的
797797
—— 不引入第二门语言,这正是 `build.mcpp` 存在的理由。
798798
799+
**模块名就是包的 `name`。** mcpp 用依赖的裸 `package.name`(而**不是**
800+
`<namespace>.<name>`)注册这个 host 模块,所以规则包的名字必须是合法的 C++ 模块名:
801+
`grpcgen` 可以,`grpc-rules` 不行 —— 连字符在包名里合法、在模块名里非法,而且报出来
802+
的是 `module 'grpc_rules' not found`,不会提示你名字有问题。
803+
804+
lib 根必须在 `src/<name>.cppm`(或 `[lib] path` 指向的位置);缺失时报
805+
*"host module 'x': no interface unit at …"*。
806+
799807
*限制:* 规则接口是单独编译的,因此可以 import `std` 与内置 `mcpp` 模块,
800808
但不能 import 第三个包。规则包按构造是叶子。
801809
810+
*仅构建期:* `host-module = true` 的依赖**不会**被编进、也不会被链进你的 target。
811+
它只在 `build.mcpp` 期间运行,别处都不出现 —— 与 Cargo 用 `[build-dependencies]`
812+
划出的是同一条界线。(2026.8.5.2 之前它还会被当作普通库再编一遍,这正是规则里
813+
`import mcpp;` 失败的原因:在那第二次编译里内置模块并不存在。)
814+
802815
## 附录 A. Schema 所有权原则(新字段准入标准)
803816
804817
> **语法封闭,词汇开放**:谁拥有解析语义谁定义键;谁拥有领域知识谁定义值。

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 = "2026.8.5.1"
3+
version = "2026.8.5.2"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/build/build_program.cppm

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,22 @@ std::expected<void, std::string> run_build_program(
478478
bool usesStdCompat = imports_module(srcText, "std.compat");
479479
bool usesStd = usesStdCompat || imports_module(srcText, "std");
480480

481+
// A rule package's interface is compiled by this same function, so what IT
482+
// imports decides what has to be built just as much as what build.mcpp
483+
// imports. Scanning only build.mcpp made a rule that said `import std;`
484+
// fail with `module 'std' not found` — the std module was never built,
485+
// because the program that triggers the build did not mention it.
486+
for (auto const& [logical, ifacePath] : env.hostModules) {
487+
std::ifstream is(ifacePath);
488+
if (!is) continue; // a missing interface is diagnosed by build_host_module
489+
std::ostringstream ss; ss << is.rdbuf();
490+
const std::string t = ss.str();
491+
if (t.find("import mcpp") != std::string::npos) usesModule = true;
492+
if (imports_module(t, "std.compat")) usesStdCompat = true;
493+
if (imports_module(t, "std")) usesStd = true;
494+
}
495+
usesStd = usesStd || usesStdCompat;
496+
481497
// The toolchain's own environment (MSVC's INCLUDE / LIB / VSLANG, which
482498
// detection synthesized from the located VC tools + Windows SDK). Needed
483499
// by every compile below, the module precompile included.
@@ -499,27 +515,6 @@ std::expected<void, std::string> run_build_program(
499515
mcppModuleObject = std::move(mf->object);
500516
}
501517

502-
// #355 step 5: dependency-provided host modules (reusable build rules
503-
// shipped as ordinary packages). Compiled HERE, with `base` and `std_flag`
504-
// — the same flags the build.mcpp compile below gets — because a BMI is
505-
// only usable by a compile that agrees with it. Doing this in a separate
506-
// sub-build would leave that agreement to chance, and disagreement shows
507-
// up as `module X CRC mismatch`, not as a clear error.
508-
std::vector<fs::path> hostModuleObjects;
509-
for (auto const& [logical, ifacePath] : env.hostModules) {
510-
auto hm = build_host_module(bdir, hostCompiler, base, std_flag, tc,
511-
compileEnv, logical, ifacePath, moduleFlags);
512-
if (!hm) return std::unexpected(hm.error());
513-
for (auto& f : hm->useFlags) {
514-
// GCC's marker is just `-fmodules`, already present when the
515-
// bundled module was built; repeating it is harmless but noisy.
516-
if (std::find(moduleFlags.begin(), moduleFlags.end(), f)
517-
== moduleFlags.end())
518-
moduleFlags.push_back(f);
519-
}
520-
hostModuleObjects.push_back(std::move(hm->object));
521-
}
522-
523518
// ── `import std;` in build.mcpp ─────────────────────────────────────────
524519
//
525520
// mcpp asks projects to `import std;` everywhere and then made their build
@@ -606,6 +601,35 @@ std::expected<void, std::string> run_build_program(
606601
stdObjects.push_back(sm->compatObjectPath.string());
607602
}
608603

604+
// #355 step 5: dependency-provided host modules (reusable build rules
605+
// shipped as ordinary packages). Compiled HERE, with `base` and `std_flag`
606+
// — the same flags the build.mcpp compile below gets — because a BMI is
607+
// only usable by a compile that agrees with it. Doing this in a separate
608+
// sub-build would leave that agreement to chance, and disagreement shows
609+
// up as `module X CRC mismatch`, not as a clear error.
610+
//
611+
// AFTER the std block, and that ordering is load-bearing: a rule may
612+
// `import std;` just as build.mcpp may, and it can only do so once the std
613+
// BMI exists and `stdFlags` names it. Compiling rules first — which is what
614+
// 2026.8.5.1 did — handed them an empty `stdFlags` and failed with
615+
// `module 'std' not found`.
616+
std::vector<fs::path> hostModuleObjects;
617+
for (auto const& [logical, ifacePath] : env.hostModules) {
618+
std::vector<std::string> use = moduleFlags;
619+
use.insert(use.end(), stdFlags.begin(), stdFlags.end());
620+
auto hm = build_host_module(bdir, hostCompiler, base, std_flag, tc,
621+
compileEnv, logical, ifacePath, use);
622+
if (!hm) return std::unexpected(hm.error());
623+
for (auto& f : hm->useFlags) {
624+
// GCC's marker is just `-fmodules`, already present when the
625+
// bundled module was built; repeating it is harmless but noisy.
626+
if (std::find(moduleFlags.begin(), moduleFlags.end(), f)
627+
== moduleFlags.end())
628+
moduleFlags.push_back(f);
629+
}
630+
hostModuleObjects.push_back(std::move(hm->object));
631+
}
632+
609633
// `-x c++` is required: the `.mcpp` extension is unknown to the compiler, so
610634
// without it the driver hands build.mcpp to the linker as a linker script.
611635
std::vector<std::string> compileArgv = { hostCompiler.string() };

src/build/prepare.cppm

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4026,6 +4026,40 @@ prepare_build(bool print_fingerprint,
40264026
if (!hit) continue;
40274027
auto rel = mcpp::manifest::resolve_lib_root_path(depPkg.manifest);
40284028
hostModulesByConsumer[0].emplace_back(canon, depPkg.root / rel);
4029+
4030+
// A build rule is BUILD-TIME ONLY. Registering the module
4031+
// is not enough: the package is still an ordinary node of
4032+
// the consumer's graph, so its interface was ALSO compiled
4033+
// as a normal library and linked into the target. That is
4034+
// wrong on its own terms — a rule has no business in the
4035+
// consumer's binary — and it made the feature nearly
4036+
// unusable, because in that second compile the bundled
4037+
// `mcpp` module does not exist: any rule that actually used
4038+
// the API it exists to wrap died with
4039+
// `fatal error: module 'mcpp' not found` (2026.8.5.1).
4040+
//
4041+
// Emptying the source globs is how a package is removed
4042+
// from the compile set here — the same mechanism the
4043+
// feature-gated-sources drop above uses. Resolution is
4044+
// untouched: the package still lands on disk, which is
4045+
// what `resolve_lib_root_path` just read.
4046+
//
4047+
// Guarded on the package being reached ONLY from the root's
4048+
// host-module edge. A package can legitimately be both a
4049+
// rule and a library — for something else in the graph, or
4050+
// for the root itself under a second spelling — and
4051+
// silently dropping its objects then would surface as an
4052+
// undefined reference far from here.
4053+
bool hostOnly = true;
4054+
for (auto const& e : dependencyEdges)
4055+
if (e.dependencyPackageIndex == d
4056+
&& e.consumerPackageIndex != 0) hostOnly = false;
4057+
if (hostOnly) {
4058+
auto& dm = packages[d].manifest;
4059+
dm.buildConfig.sources.clear();
4060+
dm.buildConfig.featureSources.clear();
4061+
dm.modules.sources.clear();
4062+
}
40294063
break;
40304064
}
40314065
}

src/version.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ import std;
3131

3232
export namespace mcpp {
3333

34-
inline constexpr std::string_view MCPP_VERSION = "2026.8.5.1";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.8.5.2";
3535

3636
} // namespace mcpp

0 commit comments

Comments
 (0)