如何正确添加包含目录与CMake

大约一年前,我问了CMake中的头依赖项

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

在CMake教程中快速搜索只指向include_directories,它似乎没有做我希望做的事情…

向CMake发出特定目录包含要包含的头文件,以及生成的Makefile应该跟踪这些头文件的正确方法是什么?

716637 次浏览

必须做两件事。

首先添加要包含的目录:

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项目,头文件也将作为依赖项出现在生成的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})

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

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

如果在各种开源项目中查找人们如何包含目录,就不会有像“适当的方法”这样的东西。但是有两种方法。

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

  2. 更优雅的方法是< A href="https://cmake.org/cmake/help/v3.0/command/target_include_directories.html" rel="noreferrer">target_include_directories。它允许为特定的项目/目标追加目录,而不需要(可能)不必要的继承或各种包含目录的冲突。还允许执行甚至细微的配置,并为该命令添加以下标记之一。

私人 -仅用于指定的构建目标

公共 -用于指定的目标和与此项目链接的目标

接口——仅用于与当前项目链接的目标

PS:

  1. 这两个命令都允许将目录标记为SYSTEM,以提示指定的目录是否包含警告与您无关。

  2. 类似的答案是其他对命令< A href="https://cmake.org/cmake/help/v3.0/command/target_compile_options.html" rel="noreferrer">target_compile_definitions/< A href="https://cmake.org/cmake/help/v3.0/command/add_definitions.html" rel="noreferrer">add_definitionstarget_compile_options/CMAKE_C_FLAGS

我也有同样的问题。

我的项目目录是这样的:

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

这完全奏效了。

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

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

确保在路径周围加上双引号。其他人没有提到这一点,这让我被困了两天。所以这个答案是给那些对CMake非常陌生和非常困惑的人的。

这招对我很管用:

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

我使用的是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)

通过这样做,程序编译成功。

项目结构

.
├── 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

不要忘记包含${CMAKE_CURRENT_LIST_DIR}

示例应该是这样的:

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

PUBLIC用于你想要包含在父项目中的依赖项。

.

.

你有两个选择。

旧:

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

网站管理员注意:这个答案很长。如果你想知道,不,这不是来自博客文章。我写这篇文章就是为了回答这个问题。如果你认为答案的长度和内容证明了这个问题需要重点,那么我对此没有任何疑虑。我个人并不喜欢这个问题,但我想给出一个好的答案,因为它多年来受到了如此多的关注,并且认为现有的答案在某些方面是缺乏的。

在所有这些问题的答案中,都有很多“如何”的问题。(得到你想要的),而珍贵的“为什么”;(深入探究引发这个问题的问题,以及提问者可能误解了不同类型的工具(如ide和构建工具)之间的交互和共享信息的方式,以及CMake传递/需要传递给这些工具的信息)。

这个问题很烦人,因为它是由特定IDE (Code::Blocks)和CMake的特定行为引起的,但随后提出了一个与IDE无关的问题,而是关于Makefiles和CMake的问题,假设他们在CMake上做错了什么,导致了Makefiles的问题,从而导致了他们的IDE的问题。

DR CMake和Makefiles有自己的方法来跟踪包含目录和源文件的头依赖关系。CMake如何配置Code::Blocks IDE是一个完全不同的故事。

什么是“外部”?在CMake?

我最近意识到这个问题似乎是CMake认为这些头文件是项目的外部。[…] 因此,在我看来,CMake认为这些头文件是项目的外部,并没有在依赖

中跟踪它们

据我所知,“外部标头”并没有官方的或有用的定义。当谈到CMake时。我没有在文档中看到过这个短语。还要注意“项目”这个词;是一个重载的术语。CMake生成的每个构建系统都由一个顶级项目组成,可能还包括其他外部或子目录项目。每个项目可以包含多个目标(库、可执行文件等)。CMake所指的目标有时转换为ide所称的项目(Ix。Visual Studio,可能还有Code::Blocks)。如果你必须给这样一个短语一个意义,下面是对我来说有意义的:

在这种情况下,这个问题是指一些ide的“项目”一词的意义,CMake称之为“目标”,项目外部的头文件是那些不打算通过目标的任何包含目录访问的头文件(例如,包括来自目标链接到目标的目录)。

如果问题指的是CMake对“项目”这个词的理解:目标要么是项目的一部分(通过调用project()命令定义/创建,并由生成的构建系统构建),要么是IMPORTED(不是由生成的构建系统构建,并且预计已经存在,或者是通过一些自定义步骤添加到生成的构建系统中构建,例如通过ExternalProject_Add)。包含IMPORTED目标的目录将是那些在CMake项目外部的头文件,包含非-IMPORTED目标的目录将是那些“;部分”的目录。该项目。

CMake跟踪头依赖项吗?(这取决于!)

