Skip to content

add compat.catch2 — one package for 2.13.10 + 3.15.2 - #126

Merged
Sunrisepeak merged 2 commits into
mcpplibs:mainfrom
ZheFeng7110:add-compat-catch2
Jul 27, 2026
Merged

add compat.catch2 — one package for 2.13.10 + 3.15.2#126
Sunrisepeak merged 2 commits into
mcpplibs:mainfrom
ZheFeng7110:add-compat-catch2

Conversation

@ZheFeng7110

@ZheFeng7110 ZheFeng7110 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

把 Catch2 加进 mcpp-index。已从初版的两个包改为单个 compat.catch2,同时携带 2.13.10 与 3.15.2。

1. 为什么是一个包,不是两个

初版拆成 compat.catch2(v3)与 compat.catch2-v2(v2),理由是"v2→v3 布局完全改变,无法共用一个 mcpp 块"。前提对,结论不对 —— 两套 glob 互不相交:

glob 2.13.10 3.15.2
*/single_include
*/src/catch2/**/*.cpp 0 个匹配(v2 的 src/ 里只有 src/catch_with_main.cpp,不在 src/catch2/ 下) 107
catch2/catch_all.hpp

所以一个 mcpp 块可以承载并集,每个版本恰好点亮一半:v2 退化为 header-only + anchor TU,v3 是 106 TU 的静态库。匹配不到的 glob 不是错误,这正是并集安全的原因。最后一行还顺带给出一个编译期 major 判别式:__has_include(<catch2/catch_all.hpp>)

抛开可行性,两个包本身就是错的形态:

  • catch2-v2 把 major 塞进了 SPEC-001 身份留给原子 name 的那一段 —— 与 compat.opensslopenssl 改名要消除的是同一类问题;
  • 跨 major 的 semver 失效,消费者写不出 catch2 = "^3";
  • 这是一扇单向门:compat.catch2-v2 一旦随已发布 artifact 出去,再收回就是 breaking change。所以必须在合并前定。

与 mcpp#290 的关系

mcpp-community/mcpp#290(按版本区分构建规则)不是本 PR 的前置 —— 在 0.0.109 上今天就能跑。

Catch2 也不是 #290 论证时用的那种形态:那个 issue 的例子(llama.cpp b10069 vs b10107)共享约 95% 构建规则、只差几个源文件;catch2 v2/v3 除了样板(language/import_std/c_standard/targets/deps)什么都不共享,能合并纯粹是因为 glob 恰好不重叠。

而"恰好"正是薄弱点,也正是 #290 要消除的:不相交是两棵上游树的性质,描述符管不住。#290 落地后这里会收敛成显式的 ["2.x"] / ["3.x"] 块,隐式假设随之消失。在那之前,前提写在描述符头部,加版本时必须重核

2. main feature 原来是死的

初版 v3 的 features = ["main"] 会得到 undefined reference to 'main'。在 0.0.109 上逐一实测:

描述符形态 默认路径 features = ["main"]
glob + ! 取反,feature 指向被取反的路径 ok FAIL undefined reference to 'main'
glob 不取反,feature 指向被 glob 匹到的路径 FAIL multiple definition of 'main' ok
glob + 取反 + 再显式列一遍 ok FAIL undefined reference to 'main'
glob + ! 取反,feature 指向生成的 TU ok ok

两条机制:

  1. sources 里的 ! 取反是绝对排除,作用于最终源集合,feature 加不回来(即便之后再显式列一遍)。
  2. feature 选通按 sources字面条目匹配 —— 被 glob 匹到的文件根本不受选通,默认即编入。

所以 compat.gtest 的写法不能照搬:gtest_main.cc 是字面条目才让选通生效。现在 feature 编译一个生成的 TU,用 __has_include(<catch2/catch_all.hpp>) 分别取 v3(Catch::Session)与 v2(CATCH_CONFIG_MAIN)的写法。

代价:不复用上游 catch_main.cpp 意味着没有 LeakDetector 注册、没有 wmain 分支(只在 Windows _UNICODE 下可达)。已在描述符里记录。

3. 四个成员 —— 因为 feature 是按项目解析的

一个项目不能同时持有带 feature 和不带 feature 的同一依赖(与 asio-ssl / asio-module 同一约束),所以 2 个 major × {默认,feature} = 4 个:

成员 版本 请求 能抓到什么
catch2 3.15.2 —(自带 main()) 取反失效 → multiple definition of 'main'
catch2-main 3.15.2 features = ["main"] feature 失效 → undefined reference to 'main'
catch2-v2 2.13.10 —(自带 CATCH_CONFIG_MAIN) 并集串味;single_include 没解析上
catch2-v2-main 2.13.10 features = ["main"] __has_include 的 ELSE 分支(v3 成员只走 THEN)

初版在两个包上都声明了 main feature,覆盖 0 个 —— 这正是那个不工作的 feature 能绿着通过 CI 的原因。每个测试体都保留真断言,Catch2 退出时会打印断言数,所以"编译成空还通过"藏不住。

4. CN 镜像

两个版本已镜像到 gitcode mcpp-res/catch2,并重新下载校验:与上游逐字节一致(sha256 与大小均相同),所以单条 sha256 同时覆盖 GLOBAL 与 CN。

5. 本地验证(mcpp 0.0.109,gcc@16.1.0,linux-x86_64)

  • mcpp xpkg parse + tests/check_mirror_urls.lua:通过
  • 四个成员 mcpp test -p 全绿,各 2 assertions in 2 test cases(非空)
  • 负对照:把任一 -main 成员的 features = ["main"] 摘掉,均以 undefined reference to 'main' 失败 —— 证明入口确实由 feature 提供,而不是别的东西恰好补上了
  • 两个 tarball 分别从 GLOBAL 与 CN 重新拉取并 sha256 校验
  • 两个归档均不含 symlink,windows 解包路径安全

已知边界

  • 并集依赖"v2 无 src/catch2/"这一上游事实,已写进描述符注释,加版本时需重核;#290 落地后可迁移掉。
  • catch_user_config.hpp 只手写了两个必需的 value-define(其余全是 #cmakedefine,缺省即编译器探测)。升 v3 版本时需对照上游 .in,新增的必需 value-define 会在这里静默漏掉。

@ZheFeng7110

Copy link
Copy Markdown
Contributor Author

参考 mcpp-community/mcpp#290 的需求,如果能够支持按版本区分构建规则,可以将 catch2 和 catch2-v2 合并,下游使用更加便捷

ZheFeng7110 and others added 2 commits July 27, 2026 12:38
Catch2 is a modern C++ test framework.

- compat.catch2 (v3): C-source compat static library built from individual
  source files under src/catch2/. Includes generated catch_user_config.hpp
  (normally CMake-generated). Default main suppressed via
  -DCATCH_AMALGAMATED_CUSTOM_MAIN; `main` feature gates a generated
  default main TU (same pattern as compat.gtest).
- compat.catch2-v2 (v2): header-only, single-include via
  single_include/catch2/catch.hpp with anchor TU. `main` feature gates
  a generated TU with CATCH_CONFIG_MAIN.

Both use GitHub tag archive tarballs. No CN mirror yet (no mcpp-res
write access). Local `mcpp test -p catch2` and `mcpp test -p catch2-v2`
pass on mcpp 0.0.108 (x86_64-windows-msvc).
…ature

Collapse compat.catch2 + compat.catch2-v2 into a single `compat.catch2`
carrying 2.13.10 and 3.15.2, and repair the v3 `main` feature, which did
not work.

## One package

The two majors' globs are DISJOINT — v2 has no `src/catch2/` (its only
src/ file is `src/catch_with_main.cpp`) and v3 has no `single_include/`
— so one `mcpp` block can carry the UNION and let each version light up
exactly one half: v2 degenerates to header-only + anchor TU, v3 to a
106-TU static library. A glob that matches nothing is not an error,
which is what makes this safe.

Two packages was the wrong shape independent of that: `catch2-v2`
encodes a major into the atomic `name` segment SPEC-001 identity
reserves for the name (the same anti-pattern the compat.openssl →
openssl rename removed), and it breaks semver across the major — a
consumer cannot write `catch2 = "^3"`. It is also a one-way door: once
`compat.catch2-v2` ships in a published artifact, withdrawing it is a
breaking change.

mcpp-community/mcpp#290 (per-version build blocks) is NOT a prerequisite
— this works on 0.0.109 today — but it is the right destination: the
disjointness is a property of two upstream trees, not something the
descriptor can enforce. Recorded in the descriptor header as the premise
to re-check when adding a version.

## The `main` feature was dead

`features = ["main"]` on v3 produced `undefined reference to 'main'`.
Measured on 0.0.109:

  glob + `!` negation, feature → negated path   default ok  | feature FAIL
  glob, no negation,   feature → globbed path   default FAIL| feature ok
  glob + negation + explicit re-add             default ok  | feature FAIL
  glob + `!` negation, feature → GENERATED TU   default ok  | feature ok

Two mechanics: a `!` negation is an ABSOLUTE exclusion that a feature
cannot add back, and feature gating matches LITERAL `sources` entries —
a file pulled in by a glob is not gated at all. So compat.gtest's shape
does not transfer (gtest_main.cc is a literal entry). The feature now
compiles a generated TU that branches on
`__has_include(<catch2/catch_all.hpp>)` to pick the v3 or v2 spelling.

Cost: no LeakDetector registration and no wmain variant. Documented.

## Four members, because features are per-project

One project cannot hold catch2 both with and without `main` (same
constraint as asio-ssl vs asio-module), so 2 majors × {default, feature}
= 4 members. The previous revision declared the feature on both packages
and covered NEITHER — which is exactly why a non-functional feature
passed CI.

## CN mirror

Both versions mirrored to gitcode mcpp-res/catch2 and re-downloaded:
byte-identical to upstream (same sha256, same size), so the single
recorded sha256 covers either source.

## Verification (mcpp 0.0.109, gcc@16.1.0, linux-x86_64)

- `mcpp xpkg parse` + `tests/check_mirror_urls.lua`: OK
- all 4 members `mcpp test -p`: ok, 2 assertions each (not empty)
- negative controls: dropping `features = ["main"]` from either -main
  member fails with `undefined reference to 'main'`, so the feature is
  demonstrably what supplies the entry point
- both tarballs re-fetched from GLOBAL and CN and sha256-checked
@Sunrisepeak Sunrisepeak changed the title add compat.catch2 v3.15.2 and compat.catch2-v2 v2.13.10 add compat.catch2 — one package for 2.13.10 + 3.15.2 Jul 27, 2026
@Sunrisepeak
Sunrisepeak merged commit 4d7e5bc into mcpplibs:main Jul 27, 2026
5 checks passed
@ZheFeng7110
ZheFeng7110 deleted the add-compat-catch2 branch July 27, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants