如何使用 CMake 正确添加包含目录

Posted

技术标签:

【中文标题】如何使用 CMake 正确添加包含目录【英文标题】:How to properly add include directories with CMake 【发布时间】:2021-10-03 16:31:45 【问题描述】:

大约一年前,我询问了header dependencies in CMake。

我最近意识到问题似乎是 CMake 认为这些头文件是项目的外部。至少,在生成 Code::Blocks 项目时,头文件不会出现在项目中(源文件会出现)。因此,在我看来,CMake 认为这些标头是项目的外部,并且不会在依赖项中跟踪它们。

在 CMake 教程中的快速搜索只指向 include_directories,这似乎并没有达到我的预期......

向 CMake 发出信号表明特定目录包含要包含的标头以及应由生成的 Makefile 跟踪这些标头的正确方法是什么?

【问题讨论】:

对这个问题的编辑让人困惑。最初的问题和答案是如何在 IDE 中跟踪头文件。这与生成的 Makefile 缺少头文件依赖项以及如何解决该问题完全不同。 @Fred:我不知道你在说什么。正如编辑修订清楚地表明,最后一句话一直在那里。仅对这个问题进行了外观编辑,没有引入(或删除)任何词。 那是我的误解。在我看来,它添加了整个段落。 ***.com/questions/13703647/… 表示大家的共识是如何在 IDE 中列出头文件。这本来是指.cbp 项目文件。现在,如果 cmake 依赖项扫描器无法正确地将头文件识别为 Makefile 的依赖项,则有一些方法可以解决此问题,但在某些情况下它会出错,因为它不包含完整的预处理器。 【参考方案1】:

必须做两件事。

首先添加要包含的目录:

target_include_directories(test PRIVATE $YOUR_DIRECTORY)

如果您遇到不支持 target_include_directories 的非常旧的 CMake 版本(2.8.10 或更早版本),您也可以改用旧版 include_directories

include_directories($YOUR_DIRECTORY)

然后您还必须将头文件添加到当前目标的源文件列表中,例如:

set(SOURCES file.cpp file2.cpp $YOUR_DIRECTORY/file1.h $YOUR_DIRECTORY/file2.h)
add_executable(test $SOURCES)

这样,头文件将作为依赖项出现在 Makefile 中,例如在生成的 Visual Studio 项目中(如果您生成一个)。

如何将这些头文件用于多个目标:

set(HEADER_FILES $YOUR_DIRECTORY/file1.h $YOUR_DIRECTORY/file2.h)

add_library(mylib libsrc.cpp $HEADER_FILES)
target_include_directories(mylib PRIVATE $YOUR_DIRECTORY)
add_executable(myexec execfile.cpp $HEADER_FILES)
target_include_directories(myexec PRIVATE $YOUR_DIRECTORY)

【讨论】:

啊!我知道这一定是愚蠢的。确实,我没有列出标题...我是否需要仅列出该库的标题,还是它可能依赖的所有标题(在声明对库的依赖之上)?这是一个不断发展的项目,当我在根库中添加一个依赖项时,我非常害怕向 所有 依赖项添加一个标题。 我的问题更多是因为我有几个相互依赖的库:libroot,liba 依赖于 libroot,libb 依赖于 libroot。那么我可以在liba/CMakefilelibb/CMakefile 中使用LIBROOT_HEADER_FILES 变量吗? 这是错误的,你应该永远使用include_directories而不是target_include_directories。前者为该目录中的所有目标递归地设置它;而后者将其设置为目标。做前者打破了 CMake 中目标图的概念,而是依赖于对文件层次结构的副作用。 我编辑了答案以反映现代 CMake 代码更喜欢 target_include_directories 的当前概念。如果您不同意这些更改,请随时邀请我聊天。 @donturner 您不必将.h 文件添加到add_executable。但是,它确实具有使文件显示在预期位置的Visual Studio 项目中的好处。 Makefiles 使用内部 cmake -E cmake_depends 从源文件生成依赖项(add_executable 中的头文件被跳过)。有已知的issues 与此扫描仪。此外`CMake 的 Makefile 生成器依赖扫描器仅进行近似预处理。` 计算的标头包含并且这种事情将不起作用。【参考方案2】:

首先,您使用include_directories() 告诉CMake 将目录作为-I 添加到编译命令行。其次,在add_executable()add_library() 调用中列出标题。

