CMake
阅读本文你将收获
- 自己写出cmake来进行编译,链接
- 在写cmake的时候可能遇到的问题
- 在gitlab里面怎么使用cmake
- android里面的cmake
说在前面
- CMake是个跨平台的构建工具
- CMakeLists.txt` 为cmake默认执行的文件
- 执行cmake . 会在当前执行cmake的位置产生一堆中间文件,建议放置在生成目录下在执行cmake ..
- 可以使用ccmake 可能需要安装, 来查看当前环境的CMake相关变量,并且可以通过回车来修改当前的变量 c来保存
- cmake 中间文件 CMakeCache 为当前的环境变量,比方第一次执行cmake -DOPT1=ON .. 下次就可以直接执行cmake .. 来产生跟上面相同的行为,可以通过执行
make rebuild_cache
后重新执行cmake - cmake 指定开关 -DXXX=XX eg:-DOPT1=ON
- 命名规则 api小写, 变量名大写
需要的掌握的前提知识:
- 编译的四个过程 --推荐《程序员的自我修养》
- 了解点MakeFile的知识
使用CMake进行构建过程
一、预编译
二、编译
指定编译器
CMAKE_CXX_COMPILER | cxx编译器
CMAKE_C_COMPILER | c编译器
指定编译参数
CPPFLAGS
is supposed to be for flags for the C PreProcessor;CXXFLAGS
is for flags for the C++ compiler.The default rules in make (on my machine, at any rate) pass
CPPFLAGS
to just about everything,CFLAGS
is only passed when compiling and linking C, andCXXFLAGS
is only passed when compiling and linking C++.
CMAKE_<LANG>_FLAGS
eg:
CMAKE_C_FLAGS
# 表示预编译参数
CMAKE_CPP_FLAGS
# 表示编译参数
CMAKE_CXX_FLAGS
note: 请这样设置set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
否则会导致修改系统的CXXFLAGS
# 这是第二种修改的方式,但是这里推荐使用上面的方式来进行设置,
target_compile_options(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
指定头文件路径
include_directories
vs target_include_directories
include_directories(x/y)
affects directory scope. All targets in this CMakeList, as well as those in all subdirectories added after the point of its call, will have the pathx/y
added to their include path.
target_include_directories(t x/y)
has target scope—it addsx/y
to the include path for targett
.You want the former one if all of your targets use the include directories in question. You want the latter one if the path is specific to a target, or if you want finer control of the path's visibility. The latter comes from the fact that
target_include_directories()
supports thePRIVATE
,PUBLIC
, andINTERFACE
qualifiers.
include_directories
之后,会对之后的target的造成影响。有顺序关系。
如果有不同目录相同名称的头文件会产生影响,所以这里建议针对特定的target进行添加头文件的操作,不要使用include_directories
If PRIVATE is specified for a certain option/property, then that option/property will only impact the current target. If PUBLIC is specified, then the option/property impacts both the current target and any others that link to it. If INTERFACE is specified, then the option/property does not impact the current target but will propagate to other targets that link to it.
public: 头文件可以提供给依赖的库和自己使用
interface: 头文件提供给依赖的库使用,但自己不使用,场景:封装一个sdk,供外部使用的头文件和内部使用的头文件,这时候就很有用了
private:头文件自己使用,不提供给外部使用
target_include_directories(<target> [SYSTEM] [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
如何提供给外部依赖的库使用呢?
通过使用:target_link_libraries
来提供。但是这个api也有public、private、interface,这个请看下文。
指定源文件
直接添加到要生成的target上面
<target> 跟Makefile的目标项是一个概念。cmake怎么创建目标项呢?继续往下看
三、汇编
指定汇编器
CMAKE_AR
汇编参数
# 这里面的<LANG> 是语言的意思:C语言就是 C ,C++语言就是CXX
CMAKE_<LANG>_ARCHIVE_APPEND
CMAKE_<LANG>_ARCHIVE_CREATE
CMAKE_<LANG>_ARCHIVE_FINISH
eg:
set(CMAKE_CXX_ARCHIVE_CREATE "x")
set(CMAKE_C_ARCHIVE_CREATE "x")
四、链接
指定连接器
CMAKE_LINKER | 连接器
链接库
If you are creating a shared library and your source cpp files #include the headers of another library (Say, QtNetwork for example), but your header files don't include QtNetwork headers, then QtNetwork is a PRIVATE dependency.
If your source files and your headers include the headers of another library, then it is a PUBLIC dependency.
If your header files but not your source files include the headers of another library, then it is an INTERFACE dependency.
public: 自己的头文件以及源文件都包含了外部库头文件
private: 自己的源文件包含了外部库头文件
interface: 自己的头文件包含了外部库头文件
# 链接库
# 库名 可以不加-l 直接通过库名称来进行链接,默认查找动态库
target_link_libraries(<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
链接参数
# 设置动态链接参数
set(CMAKE_SHARED_LINKER_FLAGS "-z defs")
# 设置静态链接参数
set(CMAKE_STATIC_LINKER_FLAGS "x")
note: 如果需要添加对某些库启用某些开关的话,则直接在target_link_libraries
上面进行添加
指定生成项
#生成可执行文件,默认会添加到构建过程中,可以通过EXCLUDE_FROM_ALL来排除
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
#生成库,可以通过[STATIC | SHARED | MODULE]来选择生成静态库,动态库
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
小结:
理论上,看到这里你已经能够写出属于自己简单可执行的cmake了。
cmake 会自己读取环境变量的{CXX} 等变量,所以你只要按照上面说的api进行制定就会编译出来,你想要的库文件还是执行文件。
是不是很开心,如果你想要了解更多,就需要往下看了。
五、更多
依赖
add_executable()
add_library()
add_custom_target()
通过这里说的三种方式可以创建target
# <target> 表示需要让哪个目标建立依赖
# <target-dependency> 表示被依赖项,
# 意思是:执行<target> 之前执行 <target-dependency> 的目标项
add_dependencies(<target> [<target-dependency>]...)
执行命令
# Name 表示target的名称
# ALL 表示是否添加到构建过程中,
add_custom_target(Name [ALL] [command1 [args1...]]
[COMMAND command2 [args2...] ...]
[DEPENDS depend depend depend ... ]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[SOURCES src1 [src2...]])
# <target> 表示之前定义过的target的名称,注意这里的所有大写都是保留字
# 意思是往之前定义的target中添加命令,可以选择之前,之后
# WORKING_DIRECTORY 表示执行这条语句的工作目录
# COMMENT 表示注释
add_custom_command(TARGET <target>
PRE_BUILD | PRE_LINK | POST_BUILD
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment])
PRE_BUILD : 编译前
PRE_LINK :链接后
POST_BUILD: 全部完成后
add_custom_target(cp_ijk_header
COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/${SRC_PREFIX}/include
COMMAND cp -rp ${CMAKE_CURRENT_SOURCE_DIR}/ijkmedia/ ${CMAKE_CURRENT_BINARY_DIR}/${SRC_PREFIX}/include/
)
add_dependencies(${PROJECT_NAME} cp_ijk_header)
六、 Cmake中重要的变量
参考链接:这里
关键字 | 含义 |
---|---|
CMAKE_CURRENT_SOURCE_DIR | CMakeLists.txt所在的路径 |
CMAKE_CURRENT_BINARY_DIR | 编译的路径 |
PROJECT_NAME | 项目名称 设置了project之后就可以使用该值 |
CMAKE_CXX_FLAGS | 编译参数 |
CMAKE_C_FLAGS | 编译参数 |
CMAKE_STATIC_LINKER_FLAGS | 静态链接参数 |
CMAKE_SHARED_LINKER_FLAGS | 动态链接参数 |
CMAKE_CXX_COMPILER | cxx编译器 |
CMAKE_C_COMPILER | c编译器 |
CMAKE_AR | 汇编器 |
CMAKE_LINKER | 连接器 |
EXECUTABLE_OUTPUT_PATH | 可执行文件生成的路径 |
LIBRARY_OUTPUT_PATH | 库生成的路径 |
BUILD_SHARED_LIBS | 指定add_library() 的默认编译属性,动态库还是静态库【默认】 |
例子
macro(use_cxx11)
if (CMAKE_VERSION VERSION_LESS "3.1")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
endif ()
else ()
set (CMAKE_CXX_STANDARD 11)
endif ()
endmacro(use_cxx11)
七、Cmake 语法
设置变量
set(var ...) # 没有缓存的变量
set(Var ... CACHE) # 没有缓存才设置,相当于MakeFile 的 ?=
set(Var ... FORCE) # 强制修改变量
#eg:set(TTTT tttt) # 使用${TTTT}
打印日志
message(STATUS ...)
# eg: message(STATUS "HELLO WORLD")
设置开关
option(<variable> "<help_text>" [value])
# eg: option(TEST "example tips" ON)
使用变量
if(TEST)
# xxx
elseif(TEST1)
# xxx
else()
# xxx
endif()
逻辑控制
# 条件控制
if(XXX)
else(XXX)
endif(XXX)
# for
foreach(item ${TEST})
//${item}
endforeach(item)
遍历查找
官网:不建议使用这个方法来遍历查找源文件[.cpp],因为当你的源文件增删之后不一定会使cmake重新执行
file({GLOB | GLOB_RECURSE} <out-var> [...] [<globbing-expr>...])
# 获取从${CMAKE_CURRENT_SOURCE_DIR}开始的所有*.cpp 文件
# eg: file(GLOB_RECURSE OUTPUT *.cpp)
字符串
note: 涉及到的正则表达式是阉割版的正则表达式
# 字符串替换
string(REGEX REPLACE <regular_expression>
<replace_expression> <output variable>
<input> [<input>...])
# eg: string(REGEX REPLACE "[^-_a-zA-Z0-9.@$#=!]+" ""
# PROJECT_BRANCH_NAME
# ${PROJECT_BRANCH_NAME}
# )
# 字符串长度
string(LENGTH <string> <out-var>)
#eg string(LENGTH OUTPUT_LEGTH ${INPUT})
# 字符串截取
string(SUBSTRING <string> <begin> <length> <out-var>)
#eg: string(SUBSTRING ${INPUT} 0 ${OUTPUT_LEGTH} OUTPUT)
宏和方法
区别: 需要使用到方法里面的变量的,则需要定义为宏.
什么叫使用到里面的变量呢? 意思就是如果你函数外部要使用到函数里面定义的变量的时候,则需要使用宏。
set(变量 值)
# <name>宏名称
# <arg1> ... 宏参数
macro(<name> [<arg1> ...])
<commands>
endmacro()
# <name> 方法名称
# <arg1> ... 方法参数
function(<name> [<arg1> ...])
<commands>
endfunction()
怎么调用呢?
# 和调用普通的API一样
<name> ([<arg1> ...])
function(createName)
<commands>
endfunction()
# 调用
eg: createName()
八、 其他库
查找包
查找的路径可以自己指定:SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}),默认是在环境变量中进行查找的
# 查找包
# QUIET 表明找到后没有信息输出
# REQUIRED 表示一定需要这个库,找不到就退出
# COMPONENTS 表示这个库关联的组件
# OPTIONAL_COMPONENTS 可选组件
find_package(<package> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
调用find_package(name)
之后,就会定义以下几个变量
<p><name>_FOUND</p>
<p><name>_INCLUDE_DIR or <name>_INCLUDES</p>
<p><name>_LIBRARY or <name>_LIBRARIES</p>
自己建立一个包FindHELLO.cmake
FIND_PATH(HELLO_INCLUDE_DIR hello.c /usr/include/hello /usr/local/inlcude/hello)
FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib /usr/local/lib)
IF(HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
SET(HELLO_FOUND TRUE)
ENDIF(HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
IF(HELLO_FOUND)
IF(NOT HELLO_FIND_QUIETLY)
MESSAGE(STATE "FOUND LIBRARY :${HELLO_LIBRARY}")
ENDIF(NOT HELLO_FIND_QUIETLY)
ELSE(HELLO_FOUND)
IF(HELLO_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "CAN'T FOUND HELLO LIBRARY")
ENDIF(HELLO_FIND_REQUIRED)
ENDIF(HELLO_FOUND)
需要在自己的CMakeLists.txt 中加入该cmake的查找文件路径
set(CMAKE_MODULE_PATH xxx)
九、 参考资料
关于官网帮助文档查看: <> 必填项 {} 选择必填项 [] 可选项
十、 其他
问:请问啥是target?
答:我的理解是,它就是个函数,可以有先决和依赖