Skip to content

Commit 931b40d

Browse files
committed
feat: 添加 mp-units 2.5.0 模块接入草案
1 parent ce2c5d8 commit 931b40d

10 files changed

Lines changed: 243 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# mp-units 2.5.0 模块接入
2+
3+
日期:2026-10-04。状态:实现草案,兼容性验收未通过,不能合并收录。
4+
5+
## 范围与消费方式
6+
7+
`mpusz.mp-units@2.5.0` 复用上游三个模块,提供 `mp_units.core`、`mp_units.systems` 和总入口 `mp_units`。模块名和上游 API 不变,不另建 compat 包、长期 fork 或手写 API wrapper。
8+
9+
```toml
10+
[dependencies.mpusz]
11+
mp-units = "2.5.0"
12+
```
13+
14+
消费者使用 `import std; import mp_units;`。LLVM 22.1.8/libc++ 已通过普通 SI 单位计算、量纲约束、chrono 转换、格式化和多 TU 测试,但自然单位 quantity 构造失败;GCC 16.1.0 在构建 systems 模块时失败。该草案保留失败测试,不通过条件编译或修改 CI 隐藏问题。
15+
16+
## 来源与包结构
17+
18+
- 上游:[mpusz/mp-units v2.5.0](https://github.com/mpusz/mp-units/releases/tag/v2.5.0),MIT。
19+
- Tag commit:`27d2def9082ce00d7eb4f75695dbead4a748f23f`。
20+
- 归档:`https://github.com/mpusz/mp-units/archive/refs/tags/v2.5.0.tar.gz`。
21+
- SHA-256:`a6bd48bee699f11f0ed5b04b8c5006d15f76e6d898e058db3880554d2e47a400`,两次独立下载一致。
22+
- 根目录:`mp-units-2.5.0/`;许可、头文件和原始模块源均保留。
23+
- 内联 Form B,C++23,`import_std = true`;两个 include 根为 `src/core/include`、`src/systems/include`。
24+
- Linux、macOS、Windows 共用归档和配置。三平台元数据与解析不等于运行验收。
25+
- 初始使用纯字符串上游 URL,没有 CN 镜像或额外依赖。
26+
27+
参考 [Taskflow](../../pkgs/t/taskflow.taskflow.lua) 的上游模块和安装钩子方式、[Vulkan-Hpp](../../pkgs/k/khronos.vulkan-hpp.lua) 的多模块依赖、[magic_enum](../../pkgs/n/neargye.magic_enum.lua) 的扫描声明、[fmt](../../pkgs/f/fmtlib.fmt.lua) 的模块接口扩展名处理。
28+
29+
## 配置契约
30+
31+
| 宏 | 值 | 含义 |
32+
|---|---|---|
33+
| `MP_UNITS_IMPORT_STD` | 定义 | 使用标准库模块 |
34+
| `MP_UNITS_API_STD_FORMAT` | `1` | 标准库格式化,无 fmt 依赖 |
35+
| `MP_UNITS_API_CONTRACTS` | `0` | 上游 NONE 配置,运行时前置条件和断言检查关闭 |
36+
| `MP_UNITS_API_NO_CRTP` | `1` | C++23 explicit object parameter |
37+
| `MP_UNITS_HOSTED` | `1` | 保留 hosted API |
38+
| `MP_UNITS_API_NATURAL_UNITS` | `1` | 保留 2.5.0 默认的实验性自然单位 |
39+
| `MP_UNITS_API_THROWING_CONSTRAINTS` | `0` | 不依赖 C++26 constexpr exceptions |
40+
41+
[上游 conanfile.py](https://github.com/mpusz/mp-units/blob/v2.5.0/conanfile.py) 在 import_std 路径将 contracts 设为 none,并拒绝与其他契约后端组合。编译期量纲约束仍然存在,运行时前置条件由调用者保证。需要 gsl-lite 契约检查时,应单独审查文本标准头路径及依赖接入,不能将本配置描述成上游默认 CMake 行为。
42+
43+
包内 defines 已在实际编译命令中核实。宏不随模块导出;消费者不依赖 `QUANTITY_SPEC` 或 `MP_UNITS_STD_FMT`,也不混入 mp-units 完整文本头。首轮不提供配置 feature,避免引入未验证的 API/依赖组合。
44+
45+
## 安装与扫描
46+
47+
安装钩子检查模块声明恰好出现一次,将以下源文件原样复制为同目录 `.cppm`,读取比较确认副本一致。只编译副本,不运行上游 CMake。
48+
49+
| 原始文件 | 提供模块 | 导入模块 |
50+
|---|---|---|
51+
| `src/core/mp-units-core.cpp` | `mp_units.core` | `std` |
52+
| `src/systems/mp-units-systems.cpp` | `mp_units.systems` | `mp_units.core`、`std` |
53+
| `src/mp-units.cpp` | `mp_units` | `mp_units.core`、`mp_units.systems` |
54+
55+
core、systems 的 `import std` 有预处理守卫,通过两个精确 glob 的 `scan_overrides` 声明扫描结果;总入口正常扫描。固定版 mcpp 实际扫描和模块编译已到达编译器,未出现条件导入拒绝。原始源码内容没有补丁,没有额外线程链接参数。
56+
57+
GCC 故障的实验仅在临时解包树中进行,没有进入描述符:切换 CRTP、关闭模块惰性加载、调整导出/约束、改变语言链接均未解决;合并 core/systems 的诊断尝试把失败移到了消费 TU,仍然没有得到可用包。因此不保留这些扩大修改面的尝试,不升级全仓工具链或添加 CI 排除规则。
58+
59+
## 测试与索引登记
60+
61+
`tests/examples/mp-units-module` 仅通过一条 `mpusz` 本地索引重定向消费当前 checkout。根 workspace 和中英文 descriptor-examples 已登记草案及阻塞说明。
62+
63+
- `units.cpp` 与 `src/quantity_bridge.cpp` 在一个可执行文件中分别消费总模块、core/system 模块;跨 TU 传递同一种 quantity。
64+
- 检查 `1 km == 1000 m`、`120 km / 2 h == 60 km/h`,并用依赖类型的 requires concept 检查长度与时间不可相加。
65+
- 检查 `std::chrono::minutes{2}` 到 quantity 的转换。
66+
- 检查 `std::format` 对 `42 m` 和 UTF-8 `2 Ω` 的输出;运行时失败返回非零,不依赖 assert/NDEBUG。
67+
- `natural.cpp` 单独检查 `2 * natural::energy[GeV]` 的构造和数值读取。该测试暴露当前 LLVM 22 的真实兼容性失败,未跳过。
68+
69+
## 本地验证结果
70+
71+
使用与 CI 一致的 mcpp `2026.10.1.2`,复用已有 registry 工具链;没有修改默认版本或全局配置。
72+
73+
```sh
74+
mcpp xpkg parse --all-os pkgs/m/mpusz.mp-units.lua
75+
mcpp test -p mp-units-module --cache off --timeout 30
76+
mcpp test -p mp-units-module --toolchain llvm@22.1.8 --cache off --timeout 30
77+
```
78+
79+
| 检查 | 结果 |
80+
|---|---|
81+
| Linux GCC 16.1.0 | 失败,systems 编译报 `recursive lazy load`、`failed to load pendings for mp_units::derived_quantity_spec` |
82+
| Linux LLVM 22.1.8/libc++ | `1 passed; 1 failed`;units 通过,natural 编译失败 |
83+
| 三平台描述符解析 | 通过 |
84+
| Lua/索引 lint | 通过语法、包身份、保留 namespace、镜像 URL、全仓引用、版本一致性与重复版本检查 |
85+
| 冷安装和副本校验 | 独立消费工程重新下载并安装成功;所有原文件与归档一致,三个 `.cppm` 副本逐字节一致 |
86+
| LLVM 独立消费 build/run | 普通 SI/多 TU/chrono/格式化用例构建成功,运行退出 0 |
87+
| macOS、Windows、PR CI | 待远程验证 |
88+
89+
[上游 #717](https://github.com/mpusz/mp-units/issues/717) 记录了 GCC 模块的同类 recursive lazy load 错误,维护者归因为 GCC 问题;本次实测版本为 GCC 16.1.0,不能把上游 GCC 15 的记录当作本地结果。有限适配未找到可用修复,尚无进一步缩减的编译器最小复现。
90+
91+
[上游 #798](https://github.com/mpusz/mp-units/issues/798) 记录了 2.5.0 在 Clang 22 下使用 ISQ reference 的约束回归,维护者关联 [LLVM #175831](https://github.com/llvm/llvm-project/issues/175831)。本次 natural 测试出现同类 quantity 约束失败;与上游报告的关联是依据错误与 API 路径作出的判断,未独立证明为完全相同根因。
92+
93+
## 收录门槛与后续
94+
95+
当前只交付 Draft PR,不代表完成正常接入。转为可合并状态之前,需要解决 GCC 模块构建和 LLVM quantity 构造,并让现有 Linux GCC/LLVM、macOS、Windows 构建运行矩阵全部通过。
96+
97+
若需要长期 fork、重写模块边界、改变量纲实现、限定工具链或扩大依赖,应另行审查方案。原有完整验收目标保持不变,不能以删除失败测试、关闭 natural units 或跳过 GCC 达成绿色结果。

‎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 modules, compatibility draft | [`mpusz.mp-units`](../pkgs/m/mpusz.mp-units.lua) (2.5.0; upstream `mp_units.core`, `mp_units.systems`, and `mp_units`, copied to `.cppm` without source changes. Uses `import std` and `std::format`, with runtime contracts disabled. **Not ready for admission:** GCC 16.1.0 fails module compilation; LLVM 22.1.8 passes the SI/multi-TU test but fails natural quantity construction. The failing tests remain enabled; see the [spec](../.agents/docs/2026-10-04-add-mp-units-spec.md)) |

‎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 模块,兼容性草案 | [`mpusz.mp-units`](../../pkgs/m/mpusz.mp-units.lua)(2.5.0;复用 `mp_units.core`、`mp_units.systems`、`mp_units`,原样复制为 `.cppm`。使用 `import std` 与 `std::format`,运行时契约关闭。**尚不具备收录条件**:GCC 16.1.0 模块编译失败;LLVM 22.1.8 的 SI/多 TU 测试通过,自然单位 quantity 构造失败。失败测试保持启用,见 [spec](../../.agents/docs/2026-10-04-add-mp-units-spec.md)) |

‎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/mp-units-module",
149150
"tests/examples/tinyhttps",
150151
"tests/examples/usockets",
151152
"tests/examples/opencl",

‎pkgs/m/mpusz.mp-units.lua‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package = {
2+
spec = "1",
3+
namespace = "mpusz",
4+
name = "mp-units",
5+
description = "Physical quantities and units through the upstream mp_units C++ modules",
6+
licenses = { "MIT" },
7+
repo = "https://github.com/mpusz/mp-units",
8+
type = "package",
9+
xpm = {
10+
linux = {
11+
["2.5.0"] = {
12+
url = "https://github.com/mpusz/mp-units/archive/refs/tags/v2.5.0.tar.gz",
13+
sha256 = "a6bd48bee699f11f0ed5b04b8c5006d15f76e6d898e058db3880554d2e47a400",
14+
},
15+
},
16+
macosx = {
17+
["2.5.0"] = {
18+
url = "https://github.com/mpusz/mp-units/archive/refs/tags/v2.5.0.tar.gz",
19+
sha256 = "a6bd48bee699f11f0ed5b04b8c5006d15f76e6d898e058db3880554d2e47a400",
20+
},
21+
},
22+
windows = {
23+
["2.5.0"] = {
24+
url = "https://github.com/mpusz/mp-units/archive/refs/tags/v2.5.0.tar.gz",
25+
sha256 = "a6bd48bee699f11f0ed5b04b8c5006d15f76e6d898e058db3880554d2e47a400",
26+
},
27+
},
28+
},
29+
mcpp = {
30+
language = "c++23",
31+
import_std = true,
32+
modules = { "mp_units.core", "mp_units.systems", "mp_units" },
33+
include_dirs = { "*/src/core/include", "*/src/systems/include" },
34+
-- 上游的 import std 配置使用标准格式化,并关闭运行时契约检查
35+
defines = {
36+
"MP_UNITS_IMPORT_STD",
37+
"MP_UNITS_API_STD_FORMAT=1",
38+
"MP_UNITS_API_CONTRACTS=0",
39+
"MP_UNITS_API_NO_CRTP=1",
40+
"MP_UNITS_HOSTED=1",
41+
"MP_UNITS_API_NATURAL_UNITS=1",
42+
"MP_UNITS_API_THROWING_CONSTRAINTS=0",
43+
},
44+
sources = {
45+
"*/src/core/mp-units-core.cppm",
46+
"*/src/systems/mp-units-systems.cppm",
47+
"*/src/mp-units.cppm",
48+
},
49+
-- 两个上游接口的 std 导入受 MP_UNITS_IMPORT_STD 守卫
50+
scan_overrides = {
51+
["*/src/core/mp-units-core.cppm"] = {
52+
provides = { "mp_units.core" }, imports = { "std" },
53+
},
54+
["*/src/systems/mp-units-systems.cppm"] = {
55+
provides = { "mp_units.systems" }, imports = { "mp_units.core", "std" },
56+
},
57+
},
58+
targets = { ["mp-units"] = { kind = "lib" } },
59+
deps = {},
60+
},
61+
}
62+
63+
import("xim.libxpkg.pkginfo")
64+
65+
function install()
66+
local wrap = "mp-units-" .. pkginfo.version()
67+
local units = {
68+
["src/core/mp-units-core"] = "mp_units.core",
69+
["src/systems/mp-units-systems"] = "mp_units.systems",
70+
["src/mp-units"] = "mp_units",
71+
}
72+
for relative, name in pairs(units) do
73+
local source = path.join(wrap, relative .. ".cpp")
74+
local content = assert(io.readfile(source), "mpusz.mp-units: cannot read " .. source)
75+
local declaration = "export module " .. name .. ";"
76+
local _, count = content:gsub(declaration:gsub("(%W)", "%%%1"), "")
77+
assert(count == 1, "mpusz.mp-units: expected exactly one declaration in " .. source)
78+
-- 模块接口扩展名用于 Clang 驱动识别,原文件保留
79+
local destination = path.join(wrap, relative .. ".cppm")
80+
os.cp(source, destination)
81+
assert(io.readfile(destination) == content, "mpusz.mp-units: copy differs from " .. source)
82+
end
83+
local prefix = pkginfo.install_dir()
84+
os.tryrm(prefix)
85+
os.mkdir(prefix)
86+
os.mv(wrap, path.join(prefix, wrap))
87+
return true
88+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "mp-units-module-tests"
3+
version = "0.1.0"
4+
5+
[indices]
6+
mpusz = { path = "../../.." }
7+
8+
[dependencies.mpusz]
9+
mp-units = "2.5.0"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import std;
2+
import mp_units.core;
3+
import mp_units.systems;
4+
5+
#include "quantity_bridge.h"
6+
7+
distance add_distance(distance left, distance right) {
8+
return left + right;
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
using distance = mp_units::quantity<mp_units::si::metre, int>;
4+
5+
distance add_distance(distance left, distance right);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import std;
2+
import mp_units;
3+
4+
using namespace mp_units;
5+
using namespace mp_units::natural::unit_symbols;
6+
7+
int main() {
8+
constexpr auto energy = 2 * natural::energy[GeV];
9+
return energy.numerical_value_in(GeV) == 2 ? 0 : 1;
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import std;
2+
import mp_units;
3+
4+
#include "../src/quantity_bridge.h"
5+
6+
using namespace mp_units;
7+
using namespace mp_units::si::unit_symbols;
8+
9+
template<class Left, class Right>
10+
concept Addable = requires(Left left, Right right) { left + right; };
11+
12+
static_assert(1 * km == 1000 * m);
13+
static_assert(!Addable<decltype(1 * m), decltype(1 * s)>);
14+
static_assert((120 * km / (2 * h)).numerical_value_in(km / h) == 60);
15+
static_assert(quantity{std::chrono::minutes{2}} == 120 * s);
16+
17+
int main() {
18+
if (add_distance(20 * m, 22 * m) != 42 * m) return 1;
19+
if (std::format("{}", 42 * m) != "42 m") return 2;
20+
if (std::format("{}", 2 * ohm) != "2 \xce\xa9") return 3;
21+
return 0;
22+
}

0 commit comments

Comments
 (0)