CMake Hello World

312 阅读5分钟

对从未创建过 CMake 项目的我来说,工作中老是会遇到一些开源项目使用它。我也就是按说明文档用 cmake-gui 点两下,生成 Visual Stuio 项目文件,然后编译。

尝试学习过,但多是通篇下来没有主线和重点,全是介绍 CMake 语法糖,让人摸不清头脑,搞不清到底要记哪些重点知识,如何组织这些碎片知识。

今天台风一刮,脑中突发奇想:CMake 本质不就是创建一个项目吗?像 Visual Studio 创建解决方案一样,下一步...下一步...完成。沿着这条主线再看 CMake 似乎一下就清晰了。

这里拿 Visual Studio 类比,CMake 本质就是创建一个解决方案 (.sln),然后向解决方案中不断添加项目 (.vcxproj),再然后就是为项目添加源码,以及配置项目属性了。以下章节我将沿着这条主线展开。

一、创建解决方案 (.sln)

像 Visual Studio 一样,第一步创建解决方案 (.sln),CMake 非常简单,创建一个文本文件即可,文件名必须叫:CMakeLists.txt

> mkdir HelloCMake
> cd HelloCMake
> fsutil file createNew CMakeLists.txt 0

然后我们就可以打开 CMakeLists.txt 文件进行编辑了。

1. CMake 版本

像使用不同版本的 Visual Studio 一样,CMake 也存在一些版本间的差异,所以上来先明确我们对 CMake 最低版本的要求。

cmake_minimum_required(VERSION 3.12)

它的语法也非常简单,详见官方文档 (cmake_minimum_required)

cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
2. 创建解决方案
project(HelloCMake)

这句非常简单,就是创建一个 HelloCMake.sln 文件。它的语法也非常简单,详见官方文档 (project)

project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])

到这里,我们已经完成了解决方案的创建,完整 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.12)
project(HelloCMake)

有了这个文件,我们便可以生成 .sln 工程了,一般我们将工程生成到 build 文件夹下:

> mkdir build
> cd build
> cmake ..

当然你也可以指定 Visual Studio 版本等信息,可以通过 cmake --help 查看参数详情,这里不做重点介绍。

二、添加项目 (.vcxproj)

为了简单起见,我这里创建 3 个项目,且三个项目的源代码都与 CMakeLists.txt 文件在同级目录。文件列表如下:

CMakeLists.txt
----
app.h
app.cpp
app2.cpp
----
mylib.h
mylib.cpp
mylib2.cpp
----
mydll.h
mydll.cpp
mydll2.cpp

CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.12)

# 1. 创建解决方案
project(HelloCMake)

# 2. 向解决方案中添加应用程序项目 (exe)
add_executable(app app.cpp app2.cpp)

# 3. 向解决方案中添加静态库项目 (lib)
add_library(mylib STATIC mylib.cpp mylib2.cpp)

# 4. 向解决方案中添加动态库项目 (dll)
add_library(mydll SHARED mydll.cpp mydll2.cpp)

此时运行 cmake 命令,就会为我们生成一个 HelloCMake.sln,它包含有 3 个项目分别是 app.vcxproj、mylib.vcxproj、mydll.vcxproj。至于 add_executable 等函数的语法详情请自行移步至 官方文档 (index) 搜索。

1. 添加文件

刚才我们添加源代码的方式过于简单,对于复杂的源码目录结构,我们需要用于几个辅助函数:

# 将<dir>中扫描到的所有源代码列表赋值给变量<variable>,不包含子文件夹
aux_source_directory(<dir> <variable>)

# 将<value>列表赋值给变量<variable>
set(<variable> <value>... [PARENT_SCOPE])

# 可指定过滤条件
file(...)

直接上示例,我为我的 3 个项目分别再创建 3 个文件夹,每个文件夹下再添加一些文件:

+ src4app
--  app_header1.h
--  app_header2.h
--  app_src1.cpp
--  app_src2.cpp
+ src4lib
--  lib_header1.h
--  lib_header2.h
--  lib_src1.cpp
--  lib_src2.cpp
+ src4dll
--  dll_header1.h
--  dll_header2.h
--  dll_src1.cpp
--  dll_src2.cpp

CMakeLists.txt 文件完整内容如下:

cmake_minimum_required(VERSION 3.12)
project(HelloCMake)

aux_source_directory(src4app APP_SOURCE_LIST)
aux_source_directory(src4lib LIB_SOURCE_LIST)
aux_source_directory(src4dll DLL_SOURCE_LIST)

set(APP_SOURCE_LIST ${APP_SOURCE_LIST} app.cpp app2.cpp)
set(LIB_SOURCE_LIST ${LIB_SOURCE_LIST} mylib.cpp mylib2.cpp)
set(DLL_SOURCE_LIST ${DLL_SOURCE_LIST} mydll.cpp mydll2.cpp)

