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

408

大约一年前,我询问了关于CMake中头文件依赖的问题。

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

在CMake教程中进行快速搜索只指向了include_directories,但这似乎并不是我想要的...

如何向CMake发出信号,表明特定目录包含需要包含的头文件,并且这些头文件应由生成的Makefile跟踪?


2
对该问题所做的编辑使其变得混乱。原始问题和答案是如何在IDE中跟踪头文件。这与生成的Makefile缺少头文件依赖项以及解决此问题的方法有很大不同。 - fdk1342
@Fred:我不知道你在说什么。正如编辑修订清楚显示的那样,最后一句话一直都在那里。这个问题只进行了表面上的编辑,没有引入(或删除)任何单词。 - Matthieu M.
那么这是我的误解。在我看来,整个段落都被添加了。https://dev59.com/KWYr5IYBdhLWcg3waJdT?noredirect=1#comment29340346_13703745说的是如何在IDE中列出头文件的常见理解。这将涉及到`.cbp`项目文件。现在,如果cmake依赖项扫描器无法正确识别头文件作为Makefile的依赖项,则有修复的方法,但在某些情况下,它会出错,因为它不包括完整的预处理器。 - fdk1342
12个回答

420

必须完成两件事。

首先,添加要包含的目录:

target_include_directories(test PRIVATE ${YOUR_DIRECTORY})

如果你被困在一个非常老的CMake版本(2.8.10或更旧版本),不支持target_include_directories,你也可以使用传统的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})

1
我的问题更多地是指我有几个相互依赖的库:libroot,liba 依赖于 libroot,libb 依赖于 libroot。那么我可以在 liba/CMakefilelibb/CMakefile 中使用 LIBROOT_HEADER_FILES 变量吗? - Matthieu M.
8
这是错误的,你绝不能使用include_directories而不是target_include_directories。前者会递归地为该目录中的所有目标设置,而后者仅为一个目标设置。使用前者会破坏CMake中目标图的概念,而是依赖于对文件层次结构的副作用。 - Andy
1
为什么你必须在可执行文件的源文件中明确列出.h文件?你应该只需指定头文件的路径,然后从实现的.cpp文件中使用#include <filename.h>,不是吗? - donturner
4
@donturner 您不必将.h文件添加到add_executable中。但是,这样做有一个好处,即使得文件在Visual Studio项目中以预期位置显示。 Makefiles使用内部的cmake -E cmake_depends从源文件生成依赖项 (add_executable中的头文件被跳过)。已知此扫描器存在一些问题,请参见 issues。此外,CMakeMakefile生成器依赖关系扫描器只进行近似预处理。并且编译的头文件包含和类似的内容将无法正常工作。 - fdk1342
2
很多次我尝试寻找这个答案,但只能找到类似问题的答案,并且建议使用GLOB,最终我放弃了寻找。如果其他问题包含/链接了这个答案,或者它们被表述得更加独特和不同,那将非常方便。 - Chris
显示剩余11条评论

106

首先,你使用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
)

40
你真的需要在 add_executable 中添加头文件吗?我认为 CMake 可以自动找出包含文件的依赖关系。 - Colin D Bennett
80
你不需要因为依赖关系而列出它们 - 如果你不列出来,CMake可以很好地确定构建依赖关系。但是如果你列出它们,它们就被视为项目的一部分,并且将在IDE中列出(这是问题的主题)。 - Angew is no longer proud of SO
至少对于QtCreator而言,如果已经存在class.cpp,则不需要添加class.h。只需将lonely.h 添加到源代码中即可。请参阅www.th-thielemann.de/cmake上的教程。 - Th. Thielemann
如果您在包含目录中指定了"include",为什么还需要在源文件列表中的头文件前加上"include/"前缀呢? - Oscar
1
@Oscar 在上面的评论中回答了(作为对 Colin D Bennett 的回复)。 - Angew is no longer proud of SO
显示剩余2条评论

56

项目结构

.
├── 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
    ${CMAKE_CURRENT_LIST_DIR}/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


2
这个问题并没有询问关于单元测试或使用第三方项目的内容。对我来说,这个答案中的那些部分似乎只是不必要的噪音。当然,在一个要求这些信息的问题中,它可能是有用的,但这里并非如此。 - starball

