├── .gitattributes ├── example ├── CMakeLists.txt └── example.cpp ├── .appveyor.yml ├── .gitignore ├── cmake ├── FetchContent │ └── CMakeLists.cmake.in └── FetchContent.cmake ├── CMakeLists.txt ├── LICENSE ├── test ├── CMakeLists.txt └── test.cpp ├── README.md ├── .travis.yml └── include └── static_enum └── static_enum.hpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(static_enum_example) 2 | target_sources(static_enum_example PRIVATE example.cpp) 3 | target_link_libraries(static_enum_example PRIVATE static_enum) 4 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 5 | target_compile_options(static_enum_example PRIVATE /W4 /permissive-) 6 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 7 | target_compile_options(static_enum_example PRIVATE -Wall -Wextra -Wno-c++98-compat -Wno-c++98-compat-pedantic) 8 | endif() 9 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{branch} #{build}" 2 | 3 | shallow_clone: true 4 | 5 | image: 6 | - Visual Studio 2017 7 | 8 | platform: 9 | - Win32 10 | - x64 11 | 12 | configuration: 13 | - Debug 14 | - Release 15 | 16 | build: 17 | parallel: true 18 | 19 | environment: 20 | matrix: 21 | - GENERATOR: "Visual Studio 15 2017" 22 | 23 | before_build: 24 | - if exist build RMDIR /S /Q build 25 | - if not exist build mkdir build 26 | - cd build 27 | - cmake -G "%GENERATOR%" -A %PLATFORM% .. 28 | 29 | build_script: 30 | - cmake --build . --config %CONFIGURATION% 31 | 32 | test_script: 33 | - ctest --output-on-failure -C %CONFIGURATION% 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | ### C++ gitignore ### 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | 37 | .vs/ 38 | 39 | ### CMake gitignore ### 40 | CMakeLists.txt.user 41 | CMakeCache.txt 42 | CMakeFiles 43 | CMakeScripts 44 | Testing 45 | Makefile 46 | cmake_install.cmake 47 | install_manifest.txt 48 | compile_commands.json 49 | CTestTestfile.cmake 50 | CMakeSettings.json 51 | _deps 52 | -------------------------------------------------------------------------------- /cmake/FetchContent/CMakeLists.cmake.in: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | cmake_minimum_required(VERSION ${CMAKE_VERSION}) 5 | 6 | # We name the project and the target for the ExternalProject_Add() call 7 | # to something that will highlight to the user what we are working on if 8 | # something goes wrong and an error message is produced. 9 | 10 | project(${contentName}-populate NONE) 11 | 12 | include(ExternalProject) 13 | ExternalProject_Add(${contentName}-populate 14 | ${ARG_EXTRA} 15 | SOURCE_DIR "${ARG_SOURCE_DIR}" 16 | BINARY_DIR "${ARG_BINARY_DIR}" 17 | CONFIGURE_COMMAND "" 18 | BUILD_COMMAND "" 19 | INSTALL_COMMAND "" 20 | TEST_COMMAND "" 21 | USES_TERMINAL_DOWNLOAD YES 22 | USES_TERMINAL_UPDATE YES 23 | ) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake) 3 | project(static_enum VERSION "0.1" LANGUAGES CXX) 4 | 5 | option(STATIC_ENUM_OPT_BUILD_EXAMPLES "Build static_enum examples" ON) 6 | option(STATIC_ENUM_OPT_BUILD_TESTS "Build and perform static_enum tests" ON) 7 | set(HEADER $ $) 8 | 9 | add_library(static_enum INTERFACE) 10 | target_sources(static_enum INTERFACE ${HEADER}) 11 | target_include_directories(static_enum INTERFACE $ $) 12 | target_compile_features(static_enum INTERFACE cxx_std_17) 13 | 14 | if(MSVC) 15 | add_custom_target(static_enum.header SOURCES ${HEADER}) 16 | endif() 17 | 18 | if(STATIC_ENUM_OPT_BUILD_EXAMPLES) 19 | add_subdirectory(example) 20 | endif() 21 | 22 | if(STATIC_ENUM_OPT_BUILD_TESTS) 23 | enable_testing() 24 | add_subdirectory(test) 25 | endif() 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 konanM 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #FetchContent is only available for version 3.14 and upwards and we need it to get googletest 2 | include(FetchContent) 3 | 4 | set(gtest_force_shared_crt ON CACHE BOOL "Always use msvcrt.dll" FORCE) 5 | # Download and unpack googletest at configure time 6 | FetchContent_Declare( 7 | googletest 8 | GIT_REPOSITORY https://github.com/google/googletest.git 9 | GIT_TAG master 10 | ) 11 | 12 | # Add googletest directly to our build. This defines 13 | # the gtest and gtest_main targets. 14 | FetchContent_MakeAvailable(googletest) 15 | 16 | add_executable(static_enum_test) 17 | target_sources(static_enum_test PRIVATE test.cpp) 18 | target_link_libraries(static_enum_test PRIVATE gtest gtest_main static_enum) 19 | if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 20 | target_compile_options(static_enum_test PRIVATE /permissive-) 21 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 22 | target_compile_options(static_enum_test PRIVATE -Wall -Wextra -Wno-c++98-compat -Wno-c++98-compat-pedantic) 23 | endif() 24 | 25 | # add our tests automatically to ctest which makes them discoverable by IDE's like MSVS 26 | include(GoogleTest) 27 | gtest_discover_tests(static_enum_test) -------------------------------------------------------------------------------- /example/example.cpp: -------------------------------------------------------------------------------- 1 | 2 | // Licensed under the MIT License . 3 | // SPDX-License-Identifier: MIT 4 | // Copyright (c) 2019 Kinan Mahdi 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | #include 25 | #include 26 | #include 27 | 28 | enum class Color : int { GREEN = 7, RED = -12, BLUE = 15 }; 29 | 30 | 31 | int main() 32 | { 33 | constexpr auto colorEnumerators = static_enum::get_enumerators(); 34 | //check the size of the array to get the number of different values 35 | static_assert(colorEnumerators.size() == 3); 36 | 37 | constexpr std::array colorEnumeratorNames = {"RED", "GREEN", "BLUE"}; 38 | std::array colorsFromNames; 39 | std::transform(colorEnumeratorNames.begin(), colorEnumeratorNames.end(), colorsFromNames.begin(), [](auto& val) { return *static_enum::from_string(val); }); 40 | 41 | for (Color e : colorEnumerators) 42 | std::cout << static_enum::enum_cast(e) << ": " << static_cast(e) << "\n"; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static Enum C++ 2 | 3 | ## What is Static Enum? 4 | 5 | Static Enum is a single header C++17 library which provides compile time enumumeration information without using any macros or having to define the enum with some macro magic. 6 | As far as I'm aware this was the first library to implement a get_enumerator implementation._ 7 | 8 | * `static_enum::get_enumerators` creates a `std::array` with all enumeration values (sorted by value) 9 | * `static_enum::enum_cast` can be used like static_cast to either convert an enum to a string, or from to create an string from an enum 10 | * `static_enum::to_string` get the name from an enum variable, returns a `constexpr std::optional` 11 | * `static_enum::from_string` get the enum variable from a string, returns a `constexpr std::optional` 12 | 13 | 14 | ## Where is the drawback? 15 | Static Enum uses compiler intrinsics - namely `__PRETTY_FUNCTION__` and `__FUNCSIG__` to check whether an enumeration value is valid and make the string conversions. I have taken this trick from https://github.com/Neargye/magic_enum and adapted it slightly. 16 | The main drawback is that this library creates the enumerators by checking all the possible values. The default limit is to check for 256 values, which only works for arbitrary enums where the size of the underlying type is smaller than 2 bytes. 17 | It also works for enums of bigger types, but the enumeration values have to be in the range of [-127, 128] for signed types and [0, 255] for unsigned types. 18 | 19 | 20 | ## Features 21 | 22 | * C++17 23 | * Single Header 24 | * Dependency-free 25 | * constexpr 26 | * Works with 3rd party enums 27 | 28 | ## Example 29 | 30 | ```Cpp 31 | enum class Color : int { GREEN = 7, RED = -12, BLUE = 15 }; 32 | // the deduced type is std::array, it will update as you update the enum 33 | constexpr auto colorEnumerators = static_enum::get_enumerators(); 34 | //check the size of the array to get the number of different values 35 | static_assert(colorEnumerators.size() == 3); 36 | //you can also convert from names 37 | constexpr std::array colorEnumeratorNames = {"RED", "GREEN", "BLUE"}; 38 | std::array colorsFromNames; 39 | std::transform(colorEnumeratorNames.begin(), colorEnumeratorNames.end(), colorsFromNames.begin(), [](auto& val) { return *static_enum::from_string(val); }); 40 | //or convert an enum to a name 41 | for (Color e : colorEnumerators) 42 | std::cout << static_enum::enum_cast(e) << ": " << static_cast(e) << "\n"; 43 | ``` 44 | 45 | This will print: 46 | RED: -12 47 | GREEN: 7 48 | BLUE: 15 49 | ## Compiler compatibility 50 | 51 | * Clang/LLVM >= 5 52 | * Visual C++ >= 15.3 / Visual Studio >= 2017 53 | * Xcode >= 10.2 54 | * GCC >= 9 55 | 56 | ## Licensed under the [MIT License](LICENSE) 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux # Use linux unless specified otherwise. 2 | dist: xenial 3 | sudo: required 4 | 5 | language: cpp 6 | 7 | git: 8 | depth: 1 9 | 10 | matrix: 11 | include: 12 | - os: linux 13 | compiler: g++ 14 | env: 15 | - CXX_COMPILER=g++-9 CC_COMPILER=gcc-9 16 | 17 | - os: linux 18 | compiler: clang++ 19 | addons: 20 | apt: 21 | sources: 22 | - ubuntu-toolchain-r-test 23 | - llvm-toolchain-xenial-5.0 24 | packages: 25 | - clang-5.0 26 | env: 27 | - CXX_COMPILER=clang++-5.0 CC_COMPILER=clang-5.0 28 | 29 | - os: linux 30 | compiler: clang++ 31 | addons: 32 | apt: 33 | sources: 34 | - ubuntu-toolchain-r-test 35 | - llvm-toolchain-xenial-6.0 36 | packages: 37 | - clang-6.0 38 | env: 39 | - CXX_COMPILER=clang++-6.0 CC_COMPILER=clang-6.0 40 | 41 | - os: linux 42 | compiler: clang++ 43 | addons: 44 | apt: 45 | sources: 46 | - ubuntu-toolchain-r-test 47 | - llvm-toolchain-xenial-7 48 | packages: 49 | - clang-7 50 | env: 51 | - CXX_COMPILER=clang++-7 CC_COMPILER=clang-7 52 | 53 | - os: linux 54 | compiler: clang++ 55 | addons: 56 | apt: 57 | sources: 58 | - ubuntu-toolchain-r-test 59 | - llvm-toolchain-xenial-8 60 | packages: 61 | - clang-8 62 | env: 63 | - CXX_COMPILER=clang++-8 CC_COMPILER=clang-8 64 | 65 | - os: osx 66 | compiler: clang++ 67 | osx_image: xcode10.2 68 | env: 69 | - CXX_COMPILER=clang++ CC_COMPILER=clang 70 | 71 | allow_failures: # gcc-9 yeat unstable. 72 | - os: linux 73 | compiler: g++ 74 | env: 75 | - CXX_COMPILER=g++-9 CC_COMPILER=gcc-9 76 | 77 | install: 78 | - export CC=${CC_COMPILER} 79 | - export CXX=${CXX_COMPILER} 80 | - JOBS=2 # Travis machines have 2 cores. 81 | - | 82 | # If linux and clang install the right version of libc++. 83 | if [[ "${TRAVIS_OS_NAME}" == "linux" && "${CXX%%+*}" == "clang" && -n "$(ls -A ${LLVM_INSTALL})" ]]; then 84 | LLVM_INSTALL=${DEPS_DIR}/llvm/install 85 | if [[ "${CXX}" == "clang++-3.5" ]]; then LLVM_VERSION="3.5.2"; 86 | elif [[ "${CXX}" == "clang++-3.6" ]]; then LLVM_VERSION="3.6.2"; 87 | elif [[ "${CXX}" == "clang++-3.7" ]]; then LLVM_VERSION="3.7.1"; 88 | elif [[ "${CXX}" == "clang++-3.8" ]]; then LLVM_VERSION="3.8.1"; 89 | elif [[ "${CXX}" == "clang++-3.9" ]]; then LLVM_VERSION="3.9.1"; 90 | elif [[ "${CXX}" == "clang++-4.0" ]]; then LLVM_VERSION="4.0.1"; 91 | elif [[ "${CXX}" == "clang++-5.0" ]]; then LLVM_VERSION="5.0.2"; 92 | elif [[ "${CXX}" == "clang++-6.0" ]]; then LLVM_VERSION="6.0.1"; 93 | elif [[ "${CXX}" == "clang++-7" ]]; then LLVM_VERSION="7.0.1"; 94 | elif [[ "${CXX}" == "clang++-8" ]]; then LLVM_VERSION="8.0.0"; 95 | fi 96 | LLVM_URL="http://llvm.org/releases/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz" 97 | LIBCXX_URL="http://llvm.org/releases/${LLVM_VERSION}/libcxx-${LLVM_VERSION}.src.tar.xz" 98 | LIBCXXABI_URL="http://llvm.org/releases/${LLVM_VERSION}/libcxxabi-${LLVM_VERSION}.src.tar.xz" 99 | mkdir -p llvm llvm/build llvm/projects/libcxx llvm/projects/libcxxabi 100 | travis_retry wget -O - ${LLVM_URL} | tar --strip-components=1 -xJ -C llvm 101 | travis_retry wget -O - ${LIBCXX_URL} | tar --strip-components=1 -xJ -C llvm/projects/libcxx 102 | travis_retry wget -O - ${LIBCXXABI_URL} | tar --strip-components=1 -xJ -C llvm/projects/libcxxabi 103 | (cd llvm/build && cmake .. -DCMAKE_INSTALL_PREFIX=${LLVM_INSTALL}) 104 | (cd llvm/build/projects/libcxx && sudo make install -j${JOBS}) 105 | (cd llvm/build/projects/libcxxabi && sudo make install -j${JOBS}) 106 | export CXXFLAGS="-isystem ${LLVM_INSTALL}/include/c++/v1" 107 | export LDFLAGS="-L ${LLVM_INSTALL}/lib -l c++ -l c++abi" 108 | export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${LLVM_INSTALL}/lib" 109 | fi 110 | - | 111 | # If linux and gcc-9 install gcc-9. 112 | if [[ "${CXX}" == "g++-9" ]]; then 113 | wget http://kayari.org/gcc-latest/gcc-latest.deb 114 | sudo dpkg -i gcc-latest.deb 115 | sudo ln -s /opt/gcc-latest/bin/gcc /usr/bin/gcc-9 116 | sudo ln -s /opt/gcc-latest/bin/g++ /usr/bin/g++-9 117 | sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9 118 | sudo ldconfig /opt/gcc-latest/lib64 119 | fi 120 | 121 | before_script: 122 | - rm -rf build 123 | - mkdir -p build 124 | - cd build 125 | - cmake -G "Unix Makefiles" .. 126 | 127 | script: 128 | - cmake --build . -- -j${JOBS} 129 | - ctest --output-on-failure -j${JOBS} 130 | 131 | notifications: 132 | email: false 133 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT License . 2 | // SPDX-License-Identifier: MIT 3 | // Copyright (c) 2019 Kinan Mahdi 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #define STATIC_ENUM_RANGE 256 25 | #include 26 | 27 | enum class Simple : unsigned { ONE, TWO, THREE }; 28 | 29 | enum Directions { Up = 85, Down = -42, Right = 119, Left = -119 }; 30 | 31 | enum number : int { one = 10, two = 20, three = 30 }; 32 | 33 | enum class Numbers : unsigned char { one = 10, two = 20, three = 30 }; 34 | 35 | enum class Color : int { GREEN = 7, RED = -12, BLUE = 15 }; 36 | 37 | enum class Alphabet : size_t { a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z }; 38 | 39 | TEST(static_enum, enum_range_constexpr) 40 | { 41 | constexpr auto enumRangeAlphabet = static_enum::get_enumerators(); 42 | static_assert(enumRangeAlphabet.size() == 26); 43 | 44 | for (size_t i = 0; i < 26; ++i) 45 | ASSERT_EQ(static_enum::to_string(enumRangeAlphabet[i]).front(), ('a' + i)); 46 | 47 | constexpr auto enumRangeColor = static_enum::get_enumerators(); 48 | static_assert(enumRangeColor.size() == 3); 49 | static_assert(enumRangeColor[0] == Color::RED, "Color must match"); 50 | static_assert(enumRangeColor[1] == Color::GREEN, "Color must match"); 51 | static_assert(enumRangeColor[2] == Color::BLUE, "Color must match"); 52 | 53 | constexpr auto enumRangeDirections = static_enum::get_enumerators(); 54 | static_assert(enumRangeDirections.size() == 4); 55 | static_assert(enumRangeDirections[0] == Directions::Left, "Direction must match"); 56 | static_assert(enumRangeDirections[1] == Directions::Down, "Direction must match"); 57 | static_assert(enumRangeDirections[2] == Directions::Up, "Direction must match"); 58 | static_assert(enumRangeDirections[3] == Directions::Right, "Direction must match"); 59 | 60 | constexpr auto enumRangeSimple = static_enum::get_enumerators(); 61 | 62 | static_assert(enumRangeSimple[0] == Simple::ONE, "Number must match"); 63 | static_assert(enumRangeSimple[1] == Simple::TWO, "Number must match"); 64 | static_assert(enumRangeSimple[2] == Simple::THREE, "Number must match"); 65 | } 66 | 67 | TEST(static_enum, to_string_dynamic) 68 | { 69 | ASSERT_TRUE(static_enum::to_string(Color::RED) == "RED"); 70 | ASSERT_TRUE(static_enum::to_string(Color::BLUE) == "BLUE"); 71 | ASSERT_TRUE(static_enum::to_string(Color::GREEN) == "GREEN"); 72 | ASSERT_TRUE(static_enum::to_string(static_cast(4)).empty()); 73 | 74 | ASSERT_TRUE(static_enum::to_string(Numbers::one) == "one"); 75 | ASSERT_TRUE(static_enum::to_string(Numbers::two) == "two"); 76 | ASSERT_TRUE(static_enum::to_string(Numbers::three) == "three"); 77 | ASSERT_TRUE(static_enum::to_string(static_cast(4)).empty()); 78 | 79 | } 80 | 81 | TEST(static_enum, to_string_static) 82 | { 83 | ASSERT_TRUE(static_enum::to_string() == "RED"); 84 | ASSERT_TRUE(static_enum::to_string() == "BLUE"); 85 | ASSERT_TRUE(static_enum::to_string() == "GREEN"); 86 | ASSERT_TRUE(static_enum::to_string(4)>().empty()); 87 | 88 | ASSERT_TRUE(static_enum::to_string() == "one"); 89 | ASSERT_TRUE(static_enum::to_string() == "two"); 90 | ASSERT_TRUE(static_enum::to_string() == "three"); 91 | ASSERT_TRUE(static_enum::to_string(4)>().empty()); 92 | 93 | ASSERT_TRUE(static_enum::to_string() == "Left"); 94 | ASSERT_TRUE(static_enum::to_string() == "Right"); 95 | ASSERT_TRUE(static_enum::to_string() == "Up"); 96 | ASSERT_TRUE(static_enum::to_string() == "Down"); 97 | ASSERT_TRUE(static_enum::to_string(123)>().empty()); 98 | } 99 | 100 | TEST(static_enum, from_string) 101 | { 102 | ASSERT_TRUE(static_enum::from_string("RED").value() == Color::RED); 103 | ASSERT_TRUE(static_enum::from_string("GREEN").value() == Color::GREEN); 104 | ASSERT_TRUE(static_enum::from_string("BLUE").value() == Color::BLUE); 105 | ASSERT_TRUE(!static_enum::from_string("None").has_value()); 106 | 107 | ASSERT_TRUE(static_enum::from_string("one").value() == Numbers::one); 108 | ASSERT_TRUE(static_enum::from_string("two").value() == Numbers::two); 109 | ASSERT_TRUE(static_enum::from_string("two").value() == Numbers::two); 110 | ASSERT_TRUE(!static_enum::from_string("None").has_value()); 111 | 112 | ASSERT_TRUE(static_enum::from_string("Up").value() == Directions::Up); 113 | ASSERT_TRUE(static_enum::from_string("Down").value() == Directions::Down); 114 | ASSERT_TRUE(static_enum::from_string("Right").value() == Directions::Right); 115 | ASSERT_TRUE(static_enum::from_string("Left").value() == Directions::Left); 116 | ASSERT_TRUE(!static_enum::from_string("None").has_value()); 117 | 118 | ASSERT_TRUE(static_enum::from_string("one").value() == number::one); 119 | ASSERT_TRUE(static_enum::from_string("two").value() == number::two); 120 | ASSERT_TRUE(static_enum::from_string("two").value() == number::two); 121 | ASSERT_TRUE(!static_enum::from_string("None").has_value()); 122 | } 123 | 124 | TEST(static_enum, enum_cast) 125 | { 126 | ASSERT_TRUE(static_enum::enum_cast() == "RED"); 127 | ASSERT_TRUE(static_enum::enum_cast() == "BLUE"); 128 | ASSERT_TRUE(static_enum::enum_cast() == "GREEN"); 129 | ASSERT_TRUE(static_enum::enum_cast(4)>().empty()); 130 | 131 | ASSERT_TRUE(static_enum::enum_cast(Color::RED) == "RED"); 132 | ASSERT_TRUE(static_enum::enum_cast(Color::BLUE) == "BLUE"); 133 | ASSERT_TRUE(static_enum::enum_cast(Color::GREEN) == "GREEN"); 134 | ASSERT_TRUE(static_enum::enum_cast(static_cast(4)).empty()); 135 | 136 | ASSERT_TRUE(static_enum::enum_cast("RED") == Color::RED); 137 | ASSERT_TRUE(static_enum::enum_cast("BLUE") == Color::BLUE); 138 | ASSERT_TRUE(static_enum::enum_cast("GREEN") == Color::GREEN); 139 | ASSERT_TRUE(!static_enum::enum_cast("NotSoGreen")); 140 | } -------------------------------------------------------------------------------- /include/static_enum/static_enum.hpp: -------------------------------------------------------------------------------- 1 | 2 | // Licensed under the MIT License . 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef __cpp_lib_experimental_source_location 15 | #include 16 | #endif // __cpp_lib_experimental_source_location 17 | 18 | // Enum variable must be in range (-STATIC_ENUM_RANGE / 2, STATIC_ENUM_RANGE /2) for signed types 19 | // Enum variable must be in range (0, STATIC_ENUM_RANGE) for unsigned types 20 | // If you need a larger range, redefine the macro STATIC_ENUM_RANGE. 21 | #if !defined(STATIC_ENUM_RANGE) 22 | # define STATIC_ENUM_RANGE 256 23 | #endif 24 | 25 | namespace static_enum 26 | { 27 | static_assert(STATIC_ENUM_RANGE > 0, "STATIC_ENUM_RANGE must be positive and greater than zero."); 28 | static_assert(STATIC_ENUM_RANGE < std::numeric_limits::max(), "STATIC_ENUM_RANGE must be less INT_MAX."); 29 | 30 | namespace detail 31 | { 32 | #if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 9) 33 | inline constexpr auto suffix = sizeof("]") - 1; 34 | #define STATIC_ENUM_FUNCSIG __PRETTY_FUNCTION__ 35 | #elif defined(_MSC_VER) 36 | inline constexpr auto suffix = sizeof(">(void) noexcept") - 1; 37 | #define STATIC_ENUM_FUNCSIG __FUNCSIG__ 38 | #else 39 | #define STATIC_ENUM_FUNCSIG 40 | #error Unsupported compiler 41 | #endif 42 | [[nodiscard]] constexpr bool is_digit(char c) noexcept 43 | { 44 | return c >= '0' && c <= '9'; 45 | } 46 | //this is the hack that we're gonna use 47 | //we use either __PRETTY_FUNCTION__ or __FUNCSIG__ to analyze the function signature 48 | //if the enum value doesn't exist it will appear as a number 49 | //otherwise it will have the correct enum name for the value V 50 | //to check whether the enum is valid it's enough to check simply if the first letter of the enum is a number 51 | //this works with MSVC, gcc and clang 52 | //for the string conversion we simply have to extract the correct substring from the function signature 53 | template 54 | [[nodiscard]] constexpr std::string_view to_string_impl_static() noexcept 55 | { 56 | constexpr std::string_view name{ STATIC_ENUM_FUNCSIG }; 57 | constexpr std::size_t prefix = name.find_last_of(", :)-", name.size() - suffix) + 1; 58 | 59 | if constexpr (!is_digit(name[prefix])) 60 | return name.substr(prefix, name.size() - suffix - prefix); 61 | else 62 | return {}; 63 | } 64 | 65 | template 66 | [[nodiscard]] constexpr std::optional enum_from_string_impl(std::string_view name, std::integer_sequence) noexcept 67 | { 68 | std::optional returnValue; 69 | (void)(((to_string_impl_static(I - Offset)>() == name) ? (returnValue = static_cast(I - Offset), false) : true) && ...); 70 | return returnValue; 71 | } 72 | 73 | template 74 | [[nodiscard]] constexpr std::string_view to_string_impl(E value, std::integer_sequence) noexcept 75 | { 76 | //we have to convert the runtime value to a compile time index 77 | //this method uses an O(1) lookup via function pointers 78 | using ToStringFunctionDecl = decltype(&to_string_impl_static(0)>); 79 | constexpr std::array to_string_functions{ { to_string_impl_static(I - Offset)>... } }; 80 | return to_string_functions[size_t(Offset + static_cast(value))](); 81 | } 82 | 83 | template 84 | [[nodiscard]] constexpr decltype(auto) get_enumerators_impl(std::integer_sequence) noexcept 85 | { 86 | constexpr size_t N = sizeof...(I); 87 | //here we create an array of bool where each index indicates whether it belongs to a valid enum entry 88 | constexpr std::array validIndices{ {!to_string_impl_static(I - Offset)>().empty()...} }; 89 | //here we count the number of valid enum indices 90 | constexpr int numValid = ((validIndices[I] ? 1 : 0) + ...); 91 | //with this information we can build an array of only valid enum entries 92 | std::array enumArray{}; 93 | size_t enumIdx = 0; 94 | for (size_t i = 0; i < N && enumIdx < numValid; ++i) 95 | if (validIndices[i]) 96 | enumArray[enumIdx++] = static_cast(i - Offset); 97 | 98 | return enumArray; 99 | } 100 | 101 | //for small values like char we should check for the right range - we should not stress the compiler more than necessary 102 | template 103 | struct Limit final 104 | { 105 | static constexpr int Range = sizeof(U) <= 2 ? static_cast(std::numeric_limits::max()) - static_cast(std::numeric_limits::min()) + 1 : std::numeric_limits::max(); 106 | static constexpr int Size = std::min(Range, STATIC_ENUM_RANGE); 107 | static constexpr int Offset = std::is_signed_v ? (Size + 1) / 2 : 0; 108 | }; 109 | 110 | } // namespace detail 111 | 112 | //get_enumerators(): return a std::array with all enumeration values (sorted by value) 113 | template >> 114 | [[nodiscard]] constexpr decltype(auto) get_enumerators() noexcept 115 | { 116 | using Indices = std::make_integer_sequence::Size>; 117 | static_assert(std::is_enum_v>, "static_enum::to_string requires an enum type."); 118 | return detail::get_enumerators_impl::Offset>(Indices{}); 119 | } 120 | 121 | //to_string(Enum): get the name from an enum variable, returns a constexpr std::string_view 122 | template >> 123 | [[nodiscard]] constexpr std::string_view to_string(E value) noexcept 124 | { 125 | using Indices = std::make_integer_sequence::Size>; 126 | static_assert(std::is_enum_v>, "static_enum::to_string requires enum type."); 127 | if (static_cast(value) >= STATIC_ENUM_RANGE || static_cast(value) <= -STATIC_ENUM_RANGE) 128 | { 129 | return {}; // Enum variable out of STATIC_ENUM_RANGE. 130 | } 131 | return detail::to_string_impl, detail::Limit::Offset>(value, Indices{}); 132 | } 133 | 134 | // to_string(): get the name from an enum variable, prefer this over the to_string() version if possible, since 135 | // this version is much lighter on the compile times and is not restricted to the size limitation 136 | template 137 | [[nodiscard]] constexpr std::string_view to_string() noexcept 138 | { 139 | static_assert(std::is_enum_v>, "static_enum::to_string requires an enum type."); 140 | return detail::to_string_impl_static(); 141 | } 142 | 143 | //from_string(name): get the enum variable from a string, returns a constexpr std::optional 144 | template >> 145 | [[nodiscard]] constexpr std::optional from_string(std::string_view name) noexcept 146 | { 147 | using Indices = std::make_integer_sequence::Size>; 148 | static_assert(std::is_enum_v>, "static_enum::to_string requires an enum type."); 149 | return detail::enum_from_string_impl::Offset>(name, Indices{}); 150 | } 151 | 152 | template 153 | [[nodiscard]] constexpr std::optional enum_cast(std::string_view name) noexcept 154 | { 155 | return from_string(name); 156 | } 157 | 158 | template 159 | [[nodiscard]] constexpr std::string_view enum_cast() noexcept 160 | { 161 | static_assert(std::is_enum_v>, "static_enum::to_string requires an enum type."); 162 | return detail::to_string_impl_static(); 163 | } 164 | 165 | //to_string(Enum): get the name from an enum variable, returns a constexpr std::string_view 166 | template >> 167 | [[nodiscard]] constexpr std::string_view enum_cast(E value) noexcept 168 | { 169 | return to_string(value); 170 | } 171 | 172 | } // namespace static_enum 173 | -------------------------------------------------------------------------------- /cmake/FetchContent.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | #[=======================================================================[.rst: 5 | FetchContent 6 | ------------------ 7 | 8 | .. only:: html 9 | 10 | .. contents:: 11 | 12 | Overview 13 | ^^^^^^^^ 14 | 15 | This module enables populating content at configure time via any method 16 | supported by the :module:`ExternalProject` module. Whereas 17 | :command:`ExternalProject_Add` downloads at build time, the 18 | ``FetchContent`` module makes content available immediately, allowing the 19 | configure step to use the content in commands like :command:`add_subdirectory`, 20 | :command:`include` or :command:`file` operations. 21 | 22 | Content population details would normally be defined separately from the 23 | command that performs the actual population. This separation ensures that 24 | all of the dependency details are defined before anything may try to use those 25 | details to populate content. This is particularly important in more complex 26 | project hierarchies where dependencies may be shared between multiple projects. 27 | 28 | The following shows a typical example of declaring content details: 29 | 30 | .. code-block:: cmake 31 | 32 | FetchContent_Declare( 33 | googletest 34 | GIT_REPOSITORY https://github.com/google/googletest.git 35 | GIT_TAG release-1.8.0 36 | ) 37 | 38 | For most typical cases, populating the content can then be done with a single 39 | command like so: 40 | 41 | .. code-block:: cmake 42 | 43 | FetchContent_MakeAvailable(googletest) 44 | 45 | The above command not only populates the content, it also adds it to the main 46 | build (if possible) so that the main build can use the populated project's 47 | targets, etc. In some cases, the main project may need to have more precise 48 | control over the population or may be required to explicitly define the 49 | population steps (e.g. if CMake versions earlier than 3.14 need to be 50 | supported). The typical pattern of such custom steps looks like this: 51 | 52 | .. code-block:: cmake 53 | 54 | FetchContent_GetProperties(googletest) 55 | if(NOT googletest_POPULATED) 56 | FetchContent_Populate(googletest) 57 | add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 58 | endif() 59 | 60 | Regardless of which population method is used, when using the 61 | declare-populate pattern with a hierarchical project arrangement, projects at 62 | higher levels in the hierarchy are able to override the population details of 63 | content specified anywhere lower in the project hierarchy. The ability to 64 | detect whether content has already been populated ensures that even if 65 | multiple child projects want certain content to be available, the first one 66 | to populate it wins. The other child project can simply make use of the 67 | already available content instead of repeating the population for itself. 68 | See the :ref:`Examples ` section which demonstrates 69 | this scenario. 70 | 71 | The ``FetchContent`` module also supports defining and populating 72 | content in a single call, with no check for whether the content has been 73 | populated elsewhere in the project already. This is a more low level 74 | operation and would not normally be the way the module is used, but it is 75 | sometimes useful as part of implementing some higher level feature or to 76 | populate some content in CMake's script mode. 77 | 78 | 79 | Declaring Content Details 80 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 81 | 82 | .. command:: FetchContent_Declare 83 | 84 | .. code-block:: cmake 85 | 86 | FetchContent_Declare( ...) 87 | 88 | The ``FetchContent_Declare()`` function records the options that describe 89 | how to populate the specified content, but if such details have already 90 | been recorded earlier in this project (regardless of where in the project 91 | hierarchy), this and all later calls for the same content ```` are 92 | ignored. This "first to record, wins" approach is what allows hierarchical 93 | projects to have parent projects override content details of child projects. 94 | 95 | The content ```` can be any string without spaces, but good practice 96 | would be to use only letters, numbers and underscores. The name will be 97 | treated case-insensitively and it should be obvious for the content it 98 | represents, often being the name of the child project or the value given 99 | to its top level :command:`project` command (if it is a CMake project). 100 | For well-known public projects, the name should generally be the official 101 | name of the project. Choosing an unusual name makes it unlikely that other 102 | projects needing that same content will use the same name, leading to 103 | the content being populated multiple times. 104 | 105 | The ```` can be any of the download or update/patch options 106 | that the :command:`ExternalProject_Add` command understands. The configure, 107 | build, install and test steps are explicitly disabled and therefore options 108 | related to them will be ignored. In most cases, ```` will 109 | just be a couple of options defining the download method and method-specific 110 | details like a commit tag or archive hash. For example: 111 | 112 | .. code-block:: cmake 113 | 114 | FetchContent_Declare( 115 | googletest 116 | GIT_REPOSITORY https://github.com/google/googletest.git 117 | GIT_TAG release-1.8.0 118 | ) 119 | 120 | FetchContent_Declare( 121 | myCompanyIcons 122 | URL https://intranet.mycompany.com/assets/iconset_1.12.tar.gz 123 | URL_HASH 5588a7b18261c20068beabfb4f530b87 124 | ) 125 | 126 | FetchContent_Declare( 127 | myCompanyCertificates 128 | SVN_REPOSITORY svn+ssh://svn.mycompany.com/srv/svn/trunk/certs 129 | SVN_REVISION -r12345 130 | ) 131 | 132 | Populating The Content 133 | ^^^^^^^^^^^^^^^^^^^^^^ 134 | 135 | For most common scenarios, population means making content available to the 136 | main build according to previously declared details for that dependency. 137 | There are two main patterns for populating content, one based on calling 138 | :command:`FetchContent_GetProperties` and 139 | :command:`FetchContent_Populate` for more precise control and the other on 140 | calling :command:`FetchContent_MakeAvailable` for a simpler, more automated 141 | approach. The former generally follows this canonical pattern: 142 | 143 | .. _`fetch-content-canonical-pattern`: 144 | 145 | .. code-block:: cmake 146 | 147 | # Check if population has already been performed 148 | FetchContent_GetProperties() 149 | string(TOLOWER "" lcName) 150 | if(NOT ${lcName}_POPULATED) 151 | # Fetch the content using previously declared details 152 | FetchContent_Populate() 153 | 154 | # Set custom variables, policies, etc. 155 | # ... 156 | 157 | # Bring the populated content into the build 158 | add_subdirectory(${${lcName}_SOURCE_DIR} ${${lcName}_BINARY_DIR}) 159 | endif() 160 | 161 | The above is such a common pattern that, where no custom steps are needed 162 | between the calls to :command:`FetchContent_Populate` and 163 | :command:`add_subdirectory`, equivalent logic can be obtained by calling 164 | :command:`FetchContent_MakeAvailable` instead (and should be preferred where 165 | it meets the needs of the project). 166 | 167 | .. command:: FetchContent_Populate 168 | 169 | .. code-block:: cmake 170 | 171 | FetchContent_Populate( ) 172 | 173 | In most cases, the only argument given to ``FetchContent_Populate()`` is the 174 | ````. When used this way, the command assumes the content details have 175 | been recorded by an earlier call to :command:`FetchContent_Declare`. The 176 | details are stored in a global property, so they are unaffected by things 177 | like variable or directory scope. Therefore, it doesn't matter where in the 178 | project the details were previously declared, as long as they have been 179 | declared before the call to ``FetchContent_Populate()``. Those saved details 180 | are then used to construct a call to :command:`ExternalProject_Add` in a 181 | private sub-build to perform the content population immediately. The 182 | implementation of ``ExternalProject_Add()`` ensures that if the content has 183 | already been populated in a previous CMake run, that content will be reused 184 | rather than repopulating them again. For the common case where population 185 | involves downloading content, the cost of the download is only paid once. 186 | 187 | An internal global property records when a particular content population 188 | request has been processed. If ``FetchContent_Populate()`` is called more 189 | than once for the same content name within a configure run, the second call 190 | will halt with an error. Projects can and should check whether content 191 | population has already been processed with the 192 | :command:`FetchContent_GetProperties` command before calling 193 | ``FetchContent_Populate()``. 194 | 195 | ``FetchContent_Populate()`` will set three variables in the scope of the 196 | caller; ``_POPULATED``, ``_SOURCE_DIR`` and 197 | ``_BINARY_DIR``, where ```` is the lowercased ````. 198 | ``_POPULATED`` will always be set to ``True`` by the call. 199 | ``_SOURCE_DIR`` is the location where the 200 | content can be found upon return (it will have already been populated), while 201 | ``_BINARY_DIR`` is a directory intended for use as a corresponding 202 | build directory. The main use case for the two directory variables is to 203 | call :command:`add_subdirectory` immediately after population, i.e.: 204 | 205 | .. code-block:: cmake 206 | 207 | FetchContent_Populate(FooBar ...) 208 | add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR}) 209 | 210 | The values of the three variables can also be retrieved from anywhere in the 211 | project hierarchy using the :command:`FetchContent_GetProperties` command. 212 | 213 | A number of cache variables influence the behavior of all content population 214 | performed using details saved from a :command:`FetchContent_Declare` call: 215 | 216 | ``FETCHCONTENT_BASE_DIR`` 217 | In most cases, the saved details do not specify any options relating to the 218 | directories to use for the internal sub-build, final source and build areas. 219 | It is generally best to leave these decisions up to the ``FetchContent`` 220 | module to handle on the project's behalf. The ``FETCHCONTENT_BASE_DIR`` 221 | cache variable controls the point under which all content population 222 | directories are collected, but in most cases developers would not need to 223 | change this. The default location is ``${CMAKE_BINARY_DIR}/_deps``, but if 224 | developers change this value, they should aim to keep the path short and 225 | just below the top level of the build tree to avoid running into path 226 | length problems on Windows. 227 | 228 | ``FETCHCONTENT_QUIET`` 229 | The logging output during population can be quite verbose, making the 230 | configure stage quite noisy. This cache option (``ON`` by default) hides 231 | all population output unless an error is encountered. If experiencing 232 | problems with hung downloads, temporarily switching this option off may 233 | help diagnose which content population is causing the issue. 234 | 235 | ``FETCHCONTENT_FULLY_DISCONNECTED`` 236 | When this option is enabled, no attempt is made to download or update 237 | any content. It is assumed that all content has already been populated in 238 | a previous run or the source directories have been pointed at existing 239 | contents the developer has provided manually (using options described 240 | further below). When the developer knows that no changes have been made to 241 | any content details, turning this option ``ON`` can significantly speed up 242 | the configure stage. It is ``OFF`` by default. 243 | 244 | ``FETCHCONTENT_UPDATES_DISCONNECTED`` 245 | This is a less severe download/update control compared to 246 | ``FETCHCONTENT_FULLY_DISCONNECTED``. Instead of bypassing all download and 247 | update logic, the ``FETCHCONTENT_UPDATES_DISCONNECTED`` only disables the 248 | update stage. Therefore, if content has not been downloaded previously, 249 | it will still be downloaded when this option is enabled. This can speed up 250 | the configure stage, but not as much as 251 | ``FETCHCONTENT_FULLY_DISCONNECTED``. It is ``OFF`` by default. 252 | 253 | In addition to the above cache variables, the following cache variables are 254 | also defined for each content name (```` is the uppercased value of 255 | ````): 256 | 257 | ``FETCHCONTENT_SOURCE_DIR_`` 258 | If this is set, no download or update steps are performed for the specified 259 | content and the ``_SOURCE_DIR`` variable returned to the caller is 260 | pointed at this location. This gives developers a way to have a separate 261 | checkout of the content that they can modify freely without interference 262 | from the build. The build simply uses that existing source, but it still 263 | defines ``_BINARY_DIR`` to point inside its own build area. 264 | Developers are strongly encouraged to use this mechanism rather than 265 | editing the sources populated in the default location, as changes to 266 | sources in the default location can be lost when content population details 267 | are changed by the project. 268 | 269 | ``FETCHCONTENT_UPDATES_DISCONNECTED_`` 270 | This is the per-content equivalent of 271 | ``FETCHCONTENT_UPDATES_DISCONNECTED``. If the global option or this option 272 | is ``ON``, then updates will be disabled for the named content. 273 | Disabling updates for individual content can be useful for content whose 274 | details rarely change, while still leaving other frequently changing 275 | content with updates enabled. 276 | 277 | 278 | The ``FetchContent_Populate()`` command also supports a syntax allowing the 279 | content details to be specified directly rather than using any saved 280 | details. This is more low-level and use of this form is generally to be 281 | avoided in favour of using saved content details as outlined above. 282 | Nevertheless, in certain situations it can be useful to invoke the content 283 | population as an isolated operation (typically as part of implementing some 284 | other higher level feature or when using CMake in script mode): 285 | 286 | .. code-block:: cmake 287 | 288 | FetchContent_Populate( 289 | [QUIET] 290 | [SUBBUILD_DIR ] 291 | [SOURCE_DIR ] 292 | [BINARY_DIR ] 293 | ... 294 | ) 295 | 296 | This form has a number of key differences to that where only ```` is 297 | provided: 298 | 299 | - All required population details are assumed to have been provided directly 300 | in the call to ``FetchContent_Populate()``. Any saved details for 301 | ```` are ignored. 302 | - No check is made for whether content for ```` has already been 303 | populated. 304 | - No global property is set to record that the population has occurred. 305 | - No global properties record the source or binary directories used for the 306 | populated content. 307 | - The ``FETCHCONTENT_FULLY_DISCONNECTED`` and 308 | ``FETCHCONTENT_UPDATES_DISCONNECTED`` cache variables are ignored. 309 | 310 | The ``_SOURCE_DIR`` and ``_BINARY_DIR`` variables are still 311 | returned to the caller, but since these locations are not stored as global 312 | properties when this form is used, they are only available to the calling 313 | scope and below rather than the entire project hierarchy. No 314 | ``_POPULATED`` variable is set in the caller's scope with this form. 315 | 316 | The supported options for ``FetchContent_Populate()`` are the same as those 317 | for :command:`FetchContent_Declare()`. Those few options shown just 318 | above are either specific to ``FetchContent_Populate()`` or their behavior is 319 | slightly modified from how :command:`ExternalProject_Add` treats them. 320 | 321 | ``QUIET`` 322 | The ``QUIET`` option can be given to hide the output associated with 323 | populating the specified content. If the population fails, the output will 324 | be shown regardless of whether this option was given or not so that the 325 | cause of the failure can be diagnosed. The global ``FETCHCONTENT_QUIET`` 326 | cache variable has no effect on ``FetchContent_Populate()`` calls where the 327 | content details are provided directly. 328 | 329 | ``SUBBUILD_DIR`` 330 | The ``SUBBUILD_DIR`` argument can be provided to change the location of the 331 | sub-build created to perform the population. The default value is 332 | ``${CMAKE_CURRENT_BINARY_DIR}/-subbuild`` and it would be unusual 333 | to need to override this default. If a relative path is specified, it will 334 | be interpreted as relative to :variable:`CMAKE_CURRENT_BINARY_DIR`. 335 | 336 | ``SOURCE_DIR``, ``BINARY_DIR`` 337 | The ``SOURCE_DIR`` and ``BINARY_DIR`` arguments are supported by 338 | :command:`ExternalProject_Add`, but different default values are used by 339 | ``FetchContent_Populate()``. ``SOURCE_DIR`` defaults to 340 | ``${CMAKE_CURRENT_BINARY_DIR}/-src`` and ``BINARY_DIR`` defaults to 341 | ``${CMAKE_CURRENT_BINARY_DIR}/-build``. If a relative path is 342 | specified, it will be interpreted as relative to 343 | :variable:`CMAKE_CURRENT_BINARY_DIR`. 344 | 345 | In addition to the above explicit options, any other unrecognized options are 346 | passed through unmodified to :command:`ExternalProject_Add` to perform the 347 | download, patch and update steps. The following options are explicitly 348 | prohibited (they are disabled by the ``FetchContent_Populate()`` command): 349 | 350 | - ``CONFIGURE_COMMAND`` 351 | - ``BUILD_COMMAND`` 352 | - ``INSTALL_COMMAND`` 353 | - ``TEST_COMMAND`` 354 | 355 | If using ``FetchContent_Populate()`` within CMake's script mode, be aware 356 | that the implementation sets up a sub-build which therefore requires a CMake 357 | generator and build tool to be available. If these cannot be found by 358 | default, then the :variable:`CMAKE_GENERATOR` and/or 359 | :variable:`CMAKE_MAKE_PROGRAM` variables will need to be set appropriately 360 | on the command line invoking the script. 361 | 362 | 363 | .. command:: FetchContent_GetProperties 364 | 365 | When using saved content details, a call to :command:`FetchContent_Populate` 366 | records information in global properties which can be queried at any time. 367 | This information includes the source and binary directories associated with 368 | the content and also whether or not the content population has been processed 369 | during the current configure run. 370 | 371 | .. code-block:: cmake 372 | 373 | FetchContent_GetProperties( 374 | [SOURCE_DIR ] 375 | [BINARY_DIR ] 376 | [POPULATED ] 377 | ) 378 | 379 | The ``SOURCE_DIR``, ``BINARY_DIR`` and ``POPULATED`` options can be used to 380 | specify which properties should be retrieved. Each option accepts a value 381 | which is the name of the variable in which to store that property. Most of 382 | the time though, only ```` is given, in which case the call will then 383 | set the same variables as a call to 384 | :command:`FetchContent_Populate(name) `. This allows 385 | the following canonical pattern to be used, which ensures that the relevant 386 | variables will always be defined regardless of whether or not the population 387 | has been performed elsewhere in the project already: 388 | 389 | .. code-block:: cmake 390 | 391 | FetchContent_GetProperties(foobar) 392 | if(NOT foobar_POPULATED) 393 | FetchContent_Populate(foobar) 394 | ... 395 | endif() 396 | 397 | The above pattern allows other parts of the overall project hierarchy to 398 | re-use the same content and ensure that it is only populated once. 399 | 400 | 401 | .. command:: FetchContent_MakeAvailable 402 | 403 | .. code-block:: cmake 404 | 405 | FetchContent_MakeAvailable( [...] ) 406 | 407 | This command implements the common pattern typically needed for most 408 | dependencies. It iterates over each of the named dependencies in turn 409 | and for each one it loosely follows the same 410 | :ref:`canonical pattern ` as 411 | presented at the beginning of this section. One small difference to 412 | that pattern is that it will only call :command:`add_subdirectory` on the 413 | populated content if there is a ``CMakeLists.txt`` file in its top level 414 | source directory. This allows the command to be used for dependencies 415 | that make downloaded content available at a known location but which do 416 | not need or support being added directly to the build. 417 | 418 | 419 | .. _`fetch-content-examples`: 420 | 421 | Examples 422 | ^^^^^^^^ 423 | 424 | This first fairly straightforward example ensures that some popular testing 425 | frameworks are available to the main build: 426 | 427 | .. code-block:: cmake 428 | 429 | include(FetchContent) 430 | FetchContent_Declare( 431 | googletest 432 | GIT_REPOSITORY https://github.com/google/googletest.git 433 | GIT_TAG release-1.8.0 434 | ) 435 | FetchContent_Declare( 436 | Catch2 437 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 438 | GIT_TAG v2.5.0 439 | ) 440 | 441 | # After the following call, the CMake targets defined by googletest and 442 | # Catch2 will be defined and available to the rest of the build 443 | FetchContent_MakeAvailable(googletest Catch2) 444 | 445 | 446 | In more complex project hierarchies, the dependency relationships can be more 447 | complicated. Consider a hierarchy where ``projA`` is the top level project and 448 | it depends directly on projects ``projB`` and ``projC``. Both ``projB`` and 449 | ``projC`` can be built standalone and they also both depend on another project 450 | ``projD``. ``projB`` additionally depends on ``projE``. This example assumes 451 | that all five projects are available on a company git server. The 452 | ``CMakeLists.txt`` of each project might have sections like the following: 453 | 454 | *projA*: 455 | 456 | .. code-block:: cmake 457 | 458 | include(FetchContent) 459 | FetchContent_Declare( 460 | projB 461 | GIT_REPOSITORY git@mycompany.com:git/projB.git 462 | GIT_TAG 4a89dc7e24ff212a7b5167bef7ab079d 463 | ) 464 | FetchContent_Declare( 465 | projC 466 | GIT_REPOSITORY git@mycompany.com:git/projC.git 467 | GIT_TAG 4ad4016bd1d8d5412d135cf8ceea1bb9 468 | ) 469 | FetchContent_Declare( 470 | projD 471 | GIT_REPOSITORY git@mycompany.com:git/projD.git 472 | GIT_TAG origin/integrationBranch 473 | ) 474 | FetchContent_Declare( 475 | projE 476 | GIT_REPOSITORY git@mycompany.com:git/projE.git 477 | GIT_TAG origin/release/2.3-rc1 478 | ) 479 | 480 | # Order is important, see notes in the discussion further below 481 | FetchContent_MakeAvailable(projD projB projC) 482 | 483 | *projB*: 484 | 485 | .. code-block:: cmake 486 | 487 | include(FetchContent) 488 | FetchContent_Declare( 489 | projD 490 | GIT_REPOSITORY git@mycompany.com:git/projD.git 491 | GIT_TAG 20b415f9034bbd2a2e8216e9a5c9e632 492 | ) 493 | FetchContent_Declare( 494 | projE 495 | GIT_REPOSITORY git@mycompany.com:git/projE.git 496 | GIT_TAG 68e20f674a48be38d60e129f600faf7d 497 | ) 498 | 499 | FetchContent_MakeAvailable(projD projE) 500 | 501 | *projC*: 502 | 503 | .. code-block:: cmake 504 | 505 | include(FetchContent) 506 | FetchContent_Declare( 507 | projD 508 | GIT_REPOSITORY git@mycompany.com:git/projD.git 509 | GIT_TAG 7d9a17ad2c962aa13e2fbb8043fb6b8a 510 | ) 511 | 512 | # This particular version of projD requires workarounds 513 | FetchContent_GetProperties(projD) 514 | if(NOT projd_POPULATED) 515 | FetchContent_Populate(projD) 516 | 517 | # Copy an additional/replacement file into the populated source 518 | file(COPY someFile.c DESTINATION ${projd_SOURCE_DIR}/src) 519 | 520 | add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR}) 521 | endif() 522 | 523 | A few key points should be noted in the above: 524 | 525 | - ``projB`` and ``projC`` define different content details for ``projD``, 526 | but ``projA`` also defines a set of content details for ``projD``. 527 | Because ``projA`` will define them first, the details from ``projB`` and 528 | ``projC`` will not be used. The override details defined by ``projA`` 529 | are not required to match either of those from ``projB`` or ``projC``, but 530 | it is up to the higher level project to ensure that the details it does 531 | define still make sense for the child projects. 532 | - In the ``projA`` call to :command:`FetchContent_MakeAvailable`, ``projD`` 533 | is listed ahead of ``projB`` and ``projC`` to ensure that ``projA`` is in 534 | control of how ``projD`` is populated. 535 | - While ``projA`` defines content details for ``projE``, it does not need 536 | to explicitly call ``FetchContent_MakeAvailable(projE)`` or 537 | ``FetchContent_Populate(projD)`` itself. Instead, it leaves that to the 538 | child ``projB``. For higher level projects, it is often enough to just 539 | define the override content details and leave the actual population to the 540 | child projects. This saves repeating the same thing at each level of the 541 | project hierarchy unnecessarily. 542 | 543 | 544 | Projects don't always need to add the populated content to the build. 545 | Sometimes the project just wants to make the downloaded content available at 546 | a predictable location. The next example ensures that a set of standard 547 | company toolchain files (and potentially even the toolchain binaries 548 | themselves) is available early enough to be used for that same build. 549 | 550 | .. code-block:: cmake 551 | 552 | cmake_minimum_required(VERSION 3.14) 553 | 554 | include(FetchContent) 555 | FetchContent_Declare( 556 | mycom_toolchains 557 | URL https://intranet.mycompany.com//toolchains_1.3.2.tar.gz 558 | ) 559 | FetchContent_MakeAvailable(mycom_toolchains) 560 | 561 | project(CrossCompileExample) 562 | 563 | The project could be configured to use one of the downloaded toolchains like 564 | so: 565 | 566 | .. code-block:: shell 567 | 568 | cmake -DCMAKE_TOOLCHAIN_FILE=_deps/mycom_toolchains-src/toolchain_arm.cmake /path/to/src 569 | 570 | When CMake processes the ``CMakeLists.txt`` file, it will download and unpack 571 | the tarball into ``_deps/mycompany_toolchains-src`` relative to the build 572 | directory. The :variable:`CMAKE_TOOLCHAIN_FILE` variable is not used until 573 | the :command:`project` command is reached, at which point CMake looks for the 574 | named toolchain file relative to the build directory. Because the tarball has 575 | already been downloaded and unpacked by then, the toolchain file will be in 576 | place, even the very first time that ``cmake`` is run in the build directory. 577 | 578 | Lastly, the following example demonstrates how one might download and unpack a 579 | firmware tarball using CMake's :manual:`script mode `. The call to 580 | :command:`FetchContent_Populate` specifies all the content details and the 581 | unpacked firmware will be placed in a ``firmware`` directory below the 582 | current working directory. 583 | 584 | *getFirmware.cmake*: 585 | 586 | .. code-block:: cmake 587 | 588 | # NOTE: Intended to be run in script mode with cmake -P 589 | include(FetchContent) 590 | FetchContent_Populate( 591 | firmware 592 | URL https://mycompany.com/assets/firmware-1.23-arm.tar.gz 593 | URL_HASH MD5=68247684da89b608d466253762b0ff11 594 | SOURCE_DIR firmware 595 | ) 596 | 597 | #]=======================================================================] 598 | 599 | 600 | set(__FetchContent_privateDir "${CMAKE_CURRENT_LIST_DIR}/FetchContent") 601 | 602 | #======================================================================= 603 | # Recording and retrieving content details for later population 604 | #======================================================================= 605 | 606 | # Internal use, projects must not call this directly. It is 607 | # intended for use by FetchContent_Declare() only. 608 | # 609 | # Sets a content-specific global property (not meant for use 610 | # outside of functions defined here in this file) which can later 611 | # be retrieved using __FetchContent_getSavedDetails() with just the 612 | # same content name. If there is already a value stored in the 613 | # property, it is left unchanged and this call has no effect. 614 | # This allows parent projects to define the content details, 615 | # overriding anything a child project may try to set (properties 616 | # are not cached between runs, so the first thing to set it in a 617 | # build will be in control). 618 | function(__FetchContent_declareDetails contentName) 619 | 620 | string(TOLOWER ${contentName} contentNameLower) 621 | set(propertyName "_FetchContent_${contentNameLower}_savedDetails") 622 | get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED) 623 | if(NOT alreadyDefined) 624 | define_property(GLOBAL PROPERTY ${propertyName} 625 | BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" 626 | FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" 627 | ) 628 | set_property(GLOBAL PROPERTY ${propertyName} ${ARGN}) 629 | endif() 630 | 631 | endfunction() 632 | 633 | 634 | # Internal use, projects must not call this directly. It is 635 | # intended for use by the FetchContent_Declare() function. 636 | # 637 | # Retrieves details saved for the specified content in an 638 | # earlier call to __FetchContent_declareDetails(). 639 | function(__FetchContent_getSavedDetails contentName outVar) 640 | 641 | string(TOLOWER ${contentName} contentNameLower) 642 | set(propertyName "_FetchContent_${contentNameLower}_savedDetails") 643 | get_property(alreadyDefined GLOBAL PROPERTY ${propertyName} DEFINED) 644 | if(NOT alreadyDefined) 645 | message(FATAL_ERROR "No content details recorded for ${contentName}") 646 | endif() 647 | get_property(propertyValue GLOBAL PROPERTY ${propertyName}) 648 | set(${outVar} "${propertyValue}" PARENT_SCOPE) 649 | 650 | endfunction() 651 | 652 | 653 | # Saves population details of the content, sets defaults for the 654 | # SOURCE_DIR and BUILD_DIR. 655 | function(FetchContent_Declare contentName) 656 | 657 | set(options "") 658 | set(oneValueArgs SVN_REPOSITORY) 659 | set(multiValueArgs "") 660 | 661 | cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 662 | 663 | unset(srcDirSuffix) 664 | unset(svnRepoArgs) 665 | if(ARG_SVN_REPOSITORY) 666 | # Add a hash of the svn repository URL to the source dir. This works 667 | # around the problem where if the URL changes, the download would 668 | # fail because it tries to checkout/update rather than switch the 669 | # old URL to the new one. We limit the hash to the first 7 characters 670 | # so that the source path doesn't get overly long (which can be a 671 | # problem on windows due to path length limits). 672 | string(SHA1 urlSHA ${ARG_SVN_REPOSITORY}) 673 | string(SUBSTRING ${urlSHA} 0 7 urlSHA) 674 | set(srcDirSuffix "-${urlSHA}") 675 | set(svnRepoArgs SVN_REPOSITORY ${ARG_SVN_REPOSITORY}) 676 | endif() 677 | 678 | string(TOLOWER ${contentName} contentNameLower) 679 | __FetchContent_declareDetails( 680 | ${contentNameLower} 681 | SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src${srcDirSuffix}" 682 | BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build" 683 | ${svnRepoArgs} 684 | # List these last so they can override things we set above 685 | ${ARG_UNPARSED_ARGUMENTS} 686 | ) 687 | 688 | endfunction() 689 | 690 | 691 | #======================================================================= 692 | # Set/get whether the specified content has been populated yet. 693 | # The setter also records the source and binary dirs used. 694 | #======================================================================= 695 | 696 | # Internal use, projects must not call this directly. It is 697 | # intended for use by the FetchContent_Populate() function to 698 | # record when FetchContent_Populate() is called for a particular 699 | # content name. 700 | function(__FetchContent_setPopulated contentName sourceDir binaryDir) 701 | 702 | string(TOLOWER ${contentName} contentNameLower) 703 | set(prefix "_FetchContent_${contentNameLower}") 704 | 705 | set(propertyName "${prefix}_sourceDir") 706 | define_property(GLOBAL PROPERTY ${propertyName} 707 | BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" 708 | FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" 709 | ) 710 | set_property(GLOBAL PROPERTY ${propertyName} ${sourceDir}) 711 | 712 | set(propertyName "${prefix}_binaryDir") 713 | define_property(GLOBAL PROPERTY ${propertyName} 714 | BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" 715 | FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" 716 | ) 717 | set_property(GLOBAL PROPERTY ${propertyName} ${binaryDir}) 718 | 719 | set(propertyName "${prefix}_populated") 720 | define_property(GLOBAL PROPERTY ${propertyName} 721 | BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" 722 | FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" 723 | ) 724 | set_property(GLOBAL PROPERTY ${propertyName} True) 725 | 726 | endfunction() 727 | 728 | 729 | # Set variables in the calling scope for any of the retrievable 730 | # properties. If no specific properties are requested, variables 731 | # will be set for all retrievable properties. 732 | # 733 | # This function is intended to also be used by projects as the canonical 734 | # way to detect whether they should call FetchContent_Populate() 735 | # and pull the populated source into the build with add_subdirectory(), 736 | # if they are using the populated content in that way. 737 | function(FetchContent_GetProperties contentName) 738 | 739 | string(TOLOWER ${contentName} contentNameLower) 740 | 741 | set(options "") 742 | set(oneValueArgs SOURCE_DIR BINARY_DIR POPULATED) 743 | set(multiValueArgs "") 744 | 745 | cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 746 | 747 | if(NOT ARG_SOURCE_DIR AND 748 | NOT ARG_BINARY_DIR AND 749 | NOT ARG_POPULATED) 750 | # No specific properties requested, provide them all 751 | set(ARG_SOURCE_DIR ${contentNameLower}_SOURCE_DIR) 752 | set(ARG_BINARY_DIR ${contentNameLower}_BINARY_DIR) 753 | set(ARG_POPULATED ${contentNameLower}_POPULATED) 754 | endif() 755 | 756 | set(prefix "_FetchContent_${contentNameLower}") 757 | 758 | if(ARG_SOURCE_DIR) 759 | set(propertyName "${prefix}_sourceDir") 760 | get_property(value GLOBAL PROPERTY ${propertyName}) 761 | if(value) 762 | set(${ARG_SOURCE_DIR} ${value} PARENT_SCOPE) 763 | endif() 764 | endif() 765 | 766 | if(ARG_BINARY_DIR) 767 | set(propertyName "${prefix}_binaryDir") 768 | get_property(value GLOBAL PROPERTY ${propertyName}) 769 | if(value) 770 | set(${ARG_BINARY_DIR} ${value} PARENT_SCOPE) 771 | endif() 772 | endif() 773 | 774 | if(ARG_POPULATED) 775 | set(propertyName "${prefix}_populated") 776 | get_property(value GLOBAL PROPERTY ${propertyName} DEFINED) 777 | set(${ARG_POPULATED} ${value} PARENT_SCOPE) 778 | endif() 779 | 780 | endfunction() 781 | 782 | 783 | #======================================================================= 784 | # Performing the population 785 | #======================================================================= 786 | 787 | # The value of contentName will always have been lowercased by the caller. 788 | # All other arguments are assumed to be options that are understood by 789 | # ExternalProject_Add(), except for QUIET and SUBBUILD_DIR. 790 | function(__FetchContent_directPopulate contentName) 791 | 792 | set(options 793 | QUIET 794 | ) 795 | set(oneValueArgs 796 | SUBBUILD_DIR 797 | SOURCE_DIR 798 | BINARY_DIR 799 | # Prevent the following from being passed through 800 | CONFIGURE_COMMAND 801 | BUILD_COMMAND 802 | INSTALL_COMMAND 803 | TEST_COMMAND 804 | # We force both of these to be ON since we are always executing serially 805 | # and we want all steps to have access to the terminal in case they 806 | # need input from the command line (e.g. ask for a private key password) 807 | # or they want to provide timely progress. We silently absorb and 808 | # discard these if they are set by the caller. 809 | USES_TERMINAL_DOWNLOAD 810 | USES_TERMINAL_UPDATE 811 | ) 812 | set(multiValueArgs "") 813 | 814 | cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 815 | 816 | if(NOT ARG_SUBBUILD_DIR) 817 | message(FATAL_ERROR "Internal error: SUBBUILD_DIR not set") 818 | elseif(NOT IS_ABSOLUTE "${ARG_SUBBUILD_DIR}") 819 | set(ARG_SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SUBBUILD_DIR}") 820 | endif() 821 | 822 | if(NOT ARG_SOURCE_DIR) 823 | message(FATAL_ERROR "Internal error: SOURCE_DIR not set") 824 | elseif(NOT IS_ABSOLUTE "${ARG_SOURCE_DIR}") 825 | set(ARG_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_SOURCE_DIR}") 826 | endif() 827 | 828 | if(NOT ARG_BINARY_DIR) 829 | message(FATAL_ERROR "Internal error: BINARY_DIR not set") 830 | elseif(NOT IS_ABSOLUTE "${ARG_BINARY_DIR}") 831 | set(ARG_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${ARG_BINARY_DIR}") 832 | endif() 833 | 834 | # Ensure the caller can know where to find the source and build directories 835 | # with some convenient variables. Doing this here ensures the caller sees 836 | # the correct result in the case where the default values are overridden by 837 | # the content details set by the project. 838 | set(${contentName}_SOURCE_DIR "${ARG_SOURCE_DIR}" PARENT_SCOPE) 839 | set(${contentName}_BINARY_DIR "${ARG_BINARY_DIR}" PARENT_SCOPE) 840 | 841 | # The unparsed arguments may contain spaces, so build up ARG_EXTRA 842 | # in such a way that it correctly substitutes into the generated 843 | # CMakeLists.txt file with each argument quoted. 844 | unset(ARG_EXTRA) 845 | foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS) 846 | set(ARG_EXTRA "${ARG_EXTRA} \"${arg}\"") 847 | endforeach() 848 | 849 | # Hide output if requested, but save it to a variable in case there's an 850 | # error so we can show the output upon failure. When not quiet, don't 851 | # capture the output to a variable because the user may want to see the 852 | # output as it happens (e.g. progress during long downloads). Combine both 853 | # stdout and stderr in the one capture variable so the output stays in order. 854 | if (ARG_QUIET) 855 | set(outputOptions 856 | OUTPUT_VARIABLE capturedOutput 857 | ERROR_VARIABLE capturedOutput 858 | ) 859 | else() 860 | set(capturedOutput) 861 | set(outputOptions) 862 | message(STATUS "Populating ${contentName}") 863 | endif() 864 | 865 | if(CMAKE_GENERATOR) 866 | set(generatorOpts "-G${CMAKE_GENERATOR}") 867 | if(CMAKE_GENERATOR_PLATFORM) 868 | list(APPEND generatorOpts "-A${CMAKE_GENERATOR_PLATFORM}") 869 | endif() 870 | if(CMAKE_GENERATOR_TOOLSET) 871 | list(APPEND generatorOpts "-T${CMAKE_GENERATOR_TOOLSET}") 872 | endif() 873 | 874 | if(CMAKE_MAKE_PROGRAM) 875 | list(APPEND generatorOpts "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}") 876 | endif() 877 | 878 | else() 879 | # Likely we've been invoked via CMake's script mode where no 880 | # generator is set (and hence CMAKE_MAKE_PROGRAM could not be 881 | # trusted even if provided). We will have to rely on being 882 | # able to find the default generator and build tool. 883 | unset(generatorOpts) 884 | endif() 885 | 886 | # Create and build a separate CMake project to carry out the population. 887 | # If we've already previously done these steps, they will not cause 888 | # anything to be updated, so extra rebuilds of the project won't occur. 889 | # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project 890 | # has this set to something not findable on the PATH. 891 | configure_file("${__FetchContent_privateDir}/CMakeLists.cmake.in" 892 | "${ARG_SUBBUILD_DIR}/CMakeLists.txt") 893 | execute_process( 894 | COMMAND ${CMAKE_COMMAND} ${generatorOpts} . 895 | RESULT_VARIABLE result 896 | ${outputOptions} 897 | WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" 898 | ) 899 | if(result) 900 | if(capturedOutput) 901 | message("${capturedOutput}") 902 | endif() 903 | message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}") 904 | endif() 905 | execute_process( 906 | COMMAND ${CMAKE_COMMAND} --build . 907 | RESULT_VARIABLE result 908 | ${outputOptions} 909 | WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" 910 | ) 911 | if(result) 912 | if(capturedOutput) 913 | message("${capturedOutput}") 914 | endif() 915 | message(FATAL_ERROR "Build step for ${contentName} failed: ${result}") 916 | endif() 917 | 918 | endfunction() 919 | 920 | 921 | option(FETCHCONTENT_FULLY_DISCONNECTED "Disables all attempts to download or update content and assumes source dirs already exist") 922 | option(FETCHCONTENT_UPDATES_DISCONNECTED "Enables UPDATE_DISCONNECTED behavior for all content population") 923 | option(FETCHCONTENT_QUIET "Enables QUIET option for all content population" ON) 924 | set(FETCHCONTENT_BASE_DIR "${CMAKE_BINARY_DIR}/_deps" CACHE PATH "Directory under which to collect all populated content") 925 | 926 | # Populate the specified content using details stored from 927 | # an earlier call to FetchContent_Declare(). 928 | function(FetchContent_Populate contentName) 929 | 930 | if(NOT contentName) 931 | message(FATAL_ERROR "Empty contentName not allowed for FetchContent_Populate()") 932 | endif() 933 | 934 | string(TOLOWER ${contentName} contentNameLower) 935 | 936 | if(ARGN) 937 | # This is the direct population form with details fully specified 938 | # as part of the call, so we already have everything we need 939 | __FetchContent_directPopulate( 940 | ${contentNameLower} 941 | SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-subbuild" 942 | SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-src" 943 | BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/${contentNameLower}-build" 944 | ${ARGN} # Could override any of the above ..._DIR variables 945 | ) 946 | 947 | # Pass source and binary dir variables back to the caller 948 | set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE) 949 | set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE) 950 | 951 | # Don't set global properties, or record that we did this population, since 952 | # this was a direct call outside of the normal declared details form. 953 | # We only want to save values in the global properties for content that 954 | # honours the hierarchical details mechanism so that projects are not 955 | # robbed of the ability to override details set in nested projects. 956 | return() 957 | endif() 958 | 959 | # No details provided, so assume they were saved from an earlier call 960 | # to FetchContent_Declare(). Do a check that we haven't already 961 | # populated this content before in case the caller forgot to check. 962 | FetchContent_GetProperties(${contentName}) 963 | if(${contentNameLower}_POPULATED) 964 | message(FATAL_ERROR "Content ${contentName} already populated in ${${contentNameLower}_SOURCE_DIR}") 965 | endif() 966 | 967 | string(TOUPPER ${contentName} contentNameUpper) 968 | set(FETCHCONTENT_SOURCE_DIR_${contentNameUpper} 969 | "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}" 970 | CACHE PATH "When not empty, overrides where to find pre-populated content for ${contentName}") 971 | 972 | if(FETCHCONTENT_SOURCE_DIR_${contentNameUpper}) 973 | # The source directory has been explicitly provided in the cache, 974 | # so no population is required 975 | set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_SOURCE_DIR_${contentNameUpper}}") 976 | set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build") 977 | 978 | elseif(FETCHCONTENT_FULLY_DISCONNECTED) 979 | # Bypass population and assume source is already there from a previous run 980 | set(${contentNameLower}_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src") 981 | set(${contentNameLower}_BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build") 982 | 983 | else() 984 | # Support both a global "disconnect all updates" and a per-content 985 | # update test (either one being set disables updates for this content). 986 | option(FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper} 987 | "Enables UPDATE_DISCONNECTED behavior just for population of ${contentName}") 988 | if(FETCHCONTENT_UPDATES_DISCONNECTED OR 989 | FETCHCONTENT_UPDATES_DISCONNECTED_${contentNameUpper}) 990 | set(disconnectUpdates True) 991 | else() 992 | set(disconnectUpdates False) 993 | endif() 994 | 995 | if(FETCHCONTENT_QUIET) 996 | set(quietFlag QUIET) 997 | else() 998 | unset(quietFlag) 999 | endif() 1000 | 1001 | __FetchContent_getSavedDetails(${contentName} contentDetails) 1002 | if("${contentDetails}" STREQUAL "") 1003 | message(FATAL_ERROR "No details have been set for content: ${contentName}") 1004 | endif() 1005 | 1006 | __FetchContent_directPopulate( 1007 | ${contentNameLower} 1008 | ${quietFlag} 1009 | UPDATE_DISCONNECTED ${disconnectUpdates} 1010 | SUBBUILD_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-subbuild" 1011 | SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-src" 1012 | BINARY_DIR "${FETCHCONTENT_BASE_DIR}/${contentNameLower}-build" 1013 | # Put the saved details last so they can override any of the 1014 | # the options we set above (this can include SOURCE_DIR or 1015 | # BUILD_DIR) 1016 | ${contentDetails} 1017 | ) 1018 | endif() 1019 | 1020 | __FetchContent_setPopulated( 1021 | ${contentName} 1022 | ${${contentNameLower}_SOURCE_DIR} 1023 | ${${contentNameLower}_BINARY_DIR} 1024 | ) 1025 | 1026 | # Pass variables back to the caller. The variables passed back here 1027 | # must match what FetchContent_GetProperties() sets when it is called 1028 | # with just the content name. 1029 | set(${contentNameLower}_SOURCE_DIR "${${contentNameLower}_SOURCE_DIR}" PARENT_SCOPE) 1030 | set(${contentNameLower}_BINARY_DIR "${${contentNameLower}_BINARY_DIR}" PARENT_SCOPE) 1031 | set(${contentNameLower}_POPULATED True PARENT_SCOPE) 1032 | 1033 | endfunction() 1034 | 1035 | # Arguments are assumed to be the names of dependencies that have been 1036 | # declared previously and should be populated. It is not an error if 1037 | # any of them have already been populated (they will just be skipped in 1038 | # that case). The command is implemented as a macro so that the variables 1039 | # defined by the FetchContent_GetProperties() and FetchContent_Populate() 1040 | # calls will be available to the caller. 1041 | macro(FetchContent_MakeAvailable) 1042 | 1043 | foreach(contentName IN ITEMS ${ARGV}) 1044 | string(TOLOWER ${contentName} contentNameLower) 1045 | FetchContent_GetProperties(${contentName}) 1046 | if(NOT ${contentNameLower}_POPULATED) 1047 | FetchContent_Populate(${contentName}) 1048 | 1049 | # Only try to call add_subdirectory() if the populated content 1050 | # can be treated that way. Protecting the call with the check 1051 | # allows this function to be used for projects that just want 1052 | # to ensure the content exists, such as to provide content at 1053 | # a known location. 1054 | if(EXISTS ${${contentNameLower}_SOURCE_DIR}/CMakeLists.txt) 1055 | add_subdirectory(${${contentNameLower}_SOURCE_DIR} 1056 | ${${contentNameLower}_BINARY_DIR}) 1057 | endif() 1058 | endif() 1059 | endforeach() 1060 | 1061 | endmacro() --------------------------------------------------------------------------------