cmake基础及交叉编译

1. 概念

  • cmake 即编程
  • cmake主要特点:
    • 开放源代码,使用类 BSD 许可发布
    • 跨平台,并可生成 native 编译配置文件,在 Linux/Unix 平台,生成 makefile,在苹果平台,可以生成 xcode,在 Windows 平台,可以生成 MSVC 的工程文件
    • 能够管理大型项目
    • 简化编译构建过程和编译过程;Cmake 的工具链非常简单:cmake+make
    • 高效虑,按照 KDE 官方说法,CMake 构建 KDE4 的 kdelibs 要比使用 autotools 来构建 KDE3.5.6 的 kdelibs 快 40%,主要是因为 Cmake 在工具链中没有 libtool
    • 可扩展,可以为 cmake 编写特定功能的模块,扩充 cmake 功能
    • 支持out-of-source build:一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。
  • cmake基本结构
    • 依赖CMakeLists.txt(文件名区分大小写)文件,项目主目录只有一个,主目录中可指定包含的子目录
    • 在项目CMakeLists.txt中使用project指定项目名称,add_subdirectory或subdirs添加子目录
    • 子目录CMakeLists.txt将从父目录CMakeLists.txt继承设置( s e t )
  • 参考

2.基本语法规则

  • 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名
  • 指令(参数 1 参数 2…), 参数使用括弧括起,参数之间使用空格或分号分开
  • 指令是大小写无关的,参数和变量是大小写相关的。推荐全部使用大写指令。
$ make VERBOSE=1
  • 清理工程
$ make clean
  • 外部编译 (..为上级目标,可以为任意路径,此路径中有根CMakeLists.txt)
$ mkdir build; cd build; cmake ..

3. cmake 常用变量和常用环境变量

  • cmake变量:在CMakeLists.txt中通过set定义的变量
  • cmake环境变量:在shell中定义的环境变量
  • cmake 变量引用的方式:使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值
  • cmake 自定义变量的方式:通过set命令显示定义,或由cmake系统隐式定义
  • cmake 常用变量
    • PROJECT_BINARY_DIR / CMAKE_BINARY_DIR :二者内容一致
    • PROJECT_SOURCE_DIR / CMAKE_SOURCE_DIR :二者内容一致,都是顶层CMakeLists.txt所在的目录
    • CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
    • CMAKE_CURRRENT_BINARY_DIR:如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source编译,他指的是 target 编译目录。
    • CMAKE_CURRENT_LIST_FILE: 输出调用这个变量的 CMakeLists.txt 的完整路径
    • CMAKE_CURRENT_LIST_LINE:输出这个变量所在的行
    • CMAKE_MODULE_PATH:这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下
    • EXECUTABLE_OUTPUT_PATH :重新定义最终执行程序的存放目录
    • LIBRARY_OUTPUT_PATH:重新定义最终动态库/静态库的存放目录
    • PROJECT_NAME:返回通过 PROJECT 指令定义的项目名称
  • cmake系统信息
    • CMAKE_MAJOR_VERSION:MAKE 主版本号,比如 2.4.6 中的 2
    • CMAKE_MINOR_VERSION:CMAKE 次版本号,比如 2.4.6 中的 4
    • CMAKE_PATCH_VERSION:CMAKE 补丁等级,比如 2.4.6 中的 6
    • CMAKE_SYSTEM,系统名称:比如 Linux-2.6.22
    • CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux
    • CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22
    • CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686
    • UNIX:在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
    • WIN32:在所有的 win32 平台为 TRUE,包括 cygwin
  • cmake开关选项
    • BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,默认编译生成的库都是静态库。如果 SET(BUILD_SHARED_LIBS ON)后,默认生成的为动态库。
    • CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加
    • CMAKE_CXX_FLAGS:设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加

4. cmake指令

4.1 基本指令

  • ADD_DEFINITIONS
    • 向 C/C++编译器添加-D 定义
    • 例子
