6

跨平台编译工具CMake之语法详解与演练

 1 year ago
source link: https://www.51cto.com/article/746220.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

跨平台编译工具CMake之语法详解与演练

作者:Android开发编程 2023-02-10 14:54:20
Cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。
f6a459461eed0ec592f9430410ba72ab736614.png

一、Cmake 简介

cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。

二、常用命令

1、指定 cmake 的最小版本

cmake_minimum_required(VERSION 3.4.1)

这行命令是可选的,我们可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。

2、设置项目名称

project(demo)

这个命令不是强制性的,但最好都加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。

3、设置编译类型

  • add_executable(demo demo.cpp) # 生成可执行文件
  • add_library(common STATIC util.cpp) # 生成静态库
  • add_library(common SHARED util.cpp) # 生成动态库或共享库
  • add_library 默认生成是静态库,通过以上命令生成文件名字

在 Linux 下是:

demo
libcommon.a
libcommon.so

在 Windows 下是:

demo.exe
common.lib
common.dll

4、指定编译包含的源文件

(1)明确指定包含哪些源文件

add_library(demo demo.cpp test.cpp util.cpp)

(2)搜索所有的 cpp 文件

aux_source_directory(dir VAR) 发现一个目录下所有的源代码文件并将列表存储在一个变量中;
aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件;
add_library(demo ${SRC_LIST});

(3)自定义搜索规则

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})
# 或者
aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})

5、查找指定的库文件

find_library(VAR name path)查找到指定的预编译库,并将它的路径存储在变量中;

默认的搜索路径为 cmake 包含的系统库,因此如果是 NDK 的公共库只需要指定库的 name 即可;

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 )
类似的命令还有 find_file()、find_path()、find_program()、find_package()

6、设置包含的目录

include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_BINARY_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

Linux 下还可以通过如下方式设置包含的目录:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")

7、设置链接库搜索目录

link_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/libs
)

Linux 下还可以通过如下方式设置包含的目录:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")

8、设置 target 需要链接的库

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

在 Windows 下,系统会根据链接库目录,搜索xxx.lib 文件,Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)

(1)指定链接动态库或静态库

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

(2)指定全路径

target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)

(3)指定链接多个库

target_link_libraries(demo
    ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a
    boost_system.a
    boost_thread
    pthread)

9、设置变量

(1)set 直接设置变量的值

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

(2)set 追加设置变量的值

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

(3)list 追加或者删除变量的值

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

10、条件控制

(1) if…elseif…else…endif

逻辑判断和比较:

if (expression):expression 不为空(0,N,NO,OFF,FALSE,NOTFOUND)时为真
if (not exp):与上面相反
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 dir):当 dir 是目录时为真
if (DEFINED var):如果变量被定义为真
if (var MATCHES regex):给定的变量或者字符串能够匹配正则表达式 regex 时为真,此处 var 可以用 var 名,也可以用 ${var}
if (string MATCHES regex)

数字比较:

if (variable LESS number):LESS 小于
if (string LESS number)
if (variable GREATER number):GREATER 大于
if (string GREATER number)
if (variable EQUAL number):EQUAL 等于
if (string EQUAL number)

字母表顺序比较:

if (variable STRLESS string)
if (string STRLESS string)
if (variable STRGREATER string)
if (string STRGREATER string)
if (variable STREQUAL string)
if (string STREQUAL string)
if(MSVC)
    set(LINK_LIBS common)
else()
    set(boost_thread boost_log.a boost_system.a)
