From bea7ecc7107514f29eb6b0cef665173cf8a3d18b Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 24 Nov 2023 09:45:56 -0800 Subject: [PATCH 01/10] Disable locale-specific tests on OpenBSD --- test/util.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/util.cc b/test/util.cc index 4ff34a91..d3f2dc73 100644 --- a/test/util.cc +++ b/test/util.cc @@ -39,6 +39,11 @@ std::locale get_locale(const char* name, const char* alt_name) { auto loc = do_get_locale(name); if (loc == std::locale::classic() && alt_name) loc = do_get_locale(alt_name); +#ifdef __OpenBSD__ + // Locales are not working in OpenBSD: + // https://github.com/fmtlib/fmt/issues/3670. + loc = std::locale::classic(); +#endif if (loc == std::locale::classic()) fmt::print(stderr, "{} locale is missing.\n", name); return loc; From ffa5b14fe3c05fb111c6e1c4f9b98a7f5e0cc8ee Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 24 Nov 2023 10:09:21 -0800 Subject: [PATCH 02/10] Make gtest-extra-test more portable --- test/gtest-extra-test.cc | 2 +- test/gtest-extra.cc | 4 ++-- test/gtest-extra.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc index 42340a2d..34054b68 100644 --- a/test/gtest-extra-test.cc +++ b/test/gtest-extra-test.cc @@ -354,7 +354,7 @@ TEST(output_redirect_test, dup_error_in_ctor) { FMT_POSIX(close(fd)); std::unique_ptr redir{nullptr}; EXPECT_SYSTEM_ERROR_NOASSERT( - redir.reset(new output_redirect(f.get())), EBADF, + redir.reset(new output_redirect(f.get(), false)), EBADF, fmt::format("cannot duplicate file descriptor {}", fd)); copy.dup2(fd); // "undo" close or dtor will fail } diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc index 542e4b5e..3d27cf96 100644 --- a/test/gtest-extra.cc +++ b/test/gtest-extra.cc @@ -11,8 +11,8 @@ using fmt::file; -output_redirect::output_redirect(FILE* f) : file_(f) { - flush(); +output_redirect::output_redirect(FILE* f, bool flush) : file_(f) { + if (flush) this->flush(); int fd = FMT_POSIX(fileno(f)); // Create a file object referring to the original file. original_ = file::dup(fd); diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 03a07a2a..e08c94c0 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -77,7 +77,7 @@ class output_redirect { void restore(); public: - explicit output_redirect(FILE* file); + explicit output_redirect(FILE* file, bool flush = true); ~output_redirect() noexcept; output_redirect(const output_redirect&) = delete; From 06f1c0d725855861535e9e65cd4d502aca7c61ed Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Fri, 24 Nov 2023 10:21:57 -0800 Subject: [PATCH 03/10] Clarify that calling non-const format is deprecated --- include/fmt/core.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/fmt/core.h b/include/fmt/core.h index aaeb2cfa..ba982964 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1318,6 +1318,7 @@ template class value { parse_ctx.advance_to(f.parse(parse_ctx)); using qualified_type = conditional_t(), const T, T>; + // Calling format through a mutable reference is deprecated. ctx.advance_to(f.format(*static_cast(arg), ctx)); } }; From c3f9a73445d1569966894ed47061ae54f849188f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Nov 2023 07:41:04 -0800 Subject: [PATCH 04/10] Apply coding conventions --- test/scan-test.cc | 4 ++-- test/scan.h | 44 ++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/test/scan-test.cc b/test/scan-test.cc index bec54134..596579c6 100644 --- a/test/scan-test.cc +++ b/test/scan-test.cc @@ -70,7 +70,7 @@ namespace fmt { template <> struct scanner { std::string format; - scan_parse_context::iterator parse(scan_parse_context& ctx) { + auto parse(scan_parse_context& ctx) -> scan_parse_context::iterator { auto it = ctx.begin(); if (it != ctx.end() && *it == ':') ++it; auto end = it; @@ -82,7 +82,7 @@ template <> struct scanner { } template - typename ScanContext::iterator scan(tm& t, ScanContext& ctx) { + auto scan(tm& t, ScanContext& ctx) const -> typename ScanContext::iterator { auto result = strptime(ctx.begin(), format.c_str(), &t); if (!result) throw format_error("failed to parse time"); return result; diff --git a/test/scan.h b/test/scan.h index a2cb2aa6..d6fd6770 100644 --- a/test/scan.h +++ b/test/scan.h @@ -27,8 +27,8 @@ class scan_parse_context { explicit FMT_CONSTEXPR scan_parse_context(string_view format) : format_(format) {} - FMT_CONSTEXPR iterator begin() const { return format_.begin(); } - FMT_CONSTEXPR iterator end() const { return format_.end(); } + FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); } + FMT_CONSTEXPR auto end() const -> iterator { return format_.end(); } void advance_to(iterator it) { format_.remove_prefix(detail::to_unsigned(it - begin())); @@ -44,8 +44,8 @@ struct scan_context { explicit FMT_CONSTEXPR scan_context(string_view input) : input_(input) {} - iterator begin() const { return input_.data(); } - iterator end() const { return begin() + input_.size(); } + auto begin() const -> iterator { return input_.data(); } + auto end() const -> iterator { return begin() + input_.size(); } void advance_to(iterator it) { input_.remove_prefix(detail::to_unsigned(it - begin())); @@ -106,7 +106,7 @@ class scan_arg { template static void scan_custom_arg(void* arg, scan_parse_context& parse_ctx, scan_context& ctx) { - scanner s; + auto s = scanner(); parse_ctx.advance_to(s.parse(parse_ctx)); ctx.advance_to(s.scan(*static_cast(arg), ctx)); } @@ -134,7 +134,7 @@ struct scan_handler : error_handler { int next_arg_id_; scan_arg arg_; - template T read_uint() { + template auto read_uint() -> T { T value = 0; auto it = scan_ctx_.begin(), end = scan_ctx_.end(); while (it != end) { @@ -147,7 +147,7 @@ struct scan_handler : error_handler { return value; } - template T read_int() { + template auto read_int() -> T { auto it = scan_ctx_.begin(), end = scan_ctx_.end(); bool negative = it != end && *it == '-'; if (negative) ++it; @@ -162,7 +162,7 @@ struct scan_handler : error_handler { scan_args args) : parse_ctx_(format), scan_ctx_(input), args_(args), next_arg_id_(0) {} - const char* pos() const { return scan_ctx_.begin(); } + auto pos() const -> const char* { return scan_ctx_.begin(); } void on_text(const char* begin, const char* end) { auto size = to_unsigned(end - begin); @@ -172,13 +172,13 @@ struct scan_handler : error_handler { scan_ctx_.advance_to(it + size); } - FMT_CONSTEXPR int on_arg_id() { return on_arg_id(next_arg_id_++); } - FMT_CONSTEXPR int on_arg_id(int id) { + FMT_CONSTEXPR auto on_arg_id() -> int { return on_arg_id(next_arg_id_++); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { if (id >= args_.size) on_error("argument index out of range"); arg_ = args_.data[id]; return id; } - FMT_CONSTEXPR int on_arg_id(string_view id) { + FMT_CONSTEXPR auto on_arg_id(string_view id) -> int { if (id.data()) on_error("invalid format"); return 0; } @@ -215,7 +215,7 @@ struct scan_handler : error_handler { } } - const char* on_format_specs(int, const char* begin, const char*) { + auto on_format_specs(int, const char* begin, const char*) -> const char* { if (arg_.type != scan_type::custom_type) return begin; parse_ctx_.advance_to(begin); arg_.custom.scan(arg_.custom.value, parse_ctx_, scan_ctx_); @@ -224,21 +224,21 @@ struct scan_handler : error_handler { }; } // namespace detail -template -std::array make_scan_args(Args&... args) { +template +auto make_scan_args(T&... args) -> std::array { return {{args...}}; } -string_view::iterator vscan(string_view input, string_view format_str, - scan_args args) { - detail::scan_handler h(format_str, input, args); - detail::parse_format_string(format_str, h); +auto vscan(string_view input, string_view fmt, scan_args args) + -> string_view::iterator { + auto h = detail::scan_handler(fmt, input, args); + detail::parse_format_string(fmt, h); return input.begin() + (h.pos() - &*input.begin()); } -template -string_view::iterator scan(string_view input, string_view format_str, - Args&... args) { - return vscan(input, format_str, make_scan_args(args...)); +template +auto scan(string_view input, string_view fmt, T&... args) + -> string_view::iterator { + return vscan(input, fmt, make_scan_args(args...)); } FMT_END_NAMESPACE From c4283ec471bd3efdb114bc1ab30c7c7c5e5e0ee0 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Nov 2023 07:58:27 -0800 Subject: [PATCH 05/10] Fix a libc++ warning and move the test to the right place --- test/format-impl-test.cc | 7 ------- test/format-test.cc | 10 ++++++++++ test/xchar-test.cc | 12 ------------ 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 4d6198b6..eda1f239 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -246,13 +246,6 @@ TEST(format_impl_test, format_error_code) { } } -TEST(format_impl_test, compute_width) { - EXPECT_EQ(4, - fmt::detail::compute_width( - fmt::basic_string_view( - reinterpret_cast("ёжик")))); -} - // Tests fmt::detail::count_digits for integer type Int. template void test_count_digits() { for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::detail::count_digits(i)); diff --git a/test/format-test.cc b/test/format-test.cc index 08b4e8c1..34eb28a3 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -173,6 +173,10 @@ TEST(util_test, parse_nonnegative_int) { EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1); } +TEST(format_impl_test, compute_width) { + EXPECT_EQ(fmt::detail::compute_width("вожык"), 5); +} + TEST(util_test, utf8_to_utf16) { auto u = fmt::detail::utf8_to_utf16("лошадка"); EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str()); @@ -1053,6 +1057,12 @@ TEST(format_test, precision) { EXPECT_EQ("123456", fmt::format("{0:.6}", "123456\xad")); } +TEST(xchar_test, utf8_precision) { + auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés + EXPECT_EQ(fmt::detail::compute_width(result), 4); + EXPECT_EQ(result, "caf\u00e9"); +} + TEST(format_test, runtime_precision) { char format_str[buffer_size]; safe_sprintf(format_str, "{0:.{%u", UINT_MAX); diff --git a/test/xchar-test.cc b/test/xchar-test.cc index f72e94dc..90ada586 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -187,18 +187,6 @@ template std::string from_u8str(const S& str) { return std::string(str.begin(), str.end()); } -TEST(xchar_test, format_utf8_precision) { - using str_type = std::basic_string; - auto format = - str_type(reinterpret_cast(u8"{:.4}")); - auto str = str_type(reinterpret_cast( - u8"caf\u00e9s")); // cafés - auto result = fmt::format(format, str); - EXPECT_EQ(fmt::detail::compute_width(result), 4); - EXPECT_EQ(result.size(), 5); - EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5))); -} - TEST(xchar_test, format_to) { auto buf = std::vector(); fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0'); From ccc9ab7bf9c5aab0071708a0f65e3019bd96b8fe Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Nov 2023 08:23:41 -0800 Subject: [PATCH 06/10] Include correct header --- test/scan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scan.h b/test/scan.h index d6fd6770..a7b3b33c 100644 --- a/test/scan.h +++ b/test/scan.h @@ -9,7 +9,7 @@ #include #include -#include "fmt/format.h" +#include "fmt/core.h" FMT_BEGIN_NAMESPACE template struct scanner { From 7f8d4191157025813f5fd520cb68738b3ee0fe69 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sat, 25 Nov 2023 16:36:55 +0000 Subject: [PATCH 07/10] Fix overflow in time_point formatting with large dates (#3727) * Fix #3725 and rename fmt_safe_duration_cast to fmt_duration_cast The function is now more generic and will handle all casts. It also takes care of toggling safe vs unsafe casts using FMT_SAFE_DURATION_CAST. * Refactor fmt_duration_cast to put #ifdef inside the function * Fix compilation error with FMT_USE_LOCAL_TIME --- include/fmt/chrono.h | 113 +++++++++++++++++++++++++------------------ test/chrono-test.cc | 14 ++++++ 2 files changed, 81 insertions(+), 46 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 7bd4206d..3d2007bb 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -430,6 +430,51 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(is_same_arithmetic_type::value)> +To fmt_duration_cast(std::chrono::duration from) { +#if FMT_SAFE_DURATION_CAST + // throwing version of safe_duration_cast + // only available for integer<->integer or float<->float casts + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) FMT_THROW(format_error("cannot format duration")); + return to; +#else + // standard duration cast, may overflow and invoke undefined behavior + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +To fmt_duration_cast(std::chrono::duration from) { + // mixed integer<->float cast is not supported with safe_duration_cast + // fallback to standard duration cast in this case + return std::chrono::duration_cast(from); +} + +template +std::time_t to_time_t( + std::chrono::time_point time_point) { + // cannot use std::chrono::system_clock::to_time_t() since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return fmt_duration_cast>( + time_point.time_since_epoch()) + .count(); +} } // namespace detail FMT_BEGIN_EXPORT @@ -478,8 +523,8 @@ inline std::tm localtime(std::time_t time) { #if FMT_USE_LOCAL_TIME template inline auto localtime(std::chrono::local_time time) -> std::tm { - return localtime(std::chrono::system_clock::to_time_t( - std::chrono::current_zone()->to_sys(time))); + return localtime( + detail::to_time_t(std::chrono::current_zone()->to_sys(time))); } #endif @@ -523,9 +568,10 @@ inline std::tm gmtime(std::time_t time) { return gt.tm_; } +template inline std::tm gmtime( - std::chrono::time_point time_point) { - return gmtime(std::chrono::system_clock::to_time_t(time_point)); + std::chrono::time_point time_point) { + return gmtime(detail::to_time_t(time_point)); } namespace detail { @@ -1051,13 +1097,12 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { std::chrono::seconds::rep>::type, std::ratio<1, detail::pow10(num_fractional_digits)>>; - const auto fractional = - d - std::chrono::duration_cast(d); + const auto fractional = d - fmt_duration_cast(d); const auto subseconds = std::chrono::treat_as_floating_point< typename subsecond_precision::rep>::value ? fractional.count() - : std::chrono::duration_cast(fractional).count(); + : fmt_duration_cast(fractional).count(); auto n = static_cast>(subseconds); const int num_digits = detail::count_digits(n); @@ -1620,17 +1665,6 @@ template struct make_unsigned_or_unchanged { using type = typename std::make_unsigned::type; }; -#if FMT_SAFE_DURATION_CAST -// throwing version of safe_duration_cast -template -To fmt_safe_duration_cast(std::chrono::duration from) { - int ec; - To to = safe_duration_cast::safe_duration_cast(from, ec); - if (ec) FMT_THROW(format_error("cannot format duration")); - return to; -} -#endif - template ::value)> inline std::chrono::duration get_milliseconds( @@ -1640,17 +1674,17 @@ inline std::chrono::duration get_milliseconds( #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type::type; - const auto d_as_common = fmt_safe_duration_cast(d); + const auto d_as_common = fmt_duration_cast(d); const auto d_as_whole_seconds = - fmt_safe_duration_cast(d_as_common); + fmt_duration_cast(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = - fmt_safe_duration_cast>(diff); + fmt_duration_cast>(diff); return ms; #else - auto s = std::chrono::duration_cast(d); - return std::chrono::duration_cast(d - s); + auto s = fmt_duration_cast(d); + return fmt_duration_cast(d - s); #endif } @@ -1751,14 +1785,8 @@ struct chrono_formatter { // this may overflow and/or the result may not fit in the // target type. -#if FMT_SAFE_DURATION_CAST // might need checked conversion (rep!=Rep) - auto tmpval = std::chrono::duration(val); - s = fmt_safe_duration_cast(tmpval); -#else - s = std::chrono::duration_cast( - std::chrono::duration(val)); -#endif + s = fmt_duration_cast(std::chrono::duration(val)); } // returns true if nan or inf, writes to out. @@ -2082,25 +2110,22 @@ struct formatter, period::num != 1 || period::den != 1 || std::is_floating_point::value)) { const auto epoch = val.time_since_epoch(); - auto subsecs = std::chrono::duration_cast( - epoch - std::chrono::duration_cast(epoch)); + auto subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); if (subsecs.count() < 0) { auto second = - std::chrono::duration_cast(std::chrono::seconds(1)); + detail::fmt_duration_cast(std::chrono::seconds(1)); if (epoch.count() < ((Duration::min)() + second).count()) FMT_THROW(format_error("duration is too small")); subsecs += second; val -= second; } - return formatter::do_format( - gmtime(std::chrono::time_point_cast(val)), ctx, - &subsecs); + return formatter::do_format(gmtime(val), ctx, &subsecs); } - return formatter::format( - gmtime(std::chrono::time_point_cast(val)), ctx); + return formatter::format(gmtime(val), ctx); } }; @@ -2119,17 +2144,13 @@ struct formatter, Char> if (period::num != 1 || period::den != 1 || std::is_floating_point::value) { const auto epoch = val.time_since_epoch(); - const auto subsecs = std::chrono::duration_cast( - epoch - std::chrono::duration_cast(epoch)); + const auto subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); - return formatter::do_format( - localtime(std::chrono::time_point_cast(val)), - ctx, &subsecs); + return formatter::do_format(localtime(val), ctx, &subsecs); } - return formatter::format( - localtime(std::chrono::time_point_cast(val)), - ctx); + return formatter::format(localtime(val), ctx); } }; #endif diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 07760688..b562a50e 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -874,6 +874,20 @@ TEST(chrono_test, timestamps_ratios) { t4(std::chrono::duration>(1)); EXPECT_EQ(fmt::format("{:%M:%S}", t4), "01:03"); + + std::chrono::time_point + t5(std::chrono::seconds(32503680000)); + + EXPECT_EQ(fmt::format("{:%Y-%m-%d}", t5), "3000-01-01"); + +#if FMT_SAFE_DURATION_CAST + using years = std::chrono::duration>; + std::chrono::time_point t6( + (years(std::numeric_limits::max()))); + + EXPECT_THROW_MSG((void)fmt::format("{:%Y-%m-%d}", t6), fmt::format_error, + "cannot format duration"); +#endif } TEST(chrono_test, timestamps_sub_seconds) { From 2d1e4bb35e93f050d4cebc3ef6f5559ae448f4ea Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Nov 2023 09:05:20 -0800 Subject: [PATCH 08/10] Remove a useless comment --- include/fmt/format.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index d4d20965..8762826e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1028,7 +1028,6 @@ class basic_memory_buffer final : public detail::buffer { /** Increases the buffer capacity to *new_capacity*. */ void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } - // Directly append data into the buffer using detail::buffer::append; template void append(const ContiguousRange& range) { From 6988be3878661db9c1809e319adae1fd743a1835 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Nov 2023 09:11:53 -0800 Subject: [PATCH 09/10] Bump version --- include/fmt/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index ba982964..3c7fd048 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -18,7 +18,7 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 100101 +#define FMT_VERSION 100102 #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) From 73fae91e644215d4f7e09a9e62a14595539f4486 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 25 Nov 2023 09:45:38 -0800 Subject: [PATCH 10/10] Cleanup .gitignore --- .gitignore | 45 ++++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 8a37cb98..1406ac34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,24 @@ -.vscode/ -.vs/ - -*.iml -.idea/ -.externalNativeBuild/ -.gradle/ -gradle/ -gradlew* -local.properties -build/ -support/.cxx - -bin/ -/_CPack_Packages -/CMakeScripts -/doc/doxyxml -/doc/html -/doc/node_modules -virtualenv -/Testing -/install_manifest.txt -*~ *.a *.so* *.xcodeproj -*.zip -cmake_install.cmake -CPack*.cmake -fmt-*.cmake -CTestTestfile.cmake +*~ +.vscode/ +/CMakeScripts +/Testing +/_CPack_Packages +/doc/doxyxml +/doc/html +/doc/node_modules +/install_manifest.txt CMakeCache.txt CMakeFiles +CPack*.cmake +CTestTestfile.cmake FMT.build Makefile -run-msbuild.bat +bin/ +build/ +cmake_install.cmake +fmt-*.cmake fmt.pc +virtualenv