带你看 CMake

CMake

阅读本文你将收获

  1. 自己写出cmake来进行编译,链接
  2. 在写cmake的时候可能遇到的问题
  3. 在gitlab里面怎么使用cmake
  4. android里面的cmake

说在前面

  1. CMake是个跨平台的构建工具
  2. CMakeLists.txt` 为cmake默认执行的文件
  3. 执行cmake . 会在当前执行cmake的位置产生一堆中间文件,建议放置在生成目录下在执行cmake ..
  4. 可以使用ccmake 可能需要安装, 来查看当前环境的CMake相关变量,并且可以通过回车来修改当前的变量 c来保存
  5. cmake 中间文件 CMakeCache 为当前的环境变量,比方第一次执行cmake -DOPT1=ON .. 下次就可以直接执行cmake .. 来产生跟上面相同的行为,可以通过执行 make rebuild_cache 后重新执行cmake
  6. cmake 指定开关 -DXXX=XX eg:-DOPT1=ON
  7. 命名规则 api小写, 变量名大写

需要的掌握的前提知识:

  1. 编译的四个过程 --推荐《程序员的自我修养》
  2. 了解点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, and CXXFLAGS 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 path x/y added to their include path.

target_include_directories(t x/y) has target scope—it adds x/y to the include path for target t.

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 the PRIVATE, PUBLIC, and INTERFACE 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 会自己读取环境变量的{CC},{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)

九、 参考资料

Cmake 官网

关于官网帮助文档查看: <> 必填项 {} 选择必填项 [] 可选项

Gun make

What-how-makefile

十、 其他

问:请问啥是target?

答:我的理解是,它就是个函数,可以有先决和依赖

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容