Skip to content

Commit 0c1aa14

Browse files
1115040131claude
andauthored
feat: add fmt 12.2.0 as C++23 module package (import fmt;) (#63)
* feat: add fmt 12.2.0 as C++23 module package (import fmt;) Module sibling of the header-based compat.fmt: the public `fmt` package (empty namespace) exposes the C++23 module `fmt` so users write `import fmt;`. fmt's release ships an official module unit at src/fmt.cc, but mcpp's M1 scanner rejects any `import` inside a conditional preprocessor block (even a dead one), and fmt.cc guards `import std;` behind `#ifdef FMT_IMPORT_STD`. So the descriptor reproduces fmt.cc via generated_files with exactly those three lines removed — everything else verbatim, including the tail `#include "format.cc"/"os.cc"` that attaches the implementation to module fmt. include_dirs exposes */include and */src so both the module unit and its impl includes resolve. Empty-namespace packages can't be workspace members (like the imgui module package), so a thin driver tests/smoke_fmt_module.sh reseeds the default index and consumes `import fmt;`, wired into validate.yml as the fmt-module job. Verified locally on mcpp 0.0.81 (== CI): the smoke builds module fmt and its behavioral assertions pass. No mcpp-res write access, so the download url is a plain upstream string (CN mirror to be added by a maintainer). Co-Authored-By: Claude <noreply@anthropic.com> * test(workspace): fmt as fmtlib.fmt workspace member (drop smoke driver) Replace the shell smoke driver for the {fmt} module package with a native workspace member under tests/examples/fmtlib.fmt, matching the compat.* / nlohmann.json members. The empty-namespace `fmt` package resolved through the builtin default index, which a member can't point at a local path — so the package is renamed to the `fmtlib` namespace (fmtlib.fmt), letting the member route the dependency to this repo via `[indices] fmtlib = { path }`. The exported module is still plain `fmt` (import fmt;). Drops tests/ smoke_fmt_module.sh and its fmt-module CI job; `mcpp test --workspace` now covers it. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(fmtlib.fmt): use import std instead of stripping the guard The generated fmt_module.cc now `#define FMT_IMPORT_STD` (right after FMT_MODULE) so fmt's own `#ifndef FMT_IMPORT_STD` block takes the import-std path — dropping the ~40 `#include <algorithm>`…`<vector>` lines — and the `#ifdef FMT_IMPORT_STD` guard around `import std;` is removed so the import is unconditional at module top level (which M1's text scanner accepts). Sets import_std = true so mcpp builds the std module first. Faster to compile than the header-include variant; behavior unchanged (format_test passes). Co-Authored-By: Claude <noreply@anthropic.com> * fix(fmtlib.fmt): name module wrapper .cppm so Clang emits the BMI The generated module wrapper was `mcpp_generated/fmt_module.cc`. On macOS/Windows mcpp drives Clang in gnu mode (`--driver-mode=g++`), where Clang decides "is this a module interface unit?" purely from the file extension: `.cppm`/`.ixx` yes, `.cc` no. A `.cc` module unit still compiles (only the fopen deprecation warning), but Clang silently drops `-fmodule-output` as an unused argument, so no BMI (`fmt.pcm`) is ever written — the consumer's `import fmt;` then fails with `fatal error: module 'fmt' not found`. GCC (Linux) keys off `export module` rather than the extension, so `.cc` happened to work there and masked the bug; nlohmann.json already uses a `.cppm` wrapper, which is why it passed on all three platforms. Rename both the generated_files key and sources to `fmt_module.cppm` to match. Verified end-to-end on Windows (clang 20.1.7, MSVC STL): fmt.pcm is now produced and format_test passes. import std is unrelated and retained. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fab139d commit 0c1aa14

4 files changed

Lines changed: 119 additions & 0 deletions

