CMake使用手册

2021-11-24  本文已影响0人  猫咪不吃鱼

简介

一、安装方式

CMake 安装路径参见 官网,安装方式分为 源码编译二进制方式,分别下载对应的系统安装文件安装即可。对于 Ubuntu 系统可直接使用如下命令:

sudo apt install cmake

二、常用变量及引用方法

2.1 常用变量

2.2 cmake中调用环境变量

1. Using $ENV{NAME} : 调用系统环境变量,我们也可以使用 "SET(ENV{NAME} value)".  需要注意的是这里"ENV"没有"$".
2. CMAKE_INCLUDE_CURRENT_DIR 等同于 INCLUDE_DIRECTORY(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})

2.3 其他的内置变量

  1. BUILD_SHARED_LIBS: set the default value when using ADD_LIBRARY()
  2. CMAKE_C_FLAGS: set compiler for c language
  3. CMAKE_CXX_FLAGS: set compiler for c++ language

2.4 区分debug和release

在工程build目录下执行如下命令,再执行make

 cmake .. -DCMAKE_BUILD_TYPE=DEBUG|RELEASE

或者在顶级CMakeList.txt里加入:

set(CMAKE_BUILD_TYPE Debug|Release|MinSizeRel|RelWithDebInfo)

2.5 指定编译32bit或64bit程序

SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32")

2.6 获取所有系统环境变量

# 在CMakeList.txt中使用
execute_process(
  COMMAND ${CMAKE_COMMAND} -E environment
)

# 在终端中使用
cmake -E environment

2.7 获取绝对路径和父目录

# 获取文件绝对路径
get_filename_component(FULL_NAME "${FILE}" ABSOLUTE)

# 获取文件父路径
get_filename_component(PARENT_DIR "${FULL_NAME}" PATH)

2.8 判断操作系统平台及Win是否为32位

if(CMAKE_SYSTEM_NAME MATCHES "Linux")  // 注意区分大写
  message(STATUS "Linux platorm!")
elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
if(CMAKE_CL_64)
  message(STATUS "Windows Win64 platform!")
  else()
    message(STATUS "Windows Win32 platform!")
  endif(CMAKE_CL_64)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
  message(STATUS "FreeBSD platform!")
else()
  message(STATUS "other platform!")
endif(CMAKE_SYSTEM_NAME MATCHES "Linux")

三、使用方法

3.1 基本使用

序号 描述
样例一 单个源文件 main.cpp
样例二 分解成多个文件:main.cpp utils.cpp utils.h
样例三 先生成一个静态库,链接该库
样例四 源文件存在于不同目录
样例五 控制生成的程序和库所在的目录

在项目文件夹下创建 CMakeLists.txt 文件,添加设置

3.1.1 样例一 [单文件]

假设存在 main.cpputils.cpp 之类文件

// 指定 cmake 的最小版本 
cmake_minimum_required(VERSION 3.4.1)
# 设置工程名字和版本
project(Test VERSION 1.0)
# 设置编译类型
add_executable(Test main.cpp) # 生成可执行文件
add_library(common STATIC utils.cpp) # 生成静态库
add_library(common SHARED utils.cpp) # 生成动态库或共享库

这里注意的是在 Linux 下编译的产物分别是 Test、libcommon.a、libcommon.so;在 Windows 下是 Test.exe、common.lib、common.dll;

创建 build 文件夹,进入后执行

3.1.2 样例二 [多文件]
# 指定 cmake 的最小版本 
cmake_minimum_required(VERSION 3.4.1)
# 设置工程名字和版本
project(Test VERSION 1.0)
# 方式一:可直接包含多文件
#add_executable(Test main.cpp utils.cpp) # 生成可执行文件
# 方式二:set设置变量
set(SRC_LIST main.cpp utils.cpp)
# 方式三:使用AUX_SOURCE_DIRECTORY搜索当前目录下的文件
#AUX_SOURCE_DIRECTORY(. SRC_LIST)
add_executable(demo ${SRC_LIST})
#设置头文件搜索路径(和此txt同个路径的头文件无需设置),可选
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
3.1.3 样例三 [链接生成的静态库]
# 指定 cmake 的最小版本 
cmake_minimum_required(VERSION 3.4.1)
# 设置工程名字和版本
project(Test VERSION 1.0)

add_library(libutils utils.cpp)
# 设置编译类型
add_executable(Test main.cpp) # 生成可执行文件
# 添加了一个新的目标 libutils,并将其链接进Test程序
target_link_libraries(Test libutils)
#设置头文件搜索路径(和此txt同个路径的头文件无需设置),可选
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
3.1.4 样例四 [源文件存在不同的目录]

文件目录如下:

+
|
+--- CMakeList.txt
+--+ src/
|  |
|  +--- main.c
|  /--- CMakeList.txt
|
+--+ libhello/
|  |
|  +--- hello.h
|  +--- hello.c
|  /--- CMakeList.txt
|
/--+ build/

cmake_minimum_required(VERSION 3.16)
project(HELLO)
# 添加子目录
add_subdirectory(src)
add_subdirectory(libhello)
# 包含头文件文件
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${PROJECT_SOURCE_DIR}/libhello) 

set(APP_SRC main.cpp 
    ${PROJECT_SOURCE_DIR}/utils.cpp)
add_executable(hello ${PROJECT_SOURCE_DIR}/utils.cpp main.cpp)

target_link_libraries(hello libhello)

set(LIB_SRC hello.cpp)
add_library(libhello ${LIB_SRC})
# 前文中add_executable占据了 hello 所以这里取 libhello会导致生成 libhello.lib 或者 liblibhell.a 所以配置如下属性
# 则生成 hello.lib(或libhello.a)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

则可以在 build 目录中执行编译命令,可以得到:

3.1.5 样例五 [控制生成的程序和库所在的目录]

如果让可执行文件在 bin 目录,库文件在 lib 目录如下结构所示:

   + build/
   |
   +--+ bin/
   |  |
   |  /--- hello
   |
   /--+ lib/
      |
      /--- libhello.a
  1. 方法一:修改顶级的 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.16)
project(HELLO)
# 添加子目录
add_subdirectory(src bin)
add_subdirectory(libhello lib)
  1. 方法二: 修改其他两个文件
    src/CMakeLists.txt 文件:
# 包含头文件
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${PROJECT_SOURCE_DIR}/libhello) 

set(APP_SRC main.cpp 
    ${PROJECT_SOURCE_DIR}/utils.cpp)

add_executable(hello ${PROJECT_SOURCE_DIR}/utils.cpp main.cpp)

# 这里设置 可执行文件 路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

target_link_libraries(hello libhello)

libhello/CMakeLists.txt 文件:

set(LIB_SRC hello.cpp)
add_library(libhello ${LIB_SRC})

# 前文中add_executable占据了 hello 所以这里取 libhello会导致生成 libhello.lib 或者 liblibhell.a 所以配置如下属性
# 则生成 hello.lib(或libhello.a)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

# 设置库文件路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

四、搜索路径

4.1 自定义搜索规则

file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp")
add_library(demo ${SRC_LIST})
# 或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
file(GLOB_RECURSE SRC_LIST "*.cpp") #递归搜索
FILE(GLOB SRC_PROTOCOL RELATIVE "protocol" "*.cpp") # 相对protocol目录下搜索
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

4.2 查找指定的库文件

find_library(VAR name path)查找到指定的预编译库,并将它的路径存储在变量中。
默认的搜索路径为 cmake 包含的系统库,因此如果是 NDK 的公共库只需要指定库的 name 即可。类似的命令还有 find_file()、find_path()、find_program()、find_package()。

find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

4.3 设置链接库搜索目录

link_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/libs
)

4.4 设置 target 需要链接的库

target_link_libraries( # 目标库
                       demo
                       # 目标库需要链接的库
                       # log-lib 是上面 find_library 指定的变量名
                       ${log-lib} )

4.5 指定链接动态库或静态库

target_link_libraries(demo libface.a) # 链接libface.a
target_link_libraries(demo libface.so) # 链接libface.so

4.6 set 追加设置变量的值

set(SRC_LIST main.cpp)
set(SRC_LIST ${SRC_LIST} test.cpp)
add_executable(demo ${SRC_LIST})

4.7 list 追加或者删除变量的值

set(SRC_LIST main.cpp)
list(APPEND SRC_LIST test.cpp)
list(REMOVE_ITEM SRC_LIST main.cpp)
add_executable(demo ${SRC_LIST})

五、自定义编译选项

根据配置选择是否编译相关的库,例如MathFunctions 库设为一个可选的库,如果该选项为 ON ,就使用该库定义的数学函数来进行运算,否则就调用标准库中的数学函数库

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo)
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
    "${PROJECT_SOURCE_DIR}/config.h.in"
    "${PROJECT_BINARY_DIR}/config.h"
    )
# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
        "Use provided math implementation" ON)
# 是否加入 MathFunctions 库
if (USE_MYMATH)
    include_directories ("${PROJECT_SOURCE_DIR}/math")
    add_subdirectory (math)
    set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})

修改 main.cc 文件,让其根据 USE_MYMATH 的预定义值来决定是否调用标准库还是MathFunctions 库:

#include "config.h"
#ifdef USE_MYMATH
    #include "math/MathFunctions.h"
#else
    #include <math.h>
#endif
 
int main(int argc, char *argv[])
{
    if (argc < 3){
        printf("Usage: %s base exponent \n", argv[0]);
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);
 
#ifdef USE_MYMATH
    printf("Now we use our own Math library. \n");
    double result = power(base, exponent);
#else
    printf("Now we use the standard library. \n");
    double result = pow(base, exponent);
#endif
    printf("%g ^ %d is %g\n", base, exponent, result);
    return 0;
}

编写 config.h.in 文件
注意 main.cc 的第一行,这里引用了一个 config.h 文件,这个文件预定义了 USE_MYMATH 的值。但我们并不直接编写这个文件,为了方便从 CMakeLists.txt 中导入配置,我们编写一个 config.h.in 文件,内容如下:

#cmakedefine USE_MYMATH

这样 cmake 会自动根据 CMakeLists.txt 配置文件中的设置自动生成 config.h 文件。

六、交叉编译成Android使用的库

CMakeLists.txt 中内容不变,只需要编写 confirgure.sh 脚本:

#!/bin/bash
rm -rf build
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=/home/zsk/Android/Sdk/ndk/20.0.5594570/build/cmake/android.toolchain.cmake \
        -DANDROID_NDK=/home/zsk/Android/Sdk/ndk/20.0.5594570 \
        -DANDROID_ABI=armeabi-v7a \
        -DANDROID_TOOLCHAIN=clang++ \
        -DANDROID_PLATFORM=android-21 \
        -DCMAKE_BUILD_TYPE=debug \
        ..

make
DCMAKE_TOOLCHAIN_FILE:指定ndk交叉编译链工具的路径,在ndk16版本以上,ndk自带cmake交叉编译工具链。

DANDROID_NDK:NDK的安装跟目录

DANDROID_ABI:各大平台:如armeabi-v7a、x86、mips等。android下基本编译一个armeabi-v7a就可以了,当然也可以加一个x86,方便在虚拟机上调试。

DANDROID_TOOLCHAIN:表示交叉编译链类型,取值gcc或者clang,clang++;gcc已经被废弃

DANDROID_PLATFORM:定义最低api版本

DCMAKE_BUILD_TYPE:定义构建类型,取值Debug或Release,Release

AS平台方式:

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {
        // 指定一些编译选项
        cppFlags "-std=c++11 -frtti -fexceptions"
        ...
 
        // 也可以使用下面这种语法向变量传递参数:
        // arguments "-D变量名=参数".
        arguments "-DANDROID_ARM_NEON=TRUE",
        // 使用下面这种语法向变量传递多个参数(参数之间使用空格隔开):
        // arguments "-D变量名=参数1 参数2"
                  "-DANDROID_CPP_FEATURES=rtti exceptions"
 
        // 指定ABI
        abiFilters "armeabi-v7a" , "arm64-v8a"
      }
    }
  }
  buildTypes {...}
  
  externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
  }
}
  • defaultConfig 外面的 externalNativeBuild 里面的 cmake 指明了 CMakeList.txt 的路径(在本项目下, 和是 build.gradle 在同一个目录里面)。
  • defaultConfig 里面的 externalNativeBuild 里面的 cmake 主要填写的是CMake的命令参数。即由 arguments 中的参数最后转化成一个可执行的 CMake 的命令,可以在如下路径查看:

app/.cxx/cmake/debug/{架构}/CMakeFiles/build_command.txt

参考

[ 1 ] : https://blog.csdn.net/afei__/article/details/81201039#t4
[ 2 ] : https://www.jianshu.com/p/7477a8b3923f
[ 3 ] : https://blog.csdn.net/afei__/article/details/81201039#t21
[ 4 ] : https://www.jianshu.com/p/cb4f8136a265
[ 5 ] : https://www.jianshu.com/p/ce15d2f197a7
[ 6 ] : https://www.jianshu.com/p/f3da16a89f39
[ 7 ] : https://developer.android.google.cn/ndk/guides/cmake

上一篇 下一篇

猜你喜欢

热点阅读