From c5867968e33bffc3843f3789f9bb1f9f4d4f1f6b Mon Sep 17 00:00:00 2001 From: pyc <1115040131@qq.com> Date: Tue, 7 Jul 2026 17:50:34 +0800 Subject: [PATCH] feat: add fmt 12.2.0 as C++23 module package (import fmt;) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/validate.yml | 34 +++++++++++ pkgs/f/fmt.lua | 67 ++++++++++++++++++++++ tests/smoke_fmt_module.sh | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 pkgs/f/fmt.lua create mode 100755 tests/smoke_fmt_module.sh diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 32bef31..2da2dce 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -206,3 +206,37 @@ jobs: run: | "$MCPP" --version timeout 1800 bash tests/smoke_imgui_module.sh + + # ── Exception: the public `fmt` C++23-module package (namespace "") ── + # Same story as imgui-module above: empty namespace maps to the builtin default + # index, which a workspace member can't point at a local path, so it keeps a + # thin driver that reseeds the default index and consumes `import fmt;`. + fmt-module: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + - name: Restore mcpp registry cache + uses: actions/cache@v4 + with: + path: ~/.mcpp/registry + key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} + restore-keys: | + mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}- + - name: Download mcpp + run: | + curl -L -fsS -o mcpp.tar.gz \ + "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz" + tar -xzf mcpp.tar.gz + root="$PWD/mcpp-${MCPP_VERSION}-linux-x86_64" + mkdir -p "$HOME/.mcpp/registry" + cp -a "$root/registry/." "$HOME/.mcpp/registry/" + echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" + echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" + echo "$root/bin" >> "$GITHUB_PATH" + - name: fmt module package smoke + env: + MCPP_INDEX_MIRROR: GLOBAL + run: | + "$MCPP" --version + timeout 1800 bash tests/smoke_fmt_module.sh diff --git a/pkgs/f/fmt.lua b/pkgs/f/fmt.lua new file mode 100644 index 0000000..22ff738 --- /dev/null +++ b/pkgs/f/fmt.lua @@ -0,0 +1,67 @@ +-- 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. Empty namespace +-- (like the public `imgui` module package) so the dependency key and the module +-- name are both simply `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 (even a dead one), and fmt.cc carries +-- `#ifdef FMT_IMPORT_STD / import std; / #endif`. So we reproduce fmt.cc via +-- `generated_files` with exactly those three lines removed — everything else is +-- upstream fmt.cc verbatim: the global-module-fragment `#include`s, 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. +package = { + spec = "1", + namespace = "", + name = "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 = false, + modules = { "fmt" }, + include_dirs = { "*/include", "*/src" }, + generated_files = { + ["mcpp_generated/fmt_module.cc"] = "// 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#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\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.cc" }, + targets = { ["fmt"] = { kind = "lib" } }, + deps = { }, + }, +} diff --git a/tests/smoke_fmt_module.sh b/tests/smoke_fmt_module.sh new file mode 100755 index 0000000..81b9ea0 --- /dev/null +++ b/tests/smoke_fmt_module.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Smoke-test the public `fmt` module package through this checkout as a local +# mcpp path index. Validates user-facing import-only consumption (`import fmt;`). +# Mirrors tests/smoke_imgui_module.sh — the empty-namespace `fmt` package maps to +# the builtin default index (mcpplibs), which a workspace member cannot point at +# a local path, so it needs this thin driver that reseeds the default index. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MCPP_BIN="${MCPP:-}" +if [[ -z "$MCPP_BIN" ]]; then + MCPP_BIN="$(command -v mcpp || true)" +fi +if [[ -z "$MCPP_BIN" || ! -x "$MCPP_BIN" ]]; then + echo "FATAL: set MCPP=/path/to/mcpp or put mcpp on PATH" >&2 + exit 1 +fi + +TMP="$(mktemp -d)" +if [[ "${MCPP_INDEX_KEEP_SMOKE_TMP:-0}" == "1" ]]; then + echo "KEEP: $TMP" +else + trap 'rm -rf "$TMP"' EXIT +fi + +if [[ -n "${MCPP_INDEX_SMOKE_MCPP_HOME:-}" ]]; then + export MCPP_HOME="$MCPP_INDEX_SMOKE_MCPP_HOME" +else + export MCPP_HOME="$TMP/mcpp-home" +fi +mkdir -p "$MCPP_HOME/registry/data/xpkgs" + +USER_MCPP="${HOME}/.mcpp" +link_xpkgs() { + local src="$1" + [[ -d "$src" ]] || return 0 + find "$src" -mindepth 1 -maxdepth 1 -type d | while read -r pkg; do + ln -s "$pkg" "$MCPP_HOME/registry/data/xpkgs/$(basename "$pkg")" 2>/dev/null || true + done +} +link_xpkgs "${MCPP_INDEX_SMOKE_XPKGS_DIR:-}" +link_xpkgs "$USER_MCPP/registry/data/xpkgs" +if [[ -d "$USER_MCPP/registry/data/xim-pkgindex" ]]; then + mkdir -p "$MCPP_HOME/registry/data/xim-pkgindex" + cp -a "$USER_MCPP/registry/data/xim-pkgindex/." "$MCPP_HOME/registry/data/xim-pkgindex/" 2>/dev/null || true + rm -f "$MCPP_HOME/registry/data/xim-pkgindex/.xlings-index-cache.json" +fi +if [[ -d "$USER_MCPP/registry/bin" ]]; then + mkdir -p "$MCPP_HOME/registry" + ln -s "$USER_MCPP/registry/bin" "$MCPP_HOME/registry/bin" 2>/dev/null || true +fi +if [[ -f "$USER_MCPP/config.toml" ]]; then + cp -f "$USER_MCPP/config.toml" "$MCPP_HOME/config.toml" 2>/dev/null || true +fi + +default_index="$MCPP_HOME/registry/data/mcpplibs" +# Reseed cleanly (see smoke_imgui_module.sh for why .git is skipped). +rm -rf "$default_index" +mkdir -p "$default_index" +( cd "$ROOT" && find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -a {} "$default_index/" \; ) +rm -f "$default_index/.xlings-index-cache.json" +printf 'ok\n' > "$default_index/.mcpp-index-updated" + +"$MCPP_BIN" self config --mirror "${MCPP_INDEX_MIRROR:-GLOBAL}" >/dev/null + +mkdir -p "$TMP/fmt-module-smoke/src" +cd "$TMP/fmt-module-smoke" +cat > mcpp.toml < src/main.cpp <<'EOF' +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"; + std::println("fmt module package ok: {}", a); + return ok ? 0 : 1; +} +EOF + +"$MCPP_BIN" build +"$MCPP_BIN" run