例如,如果您的项目的源代码位于 src,并且您需要来自 include 的标头,您可以这样做:

include_directories(include)

add_executable(MyExec
  src/main.c
  src/other_source.c
  include/header1.h
  include/header2.h
)

【讨论】:

你真的需要给add_executable添加标题吗?我以为 CMake 会自动找出包含文件的依赖关系。 @ColinDBennett 您不必出于依赖原因列出它们 - 如果您不这样做,CMake 会很好地计算出构建依赖关系。但是,如果您列出它们,它们将被视为项目的一部分,并将在 IDE 中按此列出(这是问题的主题)。 至少对于 QtCreator 来说,如果 class.cpp 存在,则不需要添加 class.h。只需要将寂寞.h 添加到源中。请参阅www.th-thielemann.de/cmake 上的教程【参考方案3】:

添加include_directories("/your/path/here")

这类似于使用-I/your/path/here/ 选项调用gcc

确保在路径周围加上双引号。其他人没有提到这一点,这让我坚持了 2 天。因此,此答案适用于对 CMake 非常陌生且非常困惑的人。

【讨论】:

除了它随机失败的一半时间。 =/【参考方案4】:

如果将 CMake 与其他创建 Makefile 的方法(例如 make 或 qmake)进行比较,它更像是一种脚本语言。它不像 Python 那样很酷,但仍然如此。

如果在各种开源项目中查看人们如何包含目录,则没有像“正确的方式”这样的东西。但是有两种方法可以做到。

    Crude include_directories 将附加一个目录到当前项目和您将通过一系列add_subdirectory 命令附加的所有其他后代项目。有时人们会说这种方法是传统的。

    更优雅的方式是使用target_include_directories。它允许为特定项目/目标附加目录,而不会(可能)不必要的继承或各种包含目录的冲突。还允许执行细微的配置并为此命令附加以下标记之一。

PRIVATE - 仅用于此指定的构建目标

PUBLIC - 将其用于指定目标和与该项目链接的目标

INTERFACE -- 仅用于与当前项目链接的目标

PS:

    这两个命令都允许将目录标记为 SYSTEM,以提示指定目录包含警告不是您的业务。

    类似的答案是其他命令对target_compile_definitions/add_definitions, target_compile_options/CMAKE_C_FLAGS

【讨论】:

"会将目录附加到当前项目和您将通过一系列 add_subdirectory 附加的所有其他后代项目" -- 不幸的是,这是错误的。事实是,它适用于 same 中的所有目标(甚至是在 include_directories 调用之前出现的目标)和在调用 之后出现的 add_subdirectories 中的目标(不是之前)...这就是为什么我们说它是遗产。 永远不要使用include_directories【参考方案5】:

我遇到了同样的问题。

我的项目目录是这样的:

    --project
    ---Classes
    ----Application
    -----.h and .c files
    ----OtherFolders
    --main.cpp

以及我曾经将文件包含在所有这些文件夹中的内容:

    file(GLOB source_files CONFIGURE_DEPENDS
            "*.h"
            "*.cpp"
            "Classes/*/*.cpp"
            "Classes/*/*.h"
    )

    add_executable(Server $source_files)

它完全奏效了。

【讨论】:

记住 cmake 是“构建系统生成器”而不是使用文件 glob 的“构建系统”在现代 cmake(3.0 及更高版本的 CMake)中不是一个好主意,因为文件 glob 的评估时间为“构建”时间而不是“构建系统生成”时间。见链接:gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1 只需添加CONFIGURE_DEPENDS【参考方案6】:

项目结构

.
├── CMakeLists.txt
├── external //We simulate that code is provided by an "external" library outside of src
│   ├── CMakeLists.txt
│   ├── conversion.cpp
│   ├── conversion.hpp
│   └── README.md
├── src
│   ├── CMakeLists.txt
│   ├── evolution   //propagates the system in a time step
│   │   ├── CMakeLists.txt
│   │   ├── evolution.cpp
│   │   └── evolution.hpp
│   ├── initial    //produces the initial state
│   │   ├── CMakeLists.txt
│   │   ├── initial.cpp
│   │   └── initial.hpp
│   ├── io   //contains a function to print a row
│   │   ├── CMakeLists.txt
│   │   ├── io.cpp
│   │   └── io.hpp
│   ├── main.cpp      //the main function
│   └── parser   //parses the command-line input
│       ├── CMakeLists.txt
│       ├── parser.cpp
│       └── parser.hpp
└── tests  //contains two unit tests using the Catch2 library
    ├── catch.hpp
    ├── CMakeLists.txt
    └── test.cpp