file(GLOB APP_HEADER_LIST app.h "src4app/*.h")
file(GLOB LIB_HEADER_LIST "mylib.h" src4lib/*.h)
file(GLOB DLL_HEADER_LIST mydll.h src4dll/*.h)

add_executable(app ${APP_SOURCE_LIST} ${APP_HEADER_LIST})
add_library(mylib STATIC ${LIB_SOURCE_LIST} ${LIB_HEADER_LIST})
add_library(mydll SHARED ${DLL_SOURCE_LIST} ${DLL_HEADER_LIST})
2. 筛选文件

拿到文件列表后,我们可能需要对该列表进行一些增删改查操作,这时需要用到 list 函数,详见官方文档 (list)

Reading
  list(LENGTH <list> <out-var>)
  list(GET <list> <element index> [<index> ...] <out-var>)
  list(JOIN <list> <glue> <out-var>)
  list(SUBLIST <list> <begin> <length> <out-var>)

Search
  list(FIND <list> <value> <out-var>)

Modification
  list(APPEND <list> [<element>...])
  list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>)
  list(INSERT <list> <index> [<element>...])
  list(POP_BACK <list> [<out-var>...])
  list(POP_FRONT <list> [<out-var>...])
  list(PREPEND <list> [<element>...])
  list(REMOVE_ITEM <list> <value>...)
  list(REMOVE_AT <list> <index>...)
  list(REMOVE_DUPLICATES <list>)
  list(TRANSFORM <list> <ACTION> [...])

Ordering
  list(REVERSE <list>)
  list(SORT <list> [...])

来举例说明:

# 文件列表:a.cpp;b.cpp;c.cpp
set(MyFileList a.cpp b.cpp c.cpp)

# 将文件列表的长度赋值给变量len
list(LENGTH MyFileList len)

# 打印len值:"len = 3"
message("len = ${len}")

其他操作就不详述了,总之记录它可以实现对列表的增删改查即可。

三、项目属性设置

创建完解决方案,添加好项目及其源码后,接下来就是设置项目的属性了。

1. 头文件包含路径

对应 Visual Studio 的项目属性 -> 附加包含目录,直接上示例:

# 设置所有项目
include_directories(
    src4lib/include
    src4dll/include)

# 设置指定项目
target_include_directories(mylib PRIVATE boost/include)
2. 宏定义

对应 Visual Studio 的项目属性 -> 预处理器定义,上示例:

# 设置所有项目
add_compile_definitions(NOMINMAX)

# 设置指定项目
target_compile_definitions(mydll PRIVATE LIBADD_API_EXPORTS)
3. 编译选项

对应 Visual Studio 的项目属性 -> C/C++ -> 所有选项 -> 附加选项,上示例:

target_compile_options(mylib PRIVATE /utf-8)
4. 链接器

对应 Visual Studio 的项目属性 -> 链接器,上示例:

# 相关函数
link_directories([AFTER|BEFORE] directory1 [directory2 ...])
target_link_libraries(<target> ... <item>... ...)
target_link_directories(<target> [BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
  
# 示例:app项目,链接了mylib和mydll
target_link_libraries(app mylib mydll)

到这里,项目常用的配置信息就差不多全涉及了。CMake 可以设置更多项目细节参数,用到的时候再去找资料即可,没必要记忆。

四、多层 CMakeLists.txt

每个项目都应该有自己独立的 CMakeLists.txt 文件,而不是全混在同一个文件中。经过上述章节的介绍,我们已经可以手写一个简单的小项目了,把各个小项目放在各自的文件夹下,然后在文件夹外部,写一个总的 CMakeLists.txt,把其他所有小项目文件夹汇总即可。

add_subdirectory(mylib)
add_subdirectory(mydll)

五、功能开关

项目有时需要根据不同的编译环境,配置不同的选项,这里就需要一些开关来控制,上示例:

# option用于定义布尔型选项,可以在构建项目时启用或禁用特定的功能。[value]通常是 ON 或 OFF
option(<variable> "<help_text>" [value])

# 示例
option(ENABLE_LOGGING "Enable logging system" ON)
if (ENABLE_LOGGING)
	add_compile_definitions(ENABLE_LOGGING)
endif()

六、CMake 内置变量

CMake 内置了很多变量方便我们使用,这里列几个常用的变量:

${PROJECT_SOURCE_DIR}
# 工程根目录

${PROJECT_BINARY_DIR}
# cmake输出目录

${PROJECT_NAME}
# sln 名称

${CMAKE_CURRENT_SOURCE_DIR}
# 当前处理的 CMakeLists.txt 所在的路径

${CMAKE_CURRENT_BINARY_DIR}
# 目标编译目录

${CMAKE_CURRENT_LIST_DIR}
# CMakeLists.txt 的完整路径

${EXECUTABLE_OUTPUT_PATH}
# 重新定义目标二进制可执行文件的存放位置

${LIBRARY_OUTPUT_PATH}
# 重新定义目标链接库文件的存放位置

相信到这里,大家对 CMake 就有了非常清晰的认识。它的出现是为了解决跨平台项目的创建问题,如 Visual Studio 创建的项目 Linux 下无法使用,Makefile 在 Windows 系统下又不好编译等。CMake 会为我们的代码,自动生成 Visual Studio 工程文件,或 Makefile 文件,而无需我们去处理。


参考文档: