大约一年前,我问过CMake中的头依赖关系。

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

在CMake教程中快速搜索只指向include_directories,这似乎不是我想要的…

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


当前回答

不要忘记包含${CMAKE_CURRENT_LIST_DIR}。 这就是给我带来麻烦的原因。

示例应该是这样的:

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

PUBLIC用于您想要包含在父项目中的依赖项。 对于那些你不喜欢的人来说是私密的。

其他回答

项目结构

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

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

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

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

你有两个选择。

旧:

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

Note to site curators: This answer is very long. In case you are wondering, no it is not from a blog post. I wrote this specifically tailored to answer this question. If you think the length of the answer and its content warrant closing the question as needing focus, then I have no qualms with that. I personally am not a fan of the question anyway, but wanted to give a good answer because it has gotten so much attention over the years and thought the existing answers were lacking in certain ways.

在所有对这个问题的回答中,有大量的“如何”(得到你想要的),而很少有“为什么”(深入挖掘问题的原因,以及提问者可能误解了不同类型的工具(如ide和构建工具)相互之间的交互和共享信息的方式,以及CMake传递/需要传递给这些工具的信息)。

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

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

什么是CMake中的“外部”头?

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

As far as I know, there is no official or useful definition of "external header" when it comes to CMake. I have not seen that phrase used in documentation. Also note that the word "project" is a quite overloaded term. Each buildsystem generated by CMake consists of one top-level project, possibly including other external or subdirectory projects. Each project can contain multiple targets (libraries, executables, etc.). What CMake refers to as a target sometimes translates to what IDEs call projects (Ix. Visual Studio, and possibly Code::Blocks). If you had to given such a phrase a meaning, here's what would make sense to me:

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

In the case that the question is referring to CMake's sense of the word "project": Targets are either part of a project (defined/created by a call to the project() command, and built by the generated buildsystem), or IMPORTED, (not built by the generated buildsystem and expected to already exist, or built by some custom step added to the generated buildsystem, such as via ExternalProject_Add). Include directories of IMPORTED targets would be those headers which are external to the CMake project in question, and include directories of non-IMPORTED targets would be those that are "part of" the project.

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

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

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

CMake itself doesn't have much to do with any information related to header/include dependencies of implmentation files / translation units. The only way in which that information is important to CMake is if CMake needs to be the one to tell the generated buildsystem what those dependencies are. It's the generated buildsystem which wants to track changes in header file dependencies to avoid any unnecessary recompilation. For the Unix Makefiles generator in particular, before CMake 3.20, CMake would do the job of scanning header/include dependencies to tell the Makefiles buildsystem about those dependencies. Since v3.20, where supported by the compiler, CMake delegates that resposibility to the compiler by default. See the option which can be used to revert that behaviour here.

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

注意如何获得构建系统的头/包含依赖信息,你只需要给CMake一个目标的包含目录列表,和一个要编译的实现源文件列表。你不需要给它一个头文件列表,因为这些信息可以被扫描(通过CMake或编译器)。

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

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

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

Why would scanning be burdensome for an IDE to find header files associated with a target?(Note: this is somewhat speculation, since I am not a maintainer of any such tools and have only used a couple of them) An IDE could implement the file scanning (which itself is a complicated task), but to know which headers are "in" the target, they'd either need to get information from the buildsystem about how the translation units of the target will get compiled, and that's assuming that all "not-in-target" header include paths are specified with a "system"-like flag, which doesn't have to be the case. Or, it could try to get that information from the meta-buildsystem, which here is CMake. Or it could try to do what CMake now does and try to invoke the selected compiler to scan dependencies. But in either case, they'd have to make some difficult decision about which buildsystems, meta buildsystems, and/or compilers to support, and then do the difficult work of extracting that information from whatever formats those tools store that information in, possibly without any guarantees that those formats will be the same in future tool versions (supporting a change in the format in a newer tool version could be similar to having to supporting a completely separate tool). The IDE could do all that work, or it could just ask you to give it a list of the headers belonging to each target. As you can see, there are cons to the diversity in tooling that the C/C++ ecosystem has. There are pros too, but that's outside the scope of this question.

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

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

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

Here's something interesting: The CodeBlocks editor has the concept of source files and header files that are part of a project, and since CMake doesn't expect/require its users to tell it about each and every header file in the project (it only needs to know about what include directories should be associated with targets), it tries to use a certain heuristic to discover header files that are associated to implementation files. That heuristic is very basic: take the path of each source file in a project, and try changing the extenstion to be like one that is usually given to header files, and see if any such file exists. See the cmExtraCodeBlocksGenerator::CreateNewProjectFile member function in :/Source/cmExtraCodeBlocksGenerator.cxx.

In "Pitchfork Layout" terminology, it would be said that the heuristic assumes that the project uses "merged-header" placement instead of "split-header" placement, where there are separate src/ and include/ directories. So if you don't use merged-header layout, or otherwise have any target headers that don't meet that heuristic, such as utility header files, you'll need to explicitly tell CMake about those files (Ex. using target_sources) for it to pass that knowledge on to the IDE config it generates.

进一步阅读:

下面是CMake Code::Blocks生成器的文档(与当前主题相关的信息不多,但无论如何都可以链接)。 下面是Code::Blocks在其“项目视图”上的文档。下面是.cpb xml模式文档(请特别参阅Unit元素)。 如果你想读取CMake代码,它会进行相关的头检测,你可以在Source/cmExtraCodeBlocksGenerator中的cmExtraCodeBlocksGenerator::CreateNewProjectFile函数中找到它。cxx文件。

关闭的话

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

Note that while installation of build artifacts is an important part of many projects' lifecycles and is therefore incorporated into the designs of most C/C++ buildsystems, since the question didn't explicitly ask about the configuring the installation part, I have chosen to leave it out of this answer, since it in itself is not a trivial topic to cover (just see how long the related chapters in the "Mastering CMake" book are: The chapter on installation, and the chapter on importing and exporting).