ADD_DEFINITIONS(-DENABLE_DEBUG -DABC) #参数之间用空格分割
  • ADD_DEPENDENCIES
    • ADD_DEPENDENCIES(target-name depend-target1 depend-target2 …)
    • 定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建
  • ADD_EXECUTABLE
    • ADD_EXECUTABLE(target src1 src2 … srcN)
    • 生成可执行程序
  • AUX_SOURCE_DIRECTORY
    • AUX_SOURCE_DIRECTORY(dir VARIABLE)
    • 作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源代码文件列表;因为目前 cmake 还不能自动发现新添加的源文件
    • 例子
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
  • CMAKE_MINIMUM_REQUIRED
    • CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
    • 如果 cmake 版本小与 versionNumber ,则出现严重错误,整个过程中止
  • INCLUDE
    • 用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块
    • INCLUDE(file1 [OPTIONAL])
    • INCLUDE(module [OPTIONAL])
    • OPTIONAL 参数的作用是文件不存在也不会产生错误
  • FIND
    • FIND_FILE( VAR name1 path1 path2 …) :VAR 变量代表找到的文件全路径,包含文件名
    • FIND_LIBRARY(VAR name1 path1 path2 …):VAR 变量表示找到的库全路径,包含库文件名
    • FIND_PATH(VAR name1 path1 path2 …):VAR 变量代表包含这个路径名的路径
    • FIND_PROGRAM(VAR name1 path1 path2 …):VAR 变量代表包含这个程序的全路径
    • FIND_PACKAGE(name [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets…]]) :用来调用预定义在 CMAKE_MODULE_PATH 下的 Find name.cmake 模块,你也可以自己
      定义 Find name模块,通过 SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录中供工程使用
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “libX not found”)
ENDIF(NOT libX)
  • PROJECT
    • PROJECT(projectname [CXX] [C] [Java])
    • 此指令用于定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言
    • 此指令隐式的定义了两个 cmake 变量: ${projectname}_BINARY_DIR 以及${projectname}_SOURCE_DIR
    • 同时 cmake 系统也帮助我们预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR
      变量,他们的值分别跟 ${projectname}_BINARY_DIR 与 ${projectname}_SOURCE_DIR 一致。
  • SET
    • SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
    • SET 指令可以用来显式的定义变量
    • 例子:SET(SRC_LIST main.c alg.c show.c)
  • MESSAGE
    • MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] “message to display” …)
    • 用于向终端输出用户定义的信息,包含了三种类型:
      • SEND_ERROR:产生错误,生成过程被跳过
      • SATUS:输出前缀为–的信息
      • FATAL_ERROR:立即终止所有 cmake 过程
    • 例子:message(STATUS “### Source Dir:” ${PROJECT_SOURCE_DIR})
    • message(STATUS “### Source Dir: ${PROJECT_SOURCE_DIR}”),二者效果一样
  • ADD_SUBDIRECTORY
    • ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
    • 用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录(这个目录跟原有的 src 目录对应),指定 bin 目录后,相当于在编译时将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录
    • EXCLUDE_FROM_ALL:参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建
  • SUBDIRS
    • SUBDIRS(dir1 dir2…)
    • 此指令已经不推荐使用。它可以一次添加多个子目录,并且,即使外部编译,子目录体系仍然会被保存。
  • 批定目标二进制的位置
    • 不论是 SUBDIRS 还是 ADD_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
    • 不可继承,不同的CMakeLists.txt需要自己定义, 在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,
      如果需要改变目标存放路径,就在哪里加入此定义。
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
  • 指定安装路径
$ cmake -DCMAKE_INSTALL_PREFIX=/usr ..

4.2 IF指令

4.2.1 基本语法

  • 基本语法
IF(expression)
     # THEN section.
     COMMAND1(ARGS ...)
     COMMAND2(ARGS ...)
     ...
ELSE(expression)
     # ELSE section.
     COMMAND1(ARGS ...)
     COMMAND2(ARGS ...)
     ...
ENDIF(expression)
  • IF(var):如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或var_NOTFOUND 时,表达式为真
  • IF(NOT var ):与上述条件相反
  • IF(var1 AND var2):当两个变量都为真是为真
  • IF(var1 OR var2):当两个变量其中一个为真时为真
  • IF(COMMAND cmd):当给定的 cmd 确实是命令并可以调用则为真
  • IF(EXISTS dir)或者 IF(EXISTS file):当目录名或者文件名存在时为真
  • IF(file1 IS_NEWER_THAN file2):当 file1 比 file2 新,或者 file1/file2 其中有一个不存在时为真,文件名请使用完整路径
  • IF(IS_DIRECTORY dirname):当 dirname 是目录时,为真
  • IF(variable MATCHES regex)或IF(string MATCHES regex):当给定的变量或者字符串能够匹配正则表达式 regex 时为真。比如:
IF("hello" MATCHES "ell")
    MESSAGE("true")
ENDIF("hello" MATCHES "ell")

4.2.2 数字比较表达式

  • IF(variable LESS number)
  • IF(string LESS number)
  • IF(variable GREATER number)
  • IF(string GREATER number)
  • IF(variable EQUAL number)
  • IF(string EQUAL number)

4.2.3 按照字母序的排列进行比较

  • IF(variable STRLESS string)
  • IF(string STRLESS string)
  • IF(variable STRGREATER string)
  • IF(string STRGREATER string)
  • IF(variable STREQUAL string)
  • IF(string STREQUAL string)

4.2.4 变量定义

  • IF(DEFINED variable):如果变量被定义,为真
IF(WIN32)
    MESSAGE(STATUS “This is windows.”)
    #作一些 Windows 相关的操作
ELSE(WIN32)
    MESSAGE(STATUS “This is not windows”)
    #作一些非 Windows 相关的操作
ENDIF(WIN32)
  • 与下面的代码是等价的
#来控制 IF ELSE 语句的书写方式,使其更加简明、易读
SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
IF(WIN32)
    MESSAGE(STATUS “This is windows.”)
    #作一些 Windows 相关的操作
ELSE()
    MESSAGE(STATUS “This is not windows”)
    #作一些非 Windows 相关的操作
ENDIF()
IF(WIN32)
    #do something related to WIN32
ELSEIF(UNIX)
    #do something related to UNIX
ELSEIF(APPLE)
    #do something related to APPLE
ENDIF(WIN32)

4.3 WHILE 指令

  • 基本语法
 WHILE(condition)
     COMMAND1(ARGS ...)
     COMMAND2(ARGS ...)
     ...
 ENDWHILE(condition)
  • 其真假判断条件可以参考 IF 指令

4.4 FOREACH 指令

  • 列表
FOREACH(loop_var arg1 arg2 ...)
 COMMAND1(ARGS ...)
 COMMAND2(ARGS ...)
 ...
ENDFOREACH(loop_var)
  • 列表例子
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
    MESSAGE(${F})
ENDFOREACH(F) 
  • 范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
  • 从 0 到 total 以1为步进
  • 全例子
FOREACH(VAR RANGE 10)
    MESSAGE(${VAR})
ENDFOREACH(VAR)
# 输出0, 1, ..., 10
  • 范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
# 输出 5, 8, 11, 14

4.5 构建动态库和静态库命令

  • SET_TARGET_PROPERTIES
    • SET_TARGET_PROPERTIES(target1 target2 … PROPERTIES prop1 value1 prop2 value2 …)
    • 可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
  • ADD_LIBRARY
    • ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] src1 src2 … srcN)
    • MODULE:在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待
    • EXCLUDE_FROM_ALL:意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
    • 例子