File tree

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ members = [
1313
"tests/examples/cjson",
1414
"tests/examples/core",
1515
"tests/examples/eigen",
16+
"tests/examples/fmtlib.fmt",
1617
"tests/examples/gui-stack",
1718
"tests/examples/imgui",
1819
"tests/examples/imgui-window",

pkgs/f/fmtlib.fmt.lua

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
-- Form B inline descriptor for {fmt} exposed as the C++23 module `fmt`, so
2+
-- users can write `import fmt;` out of the box (no `#include` needed). This is
3+
-- the module sibling of the header-based `compat.fmt` package. Namespaced under
4+
-- `fmtlib` (package `fmtlib.fmt`) so a workspace member can route the dependency
5+
-- to this repo's local `[indices] fmtlib = { path }` — an empty namespace would
6+
-- bind to the builtin default index and can't be pointed at a local path. The
7+
-- exported module name is still plain `fmt`, so consumers write `import fmt;`.
8+
--
9+
-- fmt's release DOES ship an official C++20 module interface unit at
10+
-- `src/fmt.cc` (`export module fmt;`), but it cannot be fed to mcpp verbatim:
11+
-- mcpp's M1 module scanner rejects ANY `import` that appears inside a
12+
-- conditional preprocessor block (the scanner is a pre-preprocess text pass, so
13+
-- it can't evaluate the `#ifdef` — the guard is textual, not value-based), and
14+
-- fmt.cc carries `#ifdef FMT_IMPORT_STD / import std; / #endif`. So we reproduce
15+
-- fmt.cc via `generated_files` with two minimal edits: (1) `#define
16+
-- FMT_IMPORT_STD` right after `#define FMT_MODULE`, which flips fmt's own
17+
-- `#ifndef FMT_IMPORT_STD` include block to the import-std path (dropping the
18+
-- ~40 `#include <algorithm>`…`<vector>` lines); (2) that `#ifdef` guard around
19+
-- `import std;` removed so the import sits unconditional at module top level,
20+
-- which the scanner accepts. `import_std = true` makes mcpp build the `std`
21+
-- module first. Everything else is upstream fmt.cc verbatim: the
22+
-- `export module fmt;` line, the FMT_EXPORT macro plumbing (fmt's headers
23+
-- self-`export` under FMT_MODULE), and the tail `#include "format.cc"` /
24+
-- `"os.cc"` that pulls the implementation INTO the module (so its definitions
25+
-- attach to module `fmt`; compiling them as standalone TUs would leave them on
26+
-- the global module and break module linkage).
27+
--
28+
-- include_dirs exposes BOTH `*/include` (so the module unit's `#include
29+
-- <fmt/*.h>` resolves) and `*/src` (so the tail `#include "format.cc"` /
30+
-- `"os.cc"` resolve). The wrapper path under mcpp_generated/ is verdir-relative
31+
-- (no glob), like nlohmann.json / compat.eigen.
32+
--
33+
-- The wrapper MUST be named `.cppm`, not `.cc`. On macOS/Windows mcpp drives
34+
-- Clang in gnu (`--driver-mode=g++`) mode, where Clang decides "is this a module
35+
-- interface unit?" purely from the file extension: `.cppm`/`.ixx` yes, `.cc` no.
36+
-- A `.cc` module unit still compiles, but Clang silently drops `-fmodule-output`
37+
-- as an "unused argument" (-Wunused-command-line-argument), so NO BMI (`fmt.pcm`)
38+
-- is written — the consumer's `import fmt;` then fails with `module 'fmt' not
39+
-- found`. GCC (Linux) keys off `export module` instead of the extension, so `.cc`
40+
-- happened to work there and masked the bug. nlohmann.json already uses `.cppm`;
41+
-- this matches it.
42+
package = {
43+
spec = "1",
44+
namespace = "fmtlib",
45+
name = "fmtlib.fmt",
46+
description = "A modern formatting library for C++, exposed as C++23 module fmt",
47+
licenses = {"MIT"},
48+
repo = "https://github.com/fmtlib/fmt",
49+
type = "package",
50+
51+
xpm = {
52+
linux = {
53+
["12.2.0"] = {
54+
url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.2.0.tar.gz",
55+
sha256 = "8b852bb5aa6e7d8564f9e81394055395dd1d1936d38dfd3a17792a02bebd7af0",
56+
},
57+
},
58+
macosx = {
59+
["12.2.0"] = {
60+
url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.2.0.tar.gz",
61+
sha256 = "8b852bb5aa6e7d8564f9e81394055395dd1d1936d38dfd3a17792a02bebd7af0",
62+
},
63+
},
64+
windows = {
65+
["12.2.0"] = {
66+
url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.2.0.tar.gz",
67+
sha256 = "8b852bb5aa6e7d8564f9e81394055395dd1d1936d38dfd3a17792a02bebd7af0",
68+
},
69+
},
70+
},
71+
72+
mcpp = {
73+
schema = "0.1",
74+
language = "c++23",
75+
import_std = true,
76+
modules = { "fmt" },
77+
include_dirs = { "*/include", "*/src" },
78+
generated_files = {
79+
["mcpp_generated/fmt_module.cppm"] = "// Formatting library for C++ - C++20 module\n//\n// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\nmodule;\n\n#define FMT_MODULE\n\n#define FMT_IMPORT_STD\n\n#ifdef _MSVC_LANG\n# define FMT_CPLUSPLUS _MSVC_LANG\n#else\n# define FMT_CPLUSPLUS __cplusplus\n#endif\n\n// Put all implementation-provided headers into the global module fragment\n// to prevent attachment to this module.\n#ifndef FMT_IMPORT_STD\n# include <algorithm>\n# include <bitset>\n# include <chrono>\n# include <cmath>\n# include <complex>\n# include <cstddef>\n# include <cstdint>\n# include <cstdio>\n# include <cstdlib>\n# include <cstring>\n# include <ctime>\n# include <exception>\n# if FMT_CPLUSPLUS > 202002L\n# include <expected>\n# endif\n# include <filesystem>\n# include <fstream>\n# include <functional>\n# include <iterator>\n# include <limits>\n# include <locale>\n# include <memory>\n# include <optional>\n# include <ostream>\n# include <source_location>\n# include <stdexcept>\n# include <string>\n# include <string_view>\n# include <system_error>\n# include <thread>\n# include <type_traits>\n# include <typeinfo>\n# include <utility>\n# include <variant>\n# include <vector>\n#else\n# include <limits.h>\n# include <stdint.h>\n# include <stdio.h>\n# include <stdlib.h>\n# include <string.h>\n# include <time.h>\n#endif\n#include <cerrno>\n#include <climits>\n#include <version>\n\n#if __has_include(<cxxabi.h>)\n# include <cxxabi.h>\n#endif\n#if defined(_MSC_VER) || defined(__MINGW32__)\n# include <intrin.h>\n#endif\n#if defined __APPLE__ || defined(__FreeBSD__)\n# include <xlocale.h>\n#endif\n#if __has_include(<winapifamily.h>)\n# include <winapifamily.h>\n#endif\n#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \\\n defined(__linux__)) && \\\n (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))\n# include <fcntl.h>\n# include <sys/stat.h>\n# include <sys/types.h>\n# ifndef _WIN32\n# include <unistd.h>\n# else\n# include <io.h>\n# endif\n#endif\n#ifdef _WIN32\n# if defined(__GLIBCXX__)\n# include <ext/stdio_filebuf.h>\n# include <ext/stdio_sync_filebuf.h>\n# endif\n# define WIN32_LEAN_AND_MEAN\n# include <windows.h>\n#endif\n\nexport module fmt;\n\nimport std;\n\n#define FMT_EXPORT export\n#define FMT_BEGIN_EXPORT export {\n#define FMT_END_EXPORT }\n\n// If you define FMT_ATTACH_TO_GLOBAL_MODULE\n// - all declarations are detached from module 'fmt'\n// - the module behaves like a traditional static library, too\n// - all library symbols are mangled traditionally\n// - you can mix TUs with either importing or #including the {fmt} API\n#ifdef FMT_ATTACH_TO_GLOBAL_MODULE\nextern \"C++\" {\n#endif\n\n#ifndef FMT_OS\n# define FMT_OS 1\n#endif\n\n// All library-provided declarations and definitions must be in the module\n// purview to be exported.\n#include \"fmt/args.h\"\n#include \"fmt/chrono.h\"\n#include \"fmt/color.h\"\n#include \"fmt/compile.h\"\n#include \"fmt/format.h\"\n#if FMT_OS\n# include \"fmt/os.h\"\n#endif\n#include \"fmt/ostream.h\"\n#include \"fmt/printf.h\"\n#include \"fmt/ranges.h\"\n#include \"fmt/std.h\"\n#include \"fmt/xchar.h\"\n\n#ifdef FMT_ATTACH_TO_GLOBAL_MODULE\n}\n#endif\n\n#ifdef FMT_ATTACH_TO_GLOBAL_MODULE\nextern \"C++\" {\n#endif\n\n#if FMT_HAS_INCLUDE(\"format.cc\")\n# include \"format.cc\"\n#endif\n#if FMT_OS && FMT_HAS_INCLUDE(\"os.cc\")\n# include \"os.cc\"\n#endif\n\n#ifdef FMT_ATTACH_TO_GLOBAL_MODULE\n}\n#endif\n",
80+
},
81+
sources = { "mcpp_generated/fmt_module.cppm" },
82+
targets = { ["fmt"] = { kind = "lib" } },
83+
deps = { },
84+
},
85+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# {fmt} test project: consumes the C++23 module `fmt` (`import fmt;`, no
2+
# #include) from the `fmtlib.fmt` package and asserts behavior under `mcpp test`.
3+
# Part of the mcpp-index self-referential workspace: `[indices] fmtlib` points at
4+
# this repo, so the dependency resolves to the checked-in recipe (pkgs/f/
5+
# fmtlib.fmt.lua) rather than a remote index. The exported module is plain `fmt`.
6+
[package]
7+
name = "fmtlib-fmt-tests"
8+
version = "0.1.0"
9+
10+
[indices]
11+
fmtlib = { path = "../../.." }
12+
13+
[dependencies.fmtlib]
14+
fmt = "12.2.0"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Behavioral test: consume {fmt}'s compiled implementation through the C++23
2+
// module `fmt` (`import fmt;`, no #include) and assert the results. Exercises
3+
// src/format.cc via the module unit's tail includes — not just header inlines.
4+
// Returns non-zero on any mismatch.
5+
import std;
6+
import fmt;
7+
8+
int main() {
9+
std::string a = fmt::format("{} + {} = {}", 2, 3, 2 + 3);
10+
std::string b = fmt::format("{:08.3f}", 3.14159);
11+
std::string c = fmt::format("{0}-{1}-{0}", "x", "y");
12+
std::string d = fmt::format("{:#x}", 255);
13+
14+
bool ok = a == "2 + 3 = 5"
15+
&& b == "0003.142"
16+
&& c == "x-y-x"
17+
&& d == "0xff";
18+
return ok ? 0 : 1;
19+
}

0 commit comments

Comments
 (0)