判断CMake编译环境

编译类型CMAKE_BUILD_TYPE

可取Debug, Release, RelWithDebInfo, MinSizeRel等等预设值

1if (CMAKE_BUILD_TYPE MATCHES Debug)
2    #do some thing
3endif()

系统环境CMAKE_SYSTEM_NAME

代表当前系统的类型, 值有ANDROID, APPLE, IOS, UNIX, WIN32, WINCE, WINDOWS_PHONE等

可以直接对这些值进行条件判断来确定

1if (UNIX)
2    #cond1
3elseif(WIN32)
4    #cond2
5endif()

编译工具环境

编译环境包括MSVC, MINGW, BORLAND, 等

可直接用if判断

msvc版本可通过MSVC10, MSVC11, MSVC12, MSVC14, MSVC60, MSVC70, MSVC71, MSVC80, MSVC90, MSVC_TOOLSET_VERSION, MSVC_VERSION等判断

编译器设置

C/C++标准

1# 全局
2set(CMAKE_CXX_STANDARD 14)
3set(CMAKE_C_STANDARD 11)

编译器flag

主要靠修改CMAKE_CXX_FLAG_<BUILD_TYPE>来进行修改, 也可以直接修改所有类型的, 或者通过判断编译类型来配置

1set(DEFINES "-Wunused-parameter")
2set(CMAKE_CXX_FLAGS_DEBUG "-pipe -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC ${DEFINES}")
3set(CMAKE_C_FLAGS_DEBUG "-pipe -O2 -Wall -W -D_REENTRANT -fPIC ${DEFINES}")
4set(CMAKE_EXE_LINKER_FLAGS_DEBUG "-fno-pie")
5
6set(CMAKE_CXX_FLAGS_RELEASE "-pipe -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC ${DEFINES}")
7set(CMAKE_C_FLAGS_RELEASE "-pipe -O2 -Wall -W -D_REENTRANT -fPIC ${DEFINES}")
8set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-fno-pie")

特殊的编译器设置

  • sanitize (msvc目前只支持32位)

检查内存泄露和内存其他问题

1list(APPEND CMAKE_CXX_FLAGS_DEBUG -fsanitize=address -g)

配置后运行程序时发生内存问题程序会立马中断退出, 并且打印内存问题

  • /utf-8 (msvc)

强制源码使用utf-8, 避免代码到了其他平台乱码

1list(APPEND CMAKE_CXX_FLAGS_DEBUG /utf-8)
  • -DUNICODE (msvc)

决定windows下面使用标准字符api还是宽字符api

1#ifdef UNICODE
2#define ShellExecute  ShellExecuteW
3#else
4#define ShellExecute  ShellExecuteA
5#endif // !UNICODE
  • /SUBSYSTEM:WINDOWS

更改软件启动入口, 默认是/SUBSYSTEM:CONSOLE, 会先启动命令行再启动其他ui, 而更改后命令行将不再启动, 所有打印信息也将会看不到, 只有通过x64dbg这种调试软件才能看到

  • /OPT:REF /OPT:ICF

添加这两个参数release模式下编译也会生成pdb文件

根据模板生成文件

生成动态库的export头文件

一般来说Windows平台生成动态库的头文件要区分__declspec(dllexport)__declspec(dllimport),会专门用一个头文件进行定义,同时区分动态链接库和静态链接库,如果项目较复杂可以用cmake生成到build目录进行包含,利用``函数,下面是其的定义

 1GENERATE_EXPORT_HEADER( LIBRARY_TARGET
 2          [BASE_NAME <base_name>]
 3          [EXPORT_MACRO_NAME <export_macro_name>]
 4          [EXPORT_FILE_NAME <export_file_name>]
 5          [DEPRECATED_MACRO_NAME <deprecated_macro_name>]
 6          [NO_EXPORT_MACRO_NAME <no_export_macro_name>]
 7          [INCLUDE_GUARD_NAME <include_guard_name>]
 8          [STATIC_DEFINE <static_define>]
 9          [NO_DEPRECATED_MACRO_NAME <no_deprecated_macro_name>]
10          [DEFINE_NO_DEPRECATED]
11          [PREFIX_NAME <prefix_name>]
12          [CUSTOM_CONTENT_FROM_VARIABLE <variable>]
13)
14
15#例如
16
17generate_export_header(${PROJECT_NAME}
18            BASE_NAME ${MODULE_NAME}
19            EXPORT_MACRO_NAME PROJECT_${MODULE_NAME_UPPER}_API
20            EXPORT_FILE_NAME ${MODULE_NAME}_Export.hpp
21            STATIC_DEFINE ${MODULE_NAME_UPPER}_BUILT_AS_STATIC
22            )