endif()
target_link_libraries(demo ${LINK_LIBS})
# 或者
if(UNIX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -g")
else()
    add_definitions(-D_SCL_SECURE_NO_WARNINGS
    D_CRT_SECURE_NO_WARNINGS
    -D_WIN32_WINNT=0x601
    -D_WINSOCK_DEPRECATED_NO_WARNINGS)
endif()
if(${CMAKE_BUILD_TYPE} MATCHES "debug")
    ...
else()
    ...
endif()

(2)while…endwhile

while(condition)
    ...
endwhile()

(3) foreach…endforeach

foreach(loop_var RANGE start stop [step])
    ...
endforeach(loop_var)

start 表示起始数,stop 表示终止数,step 表示步长,示例:

foreach(i RANGE 1 9 2)
    message(${i})
endforeach(i)
# 输出:13579

11、打印信息

message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败

12、包含其它 cmake 文件

include(./common.cmake) # 指定包含文件的全路径
include(def) # 在搜索路径中搜索def.cmake文件
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径

三、常用变量

1、预定义变量

  • PROJECT_SOURCE_DIR:工程的根目录。
  • PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build。
  • PROJECT_NAME:返回通过 project 命令定义的项目名称。
  • CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径。
  • CMAKE_CURRENT_BINARY_DIR:target 编译目录。
  • CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径。
  • CMAKE_CURRENT_LIST_LINE:当前所在的行。
  • CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块。
  • EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置。
  • LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置。

2、环境变量

使用环境变量:

$ENV{Name}

写入环境变量:

set(ENV{Name} value) # 这里没有“$”符号

3、系统信息

  • ­CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3。
  • ­CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4。
  • ­CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1。
  • ­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。

4、主要开关选项

BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库。

CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加。

CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加。

add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔。

四、项目示例

1、初试 cmake 的 helloworld

现在新建一个 hello.cpp 源码文件,代码如下:

#include <stdio.h>
int main(int argc, char* argv[]){
  printf("Hello CMake!\n");
}

之前都是采用 gcc hello.cpp -o hello 命令来生成可执行文件,但现在我们用 CMake 这种方式来生成,新建一个 CMakeLists.txt 文件名大小写都按照这个来:

# 指定工程名
PROJECT (HELLO)
# 现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可
# 将 hello.cpp 赋值给 SRC_LIST 变量,也可以指定多个源文件,用空格隔开
# SET(SRC_LIST hello.cpp add.cpp sub.cpp)
SET(SRC_LIST hello.cpp)
# 输出打印构建目录
MESSAGE(STATUS "This is HELLO_BINARY_DIR " ${HELLO_BINARY_DIR})
# 输出打印资源目录
MESSAGE(STATUS "This is HELLO_SOURCE_DIR " ${HELLO_SOURCE_DIR})
# 输出打印资源目录,与HELLO_SOURCE_DIR 一样 
MESSAGE(STATUS "This is PROJECT_SOURCE_DIR " ${PROJECT_SOURCE_DIR})
# 输出打印 CMake 资源目录,与 PROJECT_SOURCE_DIR 一样 
MESSAGE(STATUS "This is CMAKE_SOURCE_DIR " ${CMAKE_SOURCE_DIR})
# 生成可执行文件 hello ,${SRC_LIST}是引用变量,也就是源文件 hello.cpp
ADD_EXECUTABLE(hello ${SRC_LIST})

新建 build 目录,cd 到 build 目录下,敲 cmake .. 命令,ls 一下会发现 CMake 帮我们生成了 Makefile 等等一些文件。敲 make 命令生成 hello 可执行文件,ls 文件列表如下:

ubuntu@VM-0-9-ubuntu:~/NDK_Day88/t1/build$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  hello  Makefile

2、构建生成 .so 动态库

上面的例子看不出有啥优势,甚至说还不如用 gcc hello.cpp -o hello 来得快,但像 FFmpeg 、OpenCV 等等,类似这样复杂的项目,我们敲命令去构建项目是很麻烦的。下面我们来讲一个稍微复杂一点的例子:

mkdir 新建 3 个目录分别为 src、libs、include 。src 用来存放源文件 add.ccp、sub.cpp、div.cpp。include 用来存放头文件 add.h、div.h、sub.h 。源码如下:

#include "add.h"
int add(int num1, int num2){
        return num1 + num2;
}
#include "sub.h"                         
int sub(int num1, int num2){         
        return num1 - num2;         
}
#include "div.h"                                              
int div(int num1, int num2){                    
        return num1 / num2;                  
}

基于这些准备工作,我们想用 CMake 来构建一个 libmath.so 动态库,并且将其生成在 libs 目录文件夹下

# 指定 cmake 最低编译版本
CMAKE_MINIMUM_REQUIRED(VERSION 3.14)
PROJECT (MATH)
# 把当前工程目录下的 src 目录的下的所有 .cpp 和 .c 文件赋值给 SRC_LIST
# AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR}/src SRC_LIST)
FILE(GLOB SRC_LIST "${PROJECT_SOURCE_DIR}/src/*.cpp")
# 打印 SRC_LIST 文件列表
# MESSAGE(STATUS ${SRC_LIST})
# 指定头文件目录
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
# 指定输出 .so 动态库的目录位置
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 指定生成动态库
ADD_LIBRARY(math SHARED ${SRC_LIST})
# 指定生成版本号,VERSION指代动态库版本,SOVERSION指代API版本
# SET_TARGET_PROPERTIES(math PROPERTIES VERSION 1.2 SOVERSION 1)

3、链接外部动态库和头文件

将 libs 目录和 include 目录 copy 到 hello.cpp 同级目录下,修改 hello.cpp 源码如下:

#include <stdio.h>
#include "add.h"
#include "sub.h"
#include "div.h"
int main(int argc, char* argv[]){
        int a = 20;
        int b = 10;
        printf("%d+%d=%d\n",a,b,add(a,b));
        printf("%d-%d=%d\n",a,b,sub(a,b));
        printf("%d/%d=%d\n",a,b,div(a,b));
        return 0;
}

现在我引用了 include 目录下的头文件,同时需要链接 libs 目录下的 libmath.so ,我们再次创建一个 CMakeLists.txt 来生成可执行文件 hello。

# 指定cmake最低编译版本
CMAKE_MINIMUM_REQUIRED(VERSION 3.14)
# 指定工程的名称
PROJECT(HELLO)
#指定头文件目录位置
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
#添加共享库搜索路径
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib)
#生成可执行文件
ADD_EXECUTABLE(hello hello.cpp)
#为hello添加共享库链接
TARGET_LINK_LIBRARIES(hello math)

