diff --git a/mcpp.toml b/mcpp.toml index d4e04ca..a1e19bb 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -13,6 +13,7 @@ members = [ "tests/examples/cjson", "tests/examples/core", "tests/examples/eigen", + "tests/examples/fmtlib.fmt", "tests/examples/gui-stack", "tests/examples/imgui", "tests/examples/imgui-window", diff --git a/pkgs/f/fmtlib.fmt.lua b/pkgs/f/fmtlib.fmt.lua new file mode 100644 index 0000000..be3875d --- /dev/null +++ b/pkgs/f/fmtlib.fmt.lua @@ -0,0 +1,85 @@ +-- Form B inline descriptor for {fmt} exposed as the C++23 module `fmt`, so +-- users can write `import fmt;` out of the box (no `#include` needed). This is +-- the module sibling of the header-based `compat.fmt` package. Namespaced under +-- `fmtlib` (package `fmtlib.fmt`) so a workspace member can route the dependency +-- to this repo's local `[indices] fmtlib = { path }` — an empty namespace would +-- bind to the builtin default index and can't be pointed at a local path. The +-- exported module name is still plain `fmt`, so consumers write `import fmt;`. +-- +-- fmt's release DOES ship an official C++20 module interface unit at +-- `src/fmt.cc` (`export module fmt;`), but it cannot be fed to mcpp verbatim: +-- mcpp's M1 module scanner rejects ANY `import` that appears inside a +-- conditional preprocessor block (the scanner is a pre-preprocess text pass, so +-- it can't evaluate the `#ifdef` — the guard is textual, not value-based), and +-- fmt.cc carries `#ifdef FMT_IMPORT_STD / import std; / #endif`. So we reproduce +-- fmt.cc via `generated_files` with two minimal edits: (1) `#define +-- FMT_IMPORT_STD` right after `#define FMT_MODULE`, which flips fmt's own +-- `#ifndef FMT_IMPORT_STD` include block to the import-std path (dropping the +-- ~40 `#include `…`` lines); (2) that `#ifdef` guard around +-- `import std;` removed so the import sits unconditional at module top level, +-- which the scanner accepts. `import_std = true` makes mcpp build the `std` +-- module first. Everything else is upstream fmt.cc verbatim: the +-- `export module fmt;` line, the FMT_EXPORT macro plumbing (fmt's headers +-- self-`export` under FMT_MODULE), and the tail `#include "format.cc"` / +-- `"os.cc"` that pulls the implementation INTO the module (so its definitions +-- attach to module `fmt`; compiling them as standalone TUs would leave them on +-- the global module and break module linkage). +-- +-- include_dirs exposes BOTH `*/include` (so the module unit's `#include +-- ` resolves) and `*/src` (so the tail `#include "format.cc"` / +-- `"os.cc"` resolve). The wrapper path under mcpp_generated/ is verdir-relative +-- (no glob), like nlohmann.json / compat.eigen. +-- +-- The wrapper MUST be named `.cppm`, not `.cc`. On macOS/Windows mcpp drives +-- Clang in gnu (`--driver-mode=g++`) mode, 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, but Clang silently drops `-fmodule-output` +-- as an "unused argument" (-Wunused-command-line-argument), so NO BMI (`fmt.pcm`) +-- is written — the consumer's `import fmt;` then fails with `module 'fmt' not +-- found`. GCC (Linux) keys off `export module` instead of the extension, so `.cc` +-- happened to work there and masked the bug. nlohmann.json already uses `.cppm`; +-- this matches it. +package = { + spec = "1", + namespace = "fmtlib", + name = "fmtlib.fmt", + description = "A modern formatting library for C++, exposed as C++23 module fmt", + licenses = {"MIT"}, + repo = "https://github.com/fmtlib/fmt", + type = "package", + + xpm = { + linux = { + ["12.2.0"] = { + url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.2.0.tar.gz", + sha256 = "8b852bb5aa6e7d8564f9e81394055395dd1d1936d38dfd3a17792a02bebd7af0", + }, + }, + macosx = { + ["12.2.0"] = { + url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.2.0.tar.gz", + sha256 = "8b852bb5aa6e7d8564f9e81394055395dd1d1936d38dfd3a17792a02bebd7af0", + }, + }, + windows = { + ["12.2.0"] = { + url = "https://github.com/fmtlib/fmt/archive/refs/tags/12.2.0.tar.gz", + sha256 = "8b852bb5aa6e7d8564f9e81394055395dd1d1936d38dfd3a17792a02bebd7af0", + }, + }, + }, + + mcpp = { + schema = "0.1", + language = "c++23", + import_std = true, + modules = { "fmt" }, + include_dirs = { "*/include", "*/src" }, + generated_files = { + ["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 \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# if FMT_CPLUSPLUS > 202002L\n# include \n# endif\n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n# include \n#else\n# include \n# include \n# include \n# include \n# include \n# include \n#endif\n#include \n#include \n#include \n\n#if __has_include()\n# include \n#endif\n#if defined(_MSC_VER) || defined(__MINGW32__)\n# include \n#endif\n#if defined __APPLE__ || defined(__FreeBSD__)\n# include \n#endif\n#if __has_include()\n# include \n#endif\n#if (__has_include() || defined(__APPLE__) || \\\n defined(__linux__)) && \\\n (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))\n# include \n# include \n# include \n# ifndef _WIN32\n# include \n# else\n# include \n# endif\n#endif\n#ifdef _WIN32\n# if defined(__GLIBCXX__)\n# include \n# include \n# endif\n# define WIN32_LEAN_AND_MEAN\n# include \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", + }, + sources = { "mcpp_generated/fmt_module.cppm" }, + targets = { ["fmt"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/tests/examples/fmtlib.fmt/mcpp.toml b/tests/examples/fmtlib.fmt/mcpp.toml new file mode 100644 index 0000000..118f1ed --- /dev/null +++ b/tests/examples/fmtlib.fmt/mcpp.toml @@ -0,0 +1,14 @@ +# {fmt} test project: consumes the C++23 module `fmt` (`import fmt;`, no +# #include) from the `fmtlib.fmt` package and asserts behavior under `mcpp test`. +# Part of the mcpp-index self-referential workspace: `[indices] fmtlib` points at +# this repo, so the dependency resolves to the checked-in recipe (pkgs/f/ +# fmtlib.fmt.lua) rather than a remote index. The exported module is plain `fmt`. +[package] +name = "fmtlib-fmt-tests" +version = "0.1.0" + +[indices] +fmtlib = { path = "../../.." } + +[dependencies.fmtlib] +fmt = "12.2.0" diff --git a/tests/examples/fmtlib.fmt/tests/format_test.cpp b/tests/examples/fmtlib.fmt/tests/format_test.cpp new file mode 100644 index 0000000..e10e691 --- /dev/null +++ b/tests/examples/fmtlib.fmt/tests/format_test.cpp @@ -0,0 +1,19 @@ +// Behavioral test: consume {fmt}'s compiled implementation through the C++23 +// module `fmt` (`import fmt;`, no #include) and assert the results. Exercises +// src/format.cc via the module unit's tail includes — not just header inlines. +// Returns non-zero on any mismatch. +import std; +import fmt; + +int main() { + std::string a = fmt::format("{} + {} = {}", 2, 3, 2 + 3); + std::string b = fmt::format("{:08.3f}", 3.14159); + std::string c = fmt::format("{0}-{1}-{0}", "x", "y"); + std::string d = fmt::format("{:#x}", 255); + + bool ok = a == "2 + 3 = 5" + && b == "0003.142" + && c == "x-y-x" + && d == "0xff"; + return ok ? 0 : 1; +}