引言
如果你看过源码,你会发现有很多的Android.mk文件本质上就是一段段Makefile单元,Android.mk文件用来告知编译系统以何种组织结构的形式去进行编译,无论是编译整个系统还是编译系统的某些模块或者是编译使用了各种动态、静态库都由.mk 去之道,如果要细说可以写成一整个章节,对于我们NDK开发来说没有必要,只要掌握一些主要的知识点就可以了,而且Google已经开始使用CMake来替代makefile,也推荐优先使用CMake,有必要总结下它们的使用。
- Android NDK——必知必会之配置Windows与Linux共享及 Linux NDK 交叉编译环境配置(一)
- Android NDK——必知必会之JNI和NDK基础全面详解(二)
- Android NDK——必知必会之JNI的C++操作函数详解和小结(三)
- Android NDK——必知必会之从Java 传递各种数据到C/C++进行处理全面详解(四)
- Android NDK——必知必会之C/C++传递各种类型数据到Java进行处理及互相调用详解(五)
- Android NDK——必知必会之Native线程操作及线程同步全面详解(六)
- Android NDK——必知必会之Makefile和CMake基本使用语法概述(七)
一、Makefile
一段由我们程序员所编写的源文件从书写到被执行,无论是c、c++都需要经历几个阶段:首先要编译(compile)即把源文件编译成中间代码文件(在Windows下是 .obj 文件;UNIX下是 .o 文件,即Object File),然后再链接(link)即把大量的Object File合成可执行文件或者动、静态库。通常一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,而makefile则定义了一系列的规则来指定编译规则,哪些文件需要先编译,哪些文件需要后编译,如何进行链接等等操作。简而言之makefile 就是“自动化编译”的配置脚本,告诉make命令如何编译和链接,默认的情况下,gnu make命令会在当前目录下按顺序查找以下文件"GNUmakefile”、“makefile”、“Makefile”
最好不要用“GNUmakefile”,这个文件是GNU的make识别的(Windows N make就不识别)当然,也可以使用别的文件
来书写Makefile,比如:“Make.Linux”,“Make.android”,那么在使用时候就需要make -f XX 或者 make --file XX。
二、CMake
在Android Studio 2.2及以上,构建原生库的默认工具是 CMake。CMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程),并且能够输出各种各样的makefile或者project文件。但CMake 并不直接建构出最终的软件,而是产生其他工具的脚本(如Makefile ),然后再依这个工具的构建方式使用。CMake是一个比make更高级的编译配置工具,它可以根据不同平台、不同的编译器,生成相应的Makefile或者vcproj项目。从而达到跨平台的目的。Android Studio利用CMake生成的是ninja,ninja是一个小型的关注速度的构建系统。我们不需要关心ninja的脚本,知道怎么配置CMake就可以了,从而可以看出cmake其实是一个跨平台的支持产出各种不同的构建脚本的一个工具。
1、CMake基本结构
CMake的脚本名默认是CMakeLists.txt的基本结构—— 一个最基本的CmakeLists.txt文件最少需要包含以下三行(CMake的系统指令是不区分大小写的,但是变量和字符串是严格区分的)
#cmake最低版本
cmake_minimum_required(VERSION 3.6.0)
#指定项目名称,若是在VS里生成的 VS 工程名为 Main.sln
project(Main)
#生成可执行文件 main,即执行cmake . 命令生成makefile,再执行make即可生成main程序
add_executable(main main.c)
2、CMake 基本语法
2.1、编译源码并自动执行内部构建命令,生成可执行文件
先通过cmake编译,接着使用make 指令生成可执行文件
2.2、为程序添加版本号和带有使用版本号的头文件
方法set(KEY VALUE)接受两个参数,用来声明变量。在camke语法中使用KEY并不能直接取到VALUE,必须使用${KEY}这种写法来取到VALUE。
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
# add the executable
add_executable(Tutorial tutorial.cxx)
配置文件将会被写入到可执行文件目录下,所以我们的项目必须包含这个文件夹来使用这些配置头文件,即需要在工程目录下新建一个TutorialConfig.h.in,内容如下:(其中**@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@将会被替换为CmakeLists.txt中的1和0**)
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
2.3、include_directories 添加头文件目录
g++选项中的-I参数的作用,也相当于环境变量中增加路径到CPLUS_INCLUDE_PATH变量的作用。即为了确保 CMake 可以在编译时定位头文件,可以使用 #include < xx > 引入,否则需要使用 #include “path/xx”
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
比如下图在引入giflib时在源文件中找不到对应的头文件,即使已经把giflib下的所有源文件都假如到编译过程了,但是如果是以下图的结构的话直接使用 #include “gif_lib.h” 引入是会报错的,因为根据以下工程结构来说是需要指定对应的相对地址—— #include “giflib/gif_lib.h” 才可以找到(使用<> 引用的话相当于是去系统预置的环境变量中对应的路径去查找,而使用" "引用的话相当于是去当前文件的对应的路径下去查找,而使用include_directoried也就相当于是添加路径到头文件的环境变量)
而include_directories指令就是让我们可以不通过相对路径也能引用到,在CMakeList里添加上 include_directories(src/main/giflib) 即可。
2.4、link_directories 添加需要链接的库文件目录
相当于g++命令的-L选项的作用,也相当于环境变量中增加LD_LIBRARY_PATH的路径的作用。
link_directories(directory1 directory2 ...)
2.5、find_library 查找库所在目录
默认情况下CMake在编译的时候会去默认的目录下查找库和头文件,如果你需要指定自己的库查找目录,可以通过来find_library 指定,如配置了find_library (UR_LIB rt /usr/lib /usr/local/lib NO_DEFAULT_PATH) CMake就会先在配置的目录中查找,若所有目录中都没有,值UR_LIB 就会被赋为NO_DEFAULT_PATH,简而言之就是通过find_library 方法把路径赋值给自定义变量
# VAR 为自定义的一个变量名,执行到这句之后就会被后面的参数初始化
find_library (<VAR> name1 [path1 path2 ...])
2.6、link_libraries 添加需要链接的库文件路径
如果我们生成的so中还需要引入已经存在的外部库文件,可以通过link_libraries来引入动态库或者静态库等。基本语法:
link_libraries(library1 <debug | optimized> library2 …)
# 直接是全路径
link_libraries(“xxx/lib/libcommon.a”)
# 只有库名,cmake会自动去所包含的目录搜索
link_libraries(mylib)
# ${} 使用变量
link_libraries(${UR_LIB })
# 也可以链接多个,中间使用空格分隔.
link_libraries("xx/libgif.so" "xxx/libcv.so")
2.7、target_link_libraries 设置要链接依赖的库文件的名称
如果我们生成的so中还需要引入已经存在的外部库文件,可以通过target_link_libraries来引入动态库或者静态库等
target_link_libraries(<target> [item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)
如果没有通过target_link_libraries 指定索要依赖的外部库,那么在编译的时候通常会报类似 undefined reference to 'Xxxxx’的异常错误,如下图就是因为在源文件里需要在C/C++中使用位图,而位图对象位于外部库却没有指定导致的。
表示要生成MYGIF 动态库的话需要依赖jnigraphics库(即libjnigraphics.so)
target_link_libraries(MYGIF
jnigraphics)
2.8、add_library 创建自己的静态库或动态库文件
通过这个指令可以配置要生成的库名称已经所需要的源码文件,多个源码文件之间用空格连接,即都是需要通过这些源文件参与编译过程最终生成对应的库文件。
#add_library( 动态链接库名称 SHARED 源码列表 )
#add_library( 静态链接库名称 STATIC 源码列表 )
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/main/cpp/dynamic_regist.cpp
)
2.9、add_executable 把目标文件列表编译生成可执行的二进制文件
#add_executable( 二进制名 源码文件列表 )
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
但如果源文件很多,那么一个个写进去是一件很麻烦的事情,这时候可以通过aux_source_directory把指定目录中所有的文件名全部保存到变量中
#仅仅查找当前目录(不会查找子目录)所有源文件 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
message(${DIR_SRCS})
也可以通过file 把指定后缀的文件名全部保存到变量中使用
#file( GLOB SRCS *.c *.cpp *.cc *.h *.hpp ),其中GLOB 为关键字,SRCS为自定义的变量名,可以理解为Java中的集合
// file(GLOB DIR_SRCS *.cpp) 3.18版本上失效 需要改成下面的形式
file(GLOB DIR_SRCS src/main/cpp/ *.cpp)
message(${DIR_SRCS})
如果在cmake中需要使用其他目录的cmakelist
cmake_minimum_required (VERSION 3.6.0)
project (Main)
aux_source_directory(. DIR_SRCS)
# 添加 child 子目录下的cmakelist
add_subdirectory(child)
# 指定生成目标
add_executable(main ${DIR_SRCS})
# 添加链接库
target_link_libraries(main child)
child目录下的cmake:
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库 默认生成静态库
add_library (child ${DIR_LIB_SRCS})
#指定编译为静态库
add_library (child STATIC ${DIR_LIB_SRCS})
在上面的例子中都是生成可执行文件,让我们对CMake有了一定的了解,现在到Android Studio中使用CMake
#NDK中已经有一部分预构建库 ndk库已经是被配置为cmake搜索路径的一部分 所以可以
findLibrary(log-lib log)
target_link_libraries( native-lib
${log-lib} )
#其实直接这样就行
target_link_libraries( native-lib
log )
#设置cflag和cxxflag
#定义预编译宏:TEST
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DTEST" )
set(CMAKE_Cxx_FLAGS "${CMAKE_Cxx_FLAGS} -DTEST" )
添加其他预编译库(已经提前编译好的库)
#使用 IMPORTED 标志告知 CMake 只希望将库导入到项目中
#如果是静态库则将shared改为static
add_library( imported-lib
SHARED
IMPORTED )
# 参数分别为:库、属性、导入地址、so所在地址
set_target_properties(
imported-lib
PROPERTIES
IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/${ANDROID_ABI}/libimported-lib.so )
#为了确保 CMake 可以在编译时定位头文件,即可以使用 #include <xx> 引入,否则需要使用 #include "path/xx"
include_directories( imported-lib/include/ )
#native-lib 是自己编写的源码最终要编译出的so库
target_link_libraries(native-lib imported-lib)
#===========================================================================================#
#添加其他预编译库还可以使用这种方式,使用-L指导编译时库文件的查找路径
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Lxx")
#为了确保 CMake 可以在编译时定位您的标头文件
include_directories( imported-lib/include/ )
#native-lib 是自己编写的源码最终要编译出的so库
target_link_libraries(native-lib imported-lib)
3.0、常用指令
#set命令表示声明一个变量source 变量的值是后面的可变参数
set(source a b c)
message(${source})
#逻辑判断 计较字符串
set(ANDROID_ABI "areambi-v7a")
if(${ANDROID_ABI} STREQUAL "areambi")
message("armv5")
elseif(${ANDROID_ABI} STREQUAL "areambi-v7a")
message("armv7a")
else()
endif()
#循环
foreach(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endforeach(loop_var)
foreach(i 0 1 2 3)
message(STATUS "current is ${i}")
endforeach(i)
message(STATUS "end")
endforeach(i)
一个相对完整的简单CMakeList.txt
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
file(GLOB FRAMESEQUENCE_SOURCE src/main/cpp/framesequence/*.cpp)
file(GLOB CORE_SOURCE src/main/cpp/*.cpp)
#需要giflib的支持
file(GLOB LIBGIF_SOURCE src/main/cpp/giflib/*.c)
set(LIBNAME framesequence)
# 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.
add_library( # Sets the name of the library.
${LIBNAME}
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${FRAMESEQUENCE_SOURCE}
${CORE_SOURCE}
${LIBGIF_SOURCE}
)
include_directories(src/main/cpp
src/main/cpp/giflib
#src/main/cpp/utils 如果带代码中是通过 include "log.h"则需要引入
src/main/cpp/framesequence)
# 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.
${LIBNAME}
# Links the target library to the log library included in the NDK.
jnigraphics
log )