4、基于 FFmpeg 开发的 CMakeLists.txt

音视频的播放,在线直播,音视频通话开发,后面可能都得基于 FFmpeg 来写。那么首先我们需要编译 .so 动态库,然后把动态库和头文件 copy 到 AS 来开发,这里我已经编译好了一个 3.3.9 的版本,至于怎么写 shell 编译脚本,会在下篇文章中介绍。目前大伙先直接拿过来用就行了。我把编译好的 .so 动态库和 include 目录拷贝到 AS 工程的 jniLibs 目录下

图片
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# 需要引入我们头文件,以这个配置的目录为基准
include_directories(src/main/jniLibs/include)
include_directories(src/main/jniLibs/other)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#添加共享库搜索路径
LINK_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi)
# 指定源文件目录
AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src/main/cpp SRC_LIST)
add_library( 
        # Sets the name of the library.
        native-lib
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        # src/main/cpp/native-lib.cpp
        ${SRC_LIST}
        )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
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)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( 
        # Specifies the target library.
        # 链接额外的 ffmpeg 的编译
        native-lib
        # 编解码(最重要的库)
        avcodec-57
        # 设备信息
        avdevice-57
        # 滤镜特效处理库
        avfilter-6
        # 封装格式处理库
        avformat-57
        # 工具库(大部分库都需要这个库的支持)
        avutil-55
        # 后期处理
        postproc-54
        # 音频采样数据格式转换库
        swresample-2
        # 视频像素数据格式转换
        swscale-4
        -landroid
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})
图片
责任编辑:姜华 来源: Android开发编程

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK