cmake for record

0. 变量

变量使用 ${ } 的方式取值,但是在if控制语句中直接使用变量名。

1. project

project ( project_name [CXX] [C] [Java] )

用来指定工程名称和工程语言(可省略),指令隐式定义了projectname_BINARY_DIR和projectname_SOURCE_DIR两个变量(写在cmake_cache里面),指的是编译发生的当前目录。

2. set

set ( VAR [VALUE] )

用来显式定义变量,如set (SRC_LIST main.c t1.c t2.c) 。(竟然不用套括号?)

3. message

message ( [SEND_ERROR | STATUS | FATAL_ERROR] “message to display” VAR )

用来向终端输出用户定义的信息。

4. add_executable

add_executable ( executable_filename [source_filename] )

生成名字为executable_filename的可执行文件,相关的源文件 [source_filename] 可以是一个源文件列表。

5. 清理构建结果

make clean

对构建出的可执行文件进行清理。

6. 外部构建

1
2
3
4
mkdir build
cd build
cmake ..
make

所有编译动作发生在编译目录,对原有工程没有任何影响。

7. add_subdirectory

add_subdirectory ( source_dir [binary_dir] [EXCLUDE_FROM_ALL] )

向当前工程目录添加存放源文件的子目录source_dir,并指定存放中间二进制文件和目标二进制文件的位置binary_dir。指令隐式修改 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 两个变量。

8. 更加像一个工程

  • 创建工程根目录,创建CMakeLists.txt。
1
2
3
4
5
6
7
# 指定最低编译版本
cmake_minimum_required(VERSION 3.7)
# 指定工程名字
PROJECT(HELLO)
# 测试类打印信息
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})
  • 添加子目录src,用来存放源文件,为子目录创建CMakeLists.txt。
1
2
3
# 在根目录CMakeLists.txt中添加子目录声明
add_subdirectory(src bin)
# 编译产生的中间文件以及目标文件将保存在编译文件夹的bin子目录下
1
2
3
4
5
# 编写当前子目录的CMakeLists.txt
add_executable(hello main.c)
# 修改最终生成的可执行文件以及库的路径,这两个指令要追随对应的add_executable()和add_library()指令
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_PATH}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
  • 添加子目录build,作为外部编译文件夹( ${PROJECT_BINARY_DIR} ),存放编译的过程和目标文件。
1
2
3
cd build
cmake ..
make
  • 添加子目录doc,用来存放工程文档hello.txt。
  • 添加文本文件README,COPYRIGHT。
  • 添加runhello.sh脚本,用来调用可执行文件hello。

9. 打包安装

  • 在根目录的CMakeList.txt中添加安装信息
1
2
3
4
5
6
# 安装COPYRIGHT/README到<prefix>/share/doc/cmake/t2
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
# 安装runhello.sh到<prefix>/bin
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
# 安装工程文档到<prefix>/share/doc/cmake/t2
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
  • 在子目录的CMakeList.txt中添加安装信息
1
2
3
4
# 安装脚本要调用的可执行文件hello到<prefix>/bin,
# 注意install(targets)指令也要追随对应add_executable()和add_library()指令的路径
INSTALL(TARGETS hello
RUNTIME DESTINATION bin)
  • 安装程序包
1
2
3
4
5
6
7
8
9
10
cd build
# 在cmake命令中指明安装目录的前缀<prefix>
# CMAKE_INSTALL_PREFIX 默认是/usr/local
cmake -DCMAKE_INSTALL_PREFIX=/Users/carrol/tmp ..
make
make install

# 查看目标文件夹
j tmp
tree -a

10. add_library

add_library ( name [SHARED | STATIC | MODULE] [source_filename] )

生成名字为libname.X的库文件。

  • SHARED,动态库,libname.dylib
  • STATIC,静态库,libname.a

设置目标动态库和静态库同名 set_target_properties

1
2
3
4
5
# 设置目标动静态库同名
add_library(hello SHARED hello.c)
add_library(hello_static hello.c)
set_target_properties(hello_static
PROPERTIES OUTPUT_NAME hello)