38
在CMake中,添加include_directories("/your/path/here")
这类似于使用-I/your/path/here/选项调用gcc命令。
请确保在路径周围加上双引号。其他人没有提到这一点,导致我卡了两天,所以这个答案是给那些非常新手和非常困惑的人看的。

1
除了它随机失败一半的时间。 =/ - BadZen

28

相较于其他创建Makefile的方式(例如make或qmake),CMake更像是一个脚本语言。它不像Python那样酷炫,但也很不错。

如果观察各种开源项目中人们如何包含目录,就没有所谓的“正确方式”。但有两种方法可以实现:

  1. 粗略的include_directories将把一个目录附加到当前项目和所有其他后代项目上,您可以通过一系列add_subdirectory命令来附加它们。有时人们说这种方法已经过时了。

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

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

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

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

PS:

  1. 这两个命令都允许将目录标记为系统目录,以提示它不是您需要关注的目录,可能会出现警告。

  • 类似的答案还可以在其他命令对中获得,例如target_compile_definitions/add_definitionstarget_compile_options/CMAKE_C_FLAGS


  • 3
    "将附加一个目录到当前项目和所有后代项目,您将通过一系列的add_subdirectory命令来附加其他的子目录"--不幸的是,这是错误的。实际上,它适用于同一目录中的所有目标(即使它们在include_directories调用之前),以及在调用之后(而不是之前)使用add_subdirectories命令添加的目标...... 这就是为什么我们说它已经过时了。请勿再使用include_directories命令 - Alex Reinking

    10
    我遇到了同样的问题。
    我的项目目录是这样的:
        --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})
    

    它完全奏效了。

    4
    记住CMake是一个“生成构建系统”的工具而不是构建系统本身,因此在现代CMake(版本3.0及以上)中使用文件通配符不是一个好主意,因为文件通配符在“构建”时间而不是“构建系统生成”时间被评估。 参考链接:https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1 - ggulgulia
    1
    只需添加 CONFIGURE_DEPENDS - letmaik
    1
    在 CMake 中,应尽可能避免使用文件 GLOB。当您在大型项目中使用它时,最终会有一堆包含文件的样板代码。 - thecatbehindthemask

    4
    你有两个选项。
    旧的选项:
    include_directories(${PATH_TO_DIRECTORY})
    

    以及新的

    target_include_directories(executable-name PRIVATE ${PATH_TO_DIRECTORY})
    

    要使用target_include_directories,您需要定义可执行文件 - add_executable(executable-name sourcefiles)

    因此,您的代码应如下所示

    add_executable(executable-name sourcefiles)
    target_include_directories(executable-name PRIVATE ${PATH_TO_DIRECTORY})
    

    您可以在此处阅读更多内容:https://cmake.org/cmake/help/latest/command/target_include_directories.html

    1

    不要忘记包含${CMAKE_CURRENT_LIST_DIR}。 这是导致我问题的原因。

    示例应该像这样:

    target_include_directories(projectname
        PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include"                          
    )
    

    公共依赖项适用于您希望父项目包含的依赖项。 私有依赖项则不包括在内。


    1
    这对我有用:

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

    0
    在较新的CMake版本中,我们可以将包含路径限制为目标,例如:
    target_include_directories(MyApp PRIVATE "${CMAKE_CURRENT_LIST_DIR}/myFolder")
    

    我的意思是,如果 CMakeLists.txt 有多个目标,否则,包含路径不会与其他 CMakeLists.txt 脚本共享,这时只需要做如下操作:

    include_directories("${CMAKE_CURRENT_LIST_DIR}/myFolder")
    

    然而,也许我们可以模拟 target_include_directories(...) 对于 CMake 2.8.10 或更早版本的操作,例如:

    set_property(
        TARGET MyApp
        APPEND PROPERTY
            INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/myFolder"
    )
    

    所有工作都完成了,但是如果您想要在任何头文件更改后重新编译源文件,则需要将所有这些头文件添加到每个目标中,例如:

    set(SOURCES src/main.cpp)
    
    set(HEADERS
        ${CMAKE_CURRENT_LIST_DIR}/myFolder/myHeaderFile.h
        ${CMAKE_CURRENT_LIST_DIR}/myFolder/myOtherHeader.h
    )
    
    add_executable(MyApp ${SOURCES} ${HEADERS})
    

    我所说的“似乎”是指,如果CMake愿意解析项目的C/C++文件,它就可以自动检测到这些头文件。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接