在需要导出的类或函数加上EXPORT_MACRO_NAME就可以导出动态库了

生成程序版本信息

程序版本信息有许多,包括版本号、git分支、git提交号、sdk版本、构建日期等等,Windows下还有文件版本信息(属性里面的内容)

版本信息模板

编写模板,和生成代码

 1//Global.hpp.in
 2#include "PROJECT_global.hpp"
 3#include "BuildTime.hpp"
 4
 5namespace PROJECT {
 6
 7#define PROJECT_APP_NAME "@PROJECT_APP_NAME@"
 8#define PROJECT_APP_ORG "@PROJECT_APP_ORG@"
 9#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
10#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
11#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
12#define PROJECT_VERSION_TWEAK @PROJECT_VERSION_TWEAK@
13#define PROJECT_VERSION_BETA @PROJECT_VERSION_BETA@
14#define PROJECT_VERSION "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@PROJECT_VERSION_TWEAK@"
15#define PROJECT_VERSION_STATUS "@PROJECT_VERSION_STATUS@"
16#define PROJECT_VERSION_COMMIT "@PROJECT_VERSION_COMMIT@"
17#define PROJECT_VERSION_BRANCH "@PROJECT_VERSION_BRANCH@"
18#define PROJECT_VERSION_DATE BUILD_TIME
19#define PROJECT_QT_VERSION "@PROJECT_QT_VERSION@"
20#define PROJECT_QT_PLATFORM "@PROJECT_QT_PLATFORM@"
21}
 1set(PROJECT_APP_NAME "Project")
 2set(PROJECT_APP_ORG "Company")
 3set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK})
 4set(PROJECT_VERSION_BETA 1)
 5set(PROJECT_VERSION_STATUS ${GIT_STATUS})
 6set(PROJECT_VERSION_COMMIT ${GIT_COMMIT})
 7set(PROJECT_VERSION_BRANCH ${GIT_BRANCH})
 8set(PROJECT_VERSION_DATE ${GIT_DATE})
 9set(PROJECT_QT_VERSION ${QT_VERSION})
10set(PROJECT_QT_PLATFORM ${QT_PLATFORM})
11
12configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Global.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/gen/Global.hpp @ONLY)

从文本文件获取版本号

 1file(READ ${CMAKE_SOURCE_DIR}/VERSION VERSION_STR)
 2string(REPLACE "." ";" VERSION_STR_LIST ${VERSION_STR})
 3list(LENGTH VERSION_STR_LIST LEN)
 4if (NOT LEN EQUAL 4)
 5    message(FATAL_ERROR "version number not in correct format")
 6endif ()
 7list(GET VERSION_STR_LIST 0 PROJECT_VERSION_MAJOR)
 8list(GET VERSION_STR_LIST 1 PROJECT_VERSION_MINOR)
 9list(GET VERSION_STR_LIST 2 PROJECT_VERSION_PATCH)
10list(GET VERSION_STR_LIST 3 PROJECT_VERSION_TWEAK)
11message(VERSION:${PROJECT_VERSION_MAJOR})

利用cmake读写文件可以实现buildVersion累加

