Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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 two options:

Option 1

cmake_minimum_required(VERSION 3.13)  
project(TheFsProject)  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_FLAGS "-std=c++17 -lstdc++fs")  

This does not help as the compiler still cannot find the file system library during compile time.

Option 2 (copied from: https://www.scivision.co/cmake-cpp-17-filesystem/)

make_minimum_required(VERSION 3.13)
project(TheFsProject)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_REQUIRED_FLAGS -std=c++17)
include(CheckCXXSymbolExists)
CHECK_CXX_SYMBOL_EXISTS(std::filesystem::path::preferred_separator cxx17fs)
if(cxx17fs)
  add_executable(TheFsProject main.cpp)
  set_property(TARGET TheFsProject PROPERTY CXX_STANDARD 17)
endif()

This does not help either as I get a CMake error which I don't understand.

(CHECK_CXX_SYMBOL_EXISTS):  
 CHECK_CXX_SYMBOL_EXISTS Macro invoked with incorrect arguments for macro named: CHECK_CXX_SYMBOL_EXISTS

I feel out of my depth on this topic which is why I came here. I don't mind putting extra work into finding out more but I don't know anymore where to look. Any help would be appreciated!

EDIT 1

Thanks for the replies so far! I made Option 3 based on your feedback:

cmake_minimum_required(VERSION 3.13)
project(TheFsProject)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(TheFsProject main.cpp)
target_link_libraries(TheFsProject stdc++fs)

Sadly it doesn't fix my problem. It still issues an error during compilation that it can't find the compilation header.

EDIT 2

Thanks for all the replies so far. All of these help. I tried Ashkan his answer last (because it seemed intimidating). This one returns

Compiler is missing file system capabilities.

so I'm guessing something is wrong on that end. This is useful in the sense that I now know it's probably not due to my CMake file. I now have to find out why the compiler does support the file system header though...

EDIT 3

Strictly speaking this question is answered because my question is about the CMake file. I am going to mark Ashkan his answer as the solution simply because it produced the next step in my troubleshooting search. If I could I would also mark lubgr his answer because I think that's a really good answer as well. Thanks everyone!

For those interested I made a mistake in thinking I had GCC and G++ 8.2. I made this mistake because CLion reports it has GDB 8.2 and I thought this would imply that GCC 8.2 and G++ 8.2 are installed. Furthermore, I expected these to be installed because I don't really a see a reason why this would not be updated automatically by the package manager. I was wrong; Ubuntu 18.04 ships with version 7.3 and does not automatically update this. You need to fix this manually. – MaestroMaus Jan 21, 2019 at 16:45 Some versions of Boost use the flag Boost_INCLUDE_DIR and others use the flag Boost_INCLUDEDIR (without the underscore). You can check the right one for your case by reading the FindBoost.cmake file, under path-to-cmake/Modules/FindBoost.cmake – marcelosalloum Jul 5, 2019 at 14:04

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.

This would cause the configure step to immediately fail if the compiler doesn't support the required standard setting, right? I'd say it's optional then, as the compilation would anyhow not succeed if C++17 isn't supported. – lubgr Jan 21, 2019 at 13:39 True but if you can get the error in the correct place, it is easier to solve it. This way cmake would exit instead of letting make give a compile error. – Ashkan Jan 21, 2019 at 13:43 I think on macOS the Xcode 10 release version would tell that it supports C++17, but it does not include the filesystem header, not even in experimental/ (in contrast to the beta version). – John May 31, 2021 at 17:41

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:

include(CheckCXXSymbolExists)
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).

Unfortunately on GCC-8 this will return true in cxx17fs even though the stdc++fs might be needed for successful linking. – user35443 Jan 19, 2020 at 16:24 Is there another symbol we can check for this? Happy to update my answer, or happy to upvote your answer with a working test for gcc-8 (and up, I suppose). – Matthieu Brucher Jan 20, 2020 at 17:00 No clue, it's my first time playing with this. I would dare to guess that std::filesystem::path::preferred_separator can be found because it compiles from the headers into the target executable or even that some of those symbols are already in libstdc++. I will consider trying a different stdc++fs symbol next time I am in that branch. – user35443 Jan 21, 2020 at 17:39

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;
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.