将 std::filesystem 添加到 CMake 项目时出现问题

Posted

技术标签:

【中文标题】将 std::filesystem 添加到 CMake 项目时出现问题【英文标题】:Problem adding std::filesystem to CMake Project 【发布时间】:2019-01-21 12:43:15 【问题描述】:

我是 CMake 项目的新手,我想在我的项目中使用文件系统库。我正在运行带有 GCC 8.2 和 CMake 3.13 的 Ubuntu 18.04。为了实现这一点,我尝试了两种选择:

选项 1

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

这无济于事,因为编译器仍然找不到文件系统 编译期间的库。

选项 2(复制自: 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()

这也无济于事,因为我得到了一个我没有的 CMake 错误 明白。

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

我对这个话题感觉不够深入,这就是我来到这里的原因。我不介意投入额外的工作来了解更多信息,但我不知道该去哪里找了。任何帮助将不胜感激!

编辑 1

感谢到目前为止的回复!我根据您的反馈制作了选项 3

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)

遗憾的是它不能解决我的问题。编译的时候还是会报错,找不到编译头。

编辑 2

感谢到目前为止的所有回复。所有这些都有帮助。我最后尝试了 Ashkan 他的回答(因为它看起来很吓人)。这个返回

编译器缺少文件系统功能。

所以我猜这方面有问题。这很有用,因为我现在知道这可能不是由于我的 CMake 文件。我现在必须找出为什么编译器确实支持文件系统头...

编辑 3

严格来说,这个问题得到了回答,因为我的问题是关于 CMake 文件的。我将把 Ashkan 的答案标记为解决方案,因为它在我的故障排除搜索中产生了下一步。如果可以的话,我也会标记 lubgr 他的答案,因为我认为这也是一个非常好的答案。谢谢大家!

【问题讨论】:

对于那些感兴趣的人,我误以为我拥有 GCC 和 G++ 8.2。我犯了这个错误是因为 CLion 报告它有 GDB 8.2,我认为这意味着安装了 GCC 8.2 和 G++ 8.2。此外,我希望安装这些,因为我真的看不出包管理器不会自动更新它的原因。我错了; Ubuntu 18.04 附带版本 7.3,并且不会自动更新。您需要手动解决此问题。 某些版本的 Boost 使用标志 Boost_INCLUDE_DIR,而其他版本使用标志 Boost_INCLUDEDIR不带下划线)。您可以通过阅读FindBoost.cmake 文件(位于path-to-cmake/Modules/FindBoost.cmake 下)来检查适合您情况的文件 【参考方案1】:

GCC 8.2。附带<filesystem>,因此无需调查可用性。接下来,选项 1 就足够了,但需要修复:

set(CMAKE_CXX_STANDARD 17) # no need to manually adjust the CXXFLAGS

add_executable(yourExecutable yourSourceFile.cpp)

target_link_libraries(yourExecutable stdc++fs)

这应该会导致使用-std=c++17-std=gnu++17 编译源代码并在链接时添加-lstdc++fs

编辑:请注意,正如 @Ashkan 在 cmets 中指出的那样,如果编译器不支持 C++17,而不是编译,将 CMAKE_CXX_STANDARD_REQUIRED 设置为 true 会在配置时立即出错错误(由于缺少 <filesystem> 标头)或在链接时(由于缺少共享库)。这可能是可取的。

【讨论】:

我想你也需要这个标志 -DCMAKE_CXX_STANDARD_REQUIRED=ON 如果编译器不支持所需的标准设置,这将导致配置步骤立即失败,对吧?我会说它是可选的,因为如果不支持 C++17,编译无论如何都不会成功。 是的,但是如果你能在正确的地方得到错误,解决它就更容易了。这样 cmake 将退出而不是让 make 给出编译错误。 我认为在 macOS 上,Xcode 10 发行版会告诉它支持 C++17,但它不包含文件系统标头,甚至在实验/中也不包含(与 beta 版本相比)。 【参考方案2】:

除了@lubgr 的回答。我认为更完整的方法是也执行 try_compile 以查看您是否可以实际使用文件系统标头。我认为这更好,因为一些编译器还不支持 std::filesystem 。同样在 gcc 7.x 中,文件系统位于 experimental 命名空间下。这样,您就可以在 else 子句中有一个单独的 try_compile 并检测到它。

这是相关的 cmake

# 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)

文件tests/has_filesystem.cc很简单

#include <filesystem>

namespace fs = std::filesystem;

int main()

    fs::path aPath "../";

    return 0;

你可以在你的 else 子句中 try_compile for boost::filesystem 并传递一个指令,该指令可以在你的源文件中使用,你决定是使用 c++17 文件系统还是 boost。

【讨论】:

【参考方案3】:

CHECK_CXX_SYMBOL_EXISTS 接受三个参数,而不是两个:

include(CheckCXXSymbolExists)
check_cxx_symbol_exists(std::filesystem::path::preferred_separator filesystem cxx17fs)

您忘记告诉 CMake 在哪里查找符号(声明它们的标头)。

【讨论】:

不幸的是,在 GCC-8 上这将在 cxx17fs 中返回 true,即使成功链接可能需要 stdc++fs 我们可以检查其他符号吗?很高兴更新我的答案,或者很高兴通过 gcc-8 的工作测试来支持你的答案(我想是更高版本)。 不知道,这是我第一次玩这个。我敢猜测可以找到std::filesystem::path::preferred_separator,因为它从头文件编译到目标可执行文件中,甚至其中一些符号已经在libstdc++ 中。下次我在那个分支时,我会考虑尝试不同的 stdc++fs 符号。【参考方案4】:

我发现try_compile 不够用的情况:在 MacOS "Mojave" 10.14.6 上运行的 C++17 模式下使用英特尔 C++ 编译器 (icpc (ICC) 19.1.1.216 20200306) 时。 @Ashkan 推荐的测试程序编译没有错误,甚至运行。但是,我的代码在某一时刻使用了fs::path::filename(),这导致了runtime 链接器错误(dyld: lazy symbol binding failed: Symbol not found:)。换句话说:标题在那里,实现显然不是(?)。我没有进一步调查。

解决方案是使用 try_run 而不是 try_compile 并且(在我的情况下)如果尚不支持 std::filesystem,则回退到 boost::filesystem

这里是相关的 CMake 代码部分:

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()

请注意,变量 RUNS_WITH_STDFS 在失败的情况下不会设置为 NO,而是设置为 "FAILED_TO_RUN",这不会被解释为 FALSE 布尔值(请参阅 CMake if() docs:

if() 如果常量为 1、ON、YES、TRUE、Y 或 a,则为真 非零数。如果常数为 0,则为 False,OFF,NO,FALSE,N, IGNORE、NOTFOUND、空字符串,或以-NOTFOUND后缀结尾。

所以我不得不对它的值进行字符串比较。

与@Ashkan 的解决方案相比,小测试程序也发生了一些变化:

// == PROGRAM has_stdfs.cc ==

// Check if std::filesystem is available
// Source: https://***.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;

【讨论】:

以上是关于将 std::filesystem 添加到 CMake 项目时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

检查 std::filesystem::path 是不是在目录中

std::remove 和 boost::filesystem::remove 之间的区别?

ubuntu 18.10 上的 std::filesystem 链接错误 [重复]

std::filesystem::path 的奇怪运算符/

std::filesystem::directory_iterator 链接器问题(C++17)[重复]

std::filesystem current_path 返回根目录