CMakeLists 写法总结2.0

目录

1. 文件架构的设计

2. 顶层CMakeLists的写法

3. 子层CMakeLists的写法

4. 底层CMakeLists的写法


有了之前对cmakelists写法1.0的总结(优快云),基本上对如何构建cmakelists有了了解。最近在读一些复杂工程的时候,发现其实组织cmakelists的能力其实是架构整个工程的能力,下面将从工程化的角度,进一步做cmakelists写法的进阶介绍。

1. 文件架构的设计

回顾一下1.0中的文件结构:

|——— build
|——— include
|    |—— function.h
|——— src
|    |—— function.cc
|——— submodule
|    |—— submodule.cc
|    |—— submodule.h
|    |—— CMakeLists.txt
|——— test3.cc
|——— CMakeLists.txt

这里由于test3.cc也在最外层,所以最外层的CMakeLists.txt中很多写法是对主程序test3.cc的具体执行。而对于一个大型的工程来说,往往主程序的入口不在最外层,最外层的CMakeLists.txt可以更专注于整个工程的一些总体构建思想。

下面设计一个稍微复杂一点的文件组织方式:

|——— build
|——— app
|    |—— x86
|         |—— test4.cc
|         |—— CMakeLists.txt
|    |—— arm
|         |—— test4.cc
|         |—— CMakeLists.txt
|    |—— CMakeLists.txt
|——— modules
|    |—— module1
|         |—— module1.cc
|         |—— CMakeLists.txt
|    |—— module2
|         |—— module2.cc
|         |—— CMakeLists.txt
|    |—— CMakeLists.txt
|——— CMakeLists.txt

这个文件组织方式有点接近实际的复杂工程,具体的逻辑代码放在modules文件夹下,可执行的入口函数放在app文件夹下,并提供x86和arm两个入口,可以实现同一套代码的交叉编译。下面来看最外层的CMakeLists.txt的一些写法。

2. 顶层CMakeLists的写法

# cmake 最小版本需求
cmake_minimum_required(VERSION 3.0.0)

project(test4 VERSION 2.0)

# 增加编译选项,通过-D在外部定义,如果没有定义,就set PLATFORM 为x86
if(NOT PLATFORM) 
  set(PLATFORM x86)
endif()

# 增加编译选项,通过-D在外部定义,如果没有定义,就set CMAKE_BUILD_TYPE 为Release
if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release)
endif()

# STATUS表示输出信息的级别,另外还有WARNING、AUTHOR_WARNING、SEND_ERROR和FATAL_ERROR
message(STATUS "Build for platform ${PLATFORM}")

# 在编译前确定是编译x86版本还是arm版本,需要用到的编译器名称不同
if(${PLATFORM} STREQUAL "x86")
  set(CMAKE_SYSTEM_PROCESSOR x86_64)
else()
  set(CMAKE_SYSTEM_PROCESSOR aarch64)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
  add_compile_options(-march=armv8.2)
endif()

# 设置一些基础的编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread -fmessage-length=0")
# 根据debug还是release设置更多的编译选项
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -Og -g3 -ggdb -fsanitize=address -fsanitize-recover=address")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O2 -DNDEBUG")

# 设置输出结果的目录
set(OUTPUT_ROOT ${CMAKE_SOURCE_DIR}/output)

# 连接到下一层的CMakeLists
add_subdirectory(modules)
add_subdirectory(app)

最上层的CMakeLists看着写的比较多(实际上可以扩展更多),但是因为剥离了具体的主程序编译,所以主要功能就是三类操作:

  • 设置工程名
  • set()为主的各类定义(包括编译平台,编译类型,编译选项,文件路径等等)
  • add_subdirectory()连接到子目录层的文件夹

这里我们着重从工程思维讲一下set,其他两项在1.0中都有具体解释。上述代码中PLATFORM是由于我们想提供交叉编译的可能,所以将其定义在最上层的CMakeLists中,并且在后续代码中,通过if判断,来设定不同的编译处理器,分别对x86和arm的平台增加不同的编译选项(调用编译器的路径也是不同的,可以通过PLATFORM字段进行区分,也可以搭配启动脚本做定义,这个后面再讲)

