cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。
cmake_minimum_required(VERSION 3.4.1)
这行命令是可选的,我们可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。
复制
project(demo)
1.
这个命令不是强制性的,但最好都加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。
add_executable(demo demo.cpp) # 生成可执行文件
add_library(common STATIC util.cpp) # 生成静态库
add_library(common SHARED util.cpp) # 生成动态库或共享库
add_library 默认生成是静态库,通过以上命令生成文件名字
复制
demo libcommon.a libcommon.so
1.
2.
3.
复制
demo.exe common.lib common.dll
1.
2.
3.
复制
add_library(demo demo.cpp test.cpp util.cpp)
1.
复制
aux_source_directory(dir VAR) 发现一个目录下所有的源代码文件并将列表存储在一个变量中;aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件;add_library(demo ${SRC_LIST});
1.
2.
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})
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
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()
1.
2.
3.
4.
5.
6.
复制
include_directories(${CMAKE_CURRENT_SOURCE_DIR}${CMAKE_CURRENT_BINARY_DIR}${CMAKE_CURRENT_SOURCE_DIR}/include)
1.
2.
3.
4.
5.
Linux 下还可以通过如下方式设置包含的目录:
复制
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}")
1.
复制
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs)
1.
2.
3.
Linux 下还可以通过如下方式设置包含的目录:
复制
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/libs")
1.
复制
target_link_libraries( # 目标库 demo # 目标库需要链接的库 # log-lib 是上面 find_library 指定的变量名 ${log-lib} )
1.
2.
3.
4.
5.
在 Windows 下,系统会根据链接库目录,搜索xxx.lib 文件,Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)
复制
target_link_libraries(demo libface.a) # 链接libface.atarget_link_libraries(demo libface.so) # 链接libface.so
1.
2.
复制
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.a)target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.so)
1.
2.
复制
target_link_libraries(demo${CMAKE_CURRENT_SOURCE_DIR}/libs/libface.aboost_system.aboost_threadpthread)
1.
2.
3.
4.
5.
复制
set(SRC_LIST main.cpp test.cpp)add_executable(demo ${SRC_LIST})
1.
2.
复制
set(SRC_LIST main.cpp)set(SRC_LIST ${SRC_LIST} test.cpp)add_executable(demo ${SRC_LIST})
1.
2.
3.
复制
set(SRC_LIST main.cpp) list(APPEND SRC_LIST test.cpp) list(REMOVE_ITEM SRC_LIST main.cpp) add_executable(demo ${SRC_LIST})
1.
2.
3.
4.
逻辑判断和比较:
复制
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)
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
数字比较:
复制
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)
1.
2.
3.
4.
5.
6.
字母表顺序比较:
复制
if (variable STRLESS string)if (string STRLESS string)if (variable STRGREATER string)if (string STRGREATER string)if (variable STREQUAL string)if (string STREQUAL string)
1.
2.
3.
4.
5.
6.
示例:
复制
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_WARNINGSD_CRT_SECURE_NO_WARNINGS-D_WIN32_WINNT=0x601-D_WINSOCK_DEPRECATED_NO_WARNINGS)endif()if(${CMAKE_BUILD_TYPE} MATCHES "debug") ...else() ...endif()
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
复制
while(condition) ...endwhile()
1.
2.
3.
复制
foreach(loop_var RANGE start stop [step]) ...endforeach(loop_var)
1.
2.
3.
start 表示起始数,stop 表示终止数,step 表示步长,示例:
复制
foreach(i RANGE 1 9 2)message(${i})endforeach(i)# 输出:13579
1.
2.
3.
4.
复制
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 会导致编译失败
1.
2.
3.
4.
复制
include(./common.cmake) # 指定包含文件的全路径 include(def) # 在搜索路径中搜索def.cmake文件 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径
1.
2.
3.
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:重新定义目标链接库文件的存放位置。
使用环境变量:
复制
$ENV{Name}
1.
写入环境变量:
复制
set(ENV{Name} value) # 这里没有“$”符号
1.
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。
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) # 参数之间用空格分隔。
现在新建一个 hello.cpp 源码文件,代码如下:
复制
#include <stdio.h>int main(int argc, char* argv[]){ printf("Hello CMake!\n"); }
1.
2.
3.
4.
之前都是采用 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.cppADD_EXECUTABLE(hello ${SRC_LIST})
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
新建 build 目录,cd 到 build 目录下,敲 cmake .. 命令,ls 一下会发现 CMake 帮我们生成了 Makefile 等等一些文件。敲 make 命令生成 hello 可执行文件,ls 文件列表如下:
复制
ubuntu@VM-0-9-ubuntu:~/NDK_Day88/t1/build$ lsCMakeCache.txt CMakeFiles cmake_install.cmake hello Makefile
1.
2.
上面的例子看不出有啥优势,甚至说还不如用 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; }
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
基于这些准备工作,我们想用 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)
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
将 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; }
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
现在我引用了 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)
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
音视频的播放,在线直播,音视频通话开发,后面可能都得基于 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})
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.