|
| 1 | +# See LICENSE_ENZO file for license and copyright information |
| 2 | + |
| 3 | +#.rst: |
| 4 | +# CreateStdFilesystemTarget |
| 5 | +# ------------------------- |
| 6 | +# |
| 7 | +# This module defines the create_StdFilesystem_target function |
| 8 | +# |
| 9 | +# That function: |
| 10 | +# - confirms that the chosen compiler supports the file system library included |
| 11 | +# in the standard library starting in C++17 |
| 12 | +# - defines the StdFilesystem::StdFilesystem library to communicate extra usage |
| 13 | +# requirements |
| 14 | +# |
| 15 | +# In 99% of cases, there shouldn't be any usage requirements. The main |
| 16 | +# exception relates to the implementations of the standard libraries that were |
| 17 | +# available around the time that the c++17 standard was ratified. For example: |
| 18 | +# - libstdc++ (GNU's implementation) required the `-lstdc++fs` flag |
| 19 | +# - libc++ (LLVM's implementation) required the `-lc++fs` flag |
| 20 | +# This extra argument mirrors the way in which implementations of libc on unix |
| 21 | +# systems may require the `-lm` flag when using math functions. |
| 22 | +# |
| 23 | +# At the time of writing this, much of the extra functionality for enabling |
| 24 | +# compatability with these older versions is hypothetical (it hasn't been |
| 25 | +# tested). |
| 26 | +# |
| 27 | +# FUTURE WORK |
| 28 | +# It may be worth asking "do we want to support these older versions?" Or do we |
| 29 | +# simply want to test if things "just work" out of the box? If things break in |
| 30 | +# the latter case, we could just say what's probably going wrong and tell the |
| 31 | +# user that they probably need to upgrade. As time progresses, the latter |
| 32 | +# option will become more appealing (and delete ~50% of this file's logic) |
| 33 | + |
| 34 | +function(_detect_stdlib_impl output_variable) |
| 35 | + # writes the name of the standard library implementation to the variable name |
| 36 | + # specifed by output_variable |
| 37 | + # -> known options include |
| 38 | + # "libstdc++" (The GNU implementation) |
| 39 | + # "libc++" (The LLVM implementation) |
| 40 | + # "unknown" |
| 41 | + # -> Note: the standard library doesn't have to match the compiler (e.g. |
| 42 | + # clang on linux often uses libstdc++) |
| 43 | + |
| 44 | + set(test_file |
| 45 | + ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/std-fs/stdimpl.cpp) |
| 46 | + |
| 47 | + # this detection stategy, won't work for libstdc++ older than 6.1 |
| 48 | + # (but that's okay, such versions are older than C++17) |
| 49 | + file(WRITE ${test_file} |
| 50 | +" |
| 51 | +// include headers defining implementation-specific macros (since we include |
| 52 | +// <iostream>, this may not be strictly necessary) |
| 53 | +#if __has_include(<ciso646>) |
| 54 | + // pre c++20, this header is commonly included for std library implementation |
| 55 | + // macros: https://en.cppreference.com/w/cpp/header/version |
| 56 | + #include <ciso646> |
| 57 | +#else |
| 58 | + #include <version> |
| 59 | +#endif |
| 60 | +
|
| 61 | +#include <iostream> |
| 62 | +int main(int argc, char* argv[]){ |
| 63 | +#ifdef __GLIBCXX__ |
| 64 | + std::cout << \"libstdc++\\n\"; |
| 65 | +#elif defined(_LIBCPP_VERSION) |
| 66 | + std::cout << \"libc++\\n\"; |
| 67 | +#else |
| 68 | + std::cout << \"unknown\\n\"; |
| 69 | +#endif |
| 70 | + return 0; |
| 71 | +}") |
| 72 | + |
| 73 | + try_run(exit_code compile_success ${CMAKE_BINARY_DIR} ${test_file} |
| 74 | + RUN_OUTPUT_VARIABLE test_output |
| 75 | + ) |
| 76 | + |
| 77 | + if(compile_success AND (exit_code EQUAL 0)) # successful test |
| 78 | + string(STRIP "${test_output}" stripped_implementation_name) |
| 79 | + set(${output_variable} "${stripped_implementation_name}" PARENT_SCOPE) |
| 80 | + else() |
| 81 | + message(FATAL_ERROR "Something unexpected happened") |
| 82 | + endif() |
| 83 | +endfunction() |
| 84 | + |
| 85 | + |
| 86 | +function(_StdFilesystem_try_compile link_library output_variable) |
| 87 | + # compile & run a test program with std::filesystem |
| 88 | + # -> stores a value of 1 or 0 (denoting success) in the output_variable |
| 89 | + |
| 90 | + set(test_file |
| 91 | + ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/std-fs/test.cpp) |
| 92 | + |
| 93 | + # the whole point is for this to be extremely simple |
| 94 | + file(WRITE ${test_file} |
| 95 | +" |
| 96 | +#include <filesystem> |
| 97 | +#include <iostream> |
| 98 | +
|
| 99 | +int main(int argc, char* argv[]){ |
| 100 | + std::cout << std::filesystem::temp_directory_path(); |
| 101 | + return 0; |
| 102 | +}") |
| 103 | + |
| 104 | + if (link_library STREQUAL "") |
| 105 | + try_run(exit_code compile_success ${CMAKE_BINARY_DIR} ${test_file} |
| 106 | + RUN_OUTPUT_VARIABLE test_output |
| 107 | + ) |
| 108 | + else() |
| 109 | + try_run(exit_code compile_success ${CMAKE_BINARY_DIR} ${test_file} |
| 110 | + LINK_LIBRARIES ${link_library} |
| 111 | + RUN_OUTPUT_VARIABLE test_output |
| 112 | + ) |
| 113 | + endif() |
| 114 | + |
| 115 | + if(compile_success AND (exit_code EQUAL 0)) # successful test |
| 116 | + set(${output_variable} "1" PARENT_SCOPE) |
| 117 | + else() |
| 118 | + set(${output_variable} "0" PARENT_SCOPE) |
| 119 | + endif() |
| 120 | +endfunction() |
| 121 | + |
| 122 | +function(_create_StdFilesystem_target_helper link_library) |
| 123 | + add_library(StdFilesystem::StdFilesystem INTERFACE IMPORTED) |
| 124 | + if(NOT ${link_library} STREQUAL "") |
| 125 | + target_link_libraries(StdFilesystem::StdFilesystem |
| 126 | + INTERFACE "${link_library}") |
| 127 | + endif() |
| 128 | +endfunction() |
| 129 | + |
| 130 | +function(create_StdFilesystem_target) |
| 131 | + # this does the heavy lifting |
| 132 | + if(TARGET StdFilesystem::StdFilesystem) |
| 133 | + return() |
| 134 | + elseif(DEFINED CACHE{__StdFilesystem_LINKLIBRARY}) |
| 135 | + _create_StdFilesystem_target_helper("${__StdFilesystem_LINKLIBRARY}") |
| 136 | + return() |
| 137 | + endif() |
| 138 | + |
| 139 | + set(BASE_MSG "Checking std::filesystem support") |
| 140 | + message(STATUS "${BASE_MSG}") |
| 141 | + |
| 142 | + # first, we try without any link-library |
| 143 | + set(link_library "") |
| 144 | + set(compile_success "0") |
| 145 | + _StdFilesystem_try_compile("${link_library}" compile_success) |
| 146 | + |
| 147 | + if (compile_success) |
| 148 | + set(SUCCESS_MSG "works out of the box") |
| 149 | + else() |
| 150 | + # first provide a nice error message for specific compilers |
| 151 | + if(CMAKE_CXX_COMPILER_VERSION STREQUAL "AppleClang") |
| 152 | + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.0.0") |
| 153 | + message(FATAL_ERROR |
| 154 | + "Version ${CMAKE_CXX_COMPILER_VERSION} of Apple's Clang compiler " |
| 155 | + "does NOT support std::filesystem. Please upgrade to version " |
| 156 | + "11.0.0 or newer (you probably have to update Xcode). Alternatively " |
| 157 | + "install a different c++ compiler on your system") |
| 158 | + else() |
| 159 | + message(FATAL_ERROR "Unclear why Apple's Clang compiler isn't working") |
| 160 | + endif() |
| 161 | + endif() |
| 162 | + |
| 163 | + # gracefully handle cases where the user has an older version of a |
| 164 | + # standard-library implementation that requires extra compiler args |
| 165 | + # - NOTE: the precise implementation does not necessarily correspond to the |
| 166 | + # compiler (e.g. GNU's implementation is commonly used with clang++) |
| 167 | + _detect_stdlib_impl(stdlib_impl) |
| 168 | + |
| 169 | + if (stdlib_impl STREQUAL "libstdc++") |
| 170 | + # this is the GNU implementation of the standard library |
| 171 | + set(link_library "stdc++fs") |
| 172 | + set(min_req_vers "8.0.0") |
| 173 | + set(link_lib_unneeded_vers "9.1") |
| 174 | + _StdFilesystem_try_compile("${link_library}" compile_success) |
| 175 | + elseif (stdlib_impl STREQUAL "libc++") |
| 176 | + # this is the LLVM implementation of the standard library |
| 177 | + set(link_library "c++fs") |
| 178 | + set(min_req_vers "7") |
| 179 | + set(link_lib_unneeded_vers "9") |
| 180 | + _StdFilesystem_try_compile("${link_library}" compile_success) |
| 181 | + else() |
| 182 | + message(FATAL_ERROR |
| 183 | + "std::filesystem doesn't work and the implementation of the standard" |
| 184 | + "library can't be determined (it's not a recent version of libstdc++ " |
| 185 | + "or libc++). It's likely that: the implementation doesn't support " |
| 186 | + "std::filesystem OR we may need to pass an extra linker flag to use " |
| 187 | + "std::filesystem (as is required by earlier versions of libstdc++ or " |
| 188 | + "libc++)") |
| 189 | + endif() |
| 190 | + |
| 191 | + if(NOT compile_success) |
| 192 | + message(FATAL_ERROR |
| 193 | + "std::filesystem doesn't work. You appear to be using the " |
| 194 | + "${stdlib_impl} implementation of the standard library. Note that a " |
| 195 | + "version of at least ${min_req_vers} is required.") |
| 196 | + endif() |
| 197 | + set(SUCCESS_MSG |
| 198 | + "works with the linker flag -l${link_library} (this is requirement of " |
| 199 | + "${stdlib_impl} before version ${link_lib_unneeded_vers}") |
| 200 | + endif() |
| 201 | + |
| 202 | + message(STATUS "${BASE_MSG} -- ${SUCCESS_MSG}") |
| 203 | + |
| 204 | + set(__StdFilesystem_LINKLIBRARY "${link_library}" CACHE INTERNAL |
| 205 | + "empty-string or library we must link against to use std::filesystem") |
| 206 | + _create_StdFilesystem_target_helper("${__StdFilesystem_LINKLIBRARY}") |
| 207 | + |
| 208 | +endfunction() |
0 commit comments