Skip to content

Commit 1dfe10e

Browse files
committed
feat: 添加 Quill 13.0.0 模块包
1 parent ce2c5d8 commit 1dfe10e

8 files changed

Lines changed: 261 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Quill 13.0.0 接入 mcpp 模块生态
2+
3+
日期:2026-10-04。状态:已按方案 A 实施,Linux GCC 与 LLVM/libc++ 验收通过;macOS、Windows 的运行结果由 PR CI 验证。
4+
5+
## 1. 范围与消费方式
6+
7+
包为 `odygrd.quill@13.0.0`,模块入口保持上游的 `import quill;`。复用正式发布的实验性模块,保留异步日志行为、公开 API 和内置 fmt。没有独立 compat 包、fork、自制 wrapper、额外 feature 或引擎改动。
8+
9+
```toml
10+
[dependencies.odygrd]
11+
quill = "13.0.0"
12+
```
13+
14+
尚未发布到远程索引,本地使用时需要指向本 checkout:
15+
16+
```toml
17+
[indices]
18+
odygrd = { path = "/home/helan/community/mcpp-community/mcpp-index" }
19+
```
20+
21+
消费者写 `import std; import quill;`。可通过 `quill::Frontend` 创建 sink/logger,使用无宏 API `quill::info(logger, "answer={}", 42)` 或运行时级别 `quill::log`。使用上游日志宏时额外写:
22+
23+
```cpp
24+
#define QUILL_USE_MODULE
25+
#include <quill/LogMacros.h>
26+
```
27+
28+
随后可调用 `LOG_INFO` 或 `QUILL_LOG_INFO`。13.0.0 消费端显式定义 `QUILL_USE_MODULE`;不依赖包内 defines 自动传播。宏头在此模式下跳过普通类型头,并补充宏所需的 helper,不重复包含完整实现。`QUILL_MODULE`、`FMTQUILL_MODULE` 由上游模块自行定义,消费者无需设置。
29+
30+
两种 API 的行为不完全相同:上游 [`LogFunctions.h`](https://github.com/odygrd/quill/blob/v13.0.0/include/quill/LogFunctions.h) 说明无宏 API 的参数始终求值,存在运行时元数据处理,也不能像宏一样按编译期日志级别完全移除。接入不强制迁移原有宏用法,也不作性能等价承诺。
31+
32+
模块公开面以 13.0.0 的模块入口及它实际导出的声明为准;不承诺所有可选头都能通过 import 使用。没有新增 Syslog/Systemd/Android sink、Prometheus 示例或自定义 codec/formatter 导出。完整 Quill 文本头与模块混用、跨 DLL 共享后端不在此次验收范围内。
33+
34+
## 2. 固定的上游输入
35+
36+
- 上游:[odygrd/quill v13.0.0](https://github.com/odygrd/quill/releases/tag/v13.0.0),MIT;保留根 LICENSE 和 bundled fmt 的声明。
37+
- Tag commit:`eb802a37c7d585840324886a3d8648c9c2159952`。
38+
- 归档:`https://github.com/odygrd/quill/archive/refs/tags/v13.0.0.tar.gz`。
39+
- SHA-256:`88b4a1542125577a4d51cf444c51e34d63618c422ba6a4fa9bd23894b49d696b`;spec 调研时两次独立下载一致,实施中的真实下载通过包摘要校验。
40+
- 解包根为 `quill-13.0.0/`。普通形态为头文件库,模块入口是 [`src/quill.cc`](https://github.com/odygrd/quill/blob/v13.0.0/src/quill.cc),采用 CRLF。
41+
- [`CMakeLists.txt`](https://github.com/odygrd/quill/blob/v13.0.0/CMakeLists.txt) 的 `QUILL_BUILD_MODULE` 标为 experimental,编译 `src/quill.cc` 并链接 `Threads::Threads`。
42+
- 内置格式化库位于 `include/quill/bundled/fmt/`,使用 `fmtquill` 命名空间,无需依赖 `fmtlib.fmt` 或 `compat.fmt`。
43+
44+
调研时 GitHub Releases API 的 latest 为 13.0.0;实施时该 API 返回 403,改用 `git ls-remote --tags ... 'refs/tags/v13*'` 确认仍只有 `v13.0.0`。在线 latest 文档/master 出现的 13.1.0 内容未混入固定版本实现。
45+
46+
## 3. 接入方案与适配边界
47+
48+
采用内联 Form B 描述符 [`pkgs/o/odygrd.quill.lua`](../../pkgs/o/odygrd.quill.lua):
49+
50+
| 字段 | 实现 |
51+
|---|---|
52+
| `namespace` / `name` | `odygrd` / `quill` |
53+
| `language` / `import_std` | `c++23` / `false`;保留上游 global module fragment,消费者仍可导入 std |
54+
| `modules` | `{ "quill" }` |
55+
| `include_dirs` | `{ "*/include" }`,服务模块内部包含及消费端宏头 |
56+
| `sources` | `{ "*/src/quill.cppm" }`,只编译一个入口 |
57+
| `targets` / `deps` | `quill` lib / 空依赖 |
58+
| Linux 链接 | `ldflags = { "-pthread" }` |
59+
| 三平台下载 | 相同版本、归档和摘要,使用纯字符串 GLOBAL URL |
60+
61+
安装钩子检查源文件可读且恰有一条 `export module quill;`,再按字节复制为同目录 `src/quill.cppm`。原 `.cc` 保留但不加入编译源集。逐文件比较确认 507 个上游文件内容均未改变,唯一新增文件是与原入口字节一致的 `.cppm`,保留了 CRLF。
62+
63+
扩展名适配参考 [`fmtlib.fmt`](../../pkgs/f/fmtlib.fmt.lua),避免 Clang 将 `.cc` 当普通翻译单元。实际 GCC、LLVM 构建图均只编译 `.cppm`,分别生成 `quill.gcm`、`quill.pcm`;未增加 `scan_overrides` 或完整生成式 wrapper。没有执行 CMake,`QUILL_BUILD_MODULE=ON` 不是本包的构建开关。
64+
65+
线程选项仅在 Linux 最终链接时传入,已检查两套构建图的 `ldflags`。不能只给模块或消费者一方增加影响 PCM 配置的 `-pthread` 编译选项:调研中的宿主 Clang 曾复现配置不一致,分开编译与链接后通过。当前 mcpp GCC/LLVM 构建无需额外线程编译选项。
66+
67+
上游对 MinGW 有 `ucrtbase` 分支,未将其泛化为所有 Windows 编译器的链接需求;Windows 的实际需求留待其 CI 工具链验证。未建立 CN 镜像,不声明猜测的地址;以后若增加镜像,须上传相同归档字节并核对摘要与可达性。
68+
69+
选择依据:复用模块及安装钩子参考 [Taskflow](2026-10-04-add-taskflow-spec.md),保留上游模块名参考 [`khronos.vulkan-hpp`](../../pkgs/k/khronos.vulkan-hpp.lua),内置 fmt 与 build/test 分别验证参考 [spdlog](2026-07-15-add-spdlog-plan.md)。普通头文件 `compat.quill` 无法提供所需 import;独立 Form A 适配仓会增加维护责任,目前均无必要。
70+
71+
## 4. 持久文件与测试契约
72+
73+
- 描述符:`pkgs/o/odygrd.quill.lua`。
74+
- 测试成员:[`tests/examples/quill-module/mcpp.toml`](../../tests/examples/quill-module/mcpp.toml),仅一条 odygrd 本地索引重定向;根 workspace 已登记。
75+
- 测试入口:[`tests/quill.cpp`](../../tests/examples/quill-module/tests/quill.cpp)。
76+
- 辅助 TU:[`src/log_worker.cpp`](../../tests/examples/quill-module/src/log_worker.cpp)。
77+
- 中英文目录:`docs/descriptor-examples.md`、`docs/zh/descriptor-examples.md`。README 已链接这两份完整目录,无需改变其结构。
78+
79+
一个测试可执行文件、两个消费 TU,均使用 `import std; import quill;`,覆盖:
80+
81+
1. 主 TU 启动后端、创建 FileSink/logger;辅助 TU 在工作线程查询同名 logger,断言与主 TU 的指针相同。
82+
2. 宏 `LOG_INFO` 输出整数和字符串;不包含任何 Quill 头的辅助 TU 通过 `quill::info` 输出 `std::vector<int>`,通过 `quill::log` 输出运行时级别记录。
83+
3. 两个 TU 分别提交 Debug 日志,Info 阈值下断言它们均未输出。
84+
4. producer 通过 `std::jthread` join,随后 flush/stop,再读回文件,断言三条有效消息的格式化内容和行数;不依赖时间戳、并发顺序或任意 sleep。
85+
5. 每次创建独享临时目录;断言失败返回非零,异常写 stderr;结束后停止后端并清理目录。测试超时 30 秒。
86+
87+
## 5. 实际验证
88+
89+
宿主为 Linux x86_64,命令由 Bash 执行。使用临时解包的 **mcpp 2026.10.1.2**,与 `.github/workflows/validate.yml` 一致;PATH 中较新的 mcpp 未作为验收替代。通过进程级 `MCPP_HOME=/home/helan/.mcpp` 复用 GCC 16.1.0 和 LLVM 22.1.8,没有修改全局默认版本。
90+
91+
```sh
92+
export MCPP=/tmp/quill-implementation/mcpp-2026.10.1.2-linux-x86_64/bin/mcpp
93+
export MCPP_HOME=/home/helan/.mcpp
94+
export MCPP_INDEX_MIRROR=GLOBAL
95+
export MCPP_VENDORED_XLINGS=/tmp/quill-implementation/mcpp-2026.10.1.2-linux-x86_64/registry/bin/xlings
96+
"$MCPP" xpkg parse --all-os pkgs/o/odygrd.quill.lua
97+
"$MCPP" test -p quill-module --cache off --timeout 30
98+
"$MCPP" test -p quill-module --toolchain llvm@22.1.8 --cache off --timeout 30
99+
"$MCPP" test -p quill-module --timeout 30
100+
```
101+
102+
| 检查 | 实际结果 |
103+
|---|---|
104+
| 隔离临时索引安装及 GCC/LLVM 测试 | 各 `1 passed; 0 failed` |
105+
| 正式 workspace Linux GCC 16.1.0 | `1 passed; 0 failed`,19.78 秒,包含 6.9 秒下载 |
106+
| 正式 workspace Linux LLVM 22.1.8 / libc++ | `1 passed; 0 failed`,6.03 秒 |
107+
| 正式 workspace GCC 增量 | `1 passed; 0 failed`,0.15 秒,构建 0.03 秒 |
108+
| 独立普通消费工程 | `/tmp/quill-implementation/consumer` 指向正式 checkout,`mcpp run --cache off` 实际编译、链接、运行上述日志断言,退出 0 |
109+
| 冷安装 | 隔离工程、正式 workspace、普通消费工程分别实际下载和安装;不将 `--cache off` 本身当作重装证据 |
110+
| 安装文件比较 | 507 个上游文件内容不变,仅新增字节一致的 `.cppm` |
111+
| Lua 语法与三平台 xpkg 解析 | 通过,三平台均解析为 1 个 source、1 个 include 根 |
112+
| 镜像 URL、包身份、保留 namespace | 新描述符通过对应 lint |
113+
| 跨包引用、三平台版本一致性、重复版本 | 全仓对应 lint 通过 |
114+
| CI 选择规则 | 描述符全名匹配唯一 `quill-module` 成员;新增 workspace 成员及测试路径也命中现有规则 |
115+
| diff 空白检查 | 通过 |
116+
117+
临时工具、独立消费工程及日志位于 `/tmp/quill-implementation/`;原归档和宿主探测材料位于 `/tmp/quill-spec-G8O9Nt/`,不是长期源码依赖。
118+
119+
## 6. 未验证与发布边界
120+
121+
macOS、Windows 尚未实际构建运行,三平台描述符解析不等于运行验收。本节记录本地验证边界,跨平台结果以 PR CI 为准;未上传 CN 镜像。上游仍将模块标为实验性;本次不承诺所有 sink/codec/metrics、跨 DLL、完整文本头混用或性能指标。
122+
123+
后续三平台发布前应让 macOS/Windows 运行同一成员;若需要超出模块入口的小范围适配、改动日志实现或 mcpp 引擎,应先保留失败复现并重新审查范围,不以跳过平台或静默改成头文件包代替验收。

‎docs/descriptor-examples.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,4 @@ in the [root README](../README.md#reference-examples).
4848
| 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) |
4949
| 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) |
5050
| 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 `<algorithm>` 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) |
51+
| C++23 module, upstream asynchronous logging | [`odygrd.quill`](../pkgs/o/odygrd.quill.lua) (Quill 13.0.0, upstream experimental `quill` module; the install hook copies `src/quill.cc` byte-for-byte to `.cppm`. 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) |

‎docs/zh/descriptor-examples.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@
4646
| 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 官方库) |
4747
| 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` 就是这条的回归)|
4848
| C++23 module,上游 CPU 分区 | [`taskflow.taskflow`](../../pkgs/t/taskflow.taskflow.lua)(Taskflow 4.1.0,`import tf;`;复用上游四个 CPU 模块单元,排除提供同名主模块的 CUDA 入口。安装钩子逐项校验匹配次数:移除两个不存在的导出,将总头文件及版本导出移至 core 并优先导入 core 以兼容 GCC,为 utility 补 `<algorithm>` 以兼容 libc++。仅适配三个模块文件,头文件和调度实现保持上游原样,无独立 fork;宏不随模块导出,版本查询使用 `tf::version()`) |
49+
| C++23 module,上游异步日志 | [`odygrd.quill`](../../pkgs/o/odygrd.quill.lua)(Quill 13.0.0,上游实验性 `quill` 模块;安装钩子将 `src/quill.cc` 按字节复制为 `.cppm`。消费者使用 `import std; import quill;` 和无宏 API,或定义 `QUILL_USE_MODULE` 并包含 `quill/LogMacros.h` 使用日志宏。自带 fmt,无需额外依赖;Linux 链接使用 `-pthread`。测试覆盖多 TU logger 身份、工作线程输出、格式化和过滤) |

‎mcpp.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ members = [
146146
"tests/examples/sqlite3",
147147
"tests/examples/sqlitecpp",
148148
"tests/examples/taskflow-module",
149+
"tests/examples/quill-module",
149150
"tests/examples/tinyhttps",
150151
"tests/examples/usockets",
151152
"tests/examples/opencl",

‎pkgs/o/odygrd.quill.lua‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package = {
2+
spec = "1",
3+
namespace = "odygrd",
4+
name = "quill",
5+
description = "Asynchronous logging through the upstream quill C++ module",
6+
licenses = { "MIT" },
7+
repo = "https://github.com/odygrd/quill",
8+
type = "package",
9+
10+
xpm = {
11+
linux = {
12+
["13.0.0"] = {
13+
url = "https://github.com/odygrd/quill/archive/refs/tags/v13.0.0.tar.gz",
14+
sha256 = "88b4a1542125577a4d51cf444c51e34d63618c422ba6a4fa9bd23894b49d696b",
15+
},
16+
},
17+
macosx = {
18+
["13.0.0"] = {
19+
url = "https://github.com/odygrd/quill/archive/refs/tags/v13.0.0.tar.gz",
20+
sha256 = "88b4a1542125577a4d51cf444c51e34d63618c422ba6a4fa9bd23894b49d696b",
21+
},
22+
},
23+
windows = {
24+
["13.0.0"] = {
25+
url = "https://github.com/odygrd/quill/archive/refs/tags/v13.0.0.tar.gz",
26+
sha256 = "88b4a1542125577a4d51cf444c51e34d63618c422ba6a4fa9bd23894b49d696b",
27+
},
28+
},
29+
},
30+
31+
mcpp = {
32+
language = "c++23",
33+
import_std = false,
34+
modules = { "quill" },
35+
include_dirs = { "*/include" },
36+
sources = { "*/src/quill.cppm" },
37+
targets = { ["quill"] = { kind = "lib" } },
38+
deps = {},
39+
linux = {
40+
-- 模块与消费者的线程编译配置须一致,线程库在最终链接时引入
41+
ldflags = { "-pthread" },
42+
},
43+
},
44+
}
45+
46+
import("xim.libxpkg.pkginfo")
47+
48+
function install()
49+
local wrap = "quill-" .. pkginfo.version()
50+
local source = path.join(wrap, "src/quill.cc")
51+
local content = assert(io.readfile(source), "odygrd.quill: cannot read " .. source)
52+
local _, count = content:gsub("export module quill;", "")
53+
assert(count == 1, "odygrd.quill: expected exactly one module declaration")
54+
-- Clang 通过接口扩展名识别模块,副本保留上游内容和换行
55+
os.cp(source, path.join(wrap, "src/quill.cppm"))
56+
57+
local prefix = pkginfo.install_dir()
58+
os.tryrm(prefix)
59+
os.mkdir(prefix)
60+
os.mv(wrap, path.join(prefix, wrap))
61+
return true
62+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 本成员通过本地索引验证 odygrd.quill 的模块消费
2+
[package]
3+
name = "quill-module-tests"
4+
version = "0.1.0"
5+
6+
[indices]
7+
odygrd = { path = "../../.." }
8+
9+
[dependencies.odygrd]
10+
quill = "13.0.0"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import std;
2+
import quill;
3+
4+
bool log_from_worker(quill::Logger* expected) {
5+
auto* logger = quill::Frontend::get_logger("quill-module-test");
6+
if (logger != expected) return false;
7+
quill::info(logger, "worker values={}", std::vector<int>{1, 2, 3});
8+
quill::log(logger, quill::LogLevel::Warning, "runtime answer={}", 43);
9+
quill::debug(logger, "filtered worker message");
10+
return true;
11+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import std;
2+
import quill;
3+
4+
#define QUILL_USE_MODULE
5+
#include <quill/LogMacros.h>
6+
7+
bool log_from_worker(quill::Logger* expected);
8+
9+
bool check_logging(std::filesystem::path const& filename) {
10+
quill::Backend::start();
11+
quill::FileSinkConfig config;
12+
config.set_open_mode('w');
13+
auto sink = quill::Frontend::create_or_get_sink<quill::FileSink>(filename.string(), config);
14+
auto* logger = quill::Frontend::create_or_get_logger(
15+
"quill-module-test", sink, quill::PatternFormatterOptions{"%(message)"});
16+
logger->set_log_level(quill::LogLevel::Info);
17+
18+
LOG_INFO(logger, "macro answer={} text={}", 42, std::string{"hello"});
19+
LOG_DEBUG(logger, "filtered main message");
20+
bool worker_ok = false;
21+
{
22+
std::jthread worker([&] { worker_ok = log_from_worker(logger); });
23+
}
24+
logger->flush_log();
25+
quill::Backend::stop();
26+
27+
std::ifstream file(filename);
28+
std::string text((std::istreambuf_iterator<char>(file)), {});
29+
return worker_ok && file.is_open()
30+
&& text.find("macro answer=42 text=hello") != std::string::npos
31+
&& text.find("worker values=[1, 2, 3]") != std::string::npos
32+
&& text.find("runtime answer=43") != std::string::npos
33+
&& text.find("filtered") == std::string::npos
34+
&& std::count(text.begin(), text.end(), '\n') == 3;
35+
}
36+
37+
int main() {
38+
auto directory = std::filesystem::temp_directory_path()
39+
/ ("mcpp-quill-" + std::to_string(std::random_device{}()));
40+
if (!std::filesystem::create_directory(directory)) return 1;
41+
bool ok = false;
42+
try {
43+
ok = check_logging(directory / "output.log");
44+
} catch (std::exception const& error) {
45+
std::cerr << error.what() << '\n';
46+
}
47+
quill::Backend::stop();
48+
std::error_code error;
49+
std::filesystem::remove_all(directory, error);
50+
if (!ok) std::cerr << "Quill module logging assertions failed\n";
51+
return ok && !error ? 0 : 1;
52+
}

0 commit comments

Comments
 (0)