获取git信息

 1include(FindGit)
 2if (${GIT_FOUND})
 3    unset(GIT_VERSION_NUM CACHE)
 4    unset(GIT_BRANCH CACHE)
 5    message("Found Git executable, version: ${GIT_VERSION_STRING}")
 6    execute_process(
 7            COMMAND ${GIT_EXECUTABLE} describe --tags --exact-match --abbrev=0
 8            WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
 9            OUTPUT_VARIABLE GIT_TAG
10            OUTPUT_STRIP_TRAILING_WHITESPACE
11            RESULT_VARIABLE GIT_VERSION_RESULT
12    )
13    if (NOT ${GIT_VERSION_RESULT} EQUAL 0)
14        set(GIT_STATUS "u")
15        set(GIT_TAG "notag")
16    else ()
17        set(GIT_VERSION_STATUS "")
18    endif ()
19
20    execute_process(
21            COMMAND ${GIT_EXECUTABLE} log -1 --format=%H
22            WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
23            OUTPUT_VARIABLE GIT_COMMIT
24            OUTPUT_STRIP_TRAILING_WHITESPACE
25            RESULT_VARIABLE GIT_COMMIT_RESULT
26    )
27    if (NOT GIT_COMMIT_RESULT EQUAL 0)
28        message(FATAL_ERROR "cannot find git commit number")
29    else ()
30        message("git commit: ${GIT_COMMIT}")
31    endif ()
32
33    execute_process(
34            COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
35            WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
36            OUTPUT_VARIABLE GIT_BRANCH
37            OUTPUT_STRIP_TRAILING_WHITESPACE
38            RESULT_VARIABLE GIT_BRANCH_RESULT
39    )
40    if (NOT GIT_BRANCH_RESULT EQUAL 0)
41        message(WARNING "cannot find git branch name")
42    else ()
43        message("git branch: ${GIT_BRANCH}")
44    endif ()
45
46    set(GIT_STATUS ${GIT_STATUS} CACHE INTERNAL "git version status")
47    set(GIT_COMMIT ${GIT_COMMIT} CACHE INTERNAL "git commit number")
48    set(GIT_BRANCH ${GIT_BRANCH} CACHE INTERNAL "git branch name")
49else ()
50    MESSAGE(WARNING "cannot find Git executable, skip git version check")
51    set(GIT_COMMIT "unknown")
52    set(GIT_BRANCH "unknown")
53endif ()

构建日期

构建日期用cmake生成问题在于编译的时候不一定会重新跑cmake,所以需要用到cmake执行cmake脚本的命令

更新时间cmake:

 1message("start updating build time")
 2if (NOT DEFINED BUILD_TIME_HEADER)
 3    message(FATAL_ERROR "Need to pass `-DBUILD_TIME_HEADER:PATH=" /path/to/header"` to this script")
 4endif ()
 5unset(DATE CACHE)
 6string(TIMESTAMP DATE "%b %d %Y %a %H:%M:%S")
 7set(DATE "${DATE}")
 8message("compile date: ${DATE}")
 9file(WRITE ${BUILD_TIME_HEADER}
10        "//this file is automatically generated by UpdateBuildTime.cmake\n"
11        "#ifndef BUILD_TIME_HEADER\n"
12        "#define BUILD_TIME_HEADER\n"
13        "\n"
14        "#define BUILD_TIME \"${DATE}\"\n"
15        "\n"
16        "#endif //BUILD_TIME_HEADER\n"
17        )
18message("Write build date to file ${BUILD_TIME_HEADER}")

在构建时增加BuildTime目标依赖

1add_custom_target(BuildTime
2        COMMAND ${CMAKE_COMMAND}
3        -DBUILD_TIME_HEADER:PATH="${BUILD_TIME_HEADER}"
4        -P "${CMAKE_SOURCE_DIR}/cmake/UpdateBuildTime.cmake")
5if (CMAKE_BUILD_TYPE MATCHES Release)
6    add_dependencies(${PROJECT_NAME} BuildTime)
7endif ()

增加条件的原因是防止调试编译时过于频繁的更新编译时间

生成Windows下文件信息

将信息编译到exe或者dll中, 同样利用confugure_file

 1#if defined(UNDER_CE)
 2#include <winbase.h>
 3#else
 4#include <winver.h>
 5#endif
 6VS_VERSION_INFO VERSIONINFO
 7        FILEVERSION @RC_FILE_VERSION_NUM@
 8        PRODUCTVERSION @RC_PRODUCT_VERSION_NUM@
 9        FILEFLAGSMASK 0x3fL
