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 1/4] 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 From b04f3aa6ad56ddec9a59da43e372383715b1389c Mon Sep 17 00:00:00 2001 From: pyc <1115040131@qq.com> Date: Wed, 8 Jul 2026 15:06:40 +0800 Subject: [PATCH 2/4] test(workspace): fmt as fmtlib.fmt workspace member (drop smoke driver) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/validate.yml | 34 ------ mcpp.toml | 1 + pkgs/f/{fmt.lua => fmtlib.fmt.lua} | 12 ++- tests/examples/fmtlib.fmt/mcpp.toml | 14 +++ .../examples/fmtlib.fmt/tests/format_test.cpp | 19 ++++ tests/smoke_fmt_module.sh | 101 ------------------ 6 files changed, 41 insertions(+), 140 deletions(-) rename pkgs/f/{fmt.lua => fmtlib.fmt.lua} (94%) create mode 100644 tests/examples/fmtlib.fmt/mcpp.toml create mode 100644 tests/examples/fmtlib.fmt/tests/format_test.cpp delete mode 100755 tests/smoke_fmt_module.sh diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 2da2dce..32bef31 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -206,37 +206,3 @@ 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/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/fmt.lua b/pkgs/f/fmtlib.fmt.lua similarity index 94% rename from pkgs/f/fmt.lua rename to pkgs/f/fmtlib.fmt.lua index 22ff738..3753c39 100644 --- a/pkgs/f/fmt.lua +++ b/pkgs/f/fmtlib.fmt.lua @@ -1,8 +1,10 @@ -- 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`. +-- 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: @@ -23,8 +25,8 @@ -- (no glob), like nlohmann.json / compat.eigen. package = { spec = "1", - namespace = "", - name = "fmt", + 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", 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; +} diff --git a/tests/smoke_fmt_module.sh b/tests/smoke_fmt_module.sh deleted file mode 100755 index 81b9ea0..0000000 --- a/tests/smoke_fmt_module.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/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 From 8c349bc0072ded8c9d95104bab50c4ccfe174c71 Mon Sep 17 00:00:00 2001 From: pyc <1115040131@qq.com> Date: Wed, 8 Jul 2026 15:25:36 +0800 Subject: [PATCH 3/4] refactor(fmtlib.fmt): use import std instead of stripping the guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `…`` 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 --- pkgs/f/fmtlib.fmt.lua | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pkgs/f/fmtlib.fmt.lua b/pkgs/f/fmtlib.fmt.lua index 3753c39..cfba66a 100644 --- a/pkgs/f/fmtlib.fmt.lua +++ b/pkgs/f/fmtlib.fmt.lua @@ -9,10 +9,16 @@ -- 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 +-- 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 @@ -56,11 +62,11 @@ package = { mcpp = { schema = "0.1", language = "c++23", - import_std = false, + import_std = true, 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", + ["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#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.cc" }, targets = { ["fmt"] = { kind = "lib" } }, From d627c9f96927009d2989cccd3145476ed9e914bb Mon Sep 17 00:00:00 2001 From: pyc <1115040131@qq.com> Date: Wed, 8 Jul 2026 20:14:50 +0800 Subject: [PATCH 4/4] fix(fmtlib.fmt): name module wrapper .cppm so Clang emits the BMI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- pkgs/f/fmtlib.fmt.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkgs/f/fmtlib.fmt.lua b/pkgs/f/fmtlib.fmt.lua index cfba66a..be3875d 100644 --- a/pkgs/f/fmtlib.fmt.lua +++ b/pkgs/f/fmtlib.fmt.lua @@ -29,6 +29,16 @@ -- ` 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", @@ -66,9 +76,9 @@ package = { 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#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", + ["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.cc" }, + sources = { "mcpp_generated/fmt_module.cppm" }, targets = { ["fmt"] = { kind = "lib" } }, deps = { }, },