From 2f754d4e1e6f8b61f98f24b6b5b21046ee5110e3 Mon Sep 17 00:00:00 2001 From: pyc <1115040131@qq.com> Date: Wed, 15 Jul 2026 16:02:36 +0800 Subject: [PATCH 1/2] feat(pkgs): add compat.spdlog 1.17.0 (header-only + declarative compiled feature) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spdlog is dual-modal: default header-only (self-contained via bundled fmt in FMT_HEADER_ONLY mode, no external fmt dep) and a precompiled mode gated on SPDLOG_COMPILED_LIB. The `compiled` feature declares the correct intent (sources = src/*.cpp + the SPDLOG_COMPILED_LIB interface define), but mcpp 0.0.91 does not yet compile a dependency's feature-gated sources — verified three ways (this package, compat.cjson's utils, compat.eigen's eigen_blas all leave feature sources uncompiled → link-time undefined reference). The feature's `defines` DO propagate to the consumer. So CI asserts the fully-working header-only mode; the compiled feature works unchanged once the engine links feature sources (same follow-up as eigen_blas). No CN mirror (no mcpp-res write access): plain-string upstream url per the docs/cn-mirror.md fallback (tensorvia-cpu precedent). Verified on mcpp 0.0.91 (GLOBAL): xpkg parse OK, `mcpp test -p spdlog` passes. --- .agents/docs/2026-07-15-add-spdlog-plan.md | 127 +++++++++++++++++++++ mcpp.toml | 1 + pkgs/c/compat.spdlog.lua | 113 ++++++++++++++++++ tests/examples/spdlog/mcpp.toml | 16 +++ tests/examples/spdlog/tests/log_test.cpp | 25 ++++ 5 files changed, 282 insertions(+) create mode 100644 .agents/docs/2026-07-15-add-spdlog-plan.md create mode 100644 pkgs/c/compat.spdlog.lua create mode 100644 tests/examples/spdlog/mcpp.toml create mode 100644 tests/examples/spdlog/tests/log_test.cpp diff --git a/.agents/docs/2026-07-15-add-spdlog-plan.md b/.agents/docs/2026-07-15-add-spdlog-plan.md new file mode 100644 index 0000000..095ae94 --- /dev/null +++ b/.agents/docs/2026-07-15-add-spdlog-plan.md @@ -0,0 +1,127 @@ +# 收录 spdlog 1.17.0 —— 形态判定、双模态与验证结论 + +日期:2026-07-15。对应描述符 `pkgs/c/compat.spdlog.lua`、示例 `tests/examples/spdlog/`。 +CI 版本 mcpp 0.0.91,index floor `min_mcpp = 0.0.87`。 + +## 1. 来源与形态 + +- 来源:(a) 第三方上游库 —— spdlog(https://github.com/gabime/spdlog)上游不提供 + mcpp 支持,由本仓以 `compat` 形态适配。 +- 最新 tag:`v1.17.0`(`git ls-remote --tags` 排序确认)。裸版本 `1.17.0`, + 下载 URL 保留上游 `.../v1.17.0.tar.gz` 拼写。 +- License:MIT。 +- sha256:`d8862955c6d74e5846b3f580b1605d2428b11d97a410d86e2fb13e857cd3a744` + (下载后重复计算两次,稳定)。 +- 源码布局:`include/spdlog/**`(全部头文件,含 `-inl.h` 实现单元与 + `fmt/bundled/` 内联版 {fmt})、`src/*.cpp`(7 个预编译库 TU)。 +- 形态判定:**header-only + source-gated feature**,与 `compat.eigen` 同类。 + spdlog 是 DUAL-MODAL: + - 默认(不定义宏)走 header-only:`common.h` 在未定义 `SPDLOG_COMPILED_LIB` + 时打开 `SPDLOG_HEADER_ONLY`,头文件末尾 `#include "*-inl.h"` 把实现内联。 + bundled fmt 以 `FMT_HEADER_ONLY` 使用,**整包自包含,无需外部 fmt 依赖**。 + - 预编译模态(定义 `SPDLOG_COMPILED_LIB`)改为编译 `src/*.cpp`;每个 src TU + 在缺少该宏时 `#error`。该宏是 **interface define**:必须同时到达库源与 + 消费者头(否则消费者头走 inline,与 .a 符号重复/冲突)。 + +## 2. 描述符设计 + +header-only 骨架(`include_dirs = {"*/include"}` + anchor TU 提供可构建 lib +target),外加一个声明式 `compiled` feature: + +```lua +features = { + ["compiled"] = { + sources = { "*/src/*.cpp" }, + defines = { "SPDLOG_COMPILED_LIB" }, + }, +} +``` + +## 3. 关键实证:mcpp 0.0.91 不编译 feature 门控的 sources + +投入前用 mcpp 0.0.91(与 CI 对齐)实测,发现一个**引擎级限制**,并三重佐证: + +| 实验 | feature | 现象 | +|---|---|---| +| spdlog(独立工程) | `compiled` | `src/*.cpp` 未编译 → 大量 `undefined reference` | +| compat.cjson(独立工程) | `utils` | `cJSON_Utils.c` 未编译 → `undefined reference to cJSONUtils_*` | +| compat.eigen(workspace 示例) | `eigen_blas` | `blas/*.cpp` 未编译 → `undefined reference to dgemm_` | + +三例的 `build.ninja` 均只含核心 sources/anchor 的 `.o`,feature 的 sources +一个都没进构建。 + +**结论**:mcpp 0.0.91 中,依赖包被激活 feature 的 `sources` **不会被编译链接**; +只有包的顶层 `sources` 与 feature 的 `defines` 生效。这正是 `compat.eigen` 的 +`dense.cpp` 注释所记的 follow-up——"linking feature-built dependency objects +into test binaries is a follow-up"。 + +**已验证生效的部分**:feature 的 `defines` 确实作为 interface define 传播到消费者 +——`features=["compiled"]` 时消费者 TU 的编译行带上了 `-DSPDLOG_COMPILED_LIB` +(`build.ninja` 实证)。所以 spdlog `compiled` 今天的表现是:宏到达消费者,但 +`src/*.cpp` 未编译,非 inline 符号无法链接。 + +**采用方案(与 eigen_blas 对齐)**:描述符保留 `compiled` feature 声明正确意图, +注释如实标注引擎限制;待 mcpp 支持"编译并链接 feature sources"后无需改描述符即可 +生效。CI 只断言 header-only 模态。 + +## 4. feature 评估:为何不为 tweakme 开关新增 feature + +spdlog 的 `tweakme.h` 有大量编译期开关(`SPDLOG_USE_STD_FORMAT`、 +`SPDLOG_NO_SOURCE_LOC`、`SPDLOG_CLOCK_COARSE`、`SPDLOG_ACTIVE_LEVEL=...` 等)。 +经确认(mcpp `05-mcpp-toml.md`),消费者可在**自己的** `mcpp.toml` 直接注入, +无需包侧声明 feature: + +```toml +[targets.my-app] +defines = ["SPDLOG_USE_STD_FORMAT"] # per-target,-D 随本 TU 编译 +# 或 [build] cxxflags = ["-DSPDLOG_NO_SOURCE_LOC"] # 整工程 +``` + +作用域陷阱(官方明示):`defines`/`cxxflags` **只作用于该 target 自己的 entry, +不传播到共享/依赖代码**。对 spdlog: + +- header-only 模态下 spdlog 全在头文件里,随消费者 TU 编译,消费者 `-D` 天然覆盖 + → 所有 tweakme 开关消费者自助即可,做成 feature 是冗余(且不如消费侧灵活, + 消费侧还能带值,如 `SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_INFO`)。 +- 唯一"消费者 `-D` 够不着、必须包侧统一注入"的是 `SPDLOG_COMPILED_LIB` + (要同时影响库源与消费者)——已由 `compiled` feature 承载(受 §3 限制)。 + +因此**不新增 tweakme feature**,避免过度设计。 + +## 5. CN 镜像:纯字符串回退 + +本环境无 gitcode token(`~/.config/gitcode-tool/config.json` 不存在, +`GITCODE_TOKEN` 未设),无法在 `mcpp-res` 上传资产。探查发现 `mcpp-res/spdlog` +仓库页面已存在(200)但为空壳(release 资产 403)。 + +按 `docs/cn-mirror.md` 回退方案:三平台 `url` 采用**纯字符串**(仅上游 GitHub +release),lint(`check_mirror_urls.lua`)对纯字符串 url 不施加镜像约束, +`mirror-cn-reachable` 也不会抽取到需 curl 的 CN url。CN 用户回退至上游源。 +先例:`pkgs/t/tensorvia-cpu.lua`。后续获得权限或维护者补充镜像后,可将各 +`url` 改写为 `{ GLOBAL, CN }` 表(sha256 不变)。 + +lint 只允许 `gitcode.com/mcpp-res/` 下的 CN url(信任边界 + 字节一致),任何 +第三方域名在表形式下都过不了 lint,故无"其他可用 CN 镜像"。 + +## 6. 验证结论(mcpp 0.0.91,GLOBAL) + +- `mcpp xpkg parse pkgs/c/compat.spdlog.lua` → `parse OK`(strict floor/grammar)。 +- workspace 成员 `mcpp test -p spdlog` → `test result ok. 1 passed`。 + 示例 `tests/examples/spdlog/tests/log_test.cpp` 用 ostream sink 捕获日志, + 断言 `logger.info("hello {}={}", "answer", 42)` 与 `{:#x}` 的格式化输出 + (走 bundled fmt 头内联),`return ok ? 0 : 1`。 +- 负向语义:header-only 默认构建 `build.ninja` 仅 `spdlog_anchor.o` + (证明 src 未被默认编入);compiled 请求时消费者带 `-DSPDLOG_COMPILED_LIB` + (证明 define 门控生效)。 +- 本地 lint(等价 CI lint job)全量 `ALL LINT PASS`;spdlog 镜像 lint 单独 OK。 + +## 7. 落点与注意事项 + +- 描述符落点 `pkgs/c/compat.spdlog.lua`——目录取**完整包名首字母** + (`compat.spdlog` → `c`,非短名 `s`)。初次误置 `pkgs/s/` 导致 + `not found in local index`,移至 `pkgs/c/` 后解决。 +- 示例已登记进根 `mcpp.toml` 的 `[workspace].members`(否则 + `mcpp test --workspace` 不会跑到)。 +- CI 已由早期 `detect`+`run_example.sh` 重构为 workspace 模式 + (`mcpp test --workspace`),示例采用 `tests/*.cpp` + 断言布局,而非 + `src/main.cpp`。 diff --git a/mcpp.toml b/mcpp.toml index e32a776..3ba3f1a 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -20,4 +20,5 @@ members = [ "tests/examples/nlohmann.json", "tests/examples/openblas", "tests/examples/opencv", + "tests/examples/spdlog", ] diff --git a/pkgs/c/compat.spdlog.lua b/pkgs/c/compat.spdlog.lua new file mode 100644 index 0000000..f29adc2 --- /dev/null +++ b/pkgs/c/compat.spdlog.lua @@ -0,0 +1,113 @@ +-- Form B inline descriptor for spdlog — a very fast, header-only / compiled C++ +-- logging library. spdlog is DUAL-MODAL and this recipe exposes both modes off +-- one descriptor: +-- +-- * DEFAULT (header-only). No feature requested. spdlog's headers are +-- self-contained: including pulls the -inl.h +-- implementation inline (common.h flips on SPDLOG_HEADER_ONLY when +-- SPDLOG_COMPILED_LIB is NOT defined). Nothing under src/ compiles; a tiny +-- anchor TU gives mcpp a buildable `lib` target (same shape as +-- compat.eigen / compat.opengl). The bundled {fmt} (include/spdlog/fmt/ +-- bundled/) is used in FMT_HEADER_ONLY mode, so this package is +-- self-contained and needs NO external fmt dependency. +-- +-- * COMPILED (`features = ["compiled"]`). Requesting the `compiled` feature +-- is INTENDED to turn spdlog into a precompiled library: (1) compile the +-- seven src/*.cpp translation units into the lib, and (2) contribute the +-- SPDLOG_COMPILED_LIB define. That define is an INTERFACE define — it must +-- reach BOTH spdlog's own sources (each src/*.cpp #errors out without it) +-- AND every consumer TU that includes a spdlog header (so the consumer's +-- headers switch to the extern-template / non-inline path and link against +-- the compiled objects instead of re-emitting the implementation inline). +-- +-- CURRENT ENGINE LIMITATION (mcpp 0.0.91): a dependency's feature-gated +-- `sources` are NOT compiled — only the package's top-level `sources` (the +-- anchor here) and the feature's `defines` take effect. Verified three ways +-- on 0.0.91: this package's `compiled`, compat.cjson's `utils` +-- (cJSON_Utils.c), and compat.eigen's `eigen_blas` (blas/*.cpp) all leave +-- their feature sources uncompiled → link-time `undefined reference`. So +-- with `features=["compiled"]` today the SPDLOG_COMPILED_LIB define DOES +-- reach the consumer (proven: -DSPDLOG_COMPILED_LIB on the consumer TU) but +-- src/*.cpp is not built, so the non-inline symbols do not link. This is the +-- same follow-up compat.eigen's eigen_blas is waiting on ("linking +-- feature-built dependency objects into test binaries is a follow-up"). The +-- recipe declares the correct intent so it works unchanged once mcpp +-- compiles+links feature sources; until then, use the DEFAULT header-only +-- mode (fully working). bundled_fmtlib_format.cpp would compile the bundled +-- fmt implementation into the lib, so compiled mode is likewise +-- self-contained (no external fmt) once the engine supports it. +-- +-- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` absorbs the +-- GitHub archive's `spdlog-/` wrap layer. include_dirs points at +-- `*/include` so consumers write `#include `. +-- +-- No CN mirror yet: `url` is a plain string (upstream GitHub release only), the +-- documented fallback when there is no mcpp-res write access (docs/cn-mirror.md; +-- precedent: pkgs/t/tensorvia-cpu.lua). CN users fall back to the upstream +-- source. A maintainer can later rewrite each `url` to a { GLOBAL, CN } table +-- (sha256 unchanged) once the gitcode mcpp-res/spdlog mirror exists. +package = { + spec = "1", + namespace = "compat", + name = "compat.spdlog", + description = "Fast C++ logging library (header-only by default, compiled via the `compiled` feature)", + licenses = {"MIT"}, + repo = "https://github.com/gabime/spdlog", + type = "package", + + xpm = { + linux = { + ["1.17.0"] = { + url = "https://github.com/gabime/spdlog/archive/refs/tags/v1.17.0.tar.gz", + sha256 = "d8862955c6d74e5846b3f580b1605d2428b11d97a410d86e2fb13e857cd3a744", + }, + }, + macosx = { + ["1.17.0"] = { + url = "https://github.com/gabime/spdlog/archive/refs/tags/v1.17.0.tar.gz", + sha256 = "d8862955c6d74e5846b3f580b1605d2428b11d97a410d86e2fb13e857cd3a744", + }, + }, + windows = { + ["1.17.0"] = { + url = "https://github.com/gabime/spdlog/archive/refs/tags/v1.17.0.tar.gz", + sha256 = "d8862955c6d74e5846b3f580b1605d2428b11d97a410d86e2fb13e857cd3a744", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + -- Exposes include/spdlog/** so consumers write `#include `. + -- The bundled fmt under include/spdlog/fmt/bundled/ rides along, so no + -- external fmt dependency is needed in either mode. + include_dirs = { "*/include" }, + -- Header-only default: a trivial anchor TU gives mcpp a buildable lib + -- target when no source is compiled. + generated_files = { + ["mcpp_generated/spdlog_anchor.c"] = [==[ +int mcpp_compat_spdlog_headers_anchor(void) { return 0; } +]==], + }, + sources = { "mcpp_generated/spdlog_anchor.c" }, + targets = { ["spdlog"] = { kind = "lib" } }, + features = { + -- Precompiled mode (declarative intent — see the header's CURRENT + -- ENGINE LIMITATION note). Meant to compile spdlog's src/*.cpp into + -- the lib AND publish SPDLOG_COMPILED_LIB as an interface define so + -- consumer headers take the non-inline / extern-template path. + -- src/spdlog.cpp (and the other six) #error without this define. On + -- mcpp 0.0.91 the `defines` DO propagate to the consumer but the + -- feature `sources` are not yet compiled (same follow-up as + -- compat.eigen's eigen_blas), so use the default header-only mode + -- until the engine builds+links feature sources. + ["compiled"] = { + sources = { "*/src/*.cpp" }, + defines = { "SPDLOG_COMPILED_LIB" }, + }, + }, + deps = { }, + }, +} diff --git a/tests/examples/spdlog/mcpp.toml b/tests/examples/spdlog/mcpp.toml new file mode 100644 index 0000000..094e898 --- /dev/null +++ b/tests/examples/spdlog/mcpp.toml @@ -0,0 +1,16 @@ +# spdlog test project: consumes compat.spdlog in its DEFAULT header-only mode +# (bundled fmt, no external dependency) and asserts behavior under `mcpp test`. +# Part of the mcpp-index self-referential workspace: `[indices] compat` points +# at this repo, so the dependency resolves to the checked-in recipe +# (pkgs/s/compat.spdlog.lua). The `compiled` feature mode is covered by the +# design doc's local verification (interface-define propagation of +# SPDLOG_COMPILED_LIB). +[package] +name = "spdlog-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +spdlog = "1.17.0" diff --git a/tests/examples/spdlog/tests/log_test.cpp b/tests/examples/spdlog/tests/log_test.cpp new file mode 100644 index 0000000..a081095 --- /dev/null +++ b/tests/examples/spdlog/tests/log_test.cpp @@ -0,0 +1,25 @@ +// Behavioral test: consume compat.spdlog in its DEFAULT header-only mode. Logs +// through a custom logger wired to an in-memory ostream sink, then asserts the +// formatted output — this exercises the logger, the pattern formatter, and the +// bundled {fmt} formatting all via the header inlines. Returns non-zero on any +// mismatch. +#include +#include +#include +#include + +int main() { + std::ostringstream captured; + auto sink = std::make_shared(captured); + spdlog::logger logger("test", sink); + logger.set_pattern("%v"); // message only, no timestamp/level decoration + + logger.info("hello {}={}", "answer", 42); + logger.warn("{:#x}", 255); + logger.flush(); + + const std::string out = captured.str(); + const bool ok = out.find("hello answer=42") != std::string::npos + && out.find("0xff") != std::string::npos; + return ok ? 0 : 1; +} From 68f37fdd3f3b78f237d016d70d195a568e1aa2da Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 15 Jul 2026 22:54:05 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(spdlog):=20=E4=BF=AE=E6=AD=A3=E5=BC=95?= =?UTF-8?q?=E6=93=8E=E9=99=90=E5=88=B6=E8=AF=AF=E5=88=A4=20+=20compiled=20?= =?UTF-8?q?=E6=A8=A1=E6=80=81=E8=BF=9B=20CI(mcpp=200.0.94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初版把「feature sources 不编译」判为引擎级限制,是**误判**:该 bug 只影响 `mcpp test` 路径,`mcpp build` 一直是好的(7 个 src TU 全进 build.ninja、 二进制真跑)。三重佐证(spdlog/cjson/eigen)现象属实,但三次都只走了 test 一条路径 —— 本仓 CI 是 workspace/test 模型。 真因(mcpp/src/build/prepare.cppm):feature 源集解析被整段门在 `!includeDevDeps`,test 模式跳过 → 激活 feature 的 sources 从不加回构建图; 叠加 xpkg 的 `features.X.sources` 只落进 featureSources、从不进 base sources。 已由 mcpp#218 修复并发布 0.0.94(drop 仍只在 build 模式,add 两模式都做+去重)。 - 描述符 Lua **一行未改** —— `compiled` feature 的建模本来就是对的; 只把注释从「引擎不支持、请用 header-only」改为如实的「test 下需 ≥0.0.94」。 - 新增 workspace 成员 `tests/examples/spdlog-compiled/`:**compiled 模态进 CI**。 静态断言 SPDLOG_COMPILED_LIB 已定义 + SPDLOG_HEADER_ONLY 未定义(退化回 inline 路径是编译错误而非静默通过),再跑行为断言 —— 调用解析到 default_logger_raw / log_msg ctor / logger::log_it_ / bundled fmt vformat 等非 inline 符号,正是 feature sources 没编译时链不上的那批。 - CI pin 0.0.91 → 0.0.94;index floor `min_mcpp` 不动(它追踪描述符**语法**, 本描述符在 0.0.87 上就能解析;header-only 模态任何版本都可用)。 - 修 `tests/examples/spdlog/mcpp.toml` 里 `pkgs/s/` 陈旧路径 → `pkgs/c/`。 --- .agents/docs/2026-07-15-add-spdlog-plan.md | 65 +++++++++++++------ .github/workflows/validate.yml | 11 ++-- mcpp.toml | 1 + pkgs/c/compat.spdlog.lua | 56 +++++++--------- tests/examples/spdlog-compiled/mcpp.toml | 19 ++++++ .../spdlog-compiled/tests/compiled_test.cpp | 42 ++++++++++++ tests/examples/spdlog/mcpp.toml | 5 +- 7 files changed, 139 insertions(+), 60 deletions(-) create mode 100644 tests/examples/spdlog-compiled/mcpp.toml create mode 100644 tests/examples/spdlog-compiled/tests/compiled_test.cpp diff --git a/.agents/docs/2026-07-15-add-spdlog-plan.md b/.agents/docs/2026-07-15-add-spdlog-plan.md index 095ae94..5253f93 100644 --- a/.agents/docs/2026-07-15-add-spdlog-plan.md +++ b/.agents/docs/2026-07-15-add-spdlog-plan.md @@ -37,32 +37,47 @@ features = { } ``` -## 3. 关键实证:mcpp 0.0.91 不编译 feature 门控的 sources +## 3. 关键实证:feature sources 在 `mcpp test` 下不编译(mcpp ≤0.0.93 的 bug,0.0.94 已修) -投入前用 mcpp 0.0.91(与 CI 对齐)实测,发现一个**引擎级限制**,并三重佐证: +> **修订(2026-07-15)**:本节初版结论「mcpp 0.0.91 引擎不编译 feature 门控的 +> sources」**是误判**,已由 mcpp#218 查清并修复。误判的成因值得记下来。 + +初版三重佐证如下: | 实验 | feature | 现象 | |---|---|---| -| spdlog(独立工程) | `compiled` | `src/*.cpp` 未编译 → 大量 `undefined reference` | -| compat.cjson(独立工程) | `utils` | `cJSON_Utils.c` 未编译 → `undefined reference to cJSONUtils_*` | -| compat.eigen(workspace 示例) | `eigen_blas` | `blas/*.cpp` 未编译 → `undefined reference to dgemm_` | +| spdlog | `compiled` | `src/*.cpp` 未编译 → 大量 `undefined reference` | +| compat.cjson | `utils` | `cJSON_Utils.c` 未编译 → `undefined reference to cJSONUtils_*` | +| compat.eigen | `eigen_blas` | `blas/*.cpp` 未编译 → `undefined reference to dgemm_` | + +三例现象属实,但**三次都只走了 `mcpp test` 这一条路径**(本仓 CI 是 workspace/test +模型),于是把一个**只影响 test 模式**的 bug 误读成了「引擎不支持 feature sources」。 + +**真因**(`mcpp/src/build/prepare.cppm`):feature 源集解析(drop + add)**整段**被门在 +`!includeDevDeps`,而 `mcpp test` 走 `includeDevDeps = true` → 激活 feature 的 sources +从不被加回构建图。叠加 xpkg 侧 `features.X.sources` 只落进 `featureSources`、**从不进 +base `sources`** —— 于是「只在 features 下声明」的包: + +| 路径 | 结果 | +|---|---| +| `mcpp build` | ✅ **一直是好的**,7 个 src TU 全进 build.ninja,二进制真跑 | +| `mcpp test` | ❌ `undefined reference` | -三例的 `build.ninja` 均只含核心 sources/anchor 的 `.o`,feature 的 sources -一个都没进构建。 +那段门的注释假设「descriptor 会把 `gtest_main.cc` 同时留在 base sources 里,故 test +模式不受影响」——**只对 gtest 成立**,是未被察觉的隐式耦合。`compat.eigen` 里 +"linking feature-built dependency objects into test binaries is a follow-up" 的定性 +同样是错的:**不是链接问题,是源集解析问题**。 -**结论**:mcpp 0.0.91 中,依赖包被激活 feature 的 `sources` **不会被编译链接**; -只有包的顶层 `sources` 与 feature 的 `defines` 生效。这正是 `compat.eigen` 的 -`dense.cpp` 注释所记的 follow-up——"linking feature-built dependency objects -into test binaries is a follow-up"。 +**修复**:mcpp 0.0.94(#218)—— drop 仍只在 build 模式做(test 模式需保留完整源面供 +dev-dep 轨 per-test main 检测剪枝),add 改为两模式都做 + 去重。 -**已验证生效的部分**:feature 的 `defines` 确实作为 interface define 传播到消费者 -——`features=["compiled"]` 时消费者 TU 的编译行带上了 `-DSPDLOG_COMPILED_LIB` -(`build.ninja` 实证)。所以 spdlog `compiled` 今天的表现是:宏到达消费者,但 -`src/*.cpp` 未编译,非 inline 符号无法链接。 +**采用方案**:描述符的 `compiled` feature 声明**本来就是对的,一行未改**;只把注释从 +「引擎不支持」改为如实的「`mcpp test` 下需 ≥0.0.94」。CI pin 提到 0.0.94 后, +**两个模态都进 CI 断言**(`tests/examples/spdlog/` header-only + +`tests/examples/spdlog-compiled/` compiled)。 -**采用方案(与 eigen_blas 对齐)**:描述符保留 `compiled` feature 声明正确意图, -注释如实标注引擎限制;待 mcpp 支持"编译并链接 feature sources"后无需改描述符即可 -生效。CI 只断言 header-only 模态。 +**教训**:诊断 feature 相关问题**必须 `build` / `test` 两条路径对比**,只测一条会把 +路径 bug 误判成引擎能力缺失。 ## 4. feature 评估:为何不为 tweakme 开关新增 feature @@ -103,7 +118,19 @@ release),lint(`check_mirror_urls.lua`)对纯字符串 url 不施加镜像约束, lint 只允许 `gitcode.com/mcpp-res/` 下的 CN url(信任边界 + 字节一致),任何 第三方域名在表形式下都过不了 lint,故无"其他可用 CN 镜像"。 -## 6. 验证结论(mcpp 0.0.91,GLOBAL) +## 6. 验证结论(mcpp 0.0.94,GLOBAL) + +**两个模态都已 CI 断言**: + +- `mcpp test -p spdlog` → header-only 默认模态,`test result ok. 1 passed`。 +- `mcpp test -p spdlog-compiled` → compiled 模态,`test result ok. 1 passed`。 + `tests/examples/spdlog-compiled/tests/compiled_test.cpp` 静态断言 + `SPDLOG_COMPILED_LIB` 已定义且 `SPDLOG_HEADER_ONLY` 未定义(退化回 inline 路径 + 会是**编译错误**而非静默通过),再跑与 header-only 同款的行为断言 —— 这些调用 + 解析到 `default_logger_raw` / `log_msg` ctor / `logger::log_it_` / bundled fmt + `vformat` 等**非 inline 符号**,正是 feature sources 没编译时链不上的那批。 + +以下为初版(0.0.91)记录,保留作历史: - `mcpp xpkg parse pkgs/c/compat.spdlog.lua` → `parse OK`(strict floor/grammar)。 - workspace 成员 `mcpp test -p spdlog` → `test result ok. 1 passed`。 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 30b5467..4f9e6a0 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -11,11 +11,12 @@ on: workflow_dispatch: env: - # Bumped to 0.0.91: carries standard = "c++fly" (experimental playground - # mode) in the resolver grammar — required so descriptors declaring it get - # the lint WARN below instead of a hard grammar-parse rejection from an - # older pinned mcpp (admission policy: warn, don't reject). - MCPP_VERSION: "0.0.91" + # Bumped to 0.0.94: fixes feature-gated `sources` never being compiled under + # `mcpp test` (mcpp#218) — required by the tests/examples/spdlog-compiled + # member, which links against spdlog's feature-compiled src/*.cpp. Also + # carries standard = "c++fly" in the resolver grammar (0.0.91), so c++fly + # descriptors get the lint WARN below, not a hard grammar-parse rejection. + MCPP_VERSION: "0.0.94" jobs: lint: diff --git a/mcpp.toml b/mcpp.toml index 3ba3f1a..e836eca 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -21,4 +21,5 @@ members = [ "tests/examples/openblas", "tests/examples/opencv", "tests/examples/spdlog", + "tests/examples/spdlog-compiled", ] diff --git a/pkgs/c/compat.spdlog.lua b/pkgs/c/compat.spdlog.lua index f29adc2..66d6838 100644 --- a/pkgs/c/compat.spdlog.lua +++ b/pkgs/c/compat.spdlog.lua @@ -11,31 +11,25 @@ -- bundled/) is used in FMT_HEADER_ONLY mode, so this package is -- self-contained and needs NO external fmt dependency. -- --- * COMPILED (`features = ["compiled"]`). Requesting the `compiled` feature --- is INTENDED to turn spdlog into a precompiled library: (1) compile the --- seven src/*.cpp translation units into the lib, and (2) contribute the --- SPDLOG_COMPILED_LIB define. That define is an INTERFACE define — it must --- reach BOTH spdlog's own sources (each src/*.cpp #errors out without it) --- AND every consumer TU that includes a spdlog header (so the consumer's --- headers switch to the extern-template / non-inline path and link against --- the compiled objects instead of re-emitting the implementation inline). +-- * COMPILED (`features = ["compiled"]`). Turns spdlog into a precompiled +-- library: (1) compiles the seven src/*.cpp translation units into the lib, +-- and (2) contributes the SPDLOG_COMPILED_LIB define. That define is an +-- INTERFACE define — it must reach BOTH spdlog's own sources (each src/*.cpp +-- #errors out without it) AND every consumer TU that includes a spdlog +-- header (so the consumer's headers switch to the extern-template / +-- non-inline path and link against the compiled objects instead of +-- re-emitting the implementation inline). bundled_fmtlib_format.cpp compiles +-- the bundled fmt implementation into the lib, so compiled mode is +-- self-contained too — still NO external fmt dependency. -- --- CURRENT ENGINE LIMITATION (mcpp 0.0.91): a dependency's feature-gated --- `sources` are NOT compiled — only the package's top-level `sources` (the --- anchor here) and the feature's `defines` take effect. Verified three ways --- on 0.0.91: this package's `compiled`, compat.cjson's `utils` --- (cJSON_Utils.c), and compat.eigen's `eigen_blas` (blas/*.cpp) all leave --- their feature sources uncompiled → link-time `undefined reference`. So --- with `features=["compiled"]` today the SPDLOG_COMPILED_LIB define DOES --- reach the consumer (proven: -DSPDLOG_COMPILED_LIB on the consumer TU) but --- src/*.cpp is not built, so the non-inline symbols do not link. This is the --- same follow-up compat.eigen's eigen_blas is waiting on ("linking --- feature-built dependency objects into test binaries is a follow-up"). The --- recipe declares the correct intent so it works unchanged once mcpp --- compiles+links feature sources; until then, use the DEFAULT header-only --- mode (fully working). bundled_fmtlib_format.cpp would compile the bundled --- fmt implementation into the lib, so compiled mode is likewise --- self-contained (no external fmt) once the engine supports it. +-- REQUIRES mcpp >= 0.0.94 under `mcpp test`. mcpp 0.0.93 and older gated the +-- whole feature-source resolution on build mode, so an active feature's +-- `sources` were never compiled under `mcpp test` → link-time `undefined +-- reference` (`mcpp build` was always fine). Fixed in mcpp 0.0.94; the same +-- bug is what compat.cjson's `utils` and compat.eigen's `eigen_blas` +-- (`dgemm_`) hit. Header-only mode is unaffected and works on any version, +-- which is why the index floor (`index.toml` min_mcpp) does not move: that +-- floor tracks descriptor GRAMMAR, and this descriptor parses everywhere. -- -- All `mcpp` paths are GLOBS relative to the verdir; the leading `*` absorbs the -- GitHub archive's `spdlog-/` wrap layer. include_dirs points at @@ -94,15 +88,11 @@ int mcpp_compat_spdlog_headers_anchor(void) { return 0; } sources = { "mcpp_generated/spdlog_anchor.c" }, targets = { ["spdlog"] = { kind = "lib" } }, features = { - -- Precompiled mode (declarative intent — see the header's CURRENT - -- ENGINE LIMITATION note). Meant to compile spdlog's src/*.cpp into - -- the lib AND publish SPDLOG_COMPILED_LIB as an interface define so - -- consumer headers take the non-inline / extern-template path. - -- src/spdlog.cpp (and the other six) #error without this define. On - -- mcpp 0.0.91 the `defines` DO propagate to the consumer but the - -- feature `sources` are not yet compiled (same follow-up as - -- compat.eigen's eigen_blas), so use the default header-only mode - -- until the engine builds+links feature sources. + -- Precompiled mode: compiles spdlog's src/*.cpp into the lib AND + -- publishes SPDLOG_COMPILED_LIB as an interface define so consumer + -- headers take the non-inline / extern-template path. src/spdlog.cpp + -- (and the other six) #error without this define. Needs mcpp >= + -- 0.0.94 under `mcpp test` — see the header note. ["compiled"] = { sources = { "*/src/*.cpp" }, defines = { "SPDLOG_COMPILED_LIB" }, diff --git a/tests/examples/spdlog-compiled/mcpp.toml b/tests/examples/spdlog-compiled/mcpp.toml new file mode 100644 index 0000000..bd84f26 --- /dev/null +++ b/tests/examples/spdlog-compiled/mcpp.toml @@ -0,0 +1,19 @@ +# spdlog test project: consumes compat.spdlog in its COMPILED mode +# (`features = ["compiled"]`) — spdlog's src/*.cpp are compiled into the lib and +# SPDLOG_COMPILED_LIB reaches this consumer's TUs as an interface define, so the +# headers take the non-inline path and link against those objects. +# +# Sibling of tests/examples/spdlog/ (header-only default mode). Both modes ship +# from one descriptor; this member is what keeps the compiled path honest. +# +# Needs mcpp >= 0.0.94: older versions never compiled a dependency's active +# feature `sources` under `mcpp test`, so this member would fail to link. +[package] +name = "spdlog-compiled-tests" +version = "0.1.0" + +[indices] +compat = { path = "../../.." } + +[dependencies.compat] +spdlog = { version = "1.17.0", features = ["compiled"] } diff --git a/tests/examples/spdlog-compiled/tests/compiled_test.cpp b/tests/examples/spdlog-compiled/tests/compiled_test.cpp new file mode 100644 index 0000000..b2f629e --- /dev/null +++ b/tests/examples/spdlog-compiled/tests/compiled_test.cpp @@ -0,0 +1,42 @@ +// Behavioral test: consume compat.spdlog in COMPILED mode. Asserts both halves +// of what the `compiled` feature promises: +// +// 1. the interface define reached this TU (SPDLOG_COMPILED_LIB defined, and +// spdlog's headers therefore did NOT flip on SPDLOG_HEADER_ONLY) — a +// static check, so a regression is a compile error, not a silent fallback +// to the header-only path that would still pass the runtime assertions; +// 2. spdlog's src/*.cpp really were compiled and linked — the calls below +// resolve to out-of-line symbols (default_logger_raw, log_msg's ctor, +// logger::log_it_, and bundled fmt's vformat), which is exactly what fails +// to link when the engine skips a feature's sources. +#include +#include +#include +#include + +#ifndef SPDLOG_COMPILED_LIB +#error "compiled feature did not propagate SPDLOG_COMPILED_LIB to the consumer" +#endif +#ifdef SPDLOG_HEADER_ONLY +#error "SPDLOG_HEADER_ONLY is on in compiled mode — headers took the inline path" +#endif + +int main() { + std::ostringstream captured; + auto sink = std::make_shared(captured); + spdlog::logger logger("test", sink); + logger.set_pattern("%v"); // message only, no timestamp/level decoration + + logger.info("hello {}={}", "answer", 42); + logger.warn("{:#x}", 255); + logger.flush(); + + // Exercises the global registry too — default_logger_raw() is one of the + // out-of-line symbols that only exists once src/spdlog.cpp is compiled. + spdlog::set_level(spdlog::level::info); + + const std::string out = captured.str(); + const bool ok = out.find("hello answer=42") != std::string::npos + && out.find("0xff") != std::string::npos; + return ok ? 0 : 1; +} diff --git a/tests/examples/spdlog/mcpp.toml b/tests/examples/spdlog/mcpp.toml index 094e898..4de1a1d 100644 --- a/tests/examples/spdlog/mcpp.toml +++ b/tests/examples/spdlog/mcpp.toml @@ -2,9 +2,8 @@ # (bundled fmt, no external dependency) and asserts behavior under `mcpp test`. # Part of the mcpp-index self-referential workspace: `[indices] compat` points # at this repo, so the dependency resolves to the checked-in recipe -# (pkgs/s/compat.spdlog.lua). The `compiled` feature mode is covered by the -# design doc's local verification (interface-define propagation of -# SPDLOG_COMPILED_LIB). +# (pkgs/c/compat.spdlog.lua). Compiled mode is covered by the sibling member +# tests/examples/spdlog-compiled/. [package] name = "spdlog-tests" version = "0.1.0"