[…CMake认为这些头文件是项目外部的,并且不会在依赖项中跟踪它们

我不太熟悉CMake的历史,也不太熟悉构建工具中的头依赖跟踪,但以下是我从对这个主题的搜索中收集到的信息。

CMake本身与实现文件/翻译单元的头/包含依赖关系相关的信息没有太多关系。该信息对CMake重要的唯一方式是,如果CMake需要告诉生成的构建系统这些依赖项是什么。生成的构建系统希望跟踪头文件依赖关系的更改,以避免任何不必要的重新编译。特别是Unix Makefiles生成器,在CMake 3.20之前,CMake会扫描头文件/包含依赖项,告诉Makefiles构建系统这些依赖项。自v3.20版本(编译器支持)以来,CMake默认将该职责委托给编译器。看到可用于恢复此行为的选项

对于每个支持的CMake生成器,头/包含依赖项扫描的确切细节是不同的。例如,你可以找到一些关于Ninja功能/方法在他们的手册上的高级描述。由于这个问题只是关于makefile的,所以我不会尝试详细讨论其他生成器。

注意如何获取buildsystem的头/include依赖项信息,你只需要给CMake一个目标的include目录列表,以及一个要编译的实现源文件列表。你不需要给它一个头文件列表,因为这些信息可以被扫描(通过CMake或编译器)。

ide是否通过扫描获取目标头信息?

每个IDE都可以以任何它想要的方式显示信息。类似IDE不显示头文件的问题通常只发生在项目布局的IDE显示格式上,而不是文件系统布局(项目头文件通常与实现文件位于同一个项目目录中)。例如,这样的非文件系统布局视图在Visual Studio和Code::Blocks中可用。

每个IDE都可以以其选择的任何方式获取头信息。据我所知(但我可能对Visual Studio错了),Visual Studio和Code::Blocks都期望在IDE项目配置文件中显式列出项目头的列表。还有其他可能的方法(例如头依赖项扫描),但似乎许多ide选择显式列表方法。我猜是因为它的实现很简单。

为什么对IDE来说,查找与目标相关的头文件的扫描是一种负担?(注:这有点像猜测,因为我不是任何此类工具的维护者,只使用过其中的两个)IDE可以实现文件扫描(这本身是一个复杂的任务),但要知道哪些头文件是“;在”;对于目标,他们要么需要从构建系统中获得关于目标的翻译单元将如何编译的信息,而这是假设所有“不在目标中的”;头包含路径是用“system" like标志”指定的,但不一定是这样。或者,它可以尝试从元构建系统获取信息,这里是CMake。或者它可以尝试做CMake现在做的事情,并尝试调用所选的编译器来扫描依赖项。但在任何一种情况下,他们都必须做出一些艰难的决定,关于支持哪个构建系统、元构建系统和/或编译器,然后做一些艰难的工作,从这些工具存储信息的格式中提取信息,可能没有任何保证这些格式在未来的工具版本中是相同的(在新工具版本中支持格式的变化可能类似于必须支持一个完全独立的工具)。IDE可以完成所有这些工作,也可以只要求您给它一个属于每个目标的标题列表。正如您所看到的,C/ c++生态系统拥有的工具多样性也有缺点。当然也有好处,但这超出了这个问题的范围。

好的一面是,CMake实际上有一种机制,可以帮你分担一些工作。对于这些具有非文件系统视图的ide,它确实实现了一个简单的启发式方法来尝试查找与源文件相关联的头文件……

头文件发现如何为Code::Block IDE生成器CMake工作?

至少,在生成Code::Blocks项目时,头文件不会出现在项目中(源文件会出现)。

这里有一些有趣的东西:CodeBlocks编辑器有源文件和头文件的概念,它们是项目的一部分,由于CMake不预期/要求它的用户告诉它项目中的每个头文件(它只需要知道哪些包含目录应该与目标相关联),它试图使用某种启发式来发现与实现文件相关联的头文件。这种启发是非常基本的:获取项目中每个源文件的路径,并尝试将扩展名更改为通常给予头文件的扩展名,然后查看是否存在这样的文件。参见:/Source/cmExtraCodeBlocksGenerator.cxx中的cmExtraCodeBlocksGenerator::CreateNewProjectFile成员函数。

“Pitchfork Layout"术语中,可以说启发式假设项目使用&;合并头文件&;代替“split- heading”的位置;其中有单独的src/include/目录。因此,如果你不使用合并头布局,或者有任何不符合启发式的目标头文件,例如实用程序头文件,你需要显式地告诉CMake关于这些文件(例如使用target_sources),以便它将这些知识传递给它生成的IDE配置。

进一步阅读:

关闭的话

我相信有很多人比我更了解这些工具。如果你是这些人中的一员,并且注意到我犯了一个错误,请在评论或聊天中慷慨地纠正我,或者只是编辑这篇文章。

请注意,虽然构建构件的安装是许多项目生命周期的重要组成部分,因此被纳入到大多数C/ c++构建系统的设计中,由于这个问题没有明确地询问配置安装部分,我选择将其从这个答案中删除,因为它本身不是一个微不足道的主题(只需查看“掌握cmake&c”中的相关章节多长时间即可。书是:安装章节关于进口和出口的章节)。

在更新的CMake版本中,我们可以将包含路径限制为target,例如:

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

Where with "seems"我的意思是,如果CMake愿意,它可以自动检测到这样的头文件,因为它无论如何都会解析项目的C/ c++文件。