防止构建中清理同名文件 set_target_properties

cmake在构建一个target时,会尝试清理掉其他使用这个名字的库——在构建libhello.a时会清理掉libhello.dylib。

我实际操作时候会保留两个库文件,但是在作为第三方被引用的时候会报错:

dyld: Library not loaded: libhello.dylib

Reason: image not found

1
2
3
4
SET_TARGET_PROPERTIES(hello 
PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static
PROPERTIES CLEAN_DIRECT_OUTPUT 1)

设置动态版本号 set_target_properties

1
2
3
4
# 设置动态库版本号
set_target_properties(hello
PROPERTIES VERSION 1.2
SOVERSION 1)

编译文件夹下生成了libhello.1.2.dylib、libhello.1.dylib、libhello.dylib三个动态库文件,只有一个是真的,另外两个是替身。

安装共享库和头文件

修改库的源文件夹下的CMakeLIsts.txt

1
2
3
4
5
6
# 库文件
install(TARGETS hello hello_static
ARCHIVE DESTINATION lib //静态库
LIBRARY DESTINATION lib) //动态库
# 头文件
install(FILES hello.h DESTINATION include/hello)

11. include_directories

include_directories( dir1 dir2 … )

用来向工程添加多个特定的头文件搜索路径

link_directories( dir1 dir2 … )

添加非标准的共享库搜索路径

target_link_libraries( target lib1 lib2 … )

用来为目标target添加需要链接的共享库,target可以是一个可执行文件,也可以是一个库文件。

查看生成目标的库依赖情况

1
2
3
4
5
# 生成的目标可执行文件为main
# for OSX
otool -L main
# for linux
ldd main

只能列出动态库。

13. 常用变量

PROJECT_BINARY_DIR:编译发生的目录

PROJECT_SOURCE_DIR:工程顶层目录

CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt所在目录

CMAKE_MODULE_PATH:自定义的cmake模块所在路径

LIBRARY_OUTPUT_PATH:重定义目标库文件存放目录

EXECUTABLE_OUTPUT_PATH:重定义目标可执行文件存放目录

14. findNAME.cmake模块

  1. 在工程目录中创建cmake文件夹,并创建FindHELLO.cmake模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 示例
FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/local/include/hello)
FIND_LIBRARY(HELLO_LIBRARY hello /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(STATUS "Found Hello: ${HELLO_LIBRARY}")
ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
IF (HELLO_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find hello library")
ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)
  1. 在主目录CMakeLists.txt中添加cmake模块所在路径:
1
2
# 为find_package()指令成功执行
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
  1. 然后就可以在源文件CMakeLists.txt中调用 find_package:

find_package ( name [QUIET] [REQUIRED] )

用来调用预定义在CMAKE_MODULE_PATH下的Find\.cmake模块。

每一个模块都会定义以下几个变量:

  • NAME_FOUND
  • NAME_INCLUDE_DIR or NAME_INCLUDES
  • NAME_LIBRARY or NAME_LIBRARIES

根据指令后面的参数还会有以下变量:

  • NAME_FIND_QUIETLY,如果指定了QUIET参数,就不会执行如下语句:

    1
    MESSAGE(STATUS "Found Hello: ${NAME_LIBRARY}")
  • NAME_FIND_REQUIRED,如果指定了REQUIRED参数,就是指这个共享库是工程必须的,如果找不到,工程就不能编译,对应地会执行如下语句:

    1
    MESSAGE(FATAL_ERROR "Could not find NAME library")

可以通过\_FOUND判断模块是否被找到,并执行不同的操作(如添加非标准路径、输出错误信息等)。

15. find_指令

  • find_path

    find_path ( VAR name1 path1 path2 … )

    VAR变量代表包含name1文件的路径——路径。

  • find_library

    find_library ( VAR name1 path1 path2 …)

    VAR变量包含找到的库的全路径包括库文件名——路径下的所有文件。