diff --git a/.github/workflows/install.yaml b/.github/workflows/install.yaml new file mode 100644 index 000000000..09377c352 --- /dev/null +++ b/.github/workflows/install.yaml @@ -0,0 +1,135 @@ +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: Build and install header-only package + run: | + cmake -S . -B build-install \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="${RUNNER_TEMP}/configured-install" \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=OFF \ + -DMAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS=ON + cmake --build build-install \ + --target magic_enum_verify_interface_header_sets \ + --parallel + cmake --install build-install --prefix "${RUNNER_TEMP}/magic_enum" + + - name: Test installed CMake package + run: | + cmake -S . -B build-installed-cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/magic_enum" \ + -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=ON + cmake --build build-installed-cmake --parallel + ctest --test-dir build-installed-cmake --output-on-failure --no-tests=error + + - name: Test installed pkg-config package + run: | + cmake -S . -B build-installed-pkgconfig \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${RUNNER_TEMP}/magic_enum" \ + -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG=ON + cmake --build build-installed-pkgconfig --parallel + ctest --test-dir build-installed-pkgconfig --output-on-failure --no-tests=error + + module-macos-gcc: + name: Module / macOS / GCC 16 / fmt::fmt / C++20 + runs-on: macos-15 + env: + CC: gcc-16 + CXX: g++-16 + + steps: + - uses: actions/checkout@v7 + + - name: Checkout fmt + uses: actions/checkout@v7 + with: + repository: fmtlib/fmt + path: fmt-src + + - name: Install toolchain and fmt + run: | + 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_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 \ + -DFMT_MODULE=OFF \ + -DFMT_TEST=OFF + cmake --build build-fmt --parallel + cmake --install build-fmt + + - name: Build and test installed module package + run: | + fmt_prefix="${RUNNER_TEMP}/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 + 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/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/ubuntu.yml b/.github/workflows/ubuntu.yml index ccaa22069..c127abd75 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,13 +72,14 @@ jobs: sudo apt update sudo apt install -y ${{ matrix.compiler.cxx }} - - name: Install fmt - if: ${{ matrix.compiler.cc == 'gcc-13' }} - run: sudo apt install -y libfmt-dev - - - 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 cmake==4.4.0 --break-system-packages + else + pip install cmake==4.3.4 --break-system-packages + fi - name: Configure Release run: | @@ -90,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: | @@ -104,27 +107,28 @@ 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=f35a9ac6-8463-4d38-8eec-5d6008153e7d \ - -DMAGIC_ENUM_USE_MODULES:BOOL=TRUE \ - -GNinja \ - -DCMAKE_CXX_MODULE_STD=ON \ - -DCMAKE_CXX_STANDARD=23 \ + -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 \ + -DMAGIC_ENUM_MODULE_WITH_FMT:BOOL=FALSE \ + -GNinja \ "${stdlib_options[@]}" - name: Build with `import std;` @@ -133,7 +137,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 deleted file mode 100644 index 9e2ec35f7..000000000 --- a/.github/workflows/ubuntu_test_installed_version.yml +++ /dev/null @@ -1,94 +0,0 @@ -name: ubuntu_test_installed_version.yml - - -on: - push: - branches: - - master - - main - pull_request: - branches: - - master - - main - -env: - CTEST_OUTPUT_ON_FAILURE: 1 - CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules - -jobs: - build: - runs-on: ubuntu-24.04 - - 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) 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 326fa65d0..a3d93cea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,8 @@ 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_USE_MODULES) - if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) - 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() project( @@ -22,27 +20,41 @@ 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(PROJECT_IS_TOP_LEVEL 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} ) +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) +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 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) + +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() @@ -50,61 +62,103 @@ if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION OR MAGIC_ENUM_OPT_TEST_INSTALLED_VERSIO return() endif() -set(INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/include/magic_enum") -set(MODULE "${CMAKE_CURRENT_SOURCE_DIR}/module") -set(EXPORT_NAMESPACE "${PROJECT_NAME}::") - -function(magic_enum_make_library 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" +) -if(MAGIC_ENUM_USE_MODULES) - add_library(${name}) - target_sources(${name} - PUBLIC - FILE_SET CXX_MODULES - BASE_DIRS "${MODULE}" - FILES "${MODULE}/magic_enum.cppm" - PUBLIC +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 "${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 "${PROJECT_SOURCE_DIR}/include" + FILES ${MAGIC_ENUM_HEADERS} ) - target_compile_features(${name} PUBLIC cxx_std_20) - target_compile_definitions(${name} - PRIVATE ${ARGN} +else() + target_include_directories( + ${PROJECT_NAME} INTERFACE $ + $ ) - 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) +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() - set(_scope PUBLIC) -else() - add_library(${name} INTERFACE) - set(_scope INTERFACE) +if(MAGIC_ENUM_MODULE_WITH_FMT) + if(NOT TARGET fmt::fmt) + find_package(fmt CONFIG REQUIRED) + endif() + if(NOT TARGET fmt::fmt) + message(FATAL_ERROR "MAGIC_ENUM_MODULE_WITH_FMT requires fmt::fmt") + endif() + message(STATUS "magic_enum module: using fmt::fmt") endif() -add_library(${EXPORT_NAMESPACE}${name} ALIAS ${name}) -target_include_directories( - ${name} ${_scope} $ - $ -) +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 + BASE_DIRS "${MAGIC_ENUM_MODULE_DIR}" + FILES "${MAGIC_ENUM_MODULE_DIR}/magic_enum.cppm" + ) + target_link_libraries(${name} PUBLIC ${PROJECT_NAME}) + target_compile_features(${name} PUBLIC cxx_std_${MAGIC_ENUM_MODULE_CXX_STANDARD}) + if(ARGC GREATER 1) + # 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) + # 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) + 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") + 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.31") + 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 +170,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 DESTINATION only sets exported include directories. - ) - - 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 +228,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..36bc463c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,71 +1,28 @@ 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 cmake -S . -B build-install \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/tmp/configured-install \ + -DMAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF \ + -DMAGIC_ENUM_OPT_BUILD_TESTS=OFF \ + -DMAGIC_ENUM_OPT_VERIFY_INTERFACE_HEADER_SETS=ON \ + && cmake --build build-install --target magic_enum_verify_interface_header_sets --parallel \ + && cmake --install build-install --prefix /tmp/magic_enum \ + && cmake -S . -B build-installed-cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/tmp/magic_enum \ + -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=ON \ + && cmake --build build-installed-cmake --parallel \ + && ctest --test-dir build-installed-cmake --output-on-failure --no-tests=error \ + && cmake -S . -B build-installed-pkgconfig \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/tmp/magic_enum \ + -DMAGIC_ENUM_OPT_TEST_INSTALLED_VERSION_PKGCONFIG=ON \ + && cmake --build build-installed-pkgconfig --parallel \ + && ctest --test-dir build-installed-pkgconfig --output-on-failure --no-tests=error diff --git a/README.md b/README.md index 9e3e02464..56b907fe0 100644 --- a/README.md +++ b/README.md @@ -333,9 +333,39 @@ Header-only C++17 library provides static reflection for enums, work with any en * Fetch sources with CMake [`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html) or [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake). Release tags use `vx.y.z` format. * Use Bazel with `MODULE.bazel` or `http_archive`; target is `@magic_enum//:magic_enum`. * Use ROS with `magic_enum` in `package.xml`, then link `magic_enum::magic_enum`. -* Use C++20 module with CMake: configure with `MAGIC_ENUM_USE_MODULES=ON`, link `magic_enum::magic_enum`, and write `import magic_enum;`. Do not mix module import and `magic_enum` headers in same program. -## Compiler compatibility +* **CMake targets**: + - `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`. CMake 3.28+ is required. + + Build the module target: + ```sh + cmake -S . -B build -G Ninja -DMAGIC_ENUM_USE_MODULES=ON + cmake --build build + ``` + + 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 CXX_SCAN_FOR_MODULES ON) + ``` + + Import the module: + ```cpp + import magic_enum; + + enum class Color { RED, GREEN, BLUE }; + 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. The pkg-config package supports only the header-only target. + + Optional settings: + - 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 * Clang/LLVM >= 5 * MSVC++ >= 15.3 / Visual Studio >= 2017 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 new file mode 100644 index 000000000..924958f3d --- /dev/null +++ b/cmake/magic_enumConfig.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +@MAGIC_ENUM_FIND_DEPENDENCIES@ + +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 b91c6462a..420880c4d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,41 +1,50 @@ 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 library standard) + add_executable(${target} ${target}.cpp) + set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) + target_compile_features(${target} PRIVATE cxx_std_${standard}) + target_compile_options(${target} PRIVATE ${MAGIC_ENUM_EXAMPLE_COMPILE_OPTIONS}) + target_link_libraries(${target} PRIVATE ${library}) 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} magic_enum::magic_enum 17) +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 + 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) + 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..cc501860b 100644 --- a/module/magic_enum.cppm +++ b/module/magic_enum.cppm @@ -1,31 +1,34 @@ module; #include -#if __has_include() +#ifdef MAGIC_ENUM_USE_FMT_HEADER # 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 +64,29 @@ 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 +97,110 @@ 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 -} -#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; + MAGIC_ENUM_EXPORT_DETAIL } #endif - -#if defined(__cpp_lib_format) && __cpp_lib_format >= 201907L -export namespace std { - using std::formatter; } -#endif -#if defined(FMT_VERSION) +#ifdef MAGIC_ENUM_USE_FMT_HEADER export namespace fmt { - using fmt::formatter; + using fmt::formatter; } #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d32901d1d..f65c2c9ff 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,166 +1,257 @@ include(CheckCXXCompilerFlag) -if(MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION) +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 REQUIRED) - pkg_check_modules(magic_enum magic_enum) - if(NOT magic_enum_FOUND) - message( - WARNING - "magic_enum was not found via pkg-config. CMake will check common locations, but this is not guaranteed. \ -Set \$\{CMAKE_PREFIX_PATH\} to a prefix containing share/pkgconfig/magic_enum.pc, for example /a/path/to/magic_enum on POSIX-like systems." - ) - 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}") + 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) +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) - # 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() +# 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) + 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() -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\"" -) +# 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) + 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) + target_include_directories(${target} SYSTEM 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_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(standard_flag MATCHES "^c\\+\\+(17|20|23)$") + set_target_properties( + ${target} + PROPERTIES + CXX_STANDARD "${CMAKE_MATCH_1}" + CXX_STANDARD_REQUIRED ON + ) + elseif(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}) - set_target_properties(${target} PROPERTIES CXX_SCAN_FOR_MODULES ON) +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_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() -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_range.cpp test_range-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} + ) + if(MAGIC_ENUM_MODULE_WITH_FMT) + target_compile_definitions(test_module-${suffix} PRIVATE MAGIC_ENUM_TEST_FMT) + endif() -endif() + # 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() -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) + 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() + +magic_enum_add_tests(cpp17 c++17) +magic_enum_make_test(test_range.cpp test_range-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() + + 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}) +endforeach() + +if(MAGIC_ENUM_USE_MODULES) + magic_enum_add_module_tests(cpp${MAGIC_ENUM_MODULE_CXX_STANDARD}) 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) +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=OFF" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" + "-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(MAGIC_ENUM_USE_MODULES) - make_module_test(test_module-cpp23 c++23) + 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 + "-DCMAKE_RC_COMPILER=${MAGIC_ENUM_RC_COMPILER}" + "-DCMAKE_MT=${MAGIC_ENUM_MT}" + ) 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) + 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..f4df69669 --- /dev/null +++ b/test/installed_module/CMakeLists.txt @@ -0,0 +1,28 @@ +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_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) + 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..323c31265 --- /dev/null +++ b/test/installed_module/main.cpp @@ -0,0 +1,45 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT + +#include + +#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 }; + +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 4d0e7fbc1..b15201372 100644 --- a/test/magic_enum_wchar_t_test_config.hpp +++ b/test/magic_enum_wchar_t_test_config.hpp @@ -4,6 +4,9 @@ #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 379daabb3..7e7e2d8f3 100644 --- a/test/test_module.cpp +++ b/test/test_module.cpp @@ -2,6 +2,21 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2019 - 2026 Daniil Goncharov . +#include + +#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 }; @@ -19,6 +34,109 @@ static_assert(magic_enum::is_flags_v); static_assert(magic_enum::enum_reflected(Directions::RIGHT)); static_assert(!magic_enum::enum_reflected(static_cast(3))); +// 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<<; + +#ifdef MAGIC_ENUM_TEST_FMT +using fmt::formatter; +#endif + +} // 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); @@ -27,6 +145,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; @@ -44,4 +173,19 @@ 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() { + 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 +#ifdef MAGIC_ENUM_TEST_FMT + if (fmt::format("{}", Color::BLUE) != "BLUE") { + return 3; + } +#endif + return 0; +} 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; +}