Created
January 13, 2020 20:18
-
-
Save peci1/95179b9743b4874bb9e6af41bb47bf4d to your computer and use it in GitHub Desktop.
CMake module for using the most modern of std::filesystem, std::experimental::filesystem and boost::filesystem.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Distributed under the OSI-approved BSD 3-Clause License. | |
# Base on https://github.com/vector-of-bool/CMakeCM/blob/master/modules/FindFilesystem.cmake | |
#[=======================================================================[.rst: | |
FindFilesystem | |
############## | |
This module supports the C++17 standard library's filesystem utilities. Use the | |
:imp-target:`std::filesystem` imported target to enable support of it. | |
Options | |
******* | |
The ``COMPONENTS`` argument to this module supports the following values: | |
.. find-component:: Boost | |
:name: fs.Boost | |
Allows the module to find the "boost" Filesystem TS version of the | |
Filesystem library. This is the library that should be used with the | |
``boost::filesystem`` namespace. | |
.. find-component:: Experimental | |
:name: fs.Experimental | |
Allows the module to find the "experimental" Filesystem TS version of the | |
Filesystem library. This is the library that should be used with the | |
``std::experimental::filesystem`` namespace. | |
.. find-component:: Final | |
:name: fs.Final | |
Finds the final C++17 standard version of the filesystem library. | |
If no components are provided, behaves as if the | |
:find-component:`fs.Final` component was specified. | |
If more components are defined, it first looks for ``Final``, then falls back to | |
``Experimental``, and then to ``Boost``. If a component is found, all remaining | |
components are skipped. | |
Imported Targets | |
**************** | |
.. imp-target:: std::filesystem | |
The ``std::filesystem`` imported target is defined when any requested | |
version of the C++ filesystem library has been found, whether it is | |
*Experimental* or *Final*. | |
If no version of the filesystem library is available, this target will not | |
be defined. | |
The target defines all variables from the following section as preprocessor | |
directives so that they can be used in the sources. | |
.. note:: | |
For example, you can do the following in your .cpp file: | |
#include CXX_FILESYSTEM_INCLUDE | |
namespace fs = CXX_FILESYSTEM_NAMESPACE; | |
.. note:: | |
This target can have ``cxx_std_17`` as an ``INTERFACE`` | |
:ref:`compile language standard feature <req-lang-standards>`. Linking | |
to this target will automatically enable C++17 if no later standard | |
version is already required on the linking target and the found | |
component is not ``Boost``. | |
.. _fs.variables: | |
Variables | |
********* | |
.. variable:: CXX_FILESYSTEM_TYPE | |
Set to ``Final`` when the :find-component:`fs.Final` version of C++ | |
filesystem library was found and so on for ``Experimental`` and ``Boost``. | |
.. variable:: CXX_FILESYSTEM_HAVE_FS | |
Set to ``TRUE`` when a filesystem header was found. | |
.. variable:: CXX_FILESYSTEM_HEADER | |
Set to either ``filesystem``, ``experimental/filesystem`` or | |
``boost/filesystem.hpp`` depending on which component was found. | |
.. variable:: CXX_FILESYSTEM_INCLUDE | |
Set to either ``<filesystem>``, ``<experimental/filesystem>`` or | |
``<boost/filesystem.hpp>`` depending on which component was found. | |
.. variable:: CXX_FILESYSTEM_NAMESPACE | |
Set to either ``std::filesystem``, ``std::experimental::filesystem`` or | |
``boost::filesystem`` depending on which component was found. | |
Examples | |
******** | |
Using `find_package(Filesystem)` with no component arguments: | |
.. code-block:: cmake | |
find_package(Filesystem REQUIRED) | |
add_executable(my-program main.cpp) | |
target_link_libraries(my-program PRIVATE std::filesystem) | |
#]=======================================================================] | |
if(TARGET std::filesystem) | |
# This module has already been processed. Don't do it again. | |
return() | |
endif() | |
include(CMakePushCheckState) | |
include(CheckIncludeFileCXX) | |
include(CheckCXXSourceCompiles) | |
cmake_push_check_state() | |
set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY}) | |
# All of our tests required C++17 or later | |
set(CMAKE_CXX_STANDARD 17) | |
cmake_policy(SET CMP0057 NEW) | |
# Normalize and check the component list we were given | |
set(want_components ${Filesystem_FIND_COMPONENTS}) | |
if(Filesystem_FIND_COMPONENTS STREQUAL "") | |
set(want_components Final) | |
endif() | |
# Warn on any unrecognized components | |
set(extra_components ${want_components}) | |
list(REMOVE_ITEM extra_components Final Experimental Boost) | |
foreach(component IN LISTS extra_components) | |
message(WARNING "Extraneous find_package component for Filesystem: ${component}") | |
endforeach() | |
# Detect which of Experimental and Final we should look for | |
set(find_boost TRUE) | |
set(find_experimental TRUE) | |
set(find_final TRUE) | |
if(NOT "Final" IN_LIST want_components) | |
set(find_final FALSE) | |
endif() | |
if(NOT "Experimental" IN_LIST want_components) | |
set(find_experimental FALSE) | |
endif() | |
if(NOT "Boost" IN_LIST want_components) | |
set(find_boost FALSE) | |
endif() | |
if(find_final) | |
check_include_file_cxx("filesystem" _CXX_FILESYSTEM_HAVE_HEADER) | |
mark_as_advanced(_CXX_FILESYSTEM_HAVE_HEADER) | |
if(_CXX_FILESYSTEM_HAVE_HEADER) | |
# We found the non-experimental header. Don't bother looking for the | |
# other ones. | |
set(find_experimental FALSE) | |
set(find_boost FALSE) | |
endif() | |
else() | |
set(_CXX_FILESYSTEM_HAVE_HEADER FALSE) | |
endif() | |
if(find_experimental) | |
check_include_file_cxx("experimental/filesystem" _CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) | |
mark_as_advanced(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) | |
if(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) | |
# We found the non-experimental header. Don't bother looking for the | |
# other ones. | |
set(find_boost FALSE) | |
endif() | |
else() | |
set(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER FALSE) | |
endif() | |
if(find_boost) | |
find_package(Boost COMPONENTS filesystem) | |
set(_BOOST_FILESYSTEM_HAVE_HEADER "${Boost_FILESYSTEM_FOUND}") | |
else() | |
set(_BOOST_FILESYSTEM_HAVE_HEADER FALSE) | |
endif() | |
if(${_CXX_FILESYSTEM_HAVE_HEADER}) | |
set(_have_fs TRUE) | |
set(_fs_header filesystem) | |
set(_fs_namespace std::filesystem) | |
set(_fs_type Final) | |
elseif(${_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER}) | |
set(_have_fs TRUE) | |
set(_fs_header experimental/filesystem) | |
set(_fs_namespace std::experimental::filesystem) | |
set(_fs_type Experimental) | |
elseif(${_BOOST_FILESYSTEM_HAVE_HEADER}) | |
set(_have_fs TRUE) | |
set(_fs_header boost/filesystem.hpp) | |
set(_fs_namespace boost::filesystem) | |
set(_fs_type Boost) | |
else() | |
set(_have_fs FALSE) | |
endif() | |
set(CXX_FILESYSTEM_HAVE_FS ${_have_fs} CACHE BOOL "TRUE if we have the C++ filesystem headers") | |
set(CXX_FILESYSTEM_HEADER ${_fs_header} CACHE STRING "The header that should be included to obtain the filesystem APIs") | |
set(CXX_FILESYSTEM_INCLUDE "<${_fs_header}>" CACHE STRING "The #include argument to use in C++ files") | |
set(CXX_FILESYSTEM_NAMESPACE ${_fs_namespace} CACHE STRING "The C++ namespace that contains the filesystem APIs") | |
set(CXX_FILESYSTEM_TYPE ${_fs_type} CACHE STRING "The component that satisfied the filesystem API") | |
set(_found FALSE) | |
if(CXX_FILESYSTEM_HAVE_FS) | |
# We have some filesystem library available. Do link checks | |
string(CONFIGURE [[ | |
#include @CXX_FILESYSTEM_INCLUDE@ | |
int main() { | |
auto cwd = @CXX_FILESYSTEM_NAMESPACE@::current_path(); | |
return static_cast<int>(cwd.string().size()); | |
} | |
]] code @ONLY) | |
# Try to compile a simple filesystem program without any linker flags | |
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED) | |
set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED}) | |
if(NOT can_link) | |
set(prev_libraries ${CMAKE_REQUIRED_LIBRARIES}) | |
# Add the libstdc++ flag | |
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lstdc++fs) | |
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_STDCPPFS_NEEDED) | |
set(can_link ${CXX_FILESYSTEM_STDCPPFS_NEEDED}) | |
if(NOT can_link) | |
# Try the libc++ flag | |
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lc++fs) | |
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_CPPFS_NEEDED) | |
set(can_link ${CXX_FILESYSTEM_CPPFS_NEEDED}) | |
if(NOT can_link AND find_boost) | |
# Try Boost | |
set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} Boost::filesystem) | |
set(prev_includes ${CMAKE_REQUIRED_INCLUDES}) | |
set(CMAKE_REQUIRED_INCLUDES ${prev_includes} ${Boost_INCLUDE_DIRS}) | |
check_cxx_source_compiles("${code}" CXX_FILESYSTEM_BOOST_NEEDED) | |
set(can_link ${CXX_FILESYSTEM_BOOST_NEEDED}) | |
endif() | |
endif() | |
endif() | |
if(can_link) | |
add_library(std::filesystem INTERFACE IMPORTED) | |
set(_found TRUE) | |
if(CXX_FILESYSTEM_NO_LINK_NEEDED) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS cxx_std_17) | |
elseif(CXX_FILESYSTEM_STDCPPFS_NEEDED) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lstdc++fs) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS cxx_std_17) | |
elseif(CXX_FILESYSTEM_CPPFS_NEEDED) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lc++fs) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS cxx_std_17) | |
elseif(CXX_FILESYSTEM_BOOST_NEEDED) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES Boost::filesystem) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) | |
endif() | |
endif() | |
endif() | |
cmake_pop_check_state() | |
set(Filesystem_FOUND ${_found} CACHE BOOL "TRUE if we can compile and link a program using std::filesystem" FORCE) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS CXX_FILESYSTEM_FOUND=${Filesystem_FOUND}) | |
if(Filesystem_FOUND) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS CXX_FILESYSTEM_HEADER=${CXX_FILESYSTEM_HEADER}) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS CXX_FILESYSTEM_INCLUDE=${CXX_FILESYSTEM_INCLUDE}) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS CXX_FILESYSTEM_NAMESPACE=${CXX_FILESYSTEM_NAMESPACE}) | |
set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS CXX_FILESYSTEM_TYPE=${CXX_FILESYSTEM_TYPE}) | |
endif() | |
if(Filesystem_FIND_REQUIRED AND NOT Filesystem_FOUND) | |
message(FATAL_ERROR "Cannot Compile simple program using std::filesystem") | |
endif() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment