最简单但完整的CMake示例


117

某种程度上,我对CMake的工作方式完全感到困惑。每当我认为我越来越了解CMake的含义时,在我阅读的下一个示例中,它就消失了。我只想知道,我应该如何构造我的项目,以便将来CMake所需的维护最少。例如,当我在src树中添加新文件夹时,我不想更新CMakeList.txt,该文件夹的工作方式与所有其他src文件夹完全相同。

这就是我想象项目结构的方式,但是请仅这只是一个例子。如果建议的方法不同,请告诉我,并告诉我如何进行。

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

顺便说一句,重要的是我的程序必须知道资源在哪里。我想知道推荐的资源管理方式。我不想使用“ ../resources/file.png”访问我的资源


1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src tree您能举一个IDE自动收集源代码的示例吗?

7
没有助手通常不会自动收集源,因为它们不需要。当我添加一个新文件或文件夹时,我会在ide中进行操作,并且该项目也会更新。另一侧的构建系统在更改某些文件时不会注意到,因此它是一种理想的行为,它会自动收集所有源文件
Arne 2014年

4
如果看到该链接,则给人的印象是CMake在它要解决的最重要任务上失败了:简化跨平台构建系统。
Arne 2014年

Answers:


94

经过研究,我现在有了自己的最简单但完整的cmake示例版本。就在这里,它试图涵盖大多数基础知识,包括资源和包装。

它非标准地做的一件事是资源处理。默认情况下,cmake希望将它们放在/ usr / share /,/ usr / local / share /以及Windows上的等效目录中。我想要一个简单的zip / tar.gz,您可以将其提取到任何地方并运行。因此,资源是相对于可执行文件加载的。

理解cmake命令的基本规则是以下语法: <function-name>(<arg1> [<arg2> ...])不带逗号或半彩色。每个参数都是一个字符串。foobar(3.0)并且foobar("3.0")是相同的。您可以使用设置列表/变量set(args arg1 arg2)。与此变量集foobar(${args})foobar(arg1 arg2)有效地相同。不存在的变量等效于一个空列表。列表内部只是一个带有分号的字符串,用于分隔元素。因此,根据定义,仅包含一个元素的列表就是该元素,不进行装箱。变量是全局的。内置函数会提供某种形式的命名参数,因为它们期望某些id PUBLICDESTINATION在其参数列表中对参数进行分组。但这不是语言功能,这些id也是字符串,并由函数实现进行解析。

您可以从github克隆所有内容

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)

8
@SteveLorimer我只是不同意,文件泛滥是一种不良样式,我认为手动将文件树复制到CMakeLists.txt是一种不良样式,因为它是多余的。但是我知道人们在这个主题上确实存在意见分歧,因此我在代码中留下了注释,您可以在其中用明确包含所有源文件的列表替换glob。搜索set(sources src/main.cpp)
Arne'4

3
@SteveLorimer是的,我经常不得不再次调用cmake。每次我在目录树中添加内容时,都需要手动重新调用cmake,以便重新评估glob。如果将文件放在中CMakeLists.txt,则正常的make(或ninja)将触发cmake的重新调用,因此您不会忘记它。这也使团队更加友好,因为那时团队成员也不会忘记执行cmake。但是我认为,只要有人添加了文件,就无需触摸makefile。只需编写一次,没有人需要再次考虑它。
Arne 2016年

3
@SteveLorimer我也不同意在项目的每个目录中放置一个CMakeLists.txt的模式,它只是分散了项目的配置,我认为一个文件来完成所有操作就足够了,否则您将失去概述实际上是在构建过程中完成的。这并不意味着不能有带有自己的CMakeLists.txt的子目录,我只是认为这应该是一个例外。
Arne

2
假设“ VCS”“版本控制系统”的缩写,则无关紧要。问题不是,工件不会添加到源代码管理中。问题是,CMake将无法重新评估添加的源文件。它不会重新生成构建系统输入文件。构建系统将愉快地坚持使用过时的输入文件,否则将导致错误(如果很幸运的话),或者如果运气不佳就会被忽略。GLOBbing在依赖关系计算链中产生缺口。这一个重要的问题,评论没有适当地承认这一点。
IInspectable '17

2
CMake和VCS完全隔离运行。VCS不知道CMake,而CMake不知道任何VCS。它们之间没有链接。除非您建议开发人员应采取手动步骤,从VCS中获取信息,并基于启发式清理并重新运行CMake。显然,这没有规模,并且容易受到人类特有的谬论的影响。不,对不起,到目前为止,您还没有为GLOBbing文件提供说服力。
IInspectable '17

39

最基本但最完整的示例可以在CMake教程中找到:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

对于您的项目示例,您可能具有:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

对于您的其他问题,本教程再次提供一种解决方法:创建一个包含在代码中的可配置头文件。为此,制作一个configuration.h.in具有以下内容的文件:

#define RESOURCES_PATH "@RESOURCES_PATH@"

然后在您的CMakeLists.txt添加:

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

最后,在代码中需要路径的地方,可以执行以下操作:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";

非常感谢您,特别是对于RESOURCE_PATH,以某种方式我没有得到我所要的configure_file。但是您手动添加了项目中的所有文件,是否有更好的方法来简单地定义从src树中添加所有文件的模式?
Arne 2014年

请参阅Dieter的答案,以及我对为什么不应该使用它的评论。如果您真的想使其自动化,则更好的方法可能是编写一个脚本,您可以运行该脚本以重新生成源文件列表(或使用为您执行此操作的cmake感知IDE;我对此并不熟悉)。
sgvd 2014年

3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"恕我直言,硬编码源目录的绝对路径是一个坏主意。如果您需要安装项目该怎么办?

2
我知道自动收集来源听起来不错,但是会导致各种复杂情况。不久前请参见以下问题以进行简短讨论:stackoverflow.com/q/10914607/1401351
彼得

2
如果不运行cmake,将会得到完全相同的错误。手动添加文件需要花费一秒钟的时间,每次编译运行cmake都需要花费一秒钟的时间;您实际上破坏了cmake的功能;在同一项目上工作并进行更改的某人会这样做:运行make->获取未定义的引用->希望记住重新运行cmake,或者与您一起提交文件错误->运行cmake->成功运行make,而如果添加文件他所做的事情是:成功进行跑步->与家人共度时光。总结一下,不要偷懒,将来可以避免自己和他人头痛。
sgvd 2014年

2

在这里,我编写了一个最简单但完整的CMakeLists.txt文件示例。

源代码

  1. 从hello world到跨平台Android / iOS / Web / Desktop的教程。
  2. 我在每个平台上发布了一个示例应用程序。
  3. 我的工作验证了08-cross_platform文件结构
  4. 对于我自己的团队来说,这可能并不完美,但却是有用且最佳的实践

之后,我提供了详细的文档。

如果您有任何疑问,可以与我联系,我想解释一下。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.