CMake更进一步
以前翻译过 CMake
的官方文档,之后自己整理了一份入门级别的个人心得,随着很长一段时间的积累和沉淀,现在准备写项目管理中经常用到的一些比较好用的 CMake 用法。
跨平台
参考一些开源项目,摘取其中一部分比较简洁的,如下
# 如果没有指定 PROJECT_ARCH,尝试去猜一下
if(NOT DEFINED PROJECT_ARCH)
if(OS_WINDOWS AND "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "arm64")
set(PROJECT_ARCH "arm64")
elseif(CMAKE_SIZEOF_VOID_P MATCHES 8)
set(PROJECT_ARCH "x86_64")
else()
set(PROJECT_ARCH "x86")
endif()
endif()
# 暂时以 linux 和 windows 平台为例
# 定义宏 OS_LINUX,OS_WINDOWS,以及编译参数
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
set(OS_LINUX 1)
set(OS_POSIX 1)
add_definitions(-DOS_LINUX=1 -DOS_POSIX=1)
if(PROJECT_ARCH STREQUAL "x86_64")
add_compile_options(-m64 -march=x86-64)
add_link_options(-m64)
elseif(PROJECT_ARCH STREQUAL "x86")
add_compile_options(-m32)
add_link_options(-m32)
endif()
elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
set(OS_WINDOWS 1)
add_definitions(-DOS_WINDOWS=1)
endif()
版本号的玩法
之前在翻译官方文档的时候,有一节是 添加版本号和配置头文件
, 通过 configure_file()
进行一些可配置性的信息设置,在 CMakeLists.txt
中定义或从系统中获取,然后生成头文件加入到项目中
windows
project(test VERSION 1.0.0)
if(MSVC)
set(MY_VERSIONINFO_RC "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in"
"${MY_VERSIONINFO_RC}")
endif()
这里有个小细节,通过 project()
命令设置版本号的时候会给以下四个变量赋值
<PROJECT-NAME>_VERSION_MAJOR
: 版本号第一位<PROJECT-NAME>_VERSION_MINOR
: 版本号第二位<PROJECT-NAME>_VERSION_PATCH
: 版本号第三位<PROJECT-NAME>_VERSION_TWEAK
: 版本号第四位
这里的 version.rc.in
内容如下,这是windows下一个 dll 库的版本信息
1 VERSIONINFO
FILEVERSION ${PROJECT_VERSION_MAJOR}, ${PROJECT_VERSION_MINOR}, ${PROJECT_VERSION_PATCH}
PRODUCTVERSION ${PROJECT_VERSION_MAJOR}, ${PROJECT_VERSION_MINOR}, ${PROJECT_VERSION_PATCH}
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "${PROJECT_NAME} Binary"
VALUE "FileVersion", "${PROJECT_VERSION_MAJOR}, ${PROJECT_VERSION_MINOR}, ${PROJECT_VERSION_PATCH}"
VALUE "InternalName", "${PROJECT_NAME}.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "${PROJECT_NAME}.dll"
VALUE "ProductName", "${PROJECT_NAME}"
VALUE "ProductVersion", "${PROJECT_VERSION_MAJOR}, ${PROJECT_VERSION_MINOR}, ${PROJECT_VERSION_PATCH}"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
linux
linux 下一般就是给程序添加一个版本号字符串,编译成库后使用 strings 命令查看,类似下面这样
const char *LIB_INFO = "version: 1.0.0";
或者提供一个类似 getVersion
的返回字符串作为版本的接口,其实大同小异,我们就可以创建一个 VersionDefine.h.in
的文件
#pragma once
#define APP_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}"
cmake 里面只要简单的如下编写
set(COMMON_INFO "${CMAKE_CURRENT_SOURCE_DIR}/VersionDefine.h")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/VersionDefine.h.in"
"${COMMON_INFO}")
提供的接口也很简单
#include "VersionDefine.h"
std::string getVersion()
{
return APP_VERSION;
}
版本号的更改就很优雅,只需要更改 CMakeLists.txt 的 project(test VERSION 1.0.0)
一处就可以了
产物输出地址
$<config>
区分 debug, release 版本,动态库静态库可执行程序分开放,方便管理以及之后后处理等
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output/$<CONFIG>/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output/$<CONFIG>/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/output/$<CONFIG>/lib)
CMAKE_ARCHIVE_OUTPUT_DIRECTORY
:默认存放静态库的文件夹位置;CMAKE_LIBRARY_OUTPUT_DIRECTORY
:默认存放动态库的文件夹位置;LIBRARY_OUTPUT_PATH
:默认存放库文件的位置,如果产生的是静态库并且没有指定CMAKE_ARCHIVE_OUTPUT_DIRECTORY
则存放在该目录下,动态库也类似;CMAKE_RUNTIME_OUTPUT_DIRECTORY
:存放可执行软件的目录;
多模块的管理
比如我现在 Project
里面有 2 个模块 A
(动态库) 和 B
(可执行程序),项目的层级结构我一般会如下组织
Project
├─ CMakeLists.txt
└─ src
├─ A
│ └─ CMakeLists.txt
├─ B
│ ├─ CMakeLists.txt
│ └─ main.cpp
└─ common
└─ version.h.in
此时我会把 A
和 B
公共的配置提取到最外层的 CMakeLists.txt
里面
# 公共配置
cmake_minimum_required(VERSION 3.4)
...
# 编译参数,option等
...
# 统一的导出位置
...
# 跨平台宏等
...
# target
add_subdirectory(A)
add_subdirectory(B)
需要先编译 A
模块,然后编译 B
模块,我就只需要在 B
模块的 CMakeLists.txt
使用以下命令
add_dependencies(${PROJECT_NAME} A)
这样就能保证多模块的话,在编译 B
的时候,一定会先编译 A