程序员

[原创] Cmake实战指南

2018-01-04  本文已影响0人  赵国开

0 综述


我觉的Cmake比较核心的一些东西就是

1 如何组织项目编译框架


比如我当前项目源码根目录下面有这些源码目录

├── arch
├── cfg
├── doc
├── drivers
├── fs
├── include
├── kernel
├── library
├── sample
└── utils

我会在根目录放置一个 CMakeLists.txt,之后在每个需要管理的目录下面也放置一个CMakeLists.txt,如下所示(这边没有把include包含进来是因为头文件cmake会自动去构建不需要手动添加,拿C语言来举例,所有包含在.C中的.H文件,cmake全部会帮你自动计算出来,不需要手动去添加)

├── CMakeLists.txt
├── arch
│   └── CMakeLists.txt
├── cfg
│   └── CMakeLists.txt
├── doc
│   └── CMakeLists.txt
├── drivers
│   └── CMakeLists.txt
├── fs
│   └── CMakeLists.txt
├── include
├── kernel
│   └── CMakeLists.txt
├── library
│   └── CMakeLists.txt
├── sample
│   └── CMakeLists.txt
└── utils
└── CMakeLists.txt
├── cmake
├── build

需要说明下这边我多加了两个目录cmake和build,cmake目录一般用来放置一些通用的cmake源码模块(比如通用编译环境设置,编译链接的一些选项配置等等),build用来存放项目构建过程中所有的输出文件。
根目录下面CMakeLists.txt大致就可以按下面这样撰写,就可以把整个骨架搭起来。

#cmake最低版本要求
cmake_minimum_required(VERSION 3.10.0)

#项目名字为test
project(test NONE)

#包含通用的编译环境模块到顶层目录
include(${CMAKE_SOURCE_DIR}/cmake/base.cmake)

#下一级的编译目录
add_subdirectory(arch)
add_subdirectory(cfg)
add_subdirectory(doc)
add_subdirectory(drivers)
add_subdirectory(fs)
add_subdirectory(kernel)
add_subdirectory(library)
add_subdirectory(sample)
add_subdirectory(utils)

进入build目录运行整个编译框架

cd build && cmake .. && make

用到的相关命令

命令标签格式 说明
cmake_minimum_required(VERSION major.minor[.patch[.tweak]] [FATAL_ERROR]) 设置最低版本的cmake要求,一般放在第一行
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...]) 为项目设置名字,版本,启用的编程语言,我上面没有启用任何编程语言所以用NONE
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) 增加一个子目录到编译系统
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <VAR>][NO_POLICY_SCOPE]) 从给出的file文件加载和运行cmake代码

2 最终输出目标有哪些


#定义一个mytool 可执行程序输出目标,目前只有一个源文件  mytool.cpp
add_executable(mytool mytool.cpp)

#往mytool可执行程序输出目标添加源文件,现在mytool目标里面包含两个源文件
target_sources(mytool  PRIVATE mytool2.cpp)

#定义一个动态库archive 输出目标,文件有三个源文件
add_library(archive SHARED archive.cpp zip.cpp lzma.cpp)

#定义一个静态库archive 输出目标,也可以不指定STATIC 因为add_library默认输出目标是#静态库
add_library(archive STATIC archive.cpp zip.cpp lzma.cpp)

#从给出的源文件直接生成object文件,比如linux下C语言的.o文件
add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)

用到的相关命令

命令标签格式 说明
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...]) 定义一个名字为name可执行程序输出目标,其中 source1 [source2 ...]是源文件,比如C语言是.C的源文件,.H的源文件不需要指定,cmake会自动去搜索
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])

add_library(<name> OBJECT <src>...)
定义一个名字为name的库输出目标
target_sources(<target><INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) 往一个目标里面添加源文件,这个目标名字target是在add_executable() 或者add_library() 中定义的name

3 指定编译参数和链接参数


命令标签格式 说明
target_include_directories(<target> [SYSTEM] [BEFORE]<INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) Include的头文件的查找目录,也就是Gcc的[-Idir...]选项
target_compile_definitions(<target> <INTERFACE|PUBLIC|PRIVATE> [items1...][<INTERFACE|PUBLIC|PRIVATE> [items2...] ...]) 通过命令行定义的宏变量,也就是gcc的[-Dmacro[=defn]...]选项
target_compile_options(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...] [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...] gcc其他的一些编译选项指定,比如-fPIC
# gcc头文件查找目录,相当于-I选项,e.g -I/foo/bar
#CMAKE_SOURCE_DIR是cmake内置变量表示当前项目根目录
target_include_directories(test_elf
    PRIVATE
    ${CMAKE_SOURCE_DIR}
    ${CMAKE_SOURCE_DIR}/common
    ${CMAKE_SOURCE_DIR}/syscalls
)

# 编译的宏定义,e.g 相当于-D选项 e.g -Dmacro=defn
set(MONITOR_OMIT_BSS_INIT      "0")
set(MONITOR_OMIT_DATA_INIT     "0")
set(MONITOR_OMIT_T_CHECKS      "0")
target_compile_definitions(test_elf
    PRIVATE
    MONITOR_OMIT_BSS_INIT=${MONITOR_OMIT_BSS_INIT}              
    MONITOR_OMIT_DATA_INIT=${MONITOR_OMIT_DATA_INIT}            
    MONITOR_TRAP_NT_IRQS=${MONITOR_TRAP_NT_IRQS}                          
)

# 其他编译选项定义,e.g -fPIC
target_compile_options(test_elf
    PRIVATE
    -std=c99 
    -Wall 
    -Wextra 
    -Werror
)
命令标签格式 说明
target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <item>... [<PRIVATE|PUBLIC|INTERFACE> <item>...]...) item可以是链接到该目标的库的名字(去掉前缀lib和扩展名后缀之后的库名字),也就是gcc链接器的-llibrary选项

Item也可以是链接选项以-开始的item会被认为是链接选项(除了 -l和-framework)

Item也可以是要链接到该目标的库文件的完整路径(针对非标准路径的库时可以这样指定,当然也可以用link_directories命令来为链接器增加搜索库的搜索路径)
# 链接选项设置
target_link_libraries(test_elf
    PRIVATE
    -msoft-float 
    -static 
    -nostdlib
)

#设置链接的标准路径的库
target_link_libraries(test_elf PRIVATE gcc)

#设置链接的本project自己输出的目标库libkernel.a
target_link_libraries(test_elf PRIVATE kernel)

#设置链接非标准路径中别人编译好的库
target_link_libraries(test_elf 
PRIVATE 
$ENV{HOME}/lib/libtest.a
)

备注:也可以用其他方式来设置编译链接的一些参数,比如直接设置cmake内置变量或者其他命令,我这里选择这些基于目标的命令来操作主要是因为,基于目标来管理更容易维护和管理,不会污染到其他的作用域。

具体的含义如下:

PRIVATE 只给自己用,不给依赖者用
PUBLIC 自己和依赖者都可以用
INTERFACE 自己不用,给依赖着用
add_library(archive archive.cpp)
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)

add_library(serialization serialization.cpp)
target_compile_definitions(serialization INTERFACE USING_SERIALIZATION_LIB)

add_library(archiveExtras extras.cpp)
target_link_libraries(archiveExtras PUBLIC archive)
target_link_libraries(archiveExtras PRIVATE serialization)
# archiveExtras is compiled with -DUSING_ARCHIVE_LIB
# and -DUSING_SERIALIZATION_LIB

add_executable(consumer consumer.cpp)
# consumer is compiled with -DUSING_ARCHIVE_LIB
target_link_libraries(consumer archiveExtras)

4 对输出目标的其他处理


命令标签格式 说明
add_custom_command(TARGET <target>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[BYPRODUCTS [files...]]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[VERBATIM]
[USES_TERMINAL])
Target是库或者可执行文件输出目标

PRE_BUILD只有Visual Studio 8或者之后的版本才支持,其他情况等同于PRE_LINK

PRE_LINK在编译之后,链接之前运行,

POST_BUILD,该目标的所有构建规则都执行完之后才运行

COMMAND ,指定要执行的命令行命令和参数
#在test_elf链接之前,先拷贝一些test_elf需要用到的文件到编译目录,在进行编译
add_custom_command(TARGET test_elf PRE_LINK
COMMAND
cp ${CMAKE_BINARY_DIR}/cfg/start.o ${CMAKE_BINARY_DIR}/. && 
cp ${CMAKE_SOURCE_DIR}/target/imx6_gcc/imx6.ld ${CMAKE_BINARY_DIR}/.
)

#把编译输出的test_elf进行进一步处理,生成最终的bin文件
add_custom_command(TARGET test_elf POST_BUILD
    COMMAND ${CUSTOM_CMD_OBJCOPY} -O binary -S test_elf test_elf.bin
)

5 自动生成源码规则制定


#先为这个第三方工具设置一个自定义目标,这个目标并没有实际输出文件,
#只是一个目标的名字比如这边的autoconfig 
add_custom_target(autoconfig ALL)
#利用这个目标去运行这个第三方工具,这边是cfg,后面是这个工具需要的参数
add_custom_command(TARGET autoconfig PRE_BUILD
COMMAND 
cfg --pass ${pass} ${CFG_ELF_INCLUDE} ${rom_image} ${symbol_table} ${T_file} ${CFG_TABLES}
)   

#autoconfig和test_elf这两目标,没有直接的关系(像前面介绍的可执行程序,可能依赖其#他库输出目标,这里没有这样的关系),需要指定目标输出顺序,先对autoconfig进行输
#出(自动生成源码),在进行test_elf输出(编译和链接)
add_dependencies(test_elf autoconfig)

#把自动生成的源文件cfg_out.c加到test_elf目标去编译
target_sources(test_elf  PRIVATE cfg_out.c)
#指明cfg_out.c是自动生成的文件,否则会认为找不到文件而出错
set_source_files_properties(cfg_out.c PROPERTIES GENERATED 1)
命令标签格式 说明
add_custom_target(Name [ALL] [command1 [args1...]] [COMMAND command2[args2...] ...] [DEPENDS depend depend depend ... ] [BYPRODUCTS [files...]] [WORKING_DIRECTORY dir] [COMMENT comment] [VERBATIM] [USES_TERMINAL] [COMMAND_EXPAND_LISTS] [SOURCES src1 [src2...]])[items2...] ...]) 增加一个名字为Name的目标,该目标没有输出,所以它总是被构建。
ALL选项表明该目标被添加到默认的构建目标,所以它每次都会运行
add_dependencies(<target> [<target-dependency>]...) 在目标target在构建之前,后面的其他target-dependency依赖目标必须先被构建完成Target或者target-dependency目标是由add_executable(),add_library(), add_custom_target()命令输出的目标
set_source_files_properties([file1 [file2 [...]]] PROPERTIES prop1 value1 [prop2 value2 [...]]) 通过键/值对( key/value)来设置源文件的一些属性

6 如何编译汇编源文件


#支持的编程语言配置
enable_language(ASM)

#设置汇编源文件的编译器,我这边配置成和C语言的编译器一样
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})

#把汇编源码和C源码一起放置在待编译的源文件列表里面就行
set(SRC_KERNEL_LIB
    ${CMAKE_SOURCE_DIR}/target/imx6_gcc/target_support.S
    ${CMAKE_SOURCE_DIR}/arch/arm_gcc/mpcore/chip_support.S
    ${CMAKE_SOURCE_DIR}/arch/arm_gcc/common/core_support.S
    ${CMAKE_SOURCE_DIR}/drivers/mmu_table.c
)
命令标签格式 说明
enable_language(<lang> [OPTIONAL] ) 启用某种编程语言

7 如何配置cmake的交叉编译环境


#配置交叉编译变量
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_CROSSCOMPILING TRUE)
#不使用动态链接 -rdyamic
set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") 

set(CMAKE_C_COMPILER "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-gcc")
set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
set(CMAKE_AR "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-ar")
set(CMAKE_RANLIB "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-ranlib")
set(CUSTOM_CMD_OBJCOPY "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-objcopy")
set(CUSTOM_CMD_NM "$ENV{CROSS_COMPILE_ROOT_PATH}/bin/$ENV{CROSS_COMPILE_PREFIX}-nm") 

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
set(CMAKE_FIND_ROOT_PATH $ENV{CROSS_COMPILE_ROOT_PATH})

8 如何自定义编译器/链接器/归档器


CMakeCInformation.cmake里面的编译器/链接器/归档器的变量定义如下

#编译C源文件
if(NOT CMAKE_C_COMPILE_OBJECT)
  set(CMAKE_C_COMPILE_OBJECT
    "<CMAKE_C_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT>   -c <SOURCE>")
endif()

#链接C源文件
if(NOT CMAKE_C_LINK_EXECUTABLE)
  set(CMAKE_C_LINK_EXECUTABLE
    "<CMAKE_C_COMPILER> <FLAGS> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS>  -o <TARGET> <LINK_LIBRARIES>")
endif()

#静态库的链接过程
if(NOT DEFINED CMAKE_C_ARCHIVE_CREATE)
  set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qc <TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
if(NOT DEFINED CMAKE_C_ARCHIVE_APPEND)
  set(CMAKE_C_ARCHIVE_APPEND "<CMAKE_AR> q  <TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
if(NOT DEFINED CMAKE_C_ARCHIVE_FINISH)
  set(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> <TARGET>")
endif()
#自定义的ar生成规则(cmake默认的ar打包参数和我项目源码定义参数不一样,所以自己重新定义)
set(CMAKE_STATIC_LINKER_FLAGS "-rcs")
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> <LINK_FLAGS> <TARGET> <OBJECTS>")

9 后记


参考文献

[1] https://cmake.org/cmake/help/v3.10/#

上一篇 下一篇

猜你喜欢

热点阅读