怎么做

1.顶层 CMakeLists.txt 非常类似于秘诀 1,函数和宏的代码重用

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
  
project(recipe-07 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
  $CMAKE_BINARY_DIR/$CMAKE_INSTALL_LIBDIR)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
  $CMAKE_BINARY_DIR/$CMAKE_INSTALL_LIBDIR)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
  $CMAKE_BINARY_DIR/$CMAKE_INSTALL_BINDIR)

# defines targets and sources
add_subdirectory(src)

# contains an "external" library we will link to
add_subdirectory(external)

# enable testing and define tests
enable_testing()
add_subdirectory(tests)

2.目标和来源在src/CMakeLists.txt中定义(转化目标除外)

add_executable(automata main.cpp)
  
add_subdirectory(evolution)
add_subdirectory(initial)
add_subdirectory(io)
add_subdirectory(parser)

target_link_libraries(automata
  PRIVATE
    conversion
    evolution
    initial
    io
    parser
  )

3.转换库定义在external/CMakeLists.txt

add_library(conversion "")

target_sources(conversion
  PRIVATE
    $CMAKE_CURRENT_LIST_DIR/conversion.cpp
  PUBLIC
    $CMAKE_CURRENT_LIST_DIR/conversion.hpp
  )

target_include_directories(conversion
  PUBLIC
    $CMAKE_CURRENT_LIST_DIR
  )

4. src/CMakeLists.txt 文件添加了更多子目录,这些子目录又包含 CMakeLists.txt 文件。它们在结构上都相似; src/evolution/CMakeLists.txt 包含以下内容:

add_library(evolution "")

target_sources(evolution
  PRIVATE
    evolution.cpp
  PUBLIC
    $CMAKE_CURRENT_LIST_DIR/evolution.hpp
  )
target_include_directories(evolution
  PUBLIC
    $CMAKE_CURRENT_LIST_DIR
  )

5.单元测试注册在tests/CMakeLists.txt

add_executable(cpp_test test.cpp)

target_link_libraries(cpp_test evolution)

add_test(
  NAME
    test_evolution
  COMMAND
    $<TARGET_FILE:cpp_test>
  )

如何运行

$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build .

参考:https://github.com/sun1211/cmake_with_add_subdirectory

【讨论】:

这个答案充满了令人敬畏和渗出的纯粹胜利。我希望我能再提高几十次。谢谢。 同意上面的评论。非常感谢!【参考方案7】:

这对我有用:

set(SOURCE main.cpp)
add_executable($PROJECT_NAME $SOURCE)

# target_include_directories must be added AFTER add_executable
target_include_directories($PROJECT_NAME PUBLIC $INTERNAL_INCLUDES)

【讨论】:

【参考方案8】:

另一种选择:

set_property(
    TARGET MyApp
    APPEND PROPERTY
        INCLUDE_DIRECTORIES "$CMAKE_CURRENT_LIST_DIR/src"
)  

【讨论】:

【参考方案9】:

不要忘记包含$CMAKE_CURRENT_LIST_DIR。 这就是给我带来问题的原因。

示例应该是这样的:

target_include_directories(projectname
    PUBLIC "$CMAKE_CURRENT_LIST_DIR/include"                          
)

PUBLIC 用于您希望包含在父项目中的依赖项。 对那些你不知道的人来说是私人的。

【讨论】:

【参考方案10】:

我正在使用 CLion,我的项目结构如下:

--main.cpp
--Class.cpp
--Class.h
--CMakeLists.txt

CMakeLists.txt之前变化:

add_executable(ProjectName main.cpp)

CMakeLists.txt改变后:

add_executable(ProjectName main.cpp Class.cpp Class.h)

这样程序就编译成功了。

【讨论】:

问题问如何让库相互依赖。

以上是关于如何使用 CMake 正确添加包含目录的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 CMake 在 C++ 代码中运行 gtest? (未见测试)

如何在 cmake 上使用 FIND_JNI

如何在 CMake 中正确创建目标之间的依赖关系?

从 CMake 中使用的库继承包含目录

如何使用 cmake 正确链接库?

如何使用CMake构建c++项目