I am new to CMake projects and I want to use the file system library in my project. I am running Ubuntu 18.04 with GCC 8.2 and CMake 3.13. In order to achieve this I tried t
I have found a case when try_compile
was not enough: when using the Intel C++ compiler (icpc (ICC) 19.1.1.216 20200306
) in C++17 mode running on MacOS "Mojave" 10.14.6. The test program recommended by @Ashkan compiled without errors, and it even ran. However, my code uses fs::path::filename() at one point and that resulted in a runtime linker error (dyld: lazy symbol binding failed: Symbol not found:
). In other words: the header is there, the implementation apparently isn't (?). I didn't investigate this any further.
The solution is to use try_run
instead of try_compile
and (in my case) fall back to boost::filesystem
if std::filesystem
is not yet supported.
Here is the relevant CMake code section:
try_run(RUNS_WITH_STDFS COMPILES_WITH_STDFS
"${CMAKE_BINARY_DIR}/try"
"${CMAKE_SOURCE_DIR}/cmake/has_stdfs.cc"
CMAKE_FLAGS CMAKE_CXX_STANDARD=17 CMAKE_CXX_STANDARD_REQUIRED=ON
)
if (RUNS_WITH_STDFS STREQUAL "FAILED_TO_RUN")
message(STATUS "Using boost::filesystem instead of std::filesystem")
set(_boost_components ${_boost_components} filesystem system)
add_definitions(-DUSE_BOOST_FILESYSTEM)
else()
message(STATUS "std::filesystem supported")
endif()
Note that the variable RUNS_WITH_STDFS
is not set to NO
in case of failure but to "FAILED_TO_RUN"
which is not interpreted as a FALSE
Boolean (see CMake if() docs:
if() True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number. False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in the suffix -NOTFOUND.
so I had to string-compare its value.
The little test program also changed a bit compared to @Ashkan's solution:
// == PROGRAM has_stdfs.cc ==
// Check if std::filesystem is available
// Source: https://stackoverflow.com/a/54290906
// with modifications
#include <filesystem>
namespace fs = std::filesystem;
int main(int argc, char* argv[]) {
fs::path somepath{ "dir1/dir2/filename.txt" };
auto fname = somepath.filename();
return 0;
}
Gcc 8.2. comes with <filesystem>
, so there is no need to investigate with regard to the availability. Next, option 1 is sufficient, but needs a fix:
set(CMAKE_CXX_STANDARD 17) # no need to manually adjust the CXXFLAGS
add_executable(yourExecutable yourSourceFile.cpp)
target_link_libraries(yourExecutable stdc++fs)
This should result in compiling the sources with -std=c++17
or -std=gnu++17
and adding -lstdc++fs
when linking.
Edit: Note that as @Ashkan has pointed out in the comments, setting CMAKE_CXX_STANDARD_REQUIRED to true
results in an immediate error at configure time if C++17 isn't supported by the compiler, instead of a compilation error (due to the missing <filesystem>
header) or at link time (due to the missing shared library). This might be desirable.
Besides from @lubgr's answer. I think a more complete way is to also do try_compile to see that you can actually use the filesystem header. This in my opinion is better because some compilers are not supporting std::filesystem yet. Also in gcc 7.x you have the filesystem under experimental
namespace. This way you can have a separate try_compile in the else
clause and detect that.
Here is the related cmake for it
# set everything up for c++ 17 features
set(CMAKE_CXX_STANDARD 17)
# Don't add this line if you will try_compile with boost.
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# test that filesystem header actually is there and works
try_compile(HAS_FS "${CMAKE_BINARY_DIR}/temp"
"${CMAKE_SOURCE_DIR}/tests/has_filesystem.cc"
CMAKE_FLAGS -DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD_REQUIRED=ON
LINK_LIBRARIES stdc++fs)
if(HAS_FS)
message(STATUS "Compiler has filesystem support")
else()
# .... You could also try searching for boost::filesystem here.
message(FATAL_ERROR "Compiler is missing filesystem capabilities")
endif(HAS_FS)
The file tests/has_filesystem.cc is very simple
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
fs::path aPath {"../"};
return 0;
}
You could in your else clause try_compile for boost::filesystem and pass a directive that can be used in your source file where you decide if you want to use c++17 filesystem or boost.
CHECK_CXX_SYMBOL_EXISTS
takes three arguments, not two:
CHECK_CXX_SYMBOL_EXISTS(std::filesystem::path::preferred_separator filesystem cxx17fs)
You forgot to tell CMake where to look for the symbols (the header that declares them).