10#ifdef _DEBUG
11FILEFLAGS 0x1L
12#else
13FILEFLAGS 0x0L
14#endif
15FILEOS VOS__WINDOWS32
16FILETYPE VFT_DLL
17FILESUBTYPE 0x0L
18BEGIN
19        BLOCK "StringFileInfo"
20BEGIN
21        BLOCK "040904b0"
22BEGIN
23        VALUE "Comments", @RC_COMMENTS@
24        VALUE "CompanyName", @RC_COMPANY_NAME@
25        VALUE "FileDescription", @RC_FILE_DESCRIPTION@
26        VALUE "FileVersion", @RC_FILE_VERSION@
27        VALUE "InternalName", @RC_INTERNAL_NAME@
28        VALUE "LegalCopyright", @RC_LEGAL_COPYRIGHT@
29        VALUE "OriginalFilename", @RC_ORIGINAL_FILE_NAME@
30        VALUE "ProductName", @RC_PRODUCT_NAME@
31        VALUE "ProductVersion", @RC_PRODUCT_VERSION@
32        VALUE "PrivateBuild", @RC_PRIVATE_BUILD@
33        VALUE "LegalTrademarks", @RC_LEGAL_TRADEMARKS@
34        END
35END
36        BLOCK "VarFileInfo"
37BEGIN
38        VALUE "Translation", 0x804, 1300
39END
40        END

编译时和源代码一起编译就可以

导入第三方库

 1function(set_library_target NAMESPACE LIB_NAME DEBUG_LIB_FILE_NAME RELEASE_LIB_FILE_NAME INCLUDE_DIR)
 2    add_library(${NAMESPACE}::${LIB_NAME} STATIC IMPORTED)
 3    set_target_properties(${NAMESPACE}::${LIB_NAME} PROPERTIES
 4            IMPORTED_CONFIGURATIONS "RELEASE;DEBUG"
 5            IMPORTED_LOCATION_RELEASE "${RELEASE_LIB_FILE_NAME}"
 6            IMPORTED_LOCATION_DEBUG "${DEBUG_LIB_FILE_NAME}"
 7            INTERFACE_INCLUDE_DIRECTORIES "${INCLUDE_DIR}"
 8            )
 9    set(${NAMESPACE}_${LIB_NAME}_FOUND 1)
10endfunction()

FetchContent离线

以google的or-tools为例,在其cmake/dependencies/CMakeLists.txt中定义了一些需要从GitHub下载的代码库,还有代码的patch动作,如果这个or-tools库需要拷贝到另外的机器上编译,仍然会面临需要联网下载代码的可能。

仔细分析其中的过程,可以发现其下载的代码都在${CMAKE_BINARY_DIR}/_deps/xxxx-src下面,如果能够修改其CMake代码即可实现只使用本地仓库代码

修改前:

 1# ##############################################################################
 2# ABSEIL-CPP
 3# ##############################################################################
 4if(BUILD_absl)
 5  message(CHECK_START "Fetching Abseil-cpp")
 6  list(APPEND CMAKE_MESSAGE_INDENT "  ")
 7  set(ABSL_ENABLE_INSTALL ON)
 8  set(ABSL_USE_SYSTEM_INCLUDES ON)
 9  set(ABSL_PROPAGATE_CXX_STD ON)
10  FetchContent_Declare(
11    absl
12    GIT_REPOSITORY "https://github.com/abseil/abseil-cpp.git"
13    GIT_TAG "20230802.1"
14    PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../../patches/abseil-cpp-20230802.1.patch"
15  )
16  FetchContent_MakeAvailable(absl)
17  list(POP_BACK CMAKE_MESSAGE_INDENT)
18  message(CHECK_PASS "fetched")
19endif()

一台能联网的机器上使用该配置下载依赖并patch后编译通过,然后修改CMake

 1# ##############################################################################
 2# ABSEIL-CPP
 3# ##############################################################################
 4if(BUILD_absl)
 5  message(CHECK_START "Fetching Abseil-cpp")
 6  list(APPEND CMAKE_MESSAGE_INDENT "  ")
 7  set(ABSL_ENABLE_INSTALL ON)
 8  set(ABSL_USE_SYSTEM_INCLUDES ON)
 9  set(ABSL_PROPAGATE_CXX_STD ON)
10  FetchContent_Declare(
11    absl
12    # GIT_REPOSITORY "https://github.com/abseil/abseil-cpp.git"
13    SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/absl-src"
14    GIT_TAG "20230802.1"
15    # PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../../patches/abseil-cpp-20230802.1.patch"
16  )
17  FetchContent_MakeAvailable(absl)
18  list(POP_BACK CMAKE_MESSAGE_INDENT)
19  message(CHECK_PASS "fetched")
20endif()

${CMAKE_BINARY_DIR}/_deps文件夹也拷贝到另外离线机器编译目录,按照以上修改CMake后即可使用缓存的依赖离线编译