# cmake 在构建一个新的 target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.a 时,
# 就会清理掉 libhello.so,下面的属性设置解决此问题
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

# 把libhello_static.a 修改为libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

SET(LIBHELLO_SRC hello.c alg.c show.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC}) # libname不能重复,所以不能为hello
# 可以同时得到 libhello.so/libhello.a 两个库

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
# VERSION 指代动态库版本,SOVERSION 指代 API 版本

4.6 使用外部共享库和头文件

  • INCLUDE_DIRECTORIES (指定头文件搜索路径)
    • INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
    • 这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:
      • CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 SET 这个 cmake 变量为 on,可以将添加的头文件搜索路径放在已有路径的前面
      • 通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前
  • LINK_DIRECTORIES (指定共享库搜索路径)
    • LINK_DIRECTORIES(directory1 directory2 …)
    • 这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径
  • TARGET_LINK_LIBRARIES (指定链接库)
    • TARGET_LINK_LIBRARIES(target library1 {debug | optimized} library2 …)
    • 为 target 添加需要链接的共享库
    • 例子
# 链接动态链接库
TARGET_LINK_LIBRARIES(main hello)
#或
TARGET_LINK_LIBRARIES(main libhello.so)

# 链接静态链接库
TARGET_LINK_LIBRARIES(main libhello.a)

5. Find < NAME >模块

  • cmake的Find < NAME >模块 位于目标:/usr/share/cmake-2.8/Modules,例子如下:
-rw-r--r-- 1 root root 67444  1月 17  2014 FindCUDA.cmake
-rw-r--r-- 1 root root  2979  1月 17  2014 FindCups.cmake
-rw-r--r-- 1 root root  2346  1月 17  2014 FindCURL.cmake
-rw-r--r-- 1 root root  6725  1月 17  2014 FindCurses.cmake
-rw-r--r-- 1 root root  2237  1月 17  2014 FindCVS.cmake
  • FIND_PACKAGE实例
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
     INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
     TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
     MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
  • 对于系统预定义的 Find < name > .cmake 模块,使用方法一般如上所示,每一个模块都会定义以下几个变量
    • < name>_FOUND
    • < name>_INCLUDE_DIR or < name >_INCLUDES
    • < name>_LIBRARY or _LIBRARIES
  • 应用实例
SET(mySources viewer.c)
SET(optionalSources)
SET(optionalLibs)

FIND_PACKAGE(JPEG)
IF(JPEG_FOUND)
     SET(optionalSources ${optionalSources} jpegview.c)
     INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
     SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
     ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)

FIND_PACKAGE(PNG)
IF(PNG_FOUND)
     SET(optionalSources ${optionalSources} pngview.c)
     INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
     SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )
     ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)

ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )
TARGET_LINK_LIBRARIES(viewer ${optionalLibs} 
# 通过判断系统是否提供了 JPEG 库来决定程序是否支持 JPEG 功能。

6. 交叉编译

  • 指定目标编译器
    • cmake不知道你的目标平台是什么、用什么编译器、如何编译等,所以需要提供预设一些变量到cmake,其中最为方便的一个方法就是将相关的变量设置都放进一个文件(cmake脚本)中去,然后将该文件通过CMAKE_TOOLCHIAIN_FILE传递给cmake, 例如:
cmake -DCMAKE_TOOLCHIAIN_FILE="/path/qca-toolchain.cmake" ..

6.1 设置目标系统以及ToolChain

  • CMAKE_SYSTEM_NAME
    • T a r g e t O p e r a t i n g S y s t e m
    • 其值可以设置为目标系统中以下命令的输出
uname -s
 - 只有当CMAKE_SYSTEM_NAME这个变量被设置了,cmake才知道要进行交叉编译,它会自动设置一个变量CMAKE_CROSSCOMPILING为TRUE
 - CMAKE_SYSTEM_NAME即目标机target所在的操作系统名称:
     - ARM或者Linux:设置为”Linux”
     - Android平台:设置为”Android”
     - 嵌入式平台没有相关OS:设置为”Generic”

- CMAKE_SYSTEM_PROCESSOR: 是可选项,但是在移动开发中很重要,代表目标系统的硬件或者CPU型号,例如ARM,X86 etc
- CMAKE_C_COMPILER:即C语言编译器,这里可以将变量设置成完整路径或者文件名
- CMAKE_CXX_COMPILER:C++编译器,这里可以将变量设置成完整路径或者文件名
- 实例

### qca-toolchain.cmake ###
# this is required
SET(CMAKE_SYSTEM_NAME Linux)

# specify the cross compiler
SET(CMAKE_C_COMPILER   /home/test/demo/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-gcc)
SET(CMAKE_CXX_COMPILER /home/test/demo/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2/bin/mips-openwrt-linux-g++)

# where is the target environment
SET(CMAKE_FIND_ROOT_PATH  /home/test/demo/staging_dir/toolchain-mips_r2_gcc-4.6-linaro_uClibc-0.9.33.2)

# search for programs in the build host directories (not necessary)
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

# for libraries and headers in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

  • CMAKE_FIND_ROOT_PATH: 代表了一系列的相关文件夹路径的根路径的变更,它只是设置变更路径,具体应用见下面的变量:
    • CMAKE_FIND_ROOT_PATH_MODE_PROGRAM: 对FIND_PROGRAM()起作用,有三种取值,NEVER,ONLY,BOTH:
      • NEVER: 表示不在 CMAKE_FIND_ROOT_PATH设置的路径下查找
      • ONLY:表示只在这个路径下查找
      • BOTH:表示先查找这个路径,再查找全局路径
      • 对于这个变量来说,一般都是调用宿主机的程序,所以一般都设置成 N E V E R
    • CMAKE_FIND_ROOT_PATH_MODE_LIBRARY: 对FIND_LIBRARY()起作用,表示在链接库的相关选项,因此这里需要设置成 O N L Y 来保证我们的库是在交叉环境中找到的
    • CMAKE_FIND_ROOT_PATH_MODE_INCLUDE: 对FIND_PATH()和FIND_FILE()起作用,一般来说也是 O N L Y ,如果你想改变,一般也是在相关的FIND命令中增加option来改变局部设置,有:
      • NO_CMAKE_FIND_ROOT_PATH
      • ONLY_CMAKE_FIND_ROOT_PATH
      • BOTH_CMAKE_FIND_ROOT_PATH
  • a
  • a

猜你喜欢

转载自blog.csdn.net/myarrow/article/details/80480255