c++ - 最简单但完整的 CMake 示例

标签 c++ cmake project-setup

不知何故,我对 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”访问我的资源

最佳答案

经过一些研究,我现在有了自己版本的最简单但完整的 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)实际上是相同的。不存在的变量相当于一个空列表。列表在内部只是一个用分号分隔元素的字符串。因此,只有一个元素的列表根据定义就是那个元素,不会发生装箱。变量是全局的。内置函数提供了某种形式的命名参数,因为它们需要一些像 PUBLIC 这样的 id。或 DESTINATION在他们的参数列表中,对参数进行分组。但这不是语言特性,那些 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)

注意:虽然上面的用法是file(GLOB)在这里是合适的,问题特别要求一种技术,通过添加新的源文件来最大限度地减少对 CMakeLists.txt 文件的编辑频率,不鼓励使用这种技术 in the official documentation ,并在这些专门问题的答案中:#1 , #2 .

关于c++ - 最简单但完整的 CMake 示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21163188/

相关文章:

windows - MinGW Make 抛出 "The system cannot find the path specified."错误

java - Gradle - 一个项目,多个包,多个jar

c++ - 从树莓派到Windows平台

c++ - wchar_t 与 unsigned short 冲突

C++ 类模板复制构造函数返回类型

c++ - 框架管理库

windows - cmake 在 Windows 上查找 sqlite3 库

c++ - 如何在 C++ 图形中打印窗口控制台字体

html - Web 应用程序入门工具包

python - 新的、干净的 venv 项目 (Python 3/IntelliJ IDEA) 中的包要求 - 为什么会有这些要求?