mirror of
https://github.com/fmtlib/fmt.git
synced 2026-09-13 22:32:36 +08:00
* Format enums annotated with fmt::as_identifiers (requires C++26 reflection)
Format an enum as the identifier of the matching enumerator if the enum is
annotated with fmt::as_identifiers:
enum class [[=fmt::as_identifiers]] color { red, green, blue };
fmt::format("{}", color::green); // "green"
Values that don't match any enumerator are represented as their underlying
value in decimal before applying string formatting.
Identifiers are retrieved via C++26 reflection (P2996) and the annotation
via P3394. FMT_USE_REFLECTION is autodetected and can be overridden by the
user; without reflection the header is empty.
The header is also part of the fmt module, but, unlike with headers, whether
it provides anything is decided when the module is compiled, so the module
build detects reflection and enables it if the configured standard allows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Look up enum identifiers by index when the values are dense
The formatter for enums annotated with fmt::as_identifiers did a linear
search over all enumerators. Build a table indexed by the distance from
the smallest enumerator value instead, with empty string views in the
holes, which reduces the lookup to a bounds check and one load.
The table is only used if at least 70% of its elements are identifiers,
limiting its size to 10/7 of the number of enumerators. Sparser enums
keep using the linear search. Distances are computed in uint64_t so that
enums with negative values and values spanning the whole range of the
underlying type are handled without overflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Suppress -Wsfinae-incomplete in format-test on GCC 16
GCC 16 warns when a type is completed after it failed to be complete in a
SFINAE context. format-test does this deliberately to check that formatting
of incomplete types works, so the warning is a false positive there and
breaks the build with -Werror.
* Test GCC 16 on CI
GCC 16 is the first compiler with C++26 reflection support, which is needed
by fmt/enum.h, so add a job that builds with it in C++26 mode. It comes from
the ubuntu-toolchain-r/test PPA since Ubuntu 24.04 only ships GCC 14.
Also report when reflection is not detected to make it visible that
enum-test was skipped.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
291 lines
9.9 KiB
CMake
291 lines
9.9 KiB
CMake
add_subdirectory(gtest)
|
|
|
|
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
|
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
|
target_include_directories(
|
|
test-main PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
|
target_link_libraries(test-main gtest fmt)
|
|
|
|
# Adds a test.
|
|
# Usage: add_fmt_test(name srcs...)
|
|
function (add_fmt_test name)
|
|
cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY;MODULE" "" "" ${ARGN})
|
|
|
|
set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS})
|
|
if (ADD_FMT_TEST_HEADER_ONLY)
|
|
set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc)
|
|
set(libs gtest fmt-header-only)
|
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
|
|
endif ()
|
|
elseif (ADD_FMT_TEST_MODULE)
|
|
set(libs test-main fmt-module)
|
|
else ()
|
|
set(libs test-main fmt)
|
|
endif ()
|
|
add_executable(${name} ${sources})
|
|
target_link_libraries(${name} ${libs})
|
|
|
|
if (ADD_FMT_TEST_HEADER_ONLY AND NOT FMT_UNICODE)
|
|
target_compile_definitions(${name} PUBLIC FMT_UNICODE=0)
|
|
endif ()
|
|
|
|
# Define if certain C++ features can be used.
|
|
if (FMT_PEDANTIC)
|
|
target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
|
endif ()
|
|
if (FMT_WERROR)
|
|
target_compile_options(${name} PRIVATE ${WERROR_FLAG})
|
|
endif ()
|
|
add_test(NAME ${name} COMMAND ${name})
|
|
endfunction ()
|
|
|
|
add_fmt_test(args-test)
|
|
add_fmt_test(base-test)
|
|
add_fmt_test(assert-test)
|
|
add_fmt_test(chrono-test)
|
|
add_fmt_test(color-test)
|
|
add_fmt_test(gtest-extra-test)
|
|
add_fmt_test(format-test mock-allocator.h)
|
|
if (MSVC)
|
|
target_compile_options(format-test PRIVATE /bigobj)
|
|
endif ()
|
|
if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16)
|
|
# format-test completes a type after it failed to be complete in a SFINAE
|
|
# context, which is exactly what it is testing.
|
|
target_compile_options(format-test PRIVATE -Wno-sfinae-incomplete)
|
|
endif ()
|
|
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
|
add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc)
|
|
endif ()
|
|
add_fmt_test(ostream-test)
|
|
add_fmt_test(compile-test)
|
|
add_fmt_test(printf-test)
|
|
add_fmt_test(ranges-test ranges-odr-test.cc)
|
|
add_fmt_test(no-builtin-types-test HEADER_ONLY)
|
|
add_fmt_test(scan-test HEADER_ONLY)
|
|
add_fmt_test(std-test)
|
|
try_compile(
|
|
compile_result_unused ${CMAKE_CURRENT_BINARY_DIR}
|
|
SOURCES ${CMAKE_CURRENT_LIST_DIR}/detect-stdfs.cc
|
|
OUTPUT_VARIABLE RAWOUTPUT)
|
|
string(REGEX REPLACE ".*libfound \"([^\"]*)\".*" "\\1" STDLIBFS "${RAWOUTPUT}")
|
|
if (STDLIBFS)
|
|
target_link_libraries(std-test ${STDLIBFS})
|
|
endif ()
|
|
add_fmt_test(unicode-test HEADER_ONLY)
|
|
if (MSVC)
|
|
target_compile_options(unicode-test PRIVATE /utf-8)
|
|
endif ()
|
|
add_fmt_test(xchar-test)
|
|
add_fmt_test(enforce-checks-test)
|
|
target_compile_definitions(enforce-checks-test
|
|
PRIVATE -DFMT_ENFORCE_COMPILE_STRING)
|
|
|
|
# Enum formatting requires C++26 reflection, which needs compiler support and,
|
|
# in some compilers such as GCC, an extra flag to enable it.
|
|
if (NOT MSVC)
|
|
include(CheckCXXSourceCompiles)
|
|
set(CMAKE_REQUIRED_INCLUDES ${PROJECT_SOURCE_DIR}/include)
|
|
set(CMAKE_REQUIRED_FLAGS "-std=c++26")
|
|
check_cxx_source_compiles("${FMT_REFLECTION_TEST_CODE}" FMT_HAVE_REFLECTION)
|
|
if (NOT FMT_HAVE_REFLECTION)
|
|
set(CMAKE_REQUIRED_FLAGS "-std=c++26 -freflection")
|
|
check_cxx_source_compiles("${FMT_REFLECTION_TEST_CODE}"
|
|
FMT_HAVE_REFLECTION_FLAG)
|
|
endif ()
|
|
unset(CMAKE_REQUIRED_FLAGS)
|
|
unset(CMAKE_REQUIRED_INCLUDES)
|
|
|
|
if (FMT_HAVE_REFLECTION OR FMT_HAVE_REFLECTION_FLAG)
|
|
add_fmt_test(enum-test)
|
|
set_target_properties(
|
|
enum-test
|
|
PROPERTIES CXX_STANDARD 26
|
|
CXX_STANDARD_REQUIRED ON
|
|
CXX_EXTENSIONS OFF)
|
|
if (FMT_HAVE_REFLECTION_FLAG)
|
|
target_compile_options(enum-test PRIVATE -freflection)
|
|
endif ()
|
|
else ()
|
|
message(STATUS "Reflection is not supported, skipping enum-test.")
|
|
endif ()
|
|
endif ()
|
|
|
|
add_executable(perf-sanity perf-sanity.cc)
|
|
target_link_libraries(perf-sanity fmt::fmt)
|
|
|
|
if (FMT_MODULE)
|
|
add_fmt_test(module-test MODULE)
|
|
endif ()
|
|
|
|
if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
|
|
foreach (flag_var
|
|
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
|
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
|
|
if (${flag_var} MATCHES "^(/|-)(MT|MTd)")
|
|
set(MSVC_STATIC_RUNTIME ON)
|
|
break()
|
|
endif ()
|
|
endforeach ()
|
|
endif ()
|
|
|
|
if (NOT MSVC_STATIC_RUNTIME)
|
|
add_executable(posix-mock-test posix-mock-test.cc ../src/format.cc
|
|
${TEST_MAIN_SRC})
|
|
target_include_directories(posix-mock-test
|
|
PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
|
target_link_libraries(posix-mock-test gtest)
|
|
if (FMT_PEDANTIC)
|
|
target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
|
endif ()
|
|
if (MSVC)
|
|
target_compile_options(posix-mock-test PRIVATE /utf-8)
|
|
endif ()
|
|
add_test(NAME posix-mock-test COMMAND posix-mock-test)
|
|
add_fmt_test(os-test)
|
|
endif ()
|
|
|
|
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
|
|
|
|
if (FMT_PEDANTIC)
|
|
# Test that the library can be compiled with exceptions disabled.
|
|
# -fno-exception is broken in icc: https://github.com/fmtlib/fmt/issues/822.
|
|
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
|
|
check_cxx_compiler_flag(-fno-exceptions HAVE_FNO_EXCEPTIONS_FLAG)
|
|
endif ()
|
|
if (HAVE_FNO_EXCEPTIONS_FLAG)
|
|
add_library(noexception-test ../src/format.cc noexception-test.cc)
|
|
target_include_directories(noexception-test
|
|
PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
|
target_compile_options(noexception-test PRIVATE -fno-exceptions)
|
|
target_compile_options(noexception-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
|
endif ()
|
|
|
|
# Test that the library compiles without locale.
|
|
add_library(nolocale-test ../src/format.cc)
|
|
target_include_directories(nolocale-test
|
|
PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
|
target_compile_definitions(nolocale-test
|
|
PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1)
|
|
endif ()
|
|
|
|
# These tests are disabled on Windows because they take too long.
|
|
# They are disabled on GCC < 4.9 because it can't parse UDLs without a space
|
|
# after operator"" but that is an incorrect syntax for any more modern compiler.
|
|
if (FMT_PEDANTIC
|
|
AND NOT WIN32
|
|
AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION
|
|
VERSION_LESS 4.9))
|
|
# Test if incorrect API usages produce compilation error.
|
|
add_test(
|
|
compile-error-test
|
|
${CMAKE_CTEST_COMMAND}
|
|
--build-and-test
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/compile-error-test"
|
|
--build-generator
|
|
${CMAKE_GENERATOR}
|
|
--build-makeprogram
|
|
${CMAKE_MAKE_PROGRAM}
|
|
--build-options
|
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
|
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
|
"-DCXX_STANDARD_FLAG=${CXX_STANDARD_FLAG}"
|
|
"-DFMT_DIR=${CMAKE_SOURCE_DIR}")
|
|
|
|
# Test if the targets are found from the build directory.
|
|
add_test(
|
|
find-package-test
|
|
${CMAKE_CTEST_COMMAND}
|
|
-C
|
|
${CMAKE_BUILD_TYPE}
|
|
--build-and-test
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/find-package-test"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/find-package-test"
|
|
--build-generator
|
|
${CMAKE_GENERATOR}
|
|
--build-makeprogram
|
|
${CMAKE_MAKE_PROGRAM}
|
|
--build-options
|
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
|
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
|
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
|
"-DFMT_DIR=${PROJECT_BINARY_DIR}"
|
|
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
|
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
|
|
|
# Test if the targets are found when add_subdirectory is used.
|
|
add_test(
|
|
add-subdirectory-test
|
|
${CMAKE_CTEST_COMMAND}
|
|
-C
|
|
${CMAKE_BUILD_TYPE}
|
|
--build-and-test
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/add-subdirectory-test"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/add-subdirectory-test"
|
|
--build-generator
|
|
${CMAKE_GENERATOR}
|
|
--build-makeprogram
|
|
${CMAKE_MAKE_PROGRAM}
|
|
--build-options
|
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
|
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
|
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
|
endif ()
|
|
|
|
# This test is disabled on Windows because it is POSIX-specific.
|
|
if (FMT_PEDANTIC AND NOT WIN32)
|
|
add_test(
|
|
static-export-test
|
|
${CMAKE_CTEST_COMMAND}
|
|
-C
|
|
${CMAKE_BUILD_TYPE}
|
|
--build-and-test
|
|
"${CMAKE_CURRENT_SOURCE_DIR}/static-export-test"
|
|
"${CMAKE_CURRENT_BINARY_DIR}/static-export-test"
|
|
--build-generator
|
|
${CMAKE_GENERATOR}
|
|
--build-makeprogram
|
|
${CMAKE_MAKE_PROGRAM}
|
|
--build-options
|
|
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
|
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
|
|
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
|
endif ()
|
|
|
|
# Activate optional CUDA tests if CUDA is found. For version selection see
|
|
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
|
if (FMT_CUDA_TEST)
|
|
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
|
find_package(CUDA 9.0)
|
|
else ()
|
|
include(CheckLanguage)
|
|
check_language(CUDA)
|
|
if (CMAKE_CUDA_COMPILER)
|
|
enable_language(CUDA OPTIONAL)
|
|
set(CUDA_FOUND TRUE)
|
|
endif ()
|
|
endif ()
|
|
|
|
if (CUDA_FOUND)
|
|
add_subdirectory(cuda-test)
|
|
add_test(NAME cuda-test COMMAND fmt-in-cuda-test)
|
|
endif ()
|
|
endif ()
|
|
|
|
enable_language(C)
|
|
# Clang < 3.8 doesn't apply array-to-pointer decay to the controlling
|
|
# expression of _Generic (https://llvm.org/PR16340), so fmt-c.h's argument
|
|
# dispatch doesn't match string literals there.
|
|
if (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_VERSION
|
|
VERSION_LESS 3.8)
|
|
message(STATUS "Skipping c-test: _Generic is broken in Clang < 3.8")
|
|
else ()
|
|
add_executable(c-test c-test.c)
|
|
target_link_libraries(c-test PRIVATE fmt::fmt-c)
|
|
add_test(NAME c-test COMMAND c-test)
|
|
endif ()
|