CMake
选择理由是它比其他make工具应用更广泛,安装也很简单,官方持续更新了很多现代化的语法。 但目前虽然有很多文档/材料,但缺乏优秀的从零开始的CMake中文教程,之前大学里写CMake就经常遇坑,只能用StackOverflow维持生活… 所以这篇博客是关于我学习CMake的记录,也可作为新手教程以供参考。
我倾向于用实际例子记录,对出现的问题进行解释、修正,对写起来不优雅的地方进行优化。
另外,因为存在个人翻译不准确的情况,附加了一些参考的超链接。
Hello, World
CMake构建需要使用它特定的脚本语言,以HelloWorld作为开始:
- 新建项目文件夹,在其中创建CMakeLists.txt
- 指定本项目构建需要的CMake最低版本: cmake_minimum_required(VERSION xxx)
- 尽量选择3.x最近的正式发布版本作为学习
- 指定项目名称/版本,使用的编程语言:project(HelloWorld VERSION 0.1.0 LANGUAGES CXX)
- 还可以指定项目描述,项目主页等
- 单文件为例,以Main.cpp生成HelloWorld.exe
- add_executable(HelloWorld Main.cpp)
- 项目名称HelloWorld重复出现了两次,我们定义变量:SET(ProjectName HelloWorld),通过${ProjectName}来替换重复的地方
- 接下来同CMake-GUI的正常使用,生成项目
- 除了HelloWorld,还会生成其他项目
- ALL_BUILD相当于makefile的默认目标,用于构建所有项目
- ZERO_CHECK用于检查CMakeLists.txt是否过期,保证其修改后能正确重新构建
CMakeLists.txt:
# CMake vesion in my local machine is 3.17
cmake_minimum_required(VERSION 3.17)
# Language C++ : CXX
SET(ProjectName HelloWorld)
project(${ProjectName} VERSION 0.1.0 LANGUAGES CXX)
add_executable(${ProjectName} Main.cpp)
Main.cpp:
#include <iostream>
int main()
{
std::cout << "Hello World!" << std::endl;
return 0;
}
Hello, a bigger World
最近正好在学习DirectX12,试试用CMake来构建图形程序~
与构建相关的目录结构如下:
- FireSoul
- assets : 资源目录
- bin : 输出目录
- build : 构建目录
- lib :库目录
- src : 源码
- folder1
- include
- *.h
- *.cpp
- include
- folder2
- include
- *.h
- *.cpp
- include
- …
- CMakeLists.txt
- folder1
- CMakeLists.txt
组织多重目录
现在我们的代码是分散在各个子目录里的,需要使用add_subdirectory(目录)包含子目录,这些子目录下也需要创建CMakeLists.txt,会在add_subdirectory后执行。
设置输出目录为bin
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
设置Unicode字符集
add_definitions(-DUNICODE -D_UNICODE)
./CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
SET(ProjectName FireSoul)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
project(${ProjectName} VERSION 0.1.0 LANGUAGES CXX)
add_definitions(-DUNICODE -D_UNICODE)
add_subdirectory(src)
设置为窗口程序
set_target_properties(${ProjectName} PROPERTIES
LINK_FLAGS /SUBSYSTEM:WINDOWS
包含多个头文件 & 添加多个源文件
现在我们需要包含头文件目录,使用include_directories(目录)。
add_executable现在要输入多个源文件名称,我们想要让CMake可以自动搜索目录下的源文件然后输入过来。
使用aux_source_directory(目录 变量名):这个指令可以将目录下的所有源文件写入到变量中。
./source/CMakeLists.txt:
include_directories(
./core/include
./common/include
./main/include
)
aux_source_directory(. CxxFiles)
aux_source_directory(./core/ CxxFiles)
aux_source_directory(./common/ CxxFiles)
aux_source_directory(./main/ CxxFiles)
add_executable(${ProjectName} ${CxxFiles})
set_target_properties(${ProjectName} PROPERTIES
LINK_FLAGS /SUBSYSTEM:WINDOWS
)
)
[优化] 使用target_include_directories
include_directories在CMake新版本中不被推荐,可以使用进阶版的target_include_directories(项目 作用域 头文件目录)。
有三种不同的作用域:
- PRIVATE
- 头文件目录会填充指定项目的INCLUDE_DIRECTORIES属性
- 目录下的头文件可以出现在这个项目的头文件/源文件中
- INTERFACE
- 头文件目录会填充指定项目的INTERFACE_INCLUDE_DIRECTORIES属性
- 目录下的头文件可以出现在这个项目的头文件中,但不能出现在源文件中
- target_include_directories(libname INTERFACE include PRIVATE include/libname),可以直接在libname项目中包含include/libname下的头文件,但对于其他使用libname的项目只能添加libname/后缀
- PUBLIC
- 填充PRIVATE + INTERFACE会填充的属性
- 这些头文件有可能出现在项目中,也可能在使用该项目的其他项目中
[问题] 包含目录识别为外部依赖
这里发现虽然项目构建/编译成功,但是头文件被项目识别为外部依赖,没有出现在项目中。 这里我们需要把所有*.h加到add_executeable中。
./source/CMakeLists.txt:
SET(HeaderFiles
./common/include/Camera.h
./common/include/d3dApp.h
./common/include/d3dUtil.h
./common/include/d3dx12.h
./common/include/DDSTextureLoader.h
./common/include/GameTimer.h
./common/include/GeometryGenerator.h
./common/include/MathHelper.h
./common/include/UploadBuffer.h
./main/include/ShadowMap.h
./main/include/FrameResource.h
)
aux_source_directory(. CxxFiles)
aux_source_directory(./core/ CxxFiles)
aux_source_directory(./common/ CxxFiles)
aux_source_directory(./main/ CxxFiles)
add_executable(${ProjectName} ${HeaderFiles} ${CxxFiles})
set_target_properties(${ProjectName} PROPERTIES
LINK_FLAGS /SUBSYSTEM:WINDOWS
)
target_include_directories(${ProjectName} PRIVATE
./core/include
./common/include
./main/include
)