From 5b3d23a7fef3560f75e653c593b76b3046e50a44 Mon Sep 17 00:00:00 2001 From: Zhe Feng Date: Sun, 26 Jul 2026 22:30:10 +0800 Subject: [PATCH 1/2] add compat.catch2 v3.15.2 and compat.catch2-v2 v2.13.10 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). --- .agents/docs/2026-07-26-add-catch2-plan.md | 109 +++++++++++++++++++++ README.md | 1 + mcpp.toml | 2 + pkgs/c/compat.catch2-v2.lua | 69 +++++++++++++ pkgs/c/compat.catch2.lua | 82 ++++++++++++++++ tests/examples/catch2-v2/mcpp.toml | 12 +++ tests/examples/catch2-v2/tests/main.cpp | 14 +++ tests/examples/catch2/mcpp.toml | 12 +++ tests/examples/catch2/tests/main.cpp | 17 ++++ 9 files changed, 318 insertions(+) create mode 100644 .agents/docs/2026-07-26-add-catch2-plan.md create mode 100644 pkgs/c/compat.catch2-v2.lua create mode 100644 pkgs/c/compat.catch2.lua create mode 100644 tests/examples/catch2-v2/mcpp.toml create mode 100644 tests/examples/catch2-v2/tests/main.cpp create mode 100644 tests/examples/catch2/mcpp.toml create mode 100644 tests/examples/catch2/tests/main.cpp diff --git a/.agents/docs/2026-07-26-add-catch2-plan.md b/.agents/docs/2026-07-26-add-catch2-plan.md new file mode 100644 index 0000000..28f21ea --- /dev/null +++ b/.agents/docs/2026-07-26-add-catch2-plan.md @@ -0,0 +1,109 @@ +# Design doc: add Catch2 to mcpp-index + +Date: 2026-07-26 + +## Source + +- Repo: [catchorg/Catch2](https://github.com/catchorg/Catch2) +- License: BSL-1.0 (Boost Software License 1.0) +- Upstream release model: amalgamated files published as GitHub release assets + +Catch2 has two major versions with incompatible APIs and source layouts: + +| Version | Source layout | Shape | +|---------|--------------|-------| +| v2.13.10 | `single_include/catch2/catch.hpp` (single amalgamated header) | **header-only** | +| v3.15.2 | `src/catch2/**/*.{hpp,cpp}` (individual source files) | **C-source compat (static lib)** | + +The v2→v3 jump changed the repo layout completely (`single_include/` → `src/catch2/`), +so the two versions cannot share a single `mcpp` block. Two separate packages are used: + +- `compat.catch2` — v3, C-source compat (static library from individual source files) +- `compat.catch2-v2` — v2, header-only + +## Package shape decision + +### compat.catch2 (v3) + +- **Shape**: C-source compat static library (same as compat.cjson / compat.gtest) +- **Sources**: `*/src/catch2/**/*.cpp` — all Catch2 implementation files (107 TUs) +- **include_dirs**: `{ "*/src", "mcpp_generated" }` — exposes `` + etc. from the source tree, and `catch2/catch_user_config.hpp` from generated files +- **catch_user_config.hpp**: materialised via `generated_files` (normally + CMake-generated; provides `CATCH_CONFIG_DEFAULT_REPORTER` and + `CATCH_CONFIG_CONSOLE_WIDTH`). Both value-defines use `#ifndef` guards so + users can override them via `cxxflags = ["-DCATCH_CONFIG_DEFAULT_REPORTER=xml"]` + in their own `mcpp.toml`. Other boolean toggles (e.g. `CATCH_CONFIG_WCHAR`) + are auto-detected by `catch_compiler_capabilities.hpp` and can likewise be + forced on/off via `-D` flags. +- **catch_main.cpp**: provides a default `main()`. Excluded from the default + source set via `!` negation (`"!*/src/catch2/internal/catch_main.cpp"`); + only compiled when `features = ["main"]` is requested (same pattern as + `compat.gtest`'s `main` feature gating `gtest_main.cc`) + +### compat.catch2-v2 (v2) + +- **Shape**: header-only (same as compat.eigen / compat.opengl) +- **include_dirs**: `{ "*/single_include" }` — consumers `#include ` +- **Anchor**: trivial `.c` TU (`mcpp_generated/catch2_v2_anchor.c`) to give mcpp + a buildable lib target +- **`main` feature**: gates a generated `main.cpp` that defines + `CATCH_CONFIG_MAIN` and includes ``, providing a ready-made + `main()`. Excluded by default + +## Download URLs + +Both packages use GitHub tag archive tarballs (not individual release assets) +because xpm supports one URL per version. + +| Package | Version | GitHub archive URL | SHA256 | +|---------|---------|--------------------|--------| +| compat.catch2 | 3.15.2 | `https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz` | `acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420` | +| compat.catch2-v2 | 2.13.10 | `https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz` | `d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943` | + +### Individual release asset SHAs (for reference) + +| File | Release URL | SHA256 | +|------|------------|--------| +| catch.hpp (v2.13.10) | `https://github.com/catchorg/Catch2/releases/download/v2.13.10/catch.hpp` | `3725c0f0a75f376a5005dde31ead0feb8f7da7507644c201b814443de8355170` | +| catch_amalgamated.cpp (v3.15.2) | `https://github.com/catchorg/Catch2/releases/download/v3.15.2/catch_amalgamated.cpp` | `1ec0b0c0d6133f76fea521295be3e69e3aa5464ab92972897414ca59b7f674d5` | +| catch_amalgamated.hpp (v3.15.2) | `https://github.com/catchorg/Catch2/releases/download/v3.15.2/catch_amalgamated.hpp` | `f0573f46ac989896a20c524085307b633f01c8e1cdbbe6d9b39f63827c2d6c5e` | + +## CN mirror + +Not configured yet — no `mcpp-res` write access. Using plain-string upstream URLs +(no `{ GLOBAL=…, CN=… }` table). CN users will fall back to the upstream source. +Mirrors can be added later by a maintainer. + +## Test examples + +- **tests/examples/catch2/**: v3 static library build. Includes + ``, provides own `main()` using + `Catch::Session().run()`, runs two `TEST_CASE`s. +- **tests/examples/catch2-v2/**: v2 header-only. Defines `CATCH_CONFIG_MAIN` to + get a built-in main, includes ``, runs two `TEST_CASE`s + within a single TU. + +## Verification + +- SHA256 computed twice (confirmed stable) via `sha256sum` on the codeload tarballs +- File paths confirmed by `tar -tzf` against tag archives +- Both packages pass `mcpp xpkg parse` lint +- `mcpp test -p catch2` → **test result ok** (2 assertions in 2 test cases) +- `mcpp test -p catch2-v2` → **test result ok** (2 assertions in 2 test cases) +- Tested with mcpp 0.0.108 (matching CI `MCPP_VERSION`) on x86_64-windows-msvc + +## Notes + +- Catch2 v3 static library requires `catch_user_config.hpp` (normally + CMake-generated). A minimal version is materialised via `generated_files` at + `mcpp_generated/catch2/catch_user_config.hpp` with `#ifndef` guards on the + two value-defines (`CATCH_CONFIG_DEFAULT_REPORTER` and + `CATCH_CONFIG_CONSOLE_WIDTH`), allowing users to override them via `-D` flags. +- `catch_main.cpp` is gated via `!` negation in `sources` + feature listing, + following the exact `compat.gtest` pattern. No `-DCATCH_AMALGAMATED_CUSTOM_MAIN` + needed. +- Catch2 v3 requires C++14 or later; v2 requires C++11; both compatible with + mcpp's `language = "c++23"`. +- No `c_standard` needed for v3 (pure C++); anchor for v2 uses + `c_standard = "c11"`. diff --git a/README.md b/README.md index 9344d42..cb7d7e1 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ mcpp self config --mirror CN # 切换至国内镜像,默认使用 GLOBAL 上 | 原生模块库(Form A) | [`mcpplibs.xpkg`](pkgs/x/xpkg.lua) · [`mcpplibs.tinyhttps`](pkgs/t/tinyhttps.lua) · [`tensorvia-cpu`](pkgs/t/tensorvia-cpu.lua) · [`ffmpeg`](pkgs/f/ffmpeg.lua)(模块层,源码经 `compat.ffmpeg` 直编) · [`opencv`](pkgs/o/opencv.lua)(单仓库:模块层与 OpenCV 5 全源码构建同在包内,索引侧只留本描述符) | | C 源码 compat(含 `features`) | [`compat.cjson`](pkgs/c/compat.cjson.lua) · [`compat.zlib`](pkgs/c/compat.zlib.lua) | | header-only(含 `features`) | [`compat.eigen`](pkgs/c/compat.eigen.lua) | +| 单包多 major(形态随版本切换) | [`compat.catch2`](pkgs/c/compat.catch2.lua)(3.x 编 `src/catch2/` 出静态库;2.x 走 `single_include/` header-only) | | 外部构建系统(`install()` 从源码构建) | [`compat.openblas`](pkgs/c/compat.openblas.lua)(Make) · [`compat.openssl`](pkgs/c/compat.openssl.lua)(Perl Configure + Make,静态 libssl/libcrypto) | | 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | | C++23 module wrapper | [`nlohmann.json`](pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](pkgs/m/marzer.tomlplusplus.lua) | diff --git a/mcpp.toml b/mcpp.toml index 90fd673..79050f8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -11,6 +11,8 @@ members = [ "tests/examples/asio-module", "tests/examples/asio-ssl", "tests/examples/build-mcpp", + "tests/examples/catch2", + "tests/examples/catch2-v2", "tests/examples/cjson", "tests/examples/core", "tests/examples/eigen", diff --git a/pkgs/c/compat.catch2-v2.lua b/pkgs/c/compat.catch2-v2.lua new file mode 100644 index 0000000..9424aad --- /dev/null +++ b/pkgs/c/compat.catch2-v2.lua @@ -0,0 +1,69 @@ +-- Form B inline descriptor for Catch2 v2 — a modern C++ test framework. +-- v2 is HEADER-ONLY: the amalgamated single header (catch.hpp) contains +-- the full implementation. Define `CATCH_CONFIG_MAIN` before including it +-- in exactly one TU to get a ready-made main(), or define +-- `CATCH_CONFIG_RUNNER` to write your own. +-- +-- This descriptor exposes the header via include_dirs and carries a tiny +-- anchor TU so mcpp always has a buildable lib target (same shape as +-- compat.eigen / compat.opengl / compat.khrplatform). +-- +-- Features (sources-only gate): +-- `main` — compiles a generated TU that defines CATCH_CONFIG_MAIN and +-- includes , providing a default main(). Excluded by +-- default; request features = ["main"] for a no-config entry point. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` +-- absorbs the GitHub tarball's `Catch2-/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "catch2-v2", + description = "A modern, C++-native test framework for unit-tests, TDD and BDD — v2 (C++11, header-only)", + licenses = {"BSL-1.0"}, + repo = "https://github.com/catchorg/Catch2", + type = "package", + + xpm = { + linux = { + ["2.13.10"] = { + url = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", + sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", + }, + }, + macosx = { + ["2.13.10"] = { + url = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", + sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", + }, + }, + windows = { + ["2.13.10"] = { + url = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", + sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/single_include" }, + generated_files = { + ["mcpp_generated/catch2_v2_anchor.c"] = [==[ +int mcpp_compat_catch2_v2_anchor(void) { return 0; } +]==], + ["mcpp_generated/catch2_v2_main.cpp"] = [==[ +#define CATCH_CONFIG_MAIN +#include +]==], + }, + sources = { "mcpp_generated/catch2_v2_anchor.c" }, + targets = { ["catch2-v2"] = { kind = "lib" } }, + features = { + ["main"] = { sources = { "mcpp_generated/catch2_v2_main.cpp" } }, + }, + deps = { }, + }, +} diff --git a/pkgs/c/compat.catch2.lua b/pkgs/c/compat.catch2.lua new file mode 100644 index 0000000..decacb4 --- /dev/null +++ b/pkgs/c/compat.catch2.lua @@ -0,0 +1,82 @@ +-- Form B inline descriptor for Catch2 v3 — a modern C++ test framework. +-- Built as a static library from the individual source files under +-- src/catch2/ (NOT the amalgamated distribution). Consumers include +-- (or more specific headers) and provide their +-- own main() using Catch::Session, or request the `main` feature for a +-- ready-made default entry point. +-- +-- Features (sources-only gate): +-- `main` — compiles catch_main.cpp (upstream's default main via +-- Catch::Session). Excluded from the default source set via `!` +-- negation; only compiled when features = ["main"] is requested +-- (same pattern as compat.gtest's `main` feature). +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` +-- absorbs the GitHub tarball's `Catch2-/` wrap layer. +package = { + spec = "1", + namespace = "compat", + name = "catch2", + description = "A modern, C++-native test framework for unit-tests, TDD and BDD — v3 (C++14+)", + licenses = {"BSL-1.0"}, + repo = "https://github.com/catchorg/Catch2", + type = "package", + + xpm = { + linux = { + ["3.15.2"] = { + url = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + sha256 = "acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420", + }, + }, + macosx = { + ["3.15.2"] = { + url = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + sha256 = "acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420", + }, + }, + windows = { + ["3.15.2"] = { + url = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + sha256 = "acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + -- expose src/ so that #include resolves to + -- Catch2-/src/catch2/… + -- mcpp_generated/ provides catch2/catch_user_config.hpp (normally + -- CMake-generated; we materialise a reasonable default here). + include_dirs = { "*/src", "mcpp_generated" }, + -- catch_main.cpp provides a default main(). Excluded via `!` + -- negation from the default source set; only compiled when + -- features = ["main"] is requested (same pattern as compat.gtest). + generated_files = { + ["mcpp_generated/catch2/catch_user_config.hpp"] = [==[ +#ifndef CATCH_USER_CONFIG_HPP_INCLUDED +#define CATCH_USER_CONFIG_HPP_INCLUDED + +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +#endif +]==], + }, + sources = { + "*/src/catch2/**/*.cpp", + "!*/src/catch2/internal/catch_main.cpp", + }, + targets = { ["catch2"] = { kind = "lib" } }, + features = { + ["main"] = { sources = { "*/src/catch2/internal/catch_main.cpp" } }, + }, + deps = { }, + }, +} diff --git a/tests/examples/catch2-v2/mcpp.toml b/tests/examples/catch2-v2/mcpp.toml new file mode 100644 index 0000000..45c36b2 --- /dev/null +++ b/tests/examples/catch2-v2/mcpp.toml @@ -0,0 +1,12 @@ +# Catch2 v2 test project: depends on compat.catch2-v2 (header-only via this +# repo's own index) and asserts behavior under `mcpp test`. +# Part of the mcpp-index self-referential workspace. +[package] +name = "catch2-v2-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +catch2-v2 = "2.13.10" diff --git a/tests/examples/catch2-v2/tests/main.cpp b/tests/examples/catch2-v2/tests/main.cpp new file mode 100644 index 0000000..00290c1 --- /dev/null +++ b/tests/examples/catch2-v2/tests/main.cpp @@ -0,0 +1,14 @@ +#define CATCH_CONFIG_MAIN +#include + +static unsigned int factorial(unsigned int n) { + return n <= 1 ? 1 : n * factorial(n - 1); +} + +TEST_CASE("factorial of 5 is 120", "[math]") { + REQUIRE(factorial(5) == 120); +} + +TEST_CASE("factorial of 0 is 1", "[math]") { + REQUIRE(factorial(0) == 1); +} diff --git a/tests/examples/catch2/mcpp.toml b/tests/examples/catch2/mcpp.toml new file mode 100644 index 0000000..bea4fb8 --- /dev/null +++ b/tests/examples/catch2/mcpp.toml @@ -0,0 +1,12 @@ +# Catch2 v3 test project: depends on compat.catch2 (compiled from amalgamated +# source via this repo's own index) and asserts behavior under `mcpp test`. +# Part of the mcpp-index self-referential workspace. +[package] +name = "catch2-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +catch2 = "3.15.2" diff --git a/tests/examples/catch2/tests/main.cpp b/tests/examples/catch2/tests/main.cpp new file mode 100644 index 0000000..9798b12 --- /dev/null +++ b/tests/examples/catch2/tests/main.cpp @@ -0,0 +1,17 @@ +#include + +static unsigned int factorial(unsigned int n) { + return n <= 1 ? 1 : n * factorial(n - 1); +} + +TEST_CASE("factorial of 5 is 120", "[math]") { + REQUIRE(factorial(5) == 120); +} + +TEST_CASE("factorial of 0 is 1", "[math]") { + REQUIRE(factorial(0) == 1); +} + +int main(int argc, char* argv[]) { + return Catch::Session().run(argc, argv); +} From 0cc2fb2a469b9c336167f23a82cd7445813ee4d7 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Mon, 27 Jul 2026 12:45:51 +0800 Subject: [PATCH 2/2] refactor(catch2): one package for both majors, fix the dead `main` feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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()` 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 --- .agents/docs/2026-07-26-add-catch2-plan.md | 252 ++++++++++++------- mcpp.toml | 2 + pkgs/c/compat.catch2-v2.lua | 69 ----- pkgs/c/compat.catch2.lua | 148 +++++++++-- tests/examples/catch2-main/mcpp.toml | 16 ++ tests/examples/catch2-main/tests/main.cpp | 16 ++ tests/examples/catch2-v2-main/mcpp.toml | 13 + tests/examples/catch2-v2-main/tests/main.cpp | 16 ++ tests/examples/catch2-v2/mcpp.toml | 13 +- tests/examples/catch2/mcpp.toml | 11 +- 10 files changed, 359 insertions(+), 197 deletions(-) delete mode 100644 pkgs/c/compat.catch2-v2.lua create mode 100644 tests/examples/catch2-main/mcpp.toml create mode 100644 tests/examples/catch2-main/tests/main.cpp create mode 100644 tests/examples/catch2-v2-main/mcpp.toml create mode 100644 tests/examples/catch2-v2-main/tests/main.cpp diff --git a/.agents/docs/2026-07-26-add-catch2-plan.md b/.agents/docs/2026-07-26-add-catch2-plan.md index 28f21ea..f085937 100644 --- a/.agents/docs/2026-07-26-add-catch2-plan.md +++ b/.agents/docs/2026-07-26-add-catch2-plan.md @@ -1,109 +1,177 @@ # Design doc: add Catch2 to mcpp-index -Date: 2026-07-26 +Date: 2026-07-26 (revised 2026-07-27 — single package, see §"Revision") ## Source - Repo: [catchorg/Catch2](https://github.com/catchorg/Catch2) - License: BSL-1.0 (Boost Software License 1.0) -- Upstream release model: amalgamated files published as GitHub release assets Catch2 has two major versions with incompatible APIs and source layouts: -| Version | Source layout | Shape | -|---------|--------------|-------| -| v2.13.10 | `single_include/catch2/catch.hpp` (single amalgamated header) | **header-only** | -| v3.15.2 | `src/catch2/**/*.{hpp,cpp}` (individual source files) | **C-source compat (static lib)** | +| Version | Source layout | Resulting shape | +|---------|--------------|-----------------| +| 2.13.10 | `single_include/catch2/catch.hpp` (single amalgamated header) | **header-only** | +| 3.15.2 | `src/catch2/**/*.{hpp,cpp}` (individual source files) | **static library** | + +## Revision: one package, not two + +The first draft of this design shipped **two** packages, `compat.catch2` (v3) +and `compat.catch2-v2` (v2), on the reasoning that "the v2→v3 jump changed the +repo layout completely, so the two versions cannot share a single `mcpp` +block." + +The premise is right; the conclusion was not. The two layouts' globs are +**disjoint**, so a single `mcpp` block can carry the **union** of both and let +each version light up exactly one half: + +| glob | 2.13.10 | 3.15.2 | +|---|---|---| +| `*/single_include` | exists | absent | +| `*/src/catch2/**/*.cpp` | **0 matches** — v2's only `src/` file is `src/catch_with_main.cpp`, *not* under `src/catch2/` | 107 | +| `catch2/catch_all.hpp` | absent | exists | + +An `include_dirs` entry whose glob matches nothing is not an error, and a +`sources` glob that matches nothing simply contributes no TUs — which is what +makes the union safe. At 2.13.10 the package degenerates to header-only +(`single_include` + an anchor TU); at 3.15.2 it is a 106-TU static library. + +The last row doubles as a compile-time major discriminator: +`__has_include()`. + +### Why one package is the better shape regardless + +- **A version does not belong in `name`.** `catch2-v2` smuggles a major into + the atomic name segment that SPEC-001 identity reserves for the name alone — + the same anti-pattern the `compat.openssl` → `openssl` rename removed. +- **Semver stops working across the split.** With two packages a consumer + cannot write `catch2 = "^3"`, and version resolution cannot see v2 and v3 as + the same library. +- **Discovery.** One library should not answer to two package names. +- **It is a one-way door.** Once `compat.catch2-v2` ships in a published index + artifact, withdrawing it is a breaking change. The decision had to be made + before merge, not after. + +### Relationship to mcpp#290 + +[mcpp-community/mcpp#290](https://github.com/mcpp-community/mcpp/issues/290) +asks for per-version `mcpp` build blocks. It is **not** a prerequisite here — +the merge works on mcpp 0.0.109 today. + +Nor is Catch2 the case #290 argues from. That issue's motivating example +(llama.cpp b10069 vs b10107) shares ~95% of its build rules and differs in a +few source files. Catch2 v2/v3 share essentially *nothing* but boilerplate +(`language`, `import_std`, `c_standard`, `targets`, `deps`); they merge only +because their globs happen not to overlap. + +That "happen not to" is the weak point, and it is exactly what #290 would fix: +the disjointness is a property of two upstream trees, not something this +descriptor can enforce. When #290 lands, this collapses into explicit +`["2.x"]` / `["3.x"]` blocks and the implicit assumption disappears. Until +then the premise is documented in the descriptor header and must be re-checked +whenever a version is added. + +## Package shape + +- **Sources**: anchor TU + `*/src/catch2/**/*.cpp` minus + `!*/src/catch2/internal/catch_main.cpp` (107 files, 106 compiled). +- **include_dirs**: `{ "*/single_include", "*/src", "mcpp_generated" }`. +- **Anchor** (`mcpp_generated/catch2_anchor.c`): required at v2, where the + sources glob matches nothing and a lib target still needs a TU. Same shape + as compat.eigen / compat.khrplatform. Inert at v3. +- **catch_user_config.hpp**: materialised via `generated_files` (upstream ships + only `catch_user_config.hpp.in` and lets CMake fill it). Only the two VALUE + defines are mandatory — `CATCH_CONFIG_DEFAULT_REPORTER` and + `CATCH_CONFIG_CONSOLE_WIDTH`; every other entry in the `.in` is a + `#cmakedefine`, i.e. absent means "use the compiler-detected default". Both + are `#ifndef`-guarded so a consumer can override via `-D`. v2 never includes + this file. **Re-read the upstream `.in` when bumping v3** — a newly added + mandatory value-define would break silently here. + +## The `main` feature + +`features = ["main"]` compiles a **generated** TU that supplies a default entry +point, branching on `__has_include()` to pick the v3 +(`Catch::Session`) or v2 (`CATCH_CONFIG_MAIN`) spelling. + +It deliberately does **not** point at upstream's +`src/catch2/internal/catch_main.cpp`. That was the first draft's approach and +it does not work. Measured on mcpp 0.0.109: + +| descriptor form | default path | `features = ["main"]` | +|---|---|---| +| glob + `!` negation, feature → the negated path | ok | **FAIL** `undefined reference to 'main'` | +| glob, no negation, feature → the globbed path | **FAIL** `multiple definition of 'main'` | ok | +| glob + `!` negation + explicit re-add, feature → that path | ok | **FAIL** `undefined reference to 'main'` | +| glob + `!` negation, feature → **generated TU** | ok | ok | + +Two mechanics behind that table: + +1. A `!` negation in `sources` is an **absolute** exclusion applied to the + final source set. A feature cannot add the path back — not even if the path + is also listed explicitly afterwards. +2. Feature gating matches **literal** `sources` entries. A file pulled in by a + glob is not gated at all, so it lands in the default build. + +Together these mean the `compat.gtest` pattern does **not** transfer: +`gtest_main.cc` is a literal `sources` entry, so listing it under a feature +gates it. Under a glob the same idea silently breaks in one direction or the +other. A generated TU sidesteps the conflict. + +Cost of not reusing upstream's file: no `LeakDetector` registration (a Windows +CRT-debug nicety) and no `wmain` variant (reachable only under `_UNICODE` on +Windows). Consumers needing either write their own `main()`. + +## Why this needs four workspace members + +Features are resolved **per consuming project**, so one project cannot hold +`catch2` both with and without `main` (same constraint that forces `asio-ssl` +to be separate from `asio-module`). Two majors × {default, `main` feature} = 4: + +| member | version | requests | what it would catch | +|---|---|---|---| +| `catch2` | 3.15.2 | — (own `main()`) | negation stops working → `multiple definition of 'main'` | +| `catch2-main` | 3.15.2 | `features = ["main"]` | feature stops gating the TU in → `undefined reference to 'main'` | +| `catch2-v2` | 2.13.10 | — (own `CATCH_CONFIG_MAIN`) | union bleeds across versions; `single_include` unresolved | +| `catch2-v2-main` | 2.13.10 | `features = ["main"]` | the `__has_include` ELSE branch (v3 members only take THEN) | + +The first draft declared a `main` feature on both packages and covered +**neither** — which is precisely why the broken feature passed CI. Every test +body keeps a real `REQUIRE`, and Catch2 prints its assertion count on exit, so +a TU that compiles to nothing cannot pass quietly. + +## Download URLs and mirrors + +GitHub tag archive tarballs (not the individual amalgamated release assets): +xpm takes one URL per version, and the tag archive is the only form that +carries both layouts. Neither archive contains symlinks, so the Windows +extraction path is safe. + +| Version | GLOBAL | CN | SHA256 | +|---|---|---|---| +| 2.13.10 | `github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz` | `gitcode.com/mcpp-res/catch2/releases/download/2.13.10/catch2-2.13.10.tar.gz` | `d54a712b…9943` | +| 3.15.2 | `github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz` | `gitcode.com/mcpp-res/catch2/releases/download/3.15.2/catch2-3.15.2.tar.gz` | `acfae120…0420` | + +CN assets were uploaded to the `mcpp-res` gitcode org and re-downloaded: both +are byte-identical to the upstream archives (same sha256, same size), so the +single recorded `sha256` covers either source. -The v2→v3 jump changed the repo layout completely (`single_include/` → `src/catch2/`), -so the two versions cannot share a single `mcpp` block. Two separate packages are used: - -- `compat.catch2` — v3, C-source compat (static library from individual source files) -- `compat.catch2-v2` — v2, header-only - -## Package shape decision - -### compat.catch2 (v3) - -- **Shape**: C-source compat static library (same as compat.cjson / compat.gtest) -- **Sources**: `*/src/catch2/**/*.cpp` — all Catch2 implementation files (107 TUs) -- **include_dirs**: `{ "*/src", "mcpp_generated" }` — exposes `` - etc. from the source tree, and `catch2/catch_user_config.hpp` from generated files -- **catch_user_config.hpp**: materialised via `generated_files` (normally - CMake-generated; provides `CATCH_CONFIG_DEFAULT_REPORTER` and - `CATCH_CONFIG_CONSOLE_WIDTH`). Both value-defines use `#ifndef` guards so - users can override them via `cxxflags = ["-DCATCH_CONFIG_DEFAULT_REPORTER=xml"]` - in their own `mcpp.toml`. Other boolean toggles (e.g. `CATCH_CONFIG_WCHAR`) - are auto-detected by `catch_compiler_capabilities.hpp` and can likewise be - forced on/off via `-D` flags. -- **catch_main.cpp**: provides a default `main()`. Excluded from the default - source set via `!` negation (`"!*/src/catch2/internal/catch_main.cpp"`); - only compiled when `features = ["main"]` is requested (same pattern as - `compat.gtest`'s `main` feature gating `gtest_main.cc`) - -### compat.catch2-v2 (v2) - -- **Shape**: header-only (same as compat.eigen / compat.opengl) -- **include_dirs**: `{ "*/single_include" }` — consumers `#include ` -- **Anchor**: trivial `.c` TU (`mcpp_generated/catch2_v2_anchor.c`) to give mcpp - a buildable lib target -- **`main` feature**: gates a generated `main.cpp` that defines - `CATCH_CONFIG_MAIN` and includes ``, providing a ready-made - `main()`. Excluded by default - -## Download URLs - -Both packages use GitHub tag archive tarballs (not individual release assets) -because xpm supports one URL per version. - -| Package | Version | GitHub archive URL | SHA256 | -|---------|---------|--------------------|--------| -| compat.catch2 | 3.15.2 | `https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz` | `acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420` | -| compat.catch2-v2 | 2.13.10 | `https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz` | `d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943` | - -### Individual release asset SHAs (for reference) - -| File | Release URL | SHA256 | -|------|------------|--------| -| catch.hpp (v2.13.10) | `https://github.com/catchorg/Catch2/releases/download/v2.13.10/catch.hpp` | `3725c0f0a75f376a5005dde31ead0feb8f7da7507644c201b814443de8355170` | -| catch_amalgamated.cpp (v3.15.2) | `https://github.com/catchorg/Catch2/releases/download/v3.15.2/catch_amalgamated.cpp` | `1ec0b0c0d6133f76fea521295be3e69e3aa5464ab92972897414ca59b7f674d5` | -| catch_amalgamated.hpp (v3.15.2) | `https://github.com/catchorg/Catch2/releases/download/v3.15.2/catch_amalgamated.hpp` | `f0573f46ac989896a20c524085307b633f01c8e1cdbbe6d9b39f63827c2d6c5e` | - -## CN mirror - -Not configured yet — no `mcpp-res` write access. Using plain-string upstream URLs -(no `{ GLOBAL=…, CN=… }` table). CN users will fall back to the upstream source. -Mirrors can be added later by a maintainer. - -## Test examples +## Verification -- **tests/examples/catch2/**: v3 static library build. Includes - ``, provides own `main()` using - `Catch::Session().run()`, runs two `TEST_CASE`s. -- **tests/examples/catch2-v2/**: v2 header-only. Defines `CATCH_CONFIG_MAIN` to - get a built-in main, includes ``, runs two `TEST_CASE`s - within a single TU. +Local, mcpp 0.0.109 (matching CI `MCPP_VERSION`), gcc@16.1.0, linux-x86_64: -## Verification +- `mcpp xpkg parse` on the descriptor: OK +- `tests/check_mirror_urls.lua`: OK +- All four members via `mcpp test -p`: ok +- **Negative controls** (not committed): dropping `features = ["main"]` from + each `-main` member fails with `undefined reference to 'main'`, confirming + the feature is what supplies the entry point rather than something else. +- Both tarballs re-downloaded from GLOBAL and CN and sha256-checked. -- SHA256 computed twice (confirmed stable) via `sha256sum` on the codeload tarballs -- File paths confirmed by `tar -tzf` against tag archives -- Both packages pass `mcpp xpkg parse` lint -- `mcpp test -p catch2` → **test result ok** (2 assertions in 2 test cases) -- `mcpp test -p catch2-v2` → **test result ok** (2 assertions in 2 test cases) -- Tested with mcpp 0.0.108 (matching CI `MCPP_VERSION`) on x86_64-windows-msvc +CI covers linux / macOS / windows. ## Notes -- Catch2 v3 static library requires `catch_user_config.hpp` (normally - CMake-generated). A minimal version is materialised via `generated_files` at - `mcpp_generated/catch2/catch_user_config.hpp` with `#ifndef` guards on the - two value-defines (`CATCH_CONFIG_DEFAULT_REPORTER` and - `CATCH_CONFIG_CONSOLE_WIDTH`), allowing users to override them via `-D` flags. -- `catch_main.cpp` is gated via `!` negation in `sources` + feature listing, - following the exact `compat.gtest` pattern. No `-DCATCH_AMALGAMATED_CUSTOM_MAIN` - needed. -- Catch2 v3 requires C++14 or later; v2 requires C++11; both compatible with - mcpp's `language = "c++23"`. -- No `c_standard` needed for v3 (pure C++); anchor for v2 uses - `c_standard = "c11"`. +- Catch2 v3 requires C++14, v2 requires C++11; both build under + `language = "c++23"`. +- `c_standard = "c11"` is for the anchor TU. diff --git a/mcpp.toml b/mcpp.toml index 79050f8..d5a3ad0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -12,7 +12,9 @@ members = [ "tests/examples/asio-ssl", "tests/examples/build-mcpp", "tests/examples/catch2", + "tests/examples/catch2-main", "tests/examples/catch2-v2", + "tests/examples/catch2-v2-main", "tests/examples/cjson", "tests/examples/core", "tests/examples/eigen", diff --git a/pkgs/c/compat.catch2-v2.lua b/pkgs/c/compat.catch2-v2.lua deleted file mode 100644 index 9424aad..0000000 --- a/pkgs/c/compat.catch2-v2.lua +++ /dev/null @@ -1,69 +0,0 @@ --- Form B inline descriptor for Catch2 v2 — a modern C++ test framework. --- v2 is HEADER-ONLY: the amalgamated single header (catch.hpp) contains --- the full implementation. Define `CATCH_CONFIG_MAIN` before including it --- in exactly one TU to get a ready-made main(), or define --- `CATCH_CONFIG_RUNNER` to write your own. --- --- This descriptor exposes the header via include_dirs and carries a tiny --- anchor TU so mcpp always has a buildable lib target (same shape as --- compat.eigen / compat.opengl / compat.khrplatform). --- --- Features (sources-only gate): --- `main` — compiles a generated TU that defines CATCH_CONFIG_MAIN and --- includes , providing a default main(). Excluded by --- default; request features = ["main"] for a no-config entry point. --- --- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` --- absorbs the GitHub tarball's `Catch2-/` wrap layer. -package = { - spec = "1", - namespace = "compat", - name = "catch2-v2", - description = "A modern, C++-native test framework for unit-tests, TDD and BDD — v2 (C++11, header-only)", - licenses = {"BSL-1.0"}, - repo = "https://github.com/catchorg/Catch2", - type = "package", - - xpm = { - linux = { - ["2.13.10"] = { - url = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", - sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", - }, - }, - macosx = { - ["2.13.10"] = { - url = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", - sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", - }, - }, - windows = { - ["2.13.10"] = { - url = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", - sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", - }, - }, - }, - - mcpp = { - language = "c++23", - import_std = false, - c_standard = "c11", - include_dirs = { "*/single_include" }, - generated_files = { - ["mcpp_generated/catch2_v2_anchor.c"] = [==[ -int mcpp_compat_catch2_v2_anchor(void) { return 0; } -]==], - ["mcpp_generated/catch2_v2_main.cpp"] = [==[ -#define CATCH_CONFIG_MAIN -#include -]==], - }, - sources = { "mcpp_generated/catch2_v2_anchor.c" }, - targets = { ["catch2-v2"] = { kind = "lib" } }, - features = { - ["main"] = { sources = { "mcpp_generated/catch2_v2_main.cpp" } }, - }, - deps = { }, - }, -} diff --git a/pkgs/c/compat.catch2.lua b/pkgs/c/compat.catch2.lua index decacb4..d17ff65 100644 --- a/pkgs/c/compat.catch2.lua +++ b/pkgs/c/compat.catch2.lua @@ -1,43 +1,111 @@ --- Form B inline descriptor for Catch2 v3 — a modern C++ test framework. --- Built as a static library from the individual source files under --- src/catch2/ (NOT the amalgamated distribution). Consumers include --- (or more specific headers) and provide their --- own main() using Catch::Session, or request the `main` feature for a --- ready-made default entry point. +-- Form B inline descriptor for Catch2 — a modern C++-native test framework. +-- +-- ONE package, TWO majors. v2 and v3 have completely different source layouts +-- (`single_include/catch2/catch.hpp` vs `src/catch2/**`), and this descriptor +-- covers both by taking the UNION of the two layouts' globs. That works only +-- because the two sets are DISJOINT: +-- +-- | glob | 2.13.10 | 3.15.2 | +-- |-------------------------|---------------------------------|--------| +-- | */single_include | exists | absent | +-- | */src/catch2/**/*.cpp | 0 matches — v2's only src/ file | 107 | +-- | | is src/catch_with_main.cpp, | | +-- | | NOT under src/catch2/ | | +-- +-- So each version lights up exactly one half and the other degenerates to +-- empty: v2 resolves to header-only (single_include + the anchor TU below), +-- v3 to a 106-TU static library. An include_dir whose glob matches nothing is +-- not an error, which is what makes the union safe. +-- +-- THIS DISJOINTNESS IS THE LOAD-BEARING PREMISE, and it is a property of the +-- two upstream trees rather than something this descriptor can enforce. Re- +-- check the table above when adding a version. The right long-term shape is +-- per-version build blocks — mcpp-community/mcpp#290 — after which this +-- collapses into explicit ["2.x"] / ["3.x"] tables and the assumption goes +-- away entirely. Splitting into two packages was rejected: it encodes a +-- version into the `name` segment, which SPEC-001 identity reserves for an +-- atomic name, and it breaks semver across the major (a consumer could not +-- write `catch2 = "^3"`). -- -- Features (sources-only gate): --- `main` — compiles catch_main.cpp (upstream's default main via --- Catch::Session). Excluded from the default source set via `!` --- negation; only compiled when features = ["main"] is requested --- (same pattern as compat.gtest's `main` feature). +-- `main` — compiles a GENERATED TU supplying a default entry point. It +-- branches on __has_include(), which exists only in +-- v3, to pick the v3 (Catch::Session) or v2 (CATCH_CONFIG_MAIN) spelling. +-- Excluded by default; request `features = ["main"]`. -- --- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` --- absorbs the GitHub tarball's `Catch2-/` wrap layer. +-- It deliberately does NOT point at upstream's +-- src/catch2/internal/catch_main.cpp. That file is matched by the sources +-- glob and must be `!`-negated, else a consumer with its own main() gets +-- `multiple definition of 'main'`; and a `!` negation is an ABSOLUTE +-- exclusion that a feature cannot add back — pointing the feature at the +-- negated path yields a feature that silently does nothing and an +-- `undefined reference to 'main'` at link time. Both directions were +-- measured on mcpp 0.0.109. Note this is NOT `compat.gtest`'s shape: +-- gtest lists gtest_main.cc as a LITERAL `sources` entry, and feature +-- gating matches literal entries — a file pulled in by a glob is not gated +-- at all. A generated TU sidesteps the whole conflict. +-- +-- Cost of not reusing upstream's file: no LeakDetector registration (a +-- Windows CRT-debug nicety) and no wmain variant (reachable only under +-- _UNICODE on Windows). Consumers needing either write their own main(). +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` absorbs +-- the GitHub tarball's `Catch2-/` wrap layer. package = { spec = "1", namespace = "compat", name = "catch2", - description = "A modern, C++-native test framework for unit-tests, TDD and BDD — v3 (C++14+)", + description = "A modern, C++-native test framework for unit-tests, TDD and BDD", licenses = {"BSL-1.0"}, repo = "https://github.com/catchorg/Catch2", type = "package", xpm = { linux = { + ["2.13.10"] = { + url = { + GLOBAL = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", + CN = "https://gitcode.com/mcpp-res/catch2/releases/download/2.13.10/catch2-2.13.10.tar.gz", + }, + sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", + }, ["3.15.2"] = { - url = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + url = { + GLOBAL = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/catch2/releases/download/3.15.2/catch2-3.15.2.tar.gz", + }, sha256 = "acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420", }, }, macosx = { + ["2.13.10"] = { + url = { + GLOBAL = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", + CN = "https://gitcode.com/mcpp-res/catch2/releases/download/2.13.10/catch2-2.13.10.tar.gz", + }, + sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", + }, ["3.15.2"] = { - url = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + url = { + GLOBAL = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/catch2/releases/download/3.15.2/catch2-3.15.2.tar.gz", + }, sha256 = "acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420", }, }, windows = { + ["2.13.10"] = { + url = { + GLOBAL = "https://github.com/catchorg/Catch2/archive/refs/tags/v2.13.10.tar.gz", + CN = "https://gitcode.com/mcpp-res/catch2/releases/download/2.13.10/catch2-2.13.10.tar.gz", + }, + sha256 = "d54a712b7b1d7708bc7a819a8e6e47b2fde9536f487b89ccbca295072a7d9943", + }, ["3.15.2"] = { - url = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + url = { + GLOBAL = "https://github.com/catchorg/Catch2/archive/refs/tags/v3.15.2.tar.gz", + CN = "https://gitcode.com/mcpp-res/catch2/releases/download/3.15.2/catch2-3.15.2.tar.gz", + }, sha256 = "acfae120892c2b67a74142d36d060c0caa96f1c3aaa8aabd96e19961163d0420", }, }, @@ -46,15 +114,22 @@ package = { mcpp = { language = "c++23", import_std = false, - -- expose src/ so that #include resolves to - -- Catch2-/src/catch2/… - -- mcpp_generated/ provides catch2/catch_user_config.hpp (normally - -- CMake-generated; we materialise a reasonable default here). - include_dirs = { "*/src", "mcpp_generated" }, - -- catch_main.cpp provides a default main(). Excluded via `!` - -- negation from the default source set; only compiled when - -- features = ["main"] is requested (same pattern as compat.gtest). + c_standard = "c11", -- for the anchor TU below + + -- Union of both layouts. v2 resolves */single_include, v3 resolves + -- */src; the other one matches nothing and is skipped. mcpp_generated + -- carries catch2/catch_user_config.hpp for v3. + include_dirs = { "*/single_include", "*/src", "mcpp_generated" }, + generated_files = { + -- v3 only. Upstream ships catch_user_config.hpp.in and lets CMake + -- materialise it. Only the two VALUE defines are mandatory — + -- everything else in the .in is a #cmakedefine, i.e. absent means + -- "use the compiler-detected default", which is what we want. + -- #ifndef-guarded so a consumer can override via -D. + -- Re-read the upstream .in when bumping v3: a newly added + -- mandatory value-define would otherwise break silently here. + -- v2 never includes this file. ["mcpp_generated/catch2/catch_user_config.hpp"] = [==[ #ifndef CATCH_USER_CONFIG_HPP_INCLUDED #define CATCH_USER_CONFIG_HPP_INCLUDED @@ -66,16 +141,39 @@ package = { #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif +#endif // CATCH_USER_CONFIG_HPP_INCLUDED +]==], + -- Required for v2, where the sources glob below matches nothing + -- and a lib target still needs at least one TU (same shape as + -- compat.eigen / compat.khrplatform). Inert extra object on v3. + ["mcpp_generated/catch2_anchor.c"] = [==[ +int mcpp_compat_catch2_anchor(void) { return 0; } +]==], + -- The `main` feature's TU. See the header comment for why this is + -- generated rather than upstream's catch_main.cpp. + ["mcpp_generated/catch2_main.cpp"] = [==[ +#if __has_include() +// Catch2 v3: the library is compiled in; just drive a session. +# include +int main(int argc, char* argv[]) { return Catch::Session().run(argc, argv); } +#else +// Catch2 v2 is header-only: this TU is also where the implementation lands. +# define CATCH_CONFIG_MAIN +# include #endif ]==], }, + sources = { + "mcpp_generated/catch2_anchor.c", "*/src/catch2/**/*.cpp", + -- Upstream's default main(); would collide with a consumer's own. "!*/src/catch2/internal/catch_main.cpp", }, + targets = { ["catch2"] = { kind = "lib" } }, features = { - ["main"] = { sources = { "*/src/catch2/internal/catch_main.cpp" } }, + ["main"] = { sources = { "mcpp_generated/catch2_main.cpp" } }, }, deps = { }, }, diff --git a/tests/examples/catch2-main/mcpp.toml b/tests/examples/catch2-main/mcpp.toml new file mode 100644 index 0000000..6a2459d --- /dev/null +++ b/tests/examples/catch2-main/mcpp.toml @@ -0,0 +1,16 @@ +# compat.catch2 at v3 — `main` FEATURE path: the package supplies the entry +# point and this project deliberately defines no main(). If the feature stops +# gating the generated TU in, this member fails with +# `undefined reference to 'main'`. +# +# This cannot share a project with tests/examples/catch2: features are resolved +# per consuming project, so one project cannot hold catch2 both with and +# without `main` (same constraint as asio-ssl vs asio-module). +# +# The `compat` index redirect is inherited from the workspace root. +[package] +name = "catch2-main-tests" +version = "0.1.0" + +[dependencies.compat] +catch2 = { version = "3.15.2", features = ["main"] } diff --git a/tests/examples/catch2-main/tests/main.cpp b/tests/examples/catch2-main/tests/main.cpp new file mode 100644 index 0000000..99af269 --- /dev/null +++ b/tests/examples/catch2-main/tests/main.cpp @@ -0,0 +1,16 @@ +// compat.catch2 v3, `main` FEATURE: no main() here on purpose — the entry +// point comes from the package's generated TU. Catch2 prints the assertion +// count on exit, so an empty binary cannot pass quietly. +#include + +static unsigned int factorial(unsigned int n) { + return n <= 1 ? 1 : n * factorial(n - 1); +} + +TEST_CASE("factorial of 5 is 120", "[math]") { + REQUIRE(factorial(5) == 120); +} + +TEST_CASE("factorial of 0 is 1", "[math]") { + REQUIRE(factorial(0) == 1); +} diff --git a/tests/examples/catch2-v2-main/mcpp.toml b/tests/examples/catch2-v2-main/mcpp.toml new file mode 100644 index 0000000..75fe50e --- /dev/null +++ b/tests/examples/catch2-v2-main/mcpp.toml @@ -0,0 +1,13 @@ +# compat.catch2 at v2 — `main` FEATURE path: no main() and no +# CATCH_CONFIG_MAIN here; both come from the package's generated TU. This is +# also what exercises the ELSE branch of that TU's +# __has_include() discriminator — the v3 members only +# ever take the THEN branch. +# +# The `compat` index redirect is inherited from the workspace root. +[package] +name = "catch2-v2-main-tests" +version = "0.1.0" + +[dependencies.compat] +catch2 = { version = "2.13.10", features = ["main"] } diff --git a/tests/examples/catch2-v2-main/tests/main.cpp b/tests/examples/catch2-v2-main/tests/main.cpp new file mode 100644 index 0000000..babfb5c --- /dev/null +++ b/tests/examples/catch2-v2-main/tests/main.cpp @@ -0,0 +1,16 @@ +// compat.catch2 v2, `main` FEATURE: neither main() nor CATCH_CONFIG_MAIN here +// — both come from the package's generated TU, which reaches this version +// through the ELSE branch of its __has_include() test. +#include + +static unsigned int factorial(unsigned int n) { + return n <= 1 ? 1 : n * factorial(n - 1); +} + +TEST_CASE("factorial of 5 is 120", "[math]") { + REQUIRE(factorial(5) == 120); +} + +TEST_CASE("factorial of 0 is 1", "[math]") { + REQUIRE(factorial(0) == 1); +} diff --git a/tests/examples/catch2-v2/mcpp.toml b/tests/examples/catch2-v2/mcpp.toml index 45c36b2..f01d4da 100644 --- a/tests/examples/catch2-v2/mcpp.toml +++ b/tests/examples/catch2-v2/mcpp.toml @@ -1,12 +1,13 @@ -# Catch2 v2 test project: depends on compat.catch2-v2 (header-only via this -# repo's own index) and asserts behavior under `mcpp test`. +# compat.catch2 at v2 — DEFAULT path: header-only, this project defines +# CATCH_CONFIG_MAIN itself. This is the member that proves the single-package +# union holds: at 2.13.10 the `*/src/catch2/**/*.cpp` glob must match nothing +# and `*/single_include` must resolve, or the build picks up the wrong half. +# +# The `compat` index redirect is inherited from the workspace root. # Part of the mcpp-index self-referential workspace. [package] name = "catch2-v2-tests" version = "0.1.0" -[indices] -compat = { path = "../../.." } - [dependencies.compat] -catch2-v2 = "2.13.10" +catch2 = "2.13.10" diff --git a/tests/examples/catch2/mcpp.toml b/tests/examples/catch2/mcpp.toml index bea4fb8..8f0643c 100644 --- a/tests/examples/catch2/mcpp.toml +++ b/tests/examples/catch2/mcpp.toml @@ -1,12 +1,13 @@ -# Catch2 v3 test project: depends on compat.catch2 (compiled from amalgamated -# source via this repo's own index) and asserts behavior under `mcpp test`. +# compat.catch2 at v3 — DEFAULT path: the package is a static library and this +# project supplies its own main() via Catch::Session. Also pins down that +# upstream's catch_main.cpp really is excluded — if the `!` negation stopped +# working, this member would fail with `multiple definition of 'main'`. +# +# The `compat` index redirect is inherited from the workspace root. # Part of the mcpp-index self-referential workspace. [package] name = "catch2-tests" version = "0.1.0" -[indices] -compat = { path = "../../.." } - [dependencies.compat] catch2 = "3.15.2"