From ea85b81ccdb28dda636351e80a1888b1d279f325 Mon Sep 17 00:00:00 2001 From: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:10:17 +0800 Subject: [PATCH 001/111] Support fmt::runtime with wchar_t in fmt::format_to_n (#4715) --- include/fmt/xchar.h | 12 ++++++++++-- test/xchar-test.cc | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 31706f96..6d95a737 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -279,10 +279,18 @@ inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view fmt, return {buf.out(), buf.count()}; } +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, wformat_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt.get(), fmt::make_wformat_args(args...)); +} + template , - FMT_ENABLE_IF(detail::is_output_iterator::value&& - detail::is_exotic_char::value)> + FMT_ENABLE_IF(detail::is_output_iterator::value && + !std::is_same::value && + !std::is_same::value)> inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) -> format_to_n_result { return vformat_to_n(out, n, fmt::basic_string_view(fmt), diff --git a/test/xchar-test.cc b/test/xchar-test.cc index cfe8389d..f6620ddf 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -146,6 +146,26 @@ TEST(format_test, wide_format_to_n) { EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4)); } +TEST(format_test, wide_format_to_n_runtime) { + wchar_t buffer[4]; + buffer[3] = L'x'; + auto result = fmt::format_to_n(buffer, 3, fmt::runtime(L"{}"), 12345); + EXPECT_EQ(5u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ(L"123x", fmt::wstring_view(buffer, 4)); + buffer[0] = L'x'; + buffer[1] = L'x'; + buffer[2] = L'x'; + result = fmt::format_to_n(buffer, 3, fmt::runtime(L"{}"), L'A'); + EXPECT_EQ(1u, result.size); + EXPECT_EQ(buffer + 1, result.out); + EXPECT_EQ(L"Axxx", fmt::wstring_view(buffer, 4)); + result = fmt::format_to_n(buffer, 3, fmt::runtime(L"{}{} "), L'B', L'C'); + EXPECT_EQ(3u, result.size); + EXPECT_EQ(buffer + 3, result.out); + EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4)); +} + TEST(xchar_test, named_arg_udl) { using namespace fmt::literals; auto udl_a = From dc05bee30755bb993add7ed90845d87c2315f9c6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 23 Mar 2026 09:22:38 -0700 Subject: [PATCH 002/111] Don't assume nul termination in printf Thanks ZUENS2020 for reporting. --- .github/workflows/linux.yml | 4 +++- include/fmt/printf.h | 1 + test/format-test.cc | 16 ---------------- test/printf-test.cc | 11 +++++++++-- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 65a07306..014e027b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -48,6 +48,8 @@ jobs: - cxx: clang++-14 build_type: Debug std: 20 + cxxflags: -fsanitize=address + cxxflags_extra: -fno-sanitize-recover=all -fno-omit-frame-pointer - cxx: clang++-14 build_type: Debug std: 20 @@ -154,7 +156,7 @@ jobs: working-directory: ${{runner.workspace}}/build env: CXX: ${{matrix.cxx}} - CXXFLAGS: ${{matrix.cxxflags}} + CXXFLAGS: ${{matrix.cxxflags}} ${{matrix.cxxflags_extra}} run: | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 7f6f6f3a..c2e92291 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -330,6 +330,7 @@ template auto parse_header(const Char*& it, const Char* end, format_specs& specs, GetArg get_arg) -> int { int arg_index = -1; + if (it == end) return arg_index; Char c = *it; if (c >= '0' && c <= '9') { // Parse an argument index (if followed by '$') or a width possibly diff --git a/test/format-test.cc b/test/format-test.cc index c649aa4d..f18f3172 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -268,22 +268,6 @@ TEST(util_test, format_system_error) { fmt::format_system_error(message, EDOM, "test"); auto ec = std::error_code(EDOM, std::generic_category()); EXPECT_EQ(to_string(message), std::system_error(ec, "test").what()); - message = fmt::memory_buffer(); - - // Check if std::allocator throws on allocating max size_t / 2 chars. - size_t max_size = max_value() / 2; - bool throws_on_alloc = false; - try { - auto alloc = std::allocator(); - alloc.deallocate(alloc.allocate(max_size), max_size); - } catch (const std::bad_alloc&) { - throws_on_alloc = true; - } - if (!throws_on_alloc) { - fmt::print(stderr, "warning: std::allocator allocates {} chars\n", - max_size); - return; - } } TEST(util_test, system_error) { diff --git a/test/printf-test.cc b/test/printf-test.cc index f232263c..1b1f4f57 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -313,7 +313,8 @@ TEST(printf_test, positional_precision) { EXPECT_EQ("Hell", test_sprintf("%2$.*1$s", 4, "Hello")); EXPECT_THROW_MSG(test_sprintf("%2$.*1$d", 5.0, 42), format_error, "precision is not integer"); - EXPECT_THROW_MSG(test_sprintf("%2$.*1$d"), format_error, "argument not found"); + EXPECT_THROW_MSG(test_sprintf("%2$.*1$d"), format_error, + "argument not found"); EXPECT_THROW_MSG(test_sprintf("%2$.*1$d", big_num, 42), format_error, "number is too big"); } @@ -322,7 +323,8 @@ TEST(printf_test, positional_width_and_precision) { EXPECT_EQ(" 00042", test_sprintf("%3$*1$.*2$d", 7, 5, 42)); EXPECT_EQ(" ab", test_sprintf("%3$*1$.*2$s", 7, 2, "abcdef")); EXPECT_EQ(" 00042", test_sprintf("%3$*1$.*2$x", 7, 5, 0x42)); - EXPECT_EQ("100.4400000", test_sprintf("%6$-*5$.*4$f%3$s%2$s%1$s", "", "", "", 7, 4, 100.44)); + EXPECT_EQ("100.4400000", + test_sprintf("%6$-*5$.*4$f%3$s%2$s%1$s", "", "", "", 7, 4, 100.44)); } template struct make_signed { @@ -555,3 +557,8 @@ TEST(printf_test, make_printf_args) { fmt::vsprintf(fmt::basic_string_view(L"[%d] %s happened"), {fmt::make_printf_args(n, L"something")})); } + +TEST(printf_test, trailing_percent_non_nul_terminated) { + auto p = std::unique_ptr(new char('%')); + EXPECT_THROW(fmt::sprintf(fmt::string_view(p.get(), 1)), format_error); +} From 7b2c4d064bef736f712c5d0601b40e1448816b97 Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:38:47 +0800 Subject: [PATCH 003/111] Speed up iterator_buffer (#4679) --- include/fmt/base.h | 66 +++++++++++++++++++++++++++++++++++++++++++- include/fmt/format.h | 59 --------------------------------------- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 70f18c23..bf9347e0 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1858,6 +1858,65 @@ class fixed_buffer_traits { } }; +template +struct has_back_insert_iterator_container_append : std::false_type {}; + +template +struct has_back_insert_iterator_container_append< + OutputIt, InputIt, + void_t()) + .append(std::declval(), + std::declval()))>> : std::true_type {}; + +template +struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; + +template +struct has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt, + void_t()) + .insert(get_container(std::declval()).end(), + std::declval(), + std::declval()))>> : std::true_type {}; + +// An optimized version of std::copy with the output value type (T). +template ::value&& + has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value && + !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value && + has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + +template ::value && + (has_back_insert_iterator_container_append< + OutputIt, InputIt>::value || + has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt>::value)))> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + // A buffer that writes to an output iterator when flushed. template class iterator_buffer : public Traits, public buffer { @@ -1875,7 +1934,12 @@ class iterator_buffer : public Traits, public buffer { this->clear(); const T* begin = data_; const T* end = begin + this->limit(size); - while (begin != end) *out_++ = *begin++; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_move_assignable::value) + out_ = copy(begin, end, out_); + else +#endif + while (begin != end) *out_++ = *begin++; } public: diff --git a/include/fmt/format.h b/include/fmt/format.h index b358f042..b9df2526 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -548,65 +548,6 @@ FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { return out + count; } -template -struct has_back_insert_iterator_container_append : std::false_type {}; - -template -struct has_back_insert_iterator_container_append< - OutputIt, InputIt, - void_t()) - .append(std::declval(), - std::declval()))>> : std::true_type {}; - -template -struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; - -template -struct has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt, - void_t()) - .insert(get_container(std::declval()).end(), - std::declval(), - std::declval()))>> : std::true_type {}; - -// An optimized version of std::copy with the output value type (T). -template ::value&& - has_back_insert_iterator_container_append< - OutputIt, InputIt>::value)> -FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { - get_container(out).append(begin, end); - return out; -} - -template ::value && - !has_back_insert_iterator_container_append< - OutputIt, InputIt>::value && - has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt>::value)> -FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { - auto& c = get_container(out); - c.insert(c.end(), begin, end); - return out; -} - -template ::value && - (has_back_insert_iterator_container_append< - OutputIt, InputIt>::value || - has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt>::value)))> -FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { - while (begin != end) *out++ = static_cast(*begin++); - return out; -} - -template -FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { - return copy(s.begin(), s.end(), out); -} - template FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { From 602df7dab8c795777c82d30dc5c3b3a60364134c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 28 Mar 2026 09:33:45 -0700 Subject: [PATCH 004/111] Simplify copy --- include/fmt/base.h | 62 ++++++++++++++++---------------------------- include/fmt/format.h | 5 ++++ test/base-test.cc | 6 ++--- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index bf9347e0..3c629859 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1859,42 +1859,38 @@ class fixed_buffer_traits { }; template -struct has_back_insert_iterator_container_append : std::false_type {}; +struct has_append : std::false_type {}; template -struct has_back_insert_iterator_container_append< - OutputIt, InputIt, - void_t()) - .append(std::declval(), - std::declval()))>> : std::true_type {}; +struct has_append()) + .append(std::declval(), + std::declval()))>> + : std::true_type {}; -template -struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; +template +struct has_insert : std::false_type {}; -template -struct has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt, - void_t()) - .insert(get_container(std::declval()).end(), - std::declval(), - std::declval()))>> : std::true_type {}; +template +struct has_insert()) + .insert({}, std::declval(), + std::declval()))>> + : std::true_type {}; // An optimized version of std::copy with the output value type (T). template ::value&& - has_back_insert_iterator_container_append< - OutputIt, InputIt>::value)> + FMT_ENABLE_IF(is_back_insert_iterator() && + has_append())> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { get_container(out).append(begin, end); return out; } template ::value && - !has_back_insert_iterator_container_append< - OutputIt, InputIt>::value && - has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt>::value)> + FMT_ENABLE_IF(is_back_insert_iterator() && + !has_append() && + has_insert())> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { auto& c = get_container(out); c.insert(c.end(), begin, end); @@ -1902,21 +1898,14 @@ FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { } template ::value && - (has_back_insert_iterator_container_append< - OutputIt, InputIt>::value || - has_back_insert_iterator_container_insert_at_end< - OutputIt, InputIt>::value)))> + FMT_ENABLE_IF(!is_back_insert_iterator() || + !(has_append() || + has_insert()))> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { while (begin != end) *out++ = static_cast(*begin++); return out; } -template -FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { - return copy(s.begin(), s.end(), out); -} - // A buffer that writes to an output iterator when flushed. template class iterator_buffer : public Traits, public buffer { @@ -1934,12 +1923,7 @@ class iterator_buffer : public Traits, public buffer { this->clear(); const T* begin = data_; const T* end = begin + this->limit(size); -#if defined(__cpp_if_constexpr) - if constexpr (std::is_move_assignable::value) - out_ = copy(begin, end, out_); - else -#endif - while (begin != end) *out_++ = *begin++; + out_ = copy(begin, end, out_); } public: diff --git a/include/fmt/format.h b/include/fmt/format.h index b9df2526..e46b613e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -548,6 +548,11 @@ FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { return out + count; } +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + template FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, OutputIt out) -> OutputIt { diff --git a/test/base-test.cc b/test/base-test.cc index bf268875..9575e055 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -811,10 +811,10 @@ TEST(base_test, throw_in_buffer_dtor) { constexpr int buffer_size = 256; struct throwing_iterator { - int& count; + int* count; auto operator=(char) -> throwing_iterator& { - if (++count > buffer_size) throw std::exception(); + if (++*count > buffer_size) throw std::exception(); return *this; } auto operator*() -> throwing_iterator& { return *this; } @@ -824,7 +824,7 @@ TEST(base_test, throw_in_buffer_dtor) { try { int count = 0; - fmt::format_to(throwing_iterator{count}, fmt::runtime("{:{}}{"), "", + fmt::format_to(throwing_iterator{&count}, fmt::runtime("{:{}}{"), "", buffer_size + 1); } catch (const std::exception&) { } From cdb8dc76d936a12aacc20b6d283d7c24ee4307fe Mon Sep 17 00:00:00 2001 From: Mathew Benson Date: Sat, 28 Mar 2026 19:45:56 +0300 Subject: [PATCH 005/111] Add C++-Modules Based Linux CI workflow (#4708) - This adds a set of CI tests for linux that include compilers that support modules - This CI workflow uses ninja as the build system generator - Can be integrated with the other linux workflow after successful testing Co-authored-by: Mathew Benson --- .github/workflows/linux.yml | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 014e027b..6bced99b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -33,6 +33,10 @@ jobs: std: 23 install: sudo apt install g++-13 shared: -DBUILD_SHARED_LIBS=ON + - cxx: g++-14 + build_type: Release + std: 23 + install: sudo apt install g++-14 - cxx: clang++-11 build_type: Debug std: 17 @@ -55,6 +59,11 @@ jobs: std: 20 cxxflags: -stdlib=libc++ install: sudo apt install libc++-14-dev libc++abi-14-dev + - cxx: clang++-20 + build_type: Debug + std: 20 + cxxflags: -stdlib=libc++ + install: sudo apt install clang-20 libc++-20-dev libc++abi-20-dev steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 @@ -131,7 +140,14 @@ jobs: - name: Add repositories for newer GCC run: | sudo apt-add-repository ppa:ubuntu-toolchain-r/test - if: ${{ matrix.cxx == 'g++-13' }} + if: ${{ matrix.cxx == 'g++-13' || matrix.cxx == 'g++-14' }} + + - name: Install LLVM-20 + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 20 + if: ${{ matrix.cxx == 'clang++-20' }} - name: Add Ubuntu mirrors run: | @@ -164,6 +180,23 @@ jobs: -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \ ${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE + if: ${{ matrix.cxx != 'clang++-20' && matrix.cxx != 'g++-14' }} + + - name: Configure-Modules + working-directory: ${{runner.workspace}}/build + env: + CXX: ${{matrix.cxx}} + CXXFLAGS: ${{matrix.cxxflags}} + run: | + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -DCMAKE_CXX_EXTENSIONS=OFF \ + -G Ninja \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ + -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_DOC=OFF -DFMT_PEDANTIC=ON \ + ${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE + if: ${{ matrix.cxx == 'clang++-20' || matrix.cxx == 'g++-14' }} - name: Build working-directory: ${{runner.workspace}}/build From 8572a246038d7d7d929128151941bd3b02f08e5d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 2 Apr 2026 14:45:48 -0700 Subject: [PATCH 006/111] Cleanup CI config --- .github/workflows/linux.yml | 29 +++++++++-------------------- test/ranges-test.cc | 11 +++++++++-- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6bced99b..f9cca925 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -16,7 +16,6 @@ jobs: shared: [""] include: - cxx: g++-4.9 - - cxx: clang++-3.6 - cxx: g++-11 build_type: Debug std: 14 @@ -37,6 +36,8 @@ jobs: build_type: Release std: 23 install: sudo apt install g++-14 + gen: -G Ninja + - cxx: clang++-3.6 - cxx: clang++-11 build_type: Debug std: 17 @@ -64,6 +65,7 @@ jobs: std: 20 cxxflags: -stdlib=libc++ install: sudo apt install clang-20 libc++-20-dev libc++abi-20-dev + gen: -G Ninja steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 @@ -174,29 +176,16 @@ jobs: CXX: ${{matrix.cxx}} CXXFLAGS: ${{matrix.cxxflags}} ${{matrix.cxxflags_extra}} run: | - cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + cmake ${{matrix.gen}} \ + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ - -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \ - ${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE - if: ${{ matrix.cxx != 'clang++-20' && matrix.cxx != 'g++-14' }} - - - name: Configure-Modules - working-directory: ${{runner.workspace}}/build - env: - CXX: ${{matrix.cxx}} - CXXFLAGS: ${{matrix.cxxflags}} - run: | - cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ - -DCMAKE_CXX_STANDARD=${{matrix.std}} \ - -DCMAKE_CXX_EXTENSIONS=OFF \ - -G Ninja \ - -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ - -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ -DFMT_DOC=OFF -DFMT_PEDANTIC=ON \ - ${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE - if: ${{ matrix.cxx == 'clang++-20' || matrix.cxx == 'g++-14' }} + -DFMT_WERROR=ON \ + ${{matrix.fuzz}} \ + ${{matrix.shared}} \ + $GITHUB_WORKSPACE - name: Build working-directory: ${{runner.workspace}}/build diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 7ec0d76c..0c2d771d 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -710,10 +710,17 @@ struct codec_mask { int except = 0; }; +// A named functor instead of a lambda to avoid -Wsubobject-linkage: a lambda +// gives the filter_view type internal linkage, which propagates to the +// formatter base class via format_as. +struct not_equal { + int value; + bool operator()(int c) const { return c != value; } +}; + auto format_as(codec_mask mask) { - // Careful not to capture param by reference here, it will dangle. return codec_mask::codecs | - std::views::filter([mask](auto c) { return c != mask.except; }); + std::views::filter(not_equal{mask.except}); } } // namespace views_filter_view_test From 579587bee349b31fcedf24dba4bf50a4edc73a4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:20:10 -0700 Subject: [PATCH 007/111] Bump github/codeql-action from 4.32.0 to 4.35.1 (#4727) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.0 to 4.35.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/b20883b0cd1f46c72ae0ba6d1090936928f9fa30...c10b8064de6f491fea524254123dbe5e09572f13) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.35.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 7d389b2a..aad70861 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -60,6 +60,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v3.29.5 + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 with: sarif_file: results.sarif From 907f002f8ed01cab120fc4cf8f4083658dc7fc1c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 2 Apr 2026 15:36:00 -0700 Subject: [PATCH 008/111] Cleanup CI --- .github/workflows/linux.yml | 11 ++++++++--- .github/workflows/macos.yml | 3 +++ .github/workflows/windows.yml | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f9cca925..2745809e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,6 +7,11 @@ permissions: jobs: build: + name: >- + ${{matrix.cxx}} c++${{matrix.std}} ${{matrix.build_type}} + ${{matrix.gen}} ${{matrix.fuzz && 'Fuzz' || ''}} + ${{matrix.shared && 'Shared' || ''}} + ${{matrix.cxxflags_extra && 'Sanitize' || ''}} runs-on: ubuntu-22.04 strategy: matrix: @@ -36,7 +41,7 @@ jobs: build_type: Release std: 23 install: sudo apt install g++-14 - gen: -G Ninja + gen: Ninja - cxx: clang++-3.6 - cxx: clang++-11 build_type: Debug @@ -65,7 +70,7 @@ jobs: std: 20 cxxflags: -stdlib=libc++ install: sudo apt install clang-20 libc++-20-dev libc++abi-20-dev - gen: -G Ninja + gen: Ninja steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 @@ -176,7 +181,7 @@ jobs: CXX: ${{matrix.cxx}} CXXFLAGS: ${{matrix.cxxflags}} ${{matrix.cxxflags_extra}} run: | - cmake ${{matrix.gen}} \ + cmake ${{matrix.gen && '-G' || ''}} ${{matrix.gen}} \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index ef7964ac..19d78427 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -7,6 +7,9 @@ permissions: jobs: build: + name: >- + c++${{matrix.std }} ${{ matrix.build_type}} + ${{matrix.shared && 'Shared' || ''}} strategy: matrix: os: [macos-14] diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index cabd2833..0a50a2df 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -7,6 +7,9 @@ permissions: jobs: build: + name: >- + ${{matrix.platform}} ${{matrix.toolset}} c++${{matrix.standard}} + ${{matrix.build_type}} ${{matrix.shared && 'Shared' || ''}} runs-on: ${{matrix.os}} strategy: matrix: From 3febdca556d2979566cd88ebebeb809c9faabcc0 Mon Sep 17 00:00:00 2001 From: mccakit <117523238+mccakit@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:51:38 +0300 Subject: [PATCH 009/111] clang compilation failiure fix (#4718) --- src/fmt.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/fmt.cc b/src/fmt.cc index 0713da75..1973a1ac 100644 --- a/src/fmt.cc +++ b/src/fmt.cc @@ -134,11 +134,6 @@ extern "C++" { } #endif -// gcc doesn't yet implement private module fragments -#if !FMT_GCC_VERSION -module :private; -#endif - #ifdef FMT_ATTACH_TO_GLOBAL_MODULE extern "C++" { #endif From d2a159fbe7cb2bbcd61935e7f9d8f416eb9641d9 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 3 Apr 2026 17:35:28 -0700 Subject: [PATCH 010/111] Apply clang-format --- include/fmt/base.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 3c629859..49868b6d 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1872,10 +1872,10 @@ template struct has_insert : std::false_type {}; template -struct has_insert()) - .insert({}, std::declval(), - std::declval()))>> +struct has_insert< + OutputIt, T, + void_t()) + .insert({}, std::declval(), std::declval()))>> : std::true_type {}; // An optimized version of std::copy with the output value type (T). From 9396f77fe4b007d4803ff2248a0926f2ced46951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ka=C4=9Fan=20Can=20=C5=9Eit?= Date: Mon, 6 Apr 2026 04:40:48 +0300 Subject: [PATCH 011/111] Remove unused header includes in test files (#4729) --- test/chrono-test.cc | 1 - test/format-impl-test.cc | 1 - test/format-test.cc | 21 ++++++++++----------- test/gtest-extra-test.cc | 1 - test/os-test.cc | 1 - test/printf-test.cc | 1 - test/ranges-test.cc | 1 - test/scan-test.cc | 2 -- test/xchar-test.cc | 2 -- 9 files changed, 10 insertions(+), 21 deletions(-) diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 98b37000..4dee7c47 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -7,7 +7,6 @@ #include "fmt/chrono.h" -#include #include #include diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 0d906c4e..4259add3 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -5,7 +5,6 @@ // // For the license information refer to format.h. -#include #include // clang-format off diff --git a/test/format-test.cc b/test/format-test.cc index f18f3172..cd07c26a 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -15,17 +15,16 @@ #include // uint32_t -#include // fegetexceptflag and FE_ALL_EXCEPT -#include // INT_MAX -#include // std::signbit -#include // std::condition_variable -#include // std::strlen -#include // std::back_inserter -#include // std::list -#include // std::mutex -#include // std::string -#include // std::thread -#include // std::is_default_constructible +#include // fegetexceptflag and FE_ALL_EXCEPT +#include // INT_MAX +#include // std::signbit +#include // std::strlen +#include // std::back_inserter +#include // std::list +#include // std::mutex +#include // std::string +#include // std::thread +#include // std::is_default_constructible #if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE() # include #endif diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 5c860400..8e4401b8 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -9,7 +9,6 @@ #include -#include #include #include diff --git a/test/os-test.cc b/test/os-test.cc index e789e87e..5ece0eb7 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -7,7 +7,6 @@ #include "fmt/os.h" -#include // std::exit #include #include #include diff --git a/test/printf-test.cc b/test/printf-test.cc index 1b1f4f57..354143b9 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -9,7 +9,6 @@ #include #include -#include #include "fmt/xchar.h" // DEPRECATED! #include "gtest-extra.h" diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 0c2d771d..bd161d75 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/test/scan-test.cc b/test/scan-test.cc index d4f9e413..e0b7e2bc 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -7,8 +7,6 @@ #include "scan.h" -#include - #include #include diff --git a/test/xchar-test.cc b/test/xchar-test.cc index f6620ddf..ec82d425 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -7,9 +7,7 @@ #include "fmt/xchar.h" -#include #include -#include #include #include "fmt/chrono.h" From be98ea8addfd8f0dd6f319e7818f94988728ca0a Mon Sep 17 00:00:00 2001 From: Mike Vastola Date: Fri, 10 Apr 2026 11:01:13 -0400 Subject: [PATCH 012/111] Fix bug re: return type of `f(un)lockfile` wrappers for Windows (#4739) Just a tiny bugfix I spotted: The `f(un)lockfile` wrappers in `format-inl.h` that wrap Windows's `_(un)lock_file` methods are defined with a trailing return type derived by using decltype on a hypothetical call to the underlying functions. The wrappers don't contain a `return` in their bodies, however, so if the return type of the underlying functions were to ever change from `void`, there would be a compile error. This just adds `return` to each. --- include/fmt/format-inl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index cc110198..539b1fd7 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1477,10 +1477,10 @@ template struct span { }; template auto flockfile(F* f) -> decltype(_lock_file(f)) { - _lock_file(f); + return _lock_file(f); } template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { - _unlock_file(f); + return _unlock_file(f); } #ifndef getc_unlocked From 4ccf1d4faf977a77f157fcad2b96b663109f59cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Fri, 10 Apr 2026 21:23:57 +0200 Subject: [PATCH 013/111] Fix out-of-bounds read in vprintf with trailing '%' (#4742) --- include/fmt/printf.h | 2 ++ test/printf-test.cc | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/fmt/printf.h b/include/fmt/printf.h index c2e92291..235dbb06 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -438,6 +438,8 @@ void vprintf(buffer& buf, basic_string_view format, } write(out, basic_string_view(start, to_unsigned(it - 1 - start))); + if (it == end) report_error("invalid format string"); + auto specs = format_specs(); specs.set_align(align::right); diff --git a/test/printf-test.cc b/test/printf-test.cc index 354143b9..b1478526 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -46,6 +46,14 @@ auto test_sprintf(fmt::basic_string_view format, const Args&... args) TEST(printf_test, no_args) { EXPECT_EQ("test", test_sprintf("test")); } +TEST(printf_test, trailing_percent) { + EXPECT_THROW_MSG(test_sprintf("%"), format_error, "invalid format string"); + EXPECT_THROW_MSG(test_sprintf("hello%"), format_error, + "invalid format string"); + EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error, + "invalid format string"); +} + TEST(printf_test, escape) { EXPECT_EQ("%", test_sprintf("%%")); EXPECT_EQ("before %", test_sprintf("before %%")); @@ -76,8 +84,6 @@ TEST(printf_test, number_is_too_big_in_arg_index) { } TEST(printf_test, switch_arg_indexing) { - EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error, - "cannot switch from manual to automatic argument indexing"); EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2), format_error, "number is too big"); EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error, From 03cfb8645e2646fce738fe9d8e13fe7d7d947ad4 Mon Sep 17 00:00:00 2001 From: Cazadorro Date: Fri, 10 Apr 2026 18:28:52 -0500 Subject: [PATCH 014/111] Workaround a CUDA issue in handing UTF-32 literals (#4719) --- include/fmt/format.h | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index e46b613e..68c3f76b 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3161,8 +3161,25 @@ constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { // It is equal to ceil(2^31 + 2^32/10^(k + 1)). // These are stored in a string literal because we cannot have static arrays // in constexpr functions and non-static ones are poorly optimized. - return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" - U"\x800001ae\x8000002b"[index]; + + // while in C++23 we can use static constexpr, and in c++17 we can use out of + // function definition of inline constexpr, in C++11 we have to rely on string + // literals in order to avoid duplicating constant definitions across + // translation units. We take the following uint32 array definition: + // {0x9999999au, 0x828f5c29u, 0x80418938u, 0x80068db9, + // 0x8000a7c6u, 0x800010c7u, 0x800001aeu, 0x8000002b}; + // and convert that into a series of char hexidecimal literals in a char16_t + // array: + // "\x9999\x999a \x828f\x5c29 \x8041\x8938 \x8006\x8db9 + // \x8000\xa7c6 \x8000\x10c7 \x8000\x01ae \x8000\x002b"; + // Then we split this up into two separate arrays of char16_ts, so they can + // be properly recombined into uint32_t. + + return static_cast( + u"\x9999\x828f\x8041\x8006\x8000\x8000\x8000\x8000"[index]) + << 16u | + static_cast( + u"\x999a\x5c29\x8938\x8db9\xa7c6\x10c7\x01ae\x002b"[index]); } template From 27d0c03c4d2e9c7acd79d9d65f6ada2cdc96551e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 12 Apr 2026 09:50:12 -0700 Subject: [PATCH 015/111] Minor cleanup --- include/fmt/format.h | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 68c3f76b..256a99ab 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3161,25 +3161,9 @@ constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { // It is equal to ceil(2^31 + 2^32/10^(k + 1)). // These are stored in a string literal because we cannot have static arrays // in constexpr functions and non-static ones are poorly optimized. - - // while in C++23 we can use static constexpr, and in c++17 we can use out of - // function definition of inline constexpr, in C++11 we have to rely on string - // literals in order to avoid duplicating constant definitions across - // translation units. We take the following uint32 array definition: - // {0x9999999au, 0x828f5c29u, 0x80418938u, 0x80068db9, - // 0x8000a7c6u, 0x800010c7u, 0x800001aeu, 0x8000002b}; - // and convert that into a series of char hexidecimal literals in a char16_t - // array: - // "\x9999\x999a \x828f\x5c29 \x8041\x8938 \x8006\x8db9 - // \x8000\xa7c6 \x8000\x10c7 \x8000\x01ae \x8000\x002b"; - // Then we split this up into two separate arrays of char16_ts, so they can - // be properly recombined into uint32_t. - - return static_cast( - u"\x9999\x828f\x8041\x8006\x8000\x8000\x8000\x8000"[index]) + return uint32_t(u"\x9999\x828f\x8041\x8006\x8000\x8000\x8000\x8000"[index]) << 16u | - static_cast( - u"\x999a\x5c29\x8938\x8db9\xa7c6\x10c7\x01ae\x002b"[index]); + uint32_t(u"\x999a\x5c29\x8938\x8db9\xa7c6\x10c7\x01ae\x002b"[index]); } template From 8783c3c2d4a507c6f319db7a06bf395e666c12d9 Mon Sep 17 00:00:00 2001 From: Mathew Benson Date: Sun, 12 Apr 2026 20:07:16 +0300 Subject: [PATCH 016/111] enable fmt_module tests by updating module-test.cc (#4702) - Update g++ module testing to use g++ version 15 instead of 14. The module support in version 14 isn't very stable and the module testing was not working due to unresolved locale symbols. - Update CI for linux and macos to add verbosity so its consistent with windows. - Refactor test/CMakeLists.txt to enable testing for modules - The tests in `module-test.cc` seem to not have been updated in some time despite changes in the main library. - Wide String versions of several tests appear to be deprecated so have been removed. - Refactored tests related to `fmt::format_args` that now requires lvalue references as opposed to direct values. - Remove the none-working code identified in the `module-test.cc` file while updating. --- .github/workflows/linux.yml | 8 +- .github/workflows/macos.yml | 2 +- test/CMakeLists.txt | 6 +- test/module-test.cc | 144 ++++++++++-------------------------- 4 files changed, 46 insertions(+), 114 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2745809e..e27d90ed 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,10 +37,10 @@ jobs: std: 23 install: sudo apt install g++-13 shared: -DBUILD_SHARED_LIBS=ON - - cxx: g++-14 + - cxx: g++-15 build_type: Release std: 23 - install: sudo apt install g++-14 + install: sudo apt install g++-15 gen: Ninja - cxx: clang++-3.6 - cxx: clang++-11 @@ -147,7 +147,7 @@ jobs: - name: Add repositories for newer GCC run: | sudo apt-add-repository ppa:ubuntu-toolchain-r/test - if: ${{ matrix.cxx == 'g++-13' || matrix.cxx == 'g++-14' }} + if: ${{ matrix.cxx == 'g++-13' || matrix.cxx == 'g++-15' }} - name: Install LLVM-20 run: | @@ -200,6 +200,6 @@ jobs: - name: Test working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} + run: ctest -C ${{matrix.build_type}} -V env: CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 19d78427..d1a60ba9 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -53,6 +53,6 @@ jobs: - name: Test working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} + run: ctest -C ${{matrix.build_type}} -V env: CTEST_OUTPUT_ON_FAILURE: True diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a149241b..cd7ba177 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,7 @@ function (add_fmt_test name) set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables) endif () elseif (ADD_FMT_TEST_MODULE) - set(libs test-main) + set(libs test-main fmt-module) else () set(libs test-main fmt) endif () @@ -81,9 +81,7 @@ add_executable(perf-sanity perf-sanity.cc) target_link_libraries(perf-sanity fmt::fmt) if (FMT_MODULE) - # Module tests are currently disabled. - # add_fmt_test(module-test MODULE test-main.cc) - # target_link_libraries(module-test fmt-module) + add_fmt_test(module-test MODULE) endif () if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC) diff --git a/test/module-test.cc b/test/module-test.cc index bd9624f9..7b2f6e9c 100644 --- a/test/module-test.cc +++ b/test/module-test.cc @@ -25,6 +25,7 @@ #include #include #include +#include #if (__has_include() || defined(__APPLE__) || \ defined(__linux__)) && \ @@ -51,6 +52,8 @@ static bool macro_leaked = #endif #define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly + // this define plus the #include above + // prevents header inclusion after the import fmt statement #include "gtest-extra.h" // an implicitly exported namespace must be visible [module.interface]/2.2 @@ -60,32 +63,6 @@ TEST(module_test, namespace) { ASSERT_TRUE(true); } -namespace detail { -bool oops_detail_namespace_is_visible; -} - -namespace fmt { -bool namespace_detail_invisible() { -#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ - _MSC_FULL_VER <= 193700000 - // bug in msvc up to at least 17.7: - - // the namespace is visible even when it is neither - // implicitly nor explicitly exported - return true; -#else - using namespace detail; - // this fails to compile if fmt::detail is visible - return !oops_detail_namespace_is_visible; -#endif -} -} // namespace fmt - -// the non-exported namespace 'detail' must be invisible [module.interface]/2 -TEST(module_test, detail_namespace) { - EXPECT_TRUE(fmt::namespace_detail_invisible()); -} - // macros must not be imported from a *named* module [cpp.import]/5.1 TEST(module_test, macros) { #if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ @@ -170,7 +147,8 @@ TEST(module_test, format_args) { auto no_args = fmt::format_args(); EXPECT_FALSE(no_args.get(1)); - fmt::basic_format_args args = fmt::make_format_args(42); + int n = 42; + fmt::basic_format_args args = fmt::make_format_args(n); EXPECT_TRUE(args.max_size() > 0); auto arg0 = args.get(0); EXPECT_TRUE(arg0); @@ -182,41 +160,35 @@ TEST(module_test, format_args) { TEST(module_test, wformat_args) { auto no_args = fmt::wformat_args(); EXPECT_FALSE(no_args.get(1)); - fmt::basic_format_args args = fmt::make_wformat_args(42); + int n = 42; + fmt::basic_format_args args = fmt::make_wformat_args(n); EXPECT_TRUE(args.get(0)); } TEST(module_test, dynamic_format_args) { fmt::dynamic_format_arg_store dyn_store; dyn_store.push_back(fmt::arg("a42", 42)); - fmt::basic_format_args args = dyn_store; + + fmt::basic_format_args args = dyn_store; EXPECT_FALSE(args.get(3)); EXPECT_TRUE(args.get(fmt::string_view("a42"))); - - fmt::dynamic_format_arg_store wdyn_store; - wdyn_store.push_back(fmt::arg(L"a42", 42)); - fmt::basic_format_args wargs = wdyn_store; - EXPECT_FALSE(wargs.get(3)); - EXPECT_TRUE(wargs.get(fmt::wstring_view(L"a42"))); } TEST(module_test, vformat) { - EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(42))); + int n = 42; + EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(n))); EXPECT_EQ(L"42", - fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(42))); + fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(n))); } TEST(module_test, vformat_to) { - auto store = fmt::make_format_args(42); + int n = 42; + auto store = fmt::make_format_args(n); std::string s; fmt::vformat_to(std::back_inserter(s), "{}", store); EXPECT_EQ("42", s); - char buffer[4] = {0}; - fmt::vformat_to(buffer, "{:}", store); - EXPECT_EQ("42", std::string_view(buffer)); - - auto wstore = fmt::make_wformat_args(42); + auto wstore = fmt::make_wformat_args(n); std::wstring w; fmt::vformat_to(std::back_inserter(w), L"{}", wstore); EXPECT_EQ(L"42", w); @@ -227,16 +199,15 @@ TEST(module_test, vformat_to) { } TEST(module_test, vformat_to_n) { - auto store = fmt::make_format_args(12345); + int n = 12345; + auto store = fmt::make_format_args(n); std::string s; - auto result = fmt::vformat_to_n(std::back_inserter(s), 1, "{}", store); + fmt::vformat_to_n(std::back_inserter(s), 1, "{}", store); char buffer[4] = {0}; fmt::vformat_to_n(buffer, 3, "{:}", store); - auto wstore = fmt::make_wformat_args(12345); + auto wstore = fmt::make_wformat_args(n); std::wstring w; - auto wresult = fmt::vformat_to_n(std::back_inserter(w), 1, - fmt::wstring_view(L"{}"), wstore); wchar_t wbuffer[4] = {0}; fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore); } @@ -249,18 +220,14 @@ std::string as_string(std::wstring_view text) { TEST(module_test, print) { EXPECT_WRITE(stdout, fmt::print("{}µ", 42), "42µ"); EXPECT_WRITE(stderr, fmt::print(stderr, "{}µ", 4.2), "4.2µ"); - EXPECT_WRITE(stdout, fmt::print(L"{}µ", 42), as_string(L"42µ")); - EXPECT_WRITE(stderr, fmt::print(stderr, L"{}µ", 4.2), as_string(L"4.2µ")); } TEST(module_test, vprint) { - EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(42)), "42µ"); - EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(4.2)), + int n = 42; + double m = 4.2; + EXPECT_WRITE(stdout, fmt::vprint("{:}µ", fmt::make_format_args(n)), "42µ"); + EXPECT_WRITE(stderr, fmt::vprint(stderr, "{}", fmt::make_format_args(m)), "4.2"); - EXPECT_WRITE(stdout, fmt::vprint(L"{:}µ", fmt::make_wformat_args(42)), - as_string(L"42µ")); - EXPECT_WRITE(stderr, fmt::vprint(stderr, L"{}", fmt::make_wformat_args(42)), - as_string(L"42")); } TEST(module_test, named_args) { @@ -275,7 +242,8 @@ TEST(module_test, literals) { } TEST(module_test, locale) { - auto store = fmt::make_format_args(4.2); + const double m = 4.2; + auto store = fmt::make_format_args(m); const auto classic = std::locale::classic(); EXPECT_EQ("4.2", fmt::format(classic, "{:L}", 4.2)); EXPECT_EQ("4.2", fmt::vformat(classic, "{:L}", store)); @@ -284,12 +252,9 @@ TEST(module_test, locale) { EXPECT_EQ("4.2", s); EXPECT_EQ("4.2", fmt::format("{:L}", 4.2)); - auto wstore = fmt::make_wformat_args(4.2); + auto wstore = fmt::make_wformat_args(m); EXPECT_EQ(L"4.2", fmt::format(classic, L"{:L}", 4.2)); EXPECT_EQ(L"4.2", fmt::vformat(classic, L"{:L}", wstore)); - std::wstring w; - fmt::vformat_to(std::back_inserter(w), classic, L"{:L}", wstore); - EXPECT_EQ(L"4.2", w); EXPECT_EQ(L"4.2", fmt::format(L"{:L}", 4.2)); } @@ -313,34 +278,17 @@ TEST(module_test, memory_buffer) { EXPECT_EQ("42", to_string(buffer)); nbuffer.clear(); EXPECT_EQ(0u, to_string(nbuffer).size()); - - fmt::wmemory_buffer wbuffer; - EXPECT_EQ(0u, to_string(wbuffer).size()); -} - -TEST(module_test, is_char) { - EXPECT_TRUE(fmt::is_char()); - EXPECT_TRUE(fmt::is_char()); - EXPECT_TRUE(fmt::is_char()); - EXPECT_TRUE(fmt::is_char()); - EXPECT_TRUE(fmt::is_char()); - EXPECT_FALSE(fmt::is_char()); } TEST(module_test, ptr) { uintptr_t answer = 42; auto p = std::bit_cast(answer); EXPECT_EQ("0x2a", fmt::to_string(fmt::ptr(p))); - std::unique_ptr up(p); - EXPECT_EQ("0x2a", fmt::to_string(fmt::ptr(up))); - up.release(); - auto sp = std::make_shared(0); - p = sp.get(); - EXPECT_EQ(fmt::to_string(fmt::ptr(p)), fmt::to_string(fmt::ptr(sp))); } TEST(module_test, errors) { - auto store = fmt::make_format_args(42); + int n = 42; + auto store = fmt::make_format_args(n); EXPECT_THROW(throw fmt::format_error("oops"), std::exception); EXPECT_THROW(throw fmt::vsystem_error(0, "{}", store), std::system_error); EXPECT_THROW(throw fmt::system_error(0, "{}", 42), std::system_error); @@ -365,8 +313,6 @@ TEST(module_test, error_code) { fmt::format("{0}", std::error_code(42, std::generic_category()))); EXPECT_EQ("system:42", fmt::format("{0}", std::error_code(42, fmt::system_category()))); - EXPECT_EQ(L"generic:42", - fmt::format(L"{0}", std::error_code(42, std::generic_category()))); } TEST(module_test, format_int) { @@ -385,23 +331,19 @@ TEST(module_test, formatter) { EXPECT_TRUE(test_formatter{}.check()); } TEST(module_test, join) { int arr[3] = {1, 2, 3}; std::vector vec{1.0, 2.0, 3.0}; - std::initializer_list il{1, 2, 3}; auto sep = fmt::string_view(", "); EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr + 0, arr + 3, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(arr, sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec.begin(), vec.end(), sep))); EXPECT_EQ("1, 2, 3", to_string(fmt::join(vec, sep))); - EXPECT_EQ("1, 2, 3", to_string(fmt::join(il, sep))); auto wsep = fmt::wstring_view(L", "); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr + 0, arr + 3, wsep))); EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(arr, wsep))); - EXPECT_EQ(L"1, 2, 3", fmt::format(L"{}", fmt::join(il, wsep))); } TEST(module_test, time) { auto time_now = std::time(nullptr); - EXPECT_TRUE(fmt::localtime(time_now).tm_year > 120); EXPECT_TRUE(fmt::gmtime(time_now).tm_year > 120); auto chrono_now = std::chrono::system_clock::now(); EXPECT_TRUE(fmt::gmtime(chrono_now).tm_year > 120); @@ -433,42 +375,34 @@ TEST(module_test, weekday) { TEST(module_test, printf) { EXPECT_WRITE(stdout, fmt::printf("%f", 42.123456), "42.123456"); EXPECT_WRITE(stdout, fmt::printf("%d", 42), "42"); - EXPECT_WRITE(stdout, fmt::printf(L"%f", 42.123456), as_string(L"42.123456")); - EXPECT_WRITE(stdout, fmt::printf(L"%d", 42), as_string(L"42")); } TEST(module_test, fprintf) { EXPECT_WRITE(stderr, fmt::fprintf(stderr, "%d", 42), "42"); - EXPECT_WRITE(stderr, fmt::fprintf(stderr, L"%d", 42), as_string(L"42")); } TEST(module_test, sprintf) { EXPECT_EQ("42", fmt::sprintf("%d", 42)); - EXPECT_EQ(L"42", fmt::sprintf(L"%d", 42)); -} - -TEST(module_test, vprintf) { - EXPECT_WRITE(stdout, fmt::vprintf("%d", fmt::make_printf_args(42)), "42"); - EXPECT_WRITE(stdout, fmt::vprintf(L"%d", fmt::make_wprintf_args(42)), - as_string(L"42")); } TEST(module_test, vfprintf) { - auto args = fmt::make_printf_args(42); - EXPECT_WRITE(stderr, fmt::vfprintf(stderr, "%d", args), "42"); - auto wargs = fmt::make_wprintf_args(42); - EXPECT_WRITE(stderr, fmt::vfprintf(stderr, L"%d", wargs), as_string(L"42")); + int n = 42; + auto store = fmt::make_format_args(n); + auto args = fmt::basic_format_args(store); + EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args), + "42"); } TEST(module_test, vsprintf) { - EXPECT_EQ("42", fmt::vsprintf("%d", fmt::make_printf_args(42))); - EXPECT_EQ(L"42", fmt::vsprintf(L"%d", fmt::make_wprintf_args(42))); + int n = 42; + auto store = fmt::make_format_args(n); + auto args = fmt::basic_format_args(store); + EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42"); + EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args), + "42"); } TEST(module_test, color) { - auto fg_check = fg(fmt::rgb(255, 200, 30)); - auto bg_check = bg(fmt::color::dark_slate_gray) | fmt::emphasis::italic; - auto emphasis_check = fmt::emphasis::underline | fmt::emphasis::bold; EXPECT_EQ("\x1B[30m42\x1B[0m", fmt::format(fg(fmt::terminal_color::black), "{}", 42)); EXPECT_EQ(L"\x1B[30m42\x1B[0m", From 382609a920d2c43c00f2951233bc0d578a783982 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 14 Apr 2026 08:57:29 -0700 Subject: [PATCH 017/111] Cleanup module test and CI config --- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- test/module-test.cc | 94 +++---------------------------------- 3 files changed, 8 insertions(+), 90 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e27d90ed..da74465e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -200,6 +200,6 @@ jobs: - name: Test working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -V + run: ctest -C ${{matrix.build_type}} env: CTEST_OUTPUT_ON_FAILURE: True diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index d1a60ba9..19d78427 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -53,6 +53,6 @@ jobs: - name: Test working-directory: ${{runner.workspace}}/build - run: ctest -C ${{matrix.build_type}} -V + run: ctest -C ${{matrix.build_type}} env: CTEST_OUTPUT_ON_FAILURE: True diff --git a/test/module-test.cc b/test/module-test.cc index 7b2f6e9c..d84e66b8 100644 --- a/test/module-test.cc +++ b/test/module-test.cc @@ -9,12 +9,6 @@ // All Rights Reserved // {fmt} module. -#ifdef _MSC_FULL_VER -// hide some implementation bugs in msvc -// that are not essential to users of the module. -# define FMT_HIDE_MODULE_BUGS -#endif - #include #include #include @@ -43,17 +37,7 @@ import fmt; -// check for macros leaking from BMI -static bool macro_leaked = -#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H_) - true; -#else - false; -#endif - #define FMT_OS_H_ // don't pull in os.h, neither directly nor indirectly - // this define plus the #include above - // prevents header inclusion after the import fmt statement #include "gtest-extra.h" // an implicitly exported namespace must be visible [module.interface]/2.2 @@ -63,16 +47,11 @@ TEST(module_test, namespace) { ASSERT_TRUE(true); } -// macros must not be imported from a *named* module [cpp.import]/5.1 +// Macros must not be imported from a named module [cpp.import]/5.1. TEST(module_test, macros) { -#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \ - _MSC_FULL_VER <= 192930130 - // bug in msvc up to 16.11-pre2: - // include-guard macros leak from BMI - // and even worse: they cannot be #undef-ined - macro_leaked = false; +#if defined(FMT_BASE_H_) || defined(FMT_FORMAT_H_) + FAIL() << "Macros are leaking from a named module"; #endif - EXPECT_FALSE(macro_leaked); } // The following is less about functional testing (that's done elsewhere) @@ -144,41 +123,16 @@ TEST(module_test, format_to_n) { } TEST(module_test, format_args) { - auto no_args = fmt::format_args(); - EXPECT_FALSE(no_args.get(1)); - int n = 42; - fmt::basic_format_args args = fmt::make_format_args(n); - EXPECT_TRUE(args.max_size() > 0); - auto arg0 = args.get(0); - EXPECT_TRUE(arg0); - decltype(arg0) arg_none; - EXPECT_FALSE(arg_none); - EXPECT_TRUE(arg0.type() != arg_none.type()); -} - -TEST(module_test, wformat_args) { - auto no_args = fmt::wformat_args(); - EXPECT_FALSE(no_args.get(1)); - int n = 42; - fmt::basic_format_args args = fmt::make_wformat_args(n); + auto store = fmt::make_format_args(n); + fmt::format_args args = store; EXPECT_TRUE(args.get(0)); -} - -TEST(module_test, dynamic_format_args) { - fmt::dynamic_format_arg_store dyn_store; - dyn_store.push_back(fmt::arg("a42", 42)); - - fmt::basic_format_args args = dyn_store; - EXPECT_FALSE(args.get(3)); - EXPECT_TRUE(args.get(fmt::string_view("a42"))); + EXPECT_FALSE(args.get(1)); } TEST(module_test, vformat) { int n = 42; EXPECT_EQ("42", fmt::vformat("{}", fmt::make_format_args(n))); - EXPECT_EQ(L"42", - fmt::vformat(fmt::wstring_view(L"{}"), fmt::make_wformat_args(n))); } TEST(module_test, vformat_to) { @@ -187,15 +141,6 @@ TEST(module_test, vformat_to) { std::string s; fmt::vformat_to(std::back_inserter(s), "{}", store); EXPECT_EQ("42", s); - - auto wstore = fmt::make_wformat_args(n); - std::wstring w; - fmt::vformat_to(std::back_inserter(w), L"{}", wstore); - EXPECT_EQ(L"42", w); - - wchar_t wbuffer[4] = {0}; - fmt::vformat_to(wbuffer, L"{:}", wstore); - EXPECT_EQ(L"42", std::wstring_view(wbuffer)); } TEST(module_test, vformat_to_n) { @@ -205,11 +150,6 @@ TEST(module_test, vformat_to_n) { fmt::vformat_to_n(std::back_inserter(s), 1, "{}", store); char buffer[4] = {0}; fmt::vformat_to_n(buffer, 3, "{:}", store); - - auto wstore = fmt::make_wformat_args(n); - std::wstring w; - wchar_t wbuffer[4] = {0}; - fmt::vformat_to_n(wbuffer, 3, fmt::wstring_view(L"{:}"), wstore); } std::string as_string(std::wstring_view text) { @@ -251,11 +191,6 @@ TEST(module_test, locale) { fmt::vformat_to(std::back_inserter(s), classic, "{:L}", store); EXPECT_EQ("4.2", s); EXPECT_EQ("4.2", fmt::format("{:L}", 4.2)); - - auto wstore = fmt::make_wformat_args(m); - EXPECT_EQ(L"4.2", fmt::format(classic, L"{:L}", 4.2)); - EXPECT_EQ(L"4.2", fmt::vformat(classic, L"{:L}", wstore)); - EXPECT_EQ(L"4.2", fmt::format(L"{:L}", 4.2)); } TEST(module_test, string_view) { @@ -385,23 +320,6 @@ TEST(module_test, sprintf) { EXPECT_EQ("42", fmt::sprintf("%d", 42)); } -TEST(module_test, vfprintf) { - int n = 42; - auto store = fmt::make_format_args(n); - auto args = fmt::basic_format_args(store); - EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args), - "42"); -} - -TEST(module_test, vsprintf) { - int n = 42; - auto store = fmt::make_format_args(n); - auto args = fmt::basic_format_args(store); - EXPECT_EQ(fmt::vsprintf(fmt::string_view("%d"), args), "42"); - EXPECT_WRITE(stdout, fmt::vfprintf(stdout, fmt::string_view("%d"), args), - "42"); -} - TEST(module_test, color) { EXPECT_EQ("\x1B[30m42\x1B[0m", fmt::format(fg(fmt::terminal_color::black), "{}", 42)); From 2cb39832132a5c56a802bc817179e85d5f32fb9c Mon Sep 17 00:00:00 2001 From: Powerbyte7 <40431794+Powerbyte7@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:42:35 +0200 Subject: [PATCH 018/111] Update link anchor (#4748) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 040d17cb..f9ee8753 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v). [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm - Portable Unicode support - Safe [printf - implementation](https://fmt.dev/latest/api/#printf-formatting) + implementation](https://fmt.dev/latest/api/#printf-api) including the POSIX extension for positional arguments - Extensibility: [support for user-defined types](https://fmt.dev/latest/api/#formatting-user-defined-types) From 4b50ad794422c6ecbf773141a09592fd9061a6fb Mon Sep 17 00:00:00 2001 From: st0rmbtw <61053971+st0rmbtw@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:29:42 +0300 Subject: [PATCH 019/111] Replace `try` and `catch` with the `FMT_TRY` and `FMT_CATCH` macros (#4751) --- src/fmt-c.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fmt-c.cc b/src/fmt-c.cc index cb7dcc1c..5c4f4e97 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -31,12 +31,12 @@ extern "C" int fmt_vformat(char* buffer, size_t size, const char* fmt, default: return fmt_error_invalid_arg; } } - try { + FMT_TRY { auto result = fmt::vformat_to_n( buffer, size, fmt, fmt::format_args(format_args, static_cast(num_args))); return static_cast(result.size); - } catch (...) { } + FMT_CATCH(...) {} return fmt_error; } From eeff8680ed900933c28df7c618ca3e11d6a766ec Mon Sep 17 00:00:00 2001 From: user202729 <25191436+user202729@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:34:27 +0800 Subject: [PATCH 020/111] Avoid dropping null byte in format string (#4732) * Avoid dropping null byte in format string * Add a test for null byte in format string --- include/fmt/base.h | 6 +++--- test/compile-error-test/CMakeLists.txt | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 49868b6d..be7a40d5 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -2690,16 +2690,16 @@ template struct fstring { static_assert(count<(is_view>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); - if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); + if (FMT_USE_CONSTEVAL) + parse_format_string(str, checker(str, arg_pack())); constexpr bool unused = detail::enforce_compile_checks(); (void)unused; } template ::value)> FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { - auto sv = string_view(str); if (FMT_USE_CONSTEVAL) - detail::parse_format_string(sv, checker(sv, arg_pack())); + detail::parse_format_string(str, checker(str, arg_pack())); constexpr bool unused = detail::enforce_compile_checks(); (void)unused; } diff --git a/test/compile-error-test/CMakeLists.txt b/test/compile-error-test/CMakeLists.txt index 4725d333..5b57a2cf 100644 --- a/test/compile-error-test/CMakeLists.txt +++ b/test/compile-error-test/CMakeLists.txt @@ -239,6 +239,16 @@ if (CMAKE_CXX_STANDARD GREATER_EQUAL 20) #endif " ERROR) + expect_compile( + format-string-embedded-nul-error + " + #if FMT_USE_CONSTEVAL + fmt::format(\"a\\0{}\"); + #else + #error + #endif + " + ERROR) # Compile-time argument name check expect_compile( From 432fda7bfa0167403b8dd30d3cc6b43c9b80f468 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 29 Apr 2026 16:55:06 -0700 Subject: [PATCH 021/111] Fix false positive in tsan (fixes #4755) --- include/fmt/format-inl.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 539b1fd7..3e04ddf5 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -8,6 +8,11 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ +#ifdef __SANITIZE_THREAD__ +extern "C" void __tsan_acquire(void*); +extern "C" void __tsan_release(void*); +#endif + #ifndef FMT_MODULE # include // ptrdiff_t @@ -1696,6 +1701,9 @@ class file_print_buffer::value>> public: explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { flockfile(f); +#ifdef __SANITIZE_THREAD__ + __tsan_acquire(f); +#endif file_.init_buffer(); auto buf = file_.get_write_buffer(); set(buf.data, buf.size); @@ -1703,7 +1711,10 @@ class file_print_buffer::value>> ~file_print_buffer() { file_.advance_write_buffer(size()); bool flush = file_.needs_flush(); - F* f = file_; // Make funlockfile depend on the template parameter F + F* f = file_; // Make funlockfile depend on the template parameter F. +#ifdef __SANITIZE_THREAD__ + __tsan_release(f); +#endif funlockfile(f); // for the system API detection to work. if (flush) fflush(file_); } From d0e3d20af8f657090112c078d4f5d39ed7cbbd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ka=C4=9Fan=20Can=20=C5=9Eit?= Date: Thu, 30 Apr 2026 06:50:56 +0300 Subject: [PATCH 022/111] chore(IWYU):Remove redundant includes (#4758) --- include/fmt/format-inl.h | 5 +---- src/os.cc | 3 ++- test/format-impl-test.cc | 1 - test/posix-mock-test.cc | 4 ++-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 3e04ddf5..11a3cee7 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -18,10 +18,7 @@ extern "C" void __tsan_release(void*); # include # include // errno -# include -# include -# include -# include // std::bad_alloc +# include // std::bad_alloc #endif #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) diff --git a/src/os.cc b/src/os.cc index 409863a9..0e1d582a 100644 --- a/src/os.cc +++ b/src/os.cc @@ -13,7 +13,6 @@ #include "fmt/os.h" #ifndef FMT_MODULE -# include # if FMT_USE_FCNTL # include @@ -35,6 +34,8 @@ # ifdef _WIN32 # include + +# include // CHAR_BIT # endif #endif diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 4259add3..3c0a7d8b 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -13,7 +13,6 @@ #include "fmt/format.h" #include "gmock/gmock.h" -#include "util.h" using fmt::detail::bigint; using fmt::detail::fp; diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index bcd330d4..67d032c5 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -15,19 +15,19 @@ #include #include -#include #include #include "../src/os.cc" #ifdef _WIN32 # include + +# include // UINT_MAX # undef max #endif #include "gmock/gmock.h" #include "gtest-extra.h" -#include "util.h" using fmt::buffered_file; From 522b5091e509643a1f1a0ed160009be28e6a7d54 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 30 Apr 2026 07:57:20 -0700 Subject: [PATCH 023/111] Fix badge links in README --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f9ee8753..6dd5e997 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,19 @@ {fmt} -[![image](https://github.com/fmtlib/fmt/actions/workflows/linux.yml/badge.svg?branch=master)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux) -[![image](https://github.com/fmtlib/fmt/actions/workflows/macos.yml/badge.svg?branch=master)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos) -[![image](https://github.com/fmtlib/fmt/actions/workflows/windows.yml/badge.svg?branch=master)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows) -[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8880/badge)](https://www.bestpractices.dev/projects/8880) -[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) -[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) +[![image](https://github.com/fmtlib/fmt/actions/workflows/linux.yml/badge.svg?branch=master)]( +https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux) +[![image](https://github.com/fmtlib/fmt/actions/workflows/macos.yml/badge.svg?branch=master)]( +https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos) +[![image](https://github.com/fmtlib/fmt/actions/workflows/windows.yml/badge.svg?branch=master)]( +https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows) +[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)]( +https://issues.oss-fuzz.com/issues?q=title:fmt%20cc:victor.zverovich@gmail.com) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8880/badge)]( +https://www.bestpractices.dev/projects/8880) +[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)]( +https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) +[![Ask questions at StackOverflow with the tag fmt]( +https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) **{fmt}** is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams. From 8f20ee6191a8b1b00ff387e484b751d8abeb3ef8 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 30 Apr 2026 08:00:41 -0700 Subject: [PATCH 024/111] Correct image link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dd5e997..bfe7252f 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ int main() { Output on a modern terminal with Unicode support: -![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) +![image](https://github.com/fmtlib/fmt/assets/576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7) # Benchmarks From 7aabdcf0ba7336c471498ea072ab8a93da0c6e4a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 1 May 2026 13:24:30 -0700 Subject: [PATCH 025/111] Workaround apt-add-repository timeouts --- .github/workflows/linux.yml | 13 ++++--------- CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index da74465e..5cda5161 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -12,7 +12,7 @@ jobs: ${{matrix.gen}} ${{matrix.fuzz && 'Fuzz' || ''}} ${{matrix.shared && 'Shared' || ''}} ${{matrix.cxxflags_extra && 'Sanitize' || ''}} - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} strategy: matrix: cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11] @@ -35,12 +35,12 @@ jobs: - cxx: g++-13 build_type: Release std: 23 - install: sudo apt install g++-13 + os: ubuntu-24.04 shared: -DBUILD_SHARED_LIBS=ON - - cxx: g++-15 + - cxx: g++-14 build_type: Release std: 23 - install: sudo apt install g++-15 + os: ubuntu-24.04 gen: Ninja - cxx: clang++-3.6 - cxx: clang++-11 @@ -144,11 +144,6 @@ jobs: clang-3.6_3.6.2-3ubuntu2_amd64.deb if: ${{ matrix.cxx == 'clang++-3.6' }} - - name: Add repositories for newer GCC - run: | - sudo apt-add-repository ppa:ubuntu-toolchain-r/test - if: ${{ matrix.cxx == 'g++-13' || matrix.cxx == 'g++-15' }} - - name: Install LLVM-20 run: | wget https://apt.llvm.org/llvm.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index cc47c96e..1045f3ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.28 AND CMAKE_CXX_STANDARD OUTPUT_VARIABLE NINJA_VERSION) if (NINJA_VERSION VERSION_GREATER_EQUAL 1.11) if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION - GREATER_EQUAL 14) + GREATER_EQUAL 15) OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL 16) OR (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION From 5a9a184448b6db8a7d037ed5e5ff51258e96d49a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 3 May 2026 07:33:53 -0700 Subject: [PATCH 026/111] Apply clang-format --- include/fmt/format-inl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 11a3cee7..906763ba 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1708,7 +1708,7 @@ class file_print_buffer::value>> ~file_print_buffer() { file_.advance_write_buffer(size()); bool flush = file_.needs_flush(); - F* f = file_; // Make funlockfile depend on the template parameter F. + F* f = file_; // Make funlockfile depend on the template parameter F. #ifdef __SANITIZE_THREAD__ __tsan_release(f); #endif From 9cb8c0f92b4c345fb974a75d71370c23047528aa Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 3 May 2026 19:37:46 +0500 Subject: [PATCH 027/111] Fix ambiguous formatter> between fmt/std.h and fmt/ranges.h on C++26 (P3168R2) (#4761) Signed-off-by: Vladislav Shchapov --- include/fmt/ranges.h | 12 ++++++++++-- test/ranges-test.cc | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index d7dbc168..6ce57f5e 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -129,6 +129,13 @@ template class is_tuple_like_ { !std::is_void(nullptr))>::value; }; +template +struct is_optional_like_ : std::false_type {}; +template +struct is_optional_like_().has_value()), + decltype(std::declval().value())>> + : std::true_type {}; + // Check for integer_sequence #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 template @@ -343,8 +350,9 @@ struct formatter struct is_range { - static constexpr bool value = - detail::is_range_::value && !detail::has_to_string_view::value; + static constexpr bool value = detail::is_range_::value && + !detail::is_optional_like_::value && + !detail::has_to_string_view::value; }; namespace detail { diff --git a/test/ranges-test.cc b/test/ranges-test.cc index bd161d75..4afd6c72 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -271,6 +271,23 @@ TEST(ranges_test, disabled_range_formatting_of_path) { fmt::range_format::disabled); } +template struct optional_like { + auto begin() const -> const T*; + auto end() const -> const T*; + + bool has_value() const noexcept; + T& value() &; + const T& value() const&; + T&& value() &&; + const T&& value() const&&; +}; + +TEST(ranges_test, disabled_range_formatting_of_optional) { + // (C++26) Has a range support for std::optional. + EXPECT_EQ((fmt::range_format_kind, char>::value), + fmt::range_format::disabled); +} + struct vector_string : std::vector { using base = std::vector; using base::base; From 2f18a88e68af42bdda0e5210f80fac07ae756bd6 Mon Sep 17 00:00:00 2001 From: theflashwin Date: Tue, 5 May 2026 16:03:05 -0400 Subject: [PATCH 028/111] fix GCC PCH Breakage Triggered by fmt Scoped #pragma GCC optimize(0g) --- include/fmt/base.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index be7a40d5..9e55ba1e 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -224,9 +224,14 @@ # define FMT_PRAGMA_CLANG(x) #endif +#ifndef FMT_USE_OPTIMIZE_PRAGMA +# define FMT_USE_OPTIMIZE_PRAGMA 1 +#endif + // Enable minimal optimizations for more compact code in debug mode. FMT_PRAGMA_GCC(push_options) -#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) +#if FMT_USE_OPTIMIZE_PRAGMA && !defined(__OPTIMIZE__) && \ + !defined(__CUDACC__) && !defined(FMT_MODULE) FMT_PRAGMA_GCC(optimize("Og")) #endif From a30cf7d1adf074628c4ae765999e3ef95895843d Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Thu, 21 May 2026 19:19:19 +0500 Subject: [PATCH 029/111] Provide reasonable default definition for is_contiguous (#4770) Signed-off-by: Vladislav Shchapov --- include/fmt/base.h | 15 ++++++++++++++- test/base-test.cc | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 9e55ba1e..78a2093e 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -593,7 +593,20 @@ template class basic_appender; using appender = basic_appender; // Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; +namespace detail { + +template +struct is_contiguous_ : std::false_type {}; + +template +struct is_contiguous_().data()), + decltype(std::declval().size()), + decltype(std::declval()[size_t{}])>> + : std::true_type {}; + +} // namespace detail + +template struct is_contiguous : detail::is_contiguous_ {}; class context; template class generic_context; diff --git a/test/base-test.cc b/test/base-test.cc index 9575e055..dc62eea8 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -21,8 +21,10 @@ #include // std::equal_to #include // std::back_insert_iterator, std::distance #include // std::numeric_limits +#include // std::list #include // std::string #include // std::is_same +#include // std::vector #include "gmock/gmock.h" @@ -876,6 +878,14 @@ FMT_BEGIN_NAMESPACE template <> struct is_contiguous : std::true_type {}; FMT_END_NAMESPACE +TEST(base_test, is_contiguous) { + EXPECT_TRUE((fmt::is_contiguous::value)); + EXPECT_TRUE((fmt::is_contiguous::value)); + EXPECT_TRUE((fmt::is_contiguous::value)); + EXPECT_TRUE((fmt::is_contiguous>::value)); + EXPECT_FALSE((fmt::is_contiguous>::value)); +} + TEST(base_test, format_to_custom_container) { auto c = custom_container(); fmt::format_to(std::back_inserter(c), ""); From 93e26fa578712891ee0a5302e7563e0ba79efef9 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 22 May 2026 08:04:50 -0700 Subject: [PATCH 030/111] Cleanup is_contiguous --- include/fmt/base.h | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 78a2093e..c4a80616 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -489,6 +489,14 @@ inline FMT_CONSTEXPR auto get_container(OutputIt it) -> }; return *accessor(it).container; } + +template +struct is_contiguous : std::false_type {}; +template +struct is_contiguous().data()), + decltype(std::declval().size()), + decltype(std::declval()[size_t()])>> + : std::true_type {}; } // namespace detail // Parsing-related public API and forward declarations. @@ -592,22 +600,6 @@ using string_view = basic_string_view; template class basic_appender; using appender = basic_appender; -// Checks whether T is a container with contiguous storage. -namespace detail { - -template -struct is_contiguous_ : std::false_type {}; - -template -struct is_contiguous_().data()), - decltype(std::declval().size()), - decltype(std::declval()[size_t{}])>> - : std::true_type {}; - -} // namespace detail - -template struct is_contiguous : detail::is_contiguous_ {}; - class context; template class generic_context; template class parse_context; @@ -626,6 +618,8 @@ using buffered_context = conditional_t::value, context, generic_context, Char>>; +template struct is_contiguous : detail::is_contiguous {}; + template class basic_format_arg; template class basic_format_args; From 0acf106c52f5c7f068ce6313f2ca310c7d5e8b63 Mon Sep 17 00:00:00 2001 From: ahoarau <703240+ahoarau@users.noreply.github.com> Date: Mon, 25 May 2026 17:30:36 +0200 Subject: [PATCH 031/111] Add println functions for formatted output with color support (#4782) --- include/fmt/color.h | 36 ++++++++++++++++++++++++++++++++++++ test/color-test.cc | 12 ++++++++++++ 2 files changed, 48 insertions(+) diff --git a/include/fmt/color.h b/include/fmt/color.h index 153784f5..7d809e60 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -528,6 +528,42 @@ void print(text_style ts, format_string fmt, T&&... args) { return print(stdout, ts, fmt, std::forward(args)...); } +inline void vprintln(FILE* f, text_style ts, string_view fmt, + format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + buf.push_back('\n'); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); +} + +/** + * Formats a string and prints it to the specified file stream followed by a + * newline, using ANSI escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::println(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void println(FILE* f, text_style ts, format_string fmt, T&&... args) { + vprintln(f, ts, fmt.str, vargs{{args...}}); +} + +/** + * Formats a string and prints it to stdout followed by a newline, using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::println(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void println(text_style ts, format_string fmt, T&&... args) { + return println(stdout, ts, fmt, std::forward(args)...); +} + inline auto vformat(text_style ts, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); diff --git a/test/color-test.cc b/test/color-test.cc index 45d8cd92..67ce1833 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -136,3 +136,15 @@ TEST(color_test, print) { EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); } + +TEST(color_test, println) { + EXPECT_WRITE(stdout, + fmt::println(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), + "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m\n"); + EXPECT_WRITE(stdout, fmt::println(fmt::emphasis::bold, "bold"), + "\x1b[1mbold\x1b[0m\n"); + EXPECT_WRITE( + stdout, + fmt::println(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"), + "\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m\n"); +} From 029d543768abdc14a94360446597d0f5bc7350aa Mon Sep 17 00:00:00 2001 From: j4niwzis Date: Sun, 31 May 2026 15:21:35 +0000 Subject: [PATCH 032/111] Fix compile time format for ranges, style, and std (#4759) --- include/fmt/color.h | 6 +++--- include/fmt/format.h | 2 +- include/fmt/ranges.h | 34 ++++++++++++++++++++-------------- include/fmt/std.h | 15 ++++++++------- test/compile-test.cc | 17 ++++++++++++++++- 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/include/fmt/color.h b/include/fmt/color.h index 7d809e60..889b7947 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -471,7 +471,7 @@ template inline void reset_color(buffer& buffer) { template struct styled_arg : view { const T& value; text_style style; - styled_arg(const T& v, text_style s) : value(v), style(s) {} + FMT_CONSTEXPR styled_arg(const T& v, text_style s) : value(v), style(s) {} }; template @@ -619,8 +619,8 @@ inline auto format_to(OutputIt out, text_style ts, format_string fmt, template struct formatter, Char> : formatter { template - auto format(const detail::styled_arg& arg, FormatContext& ctx) const - -> decltype(ctx.out()) { + FMT_CONSTEXPR auto format(const detail::styled_arg& arg, + FormatContext& ctx) const -> decltype(ctx.out()) { const auto& ts = arg.style; auto out = ctx.out(); diff --git a/include/fmt/format.h b/include/fmt/format.h index 256a99ab..f39a6512 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3888,7 +3888,7 @@ template class generic_context { constexpr auto out() const -> iterator { return out_; } - void advance_to(iterator it) { + FMT_CONSTEXPR void advance_to(iterator it) { if (!detail::is_back_insert_iterator()) out_ = it; } diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 6ce57f5e..99bef841 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -69,11 +69,12 @@ struct has_member_fn_begin_end_t().begin()), // Member function overloads. template -auto range_begin(T&& rng) -> decltype(static_cast(rng).begin()) { +FMT_CONSTEXPR auto range_begin(T&& rng) + -> decltype(static_cast(rng).begin()) { return static_cast(rng).begin(); } template -auto range_end(T&& rng) -> decltype(static_cast(rng).end()) { +FMT_CONSTEXPR auto range_end(T&& rng) -> decltype(static_cast(rng).end()) { return static_cast(rng).end(); } @@ -468,7 +469,8 @@ struct range_formatter< } template - auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { + FMT_CONSTEXPR auto format(R&& range, FormatContext& ctx) const + -> decltype(ctx.out()) { auto out = ctx.out(); auto it = detail::range_begin(range); auto end = detail::range_end(range); @@ -524,7 +526,7 @@ struct formatter< } template - auto format(range_type& range, FormatContext& ctx) const + FMT_CONSTEXPR auto format(range_type& range, FormatContext& ctx) const -> decltype(ctx.out()) { return range_formatter_.format(range, ctx); } @@ -633,7 +635,7 @@ struct join_view : detail::view { Sentinel end; basic_string_view sep; - join_view(It b, Sentinel e, basic_string_view s) + FMT_CONSTEXPR join_view(It b, Sentinel e, basic_string_view s) : begin(std::move(b)), end(e), sep(s) {} }; @@ -660,7 +662,8 @@ struct formatter, Char> { } template - auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) { + FMT_CONSTEXPR auto format(view& value, FormatContext& ctx) const + -> decltype(ctx.out()) { using iter = conditional_t::value, It, It&>; iter it = value.begin; @@ -683,7 +686,7 @@ template struct tuple_join_view : detail::view { const Tuple& tuple; basic_string_view sep; - tuple_join_view(const Tuple& t, basic_string_view s) + FMT_CONSTEXPR tuple_join_view(const Tuple& t, basic_string_view s) : tuple(t), sep{s} {} }; @@ -702,8 +705,9 @@ struct formatter, Char, } template - auto format(const tuple_join_view& value, - FormatContext& ctx) const -> typename FormatContext::iterator { + FMT_CONSTEXPR auto format(const tuple_join_view& value, + FormatContext& ctx) const -> + typename FormatContext::iterator { return do_format(value, ctx, std::tuple_size()); } @@ -734,15 +738,17 @@ struct formatter, Char, } template - auto do_format(const tuple_join_view&, FormatContext& ctx, - std::integral_constant) const -> + FMT_CONSTEXPR auto do_format(const tuple_join_view&, + FormatContext& ctx, + std::integral_constant) const -> typename FormatContext::iterator { return ctx.out(); } template - auto do_format(const tuple_join_view& value, FormatContext& ctx, - std::integral_constant) const -> + FMT_CONSTEXPR auto do_format(const tuple_join_view& value, + FormatContext& ctx, + std::integral_constant) const -> typename FormatContext::iterator { using std::get; auto out = @@ -821,7 +827,7 @@ auto join(It begin, Sentinel end, string_view sep) -> join_view { * // Output: 01, 02, 03 */ template ::value)> -auto join(Range&& r, string_view sep) +FMT_CONSTEXPR auto join(Range&& r, string_view sep) -> join_view { return {detail::range_begin(r), detail::range_end(r), sep}; diff --git a/include/fmt/std.h b/include/fmt/std.h index b6b98bb7..dfde1fb7 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -133,8 +133,8 @@ void write_escaped_path(basic_memory_buffer& quoted, #if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT template -auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx) - -> OutputIt { +FMT_CONSTEXPR auto write_escaped_alternative(OutputIt out, const T& v, + FormatContext& ctx) -> OutputIt { if constexpr (has_to_string_view::value) return write_escaped_string(out, detail::to_string_view(v)); if constexpr (std::is_same_v) return write_escaped_char(out, v); @@ -508,7 +508,7 @@ template struct formatter { } template - auto format(const std::monostate&, FormatContext& ctx) const + FMT_CONSTEXPR auto format(const std::monostate&, FormatContext& ctx) const -> decltype(ctx.out()) { return detail::write(ctx.out(), "monostate"); } @@ -524,7 +524,7 @@ struct formatter - auto format(const Variant& value, FormatContext& ctx) const + FMT_CONSTEXPR20 auto format(const Variant& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); @@ -635,7 +635,7 @@ struct formatter< } template - auto format(const std::exception& ex, Context& ctx) const + FMT_CONSTEXPR auto format(const std::exception& ex, Context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); #if FMT_USE_RTTI @@ -690,11 +690,12 @@ struct formatter struct formatter : formatter { - static auto format_as(std::byte b) -> unsigned char { + FMT_CONSTEXPR static auto format_as(std::byte b) -> unsigned char { return static_cast(b); } template - auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { + FMT_CONSTEXPR auto format(std::byte b, Context& ctx) const + -> decltype(ctx.out()) { return formatter::format(format_as(b), ctx); } }; diff --git a/test/compile-test.cc b/test/compile-test.cc index 9975b441..fd95f63f 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -7,13 +7,16 @@ #include "fmt/compile.h" +#include #include #include #include #include #include "fmt/chrono.h" +#include "fmt/color.h" #include "fmt/ranges.h" +#include "fmt/std.h" #include "gmock/gmock.h" #include "gtest-extra.h" @@ -87,7 +90,6 @@ TEST(compile_test, format_escape) { EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc")); } - TEST(compile_test, format_specs) { EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("1.2 ms ", @@ -224,6 +226,19 @@ TEST(compile_test, constexpr_formatted_size) { FMT_CONSTEXPR20 size_t str_size = fmt::formatted_size(FMT_COMPILE("{:s}"), "abc"); EXPECT_EQ(str_size, 3); + FMT_CONSTEXPR20 size_t tuple_size = fmt::formatted_size( + FMT_COMPILE("{}"), fmt::join(std::tuple(1, 2, 3), ",")); + EXPECT_EQ(tuple_size, 5); + FMT_CONSTEXPR20 size_t array_size = fmt::formatted_size( + FMT_COMPILE("{}"), fmt::join(std::array{1, 2, 3}, ",")); + EXPECT_EQ(array_size, 5); + FMT_CONSTEXPR20 size_t styled_size = fmt::formatted_size( + FMT_COMPILE("{}"), + fmt::styled(std::array{1, 2, 3}, fmt::bg(fmt::color::green))); + EXPECT_EQ(styled_size, 32); + FMT_CONSTEXPR20 size_t variant_size = fmt::formatted_size( + FMT_COMPILE("{}"), std::variant{}); + EXPECT_EQ(variant_size, 18); } TEST(compile_test, static_format) { From a823ba06994ae1a843d0fe0c7650f119e07a7e3e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 08:24:38 -0700 Subject: [PATCH 033/111] Deconstexprify exception formatter --- include/fmt/std.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index dfde1fb7..6954df16 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -635,7 +635,7 @@ struct formatter< } template - FMT_CONSTEXPR auto format(const std::exception& ex, Context& ctx) const + auto format(const std::exception& ex, Context& ctx) const -> decltype(ctx.out()) { auto out = ctx.out(); #if FMT_USE_RTTI From 46cf422f54dcc21d3805fb7bbec7777395eb6d0e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 08:54:06 -0700 Subject: [PATCH 034/111] Unify copyright notices --- include/fmt/args.h | 2 +- include/fmt/base.h | 2 +- include/fmt/chrono.h | 2 +- include/fmt/color.h | 2 +- include/fmt/compile.h | 2 +- include/fmt/core.h | 7 +++++++ include/fmt/fmt-c.h | 2 +- include/fmt/format-inl.h | 2 +- include/fmt/format.h | 2 +- include/fmt/os.h | 2 +- include/fmt/ostream.h | 2 +- include/fmt/printf.h | 2 +- include/fmt/std.h | 2 +- include/fmt/xchar.h | 2 +- src/fmt-c.cc | 2 +- src/fmt.cc | 7 +++++++ src/format.cc | 2 +- src/os.cc | 2 +- test/args-test.cc | 2 +- test/assert-test.cc | 2 +- test/base-test.cc | 2 +- test/c-test.c | 2 +- test/chrono-test.cc | 2 +- test/color-test.cc | 2 +- test/compile-test.cc | 2 +- test/detect-stdfs.cc | 2 +- test/enforce-checks-test.cc | 2 +- test/format-impl-test.cc | 2 +- test/format-test.cc | 2 +- test/gtest-extra-test.cc | 2 +- test/gtest-extra.cc | 2 +- test/gtest-extra.h | 2 +- test/mock-allocator.h | 2 +- test/module-test.cc | 6 +----- test/no-builtin-types-test.cc | 2 +- test/noexception-test.cc | 2 +- test/os-test.cc | 2 +- test/ostream-test.cc | 2 +- test/posix-mock-test.cc | 2 +- test/posix-mock.h | 2 +- test/printf-test.cc | 2 +- test/ranges-odr-test.cc | 2 +- test/scan-test.cc | 2 +- test/scan.h | 2 +- test/std-test.cc | 2 +- test/test-assert.h | 2 +- test/test-main.cc | 2 +- test/unicode-test.cc | 2 +- test/util.cc | 2 +- test/util.h | 2 +- test/xchar-test.cc | 2 +- 51 files changed, 63 insertions(+), 53 deletions(-) diff --git a/include/fmt/args.h b/include/fmt/args.h index 7b04ef0d..3283e6fa 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -1,6 +1,6 @@ // Formatting library for C++ - dynamic argument lists // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/base.h b/include/fmt/base.h index c4a80616..bcd06319 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1,6 +1,6 @@ // Formatting library for C++ - the base API for char/UTF-8 // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 752b9699..517f9576 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1,6 +1,6 @@ // Formatting library for C++ - chrono support // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/color.h b/include/fmt/color.h index 889b7947..bf0c32d6 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -1,6 +1,6 @@ // Formatting library for C++ - color support // -// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// Copyright (c) 2018 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 5c6820f7..1a987cb0 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -1,6 +1,6 @@ // Formatting library for C++ - experimental format string compilation // -// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/core.h b/include/fmt/core.h index f1490a9f..eff2ae89 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1,3 +1,10 @@ +// Formatting library for C++ - core API +// +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors +// All rights reserved. +// +// For the license information refer to format.h. + #include "base.h" // Using fmt::format via fmt/core.h has been deprecated since version 11 diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index ce878fdc..20d163a7 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -1,6 +1,6 @@ // Formatting library for C++ - the C API // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 906763ba..665f7308 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -1,6 +1,6 @@ // Formatting library for C++ - implementation // -// Copyright (c) 2012 - 2016, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/format.h b/include/fmt/format.h index f39a6512..5044befd 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1,7 +1,7 @@ /* Formatting library for C++ - Copyright (c) 2012 - present, Victor Zverovich + Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/include/fmt/os.h b/include/fmt/os.h index 3fbd6905..a3ee58f3 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -1,6 +1,6 @@ // Formatting library for C++ - optional OS-specific functionality // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index a139e825..da9c65e0 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -1,6 +1,6 @@ // Formatting library for C++ - std::ostream support // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 235dbb06..8d792fbd 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -1,6 +1,6 @@ // Formatting library for C++ - legacy printf implementation // -// Copyright (c) 2012 - 2016, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/std.h b/include/fmt/std.h index 6954df16..f7d8d495 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -1,6 +1,6 @@ // Formatting library for C++ - formatters for standard library types // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 6d95a737..9631372a 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -1,6 +1,6 @@ // Formatting library for C++ - optional wchar_t and exotic character support // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/src/fmt-c.cc b/src/fmt-c.cc index 5c4f4e97..86de48e9 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - the C API // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/src/fmt.cc b/src/fmt.cc index 1973a1ac..f319ce94 100644 --- a/src/fmt.cc +++ b/src/fmt.cc @@ -1,3 +1,10 @@ +// Formatting library for C++ - C++20 module +// +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors +// All rights reserved. +// +// For the license information refer to format.h. + module; #define FMT_MODULE diff --git a/src/format.cc b/src/format.cc index 526082e3..298c5b4b 100644 --- a/src/format.cc +++ b/src/format.cc @@ -1,6 +1,6 @@ // Formatting library for C++ // -// Copyright (c) 2012 - 2016, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/src/os.cc b/src/os.cc index 0e1d582a..4840a658 100644 --- a/src/os.cc +++ b/src/os.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - optional OS-specific functionality // -// Copyright (c) 2012 - 2016, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/args-test.cc b/test/args-test.cc index dd0e57dc..8e9f53e5 100644 --- a/test/args-test.cc +++ b/test/args-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - dynamic argument store tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/assert-test.cc b/test/assert-test.cc index 368fb587..e0588184 100644 --- a/test/assert-test.cc +++ b/test/assert-test.cc @@ -4,7 +4,7 @@ // which are slow on some platforms. In other tests FMT_ASSERT is made to throw // an exception which is much faster and easier to check. // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/base-test.cc b/test/base-test.cc index dc62eea8..40cc3810 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - core tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/c-test.c b/test/c-test.c index 71056841..81c9bdfb 100644 --- a/test/c-test.c +++ b/test/c-test.c @@ -1,6 +1,6 @@ // Formatting library for C++ - the C API tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 4dee7c47..7b4bf402 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - time formatting tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/color-test.cc b/test/color-test.cc index 67ce1833..1ff5235a 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - color tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/compile-test.cc b/test/compile-test.cc index fd95f63f..5a944612 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - formatting library tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/detect-stdfs.cc b/test/detect-stdfs.cc index 2dc66538..ae3cdab5 100644 --- a/test/detect-stdfs.cc +++ b/test/detect-stdfs.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - tests of formatters for standard library types // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/enforce-checks-test.cc b/test/enforce-checks-test.cc index 960a7fcd..f513d828 100644 --- a/test/enforce-checks-test.cc +++ b/test/enforce-checks-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - formatting library tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 3c0a7d8b..eef041e4 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - formatting library implementation tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/format-test.cc b/test/format-test.cc index cd07c26a..61a53c72 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - formatting library tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 8e4401b8..0f47dc98 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - tests of custom Google Test assertions // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index 0f9fff5e..d819ec3c 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - custom Google Test assertions // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 71859835..7f5a9fe6 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -1,6 +1,6 @@ // Formatting library for C++ - custom Google Test assertions // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/mock-allocator.h b/test/mock-allocator.h index 0bf015a4..254849ee 100644 --- a/test/mock-allocator.h +++ b/test/mock-allocator.h @@ -1,6 +1,6 @@ // Formatting library for C++ - mock allocator // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/module-test.cc b/test/module-test.cc index d84e66b8..a7b67a4a 100644 --- a/test/module-test.cc +++ b/test/module-test.cc @@ -1,13 +1,9 @@ // Formatting library for C++ - module tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. -// -// Copyright (c) 2021 - present, Daniela Engert -// All Rights Reserved -// {fmt} module. #include #include diff --git a/test/no-builtin-types-test.cc b/test/no-builtin-types-test.cc index 1780220a..13f84151 100644 --- a/test/no-builtin-types-test.cc +++ b/test/no-builtin-types-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - formatting library tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/noexception-test.cc b/test/noexception-test.cc index 0e44e13b..2bf9605b 100644 --- a/test/noexception-test.cc +++ b/test/noexception-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - Noexception tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/os-test.cc b/test/os-test.cc index 5ece0eb7..5bc9f7eb 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - tests of the OS-specific functionality // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/ostream-test.cc b/test/ostream-test.cc index fc38c4c6..01fb4371 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - std::ostream support tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc index 67d032c5..15b173a0 100644 --- a/test/posix-mock-test.cc +++ b/test/posix-mock-test.cc @@ -1,6 +1,6 @@ // Tests of the C++ interface to POSIX functions that require mocks // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/posix-mock.h b/test/posix-mock.h index 54580871..0bcb9f52 100644 --- a/test/posix-mock.h +++ b/test/posix-mock.h @@ -1,6 +1,6 @@ // Formatting library for C++ - mocks of POSIX functions // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/printf-test.cc b/test/printf-test.cc index b1478526..f2d2a141 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - printf tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/ranges-odr-test.cc b/test/ranges-odr-test.cc index 6115dcb7..9be56243 100644 --- a/test/ranges-odr-test.cc +++ b/test/ranges-odr-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - the core API // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/scan-test.cc b/test/scan-test.cc index e0b7e2bc..2c6f29e0 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - scanning API test // -// Copyright (c) 2019 - present, Victor Zverovich +// Copyright (c) 2019 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/scan.h b/test/scan.h index 257ac8d7..951410f1 100644 --- a/test/scan.h +++ b/test/scan.h @@ -1,6 +1,6 @@ // Formatting library for C++ - scanning API proof of concept // -// Copyright (c) 2019 - present, Victor Zverovich +// Copyright (c) 2019 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/std-test.cc b/test/std-test.cc index 1684a5ed..f6fc1284 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - tests of formatters for standard library types // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/test-assert.h b/test/test-assert.h index bec3dfc3..787780b1 100644 --- a/test/test-assert.h +++ b/test/test-assert.h @@ -1,6 +1,6 @@ // Formatting library for C++ - test version of FMT_ASSERT // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/test-main.cc b/test/test-main.cc index 39d2789d..496fcd8b 100644 --- a/test/test-main.cc +++ b/test/test-main.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - test main function. // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/unicode-test.cc b/test/unicode-test.cc index 2b59cff9..58c5d03e 100644 --- a/test/unicode-test.cc +++ b/test/unicode-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - Unicode tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/util.cc b/test/util.cc index 8ea26610..1e2fa77d 100644 --- a/test/util.cc +++ b/test/util.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - test utilities // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/util.h b/test/util.h index 1f2916a9..b02ceab1 100644 --- a/test/util.h +++ b/test/util.h @@ -1,6 +1,6 @@ // Formatting library for C++ - test utilities // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. diff --git a/test/xchar-test.cc b/test/xchar-test.cc index ec82d425..23be4bed 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -1,6 +1,6 @@ // Formatting library for C++ - formatting library tests // -// Copyright (c) 2012 - present, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors // All rights reserved. // // For the license information refer to format.h. From 2a24d1ae2e595db3d47fffee374c620e47e35d46 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 09:08:03 -0700 Subject: [PATCH 035/111] Add a temporary workflow to backfill SLSA provenance for existing releases --- .../workflows/backfill-slsa-provenance.yml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/backfill-slsa-provenance.yml diff --git a/.github/workflows/backfill-slsa-provenance.yml b/.github/workflows/backfill-slsa-provenance.yml new file mode 100644 index 00000000..17bb2754 --- /dev/null +++ b/.github/workflows/backfill-slsa-provenance.yml @@ -0,0 +1,56 @@ +# One-shot workflow to retrofit SLSA provenance onto previously published +# releases. Trigger via "Run workflow" with the existing release tag. +# +# This is intended to be deleted (along with its branch) once the last five +# non-source-only releases each carry a *.intoto.jsonl asset. New releases +# should produce provenance through the regular release workflow rather than +# through this one. + +name: backfill-slsa-provenance + +on: + workflow_dispatch: + inputs: + tag: + description: "Existing release tag (e.g. 11.1.4)" + required: true + type: string + +permissions: read-all + +jobs: + hash: + name: Hash existing release assets + runs-on: ubuntu-latest + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + steps: + - name: Download release assets + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir dist + cd dist + gh release download "${{ inputs.tag }}" \ + --repo "${{ github.repository }}" \ + --pattern 'fmt-*.zip' + ls -la + + - name: Compute base64-encoded SHA-256 subjects + id: hash + working-directory: dist + run: | + echo "hashes=$(sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT" + + provenance: + needs: [hash] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.hash.outputs.hashes }} + provenance-name: "fmt-${{ inputs.tag }}.intoto.jsonl" + upload-assets: true + upload-tag-name: ${{ inputs.tag }} From 60c33842346447bfbf4fd9276e5ba2c197b166ed Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 10:14:06 -0700 Subject: [PATCH 036/111] Move support link to badge --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bfe7252f..c5ec813f 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,12 @@ https://www.bestpractices.dev/projects/8880) https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt) [![Ask questions at StackOverflow with the tag fmt]( https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt) +[![Support Ukraine]( +https://img.shields.io/badge/Support-Ukraine-005BBB?labelColor=FFD500)](https://novaukraine.org/) **{fmt}** is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams. -If you like this project, please consider donating to one of the funds -that help victims of the war in Ukraine: . - [Documentation](https://fmt.dev) [Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html) From cc4cee596eb3049652ca4cc781603a6f37c3d3f9 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 10:38:48 -0700 Subject: [PATCH 037/111] Update README.md --- README.md | 79 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index c5ec813f..ce4176d9 100644 --- a/README.md +++ b/README.md @@ -380,82 +380,11 @@ If you are aware of other projects using this library, please let me know by [email](mailto:victor.zverovich@gmail.com) or by submitting an [issue](https://github.com/fmtlib/fmt/issues). -# Motivation +# Sponsors -So why yet another formatting library? - -There are plenty of methods for doing this task, from standard ones like -the printf family of function and iostreams to Boost Format and -FastFormat libraries. The reason for creating a new library is that -every existing solution that I found either had serious issues or -didn\'t provide all the features I needed. - -## printf - -The good thing about `printf` is that it is pretty fast and readily -available being a part of the C standard library. The main drawback is -that it doesn\'t support user-defined types. `printf` also has safety -issues although they are somewhat mitigated with [\_\_attribute\_\_ -((format (printf, -\...))](https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html) in -GCC. There is a POSIX extension that adds positional arguments required -for -[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization) -to `printf` but it is not a part of C99 and may not be available on some -platforms. - -## iostreams - -The main issue with iostreams is best illustrated with an example: - -``` c++ -std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; -``` - -which is a lot of typing compared to printf: - -``` c++ -printf("%.2f\n", 1.23456); -``` - -Matthew Wilson, the author of FastFormat, called this \"chevron hell\". -iostreams don\'t support positional arguments by design. - -The good part is that iostreams support user-defined types and are safe -although error handling is awkward. - -## Boost Format - -This is a very powerful library that supports both `printf`-like format -strings and positional arguments. Its main drawback is performance. -According to various benchmarks, it is much slower than other methods -considered here. Boost Format also has excessive build times and severe -code bloat issues (see [Benchmarks](#benchmarks)). - -## FastFormat - -This is an interesting library that is fast, safe and has positional -arguments. However, it has significant limitations, citing its author: - -> Three features that have no hope of being accommodated within the -> current design are: -> -> - Leading zeros (or any other non-space padding) -> - Octal/hexadecimal encoding -> - Runtime width/alignment specification - -It is also quite big and has a heavy dependency, on STLSoft, which might be -too restrictive for use in some projects. - -## Boost Spirit.Karma - -This is not a formatting library but I decided to include it here for -completeness. As iostreams, it suffers from the problem of mixing -verbatim text with arguments. The library is pretty fast, but slower on -integer formatting than `fmt::format_to` with format string compilation -on Karma\'s own benchmark, see [Converting a hundred million integers to -strings per -second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html). +{fmt} development is supported by individual sponsors. If you find this +library useful, please consider [sponsoring its development on GitHub +Sponsors](https://github.com/sponsors/vitaut). # License From e743e0f7ea620d26f25347996d0e313ba31cd6c5 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 11:31:24 -0700 Subject: [PATCH 038/111] Merge Documentation License into License --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ce4176d9..6a6d586c 100644 --- a/README.md +++ b/README.md @@ -391,15 +391,12 @@ Sponsors](https://github.com/sponsors/vitaut). {fmt} is distributed under the MIT [license](https://github.com/fmtlib/fmt/blob/master/LICENSE). -# Documentation License - The [Format String Syntax](https://fmt.dev/latest/syntax/) section in the documentation is based on the one from Python [string module documentation](https://docs.python.org/3/library/string.html#module-string). For this reason, the documentation is distributed under the Python Software Foundation license available in [doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt). -It only applies if you distribute the documentation of {fmt}. # Maintainers From da081318af0520fcaff9d5d6230fb841963c5d6d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 15:01:24 -0700 Subject: [PATCH 039/111] Add workflow_dispatch trigger to scorecard.yml --- .github/workflows/scorecard.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index aad70861..d10763dc 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -4,6 +4,7 @@ name: Scorecard supply-chain security on: + workflow_dispatch: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: From d079860923ddd47d2a1c5c8eab60f9b7b2fd71a8 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 18:15:24 -0700 Subject: [PATCH 040/111] Remove a temporary workflow --- .../workflows/backfill-slsa-provenance.yml | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 .github/workflows/backfill-slsa-provenance.yml diff --git a/.github/workflows/backfill-slsa-provenance.yml b/.github/workflows/backfill-slsa-provenance.yml deleted file mode 100644 index 17bb2754..00000000 --- a/.github/workflows/backfill-slsa-provenance.yml +++ /dev/null @@ -1,56 +0,0 @@ -# One-shot workflow to retrofit SLSA provenance onto previously published -# releases. Trigger via "Run workflow" with the existing release tag. -# -# This is intended to be deleted (along with its branch) once the last five -# non-source-only releases each carry a *.intoto.jsonl asset. New releases -# should produce provenance through the regular release workflow rather than -# through this one. - -name: backfill-slsa-provenance - -on: - workflow_dispatch: - inputs: - tag: - description: "Existing release tag (e.g. 11.1.4)" - required: true - type: string - -permissions: read-all - -jobs: - hash: - name: Hash existing release assets - runs-on: ubuntu-latest - outputs: - hashes: ${{ steps.hash.outputs.hashes }} - steps: - - name: Download release assets - env: - GH_TOKEN: ${{ github.token }} - run: | - mkdir dist - cd dist - gh release download "${{ inputs.tag }}" \ - --repo "${{ github.repository }}" \ - --pattern 'fmt-*.zip' - ls -la - - - name: Compute base64-encoded SHA-256 subjects - id: hash - working-directory: dist - run: | - echo "hashes=$(sha256sum * | base64 -w0)" >> "$GITHUB_OUTPUT" - - provenance: - needs: [hash] - permissions: - actions: read - id-token: write - contents: write - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 - with: - base64-subjects: ${{ needs.hash.outputs.hashes }} - provenance-name: "fmt-${{ inputs.tag }}.intoto.jsonl" - upload-assets: true - upload-tag-name: ${{ inputs.tag }} From a9e889e16154e1fe8826f3c2ee13fc8fa8b4bde6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 09:37:40 -0700 Subject: [PATCH 041/111] Build release artifacts and SLSA provenance in CI Adds .github/workflows/release.yml, triggered on `release: created`, which: - checks out the release's target_commitish, - builds the source zip via CMake/CPack (`package_source`), - uploads the zip to the draft release, - calls slsa-framework/slsa-github-generator to produce a SLSA v1.0 *.intoto.jsonl provenance file and attach it to the same draft release. After CI completes, the draft has both the zip and the provenance attached, and the maintainer reviews and publishes as before. Updates support/release.py to stop building and uploading the zip locally; that work has moved to CI so the SLSA provenance attests to the actual build environment that produced the artifact, not to a hash observed after the fact. The script still builds docs locally because the subsequent mkdocs deploy step depends on them. --- .github/workflows/release.yml | 69 +++++++++++++++++++++++++++++++++++ support/release.py | 25 ++++--------- 2 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f10d8577 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,69 @@ +# Builds the release source package in CI when a draft release is created +# (typically via support/release.py), uploads the zip to that release, and +# attaches a SLSA v1.0 provenance attestation generated by the OpenSSF +# slsa-github-generator. The maintainer reviews the draft (which by then has +# both the zip and *.intoto.jsonl attached) and clicks Publish to finalize. +# +# This makes the provenance attest to the actual build that produced the +# artifact, rather than just attesting to a hash observed after the fact. + +name: release + +on: + release: + types: [created] + +permissions: read-all + +jobs: + build: + name: Build source package + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + hashes: ${{ steps.hash.outputs.hashes }} + package: ${{ steps.build.outputs.package }} + steps: + - name: Checkout the release ref + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + ref: ${{ github.event.release.target_commitish }} + persist-credentials: false + + - name: Build source zip via CPack + id: build + run: | + cmake -B build . + cmake --build build --target package_source + pkg=$(ls build/fmt-*.zip) + test -f "$pkg" + echo "package=$pkg" >> "$GITHUB_OUTPUT" + + - name: Compute base64-encoded SHA-256 subjects + id: hash + run: | + file="${{ steps.build.outputs.package }}" + subjects=$(cd "$(dirname "$file")" && sha256sum "$(basename "$file")") + echo "hashes=$(printf '%s' "$subjects" | base64 -w0)" >> "$GITHUB_OUTPUT" + + - name: Upload zip to the release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload "${{ github.event.release.tag_name }}" \ + "${{ steps.build.outputs.package }}" \ + --repo "${{ github.repository }}" --clobber + + provenance: + needs: [build] + permissions: + actions: read + id-token: write + contents: write + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0 + with: + base64-subjects: ${{ needs.build.outputs.hashes }} + provenance-name: "fmt-${{ github.event.release.tag_name }}.intoto.jsonl" + upload-assets: true + upload-tag-name: ${{ github.event.release.tag_name }} diff --git a/support/release.py b/support/release.py index 26de7f4f..caabf431 100755 --- a/support/release.py +++ b/support/release.py @@ -151,12 +151,17 @@ if __name__ == '__main__': fmt_repo.add(changelog) fmt_repo.commit('-m', 'Update version') - # Build the docs and package. + # Build the docs locally; the source zip is now built and attached to the + # release in CI by .github/workflows/release.yml, which also generates a + # SLSA provenance attestation for it. run = Runner(fmt_repo.dir) run('cmake', '.') - run('make', 'doc', 'package_source') + run('make', 'doc') - # Create a release on GitHub. + # Create a draft release on GitHub. The release workflow triggers on + # `release: created`, builds the source zip from `target_commitish`, and + # attaches the zip plus *.intoto.jsonl provenance to this draft. After + # reviewing the draft, the maintainer clicks Publish to finalize. fmt_repo.push('origin', 'release') auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} req = urllib.request.Request( @@ -169,20 +174,6 @@ if __name__ == '__main__': if response.status != 201: raise Exception(f'Failed to create a release ' + '{response.status} {response.reason}') - response_data = json.loads(response.read().decode('utf-8')) - id = response_data['id'] - - # Upload the package. - uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' - package = 'fmt-{}.zip'.format(version) - req = urllib.request.Request( - f'{uploads_url}/{id}/assets?name={package}', - headers={'Content-Type': 'application/zip'} | auth_headers, - data=open('build/fmt/' + package, 'rb').read(), method='POST') - with urllib.request.urlopen(req) as response: - if response.status != 201: - raise Exception(f'Failed to upload an asset ' - '{response.status} {response.reason}') short_version = '.'.join(version.split('.')[:-1]) check_call(['./mkdocs', 'deploy', short_version]) From 49780cabbe563c9a7515846fc115541255f41c33 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 19:10:01 -0700 Subject: [PATCH 042/111] Pin deps --- .github/workflows/lint.yml | 2 +- support/cmake-format-requirements.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 support/cmake-format-requirements.txt diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 87900ac9..87992008 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install cmake-format - run: pip install cmakelang + run: pip install --require-hashes -r support/cmake-format-requirements.txt - name: Run cmake-format run: | diff --git a/support/cmake-format-requirements.txt b/support/cmake-format-requirements.txt new file mode 100644 index 00000000..ee242a8c --- /dev/null +++ b/support/cmake-format-requirements.txt @@ -0,0 +1,6 @@ +cmakelang==0.6.13 \ + --hash=sha256:764b9467195c7c36453d60a829f30229720d26c7dffd41cb516b99bd9c7daf4e \ + --hash=sha256:03982e87b00654d024d73ef972d9d9bb0e5726cdb6b8a424a15661fb6278e67f +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 From 6a1d56c425454afa1027b3a3a43c19be7d13b4d7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 20:12:06 -0700 Subject: [PATCH 043/111] Pin doc deps --- .github/workflows/doc.yml | 2 +- support/doc-requirements.in | 4 + support/doc-requirements.txt | 502 +++++++++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 support/doc-requirements.in create mode 100644 support/doc-requirements.txt diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 7aa2c011..fec5dffb 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -24,7 +24,7 @@ jobs: run: | sudo apt update sudo apt install doxygen - pip install mkdocs-material==9.7.0 mkdocstrings==1.0.0 mike==2.1.3 typing_extensions==4.15.0 + pip install --require-hashes -r support/doc-requirements.txt cmake -E make_directory ${{runner.workspace}}/build # Workaround https://github.com/actions/checkout/issues/13: git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" diff --git a/support/doc-requirements.in b/support/doc-requirements.in new file mode 100644 index 00000000..2ed93929 --- /dev/null +++ b/support/doc-requirements.in @@ -0,0 +1,4 @@ +mkdocs-material==9.7.0 +mkdocstrings==1.0.0 +mike==2.1.3 +typing_extensions==4.15.0 diff --git a/support/doc-requirements.txt b/support/doc-requirements.txt new file mode 100644 index 00000000..4e037774 --- /dev/null +++ b/support/doc-requirements.txt @@ -0,0 +1,502 @@ +# Pinned, hash-verified Python dependencies for building the docs. +# To regenerate, run from the repository root: +# uv pip compile support/doc-requirements.in --generate-hashes \ +# --python-version 3.10 --python-platform x86_64-unknown-linux-gnu \ +# --output-file support/doc-requirements.txt +babel==2.18.0 \ + --hash=sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d \ + --hash=sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35 + # via mkdocs-material +backrefs==7.0 \ + --hash=sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82 \ + --hash=sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475 \ + --hash=sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9 \ + --hash=sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9 \ + --hash=sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12 \ + --hash=sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a + # via mkdocs-material +certifi==2026.5.20 \ + --hash=sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897 \ + --hash=sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d + # via requests +charset-normalizer==3.4.7 \ + --hash=sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc \ + --hash=sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c \ + --hash=sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67 \ + --hash=sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4 \ + --hash=sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0 \ + --hash=sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c \ + --hash=sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5 \ + --hash=sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444 \ + --hash=sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153 \ + --hash=sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9 \ + --hash=sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01 \ + --hash=sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217 \ + --hash=sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b \ + --hash=sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c \ + --hash=sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a \ + --hash=sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83 \ + --hash=sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5 \ + --hash=sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7 \ + --hash=sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb \ + --hash=sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c \ + --hash=sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1 \ + --hash=sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42 \ + --hash=sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab \ + --hash=sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df \ + --hash=sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e \ + --hash=sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207 \ + --hash=sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18 \ + --hash=sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734 \ + --hash=sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38 \ + --hash=sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110 \ + --hash=sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18 \ + --hash=sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44 \ + --hash=sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d \ + --hash=sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48 \ + --hash=sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e \ + --hash=sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5 \ + --hash=sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d \ + --hash=sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53 \ + --hash=sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790 \ + --hash=sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c \ + --hash=sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b \ + --hash=sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116 \ + --hash=sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d \ + --hash=sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10 \ + --hash=sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6 \ + --hash=sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2 \ + --hash=sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776 \ + --hash=sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a \ + --hash=sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265 \ + --hash=sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008 \ + --hash=sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943 \ + --hash=sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374 \ + --hash=sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246 \ + --hash=sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e \ + --hash=sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5 \ + --hash=sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616 \ + --hash=sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15 \ + --hash=sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41 \ + --hash=sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960 \ + --hash=sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752 \ + --hash=sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e \ + --hash=sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72 \ + --hash=sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7 \ + --hash=sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8 \ + --hash=sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b \ + --hash=sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4 \ + --hash=sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545 \ + --hash=sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706 \ + --hash=sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366 \ + --hash=sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb \ + --hash=sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a \ + --hash=sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e \ + --hash=sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00 \ + --hash=sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f \ + --hash=sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a \ + --hash=sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1 \ + --hash=sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66 \ + --hash=sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356 \ + --hash=sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319 \ + --hash=sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4 \ + --hash=sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad \ + --hash=sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d \ + --hash=sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5 \ + --hash=sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7 \ + --hash=sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0 \ + --hash=sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686 \ + --hash=sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34 \ + --hash=sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49 \ + --hash=sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c \ + --hash=sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1 \ + --hash=sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e \ + --hash=sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60 \ + --hash=sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0 \ + --hash=sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274 \ + --hash=sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d \ + --hash=sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0 \ + --hash=sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae \ + --hash=sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f \ + --hash=sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d \ + --hash=sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe \ + --hash=sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3 \ + --hash=sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393 \ + --hash=sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1 \ + --hash=sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af \ + --hash=sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44 \ + --hash=sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00 \ + --hash=sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c \ + --hash=sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3 \ + --hash=sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7 \ + --hash=sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd \ + --hash=sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e \ + --hash=sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b \ + --hash=sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8 \ + --hash=sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259 \ + --hash=sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859 \ + --hash=sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46 \ + --hash=sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30 \ + --hash=sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b \ + --hash=sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46 \ + --hash=sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24 \ + --hash=sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a \ + --hash=sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24 \ + --hash=sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc \ + --hash=sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215 \ + --hash=sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063 \ + --hash=sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832 \ + --hash=sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6 \ + --hash=sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79 \ + --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464 + # via requests +click==8.4.1 \ + --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 \ + --hash=sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96 + # via mkdocs +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via mkdocs-material +ghp-import==2.1.0 \ + --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \ + --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343 + # via mkdocs +idna==3.17 \ + --hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \ + --hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f + # via requests +importlib-metadata==9.0.0 \ + --hash=sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7 \ + --hash=sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc + # via mike +importlib-resources==7.1.0 \ + --hash=sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708 \ + --hash=sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1 + # via mike +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via + # mike + # mkdocs + # mkdocs-material + # mkdocstrings +markdown==3.10.2 \ + --hash=sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950 \ + --hash=sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36 + # via + # mkdocs + # mkdocs-autorefs + # mkdocs-material + # mkdocstrings + # pymdown-extensions +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via + # jinja2 + # mkdocs + # mkdocs-autorefs + # mkdocstrings +mergedeep==1.3.4 \ + --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \ + --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 + # via + # mkdocs + # mkdocs-get-deps +mike==2.1.3 \ + --hash=sha256:abd79b8ea483fb0275b7972825d3082e5ae67a41820f8d8a0dc7a3f49944e810 \ + --hash=sha256:d90c64077e84f06272437b464735130d380703a76a5738b152932884c60c062a + # via -r /tmp/doc-requirements.in +mkdocs==1.6.1 \ + --hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \ + --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e + # via + # mike + # mkdocs-autorefs + # mkdocs-material + # mkdocstrings +mkdocs-autorefs==1.4.4 \ + --hash=sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089 \ + --hash=sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197 + # via mkdocstrings +mkdocs-get-deps==0.2.2 \ + --hash=sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1 \ + --hash=sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650 + # via mkdocs +mkdocs-material==9.7.0 \ + --hash=sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec \ + --hash=sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887 + # via -r /tmp/doc-requirements.in +mkdocs-material-extensions==1.3.1 \ + --hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \ + --hash=sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 + # via mkdocs-material +mkdocstrings==1.0.0 \ + --hash=sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a \ + --hash=sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa + # via -r /tmp/doc-requirements.in +packaging==26.2 \ + --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ + --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 + # via mkdocs +paginate==0.5.7 \ + --hash=sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945 \ + --hash=sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 + # via mkdocs-material +pathspec==1.1.1 \ + --hash=sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a \ + --hash=sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189 + # via mkdocs +platformdirs==4.10.0 \ + --hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \ + --hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a + # via mkdocs-get-deps +pygments==2.20.0 \ + --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ + --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + # via mkdocs-material +pymdown-extensions==10.21.3 \ + --hash=sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354 \ + --hash=sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6 + # via + # mkdocs-material + # mkdocstrings +pyparsing==3.3.2 \ + --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \ + --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc + # via mike +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via ghp-import +pyyaml==6.0.3 \ + --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ + --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ + --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ + --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ + --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ + --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ + --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ + --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ + --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ + --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ + --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ + --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ + --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ + --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ + --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ + --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ + --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ + --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ + --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ + --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ + --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ + --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ + --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ + --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 + # via + # mike + # mkdocs + # mkdocs-get-deps + # pymdown-extensions + # pyyaml-env-tag +pyyaml-env-tag==1.1 \ + --hash=sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 \ + --hash=sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff + # via + # mike + # mkdocs +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ + --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed + # via mkdocs-material +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil +typing-extensions==4.15.0 \ + --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 + # via -r /tmp/doc-requirements.in +urllib3==2.7.0 \ + --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ + --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 + # via requests +verspec==0.1.0 \ + --hash=sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31 \ + --hash=sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e + # via mike +watchdog==6.0.0 \ + --hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \ + --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 \ + --hash=sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f \ + --hash=sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c \ + --hash=sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c \ + --hash=sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c \ + --hash=sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0 \ + --hash=sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13 \ + --hash=sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 \ + --hash=sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa \ + --hash=sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e \ + --hash=sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379 \ + --hash=sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a \ + --hash=sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11 \ + --hash=sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282 \ + --hash=sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b \ + --hash=sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f \ + --hash=sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c \ + --hash=sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112 \ + --hash=sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948 \ + --hash=sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881 \ + --hash=sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860 \ + --hash=sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3 \ + --hash=sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 \ + --hash=sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26 \ + --hash=sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26 \ + --hash=sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e \ + --hash=sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8 \ + --hash=sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c \ + --hash=sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 + # via mkdocs +zipp==4.1.0 \ + --hash=sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f \ + --hash=sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602 + # via importlib-metadata From e48f8a9070377f1020d9530493c18bb1823f642b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 20:56:33 -0700 Subject: [PATCH 044/111] Pin clang-format install Co-authored-by: Cursor --- .github/workflows/lint.yml | 12 ++++++-- support/llvm-snapshot.gpg.key | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 support/llvm-snapshot.gpg.key diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 87992008..cbb1bdc4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,9 +19,15 @@ jobs: - name: Install clang-format run: | - wget https://apt.llvm.org/llvm.sh - sudo bash ./llvm.sh 21 - sudo apt install clang-format-21 + sudo install -D -m 0644 support/llvm-snapshot.gpg.key \ + /etc/apt/keyrings/apt.llvm.org.asc + codename=$(lsb_release -cs) + echo "deb [signed-by=/etc/apt/keyrings/apt.llvm.org.asc]" \ + "https://apt.llvm.org/$codename/" \ + "llvm-toolchain-$codename-21 main" \ + | sudo tee /etc/apt/sources.list.d/llvm.list + sudo apt-get update + sudo apt-get install -y clang-format-21 - name: Run clang-format run: | diff --git a/support/llvm-snapshot.gpg.key b/support/llvm-snapshot.gpg.key new file mode 100644 index 00000000..27f27e13 --- /dev/null +++ b/support/llvm-snapshot.gpg.key @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFE9lCwBEADi0WUAApM/mgHJRU8lVkkw0CHsZNpqaQDNaHefD6Rw3S4LxNmM +EZaOTkhP200XZM8lVdbfUW9xSjA3oPldc1HG26NjbqqCmWpdo2fb+r7VmU2dq3NM +R18ZlKixiLDE6OUfaXWKamZsXb6ITTYmgTO6orQWYrnW6ckYHSeaAkW0wkDAryl2 +B5v8aoFnQ1rFiVEMo4NGzw4UX+MelF7rxaaregmKVTPiqCOSPJ1McC1dHFN533FY +Wh/RVLKWo6npu+owtwYFQW+zyQhKzSIMvNujFRzhIxzxR9Gn87MoLAyfgKEzrbbT +DhqqNXTxS4UMUKCQaO93TzetX/EBrRpJj+vP640yio80h4Dr5pAd7+LnKwgpTDk1 +G88bBXJAcPZnTSKu9I2c6KY4iRNbvRz4i+ZdwwZtdW4nSdl2792L7Sl7Nc44uLL/ +ZqkKDXEBF6lsX5XpABwyK89S/SbHOytXv9o4puv+65Ac5/UShspQTMSKGZgvDauU +cs8kE1U9dPOqVNCYq9Nfwinkf6RxV1k1+gwtclxQuY7UpKXP0hNAXjAiA5KS5Crq +7aaJg9q2F4bub0mNU6n7UI6vXguF2n4SEtzPRk6RP+4TiT3bZUsmr+1ktogyOJCc +Ha8G5VdL+NBIYQthOcieYCBnTeIH7D3Sp6FYQTYtVbKFzmMK+36ERreL/wARAQAB +tD1TeWx2ZXN0cmUgTGVkcnUgLSBEZWJpYW4gTExWTSBwYWNrYWdlcyA8c3lsdmVz +dHJlQGRlYmlhbi5vcmc+iQJMBBMBCgA2AhsDAh4BAheABAsJCAcEFQoJCAUWAgMB +ABYhBGCE88+BS1fBzxLv1RXPTRivT3QhBQJpl4k7AAoJEBXPTRivT3QhivEP/2Ix +/Pi72KhD47zbxLecS0VLB/gURPC2h5AgWgW5P/GSQe0QXHAkqCWFd9y1Y5PI8e3v +6RZFDCPJrfVKfHzIx4nciUHr0qlJqGaplvf6AZvgvF8Xrua9VZaHnZJO+GmK+WtH +/bCgtzk91O08w8wcZ/4wmopQjcSu69OcpqpPBVClhIxaIIFFlATdUAKNVAzXDQDf +WfWT2N3ghsbac1MRQKKzddzXgvVw5mRAw0NCKUJ2DPSohfRsS1LjVG4wThkLWZJb +tPx6+meunBspvvROthh+HEEjiB5o4i7230AOhyYAOjgZj3wQq7A/2O0Ro1A2zl5L +vB17a6WLnEdUzLgj0NYtE1x4W0fNAspEQpIXvVRkZzHA+GH31a2Fs/S2kqsuF/OY +hsD35d/6lJtxbNY2sYsprDxT2tfdnUG9KCJSI3p/vjGIezFgrfBB7u6lUHx/GAQM +nwpiXKeRRS4fT03aZ79FR3xVE7pLOHRq4tUVEH1lvHQB8aU07NHK61BR8NXS++cg +LhveD+TwCKNQ9WaDdNNDl0jGbOBzcou+KguZD8wfoCj/Ko0CdCUFdO+H+TPsIH4F +nRovkQ5bN7Y7iTHiV96mDOtnE2kd4ctbioEl8/4zroKCZTQEzJRQ/Zz+NdtRa/ev +n2jUyoHWs2Vc5bYLyvwjk6CCurIumdT6JAdEUMF6uQINBFE9lCwBEADiPFiZtMfC +YyOpKLOUwlg24Y6kiJ0R3H3NDmDM6vjfX9V9arhL5WEwOGzXmP2XKaXTLiiTbXlV +EXCsBEQODpQ8yvtqMuYFAZq7QVxu9kn3SXg2NenmnQ0QT+o5v1QQdPsQaQSFpnV6 +SRYVNYKCzLutFd9kvEHqyTunMsIOyHJbfOMhqRYCelDmPmhuIPLvTylF4C4YH3g1 +LJvUehb/EawaC+8LDivTDfE9K3AXU+GZJwTo1MEh8uWJ5remXy+Eo+UKHngeTsjx +AaOjUROVE/hCWXVnEcny7bl/+1pERLYDDweLuPQRVxnoxP0lZMjzfJRydSVk9lLY +fJ5ZxSP7ydQLxbg3wCf5o2mz2X/bWKciXZNsajdifUlAucDVgKgbALfiDxBlMmES +URAE3Rfb1rJZbo50XHwwO+WVsFXKaYqraUrVTnmZBN8OWLtLe98bP0an+hDBnxtC +k55SRzmFtmzNjA/hFf7rzEM9+f4z252X6pb3C74vJ49tbodf3QuaSE2SRjZnNQ9E +8MdkA1Tlu1o9dzYft51fjLiZpBrF6eoPSUaH0sbP+amzgcRm0IjkJjyjS4IwXeVP +Rd9eG4uSyvs/G/nvK57wstMi04l30ebOn0b3tJFhh6pT7yXI1e1TY92ssurtAeTO +uyWgbvyUKlBxqUcYAInOuVRZU/983ce3eQARAQABiQI2BBgBCgAgAhsMFiEEYITz +z4FLV8HPEu/VFc9NGK9PdCEFAmmXid0ACgkQFc9NGK9PdCF/PxAAlMhoX2YtQ2vc +aPt+6hZ3q/8y+xht/WK7YmPm76b7tmRvahZIIfr9Criu9HyL4nFXlVnYot63qIqa +ebu4GUr7Dau+oimJ2olLXMbSjhg7R/Q9ESqG/6pvBl6gwgKqxQAh8lVgK4KevK++ +nxdPLLNe9F1kkwnLKCUvYElEYEUMrqI4GMHfgPw84vCCWmR8CSZ+AJ95tft5Xzxf +7uENFADYkDkLiA4giHSasMOhiVxhZWSI+UVJ2vTGuscma509kqs+CjzXOzCKymQ4 +qVCjKLaSkQK4rUV125AU0WfRFpaKtiHWmLic5opBzCGhvJ7HI8iOxAFq3bHB1WN1 +Ba7VHOCbSKAFIw+7n+onEmKPpiZe55pc684wHy1RD3YnimdJWyHxMXSGuhggv3W1 +CWxE1U2fmnMzXbn3T1VXk5/q/5X4wtirzWobZIzVcg5WcoSx0w0LOqIgf6C1z0jq +FLloorOrbdfYOg6GEhaoiiYiwnWwPiKHNqONpMqXitwh/lCK3SomtdsACy1FlVP7 +HvM41+jf7qZ9WqCvUZKc+11XAUdOaqAoxQU/T1I1gPGTTJ3uYvAsOqQc9SZAt3Io +8QkYjS9LQvxtZqAdWUALaiQIYKf3uJE5eWP3sF6uEJA3LTyIZsMd7MO25UEPLKCw +O9BuqGhXJW1iyMZ/Smhp993FdBBf4FA= +=/NY6 +-----END PGP PUBLIC KEY BLOCK----- From 1f0533a92348fd1d56b5f8ccfc0c4915f2065c05 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 21:23:21 -0700 Subject: [PATCH 045/111] Pin clang-20 install Co-authored-by: Cursor --- .github/workflows/linux.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5cda5161..2a67495f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -146,9 +146,13 @@ jobs: - name: Install LLVM-20 run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 20 + sudo install -D -m 0644 support/llvm-snapshot.gpg.key \ + /etc/apt/keyrings/apt.llvm.org.asc + codename=$(lsb_release -cs) + echo "deb [signed-by=/etc/apt/keyrings/apt.llvm.org.asc]" \ + "https://apt.llvm.org/$codename/" \ + "llvm-toolchain-$codename-20 main" \ + | sudo tee /etc/apt/sources.list.d/llvm.list if: ${{ matrix.cxx == 'clang++-20' }} - name: Add Ubuntu mirrors From ed5d7ec5a3856be2c90af7264dd5f168c43aa00a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 31 May 2026 21:45:33 -0700 Subject: [PATCH 046/111] Add CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..2145fe5f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @vitaut From 1c98a69c6671fa0358d141d8f906638e5daaaffd Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 06:31:07 -0700 Subject: [PATCH 047/111] Move security policy from README to .github/SECURITY.md --- .github/SECURITY.md | 8 ++++++++ README.md | 9 --------- 2 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000..1b6fba6b --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,8 @@ +# Security Policy + +To report a security issue, please disclose it at [security +advisory](https://github.com/fmtlib/fmt/security/advisories/new). + +This project is maintained by a team of volunteers on a +reasonable-effort basis. As such, please give us at least 90 days to +work on a fix before public disclosure. diff --git a/README.md b/README.md index 6a6d586c..6f7f249e 100644 --- a/README.md +++ b/README.md @@ -407,12 +407,3 @@ people. See [Releases](https://github.com/fmtlib/fmt/releases) for some of the names. Let us know if your contribution is not listed or mentioned incorrectly and we\'ll make it right. - -# Security Policy - -To report a security issue, please disclose it at [security -advisory](https://github.com/fmtlib/fmt/security/advisories/new). - -This project is maintained by a team of volunteers on a -reasonable-effort basis. As such, please give us at least *90* days to -work on a fix before public exposure. From 95390170a9f63e04d455298e8787cddb9265e89d Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 08:54:54 -0700 Subject: [PATCH 048/111] Rename scorecard workflow to supply-chain --- .github/workflows/{scorecard.yml => supply-chain.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{scorecard.yml => supply-chain.yml} (100%) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/supply-chain.yml similarity index 100% rename from .github/workflows/scorecard.yml rename to .github/workflows/supply-chain.yml From 3046d2ca7ec6d15db34b58120f2aa9c9015a367a Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 09:31:01 -0700 Subject: [PATCH 049/111] Add CodeQL workflow --- .github/workflows/codeql.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..6a87572e --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,31 @@ +name: codeql + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + security-events: write + actions: read + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + + - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 + with: + languages: c-cpp + + - name: Build + run: | + cmake -B build -DFMT_DOC=OFF -DFMT_TEST=OFF + cmake --build build --parallel + + - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 + with: + category: /language:c-cpp From b18919cb38409b730cf2cb1f1cf1fee2f1837282 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 10:22:33 -0700 Subject: [PATCH 050/111] Clean up supply-chain workflow --- .github/workflows/supply-chain.yml | 31 ++++++------------------------ 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index d10763dc..539c51dc 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -1,8 +1,4 @@ -# This workflow uses actions that are not certified by GitHub. They are provided -# by a third-party and are governed by separate terms of service, privacy -# policy, and support documentation. - -name: Scorecard supply-chain security +name: supply-chain on: workflow_dispatch: # For Branch-Protection check. Only the default branch is supported. See @@ -13,14 +9,13 @@ on: schedule: - cron: '26 14 * * 5' push: - branches: [ "master" ] + branches: [main] # Declare default permissions as read only. permissions: read-all jobs: - analysis: - name: Scorecard analysis + scorecard: runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. @@ -29,20 +24,15 @@ jobs: id-token: write steps: - - name: "Checkout code" - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: persist-credentials: false - - name: "Run analysis" + - name: Run analysis uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif - # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: - # - you want to enable the Branch-Protection check on a *public* repository, or - # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. - # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Public repositories: # - Publish results to OpenSSF REST API for easy access by consumers @@ -50,17 +40,8 @@ jobs: # - See https://github.com/ossf/scorecard-action#publishing-results. publish_results: true - # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF - # format to the repository Actions tab. - - name: "Upload artifact" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - # Upload the results to GitHub's code scanning dashboard. - - name: "Upload to code-scanning" + - name: Upload to code-scanning uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5 with: sarif_file: results.sarif From 2538c89e71cf3c2bb87d1356355d8326fcecbb55 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 10:49:42 -0700 Subject: [PATCH 051/111] Scope CodeQL write permissions to the analyze job --- .github/workflows/codeql.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6a87572e..69259c98 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,12 +8,14 @@ on: permissions: contents: read - security-events: write - actions: read jobs: analyze: runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 From 6b2d7f9726986f63e7cd5e5041ce68bc60459247 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 12:02:54 -0700 Subject: [PATCH 052/111] Rename CIFuzz workflow to fuzz Match the naming convention used by the other workflows: lowercase, single-word names that describe what the workflow does rather than the tool it uses. Also rename the job from Fuzzing to fuzz for consistency. --- .github/workflows/{cifuzz.yml => fuzz.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{cifuzz.yml => fuzz.yml} (97%) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/fuzz.yml similarity index 97% rename from .github/workflows/cifuzz.yml rename to .github/workflows/fuzz.yml index d6b94918..660e8c49 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/fuzz.yml @@ -1,11 +1,11 @@ -name: CIFuzz +name: fuzz on: [pull_request] permissions: contents: read jobs: - Fuzzing: + fuzz: runs-on: ubuntu-latest steps: - name: Build fuzzers From ab921915538eb2210bfe640795ea8ef65650eaa0 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 13:10:08 -0700 Subject: [PATCH 053/111] Rewrite syntax.md from scratch Only reuse the BNF syntax. --- doc/syntax.md | 683 ++++++++++++++++++-------------------------------- 1 file changed, 244 insertions(+), 439 deletions(-) diff --git a/doc/syntax.md b/doc/syntax.md index ac512823..2007cb2a 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -1,14 +1,15 @@ # Format String Syntax -Formatting functions such as [`fmt::format`](api.md#format) and [`fmt::print`]( -api.md#print) use the same format string syntax described in this section. +The formatting functions in this library — most notably +[`fmt::format`](api.md#format) and [`fmt::print`](api.md#print) — accept +format strings written in the syntax described here. -Format strings contain "replacement fields" surrounded by curly braces `{}`. -Anything that is not contained in braces is considered literal text, which is -copied unchanged to the output. If you need to include a brace character in -the literal text, it can be escaped by doubling: `{{` and `}}`. +A format string is plain text with embedded *replacement fields* delimited by +the braces `{` and `}`. Characters outside of any replacement field are +copied to the output unchanged. To emit a literal brace, double it: `{{` +yields a single `{` in the output, and `}}` yields a single `}`. -The grammar for a replacement field is as follows: +A replacement field is described by the grammar below.

 
-In less formal terms, the replacement field can start with an *arg_id* that -specifies the argument whose value is to be formatted and inserted into the -output instead of the replacement field. The *arg_id* is optionally followed -by a *format_spec*, which is preceded by a colon `':'`. These specify a -non-default format for the replacement value. +An *arg_id* selects which argument to format. It may be a non-negative +integer (positional reference) or an identifier matching the name of an +argument passed via [`fmt::arg`](api.md#arg) (named reference). When *arg_id* +is omitted, arguments are consumed in left-to-right order; this *automatic* +indexing must be used uniformly throughout the format string — mixing +automatic and explicit numeric ids is a compile-time error (or a +`format_error` at runtime). -See also the [Format Specification -Mini-Language](#format-specification-mini-language) section. +A *format_spec*, introduced by `:`, describes how the value should be +rendered. Its grammar is type-dependent; the form used by the standard +built-in types is documented in the next section. -If the numerical arg_ids in a format string are 0, 1, 2, ... in sequence, -they can all be omitted (not just some) and the numbers 0, 1, 2, ... will be -automatically inserted in that order. - -Named arguments can be referred to by their names or indices. - -Some simple format string examples: +For example: ```c++ -"First, thou shalt count to {0}" // References the first argument -"Bring me a {}" // Implicitly references the first argument -"From {} to {}" // Same as "From {0} to {1}" +fmt::format("hello, {}", "world"); +// Result: "hello, world" + +fmt::format("{1}, {0}!", "world", "hello"); +// Result: "hello, world!" + +fmt::format("{greeting}, {name}!", + fmt::arg("greeting", "hi"), fmt::arg("name", "fmt")); +// Result: "hi, fmt!" ``` -The *format_spec* field contains a specification of how the value should -be presented, including such details as field width, alignment, padding, -decimal precision and so on. Each value type can define its own -"formatting mini-language" or interpretation of the *format_spec*. +A *width* or *precision* inside a *format_spec* may itself be written as a +nested replacement field — `{}` or `{arg_id}` — in which case it takes its +value from an integer argument at runtime. Nested fields accept only an +*arg_id*; they cannot themselves contain a *format_spec*. -Most built-in types support a common formatting mini-language, which is -described in the next section. +## Standard Format Specification -A *format_spec* field can also include nested replacement fields in -certain positions within it. These nested replacement fields can contain -only an argument id; format specifications are not allowed. This allows -the formatting of a value to be dynamically specified. - -See the [Format Examples](#format-examples) section for some examples. - -## Format Specification Mini-Language - -"Format specifications" are used within replacement fields contained within a -format string to define how individual values are presented. Each formattable -type may define how the format specification is to be interpreted. - -Most built-in types implement the following options for format -specifications, although some of the formatting options are only -supported by the numeric types. - -The general form of a *standard format specifier* is: +The grammar below describes the *format_spec* shared by the standard built-in +types — integers, floating-point values, characters, strings, booleans, and +pointers — as well as by any user-defined type whose `formatter` reuses +fmt's parser.

 
-The *fill* character can be any Unicode code point other than `'{'` or `'}'`. -The presence of a fill character is signaled by the character following it, -which must be one of the alignment options. If the second character of -*format_spec* is not a valid alignment option, then it is assumed that both -the fill character and the alignment option are absent. +Whether a particular option is meaningful depends on the value being +formatted; options that do not apply to a value's type are diagnosed at +compile time when possible and otherwise raise a `format_error`. -The meaning of the various alignment options is as follows: +### Fill and alignment - - - - - - - - - - - - - - - - - -
OptionMeaning
'<' - Forces the field to be left-aligned within the available space (this is the - default for most objects). -
'>' - Forces the field to be right-aligned within the available space (this is - the default for numbers). -
'^'Forces the field to be centered within the available space.
+The *align* field selects where padding is placed when *width* makes the +field wider than the value's natural rendering. -Note that unless a minimum field width is defined, the field width will -always be the same size as the data to fill it, so that the alignment -option has no meaning in this case. +| Option | Effect | +|--------|-------------------------------------------------------------------| +| `<` | Left-align; pad on the right. Default for non-numeric types. | +| `>` | Right-align; pad on the left. Default for numeric types. | +| `^` | Center the value; if the padding cannot be split evenly, the extra padding character goes on the right. | -The *sign* option is only valid for floating point and signed integer types, -and can be one of the following: +The *fill* character is any single Unicode code point other than `{` or `}`, +encoded the same way as the format string. It supplies the padding character +in place of the default space. A fill character is recognized only when it is +immediately followed by an *align* character — otherwise it would be +indistinguishable from an option in another position — so to use a custom +fill you must also specify an alignment. - - - - - - - - - - - - - - - - - -
OptionMeaning
'+' - Indicates that a sign should be used for both nonnegative as well as - negative numbers. -
'-' - Indicates that a sign should be used only for negative numbers (this is the - default behavior). -
space - Indicates that a leading space should be used on nonnegative numbers, and a - minus sign on negative numbers. -
+Alignment has no observable effect when the value's natural rendering is +already at least as wide as *width*; the value is never truncated to fit. -The `'#'` option causes the "alternate form" to be used for the -conversion. The alternate form is defined differently for different -types. This option is only valid for integer and floating-point types. -For integers, when binary, octal, or hexadecimal output is used, this -option adds the prefix respective `"0b"` (`"0B"`), `"0"`, or `"0x"` -(`"0X"`) to the output value. Whether the prefix is lower-case or -upper-case is determined by the case of the type specifier, for example, -the prefix `"0x"` is used for the type `'x'` and `"0X"` is used for -`'X'`. For floating-point numbers the alternate form causes the result -of the conversion to always contain a decimal-point character, even if -no digits follow it. Normally, a decimal-point character appears in the -result of these conversions only if a digit follows it. In addition, for -`'g'` and `'G'` conversions, trailing zeros are not removed from the -result. +```c++ +fmt::format("[{:<10}]", "42"); // Result: "[42 ]" +fmt::format("[{:>10}]", "42"); // Result: "[ 42]" +fmt::format("[{:^10}]", "42"); // Result: "[ 42 ]" +fmt::format("[{:*^10}]", "42"); // Result: "[****42****]" -- '*' as fill +``` -*width* is a decimal integer defining the minimum field width. If not -specified, then the field width will be determined by the content. +### Sign -Preceding the *width* field by a zero (`'0'`) character enables -sign-aware zero-padding for numeric types. It forces the padding to be -placed after the sign or base (if any) but before the digits. This is -used for printing fields in the form "+000000120". This option is only -valid for numeric types and it has no effect on formatting of infinity -and NaN. This option is ignored when any alignment specifier is present. +The *sign* field controls how the sign of a numeric value is emitted. It +applies to signed integer and floating-point types only. -The *precision* is a decimal number indicating how many digits should be -displayed after the decimal point for a floating-point value formatted -with `'f'` and `'F'`, or before and after the decimal point for a -floating-point value formatted with `'g'` or `'G'`. For non-number types -the field indicates the maximum field size - in other words, how many -characters will be used from the field content. The *precision* is not -allowed for integer, character, Boolean, and pointer values. Note that a -C string must be null-terminated even if precision is specified. +| Option | Effect | +|--------|-------------------------------------------------------------------| +| `+` | Always emit a sign (`+` for nonnegative values, `-` for negative).| +| `-` | Emit `-` only for negative values. This is the default. | +| space | Emit a leading space for nonnegative values and `-` for negative ones; useful for aligning columns of signed numbers. | -The `'L'` option uses the current locale setting to insert the appropriate -number separator characters. This option is only valid for numeric types. +The sign of `-0.0` is preserved in floating-point output. -Finally, the *type* determines how the data should be presented. +```c++ +fmt::format("{:+d} {:+d}", 7, -7); // Result: "+7 -7" +fmt::format("{: d} {: d}", 7, -7); // Result: " 7 -7" +``` -The available string presentation types are: +### Alternate form (`#`) - - - - - - - - - - - - - - - - - -
TypeMeaning
's' - String format. This is the default type for strings and may be omitted. -
'?'Debug format. The string is quoted and special characters escaped.
noneThe same as 's'.
+The `#` flag selects an *alternate form* whose exact meaning depends on the +presentation type: -The available character presentation types are: +- For integers rendered in binary, octal, or hexadecimal, it prepends the + appropriate base prefix (`0b`/`0B`, `0`, or `0x`/`0X`). The case of the + prefix follows the case of the type specifier — `0x` for `x`, `0X` for + `X`, and so on. +- For floating-point values, it forces the radix character to appear in the + output even if no fractional digits would otherwise be emitted, and + prevents the `g`/`G` presentation types from removing trailing zeros from + the significand. - - - - - - - - - - - - - - - - - -
TypeMeaning
'c' - Character format. This is the default type for characters and may be - omitted. -
'?'Debug format. The character is quoted and special characters escaped.
noneThe same as 'c'.
+The `#` flag is not accepted by non-numeric types. -The available integer presentation types are: +### Zero padding (`0`) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeMeaning
'b' - Binary format. Outputs the number in base 2. Using the '#' - option with this type adds the prefix "0b" to the output value. -
'B' - Binary format. Outputs the number in base 2. Using the '#' - option with this type adds the prefix "0B" to the output value. -
'c'Character format. Outputs the number as a character.
'd'Decimal integer. Outputs the number in base 10.
'o'Octal format. Outputs the number in base 8.
'x' - Hex format. Outputs the number in base 16, using lower-case letters for the - digits above 9. Using the '#' option with this type adds the - prefix "0x" to the output value. -
'X' - Hex format. Outputs the number in base 16, using upper-case letters for the - digits above 9. Using the '#' option with this type adds the - prefix "0X" to the output value. -
noneThe same as 'd'.
+A `0` placed immediately before *width* enables sign-aware zero padding for +numeric types. Zeros are inserted between the sign (or base prefix, if any) +and the most significant digit, so that a sign or `0x` prefix stays adjacent +to the digits rather than being separated by spaces. For example, `{:+08d}` +applied to `120` produces `+0000120`. -Integer presentation types can also be used with character and Boolean values -with the only exception that `'c'` cannot be used with `bool`. Boolean values -are formatted using textual representation, either `true` or `false`, if the -presentation type is not specified. +Zero padding: -The available presentation types for floating-point values are: +- applies only to numeric types; +- has no effect on `inf` or `nan`; +- is silently ignored when an explicit *align* is also present. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeMeaning
'a' - Hexadecimal floating point format. Prints the number in base 16 with - prefix "0x" and lower-case letters for digits above 9. - Uses 'p' to indicate the exponent. -
'A' - Same as 'a' except it uses upper-case letters for the - prefix, digits above 9 and to indicate the exponent. -
'e' - Exponent notation. Prints the number in scientific notation using - the letter 'e' to indicate the exponent. -
'E' - Exponent notation. Same as 'e' except it uses an - upper-case 'E' as the separator character. -
'f'Fixed point. Displays the number as a fixed-point number.
'F' - Fixed point. Same as 'f', but converts nan - to NAN and inf to INF. -
'g' -

General format. For a given precision p >= 1, - this rounds the number to p significant digits and then - formats the result in either fixed-point format or in scientific - notation, depending on its magnitude.

-

A precision of 0 is treated as equivalent to a precision - of 1.

-
'G' - General format. Same as 'g' except switches to - 'E' if the number gets too large. The representations of - infinity and NaN are uppercased, too. -
none - Similar to 'g', except that the default precision is as - high as needed to represent the particular value. -
+### Width -The available presentation types for pointers are: +*width* is a non-negative decimal integer giving the minimum number of +characters that the field should occupy. If the formatted value is shorter +than *width*, it is padded according to *align* and *fill*; if it is longer, +the value is written in full. *width* never causes the value to be +truncated. - - - - - - - - - - - - - -
TypeMeaning
'p' - Pointer format. This is the default type for pointers and may be omitted. -
noneThe same as 'p'.
+To supply *width* at runtime, write the field as `{}` to consume the next +argument, or as `{arg_id}` to reference an integer argument by position or +by name. + +When formatting strings, "width" is measured in display columns using a +Unicode-aware estimate (East Asian wide and fullwidth characters, plus +common emoji ranges, count as two columns; everything else counts as one). +This keeps fixed *width* values visually consistent in monospace renderings +that combine Latin and CJK text. + +```c++ +fmt::format("[{:6}]", 42); // Result: "[ 42]" -- right-aligned by default +fmt::format("[{:6}]", "hi"); // Result: "[hi ]" -- left-aligned by default +fmt::format("[{:{}}]", 42, 6); // Result: "[ 42]" -- width from an argument +``` + +### Precision + +*precision* is a non-negative decimal integer (introduced by `.`) whose +meaning depends on the value being formatted. As with *width*, it may be +supplied as a nested replacement field for runtime evaluation. + +| Type | Meaning of `.precision` | +|-------------------------------|-----------------------------------------------| +| `e`, `E`, `f`, `F` | Digits emitted after the radix character. | +| `g`, `G` | Total number of significant digits. | +| `a`, `A` | Digits after the radix character in the hexadecimal significand. If omitted, just enough digits are emitted to round-trip the value exactly. | +| Strings (`s`, `?`, or default) | Upper bound on the number of code points copied from the value. | + +A *precision* is not accepted for integer, character, boolean, or pointer +types. When a *precision* limits the number of characters taken from a C +string, the string must still be null-terminated. + +```c++ +fmt::format("{:.2f}", 3.14159); // Result: "3.14" +fmt::format("{:.3g}", 3.14159); // Result: "3.14" +fmt::format("{:.4}", "hello, world"); // Result: "hell" +fmt::format("{:.{}f}", 3.14159, 4); // Result: "3.1416" -- precision from an argument +``` + +### Locale (`L`) + +The `L` flag selects locale-sensitive formatting for numeric types. The +formatter inspects the C++ locale supplied to the formatting function (or +the global locale, if none was passed) and inserts the locale's digit +grouping characters and — for floating-point values — its radix character. +The flag has no effect on non-numeric types. + +```c++ +auto loc = std::locale("en_US.UTF-8"); +fmt::format(loc, "{:L}", 1234567890); // Result: "1,234,567,890" +fmt::format(loc, "{:.2Lf}", 1234567.89); // Result: "1,234,567.89" +``` + +### Presentation type + +The *type* field chooses the representation for the value. Specifiers are +grouped below by the value categories they apply to. + +**Integers, booleans, and characters:** + +| Type | Effect | +|------|---------------------------------------------------------------------| +| `b` | Base 2. The `#` flag adds a `0b` prefix. | +| `B` | Base 2. The `#` flag adds a `0B` prefix. | +| `c` | Render the integer as the character with that code point. Not allowed for `bool`. | +| `d` | Base 10. The default for integer types. | +| `o` | Base 8. | +| `x` | Base 16, lower-case digits. The `#` flag adds a `0x` prefix. | +| `X` | Base 16, upper-case digits. The `#` flag adds a `0X` prefix. | +| none | Same as `d` for integers, `c` for characters, and the textual form (`true`/`false`) for `bool`. | + +```c++ +fmt::format("{:d} {:#x} {:#o} {:#b}", 42, 42, 42, 42); +// Result: "42 0x2a 052 0b101010" + +fmt::format("{:#06x}", 0xfe); // # adds the prefix, 06 zero-pads to width 6 +// Result: "0x00fe" +``` + +**Floating-point values:** + +| Type | Effect | +|------|---------------------------------------------------------------------| +| `a` | Hexadecimal-significand form (e.g. `1.8p+1`). Lower-case digits and a lower-case `p` for the binary exponent. The `#` flag adds a `0x` prefix. | +| `A` | Same as `a`, but upper-case throughout. | +| `e` | Scientific notation with a lower-case `e` for the decimal exponent. | +| `E` | Scientific notation with an upper-case `E`. | +| `f` | Fixed-point notation. | +| `F` | Same as `f`, but renders `nan` as `NAN` and `inf` as `INF`. | +| `g` | General form: scientific notation when the exponent would be less than −4 or not less than the precision, otherwise fixed-point; trailing zeros are removed from the fractional part unless `#` is set. A precision of `0` is interpreted as `1`. | +| `G` | Same as `g`, but uses `E` for the exponent and upper-case `INF`/`NAN`. | +| none | Shortest round-trip representation: the formatted value, when parsed back into the same floating-point type, reproduces the input bit for bit. | + +**Strings and characters:** + +| Type | Effect | +|------|---------------------------------------------------------------------| +| `s` | Plain string output. Default for string types and for `bool` (which is rendered as `true` or `false`). | +| `c` | Character output. Default for character types. Not allowed for `bool`. | +| `?` | Debug output: the value is wrapped in single quotes (characters) or double quotes (strings), and non-printable, non-ASCII, and special characters are escaped using C-style escape sequences such as `\n`, `\t`, `\"`, and `\u{...}`. | +| none | Same as `s` for strings and `bool`, and as `c` for characters. | + +```c++ +fmt::format("{}", "tab\there"); // Result contains a literal tab character. +fmt::format("{:?}", "tab\there"); // Result: "\"tab\\there\"" +``` + +**Pointers:** + +| Type | Effect | +|------|---------------------------------------------------------------------| +| `p` | Hexadecimal address prefixed by `0x`. Default for pointer types. | +| none | Same as `p`. | + +A C string (`char*` or `const char*`) accepts both the string presentation +types and `p`, so the same value can be formatted as either text or an +address. ## Chrono Format Specifications @@ -713,6 +601,22 @@ The available padding modifiers (*padding_modifier*) are: These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`, `'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types. +Example: + +```c++ +#include + +auto t = std::tm(); +t.tm_year = 2010 - 1900; +t.tm_mon = 7; +t.tm_mday = 4; +t.tm_hour = 12; +t.tm_min = 15; +t.tm_sec = 58; +fmt::print("{:%Y-%m-%d %H:%M:%S}", t); +// Prints: 2010-08-04 12:15:58 +``` + ## Range Format Specifications Format specifications for range types have the following syntax: @@ -765,87 +669,13 @@ fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e}); // Output: 3.141593, 2.718282 ``` -## Format Examples + +## Examples -This section contains examples of the format syntax and comparison with -the `printf` formatting. - -In most of the cases the syntax is similar to the `printf` formatting, -with the addition of the `{}` and with `:` used instead of `%`. For -example, `"%03.2f"` can be translated to `"{:03.2f}"`. - -The new format syntax also supports new and different options, shown in -the following examples. - -Accessing arguments by position: - -```c++ -fmt::format("{0}, {1}, {2}", 'a', 'b', 'c'); -// Result: "a, b, c" -fmt::format("{}, {}, {}", 'a', 'b', 'c'); -// Result: "a, b, c" -fmt::format("{2}, {1}, {0}", 'a', 'b', 'c'); -// Result: "c, b, a" -fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated -// Result: "abracadabra" -``` - -Aligning the text and specifying a width: - -```c++ -fmt::format("{:<30}", "left aligned"); -// Result: "left aligned " -fmt::format("{:>30}", "right aligned"); -// Result: " right aligned" -fmt::format("{:^30}", "centered"); -// Result: " centered " -fmt::format("{:*^30}", "centered"); // use '*' as a fill char -// Result: "***********centered***********" -``` - -Dynamic width: - -```c++ -fmt::format("{:<{}}", "left aligned", 30); -// Result: "left aligned " -``` - -Dynamic precision: - -```c++ -fmt::format("{:.{}f}", 3.14, 1); -// Result: "3.1" -``` - -Replacing `%+f`, `%-f`, and `% f` and specifying a sign: - -```c++ -fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always -// Result: "+3.140000; -3.140000" -fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers -// Result: " 3.140000; -3.140000" -fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}' -// Result: "3.140000; -3.140000" -``` - -Replacing `%x` and `%o` and converting the value to different bases: - -```c++ -fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); -// Result: "int: 42; hex: 2a; oct: 52; bin: 101010" -// with 0x or 0 or 0b as prefix: -fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42); -// Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010" -``` - -Padded hex byte with prefix and always prints both hex characters: - -```c++ -fmt::format("{:#04x}", 0); -// Result: "0x00" -``` - -Box drawing using Unicode fill: +Most of the format options are introduced alongside their grammar in the +sections above. The example below combines several of them — nested +replacement fields, fill characters, and centering — to draw a fixed-width +box around a message: ```c++ fmt::print( @@ -854,35 +684,10 @@ fmt::print( "└{0:─^{2}}┘\n", "", "Hello, world!", 20); ``` -prints: +Output: ``` ┌────────────────────┐ │ Hello, world! │ └────────────────────┘ ``` - -Using type-specific formatting: - -```c++ -#include - -auto t = tm(); -t.tm_year = 2010 - 1900; -t.tm_mon = 7; -t.tm_mday = 4; -t.tm_hour = 12; -t.tm_min = 15; -t.tm_sec = 58; -fmt::print("{:%Y-%m-%d %H:%M:%S}", t); -// Prints: 2010-08-04 12:15:58 -``` - -Using the comma as a thousands separator: - -```c++ -#include - -auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); -// s == "1,234,567,890" -``` From b6248c5bc02527b8e631541e5f3f070557534ba7 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 15:18:47 -0700 Subject: [PATCH 054/111] Fix doc deploy condition for main branch --- .github/workflows/doc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index fec5dffb..6c041585 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -37,6 +37,6 @@ jobs: - name: Deploy env: KEY: "${{secrets.KEY}}" - if: env.KEY != '' && github.ref == 'refs/heads/master' + if: env.KEY != '' && github.ref == 'refs/heads/main' working-directory: ${{runner.workspace}}/fmt/build/fmt.dev run: git push https://$KEY@github.com/fmtlib/fmt.dev.git From f53c0408a3388de34879a7a40a616326b4459d68 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 1 Jun 2026 15:45:35 -0700 Subject: [PATCH 055/111] Remove Python license attribution The syntax documentation no longer derives from Python's string module documentation, so the PSF license no longer applies. --- README.md | 12 -- doc/python-license.txt | 290 ----------------------------------------- 2 files changed, 302 deletions(-) delete mode 100644 doc/python-license.txt diff --git a/README.md b/README.md index 6f7f249e..38632f33 100644 --- a/README.md +++ b/README.md @@ -386,18 +386,6 @@ know by [email](mailto:victor.zverovich@gmail.com) or by submitting an library useful, please consider [sponsoring its development on GitHub Sponsors](https://github.com/sponsors/vitaut). -# License - -{fmt} is distributed under the MIT -[license](https://github.com/fmtlib/fmt/blob/master/LICENSE). - -The [Format String Syntax](https://fmt.dev/latest/syntax/) section -in the documentation is based on the one from Python [string module -documentation](https://docs.python.org/3/library/string.html#module-string). -For this reason, the documentation is distributed under the Python -Software Foundation license available in -[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt). - # Maintainers The {fmt} library is maintained by Victor Zverovich diff --git a/doc/python-license.txt b/doc/python-license.txt deleted file mode 100644 index 88eed1f9..00000000 --- a/doc/python-license.txt +++ /dev/null @@ -1,290 +0,0 @@ -A. HISTORY OF THE SOFTWARE -========================== - -Python was created in the early 1990s by Guido van Rossum at Stichting -Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands -as a successor of a language called ABC. Guido remains Python's -principal author, although it includes many contributions from others. - -In 1995, Guido continued his work on Python at the Corporation for -National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) -in Reston, Virginia where he released several versions of the -software. - -In May 2000, Guido and the Python core development team moved to -BeOpen.com to form the BeOpen PythonLabs team. In October of the same -year, the PythonLabs team moved to Digital Creations (now Zope -Corporation, see http://www.zope.com). In 2001, the Python Software -Foundation (PSF, see http://www.python.org/psf/) was formed, a -non-profit organization created specifically to own Python-related -Intellectual Property. Zope Corporation is a sponsoring member of -the PSF. - -All Python releases are Open Source (see http://www.opensource.org for -the Open Source Definition). Historically, most, but not all, Python -releases have also been GPL-compatible; the table below summarizes -the various releases. - - Release Derived Year Owner GPL- - from compatible? (1) - - 0.9.0 thru 1.2 1991-1995 CWI yes - 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes - 1.6 1.5.2 2000 CNRI no - 2.0 1.6 2000 BeOpen.com no - 1.6.1 1.6 2001 CNRI yes (2) - 2.1 2.0+1.6.1 2001 PSF no - 2.0.1 2.0+1.6.1 2001 PSF yes - 2.1.1 2.1+2.0.1 2001 PSF yes - 2.2 2.1.1 2001 PSF yes - 2.1.2 2.1.1 2002 PSF yes - 2.1.3 2.1.2 2002 PSF yes - 2.2.1 2.2 2002 PSF yes - 2.2.2 2.2.1 2002 PSF yes - 2.2.3 2.2.2 2003 PSF yes - 2.3 2.2.2 2002-2003 PSF yes - 2.3.1 2.3 2002-2003 PSF yes - 2.3.2 2.3.1 2002-2003 PSF yes - 2.3.3 2.3.2 2002-2003 PSF yes - 2.3.4 2.3.3 2004 PSF yes - 2.3.5 2.3.4 2005 PSF yes - 2.4 2.3 2004 PSF yes - 2.4.1 2.4 2005 PSF yes - 2.4.2 2.4.1 2005 PSF yes - 2.4.3 2.4.2 2006 PSF yes - 2.4.4 2.4.3 2006 PSF yes - 2.5 2.4 2006 PSF yes - 2.5.1 2.5 2007 PSF yes - 2.5.2 2.5.1 2008 PSF yes - 2.5.3 2.5.2 2008 PSF yes - 2.6 2.5 2008 PSF yes - 2.6.1 2.6 2008 PSF yes - 2.6.2 2.6.1 2009 PSF yes - 2.6.3 2.6.2 2009 PSF yes - 2.6.4 2.6.3 2009 PSF yes - 2.6.5 2.6.4 2010 PSF yes - 3.0 2.6 2008 PSF yes - 3.0.1 3.0 2009 PSF yes - 3.1 3.0.1 2009 PSF yes - 3.1.1 3.1 2009 PSF yes - 3.1.2 3.1.1 2010 PSF yes - 3.1.3 3.1.2 2010 PSF yes - 3.1.4 3.1.3 2011 PSF yes - 3.2 3.1 2011 PSF yes - 3.2.1 3.2 2011 PSF yes - 3.2.2 3.2.1 2011 PSF yes - 3.2.3 3.2.2 2012 PSF yes - 3.3.0 3.2 2012 PSF yes - -Footnotes: - -(1) GPL-compatible doesn't mean that we're distributing Python under - the GPL. All Python licenses, unlike the GPL, let you distribute - a modified version without making your changes open source. The - GPL-compatible licenses make it possible to combine Python with - other software that is released under the GPL; the others don't. - -(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, - because its license has a choice of law clause. According to - CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 - is "not incompatible" with the GPL. - -Thanks to the many outside volunteers who have worked under Guido's -direction to make these releases possible. - - -B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON -=============================================================== - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python -alone or in any derivative version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. From a90ad5d6fde4b39417f63a034d566dd7f09b0a55 Mon Sep 17 00:00:00 2001 From: Jiami Lin Date: Tue, 2 Jun 2026 09:16:49 +0800 Subject: [PATCH 056/111] Fix formatting std::tm with null tm_zone (#4790) --- include/fmt/chrono.h | 1 + test/chrono-test.cc | 3 +++ 2 files changed, 4 insertions(+) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 517f9576..e4bc1ce4 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1192,6 +1192,7 @@ class tm_writer { template ::value)> void format_tz_name(const T& tm) { + if (!tm.tm_zone) FMT_THROW(format_error("no timezone")); out_ = write_tm_str(out_, tm.tm_zone, loc_); } template ::value)> diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 7b4bf402..a9153598 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -358,6 +358,9 @@ TEST(chrono_test, tm) { char tz[] = "EET"; if (fmt::detail::set_tm_zone(time, tz)) { EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET"); + fmt::detail::set_tm_zone(time, nullptr); + EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time), + fmt::format_error, "no timezone"); } else { EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time), fmt::format_error, "no timezone"); From 840a5b4190d157c26a30f44dd5a06612f3c66090 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 2 Jun 2026 08:31:36 -0700 Subject: [PATCH 057/111] Update changelog --- ChangeLog.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index e96f9df8..f7deab4b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,204 @@ +# 12.2.0 - TBD + +- Added a C11 API with type-safe formatting via `_Generic`. The new `fmt-c` + library and `fmt/fmt-c.h` header allow C code to use {fmt}'s formatter + with compile-time type checking + (https://github.com/fmtlib/fmt/issues/4663, + https://github.com/fmtlib/fmt/pull/4671, + https://github.com/fmtlib/fmt/pull/4696, + https://github.com/fmtlib/fmt/issues/4693, + https://github.com/fmtlib/fmt/pull/4694, + https://github.com/fmtlib/fmt/pull/4712). + Thanks @Soumik15630m, @yrlf and @localspook. + +- Reduced binary size of debug builds (~200k to ~85k in the bloat test) and + improved compile speed when consteval is unavailable. + +- Added a separate `fmt::fmt-module` CMake target for C++20 modules and a + Linux CI workflow that exercises module-based builds + (https://github.com/fmtlib/fmt/issues/4684, + https://github.com/fmtlib/fmt/pull/4685, + https://github.com/fmtlib/fmt/issues/4707, + https://github.com/fmtlib/fmt/pull/4708, + https://github.com/fmtlib/fmt/pull/4702, + https://github.com/fmtlib/fmt/pull/4709). + Thanks @MathewBensonCode. + +- Enabled the full Dragonbox lookup cache by default for floating-point + formatting unless optimizing for binary size (`__OPTIMIZE_SIZE__`), + giving a ~10–25% speedup. Thanks Matthias Kretz for the suggestion. + +- Improved integer formatting throughput by ~3% + (https://github.com/fmtlib/fmt/pull/4630). Thanks @user202729. + +- Optimized formatting into back-insert iterators by using bulk container + append/insert methods (e.g. on `std::vector` and custom string + types) (https://github.com/fmtlib/fmt/pull/4679). Thanks @user202729. + +- Made path formatting lossless, preserving ill-formed UTF-16 + sequences when converting `std::filesystem::path` to a narrow string. + +- Added overloads of `fmt::println` that take a `fmt::text_style` + (https://github.com/fmtlib/fmt/pull/4782). Thanks @ahoarau. + +- Added support for formatting `std::unexpected` + (https://github.com/fmtlib/fmt/pull/4675). Thanks @17steen. + +- Added support for positional arguments as width and precision specifiers + in `fmt::printf` (https://github.com/fmtlib/fmt/pull/4643). + Thanks @KareemOtoum. + +- Made `FMT_STRING` a no-op when `FMT_USE_CONSTEVAL` is enabled, since the + consteval format-string constructor already provides compile-time + validation + (https://github.com/fmtlib/fmt/issues/4611, + https://github.com/fmtlib/fmt/pull/4612). Thanks @friedkeenan. + +- Promoted `fmt::detail::named_arg` to the public API as `fmt::named_arg` and + deprecated the detail alias + (https://github.com/fmtlib/fmt/issues/4683, + https://github.com/fmtlib/fmt/pull/4687). Thanks @TPPPP72. + +- Moved the `std::byte` formatter from `fmt/format.h` to `fmt/std.h`. + +- Provided a default definition for `fmt::is_contiguous` + (https://github.com/fmtlib/fmt/pull/4731, + https://github.com/fmtlib/fmt/pull/4770). Thanks @user202729 and @phprus. + +- Added the `FMT_USE_FLOCKFILE` macro to force `has_flockfile` to false + (https://github.com/fmtlib/fmt/issues/4646, + https://github.com/fmtlib/fmt/pull/4666). Thanks @MikeVastola. + +- Added `include_guard(GLOBAL)` so that {fmt} can be used in multiple + submodules of the same project + (https://github.com/fmtlib/fmt/pull/4672). Thanks @torsten48. + +- Added `constexpr` to argument `value` constructors and to + `static_format_result` members + (https://github.com/fmtlib/fmt/pull/4659, + https://github.com/fmtlib/fmt/pull/4591). + Thanks @elbeno and @17steen. + +- Deprecated the implicit conversion from `fmt::format_string` and + `fmt::basic_fstring` to `string_view` to align with `std::format_string`; + use `format_string::get()` instead. + +- Opted out `std::complex` from tuple formatting so that the dedicated + `std::complex` formatter is always used. + +- Removed the `fmt::say` function. + +- Deprecated the `std::initializer_list` overload of `fmt::join` and the array + overload of `fmt::vformat_to`. + +- Made the `` header equivalent to `` by + default. Code that relied on `` pulling in `` + must now either include `` directly or define + `FMT_DEPRECATED_HEAVY_CORE` to opt back in. + +- Improved `wchar_t` support: `fmt::join` now accepts `wchar_t` and other + non-`char` separators, and `fmt::format_to_n` now works with `fmt::runtime` + on `wchar_t` + (https://github.com/fmtlib/fmt/pull/4686, + https://github.com/fmtlib/fmt/issues/4714, + https://github.com/fmtlib/fmt/pull/4715). + Thanks @Yancey2023 and @sunmy2019. + +- Fixed formatting of `std::tm` with a null `tm_zone` + (https://github.com/fmtlib/fmt/pull/4790). Thanks @JiamiLin. + +- Fixed compile-time formatting in `fmt/ranges.h`, `fmt/style.h` and + `fmt/std.h` (https://github.com/fmtlib/fmt/pull/4759). Thanks @j4niwzis. + +- Fixed an ambiguity between `formatter>` in `fmt/std.h` + and `fmt/ranges.h` on C++26 (P3168R2) + (https://github.com/fmtlib/fmt/pull/4761). Thanks @phprus. + +- Fixed a GCC PCH breakage triggered by a scoped `#pragma GCC optimize`. + +- Fixed a TSAN false positive in the locale handling code + (https://github.com/fmtlib/fmt/issues/4755). + +- Fixed compile-time format string checks truncating string literals at + an embedded null byte + (https://github.com/fmtlib/fmt/pull/4732). Thanks @user202729. + +- Fixed an out-of-bounds read in `fmt::vprintf` with a trailing `%` + (https://github.com/fmtlib/fmt/issues/4741, + https://github.com/fmtlib/fmt/pull/4742). Thanks @rmarin. + +- Fixed the return type of the `f(un)lockfile` wrappers on Windows + (https://github.com/fmtlib/fmt/pull/4739). Thanks @MikeVastola. + +- Worked around a CUDA issue when handling UTF-32 literals + (https://github.com/fmtlib/fmt/pull/4719). Thanks @Cazadorro. + +- Fixed missing named-argument validation for compiled format strings + (https://github.com/fmtlib/fmt/pull/4638). Thanks @JaeheonShim. + +- Fixed `fmt::format_to_n` in `` failing to compile when + `` is not transitively included + (https://github.com/fmtlib/fmt/issues/4615). + +- Fixed handling of pointers in format string compilation with + `FMT_BUILTIN_TYPES=0`. + +- Stopped assuming nul termination of the format string in `fmt::printf`. + Thanks @ZUENS2020 for reporting. + +- Fixed a build error when locale support is disabled + (https://github.com/fmtlib/fmt/pull/4627). Thanks @marcel-behlau-elfin. + +- Fixed a fallback range formatter for types with a `container_type` member + (https://github.com/fmtlib/fmt/issues/4123, + https://github.com/fmtlib/fmt/pull/4660). Thanks @Soumik15630m. + +- Fixed C++20 concept detection + (https://github.com/fmtlib/fmt/pull/4653). Thanks @lagoho7. + +- Fixed a clang compilation failure + (https://github.com/fmtlib/fmt/pull/4718). Thanks @mccakit. + +- Fixed various MSVC warnings, including C4305 and conversion warnings on + x86 (https://github.com/fmtlib/fmt/pull/4668, + https://github.com/fmtlib/fmt/pull/4594). + Thanks @kanren3 and @JBerger. + +- Updated the Android Gradle Plugin to 9.x + (https://github.com/fmtlib/fmt/issues/4651, + https://github.com/fmtlib/fmt/pull/4658). Thanks @Soumik15630m. + +- Made various code, build and test improvements + (https://github.com/fmtlib/fmt/pull/4625, + https://github.com/fmtlib/fmt/pull/4639, + https://github.com/fmtlib/fmt/pull/4644, + https://github.com/fmtlib/fmt/pull/4656, + https://github.com/fmtlib/fmt/pull/4680, + https://github.com/fmtlib/fmt/pull/4681, + https://github.com/fmtlib/fmt/pull/4704, + https://github.com/fmtlib/fmt/pull/4710, + https://github.com/fmtlib/fmt/pull/4713, + https://github.com/fmtlib/fmt/pull/4729, + https://github.com/fmtlib/fmt/pull/4751, + https://github.com/fmtlib/fmt/pull/4758). + Thanks @ZephyrLykos, @togunchan, @kagancansit, @BerndPetrovitsch, + @Skylion007, @st0rmbtw and @localspook. + +- Improved documentation, including a rewrite of the format string syntax, + better handling of doxygen tags, documenting `output_file`, fixing CSS so + that whitespace is displayed properly, and various smaller fixes + (https://github.com/fmtlib/fmt/pull/4622, + https://github.com/fmtlib/fmt/pull/4626, + https://github.com/fmtlib/fmt/pull/4631, + https://github.com/fmtlib/fmt/pull/4667, + https://github.com/fmtlib/fmt/pull/4616, + https://github.com/fmtlib/fmt/pull/4748). + Thanks @heavywatal, @ZephyrLykos, @user202729, @ssszcmawo, @bigmoonbit + and @Powerbyte7. + +- Added building of release artifacts and SLSA provenance in CI, added a + CodeQL workflow, and added the security policy in `.github/SECURITY.md`. + # 12.1.0 - 2025-10-29 - Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog From d04adde25436b410f53768c2a03b0a40f3cef7d0 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 2 Jun 2026 09:04:13 -0700 Subject: [PATCH 058/111] Improve docs --- doc/syntax.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/syntax.md b/doc/syntax.md index 2007cb2a..eae72a3b 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -101,10 +101,10 @@ Alignment has no observable effect when the value's natural rendering is already at least as wide as *width*; the value is never truncated to fit. ```c++ -fmt::format("[{:<10}]", "42"); // Result: "[42 ]" -fmt::format("[{:>10}]", "42"); // Result: "[ 42]" -fmt::format("[{:^10}]", "42"); // Result: "[ 42 ]" -fmt::format("[{:*^10}]", "42"); // Result: "[****42****]" -- '*' as fill +fmt::format("[{:<10}]", "42"); // Result: "[42 ]" +fmt::format("[{:>10}]", "42"); // Result: "[ 42]" +fmt::format("[{:^10}]", "42"); // Result: "[ 42 ]" +fmt::format("[{:*^10}]", "42"); // Result: "[****42****]" -- '*' as fill ``` ### Sign @@ -121,8 +121,8 @@ applies to signed integer and floating-point types only. The sign of `-0.0` is preserved in floating-point output. ```c++ -fmt::format("{:+d} {:+d}", 7, -7); // Result: "+7 -7" -fmt::format("{: d} {: d}", 7, -7); // Result: " 7 -7" +fmt::format("{:+d} {:+d}", 7, -7); // Result: "+7 -7" +fmt::format("{: d} {: d}", 7, -7); // Result: " 7 -7" ``` ### Alternate form (`#`) @@ -134,7 +134,7 @@ presentation type: appropriate base prefix (`0b`/`0B`, `0`, or `0x`/`0X`). The case of the prefix follows the case of the type specifier — `0x` for `x`, `0X` for `X`, and so on. -- For floating-point values, it forces the radix character to appear in the +- For floating-point values, it forces the decimal point to appear in the output even if no fractional digits would otherwise be emitted, and prevents the `g`/`G` presentation types from removing trailing zeros from the significand. @@ -153,7 +153,7 @@ Zero padding: - applies only to numeric types; - has no effect on `inf` or `nan`; -- is silently ignored when an explicit *align* is also present. +- is ignored when an explicit *align* is also present. ### Width @@ -174,9 +174,9 @@ This keeps fixed *width* values visually consistent in monospace renderings that combine Latin and CJK text. ```c++ -fmt::format("[{:6}]", 42); // Result: "[ 42]" -- right-aligned by default -fmt::format("[{:6}]", "hi"); // Result: "[hi ]" -- left-aligned by default -fmt::format("[{:{}}]", 42, 6); // Result: "[ 42]" -- width from an argument +fmt::format("[{:6}]", 42); // Result: "[ 42]" -- right-aligned by default +fmt::format("[{:6}]", "hi"); // Result: "[hi ]" -- left-aligned by default +fmt::format("[{:{}}]", 42, 6); // Result: "[ 42]" -- width from an argument ``` ### Precision @@ -187,9 +187,9 @@ supplied as a nested replacement field for runtime evaluation. | Type | Meaning of `.precision` | |-------------------------------|-----------------------------------------------| -| `e`, `E`, `f`, `F` | Digits emitted after the radix character. | +| `e`, `E`, `f`, `F` | Digits emitted after the decimal point. | | `g`, `G` | Total number of significant digits. | -| `a`, `A` | Digits after the radix character in the hexadecimal significand. If omitted, just enough digits are emitted to round-trip the value exactly. | +| `a`, `A` | Digits after the decimal point in the hexadecimal significand. If omitted, just enough digits are emitted to round-trip the value exactly. | | Strings (`s`, `?`, or default) | Upper bound on the number of code points copied from the value. | A *precision* is not accepted for integer, character, boolean, or pointer @@ -197,10 +197,10 @@ types. When a *precision* limits the number of characters taken from a C string, the string must still be null-terminated. ```c++ -fmt::format("{:.2f}", 3.14159); // Result: "3.14" -fmt::format("{:.3g}", 3.14159); // Result: "3.14" -fmt::format("{:.4}", "hello, world"); // Result: "hell" -fmt::format("{:.{}f}", 3.14159, 4); // Result: "3.1416" -- precision from an argument +fmt::format("{:.2f}", 3.14159); // Result: "3.14" +fmt::format("{:.3g}", 3.14159); // Result: "3.14" +fmt::format("{:.4}", "hello, world"); // Result: "hell" +fmt::format("{:.{}f}", 3.14159, 4); // Result: "3.1416" -- precision from an argument ``` ### Locale (`L`) @@ -208,7 +208,7 @@ fmt::format("{:.{}f}", 3.14159, 4); // Result: "3.1416" -- precision from an ar The `L` flag selects locale-sensitive formatting for numeric types. The formatter inspects the C++ locale supplied to the formatting function (or the global locale, if none was passed) and inserts the locale's digit -grouping characters and — for floating-point values — its radix character. +grouping characters and — for floating-point values — its decimal point. The flag has no effect on non-numeric types. ```c++ @@ -239,7 +239,7 @@ grouped below by the value categories they apply to. fmt::format("{:d} {:#x} {:#o} {:#b}", 42, 42, 42, 42); // Result: "42 0x2a 052 0b101010" -fmt::format("{:#06x}", 0xfe); // # adds the prefix, 06 zero-pads to width 6 +fmt::format("{:#06x}", 0xfe); // # adds the prefix, 06 zero-pads to width 6 // Result: "0x00fe" ``` From dea98ad22886479a68b7e425fedaec2c6de75eb0 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 2 Jun 2026 09:27:47 -0700 Subject: [PATCH 059/111] Improve docs --- doc/syntax.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/syntax.md b/doc/syntax.md index eae72a3b..88e45cc3 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -669,11 +669,9 @@ fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e}); // Output: 3.141593, 2.718282 ``` - -## Examples +## A Combined Example -Most of the format options are introduced alongside their grammar in the -sections above. The example below combines several of them — nested +The example below ties together several elements introduced above — nested replacement fields, fill characters, and centering — to draw a fixed-width box around a message: From 889da4274e08060ee73da54dad56cfc3e4dac47f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 2 Jun 2026 10:05:34 -0700 Subject: [PATCH 060/111] Improve docs --- doc/syntax.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/syntax.md b/doc/syntax.md index 88e45cc3..b063042f 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -104,7 +104,7 @@ already at least as wide as *width*; the value is never truncated to fit. fmt::format("[{:<10}]", "42"); // Result: "[42 ]" fmt::format("[{:>10}]", "42"); // Result: "[ 42]" fmt::format("[{:^10}]", "42"); // Result: "[ 42 ]" -fmt::format("[{:*^10}]", "42"); // Result: "[****42****]" -- '*' as fill +fmt::format("[{:*^10}]", "42"); // Result: "[****42****]" - '*' as fill ``` ### Sign @@ -174,9 +174,12 @@ This keeps fixed *width* values visually consistent in monospace renderings that combine Latin and CJK text. ```c++ -fmt::format("[{:6}]", 42); // Result: "[ 42]" -- right-aligned by default -fmt::format("[{:6}]", "hi"); // Result: "[hi ]" -- left-aligned by default -fmt::format("[{:{}}]", 42, 6); // Result: "[ 42]" -- width from an argument +fmt::format("[{:6}]", 42); +// Result: "[ 42]" - right-aligned by default +fmt::format("[{:6}]", "hi"); +// Result: "[hi ]" - left-aligned by default +fmt::format("[{:{}}]", 42, 6); +// Result: "[ 42]" - width from an argument ``` ### Precision @@ -200,7 +203,8 @@ string, the string must still be null-terminated. fmt::format("{:.2f}", 3.14159); // Result: "3.14" fmt::format("{:.3g}", 3.14159); // Result: "3.14" fmt::format("{:.4}", "hello, world"); // Result: "hell" -fmt::format("{:.{}f}", 3.14159, 4); // Result: "3.1416" -- precision from an argument +fmt::format("{:.{}f}", 3.14159, 4); +// Result: "3.1416" - precision from an argument ``` ### Locale (`L`) From f577c1f11dc9795ff8e6e3ad743cd9b5b9c87394 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 2 Jun 2026 10:30:08 -0700 Subject: [PATCH 061/111] Improve docs --- doc/syntax.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/syntax.md b/doc/syntax.md index b063042f..478da7f7 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -54,9 +54,9 @@ nested replacement field — `{}` or `{arg_id}` — in which case it takes its value from an integer argument at runtime. Nested fields accept only an *arg_id*; they cannot themselves contain a *format_spec*. -## Standard Format Specification +## Format Specification -The grammar below describes the *format_spec* shared by the standard built-in +The grammar below describes the *format_spec* shared by the built-in types — integers, floating-point values, characters, strings, booleans, and pointers — as well as by any user-defined type whose `formatter` reuses fmt's parser. From ebb9f0807ad6fd994731501ce89780fa2930ef52 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 2 Jun 2026 11:02:57 -0700 Subject: [PATCH 062/111] Consistent plurality --- doc/syntax.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/syntax.md b/doc/syntax.md index 478da7f7..48f48080 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -286,10 +286,10 @@ A C string (`char*` or `const char*`) accepts both the string presentation types and `p`, so the same value can be formatted as either text or an address. -## Chrono Format Specifications +## Chrono Format Specification -Format specifications for chrono duration and time point types as well as -`std::tm` have the following syntax: +The format specification for chrono duration and time point types as well +as `std::tm` has the following syntax:
range_format_spec ::= ["n"][range_type][":" range_underlying_spec]

From 2bab3cc0cc5fe77b5be9e745e380a8759fe539db Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 14:07:40 -0700
Subject: [PATCH 063/111] Update changelog

---
 ChangeLog.md | 42 +++++++++++++++++++++++++++++++-----------
 1 file changed, 31 insertions(+), 11 deletions(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index f7deab4b..17560b81 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,8 +1,16 @@
 # 12.2.0 - TBD
 
-- Added a C11 API with type-safe formatting via `_Generic`. The new `fmt-c`
-  library and `fmt/fmt-c.h` header allow C code to use {fmt}'s formatter
-  with compile-time type checking
+- Added a C11 API that brings fast, type-safe formatting to C. The new
+  `fmt-c` library and `fmt/fmt-c.h` header use `_Generic` to dispatch on
+  argument types and outperform `printf`/`sprintf`. For example:
+
+  ```c
+  #include 
+
+  char buf[100];
+  int n = fmt_format(buf, sizeof(buf), "The answer is {}.", 42);
+  ```
+
   (https://github.com/fmtlib/fmt/issues/4663,
   https://github.com/fmtlib/fmt/pull/4671,
   https://github.com/fmtlib/fmt/pull/4696,
@@ -11,11 +19,8 @@
   https://github.com/fmtlib/fmt/pull/4712).
   Thanks @Soumik15630m, @yrlf and @localspook.
 
-- Reduced binary size of debug builds (~200k to ~85k in the bloat test) and
-  improved compile speed when consteval is unavailable.
-
 - Added a separate `fmt::fmt-module` CMake target for C++20 modules and a
-  Linux CI workflow that exercises module-based builds
+  CI workflow that exercises module-based builds
   (https://github.com/fmtlib/fmt/issues/4684,
   https://github.com/fmtlib/fmt/pull/4685,
   https://github.com/fmtlib/fmt/issues/4707,
@@ -27,23 +32,38 @@
 - Enabled the full Dragonbox lookup cache by default for floating-point
   formatting unless optimizing for binary size (`__OPTIMIZE_SIZE__`),
   giving a ~10–25% speedup. Thanks Matthias Kretz for the suggestion.
+  Average time per `double` on Apple M1 Pro (clang 17, random digits,
+  smaller is better) measured with
+  [dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark):
 
-- Improved integer formatting throughput by ~3%
+  | Method                  | Time (ns) |
+  | ----------------------- | --------: |
+  | fmt (full)              |     22.07 |
+  | fmt (compact)           |     29.55 |
+  | ryu                     |     35.21 |
+  | double-conversion       |     81.81 |
+  | `sprintf`               |    726.27 |
+  | `ostringstream`         |    864.34 |
+
+- Improved integer formatting performance by ~3%
   (https://github.com/fmtlib/fmt/pull/4630). Thanks @user202729.
 
 - Optimized formatting into back-insert iterators by using bulk container
   append/insert methods (e.g. on `std::vector` and custom string
   types) (https://github.com/fmtlib/fmt/pull/4679). Thanks @user202729.
 
+- Reduced binary size of debug builds (~200k to ~85k in the bloat test) and
+  improved compile speed when `consteval` is unavailable.
+
 - Made path formatting lossless, preserving ill-formed UTF-16
   sequences when converting `std::filesystem::path` to a narrow string.
 
-- Added overloads of `fmt::println` that take a `fmt::text_style`
-  (https://github.com/fmtlib/fmt/pull/4782). Thanks @ahoarau.
-
 - Added support for formatting `std::unexpected`
   (https://github.com/fmtlib/fmt/pull/4675). Thanks @17steen.
 
+- Added overloads of `fmt::println` that take a `fmt::text_style`
+  (https://github.com/fmtlib/fmt/pull/4782). Thanks @ahoarau.
+
 - Added support for positional arguments as width and precision specifiers
   in `fmt::printf` (https://github.com/fmtlib/fmt/pull/4643).
   Thanks @KareemOtoum.

From 11488989c64aff07a4336a96665fa91db8d6b086 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 14:12:40 -0700
Subject: [PATCH 064/111] Update changelog

---
 ChangeLog.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index 17560b81..aae0ef08 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -4,7 +4,7 @@
   `fmt-c` library and `fmt/fmt-c.h` header use `_Generic` to dispatch on
   argument types and outperform `printf`/`sprintf`. For example:
 
-  ```c
+  ```c++
   #include 
 
   char buf[100];
@@ -85,7 +85,7 @@
   (https://github.com/fmtlib/fmt/pull/4731,
   https://github.com/fmtlib/fmt/pull/4770). Thanks @user202729 and @phprus.
 
-- Added the `FMT_USE_FLOCKFILE` macro to force `has_flockfile` to false
+- Added the `FMT_USE_FLOCKFILE` macro to disable the use of `flockfile`
   (https://github.com/fmtlib/fmt/issues/4646,
   https://github.com/fmtlib/fmt/pull/4666). Thanks @MikeVastola.
 

From a597d06854a513067f93f8378a9b534354642024 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 2 Jun 2026 15:14:38 -0700
Subject: [PATCH 065/111] Bump
 slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml
 (#4792)

Bumps [slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml](https://github.com/slsa-framework/slsa-github-generator) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/slsa-framework/slsa-github-generator/releases)
- [Changelog](https://github.com/slsa-framework/slsa-github-generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/slsa-framework/slsa-github-generator/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/release.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f10d8577..868c1e8c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -61,7 +61,7 @@ jobs:
       actions: read
       id-token: write
       contents: write
-    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
+    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
     with:
       base64-subjects: ${{ needs.build.outputs.hashes }}
       provenance-name: "fmt-${{ github.event.release.tag_name }}.intoto.jsonl"

From 57563a7ebd69d3a99501c401535d168b7bb64a97 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 2 Jun 2026 15:15:23 -0700
Subject: [PATCH 066/111] Bump github/codeql-action from 4.35.1 to 4.36.1
 (#4793)

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.1 to 4.36.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...87557b9c84dde89fdd9b10e88954ac2f4248e463)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/codeql.yml       | 4 ++--
 .github/workflows/supply-chain.yml | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 69259c98..3272f073 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -19,7 +19,7 @@ jobs:
     steps:
     - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
 
-    - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5
+    - uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v3.29.5
       with:
         languages: c-cpp
 
@@ -28,6 +28,6 @@ jobs:
         cmake -B build -DFMT_DOC=OFF -DFMT_TEST=OFF
         cmake --build build --parallel
 
-    - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5
+    - uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v3.29.5
       with:
         category: /language:c-cpp
diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml
index 539c51dc..4c0f7174 100644
--- a/.github/workflows/supply-chain.yml
+++ b/.github/workflows/supply-chain.yml
@@ -42,6 +42,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: Upload to code-scanning
-        uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5
+        uses: github/codeql-action/upload-sarif@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v3.29.5
         with:
           sarif_file: results.sarif

From 70f794e395a52be64ed54182c2b22adb5f1d1c30 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 15:16:15 -0700
Subject: [PATCH 067/111] Bump version

---
 include/fmt/base.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/include/fmt/base.h b/include/fmt/base.h
index bcd06319..20cccc2b 100644
--- a/include/fmt/base.h
+++ b/include/fmt/base.h
@@ -21,7 +21,7 @@
 #endif
 
 // The fmt library version in the form major * 10000 + minor * 100 + patch.
-#define FMT_VERSION 120101
+#define FMT_VERSION 120200
 
 // Detect compiler versions.
 #if defined(__clang__) && !defined(__ibmxl__)

From a54dbce57958ac8470d6ba06f396b600b74b00d8 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 15:17:59 -0700
Subject: [PATCH 068/111] Update branch

---
 support/release.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/support/release.py b/support/release.py
index caabf431..25b719f0 100755
--- a/support/release.py
+++ b/support/release.py
@@ -87,7 +87,7 @@ if __name__ == '__main__':
 
     branch = args.get('')
     if branch is None:
-        branch = 'master'
+        branch = 'main'
     if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
         clean_checkout(fmt_repo, branch)
 

From 9cd653cac1762df5a4f7e3b526410108a05ab2f4 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 15:32:58 -0700
Subject: [PATCH 069/111] Use venv in the release script

---
 support/release.py | 39 +++++++++++++++++++++++++++++++++------
 1 file changed, 33 insertions(+), 6 deletions(-)

diff --git a/support/release.py b/support/release.py
index 25b719f0..1e7961a7 100755
--- a/support/release.py
+++ b/support/release.py
@@ -61,11 +61,14 @@ def clean_checkout(repo, branch):
 
 
 class Runner:
-    def __init__(self, cwd):
+    def __init__(self, cwd, env=None):
         self.cwd = cwd
+        self.env = env
 
     def __call__(self, *args, **kwargs):
         kwargs['cwd'] = kwargs.get('cwd', self.cwd)
+        if self.env is not None:
+            kwargs['env'] = kwargs.get('env', self.env)
         check_call(args, **kwargs)
 
 
@@ -80,6 +83,26 @@ def create_build_env():
     return env
 
 
+def create_doc_env(env, fmt_repo):
+    """Create a virtualenv with the pinned documentation dependencies and
+    return an environment dict with it prepended to PATH. This ensures the
+    docs are built with the exact mkdocs/mkdocstrings versions required by the
+    custom handler, regardless of what is installed system-wide."""
+    venv_dir = os.path.join(env.build_dir, 'venv')
+    shutil.rmtree(venv_dir, ignore_errors=True)
+    check_call([sys.executable, '-m', 'venv', venv_dir])
+    venv_bin = os.path.join(venv_dir, 'bin')
+    pip = os.path.join(venv_bin, 'pip')
+    check_call([pip, 'install', '--quiet', '--upgrade', 'pip'])
+    requirements = os.path.join(
+        fmt_repo.dir, 'support', 'doc-requirements.txt')
+    check_call(
+        [pip, 'install', '--quiet', '--require-hashes', '-r', requirements])
+    doc_env = os.environ.copy()
+    doc_env['PATH'] = venv_bin + os.pathsep + doc_env.get('PATH', '')
+    return doc_env
+
+
 if __name__ == '__main__':
     args = docopt.docopt(__doc__)
     env = create_build_env()
@@ -151,10 +174,14 @@ if __name__ == '__main__':
     fmt_repo.add(changelog)
     fmt_repo.commit('-m', 'Update version')
 
-    # Build the docs locally; the source zip is now built and attached to the
-    # release in CI by .github/workflows/release.yml, which also generates a
-    # SLSA provenance attestation for it.
-    run = Runner(fmt_repo.dir)
+    # Build the docs locally in a virtualenv with the pinned doc dependencies;
+    # the source zip is now built and attached to the release in CI by
+    # .github/workflows/release.yml, which also generates a SLSA provenance
+    # attestation for it. The venv is prepended to PATH so that CMake's
+    # find_program(MKDOCS mkdocs) and the ./mkdocs deploy step below both pick
+    # up the correct mkdocs/mkdocstrings versions.
+    doc_env = create_doc_env(env, fmt_repo)
+    run = Runner(fmt_repo.dir, env=doc_env)
     run('cmake', '.')
     run('make', 'doc')
 
@@ -176,4 +203,4 @@ if __name__ == '__main__':
                             '{response.status} {response.reason}')
 
     short_version = '.'.join(version.split('.')[:-1])
-    check_call(['./mkdocs', 'deploy', short_version])
+    check_call(['./mkdocs', 'deploy', short_version], env=doc_env)

From f2995637c2d48a050d1fcebb2b67a341a75b24b6 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 16:21:20 -0700
Subject: [PATCH 070/111] Fix release script

---
 support/release.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/support/release.py b/support/release.py
index 1e7961a7..4e34fe62 100755
--- a/support/release.py
+++ b/support/release.py
@@ -88,7 +88,9 @@ def create_doc_env(env, fmt_repo):
     return an environment dict with it prepended to PATH. This ensures the
     docs are built with the exact mkdocs/mkdocstrings versions required by the
     custom handler, regardless of what is installed system-wide."""
-    venv_dir = os.path.join(env.build_dir, 'venv')
+    # Use an absolute path so the venv resolves on PATH regardless of the
+    # working directory the build steps run in.
+    venv_dir = os.path.abspath(os.path.join(env.build_dir, 'venv'))
     shutil.rmtree(venv_dir, ignore_errors=True)
     check_call([sys.executable, '-m', 'venv', venv_dir])
     venv_bin = os.path.join(venv_dir, 'bin')

From ae2121216cdf5608c84cea94fa47b2f426d76130 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 17:07:17 -0700
Subject: [PATCH 071/111] Update changelog

---
 ChangeLog.md | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index aae0ef08..a9d37b2c 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -17,7 +17,7 @@
   https://github.com/fmtlib/fmt/issues/4693,
   https://github.com/fmtlib/fmt/pull/4694,
   https://github.com/fmtlib/fmt/pull/4712).
-  Thanks @Soumik15630m, @yrlf and @localspook.
+  Thanks @Soumik15630m, @Ferdi265 and @localspook.
 
 - Added a separate `fmt::fmt-module` CMake target for C++20 modules and a
   CI workflow that exercises module-based builds
@@ -87,14 +87,13 @@
 
 - Added the `FMT_USE_FLOCKFILE` macro to disable the use of `flockfile`
   (https://github.com/fmtlib/fmt/issues/4646,
-  https://github.com/fmtlib/fmt/pull/4666). Thanks @MikeVastola.
+  https://github.com/fmtlib/fmt/pull/4666). Thanks @mvastola.
 
 - Added `include_guard(GLOBAL)` so that {fmt} can be used in multiple
   submodules of the same project
   (https://github.com/fmtlib/fmt/pull/4672). Thanks @torsten48.
 
-- Added `constexpr` to argument `value` constructors and to
-  `static_format_result` members
+- Improved `constexpr` support
   (https://github.com/fmtlib/fmt/pull/4659,
   https://github.com/fmtlib/fmt/pull/4591).
   Thanks @elbeno and @17steen.
@@ -125,7 +124,7 @@
   Thanks @Yancey2023 and @sunmy2019.
 
 - Fixed formatting of `std::tm` with a null `tm_zone`
-  (https://github.com/fmtlib/fmt/pull/4790). Thanks @JiamiLin.
+  (https://github.com/fmtlib/fmt/pull/4790). Thanks @Carmel0.
 
 - Fixed compile-time formatting in `fmt/ranges.h`, `fmt/style.h` and
   `fmt/std.h` (https://github.com/fmtlib/fmt/pull/4759). Thanks @j4niwzis.
@@ -145,10 +144,10 @@
 
 - Fixed an out-of-bounds read in `fmt::vprintf` with a trailing `%`
   (https://github.com/fmtlib/fmt/issues/4741,
-  https://github.com/fmtlib/fmt/pull/4742). Thanks @rmarin.
+  https://github.com/fmtlib/fmt/pull/4742). Thanks @Algunenano.
 
-- Fixed the return type of the `f(un)lockfile` wrappers on Windows
-  (https://github.com/fmtlib/fmt/pull/4739). Thanks @MikeVastola.
+- Fixed the return type of the `f(un)lockfile`   wrappers on Windows
+  (https://github.com/fmtlib/fmt/pull/4739). Thanks @mvastola.
 
 - Worked around a CUDA issue when handling UTF-32 literals
   (https://github.com/fmtlib/fmt/pull/4719). Thanks @Cazadorro.
@@ -174,7 +173,7 @@
   https://github.com/fmtlib/fmt/pull/4660). Thanks @Soumik15630m.
 
 - Fixed C++20 concept detection
-  (https://github.com/fmtlib/fmt/pull/4653). Thanks @lagoho7.
+  (https://github.com/fmtlib/fmt/pull/4653). Thanks @tearfur.
 
 - Fixed a clang compilation failure
   (https://github.com/fmtlib/fmt/pull/4718). Thanks @mccakit.
@@ -182,7 +181,7 @@
 - Fixed various MSVC warnings, including C4305 and conversion warnings on
   x86 (https://github.com/fmtlib/fmt/pull/4668,
   https://github.com/fmtlib/fmt/pull/4594).
-  Thanks @kanren3 and @JBerger.
+  Thanks @kanren3 and @blizzard4591.
 
 - Updated the Android Gradle Plugin to 9.x
   (https://github.com/fmtlib/fmt/issues/4651,

From 9764b90ddb39383c8a73e507e14f327c79ebcecf Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 17:17:04 -0700
Subject: [PATCH 072/111] Drop redundant section

---
 README.md | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/README.md b/README.md
index 38632f33..fabb2e83 100644
--- a/README.md
+++ b/README.md
@@ -380,12 +380,6 @@ If you are aware of other projects using this library, please let me
 know by [email](mailto:victor.zverovich@gmail.com) or by submitting an
 [issue](https://github.com/fmtlib/fmt/issues).
 
-# Sponsors
-
-{fmt} development is supported by individual sponsors. If you find this
-library useful, please consider [sponsoring its development on GitHub
-Sponsors](https://github.com/sponsors/vitaut).
-
 # Maintainers
 
 The {fmt} library is maintained by Victor Zverovich

From 9afcd929ba675d942371de8ca1832f9d5d09b4bd Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 2 Jun 2026 17:32:47 -0700
Subject: [PATCH 073/111] Fix release script

---
 support/release.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/support/release.py b/support/release.py
index 4e34fe62..58206755 100755
--- a/support/release.py
+++ b/support/release.py
@@ -35,6 +35,9 @@ class Git:
     def clone(self, *args):
         return self.call('clone', list(args) + [self.dir])
 
+    def fetch(self, *args):
+        return self.call('fetch', args, cwd=self.dir)
+
     def commit(self, *args):
         return self.call('commit', args, cwd=self.dir)
 
@@ -56,8 +59,11 @@ class Git:
 
 def clean_checkout(repo, branch):
     repo.clean('-f', '-d')
-    repo.reset('--hard')
+    repo.fetch('origin')
     repo.checkout(branch)
+    # Hard-reset to the remote so a reused clone picks up new commits
+    # instead of building from stale local state.
+    repo.reset('--hard', 'origin/' + branch)
 
 
 class Runner:

From ffd8397db10ceb6e8d663097c647ad8fae71a93f Mon Sep 17 00:00:00 2001
From: Ferdinand Bachmann 
Date: Fri, 5 Jun 2026 15:40:30 +0200
Subject: [PATCH 074/111] Add `fmt_print` to C API (#4789)

* Add FMT_FORMAT_ARGS(fmt, ...) macro to C API to simplify user-defined format wrappers

* Add fmt_print to C API
---
 include/fmt/fmt-c.h | 19 ++++++++++++++-----
 src/fmt-c.cc        | 34 +++++++++++++++++++++++++++++-----
 2 files changed, 43 insertions(+), 10 deletions(-)

diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h
index 20d163a7..e918e77c 100644
--- a/include/fmt/fmt-c.h
+++ b/include/fmt/fmt-c.h
@@ -10,6 +10,7 @@
 
 #include   // bool
 #include    // size_t
+#include     // FILE
 
 #ifdef __cplusplus
 extern "C" {
@@ -46,6 +47,8 @@ enum { fmt_error = -1, fmt_error_invalid_arg = -2 };
 
 int fmt_vformat(char* buffer, size_t size, const char* fmt, const fmt_arg* args,
                 size_t num_args);
+int fmt_vprint(FILE* stream, const char* fmt, const fmt_arg* args,
+               size_t num_args);
 
 #ifdef __cplusplus
 }
@@ -181,11 +184,17 @@ typedef enum {} fmt_signed_char;
     (fmt_arg[]) { FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__) }
 #  define FMT_EXPAND(v) v
 
-#  define fmt_format(buffer, size, fmt, ...)                              \
-    fmt_vformat((buffer), (size), (fmt),                                  \
-                FMT_EXPAND(FMT_VA_SELECT(FMT_MAKE_NULL, FMT_MAKE_ARGLIST, \
-                                         ##__VA_ARGS__)(__VA_ARGS__)),    \
-                FMT_NARG(, ##__VA_ARGS__))
+#  define FMT_FORMAT_ARGS(fmt, ...)                               \
+    (fmt),                                                        \
+        FMT_EXPAND(FMT_VA_SELECT(FMT_MAKE_NULL, FMT_MAKE_ARGLIST, \
+                                 ##__VA_ARGS__)(__VA_ARGS__)),    \
+        FMT_NARG(, ##__VA_ARGS__)
+
+#  define fmt_format(buffer, size, fmt, ...) \
+    fmt_vformat((buffer), (size), FMT_FORMAT_ARGS((fmt), ##__VA_ARGS__))
+
+#  define fmt_print(stream, fmt, ...) \
+    fmt_vprint((stream), FMT_FORMAT_ARGS((fmt), ##__VA_ARGS__))
 
 #endif  // __cplusplus
 
diff --git a/src/fmt-c.cc b/src/fmt-c.cc
index 86de48e9..be1e5278 100644
--- a/src/fmt-c.cc
+++ b/src/fmt-c.cc
@@ -9,12 +9,12 @@
 
 #include 
 
-extern "C" int fmt_vformat(char* buffer, size_t size, const char* fmt,
-                           const fmt_arg* args, size_t num_args) {
-  constexpr size_t max_args = 16;
-  if (num_args > max_args) return fmt_error_invalid_arg;
+constexpr size_t max_c_format_args = 16;
+static int convert_c_format_args(
+    fmt::basic_format_arg* format_args,
+    const fmt_arg* args, size_t num_args) {
+  if (num_args > max_c_format_args) return fmt_error_invalid_arg;
 
-  fmt::basic_format_arg format_args[max_args];
   for (size_t i = 0; i < num_args; ++i) {
     switch (args[i].type) {
     case fmt_int:    format_args[i] = args[i].value.int_value; break;
@@ -31,6 +31,15 @@ extern "C" int fmt_vformat(char* buffer, size_t size, const char* fmt,
     default:          return fmt_error_invalid_arg;
     }
   }
+  return 0;
+}
+
+extern "C" int fmt_vformat(char* buffer, size_t size, const char* fmt,
+                           const fmt_arg* args, size_t num_args) {
+  fmt::basic_format_arg format_args[max_c_format_args];
+  int error = convert_c_format_args(format_args, args, num_args);
+  if (error != 0) return error;
+
   FMT_TRY {
     auto result = fmt::vformat_to_n(
         buffer, size, fmt,
@@ -40,3 +49,18 @@ extern "C" int fmt_vformat(char* buffer, size_t size, const char* fmt,
   FMT_CATCH(...) {}
   return fmt_error;
 }
+
+extern "C" int fmt_vprint(FILE* stream, const char* fmt, const fmt_arg* args,
+                          size_t num_args) {
+  fmt::basic_format_arg format_args[max_c_format_args];
+  int error = convert_c_format_args(format_args, args, num_args);
+  if (error != 0) return error;
+
+  FMT_TRY {
+    fmt::vprint(stream, fmt,
+                fmt::format_args(format_args, static_cast(num_args)));
+    return 0;
+  }
+  FMT_CATCH(...) {}
+  return fmt_error;
+}

From e6a8751df7a68144952d5b21b0d4a76327b6c9f7 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Fri, 5 Jun 2026 08:06:29 -0700
Subject: [PATCH 075/111] Restore api.html redirect to fix broken deep links

Re-enable generation of the api.html redirect page during docs deploy so
legacy links such as api.html#udt forward to the new MkDocs api/ page with
their URL hash preserved.
---
 support/mkdocs | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/support/mkdocs b/support/mkdocs
index 94eddae3..e595a2be 100755
--- a/support/mkdocs
+++ b/support/mkdocs
@@ -71,15 +71,18 @@ if len(args) > 0:
     # mike stages files added by deploy for deletion for unclear reason,
     # undo it.
     ret = call(['git', 'reset', '--hard'], cwd=site_dir)
-    if False:
-      os.makedirs(current_doc_path, exist_ok=True)
-      redirect_page_path = os.path.join(current_doc_path, 'api.html')
-      with open(redirect_page_path, "w") as file:
-        file.write(redirect_page)
-      ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
-      if ret != 0:
-        sys.exit(ret)
-      ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
+    if ret != 0:
+      sys.exit(ret)
+    # Generate a redirect page from the old Sphinx api.html to the new api/
+    # page, preserving the URL hash so links such as api.html#udt keep working.
+    os.makedirs(current_doc_path, exist_ok=True)
+    redirect_page_path = os.path.join(current_doc_path, 'api.html')
+    with open(redirect_page_path, "w") as file:
+      file.write(redirect_page)
+    ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
+    if ret != 0:
+      sys.exit(ret)
+    ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
     sys.exit(ret)
   elif not command.startswith('-'):
     args += ['-f', config_path]

From 1819f7fa43e2bfb2c936ed1b2cfda098d5f204c8 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Mon, 8 Jun 2026 07:35:25 +0200
Subject: [PATCH 076/111] Update changelog

---
 ChangeLog.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index a9d37b2c..7c3430af 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -7,8 +7,7 @@
   ```c++
   #include 
 
-  char buf[100];
-  int n = fmt_format(buf, sizeof(buf), "The answer is {}.", 42);
+  fmt_print(stdout, "The answer is {}.\n", 42);
   ```
 
   (https://github.com/fmtlib/fmt/issues/4663,
@@ -16,7 +15,8 @@
   https://github.com/fmtlib/fmt/pull/4696,
   https://github.com/fmtlib/fmt/issues/4693,
   https://github.com/fmtlib/fmt/pull/4694,
-  https://github.com/fmtlib/fmt/pull/4712).
+  https://github.com/fmtlib/fmt/pull/4712,
+  https://github.com/fmtlib/fmt/pull/4789).
   Thanks @Soumik15630m, @Ferdi265 and @localspook.
 
 - Added a separate `fmt::fmt-module` CMake target for C++20 modules and a

From 841040e781f9ad3ceca12bd3069804fe48a1ab0b Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Mon, 8 Jun 2026 07:41:38 +0200
Subject: [PATCH 077/111] Attach release artifacts to draft via
 workflow_dispatch

Draft releases do not fire the `release: created` event, so the release
workflow never ran and the source zip and SLSA provenance were not
attached to the draft. Trigger the workflow explicitly from release.py
via workflow_dispatch, passing the tag to attach to and the ref to build
from, and resolve the tag/ref in the workflow for both event types.
---
 .github/workflows/release.yml | 35 +++++++++++++++++++++++++++++------
 support/release.py            | 23 +++++++++++++++++++----
 2 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 868c1e8c..13a5ee1d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,17 +1,33 @@
-# Builds the release source package in CI when a draft release is created
-# (typically via support/release.py), uploads the zip to that release, and
+# Builds the release source package in CI for a draft release (typically
+# created via support/release.py), uploads the zip to that release, and
 # attaches a SLSA v1.0 provenance attestation generated by the OpenSSF
 # slsa-github-generator. The maintainer reviews the draft (which by then has
 # both the zip and *.intoto.jsonl attached) and clicks Publish to finalize.
 #
 # This makes the provenance attest to the actual build that produced the
 # artifact, rather than just attesting to a hash observed after the fact.
+#
+# GitHub does not fire `release: created` for draft releases, so release.py
+# triggers this workflow explicitly via workflow_dispatch, passing the tag to
+# attach to and the ref to build from. The `release: created` trigger is kept
+# for non-draft releases created directly through the GitHub UI.
 
 name: release
 
 on:
   release:
     types: [created]
+  workflow_dispatch:
+    inputs:
+      tag_name:
+        description: "Release tag to attach the artifacts to (e.g. 12.2.0)"
+        required: true
+        type: string
+      ref:
+        description: "Git ref to build the source package from"
+        required: false
+        default: release
+        type: string
 
 permissions: read-all
 
@@ -24,11 +40,18 @@ jobs:
     outputs:
       hashes: ${{ steps.hash.outputs.hashes }}
       package: ${{ steps.build.outputs.package }}
+      tag: ${{ steps.vars.outputs.tag }}
     steps:
+      - name: Resolve tag and ref for both event types
+        id: vars
+        run: |
+          echo "tag=${{ github.event.release.tag_name || inputs.tag_name }}" >> "$GITHUB_OUTPUT"
+          echo "ref=${{ github.event.release.target_commitish || inputs.ref }}" >> "$GITHUB_OUTPUT"
+
       - name: Checkout the release ref
         uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
         with:
-          ref: ${{ github.event.release.target_commitish }}
+          ref: ${{ steps.vars.outputs.ref }}
           persist-credentials: false
 
       - name: Build source zip via CPack
@@ -51,7 +74,7 @@ jobs:
         env:
           GH_TOKEN: ${{ github.token }}
         run: |
-          gh release upload "${{ github.event.release.tag_name }}" \
+          gh release upload "${{ steps.vars.outputs.tag }}" \
             "${{ steps.build.outputs.package }}" \
             --repo "${{ github.repository }}" --clobber
 
@@ -64,6 +87,6 @@ jobs:
     uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
     with:
       base64-subjects: ${{ needs.build.outputs.hashes }}
-      provenance-name: "fmt-${{ github.event.release.tag_name }}.intoto.jsonl"
+      provenance-name: "fmt-${{ needs.build.outputs.tag }}.intoto.jsonl"
       upload-assets: true
-      upload-tag-name: ${{ github.event.release.tag_name }}
+      upload-tag-name: ${{ needs.build.outputs.tag }}
diff --git a/support/release.py b/support/release.py
index 58206755..b3585b84 100755
--- a/support/release.py
+++ b/support/release.py
@@ -193,10 +193,10 @@ if __name__ == '__main__':
     run('cmake', '.')
     run('make', 'doc')
 
-    # Create a draft release on GitHub. The release workflow triggers on
-    # `release: created`, builds the source zip from `target_commitish`, and
-    # attaches the zip plus *.intoto.jsonl provenance to this draft. After
-    # reviewing the draft, the maintainer clicks Publish to finalize.
+    # Create a draft release on GitHub, then trigger the release workflow to
+    # build the source zip from the `release` branch and attach the zip plus
+    # *.intoto.jsonl provenance to this draft. After reviewing the draft, the
+    # maintainer clicks Publish to finalize.
     fmt_repo.push('origin', 'release')
     auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
     req = urllib.request.Request(
@@ -210,5 +210,20 @@ if __name__ == '__main__':
             raise Exception(f'Failed to create a release ' +
                             '{response.status} {response.reason}')
 
+    # Draft releases do not fire `release: created`, so explicitly dispatch the
+    # release workflow. It runs from the default branch (`branch`) but builds
+    # and uploads artifacts from the `release` branch for tag `version`.
+    dispatch_req = urllib.request.Request(
+        'https://api.github.com/repos/fmtlib/fmt/actions/workflows/'
+        'release.yml/dispatches',
+        data=json.dumps({'ref': branch,
+                         'inputs': {'tag_name': version,
+                                    'ref': 'release'}}).encode('utf-8'),
+        headers=auth_headers, method='POST')
+    with urllib.request.urlopen(dispatch_req) as response:
+        if response.status != 204:
+            raise Exception('Failed to dispatch the release workflow ' +
+                            f'{response.status} {response.reason}')
+
     short_version = '.'.join(version.split('.')[:-1])
     check_call(['./mkdocs', 'deploy', short_version], env=doc_env)

From c1c7296bfa949c4f5dd7c7b3b749920cb838bfca Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Mon, 8 Jun 2026 08:18:43 +0200
Subject: [PATCH 078/111] Add a test for fmt_print

---
 src/fmt-c.cc  |  1 +
 test/c-test.c | 20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/src/fmt-c.cc b/src/fmt-c.cc
index be1e5278..d5945ea9 100644
--- a/src/fmt-c.cc
+++ b/src/fmt-c.cc
@@ -10,6 +10,7 @@
 #include 
 
 constexpr size_t max_c_format_args = 16;
+
 static int convert_c_format_args(
     fmt::basic_format_arg* format_args,
     const fmt_arg* args, size_t num_args) {
diff --git a/test/c-test.c b/test/c-test.c
index 81c9bdfb..db7c8360 100644
--- a/test/c-test.c
+++ b/test/c-test.c
@@ -83,10 +83,30 @@ void test_buffer_size_query(void) {
   ASSERT_INT_EQ(size, 15);
 }
 
+void test_print(void) {
+  FILE* file = tmpfile();
+  if (!file) {
+    fprintf(stderr, "\nFailed to create temporary file\n");
+    exit(1);
+  }
+
+  int ret = fmt_print(file, "{} and {}", 42, "foo");
+  ASSERT_INT_EQ(ret, 0);
+
+  rewind(file);
+  char buf[100];
+  size_t n = fread(buf, 1, sizeof(buf) - 1, file);
+  buf[n] = '\0';
+  ASSERT_STR_EQ(buf, "42 and foo");
+
+  fclose(file);
+}
+
 int main(void) {
   printf("Running C API tests\n");
   test_types();
   test_zero_arguments();
   test_buffer_size_query();
+  test_print();
   printf("C API tests passed\n");
 }

From 1ccba0cf8008548abcabee6eb1d65bdca74e05b4 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Mon, 8 Jun 2026 09:44:52 +0200
Subject: [PATCH 079/111] Attach SLSA provenance to the draft instead of a new
 release

With upload-assets the SLSA generator created and published its own
release (and tag) to host the attestation, duplicating the draft. Disable
upload-assets and add a job that downloads the provenance artifact and
attaches it to the draft alongside the source zip.
---
 .github/workflows/release.yml | 29 ++++++++++++++++++++++++++---
 1 file changed, 26 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 13a5ee1d..8b5aa3db 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -78,15 +78,38 @@ jobs:
             "${{ steps.build.outputs.package }}" \
             --repo "${{ github.repository }}" --clobber
 
+  # Generate the provenance only; do not let the generator upload it. With
+  # upload-assets, the generator creates and publishes its own release (and
+  # tag) to host the attestation, which duplicates our draft. Instead we
+  # download the attestation artifact and attach it to the draft below.
   provenance:
     needs: [build]
     permissions:
       actions: read
       id-token: write
-      contents: write
+      contents: read
     uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
     with:
       base64-subjects: ${{ needs.build.outputs.hashes }}
       provenance-name: "fmt-${{ needs.build.outputs.tag }}.intoto.jsonl"
-      upload-assets: true
-      upload-tag-name: ${{ needs.build.outputs.tag }}
+      upload-assets: false
+
+  attach-provenance:
+    name: Attach provenance to the release
+    needs: [build, provenance]
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - name: Download the provenance attestation
+        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+        with:
+          name: ${{ needs.provenance.outputs.provenance-name }}
+
+      - name: Attach provenance to the draft release
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          gh release upload "${{ needs.build.outputs.tag }}" \
+            "${{ needs.provenance.outputs.provenance-name }}" \
+            --repo "${{ github.repository }}" --clobber

From 2de684d52d5b85a5baca4c9ecef88a602f6d27e0 Mon Sep 17 00:00:00 2001
From: Pedro Montes Alcalde 
Date: Tue, 9 Jun 2026 10:03:37 -0300
Subject: [PATCH 080/111] Add .gradle directory to .gitignore (#4799)

---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 25924b1e..235a2f56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 *.so*
 *.xcodeproj
 *~
+*.gradle/
 .vscode/
 .cache/
 .vs/

From 87bb05d3b2169002e3358bd56eebbd36c43add02 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 9 Jun 2026 07:40:36 +0200
Subject: [PATCH 081/111] Fix release workflow startup failure from
 insufficient permissions

The provenance job called the SLSA generator with contents: read, but the
generator's upload-assets job declares contents: write. A reusable
workflow's job permissions may not exceed the caller's, so GitHub failed
the run at startup. Grant contents: write; the upload-assets job is still
skipped at runtime since upload-assets is false.
---
 .github/workflows/release.yml | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8b5aa3db..4eecf05c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -87,7 +87,10 @@ jobs:
     permissions:
       actions: read
       id-token: write
-      contents: read
+      # contents: write is required because the generator's (skipped)
+      # upload-assets job declares it, and a reusable workflow's job
+      # permissions may not exceed the caller's, or the run fails at startup.
+      contents: write
     uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
     with:
       base64-subjects: ${{ needs.build.outputs.hashes }}

From e60274b29c0a748df2c6280d0a374e6eef3b6c73 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 9 Jun 2026 15:51:06 +0200
Subject: [PATCH 082/111] Run lint workflow on all PRs to fix stuck required
 checks

The lint workflow was path-filtered to source files, so PRs that didn't
touch them never ran clang-format/cmake-format. Since those checks are
required, such PRs were blocked forever waiting for a status that never
came. Drop the paths filter so the workflow always reports a status.
---
 .github/workflows/lint.yml | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index cbb1bdc4..a00a5273 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -2,11 +2,6 @@ name: lint
 
 on:
   pull_request:
-    paths:
-      - '**.h'
-      - '**.cc'
-      - '**.cmake'
-      - '**/CMakeLists.txt'
 
 permissions:
   contents: read

From 128ba144ab85ddd0ad9ea39f2febdfb51802cb1b Mon Sep 17 00:00:00 2001
From: aizu-m 
Date: Wed, 10 Jun 2026 10:48:35 +0530
Subject: [PATCH 083/111] fix out-of-bounds read in basic_format_args::get
 (#4800)

---
 include/fmt/base.h  |  2 +-
 test/printf-test.cc | 11 +++++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/include/fmt/base.h b/include/fmt/base.h
index 20cccc2b..5d0241c8 100644
--- a/include/fmt/base.h
+++ b/include/fmt/base.h
@@ -2593,7 +2593,7 @@ template  class basic_format_args {
   FMT_CONSTEXPR auto get(int id) const -> format_arg {
     auto arg = format_arg();
     if (!is_packed()) {
-      if (id < max_size()) arg = args_[id];
+      if (unsigned(id) < unsigned(max_size())) arg = args_[id];
       return arg;
     }
     if (unsigned(id) >= detail::max_packed_args) return arg;
diff --git a/test/printf-test.cc b/test/printf-test.cc
index f2d2a141..baf2b168 100644
--- a/test/printf-test.cc
+++ b/test/printf-test.cc
@@ -116,6 +116,17 @@ TEST(printf_test, invalid_arg_index) {
                    "argument not found");
 }
 
+TEST(printf_test, zero_positional_width_precision) {
+  // A '0' positional index for a '*' width or precision must be rejected. Use
+  // enough arguments to exercise the unpacked argument storage path.
+  EXPECT_THROW_MSG(test_sprintf("%*0$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+                                13, 14, 15, 16),
+                   format_error, "argument not found");
+  EXPECT_THROW_MSG(test_sprintf("%.*0$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+                                13, 14, 15, 16),
+                   format_error, "argument not found");
+}
+
 TEST(printf_test, default_align_right) {
   EXPECT_PRINTF("   42", "%5d", 42);
   EXPECT_PRINTF("  abc", "%5s", "abc");

From e8deaf2ec3b53ced589fce6f640061e5b32eeeaa Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Wed, 10 Jun 2026 11:30:17 +0200
Subject: [PATCH 084/111] Update changelog

---
 ChangeLog.md | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index 7c3430af..7cfc4a8b 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -142,9 +142,11 @@
   an embedded null byte
   (https://github.com/fmtlib/fmt/pull/4732). Thanks @user202729.
 
-- Fixed an out-of-bounds read in `fmt::vprintf` with a trailing `%`
+- Fixed out-of-bounds reads in `printf` formatting
   (https://github.com/fmtlib/fmt/issues/4741,
-  https://github.com/fmtlib/fmt/pull/4742). Thanks @Algunenano.
+  https://github.com/fmtlib/fmt/pull/4742,
+  https://github.com/fmtlib/fmt/pull/4800).
+  Thanks @Algunenano and @aizu-m.
 
 - Fixed the return type of the `f(un)lockfile`   wrappers on Windows
   (https://github.com/fmtlib/fmt/pull/4739). Thanks @mvastola.
@@ -199,9 +201,10 @@
   https://github.com/fmtlib/fmt/pull/4713,
   https://github.com/fmtlib/fmt/pull/4729,
   https://github.com/fmtlib/fmt/pull/4751,
-  https://github.com/fmtlib/fmt/pull/4758).
+  https://github.com/fmtlib/fmt/pull/4758,
+  https://github.com/fmtlib/fmt/pull/4799).
   Thanks @ZephyrLykos, @togunchan, @kagancansit, @BerndPetrovitsch,
-  @Skylion007, @st0rmbtw and @localspook.
+  @Skylion007, @st0rmbtw, @localspook and @EXtremeExploit.
 
 - Improved documentation, including a rewrite of the format string syntax,
   better handling of doxygen tags, documenting `output_file`, fixing CSS so

From 11ddbcb7898d2d3445d431a54814367b21dee6ad Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Fri, 12 Jun 2026 16:24:50 +0200
Subject: [PATCH 085/111] Build docs in the release workflow so the source zip
 includes doc-html

The source-zip build moved to CI, which configured out-of-source and never
ran the doc target, so the pre-rendered HTML docs (doc-html/) silently
dropped out of the package. Install the doc toolchain, build the docs, and
stage them into the source tree before package_source so CPack picks them up.
---
 .github/workflows/release.yml | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4eecf05c..773892fc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -4,6 +4,10 @@
 # slsa-github-generator. The maintainer reviews the draft (which by then has
 # both the zip and *.intoto.jsonl attached) and clicks Publish to finalize.
 #
+# The source package includes the pre-rendered HTML documentation (doc-html/),
+# so this workflow builds the docs and stages them into the source tree before
+# running CPack's package_source target.
+#
 # This makes the provenance attest to the actual build that produced the
 # artifact, rather than just attesting to a hash observed after the fact.
 #
@@ -54,10 +58,31 @@ jobs:
           ref: ${{ steps.vars.outputs.ref }}
           persist-credentials: false
 
+      - name: Install the documentation toolchain
+        # The source zip ships the pre-rendered HTML docs (doc-html/), so the
+        # docs must be built before packaging. doxygen feeds the API reference;
+        # mkdocs plus the pinned, hash-verified deps render the site. The venv
+        # lives outside the checkout so it is not swept into the source zip, and
+        # is added to PATH so CMake's find_program(MKDOCS) and the doxygen call
+        # in the mkdocstrings handler both resolve in later steps.
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y doxygen
+          python3 -m venv "$RUNNER_TEMP/doc-venv"
+          "$RUNNER_TEMP/doc-venv/bin/pip" install --quiet --upgrade pip
+          "$RUNNER_TEMP/doc-venv/bin/pip" install --quiet --require-hashes \
+            -r support/doc-requirements.txt
+          echo "$RUNNER_TEMP/doc-venv/bin" >> "$GITHUB_PATH"
+
       - name: Build source zip via CPack
         id: build
+        # Configure first so the doc target picks up doxygen/mkdocs, build the
+        # docs, then stage the generated site into the source tree so the CPack
+        # source package (which packages the source dir) includes doc-html/.
         run: |
           cmake -B build .
+          cmake --build build --target doc
+          cp -r build/doc-html doc-html
           cmake --build build --target package_source
           pkg=$(ls build/fmt-*.zip)
           test -f "$pkg"

From 5ea06b13fe4f1f405419b12cb14c95a4439c369c Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 16 Jun 2026 07:03:37 +0200
Subject: [PATCH 086/111] Exclude the build tree from the source package

The .gitignore-derived `build/` pattern does not actually prune the build
directory or its contents from the CPack source package, so an empty build/
(and, once docs were built in CI, build/doc-html) leaked into the zip. Ignore
the whole build tree in CPACK_SOURCE_IGNORE_FILES.
---
 .github/workflows/release.yml | 6 +++---
 CMakeLists.txt                | 4 +++-
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 773892fc..d694d96c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -76,9 +76,9 @@ jobs:
 
       - name: Build source zip via CPack
         id: build
-        # Configure first so the doc target picks up doxygen/mkdocs, build the
-        # docs, then stage the generated site into the source tree so the CPack
-        # source package (which packages the source dir) includes doc-html/.
+        # Build the docs and stage the rendered site at the top level so the
+        # CPack source package includes doc-html/. The build/ tree itself is
+        # excluded by CPACK_SOURCE_IGNORE_FILES.
         run: |
           cmake -B build .
           cmake --build build --target doc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1045f3ee..ee568cec 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -582,7 +582,9 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
     string(REPLACE "*" ".*" line "${line}")
     set(ignored_files ${ignored_files} "${line}$" "${line}/")
   endforeach ()
-  set(ignored_files ${ignored_files} /.git /build/doxyxml .vagrant)
+  # Exclude the whole build tree: the .gitignore-derived `build/` pattern does
+  # not actually prune the directory or its contents from the source package.
+  set(ignored_files ${ignored_files} /.git "/build($|/)" .vagrant)
 
   set(CPACK_SOURCE_GENERATOR ZIP)
   set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})

From 1be298e1bd68957e4cd352e1f676f00e07dcfb57 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Tue, 16 Jun 2026 07:05:35 +0200
Subject: [PATCH 087/111] Update version

---
 ChangeLog.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index 7cfc4a8b..d132266d 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,4 +1,4 @@
-# 12.2.0 - TBD
+# 12.2.0 - 2026-06-16
 
 - Added a C11 API that brings fast, type-safe formatting to C. The new
   `fmt-c` library and `fmt/fmt-c.h` header use `_Generic` to dispatch on

From de4c6c502e23cef18da5ed6738bf6612663a9cd2 Mon Sep 17 00:00:00 2001
From: aizu-m 
Date: Wed, 17 Jun 2026 11:44:58 +0530
Subject: [PATCH 088/111] fix out-of-range float to int conversion in
 to_nonnegative_int (#4802)

---
 include/fmt/chrono.h | 5 ++---
 test/chrono-test.cc  | 3 +++
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h
index e4bc1ce4..46590074 100644
--- a/include/fmt/chrono.h
+++ b/include/fmt/chrono.h
@@ -934,10 +934,9 @@ inline auto to_nonnegative_int(T value, Int upper) -> Int {
 }
 template ::value)>
 inline auto to_nonnegative_int(T value, Int upper) -> Int {
-  auto int_value = static_cast(value);
-  if (int_value < 0 || value > static_cast(upper))
+  if (value < 0 || value >= static_cast(upper) + 1)
     FMT_THROW(format_error("invalid value"));
-  return int_value;
+  return static_cast(value);
 }
 
 constexpr auto pow10(std::uint32_t n) -> long long {
diff --git a/test/chrono-test.cc b/test/chrono-test.cc
index a9153598..b7a4e59a 100644
--- a/test/chrono-test.cc
+++ b/test/chrono-test.cc
@@ -989,6 +989,9 @@ TEST(chrono_test, glibc_extensions) {
 TEST(chrono_test, out_of_range) {
   auto d = std::chrono::duration(538976288);
   EXPECT_THROW((void)fmt::format("{:%j}", d), fmt::format_error);
+  // A floating-point day count that doesn't fit in int.
+  auto fd = std::chrono::duration(1e300);
+  EXPECT_THROW((void)fmt::format("{:%j}", fd), fmt::format_error);
 }
 
 TEST(chrono_test, year_month_day) {

From 979c94dff814b3d5c15415c4426a54e66b388ae1 Mon Sep 17 00:00:00 2001
From: Victor Zverovich 
Date: Wed, 17 Jun 2026 08:36:43 +0200
Subject: [PATCH 089/111] Fix hang/assertion when printing to a pipe with
 closed read end (#4797)

---
 include/fmt/format-inl.h | 10 ++++++++--
 test/format-test.cc      | 19 +++++++++++++++++++
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h
index 665f7308..ecd3ffec 100644
--- a/include/fmt/format-inl.h
+++ b/include/fmt/format-inl.h
@@ -1526,7 +1526,10 @@ template  class file_base {
       FMT_THROW(system_error(errno, FMT_STRING("ungetc failed")));
   }
 
-  void flush() { fflush(this->file_); }
+  void flush() {
+    if (fflush(this->file_) != 0)
+      FMT_THROW(system_error(errno, FMT_STRING("fflush failed")));
+  }
 };
 
 // A FILE wrapper for glibc.
@@ -1572,7 +1575,10 @@ template  class glibc_file : public file_base {
     return memchr(end, '\n', static_cast(size));
   }
 
-  void flush() { fflush_unlocked(this->file_); }
+  void flush() {
+    if (fflush_unlocked(this->file_) != 0)
+      FMT_THROW(system_error(errno, FMT_STRING("fflush failed")));
+  }
 };
 
 // A FILE wrapper for Apple's libc.
diff --git a/test/format-test.cc b/test/format-test.cc
index 61a53c72..9a2258d5 100644
--- a/test/format-test.cc
+++ b/test/format-test.cc
@@ -18,6 +18,7 @@
 #include         // fegetexceptflag and FE_ALL_EXCEPT
 #include       // INT_MAX
 #include         // std::signbit
+#include       // std::signal, SIGPIPE
 #include       // std::strlen
 #include      // std::back_inserter
 #include          // std::list
@@ -2592,6 +2593,24 @@ TEST(format_test, invalid_glibc_buffer) {
 
   fmt::print(file, "------\n");
 }
+
+TEST(format_test, print_to_broken_pipe) {
+  // Ignore SIGPIPE so that a failing write reports EPIPE instead of
+  // terminating the test process. It must stay ignored until the file is
+  // closed below because closing also flushes the remaining buffered data.
+  auto old_handler = std::signal(SIGPIPE, SIG_IGN);
+  {
+    auto pipe = fmt::pipe();
+    pipe.read_end.close();
+    auto write_end = pipe.write_end.fdopen("w");
+
+    // The data must exceed the file's buffer to force a flush during
+    // formatting, whose underlying write() then fails with EPIPE.
+    auto data = std::string(1024 * 1024, 'x');
+    EXPECT_THROW(fmt::print(write_end.get(), "{}", data), std::system_error);
+  }
+  std::signal(SIGPIPE, old_handler);
+}
 #endif  // FMT_USE_FCNTL
 
 // Only defined after the test case.

From 588b3a0f8f6a8bcf2a959cae882d5b2703e86737 Mon Sep 17 00:00:00 2001
From: Vinay Kumar 
Date: Thu, 18 Jun 2026 12:22:12 +0530
Subject: [PATCH 090/111] Fix fallback uint128 bitwise not (#4813)

---
 include/fmt/format.h | 3 +++
 test/format-test.cc  | 5 +++++
 2 files changed, 8 insertions(+)

diff --git a/include/fmt/format.h b/include/fmt/format.h
index 5044befd..57062792 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -326,6 +326,9 @@ class uint128 {
       -> uint128 {
     return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_};
   }
+  friend constexpr auto operator~(const uint128& n) -> uint128 {
+    return {~n.hi_, ~n.lo_};
+  }
   friend FMT_CONSTEXPR auto operator+(const uint128& lhs, const uint128& rhs)
       -> uint128 {
     auto result = uint128(lhs);
diff --git a/test/format-test.cc b/test/format-test.cc
index 9a2258d5..8c2670a1 100644
--- a/test/format-test.cc
+++ b/test/format-test.cc
@@ -82,6 +82,11 @@ TEST(uint128_test, minus) {
   EXPECT_EQ(n - 2, 40);
 }
 
+TEST(uint128_test, bitwise_not) {
+  auto n = ~uint128(0x123456789abcdef0, 0x0fedcba987654321);
+  EXPECT_EQ(n, uint128(0xedcba9876543210f, 0xf0123456789abcde));
+}
+
 TEST(uint128_test, plus_assign) {
   auto n = uint128(32);
   n += uint128(10);

From 0e601c34de26fe5b46f4c62dab2039efcb0acaed Mon Sep 17 00:00:00 2001
From: Yat Ho 
Date: Fri, 26 Jun 2026 03:21:59 +0800
Subject: [PATCH 091/111] fix: explicitly check `operator[]` in `is_contiguous`
 (#4825)

Prevent false positives when the container itself does not have `operator[](size_t)`, but the target type of one of the non-explicit user-defined conversion functions does.
---
 include/fmt/base.h |  7 ++++---
 test/base-test.cc  | 19 +++++++++++++++++++
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/include/fmt/base.h b/include/fmt/base.h
index 5d0241c8..60d1b792 100644
--- a/include/fmt/base.h
+++ b/include/fmt/base.h
@@ -493,9 +493,10 @@ inline FMT_CONSTEXPR auto get_container(OutputIt it) ->
 template 
 struct is_contiguous : std::false_type {};
 template 
-struct is_contiguous().data()),
-                               decltype(std::declval().size()),
-                               decltype(std::declval()[size_t()])>>
+struct is_contiguous().data()),
+                            decltype(std::declval().size()),
+                            decltype(std::declval().operator[](size_t()))>>
     : std::true_type {};
 }  // namespace detail
 
diff --git a/test/base-test.cc b/test/base-test.cc
index 40cc3810..92415cbd 100644
--- a/test/base-test.cc
+++ b/test/base-test.cc
@@ -874,6 +874,24 @@ struct custom_container {
   auto operator[](size_t) -> char& { return data; }
 };
 
+struct phantom_subscript_operator_container {
+  std::vector buffer;
+
+  using value_type = char;
+
+  auto size() const noexcept -> size_t { return buffer.size(); }
+
+  auto data() noexcept -> char* { return buffer.data(); }
+
+  auto data() const noexcept -> const char* { return buffer.data(); }
+
+  void resize(size_t n) { buffer.resize(n); }
+
+  void push_back(const char& value) { buffer.push_back(value); }
+
+  operator const char*() const noexcept { return data(); }
+};
+
 FMT_BEGIN_NAMESPACE
 template <> struct is_contiguous : std::true_type {};
 FMT_END_NAMESPACE
@@ -884,6 +902,7 @@ TEST(base_test, is_contiguous) {
   EXPECT_TRUE((fmt::is_contiguous::value));
   EXPECT_TRUE((fmt::is_contiguous>::value));
   EXPECT_FALSE((fmt::is_contiguous>::value));
+  EXPECT_FALSE((fmt::is_contiguous::value));
 }
 
 TEST(base_test, format_to_custom_container) {

From 62abc0f30acf0523de03997ce0a12e46818dc400 Mon Sep 17 00:00:00 2001
From: Brooklyn widz 
Date: Fri, 26 Jun 2026 04:39:46 +0530
Subject: [PATCH 092/111] fix extra closing paragraph tag in doc/index.md
 (#4826)

---
 doc/index.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/doc/index.md b/doc/index.md
index e312c398..217ade6b 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -130,7 +130,6 @@ hide:
   Where possible, the output of formatting functions is consistent across
   platforms.
 

-

From 719823cc6433a08b8dd830ba0a37b6c9fae4cc03 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Fri, 26 Jun 2026 15:55:44 +0200 Subject: [PATCH 093/111] Turn libfmt-c into a shared library (#4812) Linux distros don't like having static archives. Set fmt-c to shared just like the main fmt library. --- CMakeLists.txt | 13 ++++++++++++- include/fmt/fmt-c.h | 29 ++++++++++++++++++++++++----- test/CMakeLists.txt | 2 +- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee568cec..1de95cc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,8 +417,19 @@ target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) target_compile_features(fmt-header-only INTERFACE cxx_std_11) setup_target(fmt-header-only INTERFACE) -add_library(fmt-c STATIC src/fmt-c.cc) +add_library(fmt-c src/fmt-c.cc) target_compile_features(fmt-c INTERFACE c_std_11) +if (BUILD_SHARED_LIBS) + target_compile_definitions( + fmt-c + PRIVATE FMT_LIB_EXPORT + INTERFACE FMT_SHARED) +endif () +set_target_properties( + fmt-c + PROPERTIES VERSION ${FMT_VERSION} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") if (MSVC) target_compile_options(fmt-c PUBLIC /Zc:preprocessor) endif () diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index e918e77c..c1d8be69 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -12,6 +12,25 @@ #include // size_t #include // FILE +#if defined(__GNUC__) +# define FMT_CVISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_CVISIBILITY(value) +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# if defined(FMT_LIB_EXPORT) +# define FMT_CAPI __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_CAPI __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_CAPI FMT_CVISIBILITY("default") +#endif +#ifndef FMT_CAPI +# define FMT_CAPI +#endif + #ifdef __cplusplus extern "C" { #endif @@ -45,10 +64,10 @@ typedef struct { enum { fmt_error = -1, fmt_error_invalid_arg = -2 }; -int fmt_vformat(char* buffer, size_t size, const char* fmt, const fmt_arg* args, - size_t num_args); -int fmt_vprint(FILE* stream, const char* fmt, const fmt_arg* args, - size_t num_args); +int FMT_CAPI fmt_vformat(char* buffer, size_t size, const char* fmt, + const fmt_arg* args, size_t num_args); +int FMT_CAPI fmt_vprint(FILE* stream, const char* fmt, const fmt_arg* args, + size_t num_args); #ifdef __cplusplus } @@ -92,7 +111,7 @@ static inline fmt_arg fmt_from_ptr(const void* x) { return (fmt_arg){.type = fmt_pointer, .value.pointer = x}; } -void fmt_unsupported_type(void); +void FMT_CAPI fmt_unsupported_type(void); # if !defined(_MSC_VER) || defined(__clang__) typedef signed char fmt_signed_char; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cd7ba177..483d8018 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -243,5 +243,5 @@ endif () enable_language(C) add_executable(c-test c-test.c) -target_link_libraries(c-test PRIVATE fmt::fmt-c) +target_link_libraries(c-test PUBLIC fmt::fmt-c) add_test(NAME c-test COMMAND c-test) From a2206152d5b7e6007e8e2b6386e4ece28e932578 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 26 Jun 2026 12:29:26 -0700 Subject: [PATCH 094/111] Simplify test --- test/base-test.cc | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/test/base-test.cc b/test/base-test.cc index 92415cbd..837c4e5e 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -874,22 +874,16 @@ struct custom_container { auto operator[](size_t) -> char& { return data; } }; -struct phantom_subscript_operator_container { - std::vector buffer; - +struct phantom_subscript_container { using value_type = char; - auto size() const noexcept -> size_t { return buffer.size(); } + auto data() -> char*; + auto size() const -> size_t; + operator const char*() const; +}; - auto data() noexcept -> char* { return buffer.data(); } - - auto data() const noexcept -> const char* { return buffer.data(); } - - void resize(size_t n) { buffer.resize(n); } - - void push_back(const char& value) { buffer.push_back(value); } - - operator const char*() const noexcept { return data(); } +struct real_subscript_container : phantom_subscript_container { + auto operator[](size_t) -> char&; }; FMT_BEGIN_NAMESPACE @@ -902,7 +896,8 @@ TEST(base_test, is_contiguous) { EXPECT_TRUE((fmt::is_contiguous::value)); EXPECT_TRUE((fmt::is_contiguous>::value)); EXPECT_FALSE((fmt::is_contiguous>::value)); - EXPECT_FALSE((fmt::is_contiguous::value)); + EXPECT_FALSE((fmt::is_contiguous::value)); + EXPECT_TRUE((fmt::is_contiguous::value)); } TEST(base_test, format_to_custom_container) { From d24fef27d15adb537e60c7c8d54d4b5b23733949 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 26 Jun 2026 15:57:41 -0700 Subject: [PATCH 095/111] Minor cleanup --- include/fmt/fmt-c.h | 12 +++++------- test/CMakeLists.txt | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index c1d8be69..3f8714d4 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -12,12 +12,6 @@ #include // size_t #include // FILE -#if defined(__GNUC__) -# define FMT_CVISIBILITY(value) __attribute__((visibility(value))) -#else -# define FMT_CVISIBILITY(value) -#endif - #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) # if defined(FMT_LIB_EXPORT) # define FMT_CAPI __declspec(dllexport) @@ -25,7 +19,11 @@ # define FMT_CAPI __declspec(dllimport) # endif #elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) -# define FMT_CAPI FMT_CVISIBILITY("default") +# ifdef __GNUC__ +# define FMT_CAPI __attribute__((visibility("default"))) +# else +# define FMT_CAPI +# endif #endif #ifndef FMT_CAPI # define FMT_CAPI diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 483d8018..cd7ba177 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -243,5 +243,5 @@ endif () enable_language(C) add_executable(c-test c-test.c) -target_link_libraries(c-test PUBLIC fmt::fmt-c) +target_link_libraries(c-test PRIVATE fmt::fmt-c) add_test(NAME c-test COMMAND c-test) From 81516a20d975483bc55687cd04161f2ef7d38a63 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 28 Jun 2026 21:08:06 +0530 Subject: [PATCH 096/111] Skip VERSION/SOVERSION/DEBUG_POSTFIX on the header-only INTERFACE target (#4830) * Skip VERSION/SOVERSION/DEBUG_POSTFIX on the header-only INTERFACE target * Lint check fixed --- CMakeLists.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1de95cc4..8426ab85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,11 +256,13 @@ function (setup_target target kind) target_compile_definitions(${target} ${kind} FMT_UNICODE=0) endif () - set_target_properties( - ${target} - PROPERTIES VERSION ${FMT_VERSION} - SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} - DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") + if (NOT "${kind}" STREQUAL "INTERFACE") + set_target_properties( + ${target} + PROPERTIES VERSION ${FMT_VERSION} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") + endif () endfunction () set(FMT_HEADERS) From 77b6ff700be3417e0a3ad9674ca306e40c451b6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 09:07:12 -0700 Subject: [PATCH 097/111] Bump actions/checkout from 6.0.0 to 7.0.0 (#4835) Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/doc.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/supply-chain.yml | 2 +- .github/workflows/windows.yml | 4 ++-- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3272f073..8d7becf5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,7 +17,7 @@ jobs: security-events: write actions: read steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v3.29.5 with: diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 6c041585..1e2f69b3 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Add Ubuntu mirrors run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a00a5273..55879a25 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,7 +10,7 @@ jobs: clang-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Install clang-format run: | @@ -33,7 +33,7 @@ jobs: cmake-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Install cmake-format run: pip install --require-hashes -r support/cmake-format-requirements.txt diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 2a67495f..70260dd9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -73,7 +73,7 @@ jobs: gen: Ninja steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set timezone run: sudo timedatectl set-timezone 'Europe/Kyiv' diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 19d78427..87fb8414 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -25,7 +25,7 @@ jobs: runs-on: '${{ matrix.os }}' steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set timezone run: sudo systemsetup -settimezone 'Europe/Minsk' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d694d96c..30ae7b22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: echo "ref=${{ github.event.release.target_commitish || inputs.ref }}" >> "$GITHUB_OUTPUT" - name: Checkout the release ref - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ steps.vars.outputs.ref }} persist-credentials: false diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index 4c0f7174..0c30e0f8 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -24,7 +24,7 @@ jobs: id-token: write steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0a50a2df..b439bcaa 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,7 +35,7 @@ jobs: standard: 20 steps: - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set timezone run: tzutil /s "FLE Standard Time" @@ -82,7 +82,7 @@ jobs: release: false msystem: ${{matrix.sys}} pacboy: cc:p cmake:p ninja:p lld:p - - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Configure run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug env: { LDFLAGS: -fuse-ld=lld } From b54093c658ae5656799756ec5d4467f49064929f Mon Sep 17 00:00:00 2001 From: torsten48 <59708557+torsten48@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:39:09 +0200 Subject: [PATCH 098/111] avoid MSC warning C4702: unreachable code (#4822) if FMT_CONSTEXPR is resolved to constexpr MSC warns about dead code after 'return' instruction. Explicitly using 'else' in these cases avoids the warning. --- include/fmt/base.h | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 60d1b792..aca28a8a 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1285,9 +1285,12 @@ constexpr auto to_ascii(Char c) -> char { // Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { - if FMT_CONSTEXPR20 (sizeof(Char) != 1) return 1; - auto c = static_cast(*begin); - return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; + if FMT_CONSTEXPR20 (sizeof(Char) != 1) { + return 1; + } else { + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; + } } // Parses the range [begin, end) as an unsigned integer. This function assumes @@ -2896,10 +2899,12 @@ FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); template FMT_INLINE void print(format_string fmt, T&&... args) { vargs va = {{args...}}; - if FMT_CONSTEXPR20 (!detail::use_utf8) - return detail::vprint_mojibake(stdout, fmt.str, va, false); - detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) - : vprint(fmt.str, va); + if FMT_CONSTEXPR20 (!detail::use_utf8) { + detail::vprint_mojibake(stdout, fmt.str, va, false); + } else { + detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); + } } /** @@ -2913,10 +2918,12 @@ FMT_INLINE void print(format_string fmt, T&&... args) { template FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - if FMT_CONSTEXPR20 (!detail::use_utf8) - return detail::vprint_mojibake(f, fmt.str, va, false); - detail::is_locking() ? vprint_buffered(f, fmt.str, va) - : vprint(f, fmt.str, va); + if FMT_CONSTEXPR20 (!detail::use_utf8) { + detail::vprint_mojibake(f, fmt.str, va, false); + } else { + detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); + } } /// Formats `args` according to specifications in `fmt` and writes the output @@ -2924,8 +2931,11 @@ FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { template FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - if FMT_CONSTEXPR20 (detail::use_utf8) return vprintln(f, fmt.str, va); - detail::vprint_mojibake(f, fmt.str, va, true); + if FMT_CONSTEXPR20 (detail::use_utf8) { + vprintln(f, fmt.str, va); + } else { + detail::vprint_mojibake(f, fmt.str, va, true); + } } /// Formats `args` according to specifications in `fmt` and writes the output From cd67635a19d027d2eae1cd6f0f0fa8f057492907 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 4 Jul 2026 09:57:49 -0700 Subject: [PATCH 099/111] Suppress MSVC C4702 header-wide instead of restructuring code Revert the explicit-else workaround from #4822 and instead disable the "unreachable code" warning for all of base.h. Add an FMT_PRAGMA_MSVC helper (mirroring FMT_PRAGMA_GCC/CLANG) and wrap the header in a single warning(push)/disable:4702/pop pair, keeping the original control flow. --- include/fmt/base.h | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index aca28a8a..f213fa09 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -223,6 +223,11 @@ #else # define FMT_PRAGMA_CLANG(x) #endif +#if FMT_MSC_VERSION +# define FMT_PRAGMA_MSVC(x) __pragma(x) +#else +# define FMT_PRAGMA_MSVC(x) +#endif #ifndef FMT_USE_OPTIMIZE_PRAGMA # define FMT_USE_OPTIMIZE_PRAGMA 1 @@ -235,6 +240,9 @@ FMT_PRAGMA_GCC(push_options) FMT_PRAGMA_GCC(optimize("Og")) #endif +FMT_PRAGMA_MSVC(warning(push)) +FMT_PRAGMA_MSVC(warning(disable : 4702)) + #ifdef FMT_DEPRECATED // Use the provided definition. #elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) @@ -1285,12 +1293,9 @@ constexpr auto to_ascii(Char c) -> char { // Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { - if FMT_CONSTEXPR20 (sizeof(Char) != 1) { - return 1; - } else { - auto c = static_cast(*begin); - return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; - } + if FMT_CONSTEXPR20 (sizeof(Char) != 1) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; } // Parses the range [begin, end) as an unsigned integer. This function assumes @@ -2899,12 +2904,10 @@ FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); template FMT_INLINE void print(format_string fmt, T&&... args) { vargs va = {{args...}}; - if FMT_CONSTEXPR20 (!detail::use_utf8) { - detail::vprint_mojibake(stdout, fmt.str, va, false); - } else { - detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) - : vprint(fmt.str, va); - } + if FMT_CONSTEXPR20 (!detail::use_utf8) + return detail::vprint_mojibake(stdout, fmt.str, va, false); + detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); } /** @@ -2918,12 +2921,10 @@ FMT_INLINE void print(format_string fmt, T&&... args) { template FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - if FMT_CONSTEXPR20 (!detail::use_utf8) { - detail::vprint_mojibake(f, fmt.str, va, false); - } else { - detail::is_locking() ? vprint_buffered(f, fmt.str, va) - : vprint(f, fmt.str, va); - } + if FMT_CONSTEXPR20 (!detail::use_utf8) + return detail::vprint_mojibake(f, fmt.str, va, false); + detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); } /// Formats `args` according to specifications in `fmt` and writes the output @@ -2931,11 +2932,8 @@ FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { template FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - if FMT_CONSTEXPR20 (detail::use_utf8) { - vprintln(f, fmt.str, va); - } else { - detail::vprint_mojibake(f, fmt.str, va, true); - } + if FMT_CONSTEXPR20 (detail::use_utf8) return vprintln(f, fmt.str, va); + detail::vprint_mojibake(f, fmt.str, va, true); } /// Formats `args` according to specifications in `fmt` and writes the output @@ -2946,6 +2944,7 @@ FMT_INLINE void println(format_string fmt, T&&... args) { } FMT_PRAGMA_GCC(pop_options) +FMT_PRAGMA_MSVC(warning(pop)) FMT_END_EXPORT FMT_END_NAMESPACE From 6dac6cad052d6593f5fa07529d912f3bd0d6bb11 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 5 Jul 2026 18:22:18 +0200 Subject: [PATCH 100/111] feat: add formatter for std::exception_ptr (#4808) (#4819) --- include/fmt/std.h | 19 +++++++++++++++++++ test/std-test.cc | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/include/fmt/std.h b/include/fmt/std.h index f7d8d495..d606508b 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -649,6 +649,25 @@ struct formatter< } }; +template <> struct formatter : formatter { + template + auto format(const std::exception_ptr& ex_ptr, FormatContext& ctx) const + -> decltype(ctx.out()) { + if (!ex_ptr) { + return detail::write(ctx.out(), string_view("nullptr")); + } + + try { + std::rethrow_exception(ex_ptr); + } catch (const std::exception& e) { + // Reuses the base formatter::format behavior perfectly + return formatter::format(e, ctx); + } catch (...) { + return detail::write(ctx.out(), string_view("unknown exception")); + } + } +}; + template struct formatter, Char> : formatter { static_assert(N <= 64, "unsupported _BitInt"); diff --git a/test/std-test.cc b/test/std-test.cc index f6fc1284..b308fe38 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -404,6 +404,26 @@ TEST(std_test, exception) { #endif } +TEST(std_test, exception_ptr) { + std::exception_ptr p1 = nullptr; + std::exception_ptr p2; + + try { + using namespace my_ns1::my_ns2; + throw my_exception("My Exception"); + } catch (...) { + p2 = std::current_exception(); + } + + EXPECT_EQ(fmt::format("{}", p1), "nullptr"); + EXPECT_EQ(fmt::format("{}", p2), "My Exception"); + +#if FMT_USE_RTTI + EXPECT_EQ(fmt::format("{:t}", p2), + "my_ns1::my_ns2::my_exception: My Exception"); +#endif +} + #if FMT_USE_RTTI TEST(std_test, type_info) { EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)), From 9d13c41ce61329cc85c7c3c67c859ffe2b0e6178 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 11 Jul 2026 08:21:01 -0700 Subject: [PATCH 101/111] Format null std::exception_ptr as "none" Change the null exception_ptr representation from "nullptr" to "none" to convey the "no exception" state more clearly, and update the test. --- include/fmt/std.h | 10 +++------- test/std-test.cc | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index d606508b..207c9d35 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -651,16 +651,12 @@ struct formatter< template <> struct formatter : formatter { template - auto format(const std::exception_ptr& ex_ptr, FormatContext& ctx) const + auto format(const std::exception_ptr& ep, FormatContext& ctx) const -> decltype(ctx.out()) { - if (!ex_ptr) { - return detail::write(ctx.out(), string_view("nullptr")); - } - + if (!ep) return detail::write(ctx.out(), string_view("none")); try { - std::rethrow_exception(ex_ptr); + std::rethrow_exception(ep); } catch (const std::exception& e) { - // Reuses the base formatter::format behavior perfectly return formatter::format(e, ctx); } catch (...) { return detail::write(ctx.out(), string_view("unknown exception")); diff --git a/test/std-test.cc b/test/std-test.cc index b308fe38..d64ef956 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -415,7 +415,7 @@ TEST(std_test, exception_ptr) { p2 = std::current_exception(); } - EXPECT_EQ(fmt::format("{}", p1), "nullptr"); + EXPECT_EQ(fmt::format("{}", p1), "none"); EXPECT_EQ(fmt::format("{}", p2), "My Exception"); #if FMT_USE_RTTI From 7077b016cc19793e11f9b9aa3d909390acd3819b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 11 Jul 2026 09:17:57 -0700 Subject: [PATCH 102/111] Guard fast decimal path on bit width instead of byte size Use num_bits() == 32 instead of sizeof(UInt) == 4 in do_format_decimal. The magic-number two-digit algorithm requires exactly 32 value bits, so this is more precise on platforms where CHAR_BIT != 8 and is consistent with existing num_bits() usage. --- include/fmt/format.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 57062792..b13bc4cf 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1234,7 +1234,7 @@ FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) unsigned n = to_unsigned(size); while (value >= 100) { n -= 2; - if (!is_constant_evaluated() && sizeof(UInt) == 4) { + if (!is_constant_evaluated() && num_bits() == 32) { auto p = value * static_cast((1ull << 39) / 100 + 1); write2digits_i(out + n, p >> (39 - 7) & ((1 << 7) - 1)); value = static_cast(p >> 39) + From 1a99c2630cdf07f4e18924adc5e10650aaf67bc6 Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Sun, 12 Jul 2026 18:50:10 +0300 Subject: [PATCH 103/111] Format nested exceptions in std::exception formatter (#4844) Detect when a std::exception also derives from std::nested_exception (e.g. via std::throw_with_nested) and unwind the whole chain, joining each level with ": ". The type-name flag ({:t}) is honored at every level, and the std::exception_ptr formatter inherits this behavior. Co-authored-by: Claude Opus 4.8 (1M context) --- include/fmt/std.h | 26 ++++++++++++++++++++++++-- test/std-test.cc | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index 207c9d35..18b12abc 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -637,7 +637,12 @@ struct formatter< template auto format(const std::exception& ex, Context& ctx) const -> decltype(ctx.out()) { - auto out = ctx.out(); + return write(ctx.out(), ex); + } + + private: + template + auto write(OutputIt out, const std::exception& ex) const -> OutputIt { #if FMT_USE_RTTI if (with_typename_) { out = detail::write_demangled_name(out, typeid(ex)); @@ -645,7 +650,24 @@ struct formatter< *out++ = ' '; } #endif - return detail::write_bytes(out, string_view(ex.what())); + out = detail::write_bytes(out, string_view(ex.what())); +#if FMT_USE_RTTI + // If the exception carries a nested exception (e.g. via + // std::throw_with_nested), format the whole chain. + if (auto* nested = dynamic_cast(&ex)) { + if (auto ep = nested->nested_ptr()) { + out = detail::write(out, string_view(": ")); + try { + std::rethrow_exception(ep); + } catch (const std::exception& nested_ex) { + out = write(out, nested_ex); + } catch (...) { + out = detail::write(out, string_view("unknown exception")); + } + } + } +#endif + return out; } }; diff --git a/test/std-test.cc b/test/std-test.cc index d64ef956..8fb92f79 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -402,6 +402,34 @@ TEST(std_test, exception) { StartsWith("std::filesystem::filesystem_error: ")); } #endif + +#if FMT_USE_RTTI + // Nested exceptions (e.g. from std::throw_with_nested) are unwound. + try { + try { + throw std::runtime_error("inner"); + } catch (...) { + std::throw_with_nested(std::runtime_error("outer")); + } + } catch (const std::exception& ex) { + EXPECT_EQ("outer: inner", fmt::format("{}", ex)); + } + + // Multiple levels of nesting. + try { + try { + try { + throw std::runtime_error("level 3"); + } catch (...) { + std::throw_with_nested(std::runtime_error("level 2")); + } + } catch (...) { + std::throw_with_nested(std::runtime_error("level 1")); + } + } catch (const std::exception& ex) { + EXPECT_EQ("level 1: level 2: level 3", fmt::format("{}", ex)); + } +#endif } TEST(std_test, exception_ptr) { @@ -421,6 +449,19 @@ TEST(std_test, exception_ptr) { #if FMT_USE_RTTI EXPECT_EQ(fmt::format("{:t}", p2), "my_ns1::my_ns2::my_exception: My Exception"); + + // Nested exceptions are unwound through an exception_ptr too. + std::exception_ptr p3; + try { + try { + throw std::runtime_error("inner"); + } catch (...) { + std::throw_with_nested(std::runtime_error("outer")); + } + } catch (...) { + p3 = std::current_exception(); + } + EXPECT_EQ(fmt::format("{}", p3), "outer: inner"); #endif } From af2d9f2b78d68e2fa8f7d5f120da3127e954ec05 Mon Sep 17 00:00:00 2001 From: AMAN UPADHYAY Date: Sun, 12 Jul 2026 21:51:55 +0530 Subject: [PATCH 104/111] Fix FMT_COMPILE failure with format_as mapped types (Issue #4794) (#4836) --- include/fmt/compile.h | 4 ++++ test/compile-test.cc | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 1a987cb0..2a0be049 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -178,6 +178,10 @@ template struct field { if constexpr (std::is_convertible>::value) { auto s = basic_string_view(arg); return copy(s.begin(), s.end(), out); + } else if constexpr (use_format_as::value) { + return write(out, format_as(arg)); + } else if constexpr (use_format_as_member::value) { + return write(out, formatter::format_as(arg)); } else { return write(out, arg); } diff --git a/test/compile-test.cc b/test/compile-test.cc index 5a944612..73e86f35 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -479,3 +479,13 @@ TEST(compile_test, constexpr_string_format) { EXPECT_TRUE(big); } #endif // FMT_USE_CONSTEXPR_STRING + +namespace { +struct compile_format_as_type { + int value; +}; +int format_as(compile_format_as_type f) { return f.value; } +} +TEST(compile_test, format_as) { + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), compile_format_as_type{42})); +} \ No newline at end of file From a79df4504cd4e42ed004b1113fb82171e62ed822 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 12 Jul 2026 09:37:53 -0700 Subject: [PATCH 105/111] Fix links in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fabb2e83..d2c1af9a 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v). implementations of `(s)printf`, iostreams, `to_string` and `to_chars`, see [Speed tests](#speed-tests) and [Converting a hundred million integers to strings per - second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html) + second](https://vitaut.net/posts/2020/fast-int-to-string-revisited/) - Small code size both in terms of source code with the minimum configuration consisting of just three files, `base.h`, `format.h` and `format-inl.h`, and compiled code; see [Compile time and code @@ -157,7 +157,7 @@ int main() { ``` This can be [up to 9 times faster than `fprintf`]( -http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). +https://vitaut.net/posts/2020/optimal-file-buffer-size/). **Print with colors and text styles** @@ -351,7 +351,7 @@ converts to `std::print`.) cluster proxy - [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka® replacement for mission-critical systems written in C++ -- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and +- [rpclib](https://github.com/rpclib/rpclib): a modern C++ msgpack-RPC server and client library - [Salesforce Analytics Cloud](https://www.salesforce.com/analytics-cloud/overview/): @@ -359,7 +359,7 @@ converts to `std::print`.) - [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL data store that can handle 1 million transactions per second on a single server -- [Seastar](http://www.seastar-project.org/): an advanced, open-source +- [Seastar](https://seastar.io/): an advanced, open-source C++ framework for high-performance server applications on modern hardware - [spdlog](https://github.com/gabime/spdlog): super fast C++ logging From d45179c1e7092c6b0d46e4f400fa537377623398 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Jul 2026 11:16:40 -0700 Subject: [PATCH 106/111] Merge the two integer write overloads into one The appender and generic-iterator overloads only differed in whether the integer formatting code was inlined. Merge them into a single overload and select write_int_noinline vs inline write_int via if constexpr, keeping the same codegen (out of line for appenders, inlined for compiled formatting) with no size regression. --- include/fmt/format.h | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index b13bc4cf..f2ee623d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2157,29 +2157,19 @@ FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, return write_int(out, arg, specs); } -template ::value && - !std::is_same::value && - !std::is_same::value)> -FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, - const format_specs& specs, locale_ref loc) - -> basic_appender { - if (specs.localized() && write_loc(out, value, specs, loc)) return out; - return write_int_noinline(out, make_write_int_arg(value, specs.sign()), - specs); -} - -// An inlined version of write used in format string compilation. template ::value && !std::is_same::value && - !std::is_same::value && - !std::is_same>::value)> + !std::is_same::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, const format_specs& specs, locale_ref loc) -> OutputIt { if (specs.localized() && write_loc(out, value, specs, loc)) return out; - return write_int(out, make_write_int_arg(value, specs.sign()), specs); + auto arg = make_write_int_arg(value, specs.sign()); + // Out of line for appenders to avoid bloat; inlined for compiled formatting. + if FMT_CONSTEXPR20 (std::is_same>::value) + return write_int_noinline(out, arg, specs); + return write_int(out, arg, specs); } template From 7852fc384ce4232284fc685f66f70ed99b0c2cd4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Jul 2026 11:36:52 -0700 Subject: [PATCH 107/111] Fix out-of-range integers with the 'c' presentation type (#4839) Formatting an integer with ':c' used the magnitude (abs_value) and never range-checked, so negatives were mangled and out-of-range values silently truncated. Copy the value as a character and report an error when it is out of range, treating all character types as unsigned for portability. --- include/fmt/format.h | 13 +++++++++++-- test/format-test.cc | 10 ++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index f2ee623d..99743f1c 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1961,6 +1961,15 @@ FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { prefix += (1u + (value > 0xff ? 1 : 0)) << 24; } +// Writes an integer as a character, treating chars as unsigned. +template +FMT_CONSTEXPR auto write_int_chr(OutputIt out, UInt abs_value, bool negative, + const format_specs& specs) -> OutputIt { + if (negative || abs_value > max_value>()) + report_error("character value out of range"); + return write_char(out, static_cast(abs_value), specs); +} + // Writes a decimal integer with digit grouping. template auto write_int(OutputIt out, UInt value, unsigned prefix, @@ -1997,7 +2006,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, format_base2e(1, appender(buffer), value, num_digits); break; case presentation_type::chr: - return write_char(out, static_cast(value), specs); + return write_int_chr(out, value, (prefix & 0xff) == '-', specs); } unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + @@ -2124,7 +2133,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); break; case presentation_type::chr: - return write_char(out, static_cast(abs_value), specs); + return write_int_chr(out, abs_value, (prefix & 0xff) == '-', specs); } // Write an integer in the format diff --git a/test/format-test.cc b/test/format-test.cc index 8c2670a1..a1fccfc3 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1334,6 +1334,16 @@ TEST(format_test, format_int) { "invalid format specifier"); check_unknown_types(42, "bBdoxXnLc", "integer"); EXPECT_EQ(fmt::format("{:c}", static_cast('x')), "x"); + // The 'c' type treats all character types as unsigned for portability, so the + // representable range for char is [0, 255] and out-of-range values are + // reported as an error. + EXPECT_EQ(fmt::format("{:c}", 200), std::string(1, static_cast(200))); + EXPECT_EQ(fmt::format("{:c}", 255), std::string(1, static_cast(255))); + const char* msg = "character value out of range"; + EXPECT_THROW_MSG((void)fmt::format("{:c}", -1), format_error, msg); + EXPECT_THROW_MSG((void)fmt::format("{:c}", -104), format_error, msg); + EXPECT_THROW_MSG((void)fmt::format("{:c}", 256), format_error, msg); + EXPECT_THROW_MSG((void)fmt::format("{:c}", 400u), format_error, msg); } TEST(format_test, format_bin) { From b5d1e5404bd678df41f20d0f053222a6c60fe74b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 17 Jul 2026 12:12:25 -0700 Subject: [PATCH 108/111] Annotate FMT_USE_RTTI #endif directives Add trailing "// FMT_USE_RTTI" comments to the matching #endif lines to make the conditional-compilation blocks easier to follow. --- include/fmt/std.h | 4 ++-- test/std-test.cc | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/fmt/std.h b/include/fmt/std.h index 18b12abc..379b186e 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -649,7 +649,7 @@ struct formatter< *out++ = ':'; *out++ = ' '; } -#endif +#endif // FMT_USE_RTTI out = detail::write_bytes(out, string_view(ex.what())); #if FMT_USE_RTTI // If the exception carries a nested exception (e.g. via @@ -666,7 +666,7 @@ struct formatter< } } } -#endif +#endif // FMT_USE_RTTI return out; } }; diff --git a/test/std-test.cc b/test/std-test.cc index 8fb92f79..6fc92938 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -429,7 +429,7 @@ TEST(std_test, exception) { } catch (const std::exception& ex) { EXPECT_EQ("level 1: level 2: level 3", fmt::format("{}", ex)); } -#endif +#endif // FMT_USE_RTTI } TEST(std_test, exception_ptr) { @@ -462,7 +462,7 @@ TEST(std_test, exception_ptr) { p3 = std::current_exception(); } EXPECT_EQ(fmt::format("{}", p3), "outer: inner"); -#endif +#endif // FMT_USE_RTTI } #if FMT_USE_RTTI @@ -470,7 +470,7 @@ TEST(std_test, type_info) { EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)), "std::runtime_error"); } -#endif +#endif // FMT_USE_RTTI #if FMT_USE_BITINT FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") From 7b4ef1c8145f21e126167f52a594909dde8c89fe Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 19 Jul 2026 17:05:39 -0700 Subject: [PATCH 109/111] Optimize 128-bit integer formatting count_digits and do_format_decimal divided the full 128-bit value by a constant in a loop. Compilers do not strength-reduce 128-bit division, so each iteration became a slow __udivti3 library call (~27 per number). Reduce the value to 64-bit chunks by dividing by 10^19 (at most twice) and reuse the fast 64-bit paths, giving a ~5x speedup on full-range values. --- include/fmt/format.h | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index 99743f1c..3300b35b 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1064,12 +1064,6 @@ template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { count += 4; } } -#if FMT_USE_INT128 -FMT_CONSTEXPR inline auto count_digits(native_uint128 n) -> int { - return count_digits_fallback(n); -} -#endif - #ifdef FMT_BUILTIN_CLZLL // It is a separate function rather than a part of count_digits to workaround // the lack of static constexpr in constexpr functions. @@ -1100,6 +1094,20 @@ FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { return count_digits_fallback(n); } +#if FMT_USE_INT128 +FMT_CONSTEXPR20 inline auto count_digits(native_uint128 n) -> int { + if (is_constant_evaluated()) return count_digits_fallback(n); + // 128-bit division is a slow library call, so reduce to 64-bit chunks via + // 10^19 (the largest power of 10 fitting in uint64_t). + const uint64_t pow10_19 = 10000000000000000000ULL; + if ((n >> 64) == 0) return count_digits(static_cast(n)); + n /= pow10_19; + if ((n >> 64) == 0) return count_digits(static_cast(n)) + 19; + n /= pow10_19; + return count_digits(static_cast(n)) + 38; +} +#endif + // Counts the number of digits in n. BITS = log2(radix). template FMT_CONSTEXPR auto count_digits(UInt n) -> int { @@ -1231,6 +1239,26 @@ template FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) -> Char* { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); +#if FMT_USE_INT128 + // Avoid slow 128-bit division by formatting 64-bit chunks of 19 digits each. + if (!is_constant_evaluated() && num_bits() == 128) { + const uint64_t pow10_19 = 10000000000000000000ULL; + auto v = static_cast(value); + unsigned pos = to_unsigned(size); + while ((v >> 64) != 0) { + auto chunk = static_cast(v % pow10_19); + v /= pow10_19; + pos -= 19; + fill_n(out + pos, 19, Char('0')); + do_format_decimal(out + pos, chunk, 19); + } + auto top = static_cast(v); + int top_digits = count_digits(top); + pos -= to_unsigned(top_digits); + do_format_decimal(out + pos, top, top_digits); + return out + pos; + } +#endif // FMT_USE_INT128 unsigned n = to_unsigned(size); while (value >= 100) { n -= 2; From a749e9d8800c73d9888b5a32dacac187fb7d000c Mon Sep 17 00:00:00 2001 From: hexonal Date: Mon, 20 Jul 2026 18:09:34 -0400 Subject: [PATCH 110/111] Generate CMake export set regardless of FMT_INSTALL (#4850) export() was scoped inside if (FMT_INSTALL), so projects that pull in fmt via add_subdirectory()/FetchContent without installing it had no way to get the exported fmt::* targets. If such a project tries to export its own targets that depend on fmt, CMake fails with "target ... requires target fmt that is not in any export set". Hoist the target list/export name and the export() call itself out of the FMT_INSTALL guard so the build-tree export file is always generated; the install()-only pieces (config/version files, pkgconfig, install(EXPORT ...)) stay behind the guard and installed behavior is unchanged. Fixes #4806 Co-authored-by: flink --- CMakeLists.txt | 36 ++++++++++++----------- test/add-subdirectory-test/CMakeLists.txt | 9 ++++++ test/add-subdirectory-test/export-lib.cc | 3 ++ 3 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 test/add-subdirectory-test/export-lib.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8426ab85..9fd7662d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -441,6 +441,16 @@ add_library(fmt::fmt-c ALIAS fmt-c) set_target_properties(fmt-c PROPERTIES PUBLIC_HEADER include/fmt/fmt-c.h) # Install targets. +set(targets_export_name fmt-targets) +set(INSTALL_TARGETS fmt fmt-header-only fmt-c) + +if (FMT_MODULE) + list(APPEND INSTALL_TARGETS fmt-module) + if (FMT_USE_CMAKE_MODULES) + set(INSTALL_FILE_SET FILE_SET fmt DESTINATION ${FMT_INC_DIR}/fmt) + endif () +endif () + if (FMT_INSTALL) include(CMakePackageConfigHelpers) set_verbose( @@ -454,7 +464,6 @@ if (FMT_INSTALL) set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) - set(targets_export_name fmt-targets) set_verbose( FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING @@ -485,15 +494,6 @@ if (FMT_INSTALL) ${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in ${project_config} INSTALL_DESTINATION ${FMT_CMAKE_DIR}) - set(INSTALL_TARGETS fmt fmt-header-only fmt-c) - - if (FMT_MODULE) - list(APPEND INSTALL_TARGETS fmt-module) - if (FMT_USE_CMAKE_MODULES) - set(INSTALL_FILE_SET FILE_SET fmt DESTINATION ${FMT_INC_DIR}/fmt) - endif () - endif () - # Install the library and headers. install( TARGETS ${INSTALL_TARGETS} @@ -504,13 +504,6 @@ if (FMT_INSTALL) PUBLIC_HEADER DESTINATION ${FMT_INC_DIR}/fmt RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ${INSTALL_FILE_SET}) - # Use a namespace because CMake provides better diagnostics for namespaced - # imported targets. - export( - TARGETS ${INSTALL_TARGETS} - NAMESPACE fmt:: - FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) - # Install version, config and target files. install( FILES ${project_config} ${version_config} @@ -528,6 +521,15 @@ if (FMT_INSTALL) COMPONENT fmt_core) endif () +# Generate an export file in the build tree so that fmt can be used by +# add_subdirectory() or FetchContent without being installed. Use a +# namespace because CMake provides better diagnostics for namespaced +# imported targets. +export( + TARGETS ${INSTALL_TARGETS} + NAMESPACE fmt:: + FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) + function (add_doc_target) find_program(DOXYGEN doxygen PATHS "$ENV{ProgramFiles}/doxygen/bin" "$ENV{ProgramFiles\(x86\)}/doxygen/bin") diff --git a/test/add-subdirectory-test/CMakeLists.txt b/test/add-subdirectory-test/CMakeLists.txt index 362b0494..65be2525 100644 --- a/test/add-subdirectory-test/CMakeLists.txt +++ b/test/add-subdirectory-test/CMakeLists.txt @@ -15,3 +15,12 @@ if (TARGET fmt::fmt-header-only) target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) target_link_libraries(header-only-test fmt::fmt-header-only) endif () + +# Test that a target publicly depending on fmt can itself be exported even +# though fmt is added via add_subdirectory and FMT_INSTALL is off (#4806). +add_library(export-test STATIC export-lib.cc) +target_link_libraries(export-test PUBLIC fmt::fmt) +export( + TARGETS export-test + NAMESPACE test:: + FILE ${CMAKE_CURRENT_BINARY_DIR}/export-test-targets.cmake) diff --git a/test/add-subdirectory-test/export-lib.cc b/test/add-subdirectory-test/export-lib.cc new file mode 100644 index 00000000..e8adbb1c --- /dev/null +++ b/test/add-subdirectory-test/export-lib.cc @@ -0,0 +1,3 @@ +#include "fmt/base.h" + +void greet(const char* name) { fmt::print("Hello, {}!\n", name); } From bcaa44d05579c75a83571821faee7acf6a9a0d55 Mon Sep 17 00:00:00 2001 From: Yoshi <66006097+ny000815@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:24:55 -0400 Subject: [PATCH 111/111] Document that hexfloat 'a'/'A' always emits the 0x prefix (#4862) --- doc/syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/syntax.md b/doc/syntax.md index 48f48080..9df3cd93 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -251,7 +251,7 @@ fmt::format("{:#06x}", 0xfe); // # adds the prefix, 06 zero-pads to width 6 | Type | Effect | |------|---------------------------------------------------------------------| -| `a` | Hexadecimal-significand form (e.g. `1.8p+1`). Lower-case digits and a lower-case `p` for the binary exponent. The `#` flag adds a `0x` prefix. | +| `a` | Hexadecimal-significand form (e.g. `0x1.8p+1`). Lower-case digits and a lower-case `p` for the binary exponent. The `0x` prefix is always emitted, matching printf's `%a`. The `#` flag forces a decimal point (e.g. `0x1.p+1`). | `A` | Same as `a`, but upper-case throughout. | | `e` | Scientific notation with a lower-case `e` for the decimal exponent. | | `E` | Scientific notation with an upper-case `E`. |