From a20290e54732fce34b4f609939dba37d645839e6 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 01:15:07 +0100 Subject: [PATCH 01/32] Change version to 12.1.0-a.0.z --- fmt-tests/manifest | 2 +- fmt/manifest | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fmt-tests/manifest b/fmt-tests/manifest index 19a96de..81327c5 100644 --- a/fmt-tests/manifest +++ b/fmt-tests/manifest @@ -1,6 +1,6 @@ : 1 name: fmt-tests -version: 11.1.4 +version: 12.1.0-a.0.z project: fmt summary: Tests package for fmt upstream tests license: MIT diff --git a/fmt/manifest b/fmt/manifest index 1306855..a32d0a5 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -1,6 +1,6 @@ : 1 name: fmt -version: 11.1.4 +version: 12.1.0-a.0.z summary: "{fmt} is an open-source formatting library for C++. It can be used as a safe and fast alternative to (s)printf and iostreams." license: MIT description-file: README.md From a3d26a18c7c1cbc6da8ad8302ae00a80a2bd60a1 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 01:16:46 +0100 Subject: [PATCH 02/32] Update upstream submodule to 12.1.0 --- upstream | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upstream b/upstream index 1239137..407c905 160000 --- a/upstream +++ b/upstream @@ -1 +1 @@ -Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a +Subproject commit 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f From 59a0c87ecaa9996b97c85e1a018279e0414b0396 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 01:26:20 +0100 Subject: [PATCH 03/32] Move `README.md` to repository root --- fmt/README.md => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename fmt/README.md => README.md (100%) diff --git a/fmt/README.md b/README.md similarity index 100% rename from fmt/README.md rename to README.md From 2d9653140ebacefc08a09e6c45493f36e104bce6 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 01:36:13 +0100 Subject: [PATCH 04/32] Add upstream's readme, license, and changelog --- LICENSE | 1 + fmt-tests/ChangeLog.md | 1 + fmt-tests/LICENSE | 1 + fmt-tests/README.md | 1 + fmt-tests/buildfile | 2 +- fmt-tests/manifest | 4 ++++ fmt/ChangeLog.md | 1 + fmt/LICENSE | 1 + fmt/README.md | 1 + fmt/buildfile | 4 ++-- fmt/manifest | 3 +++ 11 files changed, 17 insertions(+), 3 deletions(-) create mode 120000 LICENSE create mode 120000 fmt-tests/ChangeLog.md create mode 120000 fmt-tests/LICENSE create mode 120000 fmt-tests/README.md create mode 120000 fmt/ChangeLog.md create mode 120000 fmt/LICENSE create mode 120000 fmt/README.md diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..4d5ed76 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +upstream/LICENSE \ No newline at end of file diff --git a/fmt-tests/ChangeLog.md b/fmt-tests/ChangeLog.md new file mode 120000 index 0000000..fb437de --- /dev/null +++ b/fmt-tests/ChangeLog.md @@ -0,0 +1 @@ +../upstream/ChangeLog.md \ No newline at end of file diff --git a/fmt-tests/LICENSE b/fmt-tests/LICENSE new file mode 120000 index 0000000..6246057 --- /dev/null +++ b/fmt-tests/LICENSE @@ -0,0 +1 @@ +../upstream/LICENSE \ No newline at end of file diff --git a/fmt-tests/README.md b/fmt-tests/README.md new file mode 120000 index 0000000..a3cf035 --- /dev/null +++ b/fmt-tests/README.md @@ -0,0 +1 @@ +../upstream/README.md \ No newline at end of file diff --git a/fmt-tests/buildfile b/fmt-tests/buildfile index f065128..cfda920 100644 --- a/fmt-tests/buildfile +++ b/fmt-tests/buildfile @@ -1,4 +1,4 @@ -./: manifest +./: manifest legal{LICENSE} doc{*.md} # NOTE: for the moment, we need to disable all the test targets in modules-only mode, since upstream fmt does not yet properly support it. import! [metadata, rule_hint=cxx.link] fmt_lib = fmt%lib{fmt} diff --git a/fmt-tests/manifest b/fmt-tests/manifest index 81327c5..fea872b 100644 --- a/fmt-tests/manifest +++ b/fmt-tests/manifest @@ -4,7 +4,11 @@ version: 12.1.0-a.0.z project: fmt summary: Tests package for fmt upstream tests license: MIT + +description-file: README.md +changes-file: ChangeLog.md url: https://github.com/fmtlib/fmt/ +doc-url: https://fmt.dev/ depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 diff --git a/fmt/ChangeLog.md b/fmt/ChangeLog.md new file mode 120000 index 0000000..fb437de --- /dev/null +++ b/fmt/ChangeLog.md @@ -0,0 +1 @@ +../upstream/ChangeLog.md \ No newline at end of file diff --git a/fmt/LICENSE b/fmt/LICENSE new file mode 120000 index 0000000..6246057 --- /dev/null +++ b/fmt/LICENSE @@ -0,0 +1 @@ +../upstream/LICENSE \ No newline at end of file diff --git a/fmt/README.md b/fmt/README.md new file mode 120000 index 0000000..a3cf035 --- /dev/null +++ b/fmt/README.md @@ -0,0 +1 @@ +../upstream/README.md \ No newline at end of file diff --git a/fmt/buildfile b/fmt/buildfile index 146d18d..2122444 100644 --- a/fmt/buildfile +++ b/fmt/buildfile @@ -1,5 +1,5 @@ -./: {*/ -build/ -upstream/ -doc/} manifest doc{README.md} doc/doc{**} +./: fmt/ manifest legal{LICENSE} doc{*.md} doc/doc{**} # Don't install tests. # -tests/: install = false +./: tests/: install = false diff --git a/fmt/manifest b/fmt/manifest index a32d0a5..26bcba3 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -3,9 +3,12 @@ name: fmt version: 12.1.0-a.0.z summary: "{fmt} is an open-source formatting library for C++. It can be used as a safe and fast alternative to (s)printf and iostreams." license: MIT + description-file: README.md +changes-file: ChangeLog.md doc-url: https://fmt.dev/ url: https://github.com/fmtlib/fmt/ + package-url: https://github.com/build2-packaging/fmt/ package-email: mjklaim@gmail.com From d81da494f522421440dbfe023c91b10b3b06dfb7 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 01:50:00 +0100 Subject: [PATCH 05/32] Tweak `manifest` files --- fmt-tests/manifest | 12 +++++++++--- fmt/manifest | 12 ++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/fmt-tests/manifest b/fmt-tests/manifest index fea872b..be47ade 100644 --- a/fmt-tests/manifest +++ b/fmt-tests/manifest @@ -1,14 +1,20 @@ : 1 name: fmt-tests version: 12.1.0-a.0.z +type: exe +language: c++ project: fmt -summary: Tests package for fmt upstream tests +summary: Formatting C++ library tests license: MIT description-file: README.md changes-file: ChangeLog.md -url: https://github.com/fmtlib/fmt/ -doc-url: https://fmt.dev/ +url: https://fmt.dev/ +src-url: https://github.com/fmtlib/fmt/ +email: victor.zverovich@gmail.com + +package-url: https://github.com/build2-packaging/fmt/ +package-email: packaging@build2.org depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 diff --git a/fmt/manifest b/fmt/manifest index 26bcba3..27c155c 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -1,16 +1,20 @@ : 1 name: fmt version: 12.1.0-a.0.z -summary: "{fmt} is an open-source formatting library for C++. It can be used as a safe and fast alternative to (s)printf and iostreams." +type: lib +language: c++ +project: fmt +summary: Formatting C++ library used as alternative to (s)printf and iostreams license: MIT description-file: README.md changes-file: ChangeLog.md -doc-url: https://fmt.dev/ -url: https://github.com/fmtlib/fmt/ +url: https://fmt.dev/ +src-url: https://github.com/fmtlib/fmt/ +email: victor.zverovich@gmail.com package-url: https://github.com/build2-packaging/fmt/ -package-email: mjklaim@gmail.com +package-email: packaging@build2.org tests: fmt-tests == $ From fd03c87c0e2a640442351ebfb5eb0e6cbd82d26c Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 02:12:20 +0100 Subject: [PATCH 06/32] Disable install of tests package in root buildfile --- buildfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/buildfile b/buildfile index c3c8909..57b386d 100644 --- a/buildfile +++ b/buildfile @@ -1,6 +1,5 @@ -# Glue buildfile that "pulls" all the packages in the project. -# -import pkgs = [dir_paths] $process.run_regex(\ - cat $src_root/packages.manifest, '\s*location\s*:\s*(\S+)\s*', '\1') +import pkgs = {*/ -*-tests/ -upstream/} +import tests = {*-tests/} ./: $pkgs +./: $tests: install = false From 716b080bd1a18d2793ef83b822b1692391d9857d Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 14:06:21 +0100 Subject: [PATCH 07/32] Update configuration variables for better defaults --- fmt/build/root.build | 17 +++++---- fmt/fmt/buildfile | 91 +++++++++++++++++++++++++------------------- 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/fmt/build/root.build b/fmt/build/root.build index 499fa36..3b72570 100644 --- a/fmt/build/root.build +++ b/fmt/build/root.build @@ -1,18 +1,21 @@ +# Configuration Variables + +# +# +config [bool] config.fmt.module_only ?= false +config [bool] config.fmt.disable_import_std ?= false + +# C++ Configuration +# cxx.std = latest using cxx hxx{*}: extension = h +ixx{*}: extension = cc cxx{*}: extension = cc mxx{*}: extension = cc # The test target for cross-testing (running tests under Wine, etc). # test.target = $cxx.target - - -############################## -# Project-specific options: - -config [bool] config.fmt.enable_import_std ?= false -config [bool] config.fmt.modules_only ?= false diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 56a6cbf..774a67e 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -1,28 +1,26 @@ -./ : lib{fmt} - +# Headers +# lib{fmt}: include/hxx{**} hxx{version} -# Automatically build as module if the feature is enabled -build_fmt_module = $cxx.features.modules -allow_header_usage = ($build_fmt_module == false || $config.fmt.modules_only == false) +# Sources +# +src_type = ($cxx.features.modules ? ixx : cxx) +lib{fmt}: src/$src_type{** -fmt} -if($build_fmt_module && $allow_header_usage) - info "Building fmt with dual module/header mode enabled" +# Module +# +lib{fmt}: src/mxx{fmt} : include = $cxx.features.modules -# Workaround for MSVC bug: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 -if($build_fmt_module && $cxx.class == 'msvc') - cc.reprocess = true +# Automatically build as module if the feature is enabled +# build_fmt_module = $cxx.features.modules +# allow_header_usage = ($build_fmt_module == false || $config.fmt.modules_only == false) -lib{fmt}: src/mxx{fmt} : include = ($build_fmt_module) # `fmt` C++ module only -lib{fmt}: src/cxx{** -fmt} : include = (!$build_fmt_module) # no modules only +# if($build_fmt_module && $allow_header_usage) +# info "Building fmt with dual module/header mode enabled" -# Meta data for users -lib{fmt}: -{ - export.metadata = 1 fmt - fmt.has_header = [bool] $allow_header_usage - fmt.has_module = [bool] $build_fmt_module -} +# Workaround for MSVC bug: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 +# if($build_fmt_module && $cxx.class == 'msvc') +# cc.reprocess = true # Include the generated version header into the distribution (so that we don't # pick up an installed one) and don't remove it when cleaning in src (so that @@ -42,31 +40,44 @@ cxx.poptions =+ "-I$src_base/include" "-I$out_root" {objs bmis}{*}: cxx.poptions += -DFMT_LIB_EXPORT # If building fmt module, also enable attaching to the global module in order to allow concurrent #include and import. -if($build_fmt_module) +if $cxx.features.modules { - if($allow_header_usage) - # Support mixing consuming as both modules and headers within a single build - cxx.poptions =+ -DFMT_ATTACH_TO_GLOBAL_MODULE - if($config.fmt.enable_import_std) - cxx.poptions =+ -DFMT_IMPORT_STD + cxx.poptions =+ "-I$src_base/src" + if! $config.fmt.module_only + cxx.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE + if! $config.fmt.disable_import_std + cxx.poptions += -DFMT_IMPORT_STD } # Export options. # -if($allow_header_usage) +if! $config.fmt.module_only { # Note: these poptions only relevant for header consumption lib{fmt}: cxx.export.poptions = "-I$src_base/include" "-I$out_root" - if($build_fmt_module) - { - # For consumer-built BMI compatibility, this must be exported - lib{fmt}: cxx.export.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE - } + # For consumer-built BMI compatibility, this must be exported + # if($build_fmt_module) + # lib{fmt}: cxx.export.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE libs{fmt}: cxx.export.poptions += -DFMT_SHARED } +if ($cxx.class == 'msvc') +{ + cxx.poptions += /utf-8 + lib{fmt}: cxx.export.poptions += /utf-8 +} + +# Metadata +# +lib{fmt}: +{ + export.metadata = 1 fmt + fmt.has_header = [bool] (!$config.fmt.module_only) + fmt.has_module = [bool] $cxx.features.modules +} + # For pre-releases use the complete version to make sure they cannot be used # in place of another pre-release or the final version. See the version module # for details on the version.* variable values. @@ -76,14 +87,16 @@ if $version.pre_release else lib{fmt}: bin.lib.version = @"-$version.major.$version.minor" -# Install into the fmt/ subdirectory of, say, /usr/include/ -# recreating subdirectories. +# Installation # -include/ +include/hxx{*}: +{ + install = include/ + install.subdirs = true +} + +src/{mxx ixx}{*}: { - {hxx ixx txx}{*}: - { - install = include/ - install.subdirs = true - } + install = include/fmt/ + install.subdirs = true } From 8d1e2f6408cf711abf9fb09a9566db9ae75b7e48 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 14:08:18 +0100 Subject: [PATCH 08/32] Use C++20 for unit tests Otherwise, compilation fails for `std-test` and `xchar-test`. Also, upstream only supports C++20 in its build system. --- fmt-tests/build/root.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fmt-tests/build/root.build b/fmt-tests/build/root.build index b269653..d7d8725 100644 --- a/fmt-tests/build/root.build +++ b/fmt-tests/build/root.build @@ -2,7 +2,7 @@ # #cxx.internal.scope = current -cxx.std = latest +cxx.std = 20 using cxx From 90e8f0a63f393f3ce51c5b20a6f9bc6d009898c3 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 14:21:19 +0100 Subject: [PATCH 09/32] Remove version header generation Upstream does not provide a version header for its consumers. It would be sub-optimal to rely on it when not officially supported. Also, upstream offers the `FMT_VERSION` macro. --- fmt/fmt/.gitignore | 3 --- fmt/fmt/buildfile | 14 +------------- fmt/fmt/version.h.in | 33 --------------------------------- fmt/tests/basics/driver.cxx | 10 ++++------ 4 files changed, 5 insertions(+), 55 deletions(-) delete mode 100644 fmt/fmt/.gitignore delete mode 100644 fmt/fmt/version.h.in diff --git a/fmt/fmt/.gitignore b/fmt/fmt/.gitignore deleted file mode 100644 index 0c9102a..0000000 --- a/fmt/fmt/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Generated version header. -# -version.hxx diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 774a67e..a10bb96 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -1,6 +1,6 @@ # Headers # -lib{fmt}: include/hxx{**} hxx{version} +lib{fmt}: include/hxx{**} # Sources # @@ -22,18 +22,6 @@ lib{fmt}: src/mxx{fmt} : include = $cxx.features.modules # if($build_fmt_module && $cxx.class == 'msvc') # cc.reprocess = true -# Include the generated version header into the distribution (so that we don't -# pick up an installed one) and don't remove it when cleaning in src (so that -# clean results in a state identical to distributed). -# -hxx{version} : in{version} $src_root/manifest -{ - dist = true - clean = ($src_root != $out_root) - install = include/fmt -} - - # Build options. # cxx.poptions =+ "-I$src_base/include" "-I$out_root" diff --git a/fmt/fmt/version.h.in b/fmt/fmt/version.h.in deleted file mode 100644 index 5d5e138..0000000 --- a/fmt/fmt/version.h.in +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -// The numeric version format is AAAAABBBBBCCCCCDDDE where: -// -// AAAAA - major version number -// BBBBB - minor version number -// CCCCC - bugfix version number -// DDD - alpha / beta (DDD + 500) version number -// E - final (0) / snapshot (1) -// -// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example: -// -// Version AAAAABBBBBCCCCCDDDE -// -// 0.1.0 0000000001000000000 -// 0.1.2 0000000001000020000 -// 1.2.3 0000100002000030000 -// 2.2.0-a.1 0000200001999990010 -// 3.0.0-b.2 0000299999999995020 -// 2.2.0-a.1.z 0000200001999990011 -// -#define FMT_VERSION_BUILD2 $fmt.version.project_number$ULL -#define FMT_VERSION_STR "$fmt.version.project$" -#define FMT_VERSION_ID "$fmt.version.project_id$" - -#define FMT_VERSION_MAJOR $fmt.version.major$ -#define FMT_VERSION_MINOR $fmt.version.minor$ -#define FMT_VERSION_PATCH $fmt.version.patch$ - -#define FMT_PRE_RELEASE $fmt.version.pre_release$ - -#define FMT_SNAPSHOT_SN $fmt.version.snapshot_sn$ULL -#define FMT_SNAPSHOT_ID "$fmt.version.snapshot_id$" diff --git a/fmt/tests/basics/driver.cxx b/fmt/tests/basics/driver.cxx index cff192d..066e3b3 100644 --- a/fmt/tests/basics/driver.cxx +++ b/fmt/tests/basics/driver.cxx @@ -1,15 +1,13 @@ #include -#include #include +#include #include -#include -#include +#include +#include #include #include -#include -#include +#include #include #include "tests.inl" - From da2e1925f5ca1b7701e8aa5c0491a397507eb01a Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 14:50:17 +0100 Subject: [PATCH 10/32] Reduce and refactor smoke tests --- fmt/fmt/buildfile | 35 ++++----- fmt/tests/basics/buildfile | 8 +-- .../{driver-modules.cxx => driver-modules.cc} | 0 fmt/tests/basics/{driver.cxx => driver.cc} | 0 fmt/tests/basics/tests.inl | 72 +++++++++---------- fmt/tests/build/root.build | 8 +-- 6 files changed, 54 insertions(+), 69 deletions(-) rename fmt/tests/basics/{driver-modules.cxx => driver-modules.cc} (100%) rename fmt/tests/basics/{driver.cxx => driver.cc} (100%) diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index a10bb96..654a292 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -1,6 +1,7 @@ # Headers # -lib{fmt}: include/hxx{**} +hdr_type = ($config.fmt.module_only ? ixx : hxx) +lib{fmt}: include/$hdr_type{**.h} # Sources # @@ -11,23 +12,16 @@ lib{fmt}: src/$src_type{** -fmt} # lib{fmt}: src/mxx{fmt} : include = $cxx.features.modules -# Automatically build as module if the feature is enabled -# build_fmt_module = $cxx.features.modules -# allow_header_usage = ($build_fmt_module == false || $config.fmt.modules_only == false) - -# if($build_fmt_module && $allow_header_usage) -# info "Building fmt with dual module/header mode enabled" - # Workaround for MSVC bug: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 # if($build_fmt_module && $cxx.class == 'msvc') # cc.reprocess = true -# Build options. +# Build Options # -cxx.poptions =+ "-I$src_base/include" "-I$out_root" +cxx.poptions =+ "-I$src_base/include" + {objs bmis}{*}: cxx.poptions += -DFMT_LIB_EXPORT -# If building fmt module, also enable attaching to the global module in order to allow concurrent #include and import. if $cxx.features.modules { cxx.poptions =+ "-I$src_base/src" @@ -37,17 +31,11 @@ if $cxx.features.modules cxx.poptions += -DFMT_IMPORT_STD } -# Export options. +# Export Options # if! $config.fmt.module_only { - # Note: these poptions only relevant for header consumption - lib{fmt}: cxx.export.poptions = "-I$src_base/include" "-I$out_root" - - # For consumer-built BMI compatibility, this must be exported - # if($build_fmt_module) - # lib{fmt}: cxx.export.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE - + lib{fmt}: cxx.export.poptions = "-I$src_base/include" libs{fmt}: cxx.export.poptions += -DFMT_SHARED } @@ -77,14 +65,17 @@ else # Installation # -include/hxx{*}: +if! $config.fmt.module_only + lib{fmt}: cxx.pkgconfig.include = include/fmtlib/ + +include/{hxx ixx}{*}: { - install = include/ + install = include/fmtlib/ install.subdirs = true } src/{mxx ixx}{*}: { - install = include/fmt/ + install = include/fmtlib/ install.subdirs = true } diff --git a/fmt/tests/basics/buildfile b/fmt/tests/basics/buildfile index 4c6f11d..9978a45 100644 --- a/fmt/tests/basics/buildfile +++ b/fmt/tests/basics/buildfile @@ -3,9 +3,5 @@ import! [metadata, rule_hint=cxx.link] libs = fmt%lib{fmt} ./ : exe{driver} : include = $($libs: fmt.has_header) ./ : exe{driver-modules} : include = $($libs: fmt.has_module) -exe{driver} : {cxx}{driver} hxx{tests.inl} $libs testscript{**} - -# For purposes of verifying that fmt headers are not made available for include when in modules-only mode -cxx.poptions =+ "-DFMT_BUILD2_HAS_HEADER=($($libs: fmt.has_header) ? 1 : 0)" - -exe{driver-modules} : {cxx}{driver-modules} hxx{tests.inl} $libs testscript{**} +exe{driver} : cxx{driver} ixx{tests} $libs +exe{driver-modules} : cxx{driver-modules} ixx{tests} $libs diff --git a/fmt/tests/basics/driver-modules.cxx b/fmt/tests/basics/driver-modules.cc similarity index 100% rename from fmt/tests/basics/driver-modules.cxx rename to fmt/tests/basics/driver-modules.cc diff --git a/fmt/tests/basics/driver.cxx b/fmt/tests/basics/driver.cc similarity index 100% rename from fmt/tests/basics/driver.cxx rename to fmt/tests/basics/driver.cc diff --git a/fmt/tests/basics/tests.inl b/fmt/tests/basics/tests.inl index b5de3e8..3022f4e 100644 --- a/fmt/tests/basics/tests.inl +++ b/fmt/tests/basics/tests.inl @@ -1,17 +1,17 @@ -int main () -{ - // THESE EXAMPLES ARE EXTRACTED FROM THE README AND SHOULD BE UPDATED EACH BREAKING VERSION +int main() { + // THESE EXAMPLES ARE EXTRACTED FROM THE README AND SHOULD BE UPDATED EACH + // BREAKING VERSION { - fmt::print("Hello, world!\n"); + fmt::print("Hello, world!\n"); - std::string s = fmt::format("The answer is {}.", 42); - assert(s == "The answer is 42."); - - std::string s2 = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); - assert(s2 == "I'd rather be happy than right."); + std::string s = fmt::format("The answer is {}.", 42); + assert(s == "The answer is 42."); + std::string s2 = + fmt::format("I'd rather be {1} than {0}.", "right", "happy"); + assert(s2 == "I'd rather be happy than right."); } { @@ -20,19 +20,20 @@ int main () } { - fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, - "Hello, {}!\n", "world"); + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "Hello, {}!\n", + "world"); fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | - fmt::emphasis::underline, "Olá, {}!\n", "Mundo"); - fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, - "你好{}!\n", "世界"); + fmt::emphasis::underline, + "Olá, {}!\n", "Mundo"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, "你好{}!\n", + "世界"); } - { - auto now = std::chrono::system_clock::now(); - fmt::print("Date and time: {}\n", now); - fmt::print("Time: {:%H:%M}\n", now); - } + // { + // auto now = std::chrono::system_clock::now(); + // fmt::print("Date and time: {}\n", now); + // fmt::print("Time: {:%H:%M}\n", now); + // } { fmt::print("Hello, {}!", "world"); // Python-like format string syntax @@ -40,26 +41,25 @@ int main () } { - std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); + std::string s = + fmt::format("I'd rather be {1} than {0}.", "right", "happy"); } - } -struct date { - int year, month, day; -}; - -template <> -struct fmt::formatter { - template - constexpr auto parse(ParseContext& ctx) const { return ctx.begin(); } +// struct date { +// int year, month, day; +// }; - template - constexpr auto format(const date& d, FormatContext& ctx) const { - // Namespace-qualify to avoid ambiguity with std::format_to. - return fmt::format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day); - } -}; +// template <> +// struct fmt::formatter { +// template +// constexpr auto parse(ParseContext& ctx) const { return ctx.begin(); } -std::string s = fmt::format("The date is {}", date{2012, 12, 9}); +// template +// constexpr auto format(const date& d, FormatContext& ctx) const { +// // Namespace-qualify to avoid ambiguity with std::format_to. +// return fmt::format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day); +// } +// }; +// std::string s = fmt::format("The date is {}", date{2012, 12, 9}); diff --git a/fmt/tests/build/root.build b/fmt/tests/build/root.build index 9c1f6bd..81bf1b0 100644 --- a/fmt/tests/build/root.build +++ b/fmt/tests/build/root.build @@ -2,11 +2,9 @@ cxx.std = latest using cxx -hxx{*}: extension = hxx -ixx{*}: extension = ixx -txx{*}: extension = txx -cxx{*}: extension = cxx -mxx{*}: extension = cc +hxx{*}: extension = h +ixx{*}: extension = inl +cxx{*}: extension = cc # Every exe{} in this subproject is by default a test. # From ca28a1867a02235cb8269afa91c0b1a458b16d48 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 14:53:08 +0100 Subject: [PATCH 11/32] Use all three CI build configs on default targets --- fmt/manifest | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/fmt/manifest b/fmt/manifest index 27c155c..93fad29 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -21,12 +21,6 @@ tests: fmt-tests == $ depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 -builds: default -build-exclude: linux_debian_12-clang_17 ; clang-17 bug with libstdc++ std::tuple (https://github.com/llvm/llvm-project/issues/61415) -build-exclude: **/x86_64-w64-mingw32 ; unknown error building installed tests 'error: unable to stat path D:\a\msys64\mingw64\lib\x86_64-w64-mingw32\14.1.0\pkgconfig\: the device is not ready' +module-build-config: config.cxx.features.modules=true -# Modules support still not sufficient to enable on CI -modules-builds: linux : &( +clang-18+ ) ; Modules builds only supported for latest Clang and MSVC -modules-build-config: config.cxx.features.modules=true ; Enable c++20 modules -modules-only-builds: linux : &( +clang-18+ ) ; Modules builds only supported for latest Clang and MSVC -modules-only-build-config: config.cxx.features.modules=true config.fmt.modules_only=true ; Enable c++20 modules and disable header usage +module-only-build-config: config.cxx.features.modules=true config.fmt.module_only=true From dcb060284961b9f7661e1b8b85efb1b9e90e715a Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 18:56:54 +0100 Subject: [PATCH 12/32] Use generated config header for module consumption --- fmt/build/root.build | 2 +- fmt/fmt/buildfile | 46 ++++++++++++++++++++++++++++++-------------- fmt/fmt/config.h.in | 3 +++ 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 fmt/fmt/config.h.in diff --git a/fmt/build/root.build b/fmt/build/root.build index 3b72570..a967682 100644 --- a/fmt/build/root.build +++ b/fmt/build/root.build @@ -14,7 +14,7 @@ using cxx hxx{*}: extension = h ixx{*}: extension = cc cxx{*}: extension = cc -mxx{*}: extension = cc +mxx{*}: extension = ccm # The test target for cross-testing (running tests under Wine, etc). # diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 654a292..4739a2c 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -10,7 +10,27 @@ lib{fmt}: src/$src_type{** -fmt} # Module # -lib{fmt}: src/mxx{fmt} : include = $cxx.features.modules +src/ +{ + mxx{fmt}: file{fmt.cc} + {{ + sed -e 's/^module;/module;\n#include "config.h"/' $path($<) >$path($>) + }} + + hxx{config}: ../file{config.h.in} + {{ + diag conf $< -> $> + cp $path($<) $path($>) + if! $config.fmt.module_only + echo "#define FMT_ATTACH_TO_GLOBAL_MODULE" >+$path($>) + end + if! $config.fmt.disable_import_std + echo "#define FMT_IMPORT_STD" >+$path($>) + end + }} +} + +lib{fmt}: src/hxx{config} src/mxx{fmt}: include = $cxx.features.modules # Workaround for MSVC bug: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 # if($build_fmt_module && $cxx.class == 'msvc') @@ -18,26 +38,24 @@ lib{fmt}: src/mxx{fmt} : include = $cxx.features.modules # Build Options # -cxx.poptions =+ "-I$src_base/include" +cxx.poptions =+ "-I$src_base/include" "-I$out_base/src" "-I$src_base/src" {objs bmis}{*}: cxx.poptions += -DFMT_LIB_EXPORT -if $cxx.features.modules -{ - cxx.poptions =+ "-I$src_base/src" - if! $config.fmt.module_only - cxx.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE - if! $config.fmt.disable_import_std - cxx.poptions += -DFMT_IMPORT_STD -} +# if $cxx.features.modules +# { +# cxx.poptions =+ "-I$src_base/src" +# if! $config.fmt.module_only +# cxx.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE +# if! $config.fmt.disable_import_std +# cxx.poptions += -DFMT_IMPORT_STD +# } # Export Options # if! $config.fmt.module_only -{ lib{fmt}: cxx.export.poptions = "-I$src_base/include" - libs{fmt}: cxx.export.poptions += -DFMT_SHARED -} +libs{fmt}: cxx.export.poptions += -DFMT_SHARED if ($cxx.class == 'msvc') { @@ -74,7 +92,7 @@ include/{hxx ixx}{*}: install.subdirs = true } -src/{mxx ixx}{*}: +src/{hxx mxx ixx}{*}: { install = include/fmtlib/ install.subdirs = true diff --git a/fmt/fmt/config.h.in b/fmt/fmt/config.h.in new file mode 100644 index 0000000..72fabf8 --- /dev/null +++ b/fmt/fmt/config.h.in @@ -0,0 +1,3 @@ +#ifdef FMT_SHARED +# define FMT_LIB_EXPORT +#endif From 9ac287d5f66b0304ee130b057e961f840713cd5f Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 19:46:50 +0100 Subject: [PATCH 13/32] Re-introduce MSVC workaround to fix error C7657 --- fmt/fmt/buildfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 4739a2c..5ad5668 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -32,9 +32,11 @@ src/ lib{fmt}: src/hxx{config} src/mxx{fmt}: include = $cxx.features.modules -# Workaround for MSVC bug: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 -# if($build_fmt_module && $cxx.class == 'msvc') -# cc.reprocess = true +# Workaround for MSVC Bug +# See: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 +# +if($cxx.features.modules && $cxx.class == 'msvc') + cc.reprocess = true # Build Options # From ac282863a92a80637f44ea7965ca068ee3eafac7 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 20:16:18 +0100 Subject: [PATCH 14/32] Restrict CI builds --- fmt/manifest | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/fmt/manifest b/fmt/manifest index 93fad29..f64ab87 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -21,6 +21,19 @@ tests: fmt-tests == $ depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 -module-build-config: config.cxx.features.modules=true +module-builds: default experimental : &( +gcc-15+ +clang-19+ +msvc ) +module-build-config: +\ +config.cxx.features.modules=true +; +Enable support for both the {fmt} headers and its C++ module. +\ -module-only-build-config: config.cxx.features.modules=true config.fmt.module_only=true +module-only-builds: default experimental : &( +gcc-15+ +clang-19+ +msvc ) +module-only-build-config: +\ +config.cxx.features.modules=true +config.fmt.module_only=true +; +Only use the {fmt} C++ module. +\ From 4a2f37563c82f536ed5d8966e8c9c051468a288e Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 23:28:44 +0100 Subject: [PATCH 15/32] Tweak `buildfile` and explain module configuration --- fmt/build/root.build | 2 +- fmt/fmt/buildfile | 60 +++++++++++++++++++++++++------------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/fmt/build/root.build b/fmt/build/root.build index a967682..3b72570 100644 --- a/fmt/build/root.build +++ b/fmt/build/root.build @@ -14,7 +14,7 @@ using cxx hxx{*}: extension = h ixx{*}: extension = cc cxx{*}: extension = cc -mxx{*}: extension = ccm +mxx{*}: extension = cc # The test target for cross-testing (running tests under Wine, etc). # diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 5ad5668..30f11d8 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -10,59 +10,67 @@ lib{fmt}: src/$src_type{** -fmt} # Module # +# To successfully generate a Binary Module Interface (BMI) after installation, +# the consumer must compile the module interface unit with the same macro +# configuration used during the library's original build. +# Since these macros are only required by the module itself, they should not be +# exported to the consumer's preprocessor scope via 'cxx.export.poptions'. +# To bridge this gap, we generate a persistent 'config.h' that encapsulates the +# package configuration. We then patch the module interface unit to include this +# header within the Global Module Fragment, ensuring the macros are available +# during BMI generation without leaking into the consumer's translation units. +# +lib{fmt}: src/hxx{config} src/mxx{fmt}: include = $cxx.features.modules + src/ { + # Place the header include immediately after 'module;' to ensure all + # relevant macros are available before any other macro preprocessing. + # mxx{fmt}: file{fmt.cc} {{ + diag patch $< -> $> sed -e 's/^module;/module;\n#include "config.h"/' $path($<) >$path($>) }} + # The config header needs to define all macros that were required + # to compile the module interface unit during the installation. + # hxx{config}: ../file{config.h.in} {{ - diag conf $< -> $> + diag config $< -> $> cp $path($<) $path($>) if! $config.fmt.module_only - echo "#define FMT_ATTACH_TO_GLOBAL_MODULE" >+$path($>) + echo '#define FMT_ATTACH_TO_GLOBAL_MODULE' >+$path($>) end if! $config.fmt.disable_import_std - echo "#define FMT_IMPORT_STD" >+$path($>) + echo '#define FMT_IMPORT_STD' >+$path($>) end }} } -lib{fmt}: src/hxx{config} src/mxx{fmt}: include = $cxx.features.modules - -# Workaround for MSVC Bug -# See: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 -# -if($cxx.features.modules && $cxx.class == 'msvc') - cc.reprocess = true - -# Build Options +# Build and Export Options # -cxx.poptions =+ "-I$src_base/include" "-I$out_base/src" "-I$src_base/src" +cxx.poptions =+ "-I$out_base/src" "-I$src_base/src" "-I$src_base/include" -{objs bmis}{*}: cxx.poptions += -DFMT_LIB_EXPORT - -# if $cxx.features.modules -# { -# cxx.poptions =+ "-I$src_base/src" -# if! $config.fmt.module_only -# cxx.poptions += -DFMT_ATTACH_TO_GLOBAL_MODULE -# if! $config.fmt.disable_import_std -# cxx.poptions += -DFMT_IMPORT_STD -# } - -# Export Options -# if! $config.fmt.module_only lib{fmt}: cxx.export.poptions = "-I$src_base/include" + +{objs bmis}{*}: cxx.poptions += -DFMT_LIB_EXPORT libs{fmt}: cxx.export.poptions += -DFMT_SHARED if ($cxx.class == 'msvc') { + # UTF-8 must be explicitly enabled for MSVC. + # cxx.poptions += /utf-8 lib{fmt}: cxx.export.poptions += /utf-8 + + # Workaround for MSVC Bug + # See: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 + # + if $cxx.features.modules + cc.reprocess = true } # Metadata From 25cebabc65749ed1512358901b7da246dfffa45c Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 23:42:16 +0100 Subject: [PATCH 16/32] Use `.gitignore` whitelisting During development, unit tests that are back-linked pose a constant threat to be committed to the repository's history. The whitelisting approach ensures this does not happen and is also simpler to maintain. --- .gitattributes | 2 + .gitignore | 78 ++++++++++++++++++++++++------------- fmt-tests/.gitignore | 25 ------------ fmt-tests/basics/.gitignore | 5 --- fmt-tests/build/.gitignore | 4 -- fmt/build/.gitignore | 3 -- fmt/tests/.gitignore | 8 ---- fmt/tests/build/.gitignore | 3 -- 8 files changed, 54 insertions(+), 74 deletions(-) delete mode 100644 fmt-tests/.gitignore delete mode 100644 fmt-tests/basics/.gitignore delete mode 100644 fmt-tests/build/.gitignore delete mode 100644 fmt/build/.gitignore delete mode 100644 fmt/tests/.gitignore delete mode 100644 fmt/tests/build/.gitignore diff --git a/.gitattributes b/.gitattributes index 1abc10a..6769b30 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,8 @@ # #*.png binary +# See: https://build2.org/article/symlinks.xhtml#windows +# fmt/fmt/include symlink=dir fmt/fmt/src symlink=dir fmt/doc symlink=dir diff --git a/.gitignore b/.gitignore index 6d7a9f8..0f90ab5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,52 @@ -.bdep/ -.vs/ - -# Local default options files. -# -.build2/local/ - -# Compiler/linker output. -# -*.d -*.t -*.i -*.ii -*.o -*.obj -*.so -*.dll -*.a -*.lib -*.exp -*.pdb -*.ilk -*.exe -*.exe.dlls/ -*.exe.manifest -*.pc +# Use whitelisting and ignore everything by default. +# +* + +# Don't ignore directories. +# +!*/ + +# Don't ignore Git-specific files. +# +!.git* + +# Allow all files from upstream sources. +# +!upstream + +# Don't ignore license and documentation files. +# +!*.md +!LICENSE + +# Don't ignore default C++ file extensions. +# +!*.h +!*.c +!*.cc +!*.inl + +# Don't ignore source generation files. +# +!*.in + +# Add original source files that might have been changed. +# +!*.orig + +# Don't ignore standard build2 files. +# +!buildfile +!bootstrap.build +!export.build +!root.build +!*manifest +!*testscript + +# Allow specific symbolic links to directories. +# +!fmt/fmt/include +!fmt/fmt/src +!fmt/doc +!fmt-tests/basics/test +!fmt-tests/test-main/test diff --git a/fmt-tests/.gitignore b/fmt-tests/.gitignore deleted file mode 100644 index 1c363a0..0000000 --- a/fmt-tests/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Compiler/linker output. -# -*.d -*.t -*.i -*.i.* -*.ii -*.ii.* -*.o -*.obj -*.gcm -*.pcm -*.ifc -*.so -*.dylib -*.dll -*.a -*.lib -*.exp -*.pdb -*.ilk -*.exe -*.exe.dlls/ -*.exe.manifest -*.pc diff --git a/fmt-tests/basics/.gitignore b/fmt-tests/basics/.gitignore deleted file mode 100644 index 8521b07..0000000 --- a/fmt-tests/basics/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -basics - -# Testscript output directory (can be symlink). -# -test-basics diff --git a/fmt-tests/build/.gitignore b/fmt-tests/build/.gitignore deleted file mode 100644 index 974e01d..0000000 --- a/fmt-tests/build/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/config.build -/root/ -/bootstrap/ -build/ diff --git a/fmt/build/.gitignore b/fmt/build/.gitignore deleted file mode 100644 index 4a730a3..0000000 --- a/fmt/build/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -config.build -root/ -bootstrap/ diff --git a/fmt/tests/.gitignore b/fmt/tests/.gitignore deleted file mode 100644 index 662178d..0000000 --- a/fmt/tests/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Test executables. -# -driver - -# Testscript output directories (can be symlinks). -# -test -test-* diff --git a/fmt/tests/build/.gitignore b/fmt/tests/build/.gitignore deleted file mode 100644 index 4a730a3..0000000 --- a/fmt/tests/build/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -config.build -root/ -bootstrap/ From dceb615cb974c0576e625f508f36a6e9930934fc Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sat, 28 Mar 2026 23:56:54 +0100 Subject: [PATCH 17/32] Add `module-std-headers` CI build config This build config uses the standard library via header includes and does not import the standard library module. --- fmt/manifest | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/fmt/manifest b/fmt/manifest index f64ab87..274bef6 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -21,7 +21,19 @@ tests: fmt-tests == $ depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 -module-builds: default experimental : &( +gcc-15+ +clang-19+ +msvc ) +module-std-headers-builds: default experimental : &( +gcc-11+ +clang-17+ +msvc ) +module-std-headers-builds: -bindist +module-std-headers-build-config: +\ +config.cxx.features.modules=true +config.fmt.disable_import_std=true +; +Enable support for both the {fmt} headers and its C++ module. +Use standard library header includes and do not import the standard module. +\ + +module-builds: default experimental : &( +gcc-15+ +clang-20+ +msvc ) +module-builds: -bindist module-build-config: \ config.cxx.features.modules=true @@ -29,7 +41,8 @@ config.cxx.features.modules=true Enable support for both the {fmt} headers and its C++ module. \ -module-only-builds: default experimental : &( +gcc-15+ +clang-19+ +msvc ) +module-only-builds: default experimental : &( +gcc-15+ +clang-20+ +msvc ) +module-only-builds: -bindist module-only-build-config: \ config.cxx.features.modules=true From a52d6a374dec0dc81ab285b37e51fc578f75015d Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 00:14:36 +0100 Subject: [PATCH 18/32] Install documentation to sub-directory --- fmt/buildfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fmt/buildfile b/fmt/buildfile index 2122444..732654d 100644 --- a/fmt/buildfile +++ b/fmt/buildfile @@ -3,3 +3,11 @@ # Don't install tests. # ./: tests/: install = false + +# Install documentation to sub-directory. +# +doc/doc{*}: +{ + install = doc/doc/ + install.subdirs = true +} From 54f4bb145610dbfbff2a6f4427fae2288fc3892f Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 01:16:59 +0100 Subject: [PATCH 19/32] Add `PACKAGE-README.md` --- fmt-tests/PACKAGE-README.md | 1 + fmt-tests/manifest | 1 + fmt/PACKAGE-README.md | 61 +++++++++++++++++++++++++++++++++++++ fmt/manifest | 1 + 4 files changed, 64 insertions(+) create mode 120000 fmt-tests/PACKAGE-README.md create mode 100644 fmt/PACKAGE-README.md diff --git a/fmt-tests/PACKAGE-README.md b/fmt-tests/PACKAGE-README.md new file mode 120000 index 0000000..ae1a0ee --- /dev/null +++ b/fmt-tests/PACKAGE-README.md @@ -0,0 +1 @@ +../fmt/PACKAGE-README.md \ No newline at end of file diff --git a/fmt-tests/manifest b/fmt-tests/manifest index be47ade..1f2d9df 100644 --- a/fmt-tests/manifest +++ b/fmt-tests/manifest @@ -13,6 +13,7 @@ url: https://fmt.dev/ src-url: https://github.com/fmtlib/fmt/ email: victor.zverovich@gmail.com +package-description-file: PACKAGE-README.md package-url: https://github.com/build2-packaging/fmt/ package-email: packaging@build2.org diff --git a/fmt/PACKAGE-README.md b/fmt/PACKAGE-README.md new file mode 100644 index 0000000..2a78c03 --- /dev/null +++ b/fmt/PACKAGE-README.md @@ -0,0 +1,61 @@ +# {fmt} - A Formatting C++ Library + +{fmt} is an open-source formatting library that provides a safe replacement for the `printf` family of functions. +Errors in format strings, which are a common source of vulnerabilities in C, are reported at compile time. +Formatting of most standard types, including all containers, dates, and times is supported out-of-the-box. +Also, {fmt} provides portable Unicode support on major operating systems with UTF-8 and char strings. + +# Usage +To use `fmt` in your project, add the following configurations to the respective files after you have gained access to a `build2` package repository that contains it. + +### `manifest` +To make `fmt` available for import, add the following dependency to the `manifest` of each package in your project that requires it, adjusting the version constraint as appropriate. + + depends: fmt ^12.1.0 + +### `buildfile` +To import the contained library, use the following declaration in your `buildfile`. + + import fmt = fmt%lib{fmt} + +Note also that `lib{fmt}` provides `build2` metadata that can be extracted with an immediate importation to describe its configuration. + + fmt.has_header = [bool] (!$config.fmt.module_only) + fmt.has_module = [bool] $cxx.features.modules + +### C++ Usage +If `$fmt.has_header == true`, {fmt}'s headers can be included as follows: + +```c++ +#include // Base API +#include // Formatting Functions and Locale Support +#include // Formatting of Ranges and Tuples +#include // Date and Time Formatting +#include // Formatting for Standard Library Types +#include // Format String Compilation +#include // Terminal Colors and Text Styles +#include // System APIs +#include // `std::ostream` Support +#include // Dynamic Argument Lists +#include // Safe `printf` +#include // Optional `wchar_t` Support +``` + +If `$fmt.has_module == true`, {fmt}'s C++ module can imported via: + +```c++ +import fmt; +``` + +## Configuration + +### C++20 Modules Support +{fmt}'s C++ module can be enabled with `config.cxx.features.modules=true`. +In this case, the library by default can be consumed via module importation through `import fmt;` and header inclusion through, eg., `#include `. +For this to work, all entities are attached to the global module via global module linkage through `extern "C++"`. + +To make entities use module linkage, we must disable header access by using `config.fmt.module_only=true`. +In this case, all entities are fully encapsulated in {fmt}'s C++ module. + +{fmt}'s C++ module uses importation of the C++ standard library module via `import std;` by default. +For backwards compatibility, this can be disabled by `config.fmt.disable_import_std=true`. diff --git a/fmt/manifest b/fmt/manifest index 274bef6..d0aaa79 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -13,6 +13,7 @@ url: https://fmt.dev/ src-url: https://github.com/fmtlib/fmt/ email: victor.zverovich@gmail.com +package-description-file: PACKAGE-README.md package-url: https://github.com/build2-packaging/fmt/ package-email: packaging@build2.org From 376e9945e6f90934b98550ccf88a52e6fe86dd99 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 01:43:25 +0100 Subject: [PATCH 20/32] Make tests library a utility library `libue{}` --- .gitattributes | 1 - .gitignore | 1 - fmt-tests/basics/buildfile | 20 +++++++++++--------- fmt-tests/test-main/buildfile | 14 -------------- fmt-tests/test-main/test | 1 - 5 files changed, 11 insertions(+), 26 deletions(-) delete mode 100644 fmt-tests/test-main/buildfile delete mode 120000 fmt-tests/test-main/test diff --git a/.gitattributes b/.gitattributes index 6769b30..f1abe89 100644 --- a/.gitattributes +++ b/.gitattributes @@ -24,4 +24,3 @@ fmt/fmt/include symlink=dir fmt/fmt/src symlink=dir fmt/doc symlink=dir fmt-tests/basics/test symlink=dir -fmt-tests/test-main/test symlink=dir diff --git a/.gitignore b/.gitignore index 0f90ab5..d7be5d3 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,3 @@ !fmt/fmt/src !fmt/doc !fmt-tests/basics/test -!fmt-tests/test-main/test diff --git a/fmt-tests/basics/buildfile b/fmt-tests/basics/buildfile index 512c578..2950ad3 100644 --- a/fmt-tests/basics/buildfile +++ b/fmt-tests/basics/buildfile @@ -1,4 +1,12 @@ -include ../test-main/ +import libs = \ + fmt%lib{fmt} \ + gtest%lib{gtest} \ + gmock%lib{gmock} + +libue{test-main}: \ + test/cxx{test-main gtest-extra util} \ + test/hxx{gtest-extra mock-allocator test-assert util} \ + $libs # NOTE: Maintaining explicit lists to match upstream, as it's not clear that there is intended to be a uniform pattern that can be reliably globbed. # See individual invocations of add_fmt_test() in upstream/test/CMakeLists.txt @@ -30,10 +38,7 @@ gtest_test_names = \ for test_name : $gtest_test_names { ./: exe{$test_name} : test/cxx{$test_name} - exe{$test_name}: ../test-main/liba{test-main}: - { - bin.whole = true - } + exe{$test_name}: libue{test-main}: bin.whole = false } # END [Tests that use gtest, and do not require any additional source/headers beyond a single cc file] @@ -42,10 +47,7 @@ for test_name : $gtest_test_names # Tests using gtest but with additional prerequisites ./: exe{ranges-test} : test/cxx{ranges-test ranges-odr-test} -exe{ranges-test}: ../test-main/liba{test-main}: -{ - bin.whole = true -} +exe{ranges-test}: libue{test-main}: bin.whole = false # NOTE: format-impl-test- For whatever reason, format-impl-test is tied in upstream to header-only-test, which we do not support. Attempting to compile it alone yields errors, therefore omitted. # TODO: posix-mock-test - Excluded pending further work. Needs posix-mock.h, and has some msvc/runtime conditional logic that needs looking into. diff --git a/fmt-tests/test-main/buildfile b/fmt-tests/test-main/buildfile deleted file mode 100644 index 30b42fa..0000000 --- a/fmt-tests/test-main/buildfile +++ /dev/null @@ -1,14 +0,0 @@ - -libs = -import libs += fmt%lib{fmt} -import libs += gtest%lib{gtest} gmock%lib{gmock} - -liba{test-main}: test/cxx{test-main gtest-extra util} test/hxx{gtest-extra mock-allocator test-assert util} $libs - -# Export options. -# -liba{test-main}: -{ - cxx.export.poptions += "-I$src_base/test" - cxx.export.libs = $libs -} diff --git a/fmt-tests/test-main/test b/fmt-tests/test-main/test deleted file mode 120000 index 18e4790..0000000 --- a/fmt-tests/test-main/test +++ /dev/null @@ -1 +0,0 @@ -../../upstream/test \ No newline at end of file From dfdbf1cfa5597a699d14b0d5e06e7c5f67ff1301 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 01:47:58 +0100 Subject: [PATCH 21/32] Describe MSVC bug --- fmt/fmt/buildfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 30f11d8..95bd7c6 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -67,6 +67,8 @@ if ($cxx.class == 'msvc') lib{fmt}: cxx.export.poptions += /utf-8 # Workaround for MSVC Bug + # Otherwise, MSVC emits error C7657 (private module fragment cannot + # be declared before a module declaration) when processing the module. # See: https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183 # if $cxx.features.modules From 73f45ac1f83d1f2af7a10027130229dbf10913a2 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 03:15:17 +0200 Subject: [PATCH 22/32] Sanitize execution of unit tests --- fmt-tests/basics/buildfile | 11 ++++------- fmt-tests/basics/testscript | 4 ++++ 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 fmt-tests/basics/testscript diff --git a/fmt-tests/basics/buildfile b/fmt-tests/basics/buildfile index 2950ad3..4cd87e6 100644 --- a/fmt-tests/basics/buildfile +++ b/fmt-tests/basics/buildfile @@ -36,18 +36,15 @@ gtest_test_names = \ # TODO: scan-test - Excluded due to unresolved linker errors. for test_name : $gtest_test_names -{ - ./: exe{$test_name} : test/cxx{$test_name} - exe{$test_name}: libue{test-main}: bin.whole = false -} + ./: exe{$test_name} : test/cxx{$test_name} libue{test-main} testscript # END [Tests that use gtest, and do not require any additional source/headers beyond a single cc file] # Tests using gtest but with additional prerequisites -./: exe{ranges-test} : test/cxx{ranges-test ranges-odr-test} -exe{ranges-test}: libue{test-main}: bin.whole = false +./: exe{ranges-test} : \ + test/cxx{ranges-test ranges-odr-test} libue{test-main} testscript # NOTE: format-impl-test- For whatever reason, format-impl-test is tied in upstream to header-only-test, which we do not support. Attempting to compile it alone yields errors, therefore omitted. # TODO: posix-mock-test - Excluded pending further work. Needs posix-mock.h, and has some msvc/runtime conditional logic that needs looking into. @@ -65,7 +62,7 @@ import fmt = fmt%lib{fmt} for test_name : $standalone_test_names { - ./: exe{$test_name} : test/cxx{$test_name} $fmt + ./: exe{$test_name} : test/cxx{$test_name} $fmt testscript } # END [Tests which do not use gtest and therefore should link directly to fmt only and not the test-main lib] diff --git a/fmt-tests/basics/testscript b/fmt-tests/basics/testscript new file mode 100644 index 0000000..afdedba --- /dev/null +++ b/fmt-tests/basics/testscript @@ -0,0 +1,4 @@ +# Redirect stdout to stderr (1>&2) and let stderr through (2>|). +# Cleanup anything the test might create, recursively (&***). +# +$* 1>&2 2>| &*** From 437a07a43fb495982a070245e305d04a09da3a2f Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 03:49:48 +0200 Subject: [PATCH 23/32] Write `README.md` following packaging guidelines --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f921b95..d1bf541 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,56 @@ -`{fmt}` library - Build2 package -================================ +# build2 Package Repository for {fmt} -See [`{fmt}` documentation](https://fmt.dev/) for usage and details. +This is a [build2](https://build2.org) package repository for [{fmt}](https://fmt.dev/), a modern C++ formatting library that provides a safe replacement for the `printf` family of functions. - - `{fmt}` : https://github.com/fmtlib/fmt/ - - Build2 : https://build2.org +| Package | Summary | Status | +|---|---|---| +| **[`fmt`](fmt/PACKAGE-README.md)** | C++ Formatting Library | [![cppget.org](https://img.shields.io/website/https/cppget.org/fmt.svg?down_message=offline&label=cppget.org&style=for-the-badge&up_color=blue&up_message=online)](https://cppget.org/fmt) [![queue.cppget.org](https://img.shields.io/website/https/queue.cppget.org/fmt.svg?down_message=empty&down_color=blue&label=queue.cppget.org&style=for-the-badge&up_color=orange&up_message=running)](https://queue.cppget.org/fmt) | -Note: This is the source code for the build2 package of the `{fmt}` C++ library, -the actual library sources snapshot can be found in the `./upstream/` submodule. +## Usage +If you want to use the `fmt` package in your `build2`-based project, add an appropriate repository manifest to your project's `repositories.manifest` and refer to [`fmt`'s PACKAGE README](fmt/PACKAGE-README.md). -## Configuration Options +### `repositories.manifest` +To be able to fetch the package, add one of the following prerequisites to your project's `repositories.manifest`. -### Experimental C++20 modules support +**Option A: `cppget.org` (Recommended)** -Modules support is WIP, both in the `build2` package and also in `fmt` upstream. Latest versions of MSVC or Clang are recommended, and the most up-to-date version of the package with regards to modules compatibility can be used via git and the [modules branch](https://github.com/build2-packaging/fmt/tree/modules). For example, in project `repositories.manifest`: -``` -: -role: prerequisite -location: https://github.com/build2-packaging/fmt.git#modules -``` +Based on your project's stability requirements, choose either the [`stable` section](https://cppget.org/?about#pkg%3Acppget.org%2Fstable) for thoroughly tested versions or the [`testing` section](https://cppget.org/?about#pkg%3Acppget.org%2Ftesting) for the latest releases before they are marked as stable. +For example: -Enable with `config.cxx.features.modules=true`. When enabled, by default dual mode is used meaning that the library can be consumed either through `import` or via `#include`. To enable this safely, all entities are attached to the global module (extern "C++"). See `modules_only` option for the alternative. - - `config.fmt.enable_import_std` : Set to `true` to consume the standard library as a module when building the `fmt` module. Support dependent on compiler and std lib. Defaults to `false`. - - `config.fmt.modules_only` : Set to `true` to enable modules-only mode for the package. In this mode, `fmt` entities are fully encapsulated in the `fmt` module meaning `#include`-based consumption cannot be mixed, and the package will not export headers. Defaults to `false`. + : + role: prerequisite + location: https://pkg.cppget.org/1/stable + # trust: ... +**Option B: Git Repository** + + : + role: prerequisite + location: https://github.com/build2-packaging/fmt.git + +## Development Setup +The development setup for this repository uses the standard `bdep`-based workflow. +For general information and guidance on package maintenance, please see the [`build2` Documentation](https://build2.org/doc.xhtml). + +First, clone the repository via SSH or HTTPS. + + git clone --recurse https://github.com/build2-packaging/fmt.git # HTTPS + git clone --recurse git@github.com:build2-packaging/fmt.git # SSH + +Inside the repository's directory, initialize your build configuration. + + bdep init -C @gcc cc config.cxx=g++ config.cxx.features.modules=true config.install.root=../.install config.dist.root=../.dist + +Afterwards, use `b` or `bdep` to build, test, install, and distribute the packages. + +## Issues and Notes +- Modules support is WIP, both in the `build2` package and also in `fmt` upstream. Latest versions of MSVC or Clang are recommended. +- Upstream's unit tests only support C++20 and fail for the latest C++ standard. As such, `cxx.std = 20` for the `fmt-tests` package. +- A [bug in MSVC](https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183) prevents successful processing of {fmt}'s C++ module and emits error C7657 (private module fragment cannot be declared before a module declaration). The workaround `cc.reprocess=true` can only be applied while consuming the `build2` package. Thus, unit tests running on Windows fail for the installed case. + +## Contributing +Contributions are welcome and greatly appreciated! +Please start by [opening an issue](https://github.com/build2-packaging/fmt/issues) to report a bug, suggest an improvement, or request a version update. +This helps us coordinate efforts and avoid duplicate work. +You are then welcome to submit a [pull request](https://github.com/build2-packaging/fmt/pulls) that references the issue. +For guidance on package maintenance, please see the [`build2` Packaging Guidelines](https://build2.org/build2-toolchain/doc/build2-toolchain-packaging.xhtml). From 4119d71078af4c0702e6eea8c68d07a23ceed0f8 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Sun, 29 Mar 2026 19:27:06 +0200 Subject: [PATCH 24/32] Add `buildfile` comments to explain build --- fmt/build/root.build | 7 ++++++- fmt/fmt/buildfile | 31 +++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/fmt/build/root.build b/fmt/build/root.build index 3b72570..aa6ef18 100644 --- a/fmt/build/root.build +++ b/fmt/build/root.build @@ -1,8 +1,13 @@ # Configuration Variables -# +# Disable access to `lib{fmt}`'s headers to encapsulate +# all entities in {fmt}'s C++ module with module linkage. # config [bool] config.fmt.module_only ?= false + +# Disable the use of `import std;` in the +# {fmt}'s C++ module for backwards compatibility. +# config [bool] config.fmt.disable_import_std ?= false # C++ Configuration diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index 95bd7c6..c34d131 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -1,9 +1,28 @@ -# Headers +# In a modular build, {fmt} incorporates all headers and sources into its +# module interface unit. We dynamically retype their target types as +# implementation fragments (`ixx{}`) to reflect their role as internal +# module details. While other strategies exist to achieve this, changing +# the target types provides the clearest communication of intent. + +# Headers: +# In module-only mode, headers are no longer public interface points; +# they become internal module details. Retyping to `ixx` enforces this +# encapsulation and signals that direct header access is discouraged. +# +# Note: +# In non-modular, compiled builds, `fmt/format-inl.h` acts as a private +# implementation header for `src/format.cc`. While its installation is +# technically unnecessary in this specific configuration, we do not +# explicitly exclude it to avoid complicating the installation logic. # hdr_type = ($config.fmt.module_only ? ixx : hxx) lib{fmt}: include/$hdr_type{**.h} -# Sources +# Sources: +# With {fmt}'s module, sources are no longer compiled as translation units. +# However, they must remain visible to the consumer's compiler to be read and +# processed during module importation. Using `ixx`, ensures these files are +# properly installed as source text rather than compiled into object files. # src_type = ($cxx.features.modules ? ixx : cxx) lib{fmt}: src/$src_type{** -fmt} @@ -95,6 +114,14 @@ else # Installation # +# To support post-installation usage for the C++ modules, we isolate +# the library's components within a dedicated 'fmtlib/' subdirectory. +# Headers are installed into 'fmtlib/fmt/' to satisfy {fmt}'s internal +# inclusion scheme, e.g., #include "fmt/core.h". The module interface unit, +# its configuration headers, and the implementation sources reside at the +# 'fmtlib/' root. To ensure the compiler resolves header paths correctly, +# we point the pkg-config include path to 'include/fmtlib/'. +# if! $config.fmt.module_only lib{fmt}: cxx.pkgconfig.include = include/fmtlib/ From c8c502d4fcd96dc5a3e316ab4aa9fddc66232d57 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 30 Mar 2026 02:23:00 +0200 Subject: [PATCH 25/32] Refactor smoke tests --- fmt/tests/basics/driver-modules.cc | 39 +++++++++++++------ fmt/tests/basics/driver.cc | 4 +- fmt/tests/basics/tests.inl | 62 +++++++++++++----------------- 3 files changed, 58 insertions(+), 47 deletions(-) diff --git a/fmt/tests/basics/driver-modules.cc b/fmt/tests/basics/driver-modules.cc index 507633a..e478a6b 100644 --- a/fmt/tests/basics/driver-modules.cc +++ b/fmt/tests/basics/driver-modules.cc @@ -1,19 +1,36 @@ - // Verify that fmt headers are not available if config.fmt.modules_only is true // @NOTE: Disabled since this is rather hard to make robust. -// We need to install headers even in modules only mode in order to allow for BMI compilation. -// Even though we suppress exporting of the -I paths, in the case of installation to a common root prefix it will likely be -// the case that the headers will be accessible via the general include paths anyway. Only way we could reasonably prevent this -// would be to install to a different subfolder; avoiding adding such complexity for now since modules installation is far -// from finalized anyway. -//#if __has_include() != FMT_BUILD2_HAS_HEADER -//#error fmt headers should be available for include iff config.fmt.modules_only == false -//#endif +// We need to install headers even in modules only mode in order to allow for +// BMI compilation. Even though we suppress exporting of the -I paths, in the +// case of installation to a common root prefix it will likely be the case that +// the headers will be accessible via the general include paths anyway. Only way +// we could reasonably prevent this would be to install to a different +// subfolder; avoiding adding such complexity for now since modules installation +// is far from finalized anyway. +// #if __has_include() != FMT_BUILD2_HAS_HEADER +// #error fmt headers should be available for include iff +// config.fmt.modules_only == false #endif -#include -#include +#if __has_include() +#include +#endif + +#if defined(__cpp_lib_modules) && (__cpp_lib_modules >= 202207L) +#define HAS_IMPORT_STD +#endif + +#ifndef IMPORT_STD_SUPPORT #include +#include #include +#endif + +#undef NDEBUG +#include + +#ifdef IMPORT_STD_SUPPORT +import std; +#endif import fmt; diff --git a/fmt/tests/basics/driver.cc b/fmt/tests/basics/driver.cc index 066e3b3..d4e7405 100644 --- a/fmt/tests/basics/driver.cc +++ b/fmt/tests/basics/driver.cc @@ -1,4 +1,3 @@ -#include #include #include #include @@ -10,4 +9,7 @@ #include #include +#undef NDEBUG +#include + #include "tests.inl" diff --git a/fmt/tests/basics/tests.inl b/fmt/tests/basics/tests.inl index 3022f4e..1950d5e 100644 --- a/fmt/tests/basics/tests.inl +++ b/fmt/tests/basics/tests.inl @@ -1,8 +1,23 @@ +struct date { + int year, month, day; +}; + +template <> struct fmt::formatter { + template + constexpr auto parse(ParseContext &ctx) const { + return ctx.begin(); + } + + template + constexpr auto format(const date &d, FormatContext &ctx) const { + // Namespace-qualify to avoid ambiguity with std::format_to. + return fmt::format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day); + } +}; int main() { // THESE EXAMPLES ARE EXTRACTED FROM THE README AND SHOULD BE UPDATED EACH // BREAKING VERSION - { fmt::print("Hello, world!\n"); @@ -13,53 +28,30 @@ int main() { fmt::format("I'd rather be {1} than {0}.", "right", "happy"); assert(s2 == "I'd rather be happy than right."); } - { std::vector v = {1, 2, 3}; fmt::print("{}\n", v); } - { - fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "Hello, {}!\n", - "world"); + fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, // + "Hello, {}!\n", "world"); fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | fmt::emphasis::underline, "Olá, {}!\n", "Mundo"); - fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, "你好{}!\n", - "世界"); + fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, // + "你好{}!\n", "世界"); + } + { + auto now = std::chrono::system_clock::now(); + fmt::print("Date and time: {}\n", now); + fmt::print("Time: {:%H:%M}\n", now); } - - // { - // auto now = std::chrono::system_clock::now(); - // fmt::print("Date and time: {}\n", now); - // fmt::print("Time: {:%H:%M}\n", now); - // } - { fmt::print("Hello, {}!", "world"); // Python-like format string syntax fmt::printf("Hello, %s!", "world"); // printf format string syntax } - { - std::string s = - fmt::format("I'd rather be {1} than {0}.", "right", "happy"); + std::string s = fmt::format("The date is {}", date{2012, 12, 9}); + assert(s == "The date is 2012-12-9"); } } - -// struct date { -// int year, month, day; -// }; - -// template <> -// struct fmt::formatter { -// template -// constexpr auto parse(ParseContext& ctx) const { return ctx.begin(); } - -// template -// constexpr auto format(const date& d, FormatContext& ctx) const { -// // Namespace-qualify to avoid ambiguity with std::format_to. -// return fmt::format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day); -// } -// }; - -// std::string s = fmt::format("The date is {}", date{2012, 12, 9}); From 9353cd43841333e0d941771b5149ef8f2c2d50c9 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 30 Mar 2026 02:32:32 +0200 Subject: [PATCH 26/32] Remove `config.fmt.disable_import_std` --- fmt/build/root.build | 5 ----- fmt/fmt/buildfile | 3 --- fmt/fmt/config.h.in | 9 +++++++++ fmt/manifest | 15 ++------------- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/fmt/build/root.build b/fmt/build/root.build index aa6ef18..6dee61b 100644 --- a/fmt/build/root.build +++ b/fmt/build/root.build @@ -5,11 +5,6 @@ # config [bool] config.fmt.module_only ?= false -# Disable the use of `import std;` in the -# {fmt}'s C++ module for backwards compatibility. -# -config [bool] config.fmt.disable_import_std ?= false - # C++ Configuration # cxx.std = latest diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index c34d131..c44391f 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -62,9 +62,6 @@ src/ if! $config.fmt.module_only echo '#define FMT_ATTACH_TO_GLOBAL_MODULE' >+$path($>) end - if! $config.fmt.disable_import_std - echo '#define FMT_IMPORT_STD' >+$path($>) - end }} } diff --git a/fmt/fmt/config.h.in b/fmt/fmt/config.h.in index 72fabf8..4e6eff1 100644 --- a/fmt/fmt/config.h.in +++ b/fmt/fmt/config.h.in @@ -1,3 +1,12 @@ +#if __has_include() +# include +#endif + +// Use `import std;` if the compiler supports it. +#if defined(__cpp_lib_modules) && (__cpp_lib_modules >= 202207L) +# define FMT_IMPORT_STD +#endif + #ifdef FMT_SHARED # define FMT_LIB_EXPORT #endif diff --git a/fmt/manifest b/fmt/manifest index d0aaa79..c2a42f2 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -22,18 +22,7 @@ tests: fmt-tests == $ depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 -module-std-headers-builds: default experimental : &( +gcc-11+ +clang-17+ +msvc ) -module-std-headers-builds: -bindist -module-std-headers-build-config: -\ -config.cxx.features.modules=true -config.fmt.disable_import_std=true -; -Enable support for both the {fmt} headers and its C++ module. -Use standard library header includes and do not import the standard module. -\ - -module-builds: default experimental : &( +gcc-15+ +clang-20+ +msvc ) +module-builds: default experimental : &( +gcc-11+ +clang-17+ +msvc ) module-builds: -bindist module-build-config: \ @@ -42,7 +31,7 @@ config.cxx.features.modules=true Enable support for both the {fmt} headers and its C++ module. \ -module-only-builds: default experimental : &( +gcc-15+ +clang-20+ +msvc ) +module-only-builds: default experimental : &( +gcc-11+ +clang-17+ +msvc ) module-only-builds: -bindist module-only-build-config: \ From 55b7b353cb87fcefa67142844f3ee9713d426b26 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 30 Mar 2026 03:07:40 +0200 Subject: [PATCH 27/32] Restrict GCC versions in CI builds Prior to GCC 16, module support and linkage is basically broken. --- fmt/manifest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fmt/manifest b/fmt/manifest index c2a42f2..e708ba2 100644 --- a/fmt/manifest +++ b/fmt/manifest @@ -22,7 +22,7 @@ tests: fmt-tests == $ depends: * build2 >= 0.17.0 depends: * bpkg >= 0.17.0 -module-builds: default experimental : &( +gcc-11+ +clang-17+ +msvc ) +module-builds: default experimental : &( +gcc-16+ +clang-17+ +msvc ) module-builds: -bindist module-build-config: \ @@ -31,7 +31,7 @@ config.cxx.features.modules=true Enable support for both the {fmt} headers and its C++ module. \ -module-only-builds: default experimental : &( +gcc-11+ +clang-17+ +msvc ) +module-only-builds: default experimental : &( +gcc-16+ +clang-17+ +msvc ) module-only-builds: -bindist module-only-build-config: \ From 7eb74b70cc548e9c5287eb1c130acf6fdbb62cdc Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 30 Mar 2026 03:13:13 +0200 Subject: [PATCH 28/32] Fix FreeBSD `import std;` bug --- fmt/fmt/config.h.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fmt/fmt/config.h.in b/fmt/fmt/config.h.in index 4e6eff1..1a73bbf 100644 --- a/fmt/fmt/config.h.in +++ b/fmt/fmt/config.h.in @@ -4,7 +4,9 @@ // Use `import std;` if the compiler supports it. #if defined(__cpp_lib_modules) && (__cpp_lib_modules >= 202207L) -# define FMT_IMPORT_STD +# if !defined(__FreeBSD__) +# define FMT_IMPORT_STD +# endif #endif #ifdef FMT_SHARED From fffe7d10122f3eef839f1b54e92e6e26b6e4ad08 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 30 Mar 2026 15:24:40 +0200 Subject: [PATCH 29/32] Update issues and notes in `README.md` --- README.md | 10 +++++++--- fmt/PACKAGE-README.md | 19 ++++++++++++------- fmt/fmt/buildfile | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d1bf541..e4d1a58 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,13 @@ Inside the repository's directory, initialize your build configuration. Afterwards, use `b` or `bdep` to build, test, install, and distribute the packages. ## Issues and Notes -- Modules support is WIP, both in the `build2` package and also in `fmt` upstream. Latest versions of MSVC or Clang are recommended. -- Upstream's unit tests only support C++20 and fail for the latest C++ standard. As such, `cxx.std = 20` for the `fmt-tests` package. -- A [bug in MSVC](https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183) prevents successful processing of {fmt}'s C++ module and emits error C7657 (private module fragment cannot be declared before a module declaration). The workaround `cc.reprocess=true` can only be applied while consuming the `build2` package. Thus, unit tests running on Windows fail for the installed case. +- To generate a Binary Module Interface (BMI) from {fmt}'s C++ module after installation, the consumer must compile the module interface unit using the same macro configuration as the original library build. These macros are internal to the module and are therefore not exported via `cxx.export.poptions`. Instead, a generated `config.h` header encapsulates the required configuration through preprocessor macros. The module interface unit is patched to include this header immediately after `module;` in the global module fragment, ensuring the correct macros are available during BMI generation. +- In non-modular compiled builds, `fmt/format-inl.h` acts as a private implementation header for `src/format.cc`. While its installation in this specific configuration is technically unnecessary, we do not explicitly exclude it to reduce complexity. +- C++ module support in GCC versions prior to 16 is incomplete and unreliable for this library. As a result, such configurations are considered unsupported and are excluded from CI. +- On FreeBSD 15 with Clang 19, there is a discrepancy between compiler capabilities and system libraries. Although `__cpp_lib_modules` is defined (indicating that libc++ has module support), the base system does not ship the required module source or metadata files. Consequently, `import std;` is not functional in this environment which is reflected in the generated `config.h`. +- Upstream unit tests currently only support C++20 and fail when compiled with newer language standards. Therefore, the `fmt-tests` package is explicitly restricted to `cxx.std = 20`. +- Currently, unit tests are only run when the library headers are available and not when `config.fmt.module_only=true`. The unit tests use standard header includes rather than module imports. +- A known [issue in MSVC](https://developercommunity.visualstudio.com/t/Separate-preprocessing-with-P-fails-wit/10707183) prevents correct processing of {fmt}'s C++ module, producing error C7657 (“private module fragment cannot be declared before a module declaration”). The workaround (`cc.reprocess=true`) is only applicable when consuming the package directly via `build2`. As a result, unit tests fail on Windows when using the installed package. ## Contributing Contributions are welcome and greatly appreciated! diff --git a/fmt/PACKAGE-README.md b/fmt/PACKAGE-README.md index 2a78c03..9e1110d 100644 --- a/fmt/PACKAGE-README.md +++ b/fmt/PACKAGE-README.md @@ -50,12 +50,17 @@ import fmt; ## Configuration ### C++20 Modules Support -{fmt}'s C++ module can be enabled with `config.cxx.features.modules=true`. -In this case, the library by default can be consumed via module importation through `import fmt;` and header inclusion through, eg., `#include `. -For this to work, all entities are attached to the global module via global module linkage through `extern "C++"`. +{fmt}'s C++ module support can be enabled by setting: -To make entities use module linkage, we must disable header access by using `config.fmt.module_only=true`. -In this case, all entities are fully encapsulated in {fmt}'s C++ module. + config.cxx.features.modules = true -{fmt}'s C++ module uses importation of the C++ standard library module via `import std;` by default. -For backwards compatibility, this can be disabled by `config.fmt.disable_import_std=true`. +By default, the library supports both module import through `import fmt;` and traditional header inclusion through, eg., `#include `. +To enable this dual usage, all entities are attached to the global module via global module linkage (`extern "C++"`). +This ensures compatibility between module and header-based consumption. + +To enforce strict module usage, use: + + config.fmt.module_only = true + +In this mode, header access is disabled and all entities are exclusively provided through the C++ module. +This results in full encapsulation within the module and avoids global module linkage. diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index c44391f..c766041 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -1,4 +1,4 @@ -# In a modular build, {fmt} incorporates all headers and sources into its +# Using C++ modules, {fmt} incorporates all headers and sources into its # module interface unit. We dynamically retype their target types as # implementation fragments (`ixx{}`) to reflect their role as internal # module details. While other strategies exist to achieve this, changing From a8c2893c7a28bce33a00c73f05b3f61a3bcfbd0f Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 30 Mar 2026 19:11:06 +0200 Subject: [PATCH 30/32] Remove explicit MSVC `/utf-8` flag See: https://github.com/build2/build2/blob/51dffd1be8ca37bccadb93c2c84a2df7fc343d2d/NEWS#L1198 --- fmt/fmt/buildfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fmt/fmt/buildfile b/fmt/fmt/buildfile index c766041..7e8b05d 100644 --- a/fmt/fmt/buildfile +++ b/fmt/fmt/buildfile @@ -77,11 +77,6 @@ libs{fmt}: cxx.export.poptions += -DFMT_SHARED if ($cxx.class == 'msvc') { - # UTF-8 must be explicitly enabled for MSVC. - # - cxx.poptions += /utf-8 - lib{fmt}: cxx.export.poptions += /utf-8 - # Workaround for MSVC Bug # Otherwise, MSVC emits error C7657 (private module fragment cannot # be declared before a module declaration) when processing the module. From 9c24b4c93f504d064c2bc32ede0a1f17ba5406e9 Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 6 Apr 2026 19:28:30 +0200 Subject: [PATCH 31/32] Remove directory glob in root `buildfile` --- buildfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildfile b/buildfile index 57b386d..784ed49 100644 --- a/buildfile +++ b/buildfile @@ -1,5 +1,5 @@ -import pkgs = {*/ -*-tests/ -upstream/} -import tests = {*-tests/} +import pkgs = fmt/ +import tests = fmt-tests/ ./: $pkgs ./: $tests: install = false From 241ca4eeb8ec6db21328dc35abe24e7c123f330b Mon Sep 17 00:00:00 2001 From: lyrahgames Date: Mon, 6 Apr 2026 19:32:47 +0200 Subject: [PATCH 32/32] Tweak `PACKAGE-README.md` --- fmt/PACKAGE-README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/fmt/PACKAGE-README.md b/fmt/PACKAGE-README.md index 9e1110d..9c5d6b3 100644 --- a/fmt/PACKAGE-README.md +++ b/fmt/PACKAGE-README.md @@ -27,18 +27,7 @@ Note also that `lib{fmt}` provides `build2` metadata that can be extracted with If `$fmt.has_header == true`, {fmt}'s headers can be included as follows: ```c++ -#include // Base API -#include // Formatting Functions and Locale Support -#include // Formatting of Ranges and Tuples -#include // Date and Time Formatting -#include // Formatting for Standard Library Types -#include // Format String Compilation -#include // Terminal Colors and Text Styles -#include // System APIs -#include // `std::ostream` Support -#include // Dynamic Argument Lists -#include // Safe `printf` -#include // Optional `wchar_t` Support +#include ``` If `$fmt.has_module == true`, {fmt}'s C++ module can imported via: