From 09de78209e46bfbc5ee9f833970a1fd5e6e89ecf Mon Sep 17 00:00:00 2001 From: neargye Date: Sat, 11 Jul 2026 16:20:36 +0400 Subject: [PATCH 01/11] add new magic_enum::magic_enum_module target --- .github/workflows/macos.yml | 6 +- .../module_test_installed_version.yml | 84 +++++ .github/workflows/ubuntu.yml | 41 ++- .../ubuntu_test_installed_version.yml | 77 +---- .github/workflows/windows.yml | 2 +- CMakeLists.txt | 324 +++++++++++------- Dockerfile | 72 +--- README.md | 25 +- cmake/magic_enumConfig.cmake.in | 11 + example/CMakeLists.txt | 69 ++-- module/magic_enum.cppm | 207 ++++++----- test/CMakeLists.txt | 301 +++++++++------- test/installed_module/CMakeLists.txt | 23 ++ test/installed_module/main.cpp | 42 +++ test/magic_enum_wchar_t_test_config.hpp | 3 + test/stage_package.cmake | 15 + test/test_module.cpp | 114 +++++- test/test_module_aliases.cpp | 21 ++ test/test_module_wchar_t.cpp | 20 ++ test_installed_version.bash | 53 ++- 20 files changed, 960 insertions(+), 550 deletions(-) create mode 100644 .github/workflows/module_test_installed_version.yml create mode 100644 cmake/magic_enumConfig.cmake.in create mode 100644 test/installed_module/CMakeLists.txt create mode 100644 test/installed_module/main.cpp create mode 100644 test/stage_package.cmake create mode 100644 test/test_module_aliases.cpp create mode 100644 test/test_module_wchar_t.cpp diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3f15b23db..8bb36b9c8 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -10,8 +10,8 @@ jobs: fail-fast: false matrix: os: - - macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md#xcode (Apple Silicon M1) - - macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#xcode (Apple Silicon M2) + - macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md#xcode + - macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-Readme.md#xcode build_type: [Release, Debug] name: "${{ matrix.os }} / ${{ matrix.build_type }}" @@ -27,4 +27,4 @@ jobs: run: cmake --build build --parallel --config ${{ matrix.build_type }} - name: Test - run: ctest --test-dir build --output-on-failure -C ${{ matrix.build_type }} + run: ctest --test-dir build --output-on-failure --no-tests=error -C ${{ matrix.build_type }} diff --git a/.github/workflows/module_test_installed_version.yml b/.github/workflows/module_test_installed_version.yml new file mode 100644 index 000000000..30852e36b --- /dev/null +++ b/.github/workflows/module_test_installed_version.yml @@ -0,0 +1,84 @@ +name: module_test_installed_version.yml + +on: [push, pull_request] + +permissions: read-all + +jobs: + macos-gcc: + name: macOS / GCC 16 / import std / C++26 + runs-on: macos-15 + env: + CC: gcc-16 + CXX: g++-16 + steps: + - uses: actions/checkout@v7 + + - name: Install toolchain + run: | + brew install gcc ninja fmt + python3 -m pip install --upgrade "cmake>=4.4.0" --break-system-packages + + - name: Build and test module package + run: | + modules_json="$("${CXX}" -print-file-name=libstdc++.modules.json)" + fmt_prefix="$(brew --prefix fmt)" + cmake -S . -B build-module -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=f35a9ac6-8463-4d38-8eec-5d6008153e7d \ + -DCMAKE_CXX_STDLIB_MODULES_JSON="${modules_json}" \ + -DCMAKE_CXX_STANDARD=26 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_CXX_EXTENSIONS=OFF \ + -DCMAKE_PREFIX_PATH="${fmt_prefix}" \ + -DMAGIC_ENUM_USE_MODULES=ON \ + -DMAGIC_ENUM_MODULE_IMPORT_STD=ON \ + -DMAGIC_ENUM_MODULE_WITH_FMT=ON \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=ON \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON + cmake --build build-module --parallel + cmake --build build-module --target magic_enum_verify_interface_header_sets --parallel + ctest --test-dir build-module --output-on-failure --no-tests=error + + - name: Test installed module with fmt and without import std + run: | + fmt_prefix="$(brew --prefix fmt)" + cmake -S . -B build-module-fmt -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_PREFIX_PATH="${fmt_prefix}" \ + -DMAGIC_ENUM_USE_MODULES=ON \ + -DMAGIC_ENUM_MODULE_WITH_FMT=ON \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=ON \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF + cmake --build build-module-fmt --parallel --target magic_enum_module + ctest --test-dir build-module-fmt \ + --output-on-failure \ + --no-tests=error \ + -R '^installed-module-consumer$' + + windows-msvc: + name: Windows / VS 2026 / C++20 + runs-on: windows-2025-vs2026 + steps: + - uses: actions/checkout@v7 + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Install pinned CMake + shell: pwsh + run: python -m pip install cmake==4.3.4 + + - name: Build and test module package + shell: pwsh + run: | + cmake -S . -B build-module -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_CXX_STANDARD=20 ` + -DCMAKE_CXX_STANDARD_REQUIRED=ON ` + -DMAGIC_ENUM_USE_MODULES=ON ` + -DMAGIC_ENUM_OPT_BUILD_TESTS=ON ` + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON + cmake --build build-module --parallel --target magic_enum_module example_module_usage test_module-cpp20 test_module_aliases-cpp20 test_module_wchar_t-cpp20 + cmake --build build-module --parallel --target magic_enum_verify_interface_header_sets + ctest --test-dir build-module --output-on-failure --no-tests=error -R '^(test_module(_aliases|_wchar_t)?-cpp20|installed-module-consumer)$' diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index d9bab62f6..3570955ce 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -24,7 +24,6 @@ jobs: - { cc: "clang-17", cxx: "clang++-17", os: "ubuntu-24.04", nonascii: "FALSE", modules: "FALSE", import_std: "FALSE" } - { cc: "clang-18", cxx: "clang++-18", os: "ubuntu-24.04", nonascii: "FALSE", modules: "FALSE", import_std: "FALSE" } - { cc: "clang-19", cxx: "clang++-19", os: "ubuntu-24.04", nonascii: "FALSE", modules: "FALSE", import_std: "FALSE" } - - { cc: "clang-19", cxx: "clang++-19", os: "ubuntu-24.04", nonascii: "FALSE", modules: "TRUE", import_std: "FALSE" } - { cc: "clang-19", cxx: "clang++-19", os: "ubuntu-24.04", nonascii: "FALSE", modules: "TRUE", import_std: "TRUE" } - { cc: "clang-20", cxx: "clang++-20", os: "ubuntu-24.04", nonascii: "FALSE", modules: "FALSE", import_std: "FALSE" } - { cc: "clang-20", cxx: "clang++-20", os: "ubuntu-24.04", nonascii: "FALSE", modules: "TRUE", import_std: "FALSE" } @@ -49,8 +48,11 @@ jobs: clang_version="${{ matrix.compiler.cc }}" clang_version="${clang_version#clang-}" ubuntu_codename="$(lsb_release -cs)" + llvm_repository="http://apt.llvm.org/${ubuntu_codename}/" + llvm_suite="llvm-toolchain-${ubuntu_codename}-${clang_version}" - echo "deb [signed-by=/etc/apt/keyrings/llvm-archive-keyring.gpg] http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-${clang_version} main" | sudo tee /etc/apt/sources.list.d/llvm.list + echo "deb [signed-by=/etc/apt/keyrings/llvm-archive-keyring.gpg] ${llvm_repository} ${llvm_suite} main" \ + | sudo tee /etc/apt/sources.list.d/llvm.list sudo apt update sudo apt install -y ${{ matrix.compiler.cc }} @@ -70,9 +72,14 @@ jobs: sudo apt update sudo apt install -y ${{ matrix.compiler.cxx }} - - name: Install CMake (latest) + - name: Install pinned CMake if: ${{ matrix.compiler.modules == 'TRUE' }} - run: pip install cmake --upgrade --break-system-packages + run: | + if [[ "${{ matrix.compiler.import_std }}" == "TRUE" ]]; then + pip install --upgrade "cmake>=4.4.0" --break-system-packages + else + pip install cmake==4.3.4 --break-system-packages + fi - name: Configure Release run: | @@ -86,7 +93,7 @@ jobs: run: cmake --build build-release --parallel --config Release - name: Test Release - run: ctest --test-dir build-release --output-on-failure -C Release + run: ctest --test-dir build-release --output-on-failure --no-tests=error -C Release - name: Configure Debug run: | @@ -100,27 +107,27 @@ jobs: run: cmake --build build-debug --parallel --config Debug - name: Test Debug - run: ctest --test-dir build-debug --output-on-failure -C Debug + run: ctest --test-dir build-debug --output-on-failure --no-tests=error -C Debug - name: Configure with `import std;` if: ${{ matrix.compiler.import_std == 'TRUE' }} run: | - stdlib_options=() - if [[ "${{ matrix.compiler.cc }}" == clang-* ]]; then - stdlib_options+=("-DCMAKE_CXX_FLAGS:STRING=-stdlib=libc++") - else - stdlib_options+=("-DCMAKE_CXX_STDLIB_MODULES_JSON=$("${CXX}" -print-file-name=libstdc++.modules.json)") + cxx_standard=23 + stdlib_options=(-DCMAKE_CXX_FLAGS:STRING=-stdlib=libc++) + if [[ "${{ matrix.compiler.cc }}" == gcc-* ]]; then + cxx_standard=26 + stdlib_options=(-DCMAKE_CXX_STDLIB_MODULES_JSON="$("${CXX}" -print-file-name=libstdc++.modules.json)") fi cmake -S . -B build-import-std \ -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=451f2fe2-a8a2-47c3-bc32-94786d8fc91b \ - -DMAGIC_ENUM_USE_MODULES:BOOL=TRUE \ - -GNinja \ - -DCMAKE_CXX_MODULE_STD=ON \ - -DCMAKE_CXX_STANDARD=23 \ + -DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=f35a9ac6-8463-4d38-8eec-5d6008153e7d \ + -DCMAKE_CXX_STANDARD="${cxx_standard}" \ -DCMAKE_CXX_STANDARD_REQUIRED=ON \ -DCMAKE_CXX_EXTENSIONS=OFF \ + -DMAGIC_ENUM_USE_MODULES:BOOL=TRUE \ + -DMAGIC_ENUM_MODULE_IMPORT_STD:BOOL=TRUE \ + -GNinja \ "${stdlib_options[@]}" - name: Build with `import std;` @@ -129,7 +136,7 @@ jobs: - name: Test with `import std;` if: ${{ matrix.compiler.import_std == 'TRUE' }} - run: ctest --test-dir build-import-std --output-on-failure -C Debug + run: ctest --test-dir build-import-std --output-on-failure --no-tests=error -C Debug bazel: name: Bazel diff --git a/.github/workflows/ubuntu_test_installed_version.yml b/.github/workflows/ubuntu_test_installed_version.yml index 9e2ec35f7..222cffea3 100644 --- a/.github/workflows/ubuntu_test_installed_version.yml +++ b/.github/workflows/ubuntu_test_installed_version.yml @@ -1,6 +1,5 @@ name: ubuntu_test_installed_version.yml - on: push: branches: @@ -11,9 +10,7 @@ on: - master - main -env: - CTEST_OUTPUT_ON_FAILURE: 1 - CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules +permissions: read-all jobs: build: @@ -22,73 +19,5 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: actions/cache@v6 - with: - path: "**/cpm_modules" - key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} - - - name: install library - run: | - cmake -S . -B build-install -DCMAKE_BUILD_TYPE=Release --log-level=DEBUG -DCMAKE_INSTALL_PREFIX=./install_dir - cmake --build build-install --parallel - cmake --install build-install --prefix ./install_dir - rm -rf build-install - - - name: configure with installed version via cmake - run: CMAKE_PREFIX_PATH="./install_dir" cmake -S . -B build-installed-cmake -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=1 --log-level=DEBUG - - - name: build with installed version via cmake - run: cmake --build build-installed-cmake --config Debug --parallel --verbose - - - name: test (cmake) - run: ctest --test-dir build-installed-cmake --build-config Debug --parallel $(nproc) - - - name: clear - run: rm -rf build-installed-cmake - - - name: configure clang - run: | - wget -qO - https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/llvm-archive-keyring.gpg > /dev/null - echo "deb [signed-by=/etc/apt/keyrings/llvm-archive-keyring.gpg] http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main" | sudo tee /etc/apt/sources.list.d/llvm.list - sudo apt update - sudo apt install clang-19 -y - - - name: install module library - run: | - cmake -S . -B build-module-install \ - -GNinja \ - --log-level=DEBUG \ - -DCMAKE_INSTALL_PREFIX=./install_dir_module \ - -DMAGIC_ENUM_USE_MODULES=ON \ - -DCMAKE_CXX_COMPILER=clang++-19 - cmake --build build-module-install --target magic_enum --parallel - cmake --install build-module-install - rm -rf build-module-install - - - name: configure with installed module version via cmake - run: | - CMAKE_PREFIX_PATH="./install_dir_module" cmake -S . -B build-installed-module \ - -GNinja \ - -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=1 \ - -DMAGIC_ENUM_USE_MODULES=ON \ - -DCMAKE_CXX_STANDARD=20 \ - -DCMAKE_CXX_COMPILER=clang++-19 \ - --log-level=DEBUG - - - name: build with installed module version via cmake - run: cmake --build build-installed-module --parallel --target test_flags-cpp20 test_module-cpp20 --verbose - - - name: test (modules) - run: ctest --test-dir build-installed-module -VV -R "^(test_flags|test_module)-cpp20$" - - - name: clear - run: rm -rf build-installed-module - - - name: configure with installed version via pkgconfig - run: CMAKE_PREFIX_PATH="./install_dir" cmake -S . -B build-installed-pkgconfig -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG=1 -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=1 --log-level=DEBUG - - - name: build with installed version via pkgconfig - run: cmake --build build-installed-pkgconfig --config Debug --parallel --verbose - - - name: test (pkgconfig) - run: ctest --test-dir build-installed-pkgconfig --build-config Debug --parallel $(nproc) + - name: test installed package + run: bash ./test_installed_version.bash diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 71e553a10..0bfa3c78c 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,4 +27,4 @@ jobs: run: cmake --build build --parallel --config ${{ matrix.build_type }} - name: Test - run: ctest --test-dir build --output-on-failure -C ${{ matrix.build_type }} + run: ctest --test-dir build --output-on-failure --no-tests=error -C ${{ matrix.build_type }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8150612..a7909b9c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,13 @@ -cmake_minimum_required(VERSION 3.22) +cmake_minimum_required(VERSION 3.22...4.4) if(MAGIC_ENUM_USE_MODULES AND CMAKE_VERSION VERSION_LESS "3.28") message(FATAL_ERROR "MAGIC_ENUM_USE_MODULES requires CMake >= 3.28") endif() +if(MAGIC_ENUM_MODULE_IMPORT_STD AND MAGIC_ENUM_USE_MODULES AND NOT CMAKE_CXX_STANDARD) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires an explicit CMAKE_CXX_STANDARD") +endif() + if(MAGIC_ENUM_USE_MODULES) if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) @@ -22,89 +26,133 @@ set(CPACK_PACKAGE_VENDOR "Daniil Goncharov") include(GNUInstallDirs) -set(ADDITIONAL_MODULES_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake") -list(APPEND CMAKE_MODULE_PATH "${ADDITIONAL_MODULES_DIR}") +set(MAGIC_ENUM_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH "${MAGIC_ENUM_CMAKE_DIR}") -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - set(IS_TOPLEVEL_PROJECT TRUE) -else() - set(IS_TOPLEVEL_PROJECT FALSE) +option(MAGIC_ENUM_OPT_BUILD_EXAMPLES "Build magic_enum examples" ${PROJECT_IS_TOP_LEVEL}) +option(MAGIC_ENUM_OPT_BUILD_TESTS "Build and perform magic_enum tests" ${PROJECT_IS_TOP_LEVEL}) +option(MAGIC_ENUM_OPT_INSTALL "Generate and install magic_enum target" ${PROJECT_IS_TOP_LEVEL}) +set(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT OFF) +if(MAGIC_ENUM_OPT_BUILD_TESTS AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") + set(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT ON) endif() - -option(MAGIC_ENUM_OPT_BUILD_EXAMPLES "Build magic_enum examples" ${IS_TOPLEVEL_PROJECT}) -option(MAGIC_ENUM_OPT_BUILD_TESTS "Build and perform magic_enum tests" ${IS_TOPLEVEL_PROJECT}) -option(MAGIC_ENUM_OPT_INSTALL "Generate and install magic_enum target" ${IS_TOPLEVEL_PROJECT}) -option(MAGIC_ENUM_OPT_INSTALL_PACKAGE_XML "Include package.xml when installing" - ${MAGIC_ENUM_OPT_INSTALL} -) -option(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION "When configuring tests, try use -a local magic_enum." NO -) -option(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG "When configuring tests, try use -a local magic_enum via pkgconfig" NO +option( + MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS + "Verify that public magic_enum headers can be included independently" + ${MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT} ) +option(MAGIC_ENUM_USE_MODULES "Build the magic_enum C++20 module target" OFF) +option(MAGIC_ENUM_MODULE_IMPORT_STD "Build the module target with import std" OFF) +option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt integration" OFF) +option(MAGIC_ENUM_OPT_INSTALL_PACKAGE_XML "Include package.xml when installing" ${MAGIC_ENUM_OPT_INSTALL}) + +if(MAGIC_ENUM_MODULE_IMPORT_STD AND NOT MAGIC_ENUM_USE_MODULES) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires MAGIC_ENUM_USE_MODULES=ON") +endif() +if(MAGIC_ENUM_MODULE_WITH_FMT AND NOT MAGIC_ENUM_USE_MODULES) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_WITH_FMT requires MAGIC_ENUM_USE_MODULES=ON") +endif() +option(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION "Test against an installed magic_enum CMake package" OFF) +option(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG "Test against an installed magic_enum pkg-config package" OFF) -if(${MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION} OR ${MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG}) +set(MAGIC_ENUM_MODULE_CXX_STANDARD 20) +if(DEFINED CMAKE_CXX_STANDARD AND + CMAKE_CXX_STANDARD GREATER MAGIC_ENUM_MODULE_CXX_STANDARD) + set(MAGIC_ENUM_MODULE_CXX_STANDARD ${CMAKE_CXX_STANDARD}) +endif() + +if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION OR MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG) enable_testing() add_subdirectory(test) return() endif() -set(INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/include/magic_enum") -set(MODULE "${CMAKE_CURRENT_SOURCE_DIR}/module") -set(EXPORT_NAMESPACE "${PROJECT_NAME}::") +set(MAGIC_ENUM_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/magic_enum") +set(MAGIC_ENUM_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/module") +set(MAGIC_ENUM_EXPORT_NAMESPACE "${PROJECT_NAME}::") +set(MAGIC_ENUM_HEADERS + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_all.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_containers.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_flags.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_format.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_fuse.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_iostream.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_switch.hpp" + "${MAGIC_ENUM_INCLUDE_DIR}/magic_enum_utility.hpp" +) -function(magic_enum_make_library name) +add_library(${PROJECT_NAME} INTERFACE) +add_library(${MAGIC_ENUM_EXPORT_NAMESPACE}${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.23") + target_sources( + ${PROJECT_NAME} + INTERFACE + FILE_SET HEADERS + BASE_DIRS "${PROJECT_SOURCE_DIR}/include" + FILES ${MAGIC_ENUM_HEADERS} + ) +else() + target_include_directories( + ${PROJECT_NAME} INTERFACE $ + $ + ) +endif() +if(MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS) + if(CMAKE_VERSION VERSION_LESS "3.24") + message(FATAL_ERROR "MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS requires CMake >= 3.24") + endif() + set_property(TARGET ${PROJECT_NAME} PROPERTY VERIFY_INTERFACE_HEADER_SETS ON) +endif() -if(MAGIC_ENUM_USE_MODULES) - add_library(${name}) +function(magic_enum_make_module_library name) + add_library(${name} STATIC) target_sources(${name} PUBLIC FILE_SET CXX_MODULES - BASE_DIRS "${MODULE}" - FILES "${MODULE}/magic_enum.cppm" - PUBLIC - FILE_SET HEADERS - BASE_DIRS "${INCLUDES}" - FILES - "${INCLUDES}/magic_enum.hpp" - "${INCLUDES}/magic_enum_all.hpp" - "${INCLUDES}/magic_enum_containers.hpp" - "${INCLUDES}/magic_enum_flags.hpp" - "${INCLUDES}/magic_enum_format.hpp" - "${INCLUDES}/magic_enum_fuse.hpp" - "${INCLUDES}/magic_enum_iostream.hpp" - "${INCLUDES}/magic_enum_switch.hpp" - "${INCLUDES}/magic_enum_utility.hpp" + BASE_DIRS "${MAGIC_ENUM_MODULE_DIR}" + FILES "${MAGIC_ENUM_MODULE_DIR}/magic_enum.cppm" ) - target_compile_features(${name} PUBLIC cxx_std_20) - target_compile_definitions(${name} - PRIVATE ${ARGN} - ) - set_target_properties(${name} PROPERTIES CXX_EXTENSIONS OFF) - if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30" AND - CMAKE_CXX_STANDARD IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD AND - CMAKE_CXX_MODULE_STD) - message("${name}: using import std") - target_compile_features(${name} PRIVATE cxx_std_23) - target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_STD_MODULE) + target_link_libraries(${name} PUBLIC ${PROJECT_NAME}) + target_compile_features(${name} PUBLIC cxx_std_${MAGIC_ENUM_MODULE_CXX_STANDARD}) + if(ARGN) + target_compile_definitions(${name} PRIVATE ${ARGN}) endif() - set(_scope PUBLIC) -else() - add_library(${name} INTERFACE) - set(_scope INTERFACE) -endif() - -add_library(${EXPORT_NAMESPACE}${name} ALIAS ${name}) -target_include_directories( - ${name} ${_scope} $ - $ -) + if(MAGIC_ENUM_MODULE_WITH_FMT) + if(NOT TARGET fmt::fmt) + find_package(fmt CONFIG REQUIRED) + endif() + target_link_libraries(${name} PUBLIC fmt::fmt) + target_compile_definitions(${name} PRIVATE MAGIC_ENUM_MODULE_WITH_FMT) + endif() + if(MAGIC_ENUM_MODULE_IMPORT_STD) + if(CMAKE_VERSION VERSION_LESS "3.30") + message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires CMake >= 3.30") + endif() + if(CMAKE_CXX_STANDARD LESS 23) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires CMAKE_CXX_STANDARD >= 23") + endif() + if(NOT CMAKE_CXX_STANDARD IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD) + message(FATAL_ERROR + "MAGIC_ENUM_MODULE_IMPORT_STD is not supported for C++${CMAKE_CXX_STANDARD} by the active toolchain. " + "Set CMAKE_EXPERIMENTAL_CXX_IMPORT_STD before project() and choose a supported standard." + ) + endif() + message(STATUS "${name}: using import std") + set_property(TARGET ${name} PROPERTY CXX_MODULE_STD ON) + target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_STD_MODULE) + elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30") + set_property(TARGET ${name} PROPERTY CXX_MODULE_STD OFF) + endif() endfunction() -magic_enum_make_library(${PROJECT_NAME}) +if(MAGIC_ENUM_USE_MODULES) + magic_enum_make_module_library(${PROJECT_NAME}_module) + add_library(${MAGIC_ENUM_EXPORT_NAMESPACE}${PROJECT_NAME}_module ALIAS ${PROJECT_NAME}_module) +endif() if(MAGIC_ENUM_OPT_BUILD_EXAMPLES) add_subdirectory(example) @@ -116,56 +164,56 @@ if(MAGIC_ENUM_OPT_BUILD_TESTS) endif() if(MAGIC_ENUM_OPT_INSTALL) - list(APPEND CMAKE_MODULE_PATH "${ADDITIONAL_MODULES_DIR}/GenPkgConfig") + list(APPEND CMAKE_MODULE_PATH "${MAGIC_ENUM_CMAKE_DIR}/GenPkgConfig") include(GenPkgConfig) include(CPackComponent) include(CMakePackageConfigHelpers) - if(NOT MAGIC_ENUM_USE_MODULES) - - install( - TARGETS "${PROJECT_NAME}" - EXPORT ${PROJECT_NAME} - # COMPONENT "${SDK_COMPONENT_NAME}" # component is not allowed for includes! - # Headers are installed separately! Includes only marks the headers for export - ) - - if(NOT DEFINED PROJECT_INCLUDE_HEADER_PATTERN) - set(PROJECT_INCLUDE_HEADER_PATTERN "*") - endif() - - install( - DIRECTORY ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME} - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - FILES_MATCHING - PATTERN "${PROJECT_INCLUDE_HEADER_PATTERN}" - ) - + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.23") + install( + TARGETS "${PROJECT_NAME}" + EXPORT ${PROJECT_NAME} + FILE_SET HEADERS + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + ) else() + install(TARGETS "${PROJECT_NAME}" EXPORT ${PROJECT_NAME}) + + if(NOT DEFINED PROJECT_INCLUDE_HEADER_PATTERN) + set(PROJECT_INCLUDE_HEADER_PATTERN "*") + endif() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME} + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING + PATTERN "${PROJECT_INCLUDE_HEADER_PATTERN}" + ) + endif() - install( - TARGETS "${PROJECT_NAME}" - EXPORT ${PROJECT_NAME} - FILE_SET HEADERS - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}" - FILE_SET CXX_MODULES - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}" - CXX_MODULES_BMI - DESTINATION "" - ) - + set(MAGIC_ENUM_EXPORT_TARGETS "${PROJECT_NAME}") + if(MAGIC_ENUM_USE_MODULES) + install( + TARGETS "${PROJECT_NAME}_module" + EXPORT ${PROJECT_NAME} + FILE_SET CXX_MODULES + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}" + CXX_MODULES_BMI + DESTINATION "" + ) + list(APPEND MAGIC_ENUM_EXPORT_TARGETS "${PROJECT_NAME}_module") endif() set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") - set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") + if(NOT MAGIC_ENUM_USE_MODULES) + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") + endif() set(CPACK_DEBIAN_PACKAGE_NAME "libmagicenum-dev") set(CPACK_RPM_PACKAGE_NAME "libmagicenum-devel") set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") set(CPACK_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}") set(CPACK_DEBIAN_PACKAGE_DEPENDS "") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}") - set(CPACK_PACKAGE_MAINTAINER "${CPACK_PACKAGE_VENDOR}") set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_RPM_COMPONENT_INSTALL ON) set(CPACK_NSIS_COMPONENT_INSTALL ON) @@ -174,57 +222,71 @@ if(MAGIC_ENUM_OPT_INSTALL) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") - set(CMAKE_CONFIG_FILE_BASENAME "${PROJECT_NAME}Config.cmake") - set(CMAKE_EXPORT_FILE_BASENAME "${PROJECT_NAME}Export.cmake") - set(CMAKE_CONFIG_VERSION_FILE_BASENAME "${PROJECT_NAME}ConfigVersion.cmake") - set(CMAKE_CONFIG_VERSION_FILE_NAME - "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CONFIG_VERSION_FILE_BASENAME}" - ) + set(MAGIC_ENUM_CMAKE_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}") + set(MAGIC_ENUM_TARGETS_FILE_BASENAME "${PROJECT_NAME}Targets.cmake") + set(MAGIC_ENUM_CMAKE_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake") + set(MAGIC_ENUM_CMAKE_CONFIG_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake") + + set(MAGIC_ENUM_MODULE_EXPORT_ARGS) + if(MAGIC_ENUM_USE_MODULES) + set(MAGIC_ENUM_MODULE_EXPORT_ARGS CXX_MODULES_DIRECTORY "cxx-modules") + endif() + + set(MAGIC_ENUM_FIND_DEPENDENCIES) + if(MAGIC_ENUM_MODULE_WITH_FMT) + string(APPEND MAGIC_ENUM_FIND_DEPENDENCIES + "if(NOT TARGET fmt::fmt)\n find_dependency(fmt CONFIG)\nendif()\n" + ) + endif() export( - TARGETS "${PROJECT_NAME}" - NAMESPACE "${EXPORT_NAMESPACE}" - FILE "${CMAKE_EXPORT_FILE_BASENAME}" - EXPORT_LINK_INTERFACE_LIBRARIES + TARGETS ${MAGIC_ENUM_EXPORT_TARGETS} + NAMESPACE "${MAGIC_ENUM_EXPORT_NAMESPACE}" + FILE "${CMAKE_CURRENT_BINARY_DIR}/${MAGIC_ENUM_TARGETS_FILE_BASENAME}" + ${MAGIC_ENUM_MODULE_EXPORT_ARGS} + ) + + configure_package_config_file( + "${MAGIC_ENUM_CMAKE_DIR}/${PROJECT_NAME}Config.cmake.in" + "${MAGIC_ENUM_CMAKE_CONFIG_FILE}" + INSTALL_DESTINATION "${MAGIC_ENUM_CMAKE_CONFIG_INSTALL_DIR}" ) install( EXPORT "${PROJECT_NAME}" - FILE "${CMAKE_CONFIG_FILE_BASENAME}" - NAMESPACE "${EXPORT_NAMESPACE}" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}" + FILE "${MAGIC_ENUM_TARGETS_FILE_BASENAME}" + NAMESPACE "${MAGIC_ENUM_EXPORT_NAMESPACE}" + ${MAGIC_ENUM_MODULE_EXPORT_ARGS} + DESTINATION "${MAGIC_ENUM_CMAKE_CONFIG_INSTALL_DIR}" ) + set(MAGIC_ENUM_ARCH_INDEPENDENT) + if(NOT MAGIC_ENUM_USE_MODULES) + set(MAGIC_ENUM_ARCH_INDEPENDENT ARCH_INDEPENDENT) + endif() + write_basic_package_version_file( - "${CMAKE_CONFIG_VERSION_FILE_NAME}" - # VERSION "100500.100500.100500" # any version of same bitness suits. CMake cannot compare to - # infinity, so use a large number we expect to be greater than any future version + "${MAGIC_ENUM_CMAKE_CONFIG_VERSION_FILE}" VERSION ${PROJECT_VERSION} - COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT + COMPATIBILITY AnyNewerVersion ${MAGIC_ENUM_ARCH_INDEPENDENT} ) - install(FILES "${CMAKE_CONFIG_VERSION_FILE_NAME}" - DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}" + install(FILES "${MAGIC_ENUM_CMAKE_CONFIG_FILE}" "${MAGIC_ENUM_CMAKE_CONFIG_VERSION_FILE}" + DESTINATION "${MAGIC_ENUM_CMAKE_CONFIG_INSTALL_DIR}" ) configure_pkg_config_file( "${PROJECT_NAME}" - NAME - "${PROJECT_NAME}" - VERSION - "${PROJECT_VERSION}" - DESCRIPTION - "${CPACK_PACKAGE_DESCRIPTION}" - URL - "${CPACK_PACKAGE_HOMEPAGE_URL}" - INSTALL_LIB_DIR - "${CMAKE_INSTALL_DATAROOTDIR}" - INSTALL_INCLUDE_DIR - "${CMAKE_INSTALL_INCLUDEDIR}" + NAME "${PROJECT_NAME}" + VERSION "${PROJECT_VERSION}" + DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}" + URL "${CPACK_PACKAGE_HOMEPAGE_URL}" + INSTALL_LIB_DIR "${CMAKE_INSTALL_DATAROOTDIR}" + INSTALL_INCLUDE_DIR "${CMAKE_INSTALL_INCLUDEDIR}" ) if(MAGIC_ENUM_OPT_INSTALL_PACKAGE_XML) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/package.xml - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME} + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/package.xml" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}" ) endif() diff --git a/Dockerfile b/Dockerfile index 2fe5a6ad2..b0c22ad57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,71 +1,9 @@ FROM gentoo/stage3:latest -RUN echo "Updating Gentoo index registry..." -RUN emerge-webrsync +RUN emerge-webrsync \ + && echo "dev-build/cmake ~amd64" > /etc/portage/package.accept_keywords/magic_enum \ + && emerge --quiet --verbose --jobs=2 dev-build/cmake virtual/pkgconfig - -RUN echo "Setting up package manager" -# Allow emerge newer versions of cmake. We need it because vcpkg wants it. -RUN echo "dev-build/cmake ~amd64" >> /etc/portage/package.accept_keywords/unmask -# # Get some packages as binaries and don't compile them. Actually, it allows to not compile only zip :( -# RUN rm -rf /etc/portage/binrepos.conf/* -# RUN echo -e "[binhost]\npriority = 9999\nsync-uri = https://gentoo.osuosl.org/experimental/amd64/binpkg/default/linux/17.1/x86-64/" >> /etc/portage/binrepos.conf/osuosl.conf -# RUN cat /etc/portage/binrepos.conf/* -# Arguments that going to be added automatically to `emerge` command. `man emerge` in help. Or read https://wiki.gentoo.org/wiki/Binary_package_guide/en#Installing_binary_packages . -# RUN echo 'EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --binpkg-respect-use=y --getbinpkg=y --verbose --verbose-conflicts "' >> /etc/portage/make.conf - -RUN echo "Installing dependencies..." - -RUN emerge --quiet --verbose --tree --verbose-conflicts --jobs=2 dev-vcs/git dev-build/cmake zip - -# RUN echo "Installing vcpkg..." -# RUN git clone https://github.com/Microsoft/vcpkg.git && \ -# ./vcpkg/bootstrap-vcpkg.sh -disableMetrics -# -# COPY ./vcpkg.json ./vcpkg.json -# -# RUN echo "Installing dependencies using vcpkg" -# RUN ./vcpkg/vcpkg install - -WORKDIR /backend - -RUN echo "Copy sources of the project" +WORKDIR /src COPY . . -RUN ls -lah - -# expected to be mounted at runtime as volume. -# COPY resources/ ./resources/ - - -RUN echo "Configuring project..." -RUN cmake -S ./ -B ./build --log-level DEBUG - - -# RUN echo "Building project..." -# RUN cmake --build ./build --parallel $(nproc) --verbose - -RUN echo "really installing" -RUN cmake --install ./build - -RUN echo "checking cmake-cmake" -RUN cmake -S. -B build2 -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=1 --log-level=DEBUG -RUN cmake --build build2 -j $(nproc) --verbose - -RUN echo "checking pkgconfig-cmake" -RUN cat "/usr/local/share/pkgconfig/magic_enum.pc" -RUN cmake -S. -B build3 -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG=1 --log-level=DEBUG -RUN cmake --build build3 -j $(nproc) --verbose - - - -# RUN echo "what's with linking?" -# RUN ls -lahR /usr/local/lib64 -# RUN lddtree /usr/local/lib64/libArby.so -# RUN lddtree /usr/local/bin/Arby - -# CMD ./build/Backend - - - - - +RUN bash ./test_installed_version.bash diff --git a/README.md b/README.md index 3f86a3a8b..0e41bd528 100644 --- a/README.md +++ b/README.md @@ -352,11 +352,24 @@ If you like this project, please consider donating to one of the funds that help target_link_libraries(your_executable magic_enum::magic_enum) ``` -* **C++20 modules** are supported as an alternative to the header-only mode, requires CMake 3.28+. +* **CMake targets**: + - `magic_enum::magic_enum` is always provided for the header-only library; + - `magic_enum::magic_enum_module` is provided for the C++20 module library when `MAGIC_ENUM_USE_MODULES=ON`. + + Use the header-only target with `#include `, or substitute the module target and use `import magic_enum;`. + + Maintainers using CMake 3.24+ can enable `MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS=ON` to verify that every public header can be included independently. The option is enabled by default when `MAGIC_ENUM_OPT_BUILD_TESTS` is enabled on supported CMake versions. + +* **C++20 modules** require CMake 3.28+ and are explicitly enabled when building magic_enum. Configure with CMake: + ```sh + cmake -S . -B build -G Ninja -DMAGIC_ENUM_USE_MODULES=ON + ``` + + Link the module target instead of the header-only target: ```cmake - cmake -DMAGIC_ENUM_USE_MODULES=ON -G Ninja ... + target_link_libraries(your_executable PRIVATE magic_enum::magic_enum_module) ``` Then use `import` instead of `#include`: @@ -368,8 +381,12 @@ If you like this project, please consider donating to one of the funds that help ``` Caveats: - - Do not mix `#include ` and `import magic_enum;` within the same link unit; this is an ODR violation. - - `import std;` is opt-in and experimental (requires CMake 3.30+). Enable it with `-DCMAKE_CXX_STANDARD=23 -DCMAKE_CXX_MODULE_STD=ON` and set `CMAKE_EXPERIMENTAL_CXX_IMPORT_STD` to the UUID documented by your CMake version before `project()`; for CMake 4.3, the UUID is `451f2fe2-a8a2-47c3-bc32-94786d8fc91b`. + - Do not mix `#include ` and `import magic_enum;` within the same program; this is an ODR violation. + - An installed module package must be consumed with a compatible compiler, standard library, C++ language standard, and compiler-extension mode. The module target propagates the language standard used to build it, but CMake cannot validate every toolchain option that affects module compatibility. + - An installed package provides `magic_enum::magic_enum_module` only when magic_enum was configured with `MAGIC_ENUM_USE_MODULES=ON` before installation. + - Module configuration macros are fixed when `magic_enum::magic_enum_module` is built; they are not applied independently by each consumer translation unit. + - `import std;` is opt-in and experimental (requires CMake 3.30+). Set `CMAKE_EXPERIMENTAL_CXX_IMPORT_STD` to the UUID documented by the selected CMake version before `project()`, choose a supported `CMAKE_CXX_STANDARD`, and enable `MAGIC_ENUM_MODULE_IMPORT_STD=ON`. The experimental UUID is version-specific and is intentionally not selected by magic_enum. If a consumer source also uses `import std;`, enable it on that consumer target with `set_property(TARGET your_executable PROPERTY CXX_MODULE_STD ON)`; this target property is not propagated by linking `magic_enum::magic_enum_module`. + - `{fmt}` integration is explicit for the compiled module. Enable it with `MAGIC_ENUM_MODULE_WITH_FMT=ON`; this requires the `fmt` CMake package and makes `fmt::fmt` a public dependency of `magic_enum::magic_enum_module`. The installed magic_enum package will resolve the dependency with `find_dependency(fmt CONFIG)`. ## Compiler compatibility diff --git a/cmake/magic_enumConfig.cmake.in b/cmake/magic_enumConfig.cmake.in new file mode 100644 index 000000000..ff5f9cfef --- /dev/null +++ b/cmake/magic_enumConfig.cmake.in @@ -0,0 +1,11 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +@MAGIC_ENUM_FIND_DEPENDENCIES@ + +if(NOT TARGET magic_enum::magic_enum) + include("${CMAKE_CURRENT_LIST_DIR}/@MAGIC_ENUM_TARGETS_FILE_BASENAME@") +endif() + +check_required_components(magic_enum) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index b91c6462a..ea8053e4d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,41 +1,48 @@ include(CheckCXXCompilerFlag) -if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) - set(OPTIONS -Wall -Wextra -Wshadow -pedantic-errors -Werror) -elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - set(OPTIONS /W4 /WX) - check_cxx_compiler_flag(/permissive- HAS_PERMISSIVE_FLAG) - if(HAS_PERMISSIVE_FLAG) - set(OPTIONS ${OPTIONS} /permissive-) - endif() +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(MAGIC_ENUM_EXAMPLE_COMPILE_OPTIONS -Wall -Wextra -Wshadow -pedantic-errors -Werror) +elseif(MSVC) + set(MAGIC_ENUM_EXAMPLE_COMPILE_OPTIONS /W4 /WX) + check_cxx_compiler_flag(/permissive- MAGIC_ENUM_EXAMPLE_HAS_PERMISSIVE_FLAG) + if(MAGIC_ENUM_EXAMPLE_HAS_PERMISSIVE_FLAG) + list(APPEND MAGIC_ENUM_EXAMPLE_COMPILE_OPTIONS /permissive-) + endif() endif() -function(make_example target) - add_executable(${target} ${target}.cpp) - set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) - target_compile_features(${target} PRIVATE cxx_std_17) - target_compile_options(${target} PRIVATE ${OPTIONS}) - target_link_libraries(${target} PRIVATE magic_enum::magic_enum) +function(magic_enum_add_example target) + add_executable(${target} ${target}.cpp) + set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) + target_compile_features(${target} PRIVATE cxx_std_17) + target_compile_options(${target} PRIVATE ${MAGIC_ENUM_EXAMPLE_COMPILE_OPTIONS}) + target_link_libraries(${target} PRIVATE magic_enum::magic_enum) endfunction() -make_example(example) -make_example(enum_flag_example) -make_example(example_containers_array) -make_example(example_containers_bitset) -make_example(example_containers_set) -make_example(example_containers_set_lookup) -make_example(example_custom_name) -make_example(example_switch) +set(MAGIC_ENUM_EXAMPLES + example + enum_flag_example + example_containers_array + example_containers_bitset + example_containers_set + example_containers_set_lookup + example_custom_name + example_switch +) if(MAGIC_ENUM_OPT_ENABLE_NONASCII) - make_example(example_nonascii_name) + list(APPEND MAGIC_ENUM_EXAMPLES example_nonascii_name) endif() + +foreach(MAGIC_ENUM_EXAMPLE IN LISTS MAGIC_ENUM_EXAMPLES) + magic_enum_add_example(${MAGIC_ENUM_EXAMPLE}) +endforeach() + if(MAGIC_ENUM_USE_MODULES) - make_example(example_module_usage) - target_compile_features(example_module_usage PRIVATE cxx_std_20) - set_target_properties(example_module_usage PROPERTIES CXX_SCAN_FOR_MODULES ON) - if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30" AND - CMAKE_CXX_STANDARD IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD AND - CMAKE_CXX_MODULE_STD) - target_compile_definitions(example_module_usage PRIVATE MAGIC_ENUM_TEST_IMPORT_STD) - endif() + magic_enum_add_example(example_module_usage) + target_link_libraries(example_module_usage PRIVATE magic_enum::magic_enum_module) + target_compile_features(example_module_usage PRIVATE cxx_std_20) + set_target_properties(example_module_usage PROPERTIES CXX_SCAN_FOR_MODULES ON) + if(MAGIC_ENUM_MODULE_IMPORT_STD) + set_property(TARGET example_module_usage PROPERTY CXX_MODULE_STD ON) + target_compile_definitions(example_module_usage PRIVATE MAGIC_ENUM_TEST_IMPORT_STD) + endif() endif() diff --git a/module/magic_enum.cppm b/module/magic_enum.cppm index 32ff5f76a..e098e234d 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -1,31 +1,35 @@ module; #include -#if __has_include() +#ifdef MAGIC_ENUM_MODULE_WITH_FMT # include #endif #ifndef MAGIC_ENUM_USE_STD_MODULE # include +# include # if __has_include() # include # endif +# include +# if __has_include() +# include +# endif +# if defined(_MSC_VER) && !defined(__clang__) +# include +# endif # include -# ifndef MAGIC_ENUM_USING_ALIAS_STRING -# include -# endif # include -# include # include # include -# include # include #else // MAGIC_ENUM_USE_STD_MODULE -// Note: MAGIC_ENUM_CONFIG_FILE may include STL headers, it is required to attach them to global module fragement otherwise there can be conflicting declarations with std module. +// MAGIC_ENUM_CONFIG_FILE may include STL headers. Keep them in the global module +// fragment to avoid conflicting declarations with import std. # ifdef MAGIC_ENUM_CONFIG_FILE # include MAGIC_ENUM_CONFIG_FILE # endif // MAGIC_ENUM_CONFIG_FILE @@ -61,6 +65,31 @@ extern "C++" { # include # include +# if defined(__clang__) +# pragma clang diagnostic pop +# elif defined(_MSC_VER) +# pragma warning(pop) +# endif +} +#else +extern "C++" { +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Winclude-angled-in-module-purview" +# elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 5244) +# endif + +// Keep standard-library specializations attached to the named module. Exporting +// the primary template does not make specializations from the global module +// fragment reachable to importers. +# ifndef MAGIC_ENUM_USING_ALIAS_STRING +# include +# endif +# include +# include + # if defined(__clang__) # pragma clang diagnostic pop # elif defined(_MSC_VER) @@ -71,118 +100,130 @@ extern "C++" { export namespace magic_enum { namespace customize { - using customize::adl_info; - using customize::customize_t; - using customize::enum_name; - using customize::enum_range; - using customize::default_tag; - using customize::invalid_tag; + using customize::adl_info_holder; + using customize::adl_info; + using customize::customize_t; + using customize::enum_name; + using customize::enum_range; + using customize::enum_type_name; + using customize::default_tag; + using customize::invalid_tag; } namespace istream_operators { - using istream_operators::operator>>; + using istream_operators::operator>>; } namespace ostream_operators { - using ostream_operators::operator<<; + using ostream_operators::operator<<; } namespace iostream_operators { - using iostream_operators::operator<<; - using iostream_operators::operator>>; + using iostream_operators::operator<<; + using iostream_operators::operator>>; } namespace bitwise_operators { - using bitwise_operators::operator~; - using bitwise_operators::operator|; - using bitwise_operators::operator&; - using bitwise_operators::operator^; - using bitwise_operators::operator|=; - using bitwise_operators::operator&=; - using bitwise_operators::operator^=; + using bitwise_operators::operator~; + using bitwise_operators::operator|; + using bitwise_operators::operator&; + using bitwise_operators::operator^; + using bitwise_operators::operator|=; + using bitwise_operators::operator&=; + using bitwise_operators::operator^=; } #ifndef MAGIC_ENUM_USING_ALIAS_STRING namespace containers { - using containers::array; - using containers::bitset; - using containers::comparator_indexing; - using containers::default_indexing; - using containers::get; - using containers::make_array; - using containers::name_greater; - using containers::name_greater_case_insensitive; - using containers::name_less; - using containers::name_less_case_insensitive; - using containers::raw_access; - using containers::set; - using containers::to_array; + using containers::array; + using containers::bitset; + using containers::comparator_indexing; + using containers::default_indexing; + using containers::get; + using containers::make_array; + using containers::name_greater; + using containers::name_greater_case_insensitive; + using containers::name_less; + using containers::name_less_case_insensitive; + using containers::raw_access; + using containers::set; + using containers::to_array; } #endif - using magic_enum::is_magic_enum_supported; - using magic_enum::Enum; - using magic_enum::enum_name; - using magic_enum::enum_cast; - using magic_enum::enum_value; - using magic_enum::enum_values; - using magic_enum::enum_count; - using magic_enum::enum_integer; - using magic_enum::enum_underlying; - using magic_enum::enum_names; - using magic_enum::enum_entries; - using magic_enum::enum_fuse; - using magic_enum::enum_switch; - using magic_enum::enum_for_each; - using magic_enum::enum_contains; - using magic_enum::enum_index; - using magic_enum::enum_prev_value; - using magic_enum::enum_prev_value_circular; - using magic_enum::enum_next_value; - using magic_enum::enum_next_value_circular; - using magic_enum::enum_constant; - using magic_enum::enum_flags_name; - using magic_enum::enum_flags_contains; - using magic_enum::enum_flags_test; - using magic_enum::enum_flags_test_any; - using magic_enum::enum_flags_cast; - using magic_enum::enum_type_name; - using magic_enum::enum_reflected; - using magic_enum::as_flags; - using magic_enum::as_common; - using magic_enum::case_insensitive; - using magic_enum::is_unscoped_enum; - using magic_enum::is_unscoped_enum_v; - using magic_enum::is_scoped_enum; - using magic_enum::is_scoped_enum_v; - using magic_enum::is_flags_enum; - using magic_enum::is_flags_v; - using magic_enum::underlying_type; - using magic_enum::underlying_type_t; +using magic_enum::optional; +using magic_enum::string_view; +using magic_enum::string; +using magic_enum::char_type; +using magic_enum::is_magic_enum_supported; +using magic_enum::Enum; +using magic_enum::enum_name; +using magic_enum::enum_cast; +using magic_enum::enum_value; +using magic_enum::enum_values; +using magic_enum::enum_count; +using magic_enum::enum_integer; +using magic_enum::enum_underlying; +using magic_enum::enum_names; +using magic_enum::enum_entries; +using magic_enum::enum_fuse; +using magic_enum::enum_switch; +using magic_enum::enum_for_each; +using magic_enum::enum_contains; +using magic_enum::enum_index; +using magic_enum::enum_prev_value; +using magic_enum::enum_prev_value_circular; +using magic_enum::enum_next_value; +using magic_enum::enum_next_value_circular; +using magic_enum::enum_constant; +using magic_enum::enum_flags_name; +using magic_enum::enum_flags_contains; +using magic_enum::enum_flags_test; +using magic_enum::enum_flags_test_any; +using magic_enum::enum_flags_cast; +using magic_enum::enum_type_name; +using magic_enum::enum_reflected; +using magic_enum::as_flags; +using magic_enum::as_common; +using magic_enum::case_insensitive; +using magic_enum::is_unscoped_enum; +using magic_enum::is_unscoped_enum_v; +using magic_enum::is_scoped_enum; +using magic_enum::is_scoped_enum_v; +using magic_enum::is_flags_enum; +using magic_enum::is_flags_v; +using magic_enum::underlying_type; +using magic_enum::underlying_type_t; #ifdef MAGIC_ENUM_EXPORT_DETAIL namespace detail { - MAGIC_ENUM_EXPORT_DETAIL + MAGIC_ENUM_EXPORT_DETAIL } #endif } #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L export namespace std { - using std::partial_ordering; - using std::strong_ordering; - using std::weak_ordering; + using std::partial_ordering; + using std::strong_ordering; + using std::weak_ordering; +} +#endif + +#ifndef MAGIC_ENUM_USING_ALIAS_STRING +export namespace std { + using std::hash; } #endif #if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L export namespace std { - using std::formatter; + using std::formatter; } #endif -#if defined(FMT_VERSION) +#ifdef MAGIC_ENUM_MODULE_WITH_FMT export namespace fmt { - using fmt::formatter; + using fmt::formatter; } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa4011950..cf58231e8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,170 +1,219 @@ include(CheckCXXCompilerFlag) -if(${MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION}) - find_package(magic_enum REQUIRED magic_enum) +if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION AND NOT TARGET magic_enum::magic_enum) + find_package(magic_enum CONFIG REQUIRED) endif() -if(${MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG}) - find_package(PkgConfig) - pkg_check_modules(magic_enum magic_enum) - if(NOT magic_enum_FOUND) - message( - WARNING - "magic_enum via pkgconfig is not found. \ -Next code will try check possible places for some platforms, \ -but there's no guarantee. \ -If you know where the magic_enum pkgconfig files (.pc) are, \ -then specify yourself variable \$\{CMAKE_PREFIX_PATH\} \ -with folder like /a/path/to/magic_enum (for POSIX-like pathes), \ -where in the folder exists share/pkgconfig/magic_enum.pc ." - ) - if(UNIX AND EXISTS "/usr/local/share/pkgconfig/magic_enum.pc") - message(DEBUG "\$\{CMAKE_PREFIX_PATH\} : ${CMAKE_PREFIX_PATH} ") - set(CMAKE_PREFIX_PATH "/usr/local") - message(DEBUG "\$\{CMAKE_PREFIX_PATH\} : ${CMAKE_PREFIX_PATH} ") - pkg_check_modules(magic_enum magic_enum) - endif() - # code place for future workarounds for other platforms... - if(NOT magic_enum_FOUND) - message(FATAL_ERROR "Could not find magic_enum's config. Read a warning above.") - endif() - endif() - message(DEBUG "magic_enum_FOUND : ${magic_enum_FOUND}") - message(DEBUG "magic_enum_LIBRARIES: ${magic_enum_LIBRARIES}") - message(DEBUG "magic_enum_LINK_LIBRARIES: ${magic_enum_LINK_LIBRARIES}") - message(DEBUG "magic_enum_LIBRARY_DIRS: ${magic_enum_LIBRARY_DIRS}") - message(DEBUG "magic_enum_INCLUDE_DIRS: ${magic_enum_INCLUDE_DIRS}") - message(DEBUG "magic_enum_INCLUDE_DIR: ${magic_enum_INCLUDE_DIR}") - message(DEBUG "magic_enum_LDFLAGS: ${magic_enum_LDFLAGS}") - message(DEBUG "magic_enum_LDFLAGS_OTHER: ${magic_enum_LDFLAGS_OTHER}") - message(DEBUG "magic_enum_CFLAGS: ${magic_enum_CFLAGS}") - message(DEBUG "magic_enum_CFLAGS_OTHER: ${magic_enum_CFLAGS_OTHER}") - message(DEBUG "magic_enum_INCLUDEDIR: ${magic_enum_INCLUDEDIR}") - message(DEBUG "magic_enum_LIBDIR: ${magic_enum_LIBDIR}") - message(DEBUG "magic_enum_PREFIX: ${magic_enum_PREFIX}") +if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG) + find_package(PkgConfig REQUIRED) + pkg_check_modules(magic_enum REQUIRED IMPORTED_TARGET magic_enum) endif() -if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - set(OPTIONS /W4 /WX) - check_cxx_compiler_flag(/permissive- HAS_PERMISSIVE_FLAG) - if(HAS_PERMISSIVE_FLAG) - set(OPTIONS ${OPTIONS} /permissive-) +set(MAGIC_ENUM_TEST_STANDARDS 20 23 26) + +function(magic_enum_get_standard_flag standard output) + if(MSVC AND standard GREATER_EQUAL 26) + set(flag c++latest) + else() + set(flag c++${standard}) endif() + set(${output} ${flag} PARENT_SCOPE) +endfunction() - check_cxx_compiler_flag(/std:c++20 HAS_CPP20_FLAG) - check_cxx_compiler_flag(/std:c++23 HAS_CPP23_FLAG) - check_cxx_compiler_flag(/std:c++latest HAS_CPPLATEST_FLAG) +if(MSVC) + set(MAGIC_ENUM_TEST_COMPILE_OPTIONS /W4 /WX) + check_cxx_compiler_flag(/permissive- MAGIC_ENUM_HAS_PERMISSIVE_FLAG) + if(MAGIC_ENUM_HAS_PERMISSIVE_FLAG) + list(APPEND MAGIC_ENUM_TEST_COMPILE_OPTIONS /permissive-) + endif() + set(MAGIC_ENUM_TEST_FLAG_PREFIX /std:) elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") set(CMAKE_VERBOSE_MAKEFILE ON) - set(OPTIONS -Wall -Wextra -Wshadow -pedantic-errors -Werror) + set(MAGIC_ENUM_TEST_COMPILE_OPTIONS -Wall -Wextra -Wshadow -pedantic-errors -Werror) + set(MAGIC_ENUM_TEST_FLAG_PREFIX -std=) # Clang 22+ treats __COUNTER__ as C2y extension; but doctest uses it if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "22.0") - set(OPTIONS ${OPTIONS} -Wno-c2y-extensions) + list(APPEND MAGIC_ENUM_TEST_COMPILE_OPTIONS -Wno-c2y-extensions) endif() +endif() - check_cxx_compiler_flag(-std=c++20 HAS_CPP20_FLAG) - check_cxx_compiler_flag(-std=c++23 HAS_CPP23_FLAG) - - # Clang 17 with libstdc++ 14 has a known bug in C++23 mode (tuple/get incompatibility) - # Skip C++23 tests for Clang 17.x to avoid compilation errors - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "17.0" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18.0") - set(HAS_CPP23_FLAG FALSE) - message(STATUS "Skipping C++23 tests for Clang 17 due to libstdc++ 14 incompatibility") - endif() +foreach(MAGIC_ENUM_TEST_STANDARD IN LISTS MAGIC_ENUM_TEST_STANDARDS) + magic_enum_get_standard_flag(${MAGIC_ENUM_TEST_STANDARD} MAGIC_ENUM_TEST_STANDARD_FLAG) + check_cxx_compiler_flag( + "${MAGIC_ENUM_TEST_FLAG_PREFIX}${MAGIC_ENUM_TEST_STANDARD_FLAG}" + MAGIC_ENUM_HAS_CPP${MAGIC_ENUM_TEST_STANDARD}_FLAG + ) +endforeach() + +# Clang 17 with libstdc++ 14 has a known bug in C++23 mode (tuple/get incompatibility). +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND + CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "17.0" AND + CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18.0") + set(MAGIC_ENUM_HAS_CPP23_FLAG FALSE) + message(STATUS "Skipping C++23 tests for Clang 17 due to libstdc++ 14 incompatibility") endif() if(MAGIC_ENUM_USE_MODULES AND NOT MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION) - -magic_enum_make_library(magic_enum_module_test-cpp${CMAKE_CXX_STANDARD} - "MAGIC_ENUM_CONFIG_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/magic_enum_test_config.hpp\"" -) -magic_enum_make_library(magic_enum_module_test_aliases-cpp${CMAKE_CXX_STANDARD} - "MAGIC_ENUM_CONFIG_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/aliases.hpp\"" -) -magic_enum_make_library(magic_enum_module_test_wchar_t-cpp${CMAKE_CXX_STANDARD} - "MAGIC_ENUM_CONFIG_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/magic_enum_wchar_t_test_config.hpp\"" -) - + magic_enum_make_module_library(magic_enum_module_test-cpp${MAGIC_ENUM_MODULE_CXX_STANDARD} + "MAGIC_ENUM_CONFIG_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/magic_enum_test_config.hpp\"" + ) + magic_enum_make_module_library(magic_enum_module_test_aliases-cpp${MAGIC_ENUM_MODULE_CXX_STANDARD} + "MAGIC_ENUM_CONFIG_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/aliases.hpp\"" + ) + magic_enum_make_module_library(magic_enum_module_test_wchar_t-cpp${MAGIC_ENUM_MODULE_CXX_STANDARD} + "MAGIC_ENUM_CONFIG_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/magic_enum_wchar_t_test_config.hpp\"" + ) endif() -function(make_test src target std) +function(magic_enum_make_test src target standard_flag) add_executable(${target} ${src}) - target_compile_options(${target} PRIVATE ${OPTIONS}) + target_compile_options(${target} PRIVATE ${MAGIC_ENUM_TEST_COMPILE_OPTIONS}) target_compile_definitions(${target} PRIVATE DOCTEST_CONFIG_USE_STD_HEADERS) - target_include_directories(${target} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty) - if(${MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG}) - target_include_directories(${target} PRIVATE ${magic_enum_INCLUDE_DIRS}) - elseif(TARGET magic_enum_module_${target}) - target_link_libraries(${target} PRIVATE magic_enum_module_${target}) + target_include_directories(${target} SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty) + if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG) + target_link_libraries(${target} PRIVATE PkgConfig::magic_enum) else() target_link_libraries(${target} PRIVATE magic_enum::magic_enum) endif() set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) - if(std) - if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_compile_options(${target} PRIVATE /std:${std}) - else() - target_compile_options(${target} PRIVATE -std=${std}) - endif() + if(MSVC) + target_compile_options(${target} PRIVATE /std:${standard_flag}) + else() + target_compile_options(${target} PRIVATE -std=${standard_flag}) endif() add_test(NAME ${target} COMMAND ${target}) endfunction() -function(make_module_test target std) - make_test(test_module.cpp ${target} ${std}) +function(magic_enum_make_module_test src target module_target) + add_executable(${target} ${src}) + target_compile_options(${target} PRIVATE ${MAGIC_ENUM_TEST_COMPILE_OPTIONS}) + if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION) + target_link_libraries(${target} PRIVATE magic_enum::magic_enum_module) + else() + target_link_libraries(${target} PRIVATE ${module_target}) + endif() set_target_properties(${target} PROPERTIES CXX_SCAN_FOR_MODULES ON) + add_test(NAME ${target} COMMAND ${target}) endfunction() -if(NOT MAGIC_ENUM_USE_MODULES) +set(MAGIC_ENUM_TEST_SOURCES test test_flags test_aliases test_containers test_wchar_t) -make_test(test.cpp test-cpp17 c++17) -make_test(test_flags.cpp test_flags-cpp17 c++17) -make_test(test_aliases.cpp test_aliases-cpp17 c++17) -make_test(test_containers.cpp test_containers-cpp17 c++17) -make_test(test_wchar_t.cpp test_wchar_t-cpp17 c++17) +function(magic_enum_add_tests suffix standard_flag) + foreach(test_name IN LISTS MAGIC_ENUM_TEST_SOURCES) + magic_enum_make_test(${test_name}.cpp ${test_name}-${suffix} ${standard_flag}) + endforeach() + if(MAGIC_ENUM_OPT_ENABLE_NONASCII) + magic_enum_make_test(test_nonascii.cpp test_nonascii-${suffix} ${standard_flag}) + endif() +endfunction() -if(MAGIC_ENUM_OPT_ENABLE_NONASCII) - make_test(test_nonascii.cpp test_nonascii-cpp17 c++17) -endif() +function(magic_enum_add_module_tests suffix) + magic_enum_make_module_test( + test_module.cpp + test_module-${suffix} + magic_enum_module_test-${suffix} + ) + + # Installed packages expose one module with the configuration fixed at build + # time. Alias and wchar coverage use dedicated local module targets instead. + if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION) + return() + endif() -endif() + foreach(test_variant IN ITEMS _aliases _wchar_t) + magic_enum_make_module_test( + test_module${test_variant}.cpp + test_module${test_variant}-${suffix} + magic_enum_module_test${test_variant}-${suffix} + ) + endforeach() +endfunction() -if(HAS_CPP20_FLAG AND (NOT MAGIC_ENUM_USE_MODULES OR (CMAKE_CXX_STANDARD EQUAL 20))) - make_test(test.cpp test-cpp20 c++20) - make_test(test_flags.cpp test_flags-cpp20 c++20) - make_test(test_aliases.cpp test_aliases-cpp20 c++20) - make_test(test_containers.cpp test_containers-cpp20 c++20) - make_test(test_wchar_t.cpp test_wchar_t-cpp20 c++20) - if(MAGIC_ENUM_OPT_ENABLE_NONASCII) - make_test(test_nonascii.cpp test_nonascii-cpp20 c++20) +magic_enum_add_tests(cpp17 c++17) + +foreach(MAGIC_ENUM_TEST_STANDARD IN LISTS MAGIC_ENUM_TEST_STANDARDS) + set(MAGIC_ENUM_HAS_STANDARD_FLAG MAGIC_ENUM_HAS_CPP${MAGIC_ENUM_TEST_STANDARD}_FLAG) + if(NOT ${MAGIC_ENUM_HAS_STANDARD_FLAG}) + continue() endif() - if(MAGIC_ENUM_USE_MODULES) - make_module_test(test_module-cpp20 c++20) + if(MAGIC_ENUM_USE_MODULES AND + NOT MAGIC_ENUM_MODULE_CXX_STANDARD EQUAL MAGIC_ENUM_TEST_STANDARD) + continue() endif() -endif() -if(HAS_CPP23_FLAG AND (NOT MAGIC_ENUM_USE_MODULES OR (CMAKE_CXX_STANDARD EQUAL 23))) - make_test(test.cpp test-cpp23 c++23) - make_test(test_flags.cpp test_flags-cpp23 c++23) - make_test(test_aliases.cpp test_aliases-cpp23 c++23) - make_test(test_containers.cpp test_containers-cpp23 c++23) - make_test(test_wchar_t.cpp test_wchar_t-cpp23 c++23) - if(MAGIC_ENUM_OPT_ENABLE_NONASCII) - make_test(test_nonascii.cpp test_nonascii-cpp23 c++23) - endif() + magic_enum_get_standard_flag(${MAGIC_ENUM_TEST_STANDARD} MAGIC_ENUM_TEST_STANDARD_FLAG) + set(MAGIC_ENUM_TEST_SUFFIX cpp${MAGIC_ENUM_TEST_STANDARD}) + magic_enum_add_tests(${MAGIC_ENUM_TEST_SUFFIX} ${MAGIC_ENUM_TEST_STANDARD_FLAG}) if(MAGIC_ENUM_USE_MODULES) - make_module_test(test_module-cpp23 c++23) + magic_enum_add_module_tests(${MAGIC_ENUM_TEST_SUFFIX}) endif() -endif() - -if(HAS_CPPLATEST_FLAG) - make_test(test.cpp test-cpplatest c++latest) - make_test(test_flags.cpp test_flags-cpplatest c++latest) - make_test(test_aliases.cpp test_aliases-cpplatest c++latest) - make_test(test_containers.cpp test_containers-cpplatest c++latest) - make_test(test_wchar_t.cpp test_wchar_t-cpplatest c++latest) - if(MAGIC_ENUM_OPT_ENABLE_NONASCII) - make_test(test_nonascii.cpp test_nonascii-cpplatest c++latest) +endforeach() + +if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND + NOT MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION AND + NOT CMAKE_SKIP_INSTALL_RULES AND + CMAKE_GENERATOR STREQUAL "Ninja") + set(MAGIC_ENUM_STAGED_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/stagedir") + set(MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/installed-module-consumer") + set(MAGIC_ENUM_INSTALLED_CONSUMER_PREFIX_PATH "${MAGIC_ENUM_STAGED_INSTALL_DIR}") + foreach(MAGIC_ENUM_PREFIX IN LISTS CMAKE_PREFIX_PATH) + string(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_PREFIX_PATH "\\;${MAGIC_ENUM_PREFIX}") + endforeach() + set(MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS + "-DCMAKE_BUILD_TYPE=$" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}" + "-DCMAKE_PREFIX_PATH=${MAGIC_ENUM_INSTALLED_CONSUMER_PREFIX_PATH}" + "-DMAGIC_ENUM_EXPECT_IMPORT_STD=${MAGIC_ENUM_MODULE_IMPORT_STD}" + "-DMAGIC_ENUM_EXPECT_FMT=${MAGIC_ENUM_MODULE_WITH_FMT}" + ) + if(MSVC) + find_program(MAGIC_ENUM_RC_COMPILER rc REQUIRED) + find_program(MAGIC_ENUM_MT mt REQUIRED) + list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS + "-DCMAKE_RC_COMPILER=${MAGIC_ENUM_RC_COMPILER}" + "-DCMAKE_MT=${MAGIC_ENUM_MT}" + ) endif() + if(MAGIC_ENUM_MODULE_IMPORT_STD) + list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS + "-DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=${CMAKE_EXPERIMENTAL_CXX_IMPORT_STD}" + ) + if(CMAKE_CXX_STDLIB_MODULES_JSON) + list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS + "-DCMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON}" + ) + endif() + endif() + + add_test( + NAME install-module-to-stagedir + COMMAND + ${CMAKE_COMMAND} + "-DBUILD_DIR=${CMAKE_BINARY_DIR}" + "-DSTAGE_DIR=${MAGIC_ENUM_STAGED_INSTALL_DIR}" + "-DCONSUMER_BUILD_DIR=${MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_DIR}" + "-DCONFIG=$" + -P "${CMAKE_CURRENT_SOURCE_DIR}/stage_package.cmake" + ) + set_tests_properties(install-module-to-stagedir PROPERTIES FIXTURES_SETUP magic_enum_installed_module) + + add_test( + NAME installed-module-consumer + COMMAND + ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/installed_module" + "${MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_DIR}" + --build-generator "${CMAKE_GENERATOR}" + --build-makeprogram "${CMAKE_MAKE_PROGRAM}" + --build-options ${MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS} + --test-command + ${CMAKE_CTEST_COMMAND} --output-on-failure --no-tests=error -C $ + ) + set_tests_properties(installed-module-consumer PROPERTIES FIXTURES_REQUIRED magic_enum_installed_module) endif() diff --git a/test/installed_module/CMakeLists.txt b/test/installed_module/CMakeLists.txt new file mode 100644 index 000000000..e07454760 --- /dev/null +++ b/test/installed_module/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.28) + +project(magic_enum_installed_module_test LANGUAGES CXX) + +find_package(magic_enum CONFIG REQUIRED) + +add_executable(magic_enum_installed_module_test main.cpp) +target_link_libraries(magic_enum_installed_module_test PRIVATE magic_enum::magic_enum_module) +set_property(TARGET magic_enum_installed_module_test PROPERTY CXX_SCAN_FOR_MODULES ON) + +if(MAGIC_ENUM_EXPECT_IMPORT_STD) + set_property(TARGET magic_enum_installed_module_test PROPERTY CXX_MODULE_STD ON) + target_compile_definitions( + magic_enum_installed_module_test PRIVATE MAGIC_ENUM_TEST_IMPORT_STD + ) +endif() + +if(MAGIC_ENUM_EXPECT_FMT) + target_compile_definitions(magic_enum_installed_module_test PRIVATE MAGIC_ENUM_TEST_FMT) +endif() + +enable_testing() +add_test(NAME magic_enum_installed_module_test COMMAND magic_enum_installed_module_test) diff --git a/test/installed_module/main.cpp b/test/installed_module/main.cpp new file mode 100644 index 000000000..34f65627a --- /dev/null +++ b/test/installed_module/main.cpp @@ -0,0 +1,42 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT + +#include + +#ifdef MAGIC_ENUM_TEST_IMPORT_STD +import std; +#elif defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +# include +#endif + +#ifdef MAGIC_ENUM_TEST_FMT +# include +#endif + +import magic_enum; + +enum class Color { RED, GREEN, BLUE }; + +static_assert(magic_enum::enum_count() == 3); +static_assert(magic_enum::enum_integer(Color::GREEN) == 1); +static_assert(magic_enum::string_view{"BLUE"}.size() == 4); +static_assert(magic_enum::optional{7}.value() == 7); + +constexpr magic_enum::containers::bitset color_bits{magic_enum::containers::raw_access, 5ULL}; + +int main() { + if (std::hash>{}(color_bits) != + std::hash{}(5ULL)) { + return 3; + } +#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L + if (std::format("{}", Color::GREEN) != "GREEN") { + return 1; + } +#endif +#ifdef MAGIC_ENUM_TEST_FMT + if (fmt::format("{}", Color::BLUE) != "BLUE") { + return 2; + } +#endif +} diff --git a/test/magic_enum_wchar_t_test_config.hpp b/test/magic_enum_wchar_t_test_config.hpp index f0ac09984..7e45b9875 100644 --- a/test/magic_enum_wchar_t_test_config.hpp +++ b/test/magic_enum_wchar_t_test_config.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + // Configuration header for the wchar_t module test build. // Redefines string types to their wide-character equivalents. // Used via MAGIC_ENUM_CONFIG_FILE to avoid CMake semicolon-escaping in compile definitions. diff --git a/test/stage_package.cmake b/test/stage_package.cmake new file mode 100644 index 000000000..3f3ec46ec --- /dev/null +++ b/test/stage_package.cmake @@ -0,0 +1,15 @@ +if(NOT DEFINED BUILD_DIR OR NOT DEFINED STAGE_DIR) + message(FATAL_ERROR "BUILD_DIR and STAGE_DIR are required") +endif() + +file(REMOVE_RECURSE "${STAGE_DIR}") +if(CONSUMER_BUILD_DIR) + file(REMOVE_RECURSE "${CONSUMER_BUILD_DIR}") +endif() + +set(_install_command "${CMAKE_COMMAND}" --install "${BUILD_DIR}" --prefix "${STAGE_DIR}") +if(CONFIG) + list(APPEND _install_command --config "${CONFIG}") +endif() + +execute_process(COMMAND ${_install_command} COMMAND_ERROR_IS_FATAL ANY) diff --git a/test/test_module.cpp b/test/test_module.cpp index 478023d2c..ab10fd8b4 100644 --- a/test/test_module.cpp +++ b/test/test_module.cpp @@ -35,6 +35,105 @@ static_assert(!magic_enum::is_flags_v); static_assert(magic_enum::is_flags_enum::value); static_assert(magic_enum::is_flags_v); +// Keep this list in sync with the public declarations exported by magic_enum.cppm. +// Using-declarations verify reachability without relying on unrelated standard +// library operators in a consumer that imports only magic_enum. +namespace module_api { + +using magic_enum::Enum; +using magic_enum::char_type; +using magic_enum::as_common; +using magic_enum::as_flags; +using magic_enum::case_insensitive; +using magic_enum::enum_cast; +using magic_enum::enum_constant; +using magic_enum::enum_contains; +using magic_enum::enum_count; +using magic_enum::enum_entries; +using magic_enum::enum_flags_cast; +using magic_enum::enum_flags_contains; +using magic_enum::enum_flags_name; +using magic_enum::enum_flags_test; +using magic_enum::enum_flags_test_any; +using magic_enum::enum_for_each; +using magic_enum::enum_fuse; +using magic_enum::enum_index; +using magic_enum::enum_integer; +using magic_enum::enum_name; +using magic_enum::enum_names; +using magic_enum::enum_next_value; +using magic_enum::enum_next_value_circular; +using magic_enum::enum_prev_value; +using magic_enum::enum_prev_value_circular; +using magic_enum::enum_reflected; +using magic_enum::enum_switch; +using magic_enum::enum_type_name; +using magic_enum::enum_underlying; +using magic_enum::enum_value; +using magic_enum::enum_values; +using magic_enum::is_flags_enum; +using magic_enum::is_flags_v; +using magic_enum::is_magic_enum_supported; +using magic_enum::is_scoped_enum; +using magic_enum::is_scoped_enum_v; +using magic_enum::is_unscoped_enum; +using magic_enum::is_unscoped_enum_v; +using magic_enum::optional; +using magic_enum::string; +using magic_enum::string_view; +using magic_enum::underlying_type; +using magic_enum::underlying_type_t; + +using magic_enum::customize::adl_info; +using magic_enum::customize::adl_info_holder; +using magic_enum::customize::customize_t; +using magic_enum::customize::default_tag; +using magic_enum::customize::enum_name; +using magic_enum::customize::enum_range; +using magic_enum::customize::enum_type_name; +using magic_enum::customize::invalid_tag; + +using magic_enum::containers::array; +using magic_enum::containers::bitset; +using magic_enum::containers::comparator_indexing; +using magic_enum::containers::default_indexing; +using magic_enum::containers::get; +using magic_enum::containers::make_array; +using magic_enum::containers::name_greater; +using magic_enum::containers::name_greater_case_insensitive; +using magic_enum::containers::name_less; +using magic_enum::containers::name_less_case_insensitive; +using magic_enum::containers::raw_access; +using magic_enum::containers::set; +using magic_enum::containers::to_array; + +using magic_enum::bitwise_operators::operator&; +using magic_enum::bitwise_operators::operator&=; +using magic_enum::bitwise_operators::operator|; +using magic_enum::bitwise_operators::operator|=; +using magic_enum::bitwise_operators::operator^; +using magic_enum::bitwise_operators::operator^=; +using magic_enum::bitwise_operators::operator~; +using magic_enum::iostream_operators::operator<<; +using magic_enum::iostream_operators::operator>>; +using magic_enum::istream_operators::operator>>; +using magic_enum::ostream_operators::operator<<; + +} // namespace module_api + +static_assert(magic_enum::optional{42}.value() == 42); +static_assert(magic_enum::string_view{"GREEN"}.size() == 5); +static_assert(magic_enum::customize::adl_info_holder::is_flags); + +template <> +constexpr magic_enum::customize::customize_t magic_enum::customize::enum_type_name() noexcept { + return "Paint"; +} + +constexpr auto color_type_name = magic_enum::enum_type_name(); +static_assert(color_type_name.size() == 5); +static_assert(color_type_name[0] == 'P'); + constexpr auto color_array = magic_enum::containers::make_array(1, 2, 3); static_assert(color_array[Color::GREEN] == 2); @@ -43,6 +142,17 @@ static_assert(color_bits.test(Color::RED)); static_assert(!color_bits.test(Color::GREEN)); static_assert(color_bits.test(Color::BLUE)); +using ColorBitset = magic_enum::containers::bitset; + +struct GreenOnly { + template + constexpr int operator()(magic_enum::enum_constant) const requires (C == Color::GREEN) { + return 42; + } +}; + +static_assert(magic_enum::enum_switch(GreenOnly{}, Color::GREEN) == 42); + using ColorNameLess = magic_enum::containers::name_less<>; using ColorNameGreater = magic_enum::containers::name_greater<>; using ColorNameLessCaseInsensitive = magic_enum::containers::name_less_case_insensitive; @@ -60,4 +170,6 @@ static_assert(*ColorNameIndex::at(Color::GREEN) == 1); constexpr magic_enum::containers::set color_set {Color::RED, Color::BLUE}; static_assert(color_set.contains(Color::BLUE)); -int main() {} +int main() { + return std::hash{}(color_bits) == std::hash{}(5ULL) ? 0 : 1; +} diff --git a/test/test_module_aliases.cpp b/test/test_module_aliases.cpp new file mode 100644 index 000000000..e9080911a --- /dev/null +++ b/test/test_module_aliases.cpp @@ -0,0 +1,21 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT + +import magic_enum; + +enum class Color { RED, GREEN, BLUE }; + +using Optional = magic_enum::optional; +using String = magic_enum::string; +using StringView = magic_enum::string_view; +using Char = magic_enum::char_type; + +static_assert(Optional{42}.value() == 42); +static_assert(StringView{"GREEN"}.size() == 5); +static_assert(sizeof(Char) == sizeof(char)); +static_assert(magic_enum::enum_name(Color::BLUE).size() == 4); + +int main() { + const String value{"GREEN", 5}; + return value.compare("GREEN"); +} diff --git a/test/test_module_wchar_t.cpp b/test/test_module_wchar_t.cpp new file mode 100644 index 000000000..63e33d805 --- /dev/null +++ b/test/test_module_wchar_t.cpp @@ -0,0 +1,20 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT + +import magic_enum; + +enum class Color { RED, GREEN, BLUE }; + +using String = magic_enum::string; +using StringView = magic_enum::string_view; +using Char = magic_enum::char_type; + +static_assert(StringView{L"GREEN"}.size() == 5); +static_assert(sizeof(Char) == sizeof(wchar_t)); +static_assert(magic_enum::enum_name(Color::BLUE).size() == 4); +static_assert(magic_enum::enum_name(Color::BLUE)[0] == L'B'); + +int main() { + const String value{L"GREEN"}; + return value.size() == 5 ? 0 : 1; +} diff --git a/test_installed_version.bash b/test_installed_version.bash index c3470277e..168e615a1 100644 --- a/test_installed_version.bash +++ b/test_installed_version.bash @@ -1,13 +1,42 @@ -#!/bin/bash -rm -rf ./build -rm -rf ./build2 -rm -rf ./build3 -rm -rf ./install_dir/ -cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release --log-level=DEBUG -DCMAKE_INSTALL_PREFIX=install_dir -cmake --build ./build -j$(nproc) -cmake --install ./build/ --prefix ./install_dir -CMAKE_PREFIX_PATH="./install_dir" cmake -S. -Bbuild2 -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=1 --log-level=DEBUG -cmake --build build2 --config Debug -j$(nproc) --verbose -CMAKE_PREFIX_PATH="./install_dir" cmake -S. -Bbuild3 -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG=1 --log-level=DEBUG -cmake --build build3 --config Debug -j$(nproc) --verbose +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +build_root="${repo_root}/build-installed-version" +source_build_dir="${build_root}/source" +install_dir="${build_root}/install" +cmake_consumer_build_dir="${build_root}/cmake-consumer" +pkgconfig_consumer_build_dir="${build_root}/pkgconfig-consumer" + +test_installed_consumer() { + local build_dir="$1" + shift + + CMAKE_PREFIX_PATH="${install_dir}" cmake -S "${repo_root}" -B "${build_dir}" \ + -DCMAKE_BUILD_TYPE=Debug \ + "$@" + cmake --build "${build_dir}" --parallel + ctest --test-dir "${build_dir}" --output-on-failure --no-tests=error +} + +cmake -E remove_directory "${build_root}" + +cmake -S "${repo_root}" -B "${source_build_dir}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="${install_dir}" \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=OFF \ + -DMAGIC_ENUM_OPT_INSTALL=ON \ + -DMAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS=ON +cmake --build "${source_build_dir}" \ + --target magic_enum_verify_interface_header_sets \ + --parallel +cmake --install "${source_build_dir}" + +test_installed_consumer "${cmake_consumer_build_dir}" \ + -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=ON + +test_installed_consumer "${pkgconfig_consumer_build_dir}" \ + -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG=ON \ + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON From ed9aabbf15eff6a018b34d30ac7d8bde48947cc2 Mon Sep 17 00:00:00 2001 From: neargye Date: Mon, 3 Aug 2026 13:15:26 +0400 Subject: [PATCH 02/11] wip --- .github/workflows/install.yaml | 88 ++++ .../module_test_installed_version.yml | 84 ---- .github/workflows/ubuntu.yml | 11 +- .../ubuntu_test_installed_version.yml | 23 - CMakeLists.txt | 8 +- README.md | 31 +- cmake/GenPkgConfig/GenPkgConfig.cmake | 470 ++++++++++-------- cmake/GenPkgConfig/ReadMe.md | 64 ++- .../getObjectFilesBaseNames.cmake | 54 +- cmake/magic_enumConfig.cmake.in | 4 +- example/CMakeLists.txt | 16 +- module/magic_enum.cppm | 7 +- test/CMakeLists.txt | 24 +- test/installed_module/CMakeLists.txt | 7 +- test_installed_version.bash | 9 +- 15 files changed, 486 insertions(+), 414 deletions(-) create mode 100644 .github/workflows/install.yaml delete mode 100644 .github/workflows/module_test_installed_version.yml delete mode 100644 .github/workflows/ubuntu_test_installed_version.yml diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml new file mode 100644 index 000000000..75c7a6873 --- /dev/null +++ b/.github/workflows/install.yaml @@ -0,0 +1,88 @@ +name: Install + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +permissions: read-all + +jobs: + header-only-ubuntu: + name: Header-only / Ubuntu + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v7 + + - name: test installed package + run: bash ./test_installed_version.bash + + module-macos-gcc: + name: Module / macOS / GCC 16 / fmt / C++20 + runs-on: macos-15 + env: + CC: gcc-16 + CXX: g++-16 + + steps: + - uses: actions/checkout@v7 + + - name: Install toolchain + run: | + brew install gcc ninja fmt + python3 -m pip install cmake==4.4.0 --break-system-packages + + - name: Build and test installed module package + run: | + fmt_prefix="$(brew --prefix fmt)" + cmake -S . -B build-module -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_CXX_EXTENSIONS=OFF \ + -DCMAKE_PREFIX_PATH="${fmt_prefix}" \ + -DMAGIC_ENUM_USE_MODULES=ON \ + -DMAGIC_ENUM_MODULE_WITH_FMT=ON \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=ON \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON + cmake --build build-module --parallel + cmake --build build-module \ + --target magic_enum_verify_interface_header_sets \ + --parallel + ctest --test-dir build-module \ + --output-on-failure \ + --no-tests=error \ + -R '^(test_module(_aliases|_wchar_t)?-cpp20|installed-module-consumer)$' + + module-windows-msvc: + name: Module / Windows / VS 2026 / C++20 + runs-on: windows-2025-vs2026 + + steps: + - uses: actions/checkout@v7 + - uses: ilammy/msvc-dev-cmd@v1 + + - name: Install pinned CMake + shell: pwsh + run: python -m pip install cmake==4.3.4 + + - name: Build and test installed module package + shell: pwsh + run: | + cmake -S . -B build-module -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_CXX_STANDARD=20 ` + -DCMAKE_CXX_STANDARD_REQUIRED=ON ` + -DCMAKE_CXX_EXTENSIONS=OFF ` + -DMAGIC_ENUM_USE_MODULES=ON ` + -DMAGIC_ENUM_OPT_BUILD_TESTS=ON ` + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON + cmake --build build-module --parallel --target magic_enum_module example_module_usage test_module-cpp20 test_module_aliases-cpp20 test_module_wchar_t-cpp20 + cmake --build build-module --parallel --target magic_enum_verify_interface_header_sets + ctest --test-dir build-module --output-on-failure --no-tests=error -R '^(test_module(_aliases|_wchar_t)?-cpp20|installed-module-consumer)$' diff --git a/.github/workflows/module_test_installed_version.yml b/.github/workflows/module_test_installed_version.yml deleted file mode 100644 index 30852e36b..000000000 --- a/.github/workflows/module_test_installed_version.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: module_test_installed_version.yml - -on: [push, pull_request] - -permissions: read-all - -jobs: - macos-gcc: - name: macOS / GCC 16 / import std / C++26 - runs-on: macos-15 - env: - CC: gcc-16 - CXX: g++-16 - steps: - - uses: actions/checkout@v7 - - - name: Install toolchain - run: | - brew install gcc ninja fmt - python3 -m pip install --upgrade "cmake>=4.4.0" --break-system-packages - - - name: Build and test module package - run: | - modules_json="$("${CXX}" -print-file-name=libstdc++.modules.json)" - fmt_prefix="$(brew --prefix fmt)" - cmake -S . -B build-module -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_EXPERIMENTAL_CXX_IMPORT_STD=f35a9ac6-8463-4d38-8eec-5d6008153e7d \ - -DCMAKE_CXX_STDLIB_MODULES_JSON="${modules_json}" \ - -DCMAKE_CXX_STANDARD=26 \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCMAKE_CXX_EXTENSIONS=OFF \ - -DCMAKE_PREFIX_PATH="${fmt_prefix}" \ - -DMAGIC_ENUM_USE_MODULES=ON \ - -DMAGIC_ENUM_MODULE_IMPORT_STD=ON \ - -DMAGIC_ENUM_MODULE_WITH_FMT=ON \ - -DMAGIC_ENUM_OPT_BUILD_TESTS=ON \ - -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON - cmake --build build-module --parallel - cmake --build build-module --target magic_enum_verify_interface_header_sets --parallel - ctest --test-dir build-module --output-on-failure --no-tests=error - - - name: Test installed module with fmt and without import std - run: | - fmt_prefix="$(brew --prefix fmt)" - cmake -S . -B build-module-fmt -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_STANDARD=20 \ - -DCMAKE_CXX_STANDARD_REQUIRED=ON \ - -DCMAKE_PREFIX_PATH="${fmt_prefix}" \ - -DMAGIC_ENUM_USE_MODULES=ON \ - -DMAGIC_ENUM_MODULE_WITH_FMT=ON \ - -DMAGIC_ENUM_OPT_BUILD_TESTS=ON \ - -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF - cmake --build build-module-fmt --parallel --target magic_enum_module - ctest --test-dir build-module-fmt \ - --output-on-failure \ - --no-tests=error \ - -R '^installed-module-consumer$' - - windows-msvc: - name: Windows / VS 2026 / C++20 - runs-on: windows-2025-vs2026 - steps: - - uses: actions/checkout@v7 - - uses: ilammy/msvc-dev-cmd@v1 - - - name: Install pinned CMake - shell: pwsh - run: python -m pip install cmake==4.3.4 - - - name: Build and test module package - shell: pwsh - run: | - cmake -S . -B build-module -G Ninja ` - -DCMAKE_BUILD_TYPE=Release ` - -DCMAKE_CXX_STANDARD=20 ` - -DCMAKE_CXX_STANDARD_REQUIRED=ON ` - -DMAGIC_ENUM_USE_MODULES=ON ` - -DMAGIC_ENUM_OPT_BUILD_TESTS=ON ` - -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON - cmake --build build-module --parallel --target magic_enum_module example_module_usage test_module-cpp20 test_module_aliases-cpp20 test_module_wchar_t-cpp20 - cmake --build build-module --parallel --target magic_enum_verify_interface_header_sets - ctest --test-dir build-module --output-on-failure --no-tests=error -R '^(test_module(_aliases|_wchar_t)?-cpp20|installed-module-consumer)$' diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 3570955ce..670538e3c 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,13 +70,17 @@ jobs: run: | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y sudo apt update - sudo apt install -y ${{ matrix.compiler.cxx }} + packages=("${{ matrix.compiler.cxx }}") + if [[ "${{ matrix.compiler.import_std }}" == "TRUE" ]]; then + packages+=(libfmt-dev) + fi + sudo apt install -y "${packages[@]}" - name: Install pinned CMake if: ${{ matrix.compiler.modules == 'TRUE' }} run: | if [[ "${{ matrix.compiler.import_std }}" == "TRUE" ]]; then - pip install --upgrade "cmake>=4.4.0" --break-system-packages + pip install cmake==4.4.0 --break-system-packages else pip install cmake==4.3.4 --break-system-packages fi @@ -113,9 +117,11 @@ jobs: if: ${{ matrix.compiler.import_std == 'TRUE' }} run: | cxx_standard=23 + fmt_integration=OFF stdlib_options=(-DCMAKE_CXX_FLAGS:STRING=-stdlib=libc++) if [[ "${{ matrix.compiler.cc }}" == gcc-* ]]; then cxx_standard=26 + fmt_integration=ON stdlib_options=(-DCMAKE_CXX_STDLIB_MODULES_JSON="$("${CXX}" -print-file-name=libstdc++.modules.json)") fi @@ -127,6 +133,7 @@ jobs: -DCMAKE_CXX_EXTENSIONS=OFF \ -DMAGIC_ENUM_USE_MODULES:BOOL=TRUE \ -DMAGIC_ENUM_MODULE_IMPORT_STD:BOOL=TRUE \ + -DMAGIC_ENUM_MODULE_WITH_FMT:BOOL="${fmt_integration}" \ -GNinja \ "${stdlib_options[@]}" diff --git a/.github/workflows/ubuntu_test_installed_version.yml b/.github/workflows/ubuntu_test_installed_version.yml deleted file mode 100644 index 222cffea3..000000000 --- a/.github/workflows/ubuntu_test_installed_version.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: ubuntu_test_installed_version.yml - -on: - push: - branches: - - master - - main - pull_request: - branches: - - master - - main - -permissions: read-all - -jobs: - build: - runs-on: ubuntu-24.04 - - steps: - - uses: actions/checkout@v7 - - - name: test installed package - run: bash ./test_installed_version.bash diff --git a/CMakeLists.txt b/CMakeLists.txt index a7909b9c0..9a6f2588a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,12 @@ option( ) option(MAGIC_ENUM_USE_MODULES "Build the magic_enum C++20 module target" OFF) option(MAGIC_ENUM_MODULE_IMPORT_STD "Build the module target with import std" OFF) -option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt integration" OFF) +set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT OFF) +if(MAGIC_ENUM_USE_MODULES AND TARGET fmt::fmt) + set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT ON) +endif() +option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt integration" ${MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT}) +unset(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT) option(MAGIC_ENUM_OPT_INSTALL_PACKAGE_XML "Include package.xml when installing" ${MAGIC_ENUM_OPT_INSTALL}) if(MAGIC_ENUM_MODULE_IMPORT_STD AND NOT MAGIC_ENUM_USE_MODULES) @@ -108,6 +113,7 @@ endif() function(magic_enum_make_module_library name) add_library(${name} STATIC) + set_target_properties(${name} PROPERTIES CXX_EXTENSIONS OFF) target_sources(${name} PUBLIC FILE_SET CXX_MODULES diff --git a/README.md b/README.md index 0e41bd528..cf2f1c580 100644 --- a/README.md +++ b/README.md @@ -353,26 +353,27 @@ If you like this project, please consider donating to one of the funds that help ``` * **CMake targets**: - - `magic_enum::magic_enum` is always provided for the header-only library; - - `magic_enum::magic_enum_module` is provided for the C++20 module library when `MAGIC_ENUM_USE_MODULES=ON`. + - `magic_enum::magic_enum` is the header-only target. + - `magic_enum::magic_enum_module` is the C++20 module target. Enable it with `MAGIC_ENUM_USE_MODULES=ON`. - Use the header-only target with `#include `, or substitute the module target and use `import magic_enum;`. + The pkg-config package supports only the header-only target. Use the CMake package for the module target. - Maintainers using CMake 3.24+ can enable `MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS=ON` to verify that every public header can be included independently. The option is enabled by default when `MAGIC_ENUM_OPT_BUILD_TESTS` is enabled on supported CMake versions. +* **C++20 modules** require CMake 3.28+. -* **C++20 modules** require CMake 3.28+ and are explicitly enabled when building magic_enum. - - Configure with CMake: + Enable modules when building magic_enum: ```sh cmake -S . -B build -G Ninja -DMAGIC_ENUM_USE_MODULES=ON + cmake --build build ``` - Link the module target instead of the header-only target: + Link the module target: ```cmake + find_package(magic_enum CONFIG REQUIRED) target_link_libraries(your_executable PRIVATE magic_enum::magic_enum_module) + set_target_properties(your_executable PROPERTIES CXX_EXTENSIONS OFF) ``` - Then use `import` instead of `#include`: + Import the module: ```cpp import magic_enum; @@ -380,13 +381,11 @@ If you like this project, please consider donating to one of the funds that help auto name = magic_enum::enum_name(Color::RED); // "RED" ``` - Caveats: - - Do not mix `#include ` and `import magic_enum;` within the same program; this is an ODR violation. - - An installed module package must be consumed with a compatible compiler, standard library, C++ language standard, and compiler-extension mode. The module target propagates the language standard used to build it, but CMake cannot validate every toolchain option that affects module compatibility. - - An installed package provides `magic_enum::magic_enum_module` only when magic_enum was configured with `MAGIC_ENUM_USE_MODULES=ON` before installation. - - Module configuration macros are fixed when `magic_enum::magic_enum_module` is built; they are not applied independently by each consumer translation unit. - - `import std;` is opt-in and experimental (requires CMake 3.30+). Set `CMAKE_EXPERIMENTAL_CXX_IMPORT_STD` to the UUID documented by the selected CMake version before `project()`, choose a supported `CMAKE_CXX_STANDARD`, and enable `MAGIC_ENUM_MODULE_IMPORT_STD=ON`. The experimental UUID is version-specific and is intentionally not selected by magic_enum. If a consumer source also uses `import std;`, enable it on that consumer target with `set_property(TARGET your_executable PROPERTY CXX_MODULE_STD ON)`; this target property is not propagated by linking `magic_enum::magic_enum_module`. - - `{fmt}` integration is explicit for the compiled module. Enable it with `MAGIC_ENUM_MODULE_WITH_FMT=ON`; this requires the `fmt` CMake package and makes `fmt::fmt` a public dependency of `magic_enum::magic_enum_module`. The installed magic_enum package will resolve the dependency with `find_dependency(fmt CONFIG)`. + Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. + + Optional settings: + - `{fmt}` support is enabled automatically when the `fmt::fmt` target already exists. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. + - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable experimental `import std` support. This requires a compatible CMake toolchain. ## Compiler compatibility diff --git a/cmake/GenPkgConfig/GenPkgConfig.cmake b/cmake/GenPkgConfig/GenPkgConfig.cmake index 3069fa372..9eaf5fe49 100644 --- a/cmake/GenPkgConfig/GenPkgConfig.cmake +++ b/cmake/GenPkgConfig/GenPkgConfig.cmake @@ -3,7 +3,7 @@ GenPkgConfig ------------ -This is the library helping you to generate and install pkg-config files. +This module generates and installs pkg-config files from CMake targets. Unlicense ^^^^^^^^^ @@ -12,241 +12,273 @@ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -For more information, please refer to - -Warning -^^^^^^^ - -CMake is currently merging a built-in impl of pkg-config file generator! https://gitlab.kitware.com/cmake/cmake/-/merge_requests/6363 +For more information, please refer to . Functions ^^^^^^^^^ + .. command:: configure_pkg_config_file - .. versionadded:: 3.22 + Requires CMake 3.22 or newer. - Generates a pkg-config file for + Generates and installs a pkg-config file for an interface, object, static, or shared library target. :: - configure_pkg_config_file( - NAME - VERSION - DESCRIPTION - URL - COMPONENT - INSTALL_LIB_DIR - INSTALL_INCLUDE_DIR - REQUIRES ... ... - CONFLICTS ... ... + configure_pkg_config_file( + NAME + VERSION + DESCRIPTION + URL + COMPONENT + INSTALL_LIB_DIR + INSTALL_INCLUDE_DIR + REQUIRES ... + CONFLICTS ... ) - The arguments are optional and usually are not needed to be set if global (not component-specific) CPACK vars have been set before. - - Generation is done in build time using packaging expressions. + ``VERSION`` and ``DESCRIPTION`` fall back to project or CPack metadata. Install directories must be relative. Compiled targets must be installed into ``INSTALL_LIB_DIR`` by the caller; object files are installed by this module. #]=======================================================================] -set("GEN_PKG_CONFIG_WORKAROUNDS_BUILD_TIME_SCRIPTS" "${CMAKE_CURRENT_LIST_DIR}/buildTimeScripts") - -cmake_policy(SET CMP0070 NEW) +include(GNUInstallDirs) function(configure_pkg_config_file TARGET) - cmake_parse_arguments("" - "" # options - "NAME;VERSION;DESCRIPTION;URL;COMPONENT;INSTALL_LIB_DIR;INSTALL_INCLUDE_DIR" # one_value_keywords - "REQUIRES;CONFLICTS" # multi_value_keywords - ${ARGN} - ) - - configure_pkg_config_file_vars("${TARGET}" "${_NAME}" "${_INSTALL_LIB_DIR}" "${_INSTALL_INCLUDE_DIR}" "${_COMPONENT}" "${_DESCRIPTION}" "${_URL}" "${_VERSION}" "${_REQUIRES}" "${_CONFLICTS}") + cmake_parse_arguments(PARSE_ARGV 1 ARG + "" + "NAME;VERSION;DESCRIPTION;URL;COMPONENT;INSTALL_LIB_DIR;INSTALL_INCLUDE_DIR" + "REQUIRES;CONFLICTS" + ) + + if(ARG_KEYWORDS_MISSING_VALUES) + list(JOIN ARG_KEYWORDS_MISSING_VALUES ", " _MISSING_ARGUMENTS) + message(FATAL_ERROR + "configure_pkg_config_file missing values for: ${_MISSING_ARGUMENTS}" + ) + endif() + if(ARG_UNPARSED_ARGUMENTS) + list(JOIN ARG_UNPARSED_ARGUMENTS ", " _UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "configure_pkg_config_file unknown arguments: ${_UNPARSED_ARGUMENTS}" + ) + endif() + + configure_pkg_config_file_vars( + "${TARGET}" + "${ARG_NAME}" + "${ARG_INSTALL_LIB_DIR}" + "${ARG_INSTALL_INCLUDE_DIR}" + "${ARG_COMPONENT}" + "${ARG_DESCRIPTION}" + "${ARG_URL}" + "${ARG_VERSION}" + "${ARG_REQUIRES}" + "${ARG_CONFLICTS}" + ) endfunction() -function(ge_expr_basename inputExpr outVar) - set("${outVar}" "$" PARENT_SCOPE) -endfunction() +function(configure_pkg_config_file_vars + TARGET + _NAME + _INSTALL_LIB_DIR + _INSTALL_INCLUDE_DIR + _COMPONENT + _DESCRIPTION + _URL + _VERSION + _REQUIRES + _CONFLICTS +) + if(NOT TARGET "${TARGET}") + message(FATAL_ERROR + "configure_pkg_config_file target '${TARGET}' does not exist" + ) + endif() + + get_target_property(_TARGET_TYPE "${TARGET}" TYPE) + set(_SUPPORTED_TARGET_TYPES + INTERFACE_LIBRARY + OBJECT_LIBRARY + STATIC_LIBRARY + SHARED_LIBRARY + ) + if(NOT _TARGET_TYPE IN_LIST _SUPPORTED_TARGET_TYPES) + message(FATAL_ERROR + "configure_pkg_config_file does not support ${_TARGET_TYPE} target '${TARGET}'" + ) + endif() + + if("${_NAME}" STREQUAL "") + get_target_property(_NAME "${TARGET}" NAME) + endif() + + if("${_DESCRIPTION}" STREQUAL "") + if(NOT "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}" STREQUAL "") + set(_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") + elseif(NOT "${CPACK_PACKAGE_DESCRIPTION}" STREQUAL "") + set(_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}") + else() + set(_DESCRIPTION "${PROJECT_DESCRIPTION}") + endif() + endif() + if("${_VERSION}" STREQUAL "") + if(NOT "${CPACK_PACKAGE_VERSION}" STREQUAL "") + set(_VERSION "${CPACK_PACKAGE_VERSION}") + else() + set(_VERSION "${PROJECT_VERSION}") + endif() + endif() + if("${_URL}" STREQUAL "") + if(NOT "${CPACK_PACKAGE_HOMEPAGE_URL}" STREQUAL "") + set(_URL "${CPACK_PACKAGE_HOMEPAGE_URL}") + else() + set(_URL "${PROJECT_HOMEPAGE_URL}") + endif() + endif() + + if("${_DESCRIPTION}" STREQUAL "") + message(FATAL_ERROR "configure_pkg_config_file requires DESCRIPTION") + endif() + if("${_VERSION}" STREQUAL "") + message(FATAL_ERROR "configure_pkg_config_file requires VERSION") + endif() + + if("${_INSTALL_INCLUDE_DIR}" STREQUAL "") + set(_INSTALL_INCLUDE_DIR "${CMAKE_INSTALL_INCLUDEDIR}") + endif() + if("${_INSTALL_LIB_DIR}" STREQUAL "") + set(_INSTALL_LIB_DIR "${CMAKE_INSTALL_LIBDIR}") + endif() + if(IS_ABSOLUTE "${_INSTALL_INCLUDE_DIR}" OR IS_ABSOLUTE "${_INSTALL_LIB_DIR}") + message(FATAL_ERROR + "configure_pkg_config_file requires relative install directories" + ) + endif() + + if(_TARGET_TYPE STREQUAL INTERFACE_LIBRARY) + set(_IS_INTERFACE TRUE) + else() + set(_IS_INTERFACE FALSE) + endif() + if(_TARGET_TYPE STREQUAL OBJECT_LIBRARY) + set(_IS_OBJECT TRUE) + else() + set(_IS_OBJECT FALSE) + endif() + + get_property(_HAS_PUBLIC_INCLUDES + TARGET "${TARGET}" + PROPERTY INTERFACE_INCLUDE_DIRECTORIES + SET + ) + + set(_PKG_CONFIG_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/$") + set(_PKG_CONFIG_FILE "${_PKG_CONFIG_OUTPUT_DIR}/${_NAME}.pc") + + set(_PATH_ANCHOR "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig-root") + file(RELATIVE_PATH _PREFIX_FROM_PC + "${_PATH_ANCHOR}/${_INSTALL_LIB_DIR}/pkgconfig" + "${_PATH_ANCHOR}" + ) + string(REGEX REPLACE "/$" "" _PREFIX_FROM_PC "${_PREFIX_FROM_PC}") + + set(_CONTENT "prefix=\${pcfiledir}/${_PREFIX_FROM_PC}\n") + if(NOT _IS_INTERFACE) + string(APPEND _CONTENT "libdir=\${prefix}/${_INSTALL_LIB_DIR}\n") + endif() + if(_HAS_PUBLIC_INCLUDES) + string(APPEND _CONTENT "includedir=\${prefix}/${_INSTALL_INCLUDE_DIR}\n") + endif() + + string(APPEND _CONTENT + "\nName: ${_NAME}\n" + "Description: ${_DESCRIPTION}\n" + ) + if(NOT "${_URL}" STREQUAL "") + string(APPEND _CONTENT "URL: ${_URL}\n") + endif() + string(APPEND _CONTENT "Version: ${_VERSION}\n") + + if(NOT "${_REQUIRES}" STREQUAL "") + list(JOIN _REQUIRES ", " _REQUIRES) + string(APPEND _CONTENT "Requires: ${_REQUIRES}\n") + endif() + if(NOT "${_CONFLICTS}" STREQUAL "") + list(JOIN _CONFLICTS ", " _CONFLICTS) + string(APPEND _CONTENT "Conflicts: ${_CONFLICTS}\n") + endif() + + if(_IS_OBJECT) + string(MD5 _TARGET_ID "${CMAKE_CURRENT_BINARY_DIR};${TARGET};${_NAME}") + set(_OBJECTS_PLACEHOLDER "__GEN_PKG_CONFIG_OBJECTS_${_TARGET_ID}__") + set(_TARGET_OBJECTS_FILE "${_PKG_CONFIG_OUTPUT_DIR}/${_NAME}.objects") + set(_PKG_CONFIG_FILE_UNFINISHED "${_PKG_CONFIG_FILE}.unfinished") + file(GENERATE + OUTPUT "${_TARGET_OBJECTS_FILE}" + CONTENT "$" + TARGET "${TARGET}" + ) + string(APPEND _CONTENT "Libs: ${_OBJECTS_PLACEHOLDER}\n") + elseif(NOT _IS_INTERFACE) + string(APPEND _CONTENT + "Libs: \${libdir}/$\n" + ) + endif() + + if(_HAS_PUBLIC_INCLUDES) + string(APPEND _CONTENT "Cflags: -I\${includedir}\n") + endif() + + set(_INSTALL_COMPONENT_ARGS) + if(NOT "${_COMPONENT}" STREQUAL "") + list(APPEND _INSTALL_COMPONENT_ARGS COMPONENT "${_COMPONENT}") + endif() + + if(_IS_OBJECT) + file(GENERATE + OUTPUT "${_PKG_CONFIG_FILE_UNFINISHED}" + CONTENT "${_CONTENT}" + TARGET "${TARGET}" + ) -function(configure_pkg_config_file_vars TARGET _NAME _INSTALL_LIB_DIR _INSTALL_INCLUDE_DIR _COMPONENT _DESCRIPTION _URL _VERSION _REQUIRES _CONFLICTS) - #$ - #INTERFACE_LINK_DIRECTORIES - #INTERFACE_LINK_LIBRARIES - #INTERFACE_LINK_OPTIONS - - if(_NAME) - else() - set(_NAME "$") - endif() - - if(_DESCRIPTION) - else() - set(_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}") - endif() - - if(_VERSION) - else() - set(_VERSION "${CPACK_PACKAGE_VERSION}") - endif() - - if(_URL) - else() - set(_URL "${CPACK_PACKAGE_HOMEPAGE_URL}") - endif() - - if(_INSTALL_INCLUDE_DIR) - else() - set(_INSTALL_INCLUDE_DIR "${CMAKE_INSTALL_INCLUDEDIR}") - endif() - - if(_INSTALL_LIB_DIR) - else() - set(_INSTALL_LIB_DIR "${CMAKE_INSTALL_LIBDIR}") - endif() - - set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${_NAME}.pc") - - set(PUBLIC_INCLUDES "$") - set(PUBLIC_LIBS "$") - set(PUBLIC_COMPILE_FLAGS "$") - - set("IS_INTERFACE" "$,INTERFACE_LIBRARY>") - set("IS_OBJECT" "$,OBJECT_LIBRARY>") - get_target_property(CONFIGURE_TIME_TARGET_TYPE "${TARGET}" TYPE) - if(CONFIGURE_TIME_TARGET_TYPE STREQUAL OBJECT_LIBRARY) - set(CONFIGURE_TIME_IS_OBJECT ON) # Special measures have to be taken!!! - endif() - - set("NEEDS_LIBS" "$,$>") - set("NEEDS_LIB_DIR" "$") - string(REPLACE "," "$" NEEDS_LIBS_ESCAPED "${NEEDS_LIBS}") - string(REPLACE ">" "$" NEEDS_LIBS_ESCAPED "${NEEDS_LIBS_ESCAPED}") - - # Only use prefix if paths are not absolute like they are with nix - # See also: https://github.com/NixOS/nixpkgs/issues/144170 - if(NOT(IS_ABSOLUTE "${_INSTALL_LIB_DIR}" AND IS_ABSOLUTE "${_INSTALL_INCLUDE_DIR}")) - list(APPEND header "prefix=${CMAKE_INSTALL_PREFIX}") - endif() - - if(IS_ABSOLUTE "${_INSTALL_LIB_DIR}") - list(APPEND header "$,${NEEDS_LIB_DIR}>,libdir=${_INSTALL_LIB_DIR},>") - else() - list(APPEND header "$,${NEEDS_LIB_DIR}>,libdir=\${prefix}/${_INSTALL_LIB_DIR},>") - endif() - - if(IS_ABSOLUTE "${_INSTALL_INCLUDE_DIR}") - list(APPEND header "$,includedir=${_INSTALL_INCLUDE_DIR},>") - else() - list(APPEND header "$,includedir=\${prefix}/${_INSTALL_INCLUDE_DIR},>") - endif() - - list(APPEND libSpecific "Name: ${_NAME}") - if(_DESCRIPTION) - list(APPEND libSpecific "Description: ${_DESCRIPTION}") - endif() - if(_URL) - list(APPEND libSpecific "URL: ${_URL}") - endif() - if(_VERSION) - list(APPEND libSpecific "Version: ${_VERSION}") - endif() - if(_REQUIRES) - list(APPEND libSpecific "Requires: ${_REQUIRES}") - endif() - if(_CONFLICTS) - list(APPEND libSpecific "Conflicts: ${_CONFLICTS}") - endif() - - set(OTHER_INCLUDE_FLAGS "-I$, -I>") # Not needed, we can only get build interface flags here. Insert them after -I\${includedir} if you find a way to fix/workaround it - - # Here is a workaround to inability to use TARGET_LINKER_FILE_NAME for targets not involving library generation. - # Strangely $<") # A hack because there is no escape for `$` or `<` or `$<`. So we just disrupt $< into pieces - set(CURRENT_LIB_ESCAPED_BINARY_NAME "${ESCAPED_GENEXPR_BEGINNING}TARGET_LINKER_FILE_NAME:${TARGET}$") - set(LINK_CURRENT_LIB_FLAG "$>") - - if(CONFIGURE_TIME_IS_OBJECT) - set(IS_TARGET_OBJECTS_CONFIGURE_TIME_UNAVAILABLE ON) - if(IS_TARGET_OBJECTS_CONFIGURE_TIME_UNAVAILABLE) - message(WARNING "CMake is shit: There is (at least was at the time of writing of this code) no generator expression to get only basenames of object files. They are also unavailable at configure stage. And there were no CMake generator expressions for making replacements in strings. So we workaround this.") - set(TARGET_OBJECTS_FILE "${TARGET}.obj_list") - set(OBJECTS_FILE_RETRIEVAL_TARGET_NAME "${TARGET}_get_objects_list") - - set(PKGCONFIG_DUMMY_UNFINISHED_GEN_TARGET_NAME "${TARGET}_pkgconfig_unfinished") - set(PKGCONFIG_PATCH_TARGET_NAME "${TARGET}_patch_pkgconfig") - - set(PKG_CONFIG_FILE_NAME_FINISHED "${PKG_CONFIG_FILE_NAME}") - set(PKG_CONFIG_FILE_NAME_UNFINISHED "${PKG_CONFIG_FILE_NAME_FINISHED}.unfinished") - - file(GENERATE OUTPUT "${TARGET_OBJECTS_FILE}" CONTENT "$") - - add_custom_command( - OUTPUT "${TARGET_OBJECTS_FILE}" - COMMENT "A dummy command to workaround cmake limitations" - ) - add_custom_target("${OBJECTS_FILE_RETRIEVAL_TARGET_NAME}" - DEPENDS "${TARGET_OBJECTS_FILE}" - ) - - add_custom_command( - OUTPUT "${PKG_CONFIG_FILE_NAME_FINISHED}" - PRE_BUILD COMMAND ${CMAKE_COMMAND} "-DobjectsFile=\"${TARGET_OBJECTS_FILE}\"" "-DpkgConfigFileUnlinished=\"${PKG_CONFIG_FILE_NAME_UNFINISHED}\"" "-DpkgConfigFileFinal=\"${PKG_CONFIG_FILE_NAME_FINISHED}\"" "-P" "${GEN_PKG_CONFIG_WORKAROUNDS_BUILD_TIME_SCRIPTS}/getObjectFilesBaseNames.cmake" - MAIN_DEPENDENCY "${TARGET_OBJECTS_FILE}" - DEPENDS "${PKG_CONFIG_FILE_NAME_UNFINISHED}" - COMMENT "Working around CMake limitations about getting list of basenames of object files and about lack of generator expressions to modify strings: ${PKG_CONFIG_FILE_NAME_UNFINISHED} + ${TARGET_OBJECTS_FILE} -> ${PKG_CONFIG_FILE_NAME_FINISHED}" - ) - add_custom_target("${PKGCONFIG_PATCH_TARGET_NAME}" ALL - DEPENDS "${PKG_CONFIG_FILE_NAME_FINISHED}" - ) - add_dependencies("${PKGCONFIG_PATCH_TARGET_NAME}" "${OBJECTS_FILE_RETRIEVAL_TARGET_NAME}" "${PKGCONFIG_DUMMY_UNFINISHED_GEN_TARGET_NAME}") - - set(PROPERLY_JOINED_TARGET_OBJECTS "@PROPERLY_JOINED_TARGET_OBJECTS@") - else() - set("RAW_TARGET_OBJECTS" "$") - message(FATAL_ERROR "This branch is unimplemented because CMake lacked the needed functionality at the time") - set(PROPERLY_JOINED_TARGET_OBJECTS "${RAW_TARGET_OBJECTS}") - endif() - endif() - - set(LINK_CURRENT_OBJECT_FLAG "$") - - list(APPEND libSpecific "$,${NEEDS_LIBS},${IS_OBJECT}>,Libs: -L\${libdir} ${LINK_CURRENT_LIB_FLAG} ${LINK_CURRENT_OBJECT_FLAG} $,-l$, -l>,>,>\n$,$>,Cflags: -I\${includedir}$, $,>,>,>") - - - list(JOIN header "\n" header) - list(JOIN libSpecific "\n" libSpecific) - set(libSpecific "${header}\n\n${libSpecific}") - - if(CONFIGURE_TIME_IS_OBJECT) - file(GENERATE OUTPUT "${PKG_CONFIG_FILE_NAME_UNFINISHED}" - CONTENT "${libSpecific}" - ) - - # Dummy target for generated files - add_custom_command( - OUTPUT "${PKG_CONFIG_FILE_NAME_UNFINISHED}" - COMMENT "A dummy command to workaround cmake limitations" - ) - add_custom_target("${PKGCONFIG_DUMMY_UNFINISHED_GEN_TARGET_NAME}" - DEPENDS "${PKG_CONFIG_FILE_NAME_UNFINISHED}" - ) - - - install(FILES "${PKG_CONFIG_FILE_NAME_FINISHED}" - DESTINATION "${_INSTALL_LIB_DIR}/pkgconfig" - COMPONENT "${_COMPONENT}" - ) - else() - file(GENERATE OUTPUT "${PKG_CONFIG_FILE_NAME}" - CONTENT "${libSpecific}" - ) - - - install(FILES "${PKG_CONFIG_FILE_NAME}" - DESTINATION "${_INSTALL_LIB_DIR}/pkgconfig" - COMPONENT "${_COMPONENT}" - ) - endif() -endfunction() + set(_OBJECTS_HELPER + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/buildTimeScripts/getObjectFilesBaseNames.cmake" + ) + add_custom_command( + OUTPUT "${_PKG_CONFIG_FILE}" + COMMAND "${CMAKE_COMMAND}" + "-DobjectsFile=${_TARGET_OBJECTS_FILE}" + "-DpkgConfigFileUnfinished=${_PKG_CONFIG_FILE_UNFINISHED}" + "-DpkgConfigFileFinal=${_PKG_CONFIG_FILE}" + "-DobjectsPlaceholder=${_OBJECTS_PLACEHOLDER}" + -P "${_OBJECTS_HELPER}" + DEPENDS + "${TARGET}" + "${_TARGET_OBJECTS_FILE}" + "${_PKG_CONFIG_FILE_UNFINISHED}" + "${_OBJECTS_HELPER}" + COMMENT "Finalize ${_NAME}.pc" + VERBATIM + ) + add_custom_target("gen_pkg_config_${_TARGET_ID}" ALL + DEPENDS "${_PKG_CONFIG_FILE}" + ) + install(FILES "$" + DESTINATION "${_INSTALL_LIB_DIR}" + ${_INSTALL_COMPONENT_ARGS} + ) + else() + file(GENERATE + OUTPUT "${_PKG_CONFIG_FILE}" + CONTENT "${_CONTENT}" + TARGET "${TARGET}" + ) + endif() + + install(FILES "${_PKG_CONFIG_FILE}" + DESTINATION "${_INSTALL_LIB_DIR}/pkgconfig" + RENAME "${_NAME}.pc" + ${_INSTALL_COMPONENT_ARGS} + ) +endfunction() diff --git a/cmake/GenPkgConfig/ReadMe.md b/cmake/GenPkgConfig/ReadMe.md index 4d7178273..5a2271339 100644 --- a/cmake/GenPkgConfig/ReadMe.md +++ b/cmake/GenPkgConfig/ReadMe.md @@ -1,45 +1,41 @@ -GenPkgConfig.cmake -=================== +# GenPkgConfig.cmake -A script generating pkg-config files. +`GenPkgConfig.cmake` generates and installs pkg-config metadata for CMake +interface, object, static, and shared library targets. It requires CMake 3.22 +or newer. -WARNING: CMake [is currently merging own built-in pkgconfig generation implementation](https://gitlab.kitware.com/cmake/cmake/-/merge_requests/6363)! +The module was added to nameof and magic_enum through +[Neargye/nameof#44](https://github.com/Neargye/nameof/pull/44) and +[Neargye/magic_enum#202](https://github.com/Neargye/magic_enum/pull/202). -If you require such a new version of CMake, you probably should use the built-in impl instead. - -Syntax ------- +## Usage ```cmake -configure_pkg_config_file( - NAME - VERSION - DESCRIPTION - URL - COMPONENT - INSTALL_LIB_DIR - INSTALL_INCLUDE_DIR - REQUIRES ... ... - CONFLICTS ... ... +configure_pkg_config_file( + NAME + VERSION + DESCRIPTION + URL + COMPONENT + INSTALL_LIB_DIR + INSTALL_INCLUDE_DIR + REQUIRES ... + CONFLICTS ... ) ``` -Issuees -------- - -1. For `OBJECT` targets we have run into big issues. CMake - 1. Doesn't allow to get the list of object files at configure time - 2. Allows to get a list of object files as a generator exression ... - 3. BUT ... the path to them is full, but we need only file name! - 4. CMake doesn't allow to strip directory path via generator expression - 5. ... neither it allows string editing within generator expressions ... - - so ... we have to create a custom target using a custom CMake script executed separately, but ... - - 6. `file(GENERATE` doesn't properly register dependencies - ... so we have to use `add_custom_command` to say CMake that these files are generated +`NAME` defaults to the target name. `VERSION` and `DESCRIPTION` fall back to +project or CPack metadata and are required after fallback. Unknown arguments +and keywords without values are rejected. - 7. And CMake `install(FILES` doesn't mean that the targets generating these files are automatically executed, +Install directories must be relative. Generated paths are based on +`${pcfiledir}`, so install-time `cmake --install --prefix` overrides and +`DESTDIR` staging remain relocatable. - So we have to use `ALL` in `add_custom_target`. +Call `configure_pkg_config_file()` after setting the target's public include +directories. Dependencies are not inferred from CMake target properties; pass +pkg-config dependencies explicitly with `REQUIRES`. +The caller must install static and shared targets into `INSTALL_LIB_DIR`. +Object files are installed by this module and listed in `Libs`; their basenames +must be unique, including case-insensitively on Windows. diff --git a/cmake/GenPkgConfig/buildTimeScripts/getObjectFilesBaseNames.cmake b/cmake/GenPkgConfig/buildTimeScripts/getObjectFilesBaseNames.cmake index e0ca0a28b..c6bb77f44 100644 --- a/cmake/GenPkgConfig/buildTimeScripts/getObjectFilesBaseNames.cmake +++ b/cmake/GenPkgConfig/buildTimeScripts/getObjectFilesBaseNames.cmake @@ -1,17 +1,49 @@ -message(STATUS "objectsFile ${objectsFile}") -message(STATUS "pkgConfigFileFinal ${pkgConfigFileFinal}") -message(STATUS "pkgConfigFileUnlinished ${pkgConfigFileUnlinished}") +cmake_minimum_required(VERSION 3.22) -file(READ "${objectsFile}" TARGET_OBJECTS) +foreach(required_variable + objectsFile + pkgConfigFileUnfinished + pkgConfigFileFinal + objectsPlaceholder +) + if(NOT DEFINED "${required_variable}") + message(FATAL_ERROR "${required_variable} is required") + endif() +endforeach() -set(PROPERLY_JOINED_TARGET_OBJECTS "") +file(READ "${objectsFile}" TARGET_OBJECTS) -foreach(objFullPath ${TARGET_OBJECTS}) - get_filename_component(objFullPath "${objFullPath}" NAME) - list(APPEND PROPERLY_JOINED_TARGET_OBJECTS "${objFullPath}") +set(PROPERLY_JOINED_TARGET_OBJECTS) +set(OBJECT_NAME_KEYS) +foreach(object_path IN LISTS TARGET_OBJECTS) + get_filename_component(object_name "${object_path}" NAME) + set(object_name_key "${object_name}") + if(WIN32) + string(TOLOWER "${object_name_key}" object_name_key) + endif() + if(object_name_key IN_LIST OBJECT_NAME_KEYS) + message(FATAL_ERROR + "Object files for pkg-config must have unique basenames: ${object_name}" + ) + endif() + list(APPEND OBJECT_NAME_KEYS "${object_name_key}") + string(REPLACE "\\" "\\\\" object_name "${object_name}") + string(REPLACE " " "\\ " object_name "${object_name}") + list(APPEND PROPERLY_JOINED_TARGET_OBJECTS "\${libdir}/${object_name}") endforeach() list(JOIN PROPERLY_JOINED_TARGET_OBJECTS " " PROPERLY_JOINED_TARGET_OBJECTS) -message(STATUS "PROPERLY_JOINED_TARGET_OBJECTS AFTER ${PROPERLY_JOINED_TARGET_OBJECTS}") - -configure_file("${pkgConfigFileUnlinished}" "${pkgConfigFileFinal}" @ONLY) +file(READ "${pkgConfigFileUnfinished}" PKG_CONFIG_CONTENT) +string(FIND "${PKG_CONFIG_CONTENT}" "${objectsPlaceholder}" OBJECTS_PLACEHOLDER_INDEX) +if(OBJECTS_PLACEHOLDER_INDEX EQUAL -1) + message(FATAL_ERROR + "Object placeholder is missing from the unfinished pkg-config file" + ) +endif() +string(REPLACE + "${objectsPlaceholder}" + "${PROPERLY_JOINED_TARGET_OBJECTS}" + PKG_CONFIG_CONTENT + "${PKG_CONFIG_CONTENT}" +) +file(WRITE "${pkgConfigFileFinal}" "${PKG_CONFIG_CONTENT}") diff --git a/cmake/magic_enumConfig.cmake.in b/cmake/magic_enumConfig.cmake.in index ff5f9cfef..924958f3d 100644 --- a/cmake/magic_enumConfig.cmake.in +++ b/cmake/magic_enumConfig.cmake.in @@ -4,8 +4,6 @@ include(CMakeFindDependencyMacro) @MAGIC_ENUM_FIND_DEPENDENCIES@ -if(NOT TARGET magic_enum::magic_enum) - include("${CMAKE_CURRENT_LIST_DIR}/@MAGIC_ENUM_TARGETS_FILE_BASENAME@") -endif() +include("${CMAKE_CURRENT_LIST_DIR}/@MAGIC_ENUM_TARGETS_FILE_BASENAME@") check_required_components(magic_enum) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ea8053e4d..420880c4d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -10,12 +10,12 @@ elseif(MSVC) endif() endif() -function(magic_enum_add_example target) +function(magic_enum_add_example target library standard) add_executable(${target} ${target}.cpp) set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) - target_compile_features(${target} PRIVATE cxx_std_17) + target_compile_features(${target} PRIVATE cxx_std_${standard}) target_compile_options(${target} PRIVATE ${MAGIC_ENUM_EXAMPLE_COMPILE_OPTIONS}) - target_link_libraries(${target} PRIVATE magic_enum::magic_enum) + target_link_libraries(${target} PRIVATE ${library}) endfunction() set(MAGIC_ENUM_EXAMPLES @@ -33,13 +33,15 @@ if(MAGIC_ENUM_OPT_ENABLE_NONASCII) endif() foreach(MAGIC_ENUM_EXAMPLE IN LISTS MAGIC_ENUM_EXAMPLES) - magic_enum_add_example(${MAGIC_ENUM_EXAMPLE}) + magic_enum_add_example(${MAGIC_ENUM_EXAMPLE} magic_enum::magic_enum 17) endforeach() if(MAGIC_ENUM_USE_MODULES) - magic_enum_add_example(example_module_usage) - target_link_libraries(example_module_usage PRIVATE magic_enum::magic_enum_module) - target_compile_features(example_module_usage PRIVATE cxx_std_20) + magic_enum_add_example( + example_module_usage + magic_enum::magic_enum_module + ${MAGIC_ENUM_MODULE_CXX_STANDARD} + ) set_target_properties(example_module_usage PROPERTIES CXX_SCAN_FOR_MODULES ON) if(MAGIC_ENUM_MODULE_IMPORT_STD) set_property(TARGET example_module_usage PROPERTY CXX_MODULE_STD ON) diff --git a/module/magic_enum.cppm b/module/magic_enum.cppm index e098e234d..d13484f12 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -28,8 +28,7 @@ module; #else // MAGIC_ENUM_USE_STD_MODULE -// MAGIC_ENUM_CONFIG_FILE may include STL headers. Keep them in the global module -// fragment to avoid conflicting declarations with import std. +// MAGIC_ENUM_CONFIG_FILE may include STL headers. Keep them in the global module fragment to avoid conflicting declarations with import std. # ifdef MAGIC_ENUM_CONFIG_FILE # include MAGIC_ENUM_CONFIG_FILE # endif // MAGIC_ENUM_CONFIG_FILE @@ -81,9 +80,7 @@ extern "C++" { # pragma warning(disable : 5244) # endif -// Keep standard-library specializations attached to the named module. Exporting -// the primary template does not make specializations from the global module -// fragment reachable to importers. +// Keep standard-library specializations attached to the named module. Exporting the primary template does not make specializations from the global module fragment reachable to importers. # ifndef MAGIC_ENUM_USING_ALIAS_STRING # include # endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cf58231e8..641dbe298 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -46,12 +46,21 @@ foreach(MAGIC_ENUM_TEST_STANDARD IN LISTS MAGIC_ENUM_TEST_STANDARDS) ) endforeach() -# Clang 17 with libstdc++ 14 has a known bug in C++23 mode (tuple/get incompatibility). +# Clang 17 with libstdc++ 14 has a known tuple/get incompatibility in C++23 and C++26 modes. if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "17.0" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18.0") set(MAGIC_ENUM_HAS_CPP23_FLAG FALSE) - message(STATUS "Skipping C++23 tests for Clang 17 due to libstdc++ 14 incompatibility") + set(MAGIC_ENUM_HAS_CPP26_FLAG FALSE) + message(STATUS "Skipping C++23 and C++26 tests for Clang 17 due to libstdc++ 14 incompatibility") +endif() + +# GCC 14 and 15 fail valid enum flags parsing tests in C++26 mode. +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND + CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0" AND + CMAKE_CXX_COMPILER_VERSION VERSION_LESS "16.0") + set(MAGIC_ENUM_HAS_CPP26_FLAG FALSE) + message(STATUS "Skipping C++26 tests for GCC 14 and 15") endif() if(MAGIC_ENUM_USE_MODULES AND NOT MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION) @@ -93,7 +102,12 @@ function(magic_enum_make_module_test src target module_target) else() target_link_libraries(${target} PRIVATE ${module_target}) endif() - set_target_properties(${target} PROPERTIES CXX_SCAN_FOR_MODULES ON) + set_target_properties( + ${target} + PROPERTIES + CXX_EXTENSIONS OFF + CXX_SCAN_FOR_MODULES ON + ) add_test(NAME ${target} COMMAND ${target}) endfunction() @@ -163,7 +177,7 @@ if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND set(MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS "-DCMAKE_BUILD_TYPE=$" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}" + "-DCMAKE_CXX_EXTENSIONS=OFF" "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" "-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}" @@ -171,7 +185,7 @@ if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND "-DMAGIC_ENUM_EXPECT_IMPORT_STD=${MAGIC_ENUM_MODULE_IMPORT_STD}" "-DMAGIC_ENUM_EXPECT_FMT=${MAGIC_ENUM_MODULE_WITH_FMT}" ) - if(MSVC) + if(WIN32) find_program(MAGIC_ENUM_RC_COMPILER rc REQUIRED) find_program(MAGIC_ENUM_MT mt REQUIRED) list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS diff --git a/test/installed_module/CMakeLists.txt b/test/installed_module/CMakeLists.txt index e07454760..f4df69669 100644 --- a/test/installed_module/CMakeLists.txt +++ b/test/installed_module/CMakeLists.txt @@ -6,7 +6,12 @@ find_package(magic_enum CONFIG REQUIRED) add_executable(magic_enum_installed_module_test main.cpp) target_link_libraries(magic_enum_installed_module_test PRIVATE magic_enum::magic_enum_module) -set_property(TARGET magic_enum_installed_module_test PROPERTY CXX_SCAN_FOR_MODULES ON) +set_target_properties( + magic_enum_installed_module_test + PROPERTIES + CXX_EXTENSIONS OFF + CXX_SCAN_FOR_MODULES ON +) if(MAGIC_ENUM_EXPECT_IMPORT_STD) set_property(TARGET magic_enum_installed_module_test PROPERTY CXX_MODULE_STD ON) diff --git a/test_installed_version.bash b/test_installed_version.bash index 168e615a1..8c3705e45 100644 --- a/test_installed_version.bash +++ b/test_installed_version.bash @@ -4,7 +4,9 @@ set -euo pipefail repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" build_root="${repo_root}/build-installed-version" source_build_dir="${build_root}/source" +configured_install_dir="${build_root}/configured-install" install_dir="${build_root}/install" +consumer_prefix="${build_root}/relocated-install" cmake_consumer_build_dir="${build_root}/cmake-consumer" pkgconfig_consumer_build_dir="${build_root}/pkgconfig-consumer" @@ -12,7 +14,7 @@ test_installed_consumer() { local build_dir="$1" shift - CMAKE_PREFIX_PATH="${install_dir}" cmake -S "${repo_root}" -B "${build_dir}" \ + CMAKE_PREFIX_PATH="${consumer_prefix}" cmake -S "${repo_root}" -B "${build_dir}" \ -DCMAKE_BUILD_TYPE=Debug \ "$@" cmake --build "${build_dir}" --parallel @@ -23,7 +25,7 @@ cmake -E remove_directory "${build_root}" cmake -S "${repo_root}" -B "${source_build_dir}" \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="${install_dir}" \ + -DCMAKE_INSTALL_PREFIX="${configured_install_dir}" \ -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF \ -DMAGIC_ENUM_OPT_BUILD_TESTS=OFF \ -DMAGIC_ENUM_OPT_INSTALL=ON \ @@ -31,7 +33,8 @@ cmake -S "${repo_root}" -B "${source_build_dir}" \ cmake --build "${source_build_dir}" \ --target magic_enum_verify_interface_header_sets \ --parallel -cmake --install "${source_build_dir}" +cmake --install "${source_build_dir}" --prefix "${install_dir}" +cmake -E copy_directory "${install_dir}" "${consumer_prefix}" test_installed_consumer "${cmake_consumer_build_dir}" \ -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=ON From e8c7f438af92610b71b15dd8b0eef66a5eb3dfe2 Mon Sep 17 00:00:00 2001 From: neargye Date: Mon, 3 Aug 2026 15:56:44 +0400 Subject: [PATCH 03/11] wip --- CMakeLists.txt | 6 ------ README.md | 6 +++--- module/magic_enum.cppm | 20 -------------------- test/CMakeLists.txt | 4 ++++ test/installed_module/main.cpp | 16 ++++++++++------ test/test_module.cpp | 21 ++++++++++++++++++++- 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a6f2588a..76f30567a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,6 @@ if(MAGIC_ENUM_MODULE_IMPORT_STD AND MAGIC_ENUM_USE_MODULES AND NOT CMAKE_CXX_STA message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires an explicit CMAKE_CXX_STANDARD") endif() -if(MAGIC_ENUM_USE_MODULES) - if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) - endif() -endif() - project( magic_enum VERSION "0.9.8" diff --git a/README.md b/README.md index cf2f1c580..95a497bd3 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ If you like this project, please consider donating to one of the funds that help ```cmake find_package(magic_enum CONFIG REQUIRED) target_link_libraries(your_executable PRIVATE magic_enum::magic_enum_module) - set_target_properties(your_executable PROPERTIES CXX_EXTENSIONS OFF) + set_target_properties(your_executable PROPERTIES CXX_EXTENSIONS OFF CXX_SCAN_FOR_MODULES ON) ``` Import the module: @@ -381,13 +381,13 @@ If you like this project, please consider donating to one of the funds that help auto name = magic_enum::enum_name(Color::RED); // "RED" ``` - Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. + Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. Module configuration is fixed when the module target is built. Optional settings: - `{fmt}` support is enabled automatically when the `fmt::fmt` target already exists. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable experimental `import std` support. This requires a compatible CMake toolchain. -## Compiler compatibility +## Header-only compiler compatibility * Clang/LLVM >= 5 * MSVC++ >= 14.11 / Visual Studio >= 2017 diff --git a/module/magic_enum.cppm b/module/magic_enum.cppm index d13484f12..0220d72fc 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -199,26 +199,6 @@ namespace detail { #endif } -#if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L -export namespace std { - using std::partial_ordering; - using std::strong_ordering; - using std::weak_ordering; -} -#endif - -#ifndef MAGIC_ENUM_USING_ALIAS_STRING -export namespace std { - using std::hash; -} -#endif - -#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L -export namespace std { - using std::formatter; -} -#endif - #ifdef MAGIC_ENUM_MODULE_WITH_FMT export namespace fmt { using fmt::formatter; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 641dbe298..2ee21ee1b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -108,6 +108,10 @@ function(magic_enum_make_module_test src target module_target) CXX_EXTENSIONS OFF CXX_SCAN_FOR_MODULES ON ) + if(MAGIC_ENUM_MODULE_IMPORT_STD) + target_compile_definitions(${target} PRIVATE MAGIC_ENUM_TEST_IMPORT_STD) + set_property(TARGET ${target} PROPERTY CXX_MODULE_STD ON) + endif() add_test(NAME ${target} COMMAND ${target}) endfunction() diff --git a/test/installed_module/main.cpp b/test/installed_module/main.cpp index 34f65627a..1a3c13e97 100644 --- a/test/installed_module/main.cpp +++ b/test/installed_module/main.cpp @@ -3,16 +3,20 @@ #include -#ifdef MAGIC_ENUM_TEST_IMPORT_STD -import std; -#elif defined(__cpp_lib_format) && __cpp_lib_format >= 201907L -# include -#endif - +// Include fmt before import std to avoid duplicate standard-library declarations with GCC modules. #ifdef MAGIC_ENUM_TEST_FMT # include #endif +#ifdef MAGIC_ENUM_TEST_IMPORT_STD +import std; +#else +# include +# if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +# include +# endif +#endif + import magic_enum; enum class Color { RED, GREEN, BLUE }; diff --git a/test/test_module.cpp b/test/test_module.cpp index 3494b5b48..08dbbce3f 100644 --- a/test/test_module.cpp +++ b/test/test_module.cpp @@ -20,6 +20,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +#include + +#ifdef MAGIC_ENUM_TEST_IMPORT_STD +import std; +#else +# include +# if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L +# include +# endif +#endif + import magic_enum; enum class Color { RED, GREEN, BLUE }; @@ -173,5 +184,13 @@ constexpr magic_enum::containers::set color_set {Color::RE static_assert(color_set.contains(Color::BLUE)); int main() { - return std::hash{}(color_bits) == std::hash{}(5ULL) ? 0 : 1; + if (std::hash{}(color_bits) != std::hash{}(5ULL)) { + return 1; + } +#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L + if (std::format("{}", Color::GREEN) != "GREEN") { + return 2; + } +#endif + return 0; } From 7a2244ea688f7e11dedcb9b2bd8e94057db519e6 Mon Sep 17 00:00:00 2001 From: neargye Date: Mon, 3 Aug 2026 18:40:29 +0400 Subject: [PATCH 04/11] wip --- .github/workflows/install.yaml | 20 ++++++++++++++++++-- test/CMakeLists.txt | 22 +++++++++++++++++----- test/test_module.cpp | 4 ++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 75c7a6873..31ebe42c8 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -33,14 +33,30 @@ jobs: steps: - uses: actions/checkout@v7 + - name: Checkout fmt + uses: actions/checkout@v7 + with: + repository: fmtlib/fmt + ref: master + path: fmt-src + - name: Install toolchain run: | - brew install gcc ninja fmt + brew install gcc ninja python3 -m pip install cmake==4.4.0 --break-system-packages + cmake -S fmt-src -B build-fmt -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER="${CXX}" \ + -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/fmt" \ + -DBUILD_SHARED_LIBS=OFF \ + -DFMT_DOC=OFF \ + -DFMT_TEST=OFF + cmake --build build-fmt --parallel + cmake --install build-fmt - name: Build and test installed module package run: | - fmt_prefix="$(brew --prefix fmt)" + fmt_prefix="${RUNNER_TEMP}/fmt" cmake -S . -B build-module -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_STANDARD=20 \ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2ee21ee1b..4e1efb204 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -132,6 +132,9 @@ function(magic_enum_add_module_tests suffix) test_module-${suffix} magic_enum_module_test-${suffix} ) + if(MAGIC_ENUM_MODULE_WITH_FMT) + target_compile_definitions(test_module-${suffix} PRIVATE MAGIC_ENUM_TEST_FMT) + endif() # Installed packages expose one module with the configuration fixed at build # time. Alias and wchar coverage use dedicated local module targets instead. @@ -163,11 +166,12 @@ foreach(MAGIC_ENUM_TEST_STANDARD IN LISTS MAGIC_ENUM_TEST_STANDARDS) magic_enum_get_standard_flag(${MAGIC_ENUM_TEST_STANDARD} MAGIC_ENUM_TEST_STANDARD_FLAG) set(MAGIC_ENUM_TEST_SUFFIX cpp${MAGIC_ENUM_TEST_STANDARD}) magic_enum_add_tests(${MAGIC_ENUM_TEST_SUFFIX} ${MAGIC_ENUM_TEST_STANDARD_FLAG}) - if(MAGIC_ENUM_USE_MODULES) - magic_enum_add_module_tests(${MAGIC_ENUM_TEST_SUFFIX}) - endif() endforeach() +if(MAGIC_ENUM_USE_MODULES) + magic_enum_add_module_tests(cpp${MAGIC_ENUM_MODULE_CXX_STANDARD}) +endif() + if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND NOT MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION AND NOT CMAKE_SKIP_INSTALL_RULES AND @@ -183,12 +187,20 @@ if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_EXTENSIONS=OFF" "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" - "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" - "-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}" "-DCMAKE_PREFIX_PATH=${MAGIC_ENUM_INSTALLED_CONSUMER_PREFIX_PATH}" "-DMAGIC_ENUM_EXPECT_IMPORT_STD=${MAGIC_ENUM_MODULE_IMPORT_STD}" "-DMAGIC_ENUM_EXPECT_FMT=${MAGIC_ENUM_MODULE_WITH_FMT}" ) + if(CMAKE_CXX_STANDARD) + list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + ) + if(DEFINED CMAKE_CXX_STANDARD_REQUIRED) + list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS + "-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}" + ) + endif() + endif() if(WIN32) find_program(MAGIC_ENUM_RC_COMPILER rc REQUIRED) find_program(MAGIC_ENUM_MT mt REQUIRED) diff --git a/test/test_module.cpp b/test/test_module.cpp index 08dbbce3f..7a40c0f31 100644 --- a/test/test_module.cpp +++ b/test/test_module.cpp @@ -132,6 +132,10 @@ using magic_enum::iostream_operators::operator>>; using magic_enum::istream_operators::operator>>; using magic_enum::ostream_operators::operator<<; +#ifdef MAGIC_ENUM_TEST_FMT +using fmt::formatter; +#endif + } // namespace module_api static_assert(magic_enum::optional{42}.value() == 42); From c28e8a07c94f17e25d6366cdc4e544f2e2c552b8 Mon Sep 17 00:00:00 2001 From: neargye Date: Mon, 3 Aug 2026 18:56:02 +0400 Subject: [PATCH 05/11] wip --- .github/workflows/install.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 31ebe42c8..8298145af 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -37,7 +37,6 @@ jobs: uses: actions/checkout@v7 with: repository: fmtlib/fmt - ref: master path: fmt-src - name: Install toolchain From 28397c5b62bb0487f913f7c4df151f30f75037bb Mon Sep 17 00:00:00 2001 From: neargye Date: Thu, 6 Aug 2026 19:34:47 +0400 Subject: [PATCH 06/11] wip --- .github/workflows/install.yaml | 2 +- .github/workflows/ubuntu.yml | 2 +- CMakeLists.txt | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index fd3744ed1..d9af4033f 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -126,6 +126,6 @@ jobs: -DMAGIC_ENUM_USE_MODULES=ON ` -DMAGIC_ENUM_OPT_BUILD_TESTS=ON ` -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=ON - cmake --build build-module --parallel --target magic_enum_module example_module_usage test_module-cpp20 test_module_aliases-cpp20 test_module_wchar_t-cpp20 + cmake --build build-module --parallel cmake --build build-module --parallel --target magic_enum_verify_interface_header_sets ctest --test-dir build-module --output-on-failure --no-tests=error -R '^(test_module(_aliases|_wchar_t)?-cpp20|installed-module-consumer)$' diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 41a3132cc..670538e3c 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -71,7 +71,7 @@ jobs: sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y sudo apt update packages=("${{ matrix.compiler.cxx }}") - if [[ "${CC}" == "gcc-13" || "${{ matrix.compiler.import_std }}" == "TRUE" ]]; then + if [[ "${{ matrix.compiler.import_std }}" == "TRUE" ]]; then packages+=(libfmt-dev) fi sudo apt install -y "${packages[@]}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 76f30567a..64e0df965 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ option(MAGIC_ENUM_OPT_BUILD_EXAMPLES "Build magic_enum examples" ${PROJECT_IS_TO option(MAGIC_ENUM_OPT_BUILD_TESTS "Build and perform magic_enum tests" ${PROJECT_IS_TOP_LEVEL}) option(MAGIC_ENUM_OPT_INSTALL "Generate and install magic_enum target" ${PROJECT_IS_TOP_LEVEL}) set(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT OFF) -if(MAGIC_ENUM_OPT_BUILD_TESTS AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") +if(PROJECT_IS_TOP_LEVEL AND CMAKE_VERSION VERSION_GREATER_EQUAL "3.24") set(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT ON) endif() option( @@ -35,6 +35,7 @@ option( "Verify that public magic_enum headers can be included independently" ${MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT} ) +unset(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT) option(MAGIC_ENUM_USE_MODULES "Build the magic_enum C++20 module target" OFF) option(MAGIC_ENUM_MODULE_IMPORT_STD "Build the module target with import std" OFF) set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT OFF) @@ -117,13 +118,16 @@ function(magic_enum_make_module_library name) target_link_libraries(${name} PUBLIC ${PROJECT_NAME}) target_compile_features(${name} PUBLIC cxx_std_${MAGIC_ENUM_MODULE_CXX_STANDARD}) if(ARGN) + # Optional private definitions build test variants with custom magic_enum configurations. target_compile_definitions(${name} PRIVATE ${ARGN}) endif() + # Module-only definitions stay private; CMake exports them for installed module sources without propagating them to importers. if(MAGIC_ENUM_MODULE_WITH_FMT) if(NOT TARGET fmt::fmt) find_package(fmt CONFIG REQUIRED) endif() + # fmt::fmt-module is not interchangeable here because magic_enum defines its fmt formatter from headers within this named module. target_link_libraries(${name} PUBLIC fmt::fmt) target_compile_definitions(${name} PRIVATE MAGIC_ENUM_MODULE_WITH_FMT) endif() From d8daab593e9068ad5dc91064ffccfa5d7702a1af Mon Sep 17 00:00:00 2001 From: neargye Date: Thu, 6 Aug 2026 20:12:43 +0400 Subject: [PATCH 07/11] wip --- .github/workflows/install.yaml | 3 ++ CMakeLists.txt | 59 ++++++++++++++++++++---- README.md | 2 +- include/magic_enum/magic_enum_format.hpp | 2 +- module/magic_enum.cppm | 8 +++- test/CMakeLists.txt | 4 ++ test/installed_module/CMakeLists.txt | 3 ++ test/installed_module/main.cpp | 2 +- test/test_module.cpp | 9 ++++ 9 files changed, 78 insertions(+), 14 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index d9af4033f..6328976b2 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -74,9 +74,12 @@ jobs: cmake -S fmt-src -B build-fmt -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_COMPILER="${CXX}" \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/fmt" \ -DBUILD_SHARED_LIBS=OFF \ -DFMT_DOC=OFF \ + -DFMT_MODULE=ON \ -DFMT_TEST=OFF cmake --build build-fmt --parallel cmake --install build-fmt diff --git a/CMakeLists.txt b/CMakeLists.txt index 64e0df965..99eee0954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ unset(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT) option(MAGIC_ENUM_USE_MODULES "Build the magic_enum C++20 module target" OFF) option(MAGIC_ENUM_MODULE_IMPORT_STD "Build the module target with import std" OFF) set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT OFF) -if(MAGIC_ENUM_USE_MODULES AND TARGET fmt::fmt) +if(MAGIC_ENUM_USE_MODULES AND (TARGET fmt::fmt-module OR TARGET fmt::fmt)) set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT ON) endif() option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt integration" ${MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT}) @@ -106,6 +106,48 @@ if(MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS) set_property(TARGET ${PROJECT_NAME} PROPERTY VERIFY_INTERFACE_HEADER_SETS ON) endif() +set(MAGIC_ENUM_MODULE_FMT_TARGET) +set(MAGIC_ENUM_MODULE_USES_FMT_MODULE OFF) +if(MAGIC_ENUM_MODULE_WITH_FMT) + if(NOT TARGET fmt::fmt-module AND NOT TARGET fmt::fmt) + find_package(fmt CONFIG REQUIRED) + endif() + + # Named modules must be built with the same C++ standard as their importers. + if(TARGET fmt::fmt-module) + get_target_property(MAGIC_ENUM_FMT_MODULE_IMPORTED fmt::fmt-module IMPORTED) + if(MAGIC_ENUM_FMT_MODULE_IMPORTED) + get_target_property(MAGIC_ENUM_FMT_MODULE_FEATURES fmt::fmt-module IMPORTED_CXX_MODULES_COMPILE_FEATURES) + set(MAGIC_ENUM_FMT_MODULE_STANDARD 0) + else() + get_target_property(MAGIC_ENUM_FMT_MODULE_FEATURES fmt::fmt-module COMPILE_FEATURES) + get_target_property(MAGIC_ENUM_FMT_MODULE_STANDARD fmt::fmt-module CXX_STANDARD) + endif() + if(MAGIC_ENUM_FMT_MODULE_STANDARD MATCHES "-NOTFOUND$") + set(MAGIC_ENUM_FMT_MODULE_STANDARD 0) + endif() + foreach(MAGIC_ENUM_FMT_MODULE_FEATURE IN LISTS MAGIC_ENUM_FMT_MODULE_FEATURES) + if(MAGIC_ENUM_FMT_MODULE_FEATURE MATCHES "^cxx_std_([0-9]+)$" AND + (NOT MAGIC_ENUM_FMT_MODULE_STANDARD OR CMAKE_MATCH_1 GREATER MAGIC_ENUM_FMT_MODULE_STANDARD)) + set(MAGIC_ENUM_FMT_MODULE_STANDARD ${CMAKE_MATCH_1}) + endif() + endforeach() + if(MAGIC_ENUM_FMT_MODULE_STANDARD EQUAL MAGIC_ENUM_MODULE_CXX_STANDARD) + set(MAGIC_ENUM_MODULE_FMT_TARGET fmt::fmt-module) + set(MAGIC_ENUM_MODULE_USES_FMT_MODULE ON) + endif() + endif() + + if(NOT MAGIC_ENUM_MODULE_FMT_TARGET AND TARGET fmt::fmt) + set(MAGIC_ENUM_MODULE_FMT_TARGET fmt::fmt) + elseif(NOT MAGIC_ENUM_MODULE_FMT_TARGET AND TARGET fmt::fmt-module) + message(FATAL_ERROR "fmt::fmt-module is not compatible with magic_enum_module's C++${MAGIC_ENUM_MODULE_CXX_STANDARD} standard, and fmt::fmt is unavailable") + elseif(NOT MAGIC_ENUM_MODULE_FMT_TARGET) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_WITH_FMT requires fmt::fmt-module or fmt::fmt") + endif() + message(STATUS "magic_enum module: using ${MAGIC_ENUM_MODULE_FMT_TARGET}") +endif() + function(magic_enum_make_module_library name) add_library(${name} STATIC) set_target_properties(${name} PROPERTIES CXX_EXTENSIONS OFF) @@ -123,13 +165,12 @@ function(magic_enum_make_module_library name) endif() # Module-only definitions stay private; CMake exports them for installed module sources without propagating them to importers. - if(MAGIC_ENUM_MODULE_WITH_FMT) - if(NOT TARGET fmt::fmt) - find_package(fmt CONFIG REQUIRED) - endif() - # fmt::fmt-module is not interchangeable here because magic_enum defines its fmt formatter from headers within this named module. - target_link_libraries(${name} PUBLIC fmt::fmt) + if(MAGIC_ENUM_MODULE_FMT_TARGET) + target_link_libraries(${name} PUBLIC ${MAGIC_ENUM_MODULE_FMT_TARGET}) target_compile_definitions(${name} PRIVATE MAGIC_ENUM_MODULE_WITH_FMT) + if(MAGIC_ENUM_MODULE_USES_FMT_MODULE) + target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_FMT_MODULE) + endif() endif() if(MAGIC_ENUM_MODULE_IMPORT_STD) @@ -237,9 +278,9 @@ if(MAGIC_ENUM_OPT_INSTALL) endif() set(MAGIC_ENUM_FIND_DEPENDENCIES) - if(MAGIC_ENUM_MODULE_WITH_FMT) + if(MAGIC_ENUM_MODULE_FMT_TARGET) string(APPEND MAGIC_ENUM_FIND_DEPENDENCIES - "if(NOT TARGET fmt::fmt)\n find_dependency(fmt CONFIG)\nendif()\n" + "if(NOT TARGET ${MAGIC_ENUM_MODULE_FMT_TARGET})\n find_dependency(fmt CONFIG)\nendif()\n" ) endif() diff --git a/README.md b/README.md index 07140b31b..ad409f0f3 100644 --- a/README.md +++ b/README.md @@ -362,7 +362,7 @@ Header-only C++17 library provides static reflection for enums, work with any en Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. The pkg-config package supports only the header-only target. Optional settings: - - `{fmt}` support is enabled automatically when the `fmt::fmt` target already exists. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. + - `{fmt}` support is enabled automatically when `fmt::fmt-module` or `fmt::fmt` already exists. The module target is preferred when it uses the same C++ standard. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable experimental `import std` support. This requires a compatible CMake toolchain. ## Header-only compiler compatibility diff --git a/include/magic_enum/magic_enum_format.hpp b/include/magic_enum/magic_enum_format.hpp index 294fd654d..780e20fb0 100644 --- a/include/magic_enum/magic_enum_format.hpp +++ b/include/magic_enum/magic_enum_format.hpp @@ -75,7 +75,7 @@ struct std::formatter>, char> #endif -#if defined(FMT_VERSION) +#if defined(FMT_VERSION) || defined(MAGIC_ENUM_USE_FMT_MODULE) template struct fmt::formatter>, char>> : fmt::formatter { diff --git a/module/magic_enum.cppm b/module/magic_enum.cppm index 0220d72fc..7d316e045 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -1,7 +1,7 @@ module; #include -#ifdef MAGIC_ENUM_MODULE_WITH_FMT +#if defined(MAGIC_ENUM_MODULE_WITH_FMT) && !defined(MAGIC_ENUM_USE_FMT_MODULE) # include #endif @@ -41,6 +41,10 @@ module; export module magic_enum; +#ifdef MAGIC_ENUM_USE_FMT_MODULE +export import fmt; +#endif + #ifdef MAGIC_ENUM_USE_STD_MODULE import std; @@ -199,7 +203,7 @@ namespace detail { #endif } -#ifdef MAGIC_ENUM_MODULE_WITH_FMT +#if defined(MAGIC_ENUM_MODULE_WITH_FMT) && !defined(MAGIC_ENUM_USE_FMT_MODULE) export namespace fmt { using fmt::formatter; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f65c2c9ff..6ac1d549a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -141,6 +141,9 @@ function(magic_enum_add_module_tests suffix) ) if(MAGIC_ENUM_MODULE_WITH_FMT) target_compile_definitions(test_module-${suffix} PRIVATE MAGIC_ENUM_TEST_FMT) + if(MAGIC_ENUM_MODULE_USES_FMT_MODULE) + target_compile_definitions(test_module-${suffix} PRIVATE MAGIC_ENUM_TEST_FMT_MODULE) + endif() endif() # Installed packages expose one module with the configuration fixed at build @@ -198,6 +201,7 @@ if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND "-DCMAKE_PREFIX_PATH=${MAGIC_ENUM_INSTALLED_CONSUMER_PREFIX_PATH}" "-DMAGIC_ENUM_EXPECT_IMPORT_STD=${MAGIC_ENUM_MODULE_IMPORT_STD}" "-DMAGIC_ENUM_EXPECT_FMT=${MAGIC_ENUM_MODULE_WITH_FMT}" + "-DMAGIC_ENUM_EXPECT_FMT_MODULE=${MAGIC_ENUM_MODULE_USES_FMT_MODULE}" ) if(CMAKE_CXX_STANDARD) list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS diff --git a/test/installed_module/CMakeLists.txt b/test/installed_module/CMakeLists.txt index f4df69669..58bfd632b 100644 --- a/test/installed_module/CMakeLists.txt +++ b/test/installed_module/CMakeLists.txt @@ -22,6 +22,9 @@ endif() if(MAGIC_ENUM_EXPECT_FMT) target_compile_definitions(magic_enum_installed_module_test PRIVATE MAGIC_ENUM_TEST_FMT) + if(MAGIC_ENUM_EXPECT_FMT_MODULE) + target_compile_definitions(magic_enum_installed_module_test PRIVATE MAGIC_ENUM_TEST_FMT_MODULE) + endif() endif() enable_testing() diff --git a/test/installed_module/main.cpp b/test/installed_module/main.cpp index 1a3c13e97..9b1948b77 100644 --- a/test/installed_module/main.cpp +++ b/test/installed_module/main.cpp @@ -4,7 +4,7 @@ #include // Include fmt before import std to avoid duplicate standard-library declarations with GCC modules. -#ifdef MAGIC_ENUM_TEST_FMT +#if defined(MAGIC_ENUM_TEST_FMT) && !defined(MAGIC_ENUM_TEST_FMT_MODULE) # include #endif diff --git a/test/test_module.cpp b/test/test_module.cpp index 718cfa196..db4fcde82 100644 --- a/test/test_module.cpp +++ b/test/test_module.cpp @@ -4,6 +4,10 @@ #include +#if defined(MAGIC_ENUM_TEST_FMT) && !defined(MAGIC_ENUM_TEST_FMT_MODULE) +# include +#endif + #ifdef MAGIC_ENUM_TEST_IMPORT_STD import std; #else @@ -177,6 +181,11 @@ int main() { if (std::format("{}", Color::GREEN) != "GREEN") { return 2; } +#endif +#ifdef MAGIC_ENUM_TEST_FMT + if (fmt::format("{}", Color::BLUE) != "BLUE") { + return 3; + } #endif return 0; } From 2bfd2ca17082863dee3a71f2b3ab55f7fd5bc971 Mon Sep 17 00:00:00 2001 From: neargye Date: Fri, 7 Aug 2026 11:24:14 +0400 Subject: [PATCH 08/11] fix cmake version --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99eee0954..521f70e6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,8 +174,8 @@ function(magic_enum_make_module_library name) endif() if(MAGIC_ENUM_MODULE_IMPORT_STD) - if(CMAKE_VERSION VERSION_LESS "3.30") - message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires CMake >= 3.30") + if(CMAKE_VERSION VERSION_LESS "3.31") + message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires CMake >= 3.31") endif() if(CMAKE_CXX_STANDARD LESS 23) message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires CMAKE_CXX_STANDARD >= 23") @@ -189,7 +189,7 @@ function(magic_enum_make_module_library name) message(STATUS "${name}: using import std") set_property(TARGET ${name} PROPERTY CXX_MODULE_STD ON) target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_STD_MODULE) - elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30") + elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.31") set_property(TARGET ${name} PROPERTY CXX_MODULE_STD OFF) endif() endfunction() From dcf91b367ed352fa64259732fff1982c892ed0f4 Mon Sep 17 00:00:00 2001 From: neargye Date: Fri, 7 Aug 2026 11:45:41 +0400 Subject: [PATCH 09/11] remove std + fmt --- .github/workflows/install.yaml | 3 ++- .github/workflows/ubuntu.yml | 4 +--- CMakeLists.txt | 10 +++++++--- README.md | 4 ++-- module/magic_enum.cppm | 4 ++-- test/installed_module/main.cpp | 1 - 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 6328976b2..4affde50d 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -67,7 +67,7 @@ jobs: repository: fmtlib/fmt path: fmt-src - - name: Install toolchain + - name: Install toolchain and fmt run: | brew install gcc ninja python3 -m pip install cmake==4.4.0 --break-system-packages @@ -76,6 +76,7 @@ jobs: -DCMAKE_CXX_COMPILER="${CXX}" \ -DCMAKE_CXX_STANDARD=20 \ -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_CXX_SCAN_FOR_MODULES=OFF \ -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/fmt" \ -DBUILD_SHARED_LIBS=OFF \ -DFMT_DOC=OFF \ diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 670538e3c..33ea6bbef 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -117,11 +117,9 @@ jobs: if: ${{ matrix.compiler.import_std == 'TRUE' }} run: | cxx_standard=23 - fmt_integration=OFF stdlib_options=(-DCMAKE_CXX_FLAGS:STRING=-stdlib=libc++) if [[ "${{ matrix.compiler.cc }}" == gcc-* ]]; then cxx_standard=26 - fmt_integration=ON stdlib_options=(-DCMAKE_CXX_STDLIB_MODULES_JSON="$("${CXX}" -print-file-name=libstdc++.modules.json)") fi @@ -133,7 +131,7 @@ jobs: -DCMAKE_CXX_EXTENSIONS=OFF \ -DMAGIC_ENUM_USE_MODULES:BOOL=TRUE \ -DMAGIC_ENUM_MODULE_IMPORT_STD:BOOL=TRUE \ - -DMAGIC_ENUM_MODULE_WITH_FMT:BOOL="${fmt_integration}" \ + -DMAGIC_ENUM_MODULE_WITH_FMT:BOOL=FALSE \ -GNinja \ "${stdlib_options[@]}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 521f70e6a..f455783ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,13 +39,16 @@ unset(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT) option(MAGIC_ENUM_USE_MODULES "Build the magic_enum C++20 module target" OFF) option(MAGIC_ENUM_MODULE_IMPORT_STD "Build the module target with import std" OFF) set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT OFF) -if(MAGIC_ENUM_USE_MODULES AND (TARGET fmt::fmt-module OR TARGET fmt::fmt)) +if(MAGIC_ENUM_USE_MODULES AND NOT MAGIC_ENUM_MODULE_IMPORT_STD AND (TARGET fmt::fmt-module OR TARGET fmt::fmt)) set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT ON) endif() option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt integration" ${MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT}) unset(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT) option(MAGIC_ENUM_OPT_INSTALL_PACKAGE_XML "Include package.xml when installing" ${MAGIC_ENUM_OPT_INSTALL}) +if(MAGIC_ENUM_MODULE_IMPORT_STD AND MAGIC_ENUM_MODULE_WITH_FMT) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD and MAGIC_ENUM_MODULE_WITH_FMT cannot be enabled together; use std::format with import std") +endif() if(MAGIC_ENUM_MODULE_IMPORT_STD AND NOT MAGIC_ENUM_USE_MODULES) message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires MAGIC_ENUM_USE_MODULES=ON") endif() @@ -159,7 +162,7 @@ function(magic_enum_make_module_library name) ) target_link_libraries(${name} PUBLIC ${PROJECT_NAME}) target_compile_features(${name} PUBLIC cxx_std_${MAGIC_ENUM_MODULE_CXX_STANDARD}) - if(ARGN) + if(ARGC GREATER 1) # Optional private definitions build test variants with custom magic_enum configurations. target_compile_definitions(${name} PRIVATE ${ARGN}) endif() @@ -167,9 +170,10 @@ function(magic_enum_make_module_library name) # Module-only definitions stay private; CMake exports them for installed module sources without propagating them to importers. if(MAGIC_ENUM_MODULE_FMT_TARGET) target_link_libraries(${name} PUBLIC ${MAGIC_ENUM_MODULE_FMT_TARGET}) - target_compile_definitions(${name} PRIVATE MAGIC_ENUM_MODULE_WITH_FMT) if(MAGIC_ENUM_MODULE_USES_FMT_MODULE) target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_FMT_MODULE) + else() + target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_FMT_HEADER) endif() endif() diff --git a/README.md b/README.md index ad409f0f3..c9a8534bd 100644 --- a/README.md +++ b/README.md @@ -362,8 +362,8 @@ Header-only C++17 library provides static reflection for enums, work with any en Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. The pkg-config package supports only the header-only target. Optional settings: - - `{fmt}` support is enabled automatically when `fmt::fmt-module` or `fmt::fmt` already exists. The module target is preferred when it uses the same C++ standard. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. - - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable experimental `import std` support. This requires a compatible CMake toolchain. + - `{fmt}` support is enabled automatically when `fmt::fmt-module` or `fmt::fmt` already exists and `import std` is disabled. The module target is preferred when it uses the same C++ standard. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. + - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable experimental `import std` support. Use `std::format` in this mode. `{fmt}` integration cannot be enabled. This requires a compatible CMake toolchain. ## Header-only compiler compatibility diff --git a/module/magic_enum.cppm b/module/magic_enum.cppm index 7d316e045..dc3cefb3c 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -1,7 +1,7 @@ module; #include -#if defined(MAGIC_ENUM_MODULE_WITH_FMT) && !defined(MAGIC_ENUM_USE_FMT_MODULE) +#ifdef MAGIC_ENUM_USE_FMT_HEADER # include #endif @@ -203,7 +203,7 @@ namespace detail { #endif } -#if defined(MAGIC_ENUM_MODULE_WITH_FMT) && !defined(MAGIC_ENUM_USE_FMT_MODULE) +#ifdef MAGIC_ENUM_USE_FMT_HEADER export namespace fmt { using fmt::formatter; } diff --git a/test/installed_module/main.cpp b/test/installed_module/main.cpp index 9b1948b77..2248e334b 100644 --- a/test/installed_module/main.cpp +++ b/test/installed_module/main.cpp @@ -3,7 +3,6 @@ #include -// Include fmt before import std to avoid duplicate standard-library declarations with GCC modules. #if defined(MAGIC_ENUM_TEST_FMT) && !defined(MAGIC_ENUM_TEST_FMT_MODULE) # include #endif From 377065c03403b29406700c527f678816e44e81dc Mon Sep 17 00:00:00 2001 From: neargye Date: Fri, 7 Aug 2026 12:15:54 +0400 Subject: [PATCH 10/11] clean-up --- .github/workflows/install.yaml | 4 ++-- .github/workflows/ubuntu.yml | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml index 4affde50d..09377c352 100644 --- a/.github/workflows/install.yaml +++ b/.github/workflows/install.yaml @@ -52,7 +52,7 @@ jobs: ctest --test-dir build-installed-pkgconfig --output-on-failure --no-tests=error module-macos-gcc: - name: Module / macOS / GCC 16 / fmt / C++20 + name: Module / macOS / GCC 16 / fmt::fmt / C++20 runs-on: macos-15 env: CC: gcc-16 @@ -80,7 +80,7 @@ jobs: -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/fmt" \ -DBUILD_SHARED_LIBS=OFF \ -DFMT_DOC=OFF \ - -DFMT_MODULE=ON \ + -DFMT_MODULE=OFF \ -DFMT_TEST=OFF cmake --build build-fmt --parallel cmake --install build-fmt diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 33ea6bbef..c127abd75 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,11 +70,7 @@ jobs: run: | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y sudo apt update - packages=("${{ matrix.compiler.cxx }}") - if [[ "${{ matrix.compiler.import_std }}" == "TRUE" ]]; then - packages+=(libfmt-dev) - fi - sudo apt install -y "${packages[@]}" + sudo apt install -y ${{ matrix.compiler.cxx }} - name: Install pinned CMake if: ${{ matrix.compiler.modules == 'TRUE' }} From 36710bc4fd5925779ff955fe4f7c56705752e323 Mon Sep 17 00:00:00 2001 From: neargye Date: Sat, 8 Aug 2026 02:06:29 +0400 Subject: [PATCH 11/11] remove fmt module --- CMakeLists.txt | 65 ++++-------------------- README.md | 4 +- include/magic_enum/magic_enum_format.hpp | 2 +- module/magic_enum.cppm | 4 -- test/CMakeLists.txt | 4 -- test/installed_module/CMakeLists.txt | 3 -- test/installed_module/main.cpp | 2 +- test/test_module.cpp | 2 +- 8 files changed, 16 insertions(+), 70 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f455783ff..a3d93cea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,17 +38,9 @@ option( unset(MAGIC_ENUM_VERIFY_INTERFACE_HEADER_SETS_DEFAULT) option(MAGIC_ENUM_USE_MODULES "Build the magic_enum C++20 module target" OFF) option(MAGIC_ENUM_MODULE_IMPORT_STD "Build the module target with import std" OFF) -set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT OFF) -if(MAGIC_ENUM_USE_MODULES AND NOT MAGIC_ENUM_MODULE_IMPORT_STD AND (TARGET fmt::fmt-module OR TARGET fmt::fmt)) - set(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT ON) -endif() -option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt integration" ${MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT}) -unset(MAGIC_ENUM_MODULE_WITH_FMT_DEFAULT) +option(MAGIC_ENUM_MODULE_WITH_FMT "Build the module target with fmt::fmt integration" OFF) option(MAGIC_ENUM_OPT_INSTALL_PACKAGE_XML "Include package.xml when installing" ${MAGIC_ENUM_OPT_INSTALL}) -if(MAGIC_ENUM_MODULE_IMPORT_STD AND MAGIC_ENUM_MODULE_WITH_FMT) - message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD and MAGIC_ENUM_MODULE_WITH_FMT cannot be enabled together; use std::format with import std") -endif() if(MAGIC_ENUM_MODULE_IMPORT_STD AND NOT MAGIC_ENUM_USE_MODULES) message(FATAL_ERROR "MAGIC_ENUM_MODULE_IMPORT_STD requires MAGIC_ENUM_USE_MODULES=ON") endif() @@ -109,46 +101,14 @@ if(MAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS) set_property(TARGET ${PROJECT_NAME} PROPERTY VERIFY_INTERFACE_HEADER_SETS ON) endif() -set(MAGIC_ENUM_MODULE_FMT_TARGET) -set(MAGIC_ENUM_MODULE_USES_FMT_MODULE OFF) if(MAGIC_ENUM_MODULE_WITH_FMT) - if(NOT TARGET fmt::fmt-module AND NOT TARGET fmt::fmt) + if(NOT TARGET fmt::fmt) find_package(fmt CONFIG REQUIRED) endif() - - # Named modules must be built with the same C++ standard as their importers. - if(TARGET fmt::fmt-module) - get_target_property(MAGIC_ENUM_FMT_MODULE_IMPORTED fmt::fmt-module IMPORTED) - if(MAGIC_ENUM_FMT_MODULE_IMPORTED) - get_target_property(MAGIC_ENUM_FMT_MODULE_FEATURES fmt::fmt-module IMPORTED_CXX_MODULES_COMPILE_FEATURES) - set(MAGIC_ENUM_FMT_MODULE_STANDARD 0) - else() - get_target_property(MAGIC_ENUM_FMT_MODULE_FEATURES fmt::fmt-module COMPILE_FEATURES) - get_target_property(MAGIC_ENUM_FMT_MODULE_STANDARD fmt::fmt-module CXX_STANDARD) - endif() - if(MAGIC_ENUM_FMT_MODULE_STANDARD MATCHES "-NOTFOUND$") - set(MAGIC_ENUM_FMT_MODULE_STANDARD 0) - endif() - foreach(MAGIC_ENUM_FMT_MODULE_FEATURE IN LISTS MAGIC_ENUM_FMT_MODULE_FEATURES) - if(MAGIC_ENUM_FMT_MODULE_FEATURE MATCHES "^cxx_std_([0-9]+)$" AND - (NOT MAGIC_ENUM_FMT_MODULE_STANDARD OR CMAKE_MATCH_1 GREATER MAGIC_ENUM_FMT_MODULE_STANDARD)) - set(MAGIC_ENUM_FMT_MODULE_STANDARD ${CMAKE_MATCH_1}) - endif() - endforeach() - if(MAGIC_ENUM_FMT_MODULE_STANDARD EQUAL MAGIC_ENUM_MODULE_CXX_STANDARD) - set(MAGIC_ENUM_MODULE_FMT_TARGET fmt::fmt-module) - set(MAGIC_ENUM_MODULE_USES_FMT_MODULE ON) - endif() - endif() - - if(NOT MAGIC_ENUM_MODULE_FMT_TARGET AND TARGET fmt::fmt) - set(MAGIC_ENUM_MODULE_FMT_TARGET fmt::fmt) - elseif(NOT MAGIC_ENUM_MODULE_FMT_TARGET AND TARGET fmt::fmt-module) - message(FATAL_ERROR "fmt::fmt-module is not compatible with magic_enum_module's C++${MAGIC_ENUM_MODULE_CXX_STANDARD} standard, and fmt::fmt is unavailable") - elseif(NOT MAGIC_ENUM_MODULE_FMT_TARGET) - message(FATAL_ERROR "MAGIC_ENUM_MODULE_WITH_FMT requires fmt::fmt-module or fmt::fmt") + if(NOT TARGET fmt::fmt) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_WITH_FMT requires fmt::fmt") endif() - message(STATUS "magic_enum module: using ${MAGIC_ENUM_MODULE_FMT_TARGET}") + message(STATUS "magic_enum module: using fmt::fmt") endif() function(magic_enum_make_module_library name) @@ -168,13 +128,10 @@ function(magic_enum_make_module_library name) endif() # Module-only definitions stay private; CMake exports them for installed module sources without propagating them to importers. - if(MAGIC_ENUM_MODULE_FMT_TARGET) - target_link_libraries(${name} PUBLIC ${MAGIC_ENUM_MODULE_FMT_TARGET}) - if(MAGIC_ENUM_MODULE_USES_FMT_MODULE) - target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_FMT_MODULE) - else() - target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_FMT_HEADER) - endif() + if(MAGIC_ENUM_MODULE_WITH_FMT) + # Use fmt::fmt until fmt provides stable installed C++ module support. + target_link_libraries(${name} PUBLIC fmt::fmt) + target_compile_definitions(${name} PRIVATE MAGIC_ENUM_USE_FMT_HEADER) endif() if(MAGIC_ENUM_MODULE_IMPORT_STD) @@ -282,9 +239,9 @@ if(MAGIC_ENUM_OPT_INSTALL) endif() set(MAGIC_ENUM_FIND_DEPENDENCIES) - if(MAGIC_ENUM_MODULE_FMT_TARGET) + if(MAGIC_ENUM_MODULE_WITH_FMT) string(APPEND MAGIC_ENUM_FIND_DEPENDENCIES - "if(NOT TARGET ${MAGIC_ENUM_MODULE_FMT_TARGET})\n find_dependency(fmt CONFIG)\nendif()\n" + "if(NOT TARGET fmt::fmt)\n find_dependency(fmt CONFIG)\nendif()\n" ) endif() diff --git a/README.md b/README.md index c9a8534bd..56b907fe0 100644 --- a/README.md +++ b/README.md @@ -362,8 +362,8 @@ Header-only C++17 library provides static reflection for enums, work with any en Do not use `#include ` and `import magic_enum;` in the same program. Use the same compiler, standard library, and C++ standard when building and consuming an installed module. The pkg-config package supports only the header-only target. Optional settings: - - `{fmt}` support is enabled automatically when `fmt::fmt-module` or `fmt::fmt` already exists and `import std` is disabled. The module target is preferred when it uses the same C++ standard. Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to require `{fmt}`, or `OFF` to disable it. - - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable experimental `import std` support. Use `std::format` in this mode. `{fmt}` integration cannot be enabled. This requires a compatible CMake toolchain. + - Set `MAGIC_ENUM_MODULE_WITH_FMT=ON` to enable `{fmt}` support through `fmt::fmt`. It is disabled by default. The `{fmt}` C++ module is not supported. + - Set `MAGIC_ENUM_MODULE_IMPORT_STD=ON` to enable `import std` support. This requires a compatible CMake toolchain. ## Header-only compiler compatibility diff --git a/include/magic_enum/magic_enum_format.hpp b/include/magic_enum/magic_enum_format.hpp index 780e20fb0..294fd654d 100644 --- a/include/magic_enum/magic_enum_format.hpp +++ b/include/magic_enum/magic_enum_format.hpp @@ -75,7 +75,7 @@ struct std::formatter>, char> #endif -#if defined(FMT_VERSION) || defined(MAGIC_ENUM_USE_FMT_MODULE) +#if defined(FMT_VERSION) template struct fmt::formatter>, char>> : fmt::formatter { diff --git a/module/magic_enum.cppm b/module/magic_enum.cppm index dc3cefb3c..cc501860b 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -41,10 +41,6 @@ module; export module magic_enum; -#ifdef MAGIC_ENUM_USE_FMT_MODULE -export import fmt; -#endif - #ifdef MAGIC_ENUM_USE_STD_MODULE import std; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6ac1d549a..f65c2c9ff 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -141,9 +141,6 @@ function(magic_enum_add_module_tests suffix) ) if(MAGIC_ENUM_MODULE_WITH_FMT) target_compile_definitions(test_module-${suffix} PRIVATE MAGIC_ENUM_TEST_FMT) - if(MAGIC_ENUM_MODULE_USES_FMT_MODULE) - target_compile_definitions(test_module-${suffix} PRIVATE MAGIC_ENUM_TEST_FMT_MODULE) - endif() endif() # Installed packages expose one module with the configuration fixed at build @@ -201,7 +198,6 @@ if(MAGIC_ENUM_USE_MODULES AND MAGIC_ENUM_OPT_INSTALL AND "-DCMAKE_PREFIX_PATH=${MAGIC_ENUM_INSTALLED_CONSUMER_PREFIX_PATH}" "-DMAGIC_ENUM_EXPECT_IMPORT_STD=${MAGIC_ENUM_MODULE_IMPORT_STD}" "-DMAGIC_ENUM_EXPECT_FMT=${MAGIC_ENUM_MODULE_WITH_FMT}" - "-DMAGIC_ENUM_EXPECT_FMT_MODULE=${MAGIC_ENUM_MODULE_USES_FMT_MODULE}" ) if(CMAKE_CXX_STANDARD) list(APPEND MAGIC_ENUM_INSTALLED_CONSUMER_BUILD_OPTIONS diff --git a/test/installed_module/CMakeLists.txt b/test/installed_module/CMakeLists.txt index 58bfd632b..f4df69669 100644 --- a/test/installed_module/CMakeLists.txt +++ b/test/installed_module/CMakeLists.txt @@ -22,9 +22,6 @@ endif() if(MAGIC_ENUM_EXPECT_FMT) target_compile_definitions(magic_enum_installed_module_test PRIVATE MAGIC_ENUM_TEST_FMT) - if(MAGIC_ENUM_EXPECT_FMT_MODULE) - target_compile_definitions(magic_enum_installed_module_test PRIVATE MAGIC_ENUM_TEST_FMT_MODULE) - endif() endif() enable_testing() diff --git a/test/installed_module/main.cpp b/test/installed_module/main.cpp index 2248e334b..323c31265 100644 --- a/test/installed_module/main.cpp +++ b/test/installed_module/main.cpp @@ -3,7 +3,7 @@ #include -#if defined(MAGIC_ENUM_TEST_FMT) && !defined(MAGIC_ENUM_TEST_FMT_MODULE) +#ifdef MAGIC_ENUM_TEST_FMT # include #endif diff --git a/test/test_module.cpp b/test/test_module.cpp index db4fcde82..7e7e2d8f3 100644 --- a/test/test_module.cpp +++ b/test/test_module.cpp @@ -4,7 +4,7 @@ #include -#if defined(MAGIC_ENUM_TEST_FMT) && !defined(MAGIC_ENUM_TEST_FMT_MODULE) +#ifdef MAGIC_ENUM_TEST_FMT # include #endif