From 6a998b78d1eabaefd13cf5a0cbebc759bdbfc7e4 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 11 Oct 2026 01:27:53 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix(site):=20=E6=96=87=E6=A1=A3=E6=AD=BB?= =?UTF-8?q?=E9=93=BE=20=E2=80=94=E2=80=94=20=E8=A1=A5=E6=B3=A8=E5=86=8C=20?= =?UTF-8?q?docs=E3=80=81zh=20=E8=AF=91=E6=96=87=E3=80=81base=5Furl,?= =?UTF-8?q?=E5=B9=B6=E5=8A=A0=E5=86=85=E9=83=A8=E9=93=BE=E6=8E=A5=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .xpkgindex.json:注册 descriptor-examples、openkal-compat(含 zh); repository-and-schema 挂 zh 译文;base_url 改为 https://mcpp.index.xlings.org - site-check:新增 tools/site/check_links.py,产物内任一内部死链即失败 - site-check 临时 pin xpkgindex 到 openxlings/xpkgindex#10(相对链接回退、 译文 guide 保持语言),合入后改回默认分支 修复 https://mcpp.index.xlings.org/zh/docs/contributing/descriptor-examples.md 等 9 页 23 条 404。 --- .github/workflows/site-check.yml | 13 ++++++- .xpkgindex.json | 37 +++++++++++++++++-- tools/site/check_links.py | 62 ++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 tools/site/check_links.py diff --git a/.github/workflows/site-check.yml b/.github/workflows/site-check.yml index 0387e133..8133ca6e 100644 --- a/.github/workflows/site-check.yml +++ b/.github/workflows/site-check.yml @@ -15,6 +15,7 @@ on: - '.xpkgindex.json' - '.xpkgindex/**' - 'docs/**' + - 'tools/site/**' - '.github/workflows/site-check.yml' workflow_dispatch: @@ -30,7 +31,10 @@ jobs: python-version: '3.12' - name: Install xpkgindex - run: pip install git+https://github.com/openxlings/xpkgindex.git + # TEMPORARY: pinned to openxlings/xpkgindex#10 (guide link fallback + + # translated guides keep their language) for joint testing. Revert to + # the default branch once it is merged — deploy-site.yml already uses it. + run: pip install git+https://github.com/openxlings/xpkgindex.git@fix/guide-link-fallback - name: Build the site # --strict turns the growth reconciliation warning into an error: if @@ -58,6 +62,13 @@ jobs: test -s "/tmp/site/$f" || { echo "::error::missing $f"; exit 1; } done + - name: Every internal link resolves + # A guide links repository files, the templates link pages, the plugin + # links packages; a dead one fails here whichever layer produced it. + # This is what let /zh/docs/contributing/descriptor-examples.md ship as + # a 404. + run: python3 tools/site/check_links.py /tmp/site + - uses: actions/upload-artifact@v4 if: always() with: diff --git a/.xpkgindex.json b/.xpkgindex.json index 9c9ec71d..fda8f365 100644 --- a/.xpkgindex.json +++ b/.xpkgindex.json @@ -93,7 +93,7 @@ } ] }, - "base_url": "https://mcpplibs.github.io/mcpp-index", + "base_url": "https://mcpp.index.xlings.org", "languages": [ "en", "zh", @@ -169,6 +169,18 @@ "zh": "docs/zh/package-types.md" } }, + { + "slug": "descriptor-examples", + "title": { + "en": "Descriptor examples", + "zh": "描述符示例总览", + "zh-Hant": "描述符範例總覽" + }, + "path": "docs/descriptor-examples.md", + "translations": { + "zh": "docs/zh/descriptor-examples.md" + } + }, { "slug": "cn-mirror", "title": { @@ -183,8 +195,27 @@ }, { "slug": "repository-and-schema", - "title": "Repository & schema", - "path": "docs/repository-and-schema.md" + "title": { + "en": "Repository & schema", + "zh": "仓库结构与 schema", + "zh-Hant": "倉庫結構與 schema" + }, + "path": "docs/repository-and-schema.md", + "translations": { + "zh": "docs/zh/repository-and-schema.md" + } + }, + { + "slug": "openkal-compat", + "title": { + "en": "openkal compatibility", + "zh": "openkal 兼容性", + "zh-Hant": "openkal 相容性" + }, + "path": "docs/openkal-compat.md", + "translations": { + "zh": "docs/zh/openkal-compat.md" + } } ], "cta": { diff --git a/tools/site/check_links.py b/tools/site/check_links.py new file mode 100644 index 00000000..3d3436d5 --- /dev/null +++ b/tools/site/check_links.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Every internal link of a generated site resolves to a page or a file. + + python3 tools/site/check_links.py + +Resolves each relative href/src of every HTML page the way a static host +does — a directory serves its index.html — and lists those that land on +nothing. Exit 1 when there is one. + +The site build reports what it knows to be wrong as warnings; this checks the +output instead, so a dead link fails the pull request whichever layer — a +guide, a template, the plugin — produced it. External URLs are not fetched. +""" + +from __future__ import annotations + +import os +import re +import sys +import urllib.parse + +LINK = re.compile(r'(?:href|src)="([^"]+)"') +EXTERNAL = ("http://", "https://", "//", "#", "mailto:", "data:", "javascript:") + + +def dead_links(site: str) -> dict[str, list[str]]: + dead: dict[str, list[str]] = {} + for dirpath, _, files in os.walk(site): + for name in files: + if not name.endswith(".html"): + continue + page = os.path.join(dirpath, name) + with open(page, encoding="utf-8") as f: + hrefs = LINK.findall(f.read()) + for href in sorted(set(hrefs)): + if href.startswith(EXTERNAL): + continue + path = urllib.parse.unquote(href.split("#")[0].split("?")[0]) + if not path: + continue + target = os.path.normpath(os.path.join(dirpath, path)) + if not (os.path.isfile(target) + or os.path.isfile(os.path.join(target, "index.html"))): + dead.setdefault(os.path.relpath(page, site), []).append(href) + return dead + + +def main() -> int: + if len(sys.argv) != 2: + print(__doc__.strip().splitlines()[2].strip(), file=sys.stderr) + return 2 + dead = dead_links(sys.argv[1]) + for page in sorted(dead): + for href in dead[page]: + print(f"::error::{page}: dead link {href}") + total = sum(len(v) for v in dead.values()) + print(f"{total} dead link(s) on {len(dead)} page(s)") + return 1 if dead else 0 + + +if __name__ == "__main__": + sys.exit(main()) From a699958ee9b3a22953b4c50b75a9b24f9c3e6a9b Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 11 Oct 2026 01:27:53 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat(tomlplusplus):=20build.mcpp=20?= =?UTF-8?q?=E4=B8=AD=E4=B9=9F=E5=8F=AF=20import=20tomlplusplus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit marzer.tomlplusplus 由 Form B 改为 Form A:mcpp = "*/mcpp.toml",install() 把 mcpp.toml([lib] path 显式声明导出面)与上游 master 的 src/modules/tomlplusplus.cppm 写入解包后的 v3.4.0 源码树。 - host module 需要 lib root,Form B 无法声明;mcpp 编译 host module 不带包的 include_dirs,模块单元改为相对自身 include 头文件(mcpp-community/mcpp#797) - 各平台 revision = 1,旧 Form B payload 自动重装(实测迁移通过) - 新成员 tests/examples/marzer.tomlplusplus-build-mcpp:build.mcpp 解析 build-config.toml 并注入 define;与项目侧测试分开(同包双角色 bug,见 #797) --- docs/descriptor-examples.md | 3 +- docs/zh/descriptor-examples.md | 3 +- mcpp.toml | 1 + pkgs/m/marzer.tomlplusplus.lua | 149 +++++++++++++----- .../build-config.toml | 4 + .../marzer.tomlplusplus-build-mcpp/build.mcpp | 14 ++ .../marzer.tomlplusplus-build-mcpp/mcpp.toml | 17 ++ .../tests/build_mcpp.cpp | 6 + 8 files changed, 153 insertions(+), 44 deletions(-) create mode 100644 tests/examples/marzer.tomlplusplus-build-mcpp/build-config.toml create mode 100644 tests/examples/marzer.tomlplusplus-build-mcpp/build.mcpp create mode 100644 tests/examples/marzer.tomlplusplus-build-mcpp/mcpp.toml create mode 100644 tests/examples/marzer.tomlplusplus-build-mcpp/tests/build_mcpp.cpp diff --git a/docs/descriptor-examples.md b/docs/descriptor-examples.md index ef38cce7..0f786cc1 100644 --- a/docs/descriptor-examples.md +++ b/docs/descriptor-examples.md @@ -45,7 +45,8 @@ in the [root README](../README.md#reference-examples). | Whole-source direct build (config snapshot + source list, no external build system) | [`compat.ffmpeg`](../pkgs/c/compat.ffmpeg.lua) (2281 TUs including NASM assembly, declared through 28 directory globs) | | Build-time generator output vendored into the descriptor | [`compat.gmp`](../pkgs/c/compat.gmp.lua) (516 TUs, all three platforms. GMP's build COMPILES AND RUNS seven table generators and substitutes `gmp.h` from `gmp-h.in` — all of it a pure function of limb=64/nail=0, so the outputs are produced once by upstream's own generators and shipped in `generated_files` (~270 KB, of which `trialdivtab.h` is 109 KB). That is what removes the `install()` hook, autotools, and the host compiler its probes needed — and with them the reason windows was deferred, since GMP's generic C only ever needed a GCC-compatible compiler. `generated_files` also carries a one-line forwarding header per source directory, so the package compiles with **no `-I` at all** and `include_dirs` exposes `gmp.h` + `gmpxx.h` rather than GMP's private headers. Verified against a `--disable-assembly` autotools build of the same tarball: identical 598-symbol export set, and GMP's own `make check` passes 177/178 against it) | | Module layer over a compat source build (external Form-A repo) | [`godotengine.godot-cpp-m`](../pkgs/g/godotengine.godot-cpp-m.lua) (two versions tracking upstream: `10.0.0-rc1` = Godot 4.6, `4.5.0` = Godot 4.5. `import godot_cpp;` re-exports the whole `godot` namespace, ~1800 names GENERATED from the headers rather than curated; the 1022-TU build stays in `compat.godot-cpp`, so the index carries only this descriptor. Macros — `GDCLASS`, `GDREGISTER_CLASS`, `memnew`, `ERR_*` — are the one thing a named module cannot export, so the package ships a side header to include next to the import. It also ships a generated `hashfuncs.hpp` shim — upstream's header minus `static` on two functions whose bodies declare an unnamed union — without which GCC refuses the module interface outright, a hard error no `-W` flag reaches) | -| C++23 module wrapper | [`nlohmann.json`](../pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](../pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](../pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](../pkgs/b/boost-ext.ut.lua) (upstream's own `include/boost/ut.cppm` reproduced verbatim but for one `__argc`/`__argv` shim that Clang-on-MSVC needs; namespace `boost-ext` since it is NOT an official Boost library) | +| C++23 module wrapper | [`nlohmann.json`](../pkgs/n/nlohmann.json.lua) · [`neargye.magic_enum`](../pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](../pkgs/b/boost-ext.ut.lua) (upstream's own `include/boost/ut.cppm` reproduced verbatim but for one `__argc`/`__argv` shim that Clang-on-MSVC needs; namespace `boost-ext` since it is NOT an official Boost library) | +| C++23 module wrapper importable from `build.mcpp` too | [`marzer.tomlplusplus`](../pkgs/m/marzer.tomlplusplus.lua) (Form A: `mcpp = "*/mcpp.toml"`, and `install()` writes that manifest plus upstream master's `src/modules/tomlplusplus.cppm` into the unpacked v3.4.0 tree. A `build.mcpp` host module (`[build-dependencies] … host-module = true`) needs a lib root, which a Form B table cannot state, so `[lib] path` is written down instead of inferred. The unit includes its header by a path relative to itself, because mcpp compiles a host module without the package's `include_dirs` ([mcpp#797](https://github.com/mcpp-community/mcpp/issues/797)). Tested both ways: `tests/examples/marzer.tomlplusplus` imports it from the project, `marzer.tomlplusplus-build-mcpp` from `build.mcpp`) | | C++23 module, upstream's own unit | [`khronos.vulkan-hpp`](../pkgs/k/khronos.vulkan-hpp.lua) (Vulkan-Hpp 1.4.357.0 — Khronos generates `vulkan.cppm` / `vulkan_video.cppm` into every Vulkan-Headers release, so `sources` names the two units and NOTHING is authored here; the payload is the same tarball, URL and sha256 as `compat.vulkan-headers`, which is what makes the module and the headers it includes impossible to skew. `import_std = true` is forced by the unit's own unconditional `export import std;`. No `include_dirs`: the headers arrive with the `compat.vulkan` dependency, which is also what satisfies the STATIC dispatcher's direct calls at link — depending on headers alone gives a package that compiles and then fails at every consumer's link. Module names stay upstream's `vulkan` / `vulkan_video`, never `khronos.vulkan`. The second unit `import`s the first and mcpp orders the pair from the scan, which `tests/examples/vulkan-hpp-module/tests/video.cpp` is the regression for) | | C++23 module, upstream's CPU partitions | [`taskflow.taskflow`](../pkgs/t/taskflow.taskflow.lua) (Taskflow 4.1.0, `import tf;`; reuses the four upstream CPU module units and omits the competing CUDA entry point. The checked install hook removes two nonexistent exports, moves the umbrella include/version export into core and imports core first for GCC, and supplies `` to utility for libc++. Only three module files are patched; headers and scheduler implementation stay upstream. No fork; consumers use `tf::version()` because macros are not exported) | | C++23 module, upstream asynchronous logging | [`odygrd.quill`](../pkgs/o/odygrd.quill.lua) (Quill 13.0.0, upstream experimental `quill` module; the checked install hook generates `.cppm` from `src/quill.cc`, guards x86 intrinsics by target architecture and includes Apple Mach headers in the global module fragment. Consumers use `import std; import quill;` and the macro-free API, or define `QUILL_USE_MODULE` and include `quill/LogMacros.h` for logging macros. Bundled fmt needs no separate dependency; Linux links with `-pthread`. Multi-TU logger identity, worker-thread output, formatting and filtering are tested) | diff --git a/docs/zh/descriptor-examples.md b/docs/zh/descriptor-examples.md index e5e9a451..a5b06071 100644 --- a/docs/zh/descriptor-examples.md +++ b/docs/zh/descriptor-examples.md @@ -43,7 +43,8 @@ | 全源码直编(config 快照 + 源列表,零外部构建系统) | [`compat.ffmpeg`](../../pkgs/c/compat.ffmpeg.lua)(2281 TU 含 NASM 汇编,28 个目录 glob 声明) | | 构建期生成器产物内联进描述符 | [`compat.gmp`](../../pkgs/c/compat.gmp.lua)(516 TU,三平台齐全。GMP 的构建要**编译并运行**七个表生成器,还要把 `gmp-h.in` substitute 成 `gmp.h` —— 这些都只是 limb=64/nail=0 的纯函数,故用上游自己的生成器跑一次,产物写进 `generated_files`(约 270 KB,其中 `trialdivtab.h` 占 109 KB)。这一步换掉的是 `install()` 钩子、autotools 以及其探针所需的宿主编译器,连带解掉 windows 推迟的理由 —— GMP 的通用 C 内核从来只要一个 GCC 兼容编译器。`generated_files` 里还按源码目录各放一个一行转发头,于是整包编译**不需要任何 `-I`**,`include_dirs` 只暴露 `gmp.h` + `gmpxx.h`,而不是 GMP 的私有头。与同一 tarball 的 `--disable-assembly` autotools 构建对拍:导出符号 598 个完全一致,上游自带 `make check` 对本产物 177/178 通过) | | 模块层叠在 compat 源码构建之上(外部 Form-A 仓) | [`godotengine.godot-cpp-m`](../../pkgs/g/godotengine.godot-cpp-m.lua)(两个版本与上游对齐:`10.0.0-rc1` 对应 Godot 4.6,`4.5.0` 对应 Godot 4.5。`import godot_cpp;` 重导出整个 `godot` 命名空间,约 1800 个名字由头文件**生成**而非手工罗列;1022 个 TU 的构建留在 `compat.godot-cpp`,索引侧只留这一个描述符。宏 —— `GDCLASS`、`GDREGISTER_CLASS`、`memnew`、`ERR_*` —— 是具名模块唯一带不走的东西,故包内附一个与 import 并排包含的侧头文件。另外还带一份生成的 `hashfuncs.hpp` 遮蔽头 —— 上游那个头去掉两个函数的 `static`(它们体内声明了匿名 union)—— 否则 GCC 直接拒绝该模块接口,且是任何 `-W` 开关都够不到的硬错误) | -| C++23 module wrapper | [`nlohmann.json`](../../pkgs/n/nlohmann.json.lua) · [`marzer.tomlplusplus`](../../pkgs/m/marzer.tomlplusplus.lua) · [`neargye.magic_enum`](../../pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](../../pkgs/b/boost-ext.ut.lua)(逐字复用上游自带的 `include/boost/ut.cppm`,仅加一处 Clang-on-MSVC 需要的 `__argc`/`__argv` shim;命名空间取 `boost-ext`,因其并非 boost 官方库) | +| C++23 module wrapper | [`nlohmann.json`](../../pkgs/n/nlohmann.json.lua) · [`neargye.magic_enum`](../../pkgs/n/neargye.magic_enum.lua) · [`boost-ext.ut`](../../pkgs/b/boost-ext.ut.lua)(逐字复用上游自带的 `include/boost/ut.cppm`,仅加一处 Clang-on-MSVC 需要的 `__argc`/`__argv` shim;命名空间取 `boost-ext`,因其并非 boost 官方库) | +| 也能在 `build.mcpp` 中 import 的 C++23 module wrapper | [`marzer.tomlplusplus`](../../pkgs/m/marzer.tomlplusplus.lua)(Form A:`mcpp = "*/mcpp.toml"`,由 `install()` 把这份清单和上游 master 的 `src/modules/tomlplusplus.cppm` 写进解包后的 v3.4.0 源码树。`build.mcpp` 的 host module(`[build-dependencies] … host-module = true`)需要 lib root,而 Form B 表无法声明它,所以显式写 `[lib] path`,不做推导。模块单元按相对自身的路径 include 头文件,因为 mcpp 编译 host module 时不带包的 `include_dirs`([mcpp#797](https://github.com/mcpp-community/mcpp/issues/797))。两种用法都有测试:`tests/examples/marzer.tomlplusplus` 在项目里 import,`marzer.tomlplusplus-build-mcpp` 在 `build.mcpp` 里 import) | | C++23 module,上游自带单元 | [`khronos.vulkan-hpp`](../../pkgs/k/khronos.vulkan-hpp.lua)(Vulkan-Hpp 1.4.357.0 —— Khronos 把 `vulkan.cppm` / `vulkan_video.cppm` 生成进每个 Vulkan-Headers release,所以 `sources` 点名这两个单元即可,本仓**不写一行**包装体;载荷与 `compat.vulkan-headers` 是同一份 tarball、同一个 URL 与 sha256,这让模块与它 include 的头不可能错配。`import_std = true` 由单元自身无条件的 `export import std;` 决定。不声明 `include_dirs`:头随 `compat.vulkan` 依赖到达,而该依赖同时满足静态 dispatcher 在链接期的直接调用 —— 只依赖头会得到一个「能编译、每个消费者都链接失败」的包。模块名保持上游的 `vulkan` / `vulkan_video`,绝不写成 `khronos.vulkan`。第二个单元 `import` 第一个,顺序由 mcpp 扫描决定,`tests/examples/vulkan-hpp-module/tests/video.cpp` 就是这条的回归)| | C++23 module,上游 CPU 分区 | [`taskflow.taskflow`](../../pkgs/t/taskflow.taskflow.lua)(Taskflow 4.1.0,`import tf;`;复用上游四个 CPU 模块单元,排除提供同名主模块的 CUDA 入口。安装钩子逐项校验匹配次数:移除两个不存在的导出,将总头文件及版本导出移至 core 并优先导入 core 以兼容 GCC,为 utility 补 `` 以兼容 libc++。仅适配三个模块文件,头文件和调度实现保持上游原样,无独立 fork;宏不随模块导出,版本查询使用 `tf::version()`) | | C++23 module,上游异步日志 | [`odygrd.quill`](../../pkgs/o/odygrd.quill.lua)(Quill 13.0.0,上游实验性 `quill` 模块;安装钩子以 `src/quill.cc` 生成 `.cppm`,精确适配 x86 intrinsic 的架构条件与 Apple Mach 头的全局模块归属。消费者使用 `import std; import quill;` 和无宏 API,或定义 `QUILL_USE_MODULE` 并包含 `quill/LogMacros.h` 使用日志宏。自带 fmt,无需额外依赖;Linux 链接使用 `-pthread`。测试覆盖多 TU logger 身份、工作线程输出、格式化和过滤) | diff --git a/mcpp.toml b/mcpp.toml index d78f45cf..c316118e 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -74,6 +74,7 @@ members = [ "tests/examples/imgui-window", "tests/examples/jwt-cpp", "tests/examples/marzer.tomlplusplus", + "tests/examples/marzer.tomlplusplus-build-mcpp", "tests/examples/mcpp-plugins", "tests/examples/mio", "tests/examples/muduo", diff --git a/pkgs/m/marzer.tomlplusplus.lua b/pkgs/m/marzer.tomlplusplus.lua index e37c240d..cff7e774 100644 --- a/pkgs/m/marzer.tomlplusplus.lua +++ b/pkgs/m/marzer.tomlplusplus.lua @@ -1,31 +1,40 @@ --- Form B inline descriptor for toml++ (marzer/tomlplusplus) — a TOML config --- file parser and serializer for C++17 (and later), exposed as the C++23 --- module `tomlplusplus` so users can write `import tomlplusplus;` out of the --- box (no opt-in, no `#include` needed). +-- toml++ (marzer/tomlplusplus) — a TOML config file parser and serializer for +-- C++17 (and later), exposed as the C++23 module `tomlplusplus`: `import +-- tomlplusplus;` works out of the box, from the project AND from build.mcpp: -- --- Why generated: the released v3.4.0 source tarball is header-only and ships --- NO module interface unit. Upstream HAS authored an official one at --- `src/modules/tomlplusplus.cppm` (`export module tomlplusplus;`), but it --- lives on the `master` branch only and is not in any release tag yet (v3.4.0 --- 404s for that path). So we provide it ourselves via mcpp's `generated_files`, --- embedding upstream's official tomlplusplus.cppm. The base headers stay --- pinned to the reproducible v3.4.0 release tag, straight from upstream — +-- [build-dependencies.marzer] +-- tomlplusplus = { version = "3.4.0", host-module = true } +-- +-- Form A, with install() writing the manifest. Why not Form B (`mcpp = {...}` +-- with generated_files), which this package used before: +-- * a build.mcpp host module needs a lib root, `[lib] path` or +-- `src/.cppm`, and the Form B vocabulary has no `lib` key. The lib +-- root states the export surface, so it is written down here, not inferred. +-- * mcpp reads no verdir mcpp.toml while a Form B table is present, and +-- generated_files exists only inside that table — so the manifest is +-- written by install(), into the unpacked tree, next to upstream's files. +-- +-- Why generated at all: the released v3.4.0 tarball is header-only and ships NO +-- module interface unit. Upstream HAS authored an official one at +-- `src/modules/tomlplusplus.cppm` (`export module tomlplusplus;`), but only on +-- `master` (v3.4.0 404s for that path). install() writes it at that same path, +-- so the payload has the shape a future release will have. The base headers +-- stay pinned to the reproducible v3.4.0 release tag, straight from upstream — -- no fork in the trust path. -- --- ONE deviation from upstream master's cppm, deliberate and minimal: --- `using TOML_NAMESPACE::get_line;` is dropped. `get_line` was added to --- `impl/source_region.hpp` AFTER v3.4.0 and does not exist in the pinned --- headers, so re-exporting it would not compile. Every other line is verbatim. +-- TWO deviations from upstream master's cppm, deliberate and minimal: +-- * `using TOML_NAMESPACE::get_line;` is dropped: `get_line` was added to +-- `impl/source_region.hpp` AFTER v3.4.0 and does not exist in the pinned +-- headers. +-- * the global module fragment includes `"../../include/toml++/toml.hpp"` +-- instead of ``: mcpp compiles a host module alone, with +-- none of the package's include_dirs (mcpp-community/mcpp#797). The +-- relative path names the same file in every compile, so ordinary +-- consumers are unaffected. -- -- Evolution: once a toml++ release (>3.4.0) ships src/modules/tomlplusplus.cppm, --- switch `sources` to "*/src/modules/tomlplusplus.cppm", drop `generated_files`, --- and the get_line re-export comes back with it. --- --- include_dirs exposes the tarball's include/ so the module unit's global-module --- fragment `#include ` resolves (and `#include` remains --- available to users who want it). The upstream path is a GLOB — the leading --- `*` absorbs the archive's `tomlplusplus-3.4.0/` wrap layer — while the --- generated cppm path is verdir-relative (no glob), like nlohmann.json. +-- install() stops writing the unit (keeping only mcpp.toml), and get_line comes +-- back with it; once mcpp#797 lands, the include returns to ``. package = { spec = "1", namespace = "marzer", @@ -43,6 +52,8 @@ package = { CN = "https://gitcode.com/mcpp-res/tomlplusplus/releases/download/3.4.0/tomlplusplus-3.4.0.tar.gz", }, sha256 = "8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155", + -- 1: Form B -> Form A (install() writes mcpp.toml + the module unit). + revision = 1, }, }, macosx = { @@ -52,6 +63,8 @@ package = { CN = "https://gitcode.com/mcpp-res/tomlplusplus/releases/download/3.4.0/tomlplusplus-3.4.0.tar.gz", }, sha256 = "8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155", + -- 1: Form B -> Form A (install() writes mcpp.toml + the module unit). + revision = 1, }, }, windows = { @@ -61,21 +74,46 @@ package = { CN = "https://gitcode.com/mcpp-res/tomlplusplus/releases/download/3.4.0/tomlplusplus-3.4.0.tar.gz", }, sha256 = "8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155", + -- 1: Form B -> Form A (install() writes mcpp.toml + the module unit). + revision = 1, }, }, }, - mcpp = { - schema = "0.1", - language = "c++23", - import_std = false, - modules = { "tomlplusplus" }, - include_dirs = { "*/include" }, - -- Upstream's official module unit (master @ src/modules/tomlplusplus.cppm), - -- reproduced verbatim apart from the get_line drop documented above. - -- Verdir-relative path, no glob. - generated_files = { - ["mcpp_generated/tomlplusplus.cppm"] = [==[ + -- `*` absorbs the archive's tomlplusplus-/ wrap layer. + mcpp = "*/mcpp.toml", +} + +local MCPP_TOML = [==[ +[package] +namespace = "marzer" +name = "tomlplusplus" +version = "@VERSION@" +standard = "c++23" + +[language] +import_std = false + +# The export surface: the one module unit. Required by build.mcpp's +# host-module path, which has no other way to find it. +[lib] +path = "src/modules/tomlplusplus.cppm" + +[modules] +exports = ["tomlplusplus"] + +[build] +sources = ["src/modules/tomlplusplus.cppm"] +# The module unit's GMF and users who prefer `#include `. +include_dirs = ["include"] + +[targets.tomlplusplus] +kind = "lib" +]==] + +-- Upstream's official module unit (master @ src/modules/tomlplusplus.cppm), +-- reproduced verbatim apart from the two deviations documented above. +local MODULE_UNIT = [==[ /** * @file tomlpp.cppm * @brief File containing the module declaration for toml++. @@ -84,7 +122,10 @@ package = { module; #define TOML_UNDEF_MACROS 0 -#include +// mcpp#797: a host-module compile is not given this package's include_dirs, +// so reach the header relative to this file (src/modules/ -> include/). +// Same file either way; restore `` once mcpp#797 is settled. +#include "../../include/toml++/toml.hpp" export module tomlplusplus; @@ -163,10 +204,34 @@ export namespace toml { using TOML_NAMESPACE::preserve_source_value_flags; } -]==], - }, - sources = { "mcpp_generated/tomlplusplus.cppm" }, - targets = { ["tomlplusplus"] = { kind = "lib" } }, - deps = { }, - }, -} +]==] + +import("xim.libxpkg.pkginfo") + +function install() + -- Reproduce the default unpack shape — install_dir//... — so the + -- `*/mcpp.toml` pointer matches exactly one wrap level. NO SHELL and no + -- directory listing in this sandbox: the wrap is asked about by name. + local v = pkginfo.version() + local idir = pkginfo.install_dir() + local layer = path.join(idir, "tomlplusplus-" .. v) + os.tryrm(idir) + os.mkdir(idir) + for _, name in ipairs({ "tomlplusplus-" .. v, "tomlplusplus-v" .. v }) do + if os.isfile(path.join(name, "include", "toml++", "toml.hpp")) then + os.mv(name, layer) + break + end + end + if not os.isfile(path.join(layer, "include", "toml++", "toml.hpp")) then + log.error("tomlplusplus: no include/toml++/toml.hpp under %s after " + .. "unpacking; the archive layout changed", layer) + return false + end + + io.writefile(path.join(layer, "mcpp.toml"), + (MCPP_TOML:gsub("@VERSION@", v))) + os.mkdir(path.join(layer, "src", "modules")) + io.writefile(path.join(layer, "src", "modules", "tomlplusplus.cppm"), MODULE_UNIT) + return true +end diff --git a/tests/examples/marzer.tomlplusplus-build-mcpp/build-config.toml b/tests/examples/marzer.tomlplusplus-build-mcpp/build-config.toml new file mode 100644 index 00000000..2a4f61b1 --- /dev/null +++ b/tests/examples/marzer.tomlplusplus-build-mcpp/build-config.toml @@ -0,0 +1,4 @@ +# Read by build.mcpp through `import tomlplusplus;`; the value reaches the +# tests as a compile define (tests/build_mcpp.cpp). +[server] +port = 8080 diff --git a/tests/examples/marzer.tomlplusplus-build-mcpp/build.mcpp b/tests/examples/marzer.tomlplusplus-build-mcpp/build.mcpp new file mode 100644 index 00000000..b5f3f403 --- /dev/null +++ b/tests/examples/marzer.tomlplusplus-build-mcpp/build.mcpp @@ -0,0 +1,14 @@ +// toml++ imported by the BUILD PROGRAM, not only by the project: declared as a +// host module in [build-dependencies], it parses build-config.toml and hands a +// value to the build as a define. tests/build_mcpp.cpp asserts it arrived. +import std; +import mcpp; +import tomlplusplus; + +int main() { + auto cfg = toml::parse_file("build-config.toml"); + auto flag = std::format("-DTOMLPP_BUILD_PORT={}", cfg["server"]["port"].value_or(0)); + mcpp::cxxflag(flag.c_str()); + mcpp::rerun_if_changed("build-config.toml"); + return 0; +} diff --git a/tests/examples/marzer.tomlplusplus-build-mcpp/mcpp.toml b/tests/examples/marzer.tomlplusplus-build-mcpp/mcpp.toml new file mode 100644 index 00000000..8cbed885 --- /dev/null +++ b/tests/examples/marzer.tomlplusplus-build-mcpp/mcpp.toml @@ -0,0 +1,17 @@ +# toml++ imported by build.mcpp: the package as a host module +# ([build-dependencies] host-module = true). build.mcpp parses +# build-config.toml and turns a value into a define the test asserts. +# +# A member of its own rather than part of marzer.tomlplusplus: one package in +# BOTH [dependencies] and [build-dependencies] (host-module) currently loses its +# target-side BMI (mcpp-community/mcpp#797), so the two uses are tested apart. +# Same [indices] override as marzer.tomlplusplus (root declares `compat`). +[indices] +marzer = { path = "../../.." } + +[package] +name = "tomlplusplus-build-mcpp-tests" +version = "0.1.0" + +[build-dependencies.marzer] +tomlplusplus = { version = "3.4.0", host-module = true } diff --git a/tests/examples/marzer.tomlplusplus-build-mcpp/tests/build_mcpp.cpp b/tests/examples/marzer.tomlplusplus-build-mcpp/tests/build_mcpp.cpp new file mode 100644 index 00000000..d3ba5061 --- /dev/null +++ b/tests/examples/marzer.tomlplusplus-build-mcpp/tests/build_mcpp.cpp @@ -0,0 +1,6 @@ +// build.mcpp ran with toml++ as a host module and parsed build-config.toml; the +// value it read reaches this TU as a define. +#ifndef TOMLPP_BUILD_PORT +#error "build.mcpp (import tomlplusplus;) did not run or its cxxflag did not arrive" +#endif +int main() { return TOMLPP_BUILD_PORT == 8080 ? 0 : 1; } From 0f3ae8812712fd09f74dd3c5d82f9edf0e0081ce Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 11 Oct 2026 01:27:53 +0900 Subject: [PATCH 3/5] =?UTF-8?q?docs(agents):=20=E7=AB=99=E7=82=B9=E6=AD=BB?= =?UTF-8?q?=E9=93=BE=E4=B8=8E=20Form=20B=20host-module=20=E5=88=86?= =?UTF-8?q?=E6=9E=90/=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...te-doc-links-and-formb-host-module-plan.md | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 .agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md diff --git a/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md b/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md new file mode 100644 index 00000000..272ab1f8 --- /dev/null +++ b/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md @@ -0,0 +1,261 @@ +# 站点文档死链 + Form B 模块包无法被 build.mcpp 使用 —— 分析与优化方案 + +日期:2026-10-11 · 状态:实施中(见文末「落地」) · 涉及仓:mcpp-index / openxlings/xpkgindex / mcpp-community/mcpp + +--- + +## Part 1 · 站点文档链接 404 + +### 1.1 现象与复现 + +`https://mcpp.index.xlings.org/zh/docs/contributing/descriptor-examples.md` → 404。 + +本地复现(与 `site-check.yml` 同参数)+ 对产物做内部链接检查: + +```bash +xpkgindex generate . --output /tmp/site --offline +python3 check_links.py /tmp/site # 遍历 html 的 href/src,校验目标文件存在 +``` + +结果:**9 个页面、23 条坏链**,全部集中在 docs 页面(包页面无坏链): + +| 页面 (en / zh / zh-Hant 同构) | 坏链 | +|---|---| +| `docs/contributing/` | `descriptor-examples.md`、`openkal-compat.md`、`repository-and-schema.md`(仅 zh)、`../.agents/skills/add-mcpp-index-package/SKILL.md`、`zh/` / `../` | +| `docs/package-types/` | `descriptor-examples.md`、`repository-and-schema.md#包身份namespace-name`(仅 zh) | +| `docs/repository-and-schema/` | `openkal-compat.md`、`zh/repository-and-schema.md`(语言切换行未被剥离) | + +### 1.2 根因 + +**R1(xpkgindex,主因)** —— `xpkgindex/guides.py::_rewrite_links` 只把指向 **已注册 docs entry** 的相对链接改写为 +`docs//`;其余相对链接**原样输出**,在 `/docs//` 下解析即 404。 +其 docstring 写的是 *"Point relative markdown links at the rendered guide, **or at the repo**"*, +但 "or at the repo" 分支从未实现(本地 0.2.0 与上游 main HEAD 一致)。 + +**R2(mcpp-index 配置)** —— `.xpkgindex.json` 的 `docs.entries` 漏注册: +- `descriptor-examples.md`(en+zh 都存在)—— 用户报告的那条; +- `openkal-compat.md`(en+zh 都存在); +- `repository-and-schema` 已注册但**没挂 zh 翻译**(`docs/zh/repository-and-schema.md` 存在)→ + zh 页面指向它的链接不在映射表里、en 页面的 `[简体中文](zh/…)` 语言行也剥不掉。 + +**R3(文档链到了"非文档"文件)** —— 这类链接在 GitHub 上是对的,站点上没有对应页面: +- `../.agents/skills/…/SKILL.md`、`../README.md#reference-examples` → 仓库文件; +- `zh/`、`../` → 目录; +- `descriptor-examples.md` 内 38 条 `../pkgs/x/*.lua` → 实际上站点有对应包页 `packages//`。 + (该页一旦注册,这 38 条会立刻成为新坏链 —— 所以 R2 的修复要和 R1 的修复配套。) + +**R4(无守卫)** —— `site-check.yml` 只校验构建无 warning + 几个落地页存在,不查内部链接,所以死链能一路合入。 + +**R5(低优先级)** —— `.xpkgindex.json` `base_url = https://mcpplibs.github.io/mcpp-index`,而站点实际服务在 +`mcpp.index.xlings.org`;线上 `sitemap.xml` / feed 全是旧域名。 + +### 1.3 方案 + +| # | 位置 | 改动 | 解决 | +|---|---|---|---| +| F1 | mcpp-index `.xpkgindex.json` | `docs.entries` 新增 `descriptor-examples`、`openkal-compat`(均带 `translations.zh`);`repository-and-schema` 补 `translations.zh` 与 zh/zh-Hant 标题 | R2,用户报告的 URL 直接恢复 | +| F2 | xpkgindex `guides.py` | 实现 docstring 承诺的回退:未命中 slug 的相对链接 →
① `pkgs/**/.lua` → 站内包页 `packages//`(用构建期已有的 path→package 映射)
② 仓库内其它文件 → `{links.github}/blob//`,目录 → `/tree/`
③ 指向 docs 目录本身(`zh/`、`../`)→ docs landing
④ 目标在仓库里不存在 → 发 `warning:`(site-check 的 "No warnings" 自动拦截) | R1、R3 | +| F3 | mcpp-index `site-check.yml` | 新增一步内部链接检查(上面的脚本放到 `tools/site/check_links.py`),坏链即失败 | R4 | +| F4 | mcpp-index `.xpkgindex.json` | `base_url` 改为 `https://mcpp.index.xlings.org`(需确认它就是规范域名) | R5 | + +**顺序建议**:F1 先单独合(当前整页 404,注册后即便 pkgs 链接暂时坏也是净改善)→ F2 提上游 PR → +F2 发布后再上 F3(否则 F3 会被 38 条 `.lua` 链接卡死;或 F3 先上并带临时 allowlist)。F4 随 F1 一起。 + +**不建议**:把文档里的相对链接改成绝对 GitHub URL —— 治标、量大(~80 处),且丢掉 `.lua → 包页` 这种站内更好的落点。 + +--- + +## Part 2 · `marzer.tomlplusplus` 不能在 `build.mcpp` 中使用 + +### 2.1 结论 + +**是的,目前用不了**。普通 `[dependencies]` 消费正常;但作为 `build.mcpp` 的 host module +(`[build-dependencies] … host-module = true` 后 `import tomlplusplus;`)会失败,而且是**两层**问题, +"用 generated_files 生成 mcpp.toml 再补 lib.path" 的思路**不可行**(见 2.3)。 + +### 2.2 复现与根因(mcpp 2026.10.5.2 实测) + +```toml +# mcpp.toml +[build-dependencies.marzer] +tomlplusplus = { version = "3.4.0", host-module = true } +``` +```cpp +// build.mcpp +import std; import tomlplusplus; +int main() { auto t = toml::parse("x = 42\n"); + std::println("mcpp:cxxflag=-DTOML_X={}", t["x"].value_or(0)); } +``` + +**L1 —— 找不到 lib root** +``` +error: host module 'tomlplusplus': no interface unit at /src/tomlplusplus.cppm + A package offering build rules must have a lib root (src/.cppm or [lib] path). +``` +- `features.cpp::host_module_units` → `resolve_lib_root_path`:只认 `manifest.lib.path`,否则约定 `src/.cppm`。 +- Form B 词表 `kKnownXpkgKeys`(`modules/manifest/src/xpkg.cppm`)**没有 `lib` 键**,描述符无从指定。 +- 我们的 cppm 生成在 `mcpp_generated/tomlplusplus.cppm` → 永远落空。 + +**L2 —— host module 编译不带包的 include_dirs**(把 cppm 挪到 `src/` 后暴露) +``` +error: host module 'tomlplusplus' compile failed: +src/tomlplusplus.cppm:9:10: fatal error: toml++/toml.hpp: No such file or directory +``` +- `host_module_compile.cppm::provide_host_module` 的 argv = `compiler std -fmodules -c + base + useFlags`, + **不含提供方的 `include_dirs` / `defines` / `cxxflags`**,也不含其依赖的公开 include。 +- 附带:host module 只编 interface 这一个 TU,提供方的实现单元(.cpp)不会进 build program 的链接(header-only 无影响)。 + +### 2.3 为什么 "generated_files → mcpp.toml + lib.path" 不行 + +1. 描述符有 `mcpp = { … }`(Form B)时,mcpp 用内联 manifest,**根本不读** verdir 里的 mcpp.toml + (`graph_load.cpp`:只有描述符**没有** `mcpp` 字段时才 glob `mcpp.toml` / `*/mcpp.toml`)。 +2. 反过来用 Form A 指针(`mcpp = "/mcpp.toml"`),就没有 `generated_files` 可用(它是 Form B 专属键)。 +3. 即便 lib.path 设上,L2 依旧失败。 + +### 2.4 影响面 + +索引内 **7 个用 `generated_files` 合成 cppm 的模块包全部命中 L1**: +`boost-ext.ut`、`chriskohlhoff.asio`、`fmtlib.fmt`、`marzer.tomlplusplus`、`mpusz.mp-units`、`neargye.magic_enum`、`nlohmann.json`; +指向上游 cppm(`*/…` glob 路径)的 Form B 模块包同样不在 `src/.cppm`。GMF 里 `#include <…>` 依赖 +`include_dirs` 的,修了 L1 还会命中 L2。即:**目前 Form B 模块包基本都不能当 host module**,不是 toml++ 个例。 + +### 2.5 方案 + +**方案 B(推荐,根治,改 mcpp)** +- B1 lib root 推导:Form B 且 `lib.path` 为空、约定路径不存在时,从已列出的 `sources` 中取声明了 `modules[0]` + (或与包名同名)的 interface unit 作为 lib root。无需新语法,索引零改动,7 个包一起受益。 + (备选:Form B 词表加 `lib = ""` 键 —— 需要升 `min_mcpp` 并逐包改描述符,不如推导。) +- B2 host module 编译继承提供方的编译输入:`include_dirs`(含 `_after`)、`defines`、`cxxflags`、 + 其依赖的公开 include;这些同时进入 host-module 缓存 key(`common_inputs`)。 +- B3(可选)提供方的非 interface 源一并编成 host-module 对象并链接进 build program。 +- 索引侧配套:新增成员 `tests/examples/build-mcpp-host-module`(build.mcpp `import tomlplusplus;` + `nlohmann.json` + 断言注入的宏),锁住行为;`index.toml` 的 `min_mcpp` 随 CI pin 一起抬。 + +**方案 A(索引侧临时绕过,已实测通过,仅在有人急用时采用)** +```lua +generated_files = { + ["src/tomlplusplus.cppm"] = [==[ -- 放到约定 lib root,解决 L1 +module; +#define TOML_UNDEF_MACROS 0 +#if __has_include() +#include +#else // host-module 编译拿不到 include_dirs(L2),按相对路径兜底 +#include "../tomlplusplus-3.4.0/include/toml++/toml.hpp" +#endif +...]==], +}, +sources = { "src/tomlplusplus.cppm" }, +``` +实测:上面的 build.mcpp 成功运行,主程序打印 `42`;现有成员 `tests/examples/marzer.tomlplusplus` 仍 `1 passed`。 +代价:wrap 目录名 `tomlplusplus-3.4.0` 写死在 cppm 里(GLOBAL 与 CN 两个 tarball 实测同名),每次升版本要跟着改; +偏离了"cppm 与上游逐字一致"的原则;只救 toml++ 一个包。B 落地后应回退。 + +**建议**:走 B(给 mcpp 提 issue/PR,B1+B2 一起),A 不默认合入。 + +--- + +### 2.6 讨论记录(2026-10-11 Review 后) + +Review 结论:Part 1 顺序认可、`mcpp.index.xlings.org` 为规范域名;xpkgindex 直接提 PR 联调; +**lib root 是导出面的显式控制,不做推导**(B1 的"推导"选项作废)。 + +**Q:为什么普通依赖 OK,build-dependencies(host-module)不行?** + +两条路径是**两套独立实现**,对"包导出什么、在什么上下文里编"的回答不同: + +| | 普通 `[dependencies]` | `host-module = true` | +|---|---|---| +| 实现位置 | 完整构建图(modgraph 扫描 → ninja) | `build_program` 内的迷你编译器(`features.cpp::host_module_units` + `host_module_compile.cppm`) | +| 导出面 | 扫描 `sources`,每个 `export module X` 单元都可 import;Form B `modules = {…}` → `modules.exports_` 做声明校验 | lib root(`[lib] path` / `src/.cppm`)**必须存在**(硬错误),再加 `sources` 中其余 interface 单元;不读 `exports_` | +| lib root 缺失 | 仅 warning(影响的是 `mcpp pack`,不影响消费) | `no interface unit at …` 直接失败 | +| 编译上下文 | 提供方自己的 `include_dirs` / `defines` / `cxxflags` / 依赖的公开 include | 单元**单独编译**:只有 build.mcpp 的 `base` + std/mcpp/其他 host module 的 BMI | + +host-module 路径是为**规则包**(`mcpp.plugins`、`grpcgen`:纯模块、只 import std/mcpp)设计的,文档原话 +"units are otherwise compiled alone, so they import std, mcpp and nothing else"。它从没覆盖 +"在 build.mcpp 里用一个**普通第三方库**"这个场景 —— 这才是缺口本身。Cargo 的对应物是 build-dependencies +作为完整 crate 为 host 编译,而不是只编一个入口文件。 + +实测对照(同一个 Form A 包:`mcpp.toml` 含 `[lib] path` + `[build] include_dirs`,path 依赖): +- `[dependencies]` + `src/main.cpp` 里 `import tomlplusplus;` → 运行输出 `42` ✅ +- `[build-dependencies] host-module = true` + build.mcpp 里 `import tomlplusplus;` → + `toml++/toml.hpp: No such file or directory` ❌(L1 已过,卡 L2) + +**Q:再生成一个 mcpp.toml,用 Form A 指针 `mcpp = "/mcpp.toml"` 指过去,是否可行?** + +不可行,两层原因: +1. **生成不出来**:指针形式下描述符没有 `mcpp = { … }` 表,也就没有 `generated_files`(它只存在于 Form B 表里); + mcpp.toml 必须在 xlings 安装完就已在 payload 中 —— 只能重打 tarball(破坏"上游官方源、信任链无 fork", + 且 GLOBAL/CN 两份源会分叉)或写 install hook(把构建配置藏进安装副作用)。 + 另外 Form B 表存在时 mcpp 不读 verdir 里的 mcpp.toml(`graph_load.cpp`),两者无法共存。 +2. **生成出来也没用**:上面的实测已经是"有一个带 `[lib] path` 的真实 mcpp.toml"的情形,仍然卡在 L2。 + mcpp.toml 只能解决 L1,而 L1 用 Form B 加一个 `lib` 键就能显式解决,不必换形态。 + +**修订后的方案 B(mcpp 侧)** +- **B2(核心,先做)**:host-module 编译使用提供方自己的编译上下文 —— `include_dirs`(含 `_after`)、`defines`、 + `cxxflags`、依赖的公开 include —— 并纳入 host-module 缓存 key。本质是让 host-module 复用普通依赖的 + per-package 编译输入,只把工具链/标准换成 host 的。 +- **B1'(显式,不推导)**:Form B 词表新增 `lib = ""`,1:1 映射 `manifest.lib.path` + (例:`lib = "mcpp_generated/tomlplusplus.cppm"`)。lib root 仍由作者显式控制导出面。 + 需随 CI pin 抬 `min_mcpp`,再逐包给 7 个 generated-cppm 包补 `lib`。 +- **B3(可选)**:提供方的非 interface 源编成 host 对象并链接进 build program(非 header-only 库需要)。 +- 索引侧:B1'+B2 发布后新增 `tests/examples/build-mcpp-host-module` 成员锁行为。 + +### 2.7 讨论记录(第二轮) + +Review 结论:mcpp 侧涉及一般 deps 与 build deps 的规范/架构设计,**不改 mcpp 代码**,只提详细 issue +(已提交:[mcpp-community/mcpp#797](https://github.com/mcpp-community/mcpp/issues/797))。2.6 中的 B2/B1'/B3 降级为 issue 里的候选方向。 + +**Q:toml++ 用 Form A `mcpp = "*/mcpp.toml"`,由 install hook 生成 mcpp.toml,可行吗?** + +**可行,已实测**。2.6 中"生成不出来"的判断不成立:索引里已有 install hook 写文件的先例 +(`compat.libffi`、`compat.cu*` 用 `io.writefile`),payload 里的 mcpp.toml 可以由 hook 产出。 +但要点在于:**让它跑通的是相对 include,不是 Form A**。 + +实验描述符(`mcpp xpkg parse` → `form A … parse OK`): +```lua +mcpp = "*/mcpp.toml", -- `*` 吸收 tomlplusplus-/ 包装层 +... +function install() + -- 解包到 install_dir/tomlplusplus-/,写入: + -- mcpp.toml [lib] path = "src/modules/tomlplusplus.cppm" + -- [build] include_dirs = ["include"] + -- src/modules/tomlplusplus.cppm 上游 master 的同一路径;GMF 用 "../../include/toml++/toml.hpp" +end +``` + +| 用法 | `#include "../../include/toml++/toml.hpp"` | `#include `(上游原样) | +|---|---|---| +| build.mcpp host-module | ✅ 打印 42 | ❌ `toml++/toml.hpp: No such file`(L2) | +| 普通依赖(`tests/examples/marzer.tomlplusplus`) | ✅ 1 passed | ✅ | + +结论:Form A 用显式的 `[lib] path` 解决了 L1,符合"lib root 显式控制导出面"。L2 只能靠相对 include 绕过, +直到 mcpp 侧给出设计。 + +**Form A + hook vs Form B + `src/` 路径(2.5 方案 A)** + +| | Form A + install hook | Form B,生成到 `src/tomlplusplus.cppm` | +|---|---|---| +| lib root | `[lib] path` 显式 | 依赖约定路径,碰巧命中 | +| 相对 include | `../../include/…`,与版本无关(`*/mcpp.toml` 吸收包装层) | `../tomlplusplus-3.4.0/include/…`,版本写死 | +| 与上游演进 | cppm 已位于上游 master 路径 `src/modules/`;上游发版后删掉 hook 中写 cppm 的那步即可 | 需改路径与 sources | +| 静态校验 | `mcpp xpkg parse` 只看到 form A,mcpp.toml 内容在 Lua 字符串里,CI lint 校验不到 | lint 可完整校验 | +| 安装语义 | 构建配置变成安装副作用;hook 接管解包;改描述符要升 `revision` 触发重装 | mcpp 在构建期物化 generated_files | +| 一致性 | mcpp.toml 里的 version 与描述符重复(可用 `pkginfo.version()` 模板化) | 单一来源 | + +建议:若 toml++ 需要先在 build.mcpp 中可用,走 **Form A + install hook**(导出面显式、版本无关、贴近上游); +用 `install()`,不用 `config()`(payload 内容属于安装,`config` 管环境注册)。 +相对 include 在注释和 issue 中标为临时措施,mcpp 侧定案后恢复 ``。其余 6 个包暂不跟进。 + +## 落地(2026-10-11) + +| 项 | 位置 | 内容 | +|---|---|---| +| mcpp issue | [mcpp#797](https://github.com/mcpp-community/mcpp/issues/797) | 一般 deps 与 build deps(host-module)在导出面与编译上下文上的分歧;设计问题 6 条,不预设实现。追加评论:同一包同时写在 `[dependencies]` 与 `[build-dependencies]`(host-module)时,合并后的边带 `hostModule`,被 build-time-only 剪枝误判为 target 不可达,项目侧 BMI 丢失 | +| xpkgindex | [openxlings/xpkgindex#10](https://github.com/openxlings/xpkgindex/pull/10) | F2:未命中的相对链接 → 包页 / 目录 README 的 guide / `{github}/blob\|tree/HEAD/…` / 不存在则 warning;另修译文 guide 链接以 `depth=3` 跳回默认语言的问题 | +| mcpp-index 站点 | `.xpkgindex.json` | F1 注册 `descriptor-examples`、`openkal-compat`(含 zh);`repository-and-schema` 挂 zh 译文;F4 `base_url` → `https://mcpp.index.xlings.org` | +| mcpp-index CI | `site-check.yml` + `tools/site/check_links.py` | F3 产物内部链接检查;xpkgindex **临时 pin** 到 #10 分支联调,#10 合入后改回默认分支 | +| toml++ | `pkgs/m/marzer.tomlplusplus.lua` | Form A + `install()` 写 `mcpp.toml`(`[lib] path`)与 `src/modules/tomlplusplus.cppm`(相对 include,mcpp#797);各平台 `revision = 1` 迫使旧 Form B payload 重装(实测迁移通过) | +| 测试 | `tests/examples/marzer.tomlplusplus-build-mcpp` | build.mcpp `import tomlplusplus;` 解析 `build-config.toml` → define → 断言;与项目侧测试分成两个成员(规避上面的双角色 bug) | + +已知限制:`revision` 自 mcpp 2026.9.27.1 生效,而 `min_mcpp = 2026.9.18.3`;在两者之间的 mcpp 上, +若本机缓存着旧 Form B payload,会报 `mcpp pointer '*/mcpp.toml' did not match`,清掉该 payload 即可。 From 3c88bff38e06acd960c62aea113148799ff51342 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 11 Oct 2026 01:30:24 +0900 Subject: [PATCH 4/5] =?UTF-8?q?test(tomlplusplus):=20=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E4=BE=A7=E6=88=90=E5=91=98=E6=B3=A8=E6=98=8E=20build.mcpp=20?= =?UTF-8?q?=E7=94=A8=E6=B3=95=E7=9A=84=E5=85=84=E5=BC=9F=E6=88=90=E5=91=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同时让 CI 选中该成员:描述符变更按 mcpp.toml 中出现 'marzer.tomlplusplus' 字面量选成员,而 [dependencies.marzer] 写法不含该字面量。 --- tests/examples/marzer.tomlplusplus/mcpp.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/examples/marzer.tomlplusplus/mcpp.toml b/tests/examples/marzer.tomlplusplus/mcpp.toml index 0c1ceb77..73bacf5f 100644 --- a/tests/examples/marzer.tomlplusplus/mcpp.toml +++ b/tests/examples/marzer.tomlplusplus/mcpp.toml @@ -1,5 +1,9 @@ # toml++ test project: consumes the C++23 module `tomlplusplus` and asserts # parse + typed access + round-trip serialization under `mcpp test`. +# The same module imported from build.mcpp (a host module) is tested by the +# sibling member marzer.tomlplusplus-build-mcpp, kept apart because one package +# in both [dependencies] and [build-dependencies] currently loses its +# target-side BMI (mcpp-community/mcpp#797). # Overrides the workspace-root redirect: root declares `compat`, this member # needs `marzer`. A member-level [indices] REPLACES the inherited table rather # than merging with it, which is what keeps this to ONE project index repo — From aced3ab9ef44d0d8319c81a4996c1ba3d795eaa4 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 11 Oct 2026 01:51:36 +0900 Subject: [PATCH 5/5] =?UTF-8?q?ci(site-check):=20xpkgindex#10=20=E5=B7=B2?= =?UTF-8?q?=E5=90=88=E5=85=A5,=E6=94=B9=E5=9B=9E=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=88=86=E6=94=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-10-11-site-doc-links-and-formb-host-module-plan.md | 2 +- .github/workflows/site-check.yml | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md b/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md index 272ab1f8..c3ff7cb4 100644 --- a/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md +++ b/.agents/docs/2026-10-11-site-doc-links-and-formb-host-module-plan.md @@ -253,7 +253,7 @@ end | mcpp issue | [mcpp#797](https://github.com/mcpp-community/mcpp/issues/797) | 一般 deps 与 build deps(host-module)在导出面与编译上下文上的分歧;设计问题 6 条,不预设实现。追加评论:同一包同时写在 `[dependencies]` 与 `[build-dependencies]`(host-module)时,合并后的边带 `hostModule`,被 build-time-only 剪枝误判为 target 不可达,项目侧 BMI 丢失 | | xpkgindex | [openxlings/xpkgindex#10](https://github.com/openxlings/xpkgindex/pull/10) | F2:未命中的相对链接 → 包页 / 目录 README 的 guide / `{github}/blob\|tree/HEAD/…` / 不存在则 warning;另修译文 guide 链接以 `depth=3` 跳回默认语言的问题 | | mcpp-index 站点 | `.xpkgindex.json` | F1 注册 `descriptor-examples`、`openkal-compat`(含 zh);`repository-and-schema` 挂 zh 译文;F4 `base_url` → `https://mcpp.index.xlings.org` | -| mcpp-index CI | `site-check.yml` + `tools/site/check_links.py` | F3 产物内部链接检查;xpkgindex **临时 pin** 到 #10 分支联调,#10 合入后改回默认分支 | +| mcpp-index CI | `site-check.yml` + `tools/site/check_links.py` | F3 产物内部链接检查(联调期曾临时 pin 到 xpkgindex#10 分支,#10 已合入,改回默认分支) | | toml++ | `pkgs/m/marzer.tomlplusplus.lua` | Form A + `install()` 写 `mcpp.toml`(`[lib] path`)与 `src/modules/tomlplusplus.cppm`(相对 include,mcpp#797);各平台 `revision = 1` 迫使旧 Form B payload 重装(实测迁移通过) | | 测试 | `tests/examples/marzer.tomlplusplus-build-mcpp` | build.mcpp `import tomlplusplus;` 解析 `build-config.toml` → define → 断言;与项目侧测试分成两个成员(规避上面的双角色 bug) | diff --git a/.github/workflows/site-check.yml b/.github/workflows/site-check.yml index 8133ca6e..1a09d346 100644 --- a/.github/workflows/site-check.yml +++ b/.github/workflows/site-check.yml @@ -31,10 +31,7 @@ jobs: python-version: '3.12' - name: Install xpkgindex - # TEMPORARY: pinned to openxlings/xpkgindex#10 (guide link fallback + - # translated guides keep their language) for joint testing. Revert to - # the default branch once it is merged — deploy-site.yml already uses it. - run: pip install git+https://github.com/openxlings/xpkgindex.git@fix/guide-link-fallback + run: pip install git+https://github.com/openxlings/xpkgindex.git - name: Build the site # --strict turns the growth reconciliation warning into an error: if