另外有一个CMAKE_BUILD_TYPE字段,这个我们在调用make命令的时候,能通过-DCMAKE_BUILD_TYPE来进行赋值,这样就能在make的时候,决定编译的时候用release还是debug。并且在上面代码中设置基础编译选项后,对debug和release设置一些差异化的选项。

最后还有一些文件路径等,都可以在顶层的CMakeLists中进行设置。

3. 子层CMakeLists的写法

cmake_minimum_required(VERSION 3.0.0)

# 根据平台不同,进行赋值
if(${PLATFORM} STREQUAL "x86")
    set(X86 ON)
else()
    set(ARM OFF)

# 如果一个平台想编译不同功能的接口,也可以在这里再进行分支的设置
set(BASIC OFF)

# 选择连接哪个子文件夹的cmakelist
if(X86)
  add_subdirectory(x86)
endif()

if(ARM)
  add_subdirectory(arm)
endif()


if(BASIC)
    add_subdirectory(basic)
endif()

在app文件夹下的子层CMakeLists逻辑就简单很多,主要是根据平台的不同,选择不同的子文件夹路径,这里对同一个平台,比如x86平台想编译不同功能的主函数入口,也可以从这里进行分支,主要就是找到对应平台及功能主函数的路径下的底层CMakeLists.txt。

4. 底层CMakeLists的写法

cmake_minimum_required(VERSION 3.0.0)

project(x86_test VERSION 2.0)

# 因为可能有不同的分支生成结果,所以用底层工程名来做生成结果路径下的文件夹的区分
set(PROJECT_OUTPUT_ROOT ${OUTPUT_ROOT}/${PROJECT_NAME})
# 针对当前功能主函数入口可以再增加一些编译选项的区分
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE -pie")

# 除了包含子module生成的库,还可以包含一些公共库
set(LINK_LIBS 
  module1
  module2
  # commlib
)

# 设置需要编译的文件
aux_source_directory(. APP_DIR_SRCS)
# 生成编译完成后的可执行文件
add_executable(${PROJECT_NAME} ${APP_DIR_SRCS})

# 设置编译预处理阶段头文件的查找路径(关乎源文件中#include的写法)
# 用docker的时候,有些路径需要明前定义出来
target_include_directories(${PROJECT_NAME}
  ${CMAKE_SOURCE_DIR}/
#   ${BUILD_SYSROOT}/lib/
#   ${BUILD_SYSROOT}/usr/lib/
)

# 设置链接阶段库文件的查找路径
target_link_directories(${PROJECT_NAME} PRIVATE
  ${LIBRARY_OUTPUT_PATH}# 这里包含子文件夹编译成库后的结果路径
)

# 设置链接阶段需要链接的库的名称
target_link_libraries(${PROJECT_NAME} PRIVATE
    ${LINK_LIBS} # 需要链接的库名称在上面定义好
)

#将生成的结果安装到最终的结果文件夹中
install(
  DIRECTORY
  ${CMAKE_CURRENT_SOURCE_DIR}/etc/
  DESTINATION ${PROJECT_OUTPUT_ROOT}/etc
)

最底层的cmakelists就是最接近源文件的,那当然涉及到 aux_source_directory(. APP_DIR_SRCS) 和 add_executable(${PROJECT_NAME} ${APP_DIR_SRCS})这种基本操作。但是对于最底层的cmakelists,因为涉及到具体的编译和链接,那么最重要的就是弄清楚源文件涉及到的所有头文件路径,库文件路径和库名称,这里着重强调三个命令:

  • target_include_directories:设置编译预处理阶段头文件的查找路径(关乎源文件中#include的写法)
  • target_link_directories:设置链接阶段库文件的查找路径
  • target_link_libraries:设置链接阶段需要链接的库的名称

弄清楚这三个命令,并且结合set()将所涉及到的路径和名称打包定义好,一个工程可用的CMakeLists体系就建立起来了。另外需要注意,add_executable命令需要放在包含target的命令之前。

上面的代码中还有install和一些生成文件结果的路径设置等,都是具体工程中可以根据实际情况修改的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值