人间观察
带饭去上班的都是成年人的奢侈品!
技术永远在不断的更新升级,Android也一样。
目前在Android中的JNI开发都是采用的CMake
进行编译c,c++代码来构建项目,早期都是Android.mk、Application.mk文件来构建项目的。
那CMake
是啥呢?简单的说它是一个跨平台的编译工具,它可以用简单的配置文件就可以生成编译的中间产物(Makefile 或者 project 文件),然后用make生成可执行的文件。
CMake官网地址 CMake官网
在Android Studio IDE中不需要我们单独安装CMake,因为NDK已经自带了。
配置文件就是CMakeLists.txt
文件,我们需要熟悉它的语法,不然你看JNI项目的都看不懂,那还搞啥呢!
本篇也是自己学习后的记录,之前自己对CMakeLists.txt的了解也较少。
CMakeLists.txt 语法介绍
1是有的项目使用cmake的时候一些函数都是用大写的,CMakeLists.txt文件不区分大小写,所以看到不要觉得奇怪,一样的。
2是这个文件ide不会对api进行提示,所以你写错一点就会很难查。所以写的时候需要特别注意,我们相信Android studio后续版本会支持。
设置cmake的版本
指定cmake的最小版本,用于编译该项目的cmake版本
cmake_minimum_required(VERSION 3.4.1)
设置项目名称
设置项目的名称,这个是可选的。如果设置了会cmake会自动给你定义两个变量:demo_SOURCE_DIR,demo_BINARY_DIR。不过不指定的话默认有PROJECT_SOURCE__DIR ,PROJECT_BINARY_DIR,分别代表项目的源文件目录,项目编译后生成的二进制目录。
project(demo)
调试信息打印
支持变量取值输出${}
,字符串打印
message("build with debug mode")
message("project source dir=${PROJECT_SOURCE_DIR}")
message(WARNING "输出了一条警告信息")
设置生成的类型
用于生成可执行文件还是so库,一般自己调试/写demo的时候使用add_executable
,对外提供so用add_library
。
add_executable(hello hello.cpp)
生成的是可执行文件,其中第一个参数是生成可执行文件的名字,后面的是源文件。就像之前用gcc
编译一样,采用 gcc hello.cpp -o hello
命令来生成可执行文件hello
。
add_executable(hello hello.cpp) # 生成可执行文件
add_library(demo STATIC demo.cpp) # 生成静态库
add_library(demo SHARED demo.cpp) # 生成动态库或共享库
设置需要编译的源文件和头文件
指定头文件目录
include_directories
中可以设置多个如下,include_directories
不区分大小写
INCLUDE_DIRECTORIES(
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}
)
明确指定包含哪些源文件
add_library(hello hello.cpp a.cpp b.cpp)
指定目录下的源文件
aux_source_directory(dir var)
第一个是目录,第二个是变量,意思就是把当前工程目录下的 src 目录的下的所有源文件赋值给 SRC_LIST。赋值后用到的时候通过${SRC_LIST}
即可。
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LIST)
add_library(hello ${SRC_LIST})
下面这个方式等同于上面的,意思是当前工程目录下的 fun1 目录的下的所有源文件赋值给 SRC_LIST
file(GLOB SRC_LIST_1 "fun1/*.cpp")
add_library(hello ${SRC_LIST_1})
可以调用多次,比如不同的目录设置不同的变量
file(GLOB SRC_LIST_1 "fun1/*.cpp")
file(GLOB SRC_LIST_2 "fun2/*.cpp")
add_library(hello ${SRC_LIST_1} ${SRC_LIST_2)
当然上面的aux_source_directory
也可以调用多次,可以理解成搜索你想要的源文件并设置到多个变量上,比如:
aux_source_directory(fun1 SRC_LIST_1)
aux_source_directory(fun2 SRC_LIST_2)
add_library(hello ${SRC_LIST_1} ${SRC_LIST_2)
接下来,我们先试一下如上的这些。我们不在Android studio测试,我们直接安装cmake工具测试,这样方便我们更容易理解其中的道理。
如何安装CMake
呢 ?
可以参考网上的这篇 CMake的安装
我们先测试一下生成可执行文件并打印的有关的cmake提供的一些常量,我们随便建立一个目录,并在目录下建立一个hello.cpp的源文件和CMakeLists.txt文件代码如下:
#include
int main(int argc, char* argv[]){
int a=100;
int b=200;
printf("a+b=%d\n",(a+b));
return 0;
}
# 指定 cmake 最低编译版本
cmake_minimum_required(VERSION 3.4.1)
# 指定项目名字
project (HELLO)
# 输出打印构建目录
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_BINARY_DIR " ${PROJECT_BINARY_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
ADD_EXECUTABLE(hello hello.cpp)
开始编译,新建 build 目录(为什么要新建这个make模块一般都是这个,为了生成的中间产物和我们的源代码分离),cd 到 build 目录下,敲 cmake .. 命令,然后make指令生成可执行文件。输出:
B000000073160:build guxiuzhong$ cmake ..
-- The C compiler identification is AppleClang 10.0.0.10001044
-- The CXX compiler identification is AppleClang 10.0.0.10001044
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- This is HELLO_BINARY_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello/build
-- This is HELLO_SOURCE_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello
-- This is PROJECT_BINARY_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello/build
-- This is PROJECT_SOURCE_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello
-- This is CMAKE_SOURCE_DIR /Users/guxiuzhong/Desktop/cmake/cmake-hello
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/guxiuzhong/Desktop/cmake/cmake-hello/build
B000000073160:build guxiuzhong$ make
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
B000000073160:build guxiuzhong$
可以看到打印的有关的cmake提供的一些常量值。
同时ls 一下会发现 CMake 帮我们生成了 Makefile 等等一些文件。敲 make 命令生成 hello 可执行文件
B000000073160:build guxiuzhong$ ls -al
total 96
drwxr-xr-x 8 guxiuzhong 672505530 256 11 1 13:07 .
drwxr-xr-x 7 guxiuzhong 672505530 224 11 1 13:07 ..
-rw-r--r--@ 1 guxiuzhong 672505530 6148 11 1 13:03 .DS_Store
-rw-r--r-- 1 guxiuzhong 672505530 13816 11 1 13:07 CMakeCache.txt
drwxr-xr-x 12 guxiuzhong 672505530 384 11 1 13:07 CMakeFiles
-rw-r--r-- 1 guxiuzhong 672505530 5315 11 1 13:07 Makefile
-rw-r--r-- 1 guxiuzhong 672505530 1558 11 1 13:07 cmake_install.cmake
-rwxr-xr-x 1 guxiuzhong 672505530 8432 11 1 13:07 hello
B000000073160:build guxiuzhong$ ./hello
a+b=200
B000000073160:build guxiuzhong$
来我们接着看其它语法。
查找指定的链接库文件
find_library(var path)
查找到指定的预编译库,并把它的路径存储在变量var中。默认的搜索路径为 cmake 包含的系统库,所以NDK 的公共库只需要指定库的 name 即可。比如Android jni中用于打印日志的系统库log,可以如下写法:
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 )
设置链接库搜索目录
也可以指定多个目录
link_directories(
${PROJECT_CURRENT_SOURCE_DIR}/libs
设置本项目生产的库需要链接的其它库
如果编译本项目需要依赖其它的so库,通过如上的查找后,就可以通过下面的就进行设置了。
target_link_libraries( # 目标库
hello
# 目标库需要链接的库
# log-lib 是上面 find_library 指定的变量名
${log-lib} )
到这里,我们演示下如何链接别人的库,首先我们先用add_library
创建一个库,我们新建一个目录cmake-createso
然后里面建立2个名为include
src
的子目录,里面放头文件和源代码。
include 文件夹下的2个文件
// add.h 文件
#ifndef _ADD_H
#define _ADD_H
#endif
int add(int num1, int num2);
// sub.h
#ifndef _SUB_H
#define _SUB_H
#endif
int sub(int num1, int num2);
src文件夹下的2个文件
// add.cpp
#include "add.h"
int add(int num1, int num2){
return num1 + num2;
}
// sub.cpp
#include "sub.h"
int sub(int num1, int num2){
return num1 - num2;
}
关键的我们的CMakeLists.txt
文件
#版本号
cmake_minimum_required(VERSION 3.4.1)
#项目名字
project (math)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定源文件
file(GLOB SRC_LIST "${PROJECT_SOURCE_DIR}/src/*.cpp")
# 指定输出 .so 动态库的目录位置
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
# 指定生成动态库
add_library(math SHARED ${SRC_LIST})
同样的新建 build 目录,建立完成目录结构如下:
├── build
│ CMakeLists.txt
└── libs
└── include
└── add.h
└── sub.h
└── src
└── add.cpp
└── sub.cpp
进入到build目录,cmake ..
没有报错的话然后make
,成功后会在lib下生成名为math
的库,我的是mac电脑生成的是libmath.dylib
,如果是在Android studio的ide中生成的是libmath.so
。
ok ,既然生成了so,接下来我们使用一下。我们在第一次生成hello的demo中链接下libmath.so
,既然是使用so我们需要把include头文件(方法声明)夹和libs库都拷贝到第一次生成hello的demo的build的统计目录下。同时修改下hello.cpp 如下:
#include
#include "add.h"
#include "sub.h"
int main(int argc, char* argv[]){
int a = 100;
int b = 200;
printf("%d+%d=%d\n",a,b,add(a,b));
printf("%d-%d=%d\n",a,b,sub(a,b));
return 0;
}
然后target_link_libraries
通过CMakeLists.txt
添加链接的so库。如下:
# 指定 cmake 最低编译版本
cmake_minimum_required(VERSION 3.4.1)
project (HELLO)
#指定头文件目录位置
include_directories(${PROJECT_SOURCE_DIR}/include)
#添加共享库搜索路径
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/libs)
#生成可执行文件
add_library(hello hello.cpp)
#为hello添加共享库链接
target_link_libraries(hello math)
一样进入到build目录,cmake ..
没有报错的话然后make
,成功后输出
B000000073160:build guxiuzhong$ make
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
B000000073160:build guxiuzhong$ ./hello
100+200=300
100-200=-100
设置变量
- set 直接设置变量的值,比如上面的demo
# 指定输出 .so 动态库的目录位置
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
- 追加设置变量的值
set(SRC_LIST hello.cpp)
set(SRC_LIST ${SRC_LIST} fun1.cpp)
add_executable(hello ${SRC_LIST})
ok,到这里就结束了。
上面写的这些知识只是简单的了解和入门,不至于下次看到觉得一脸懵逼。
cmake的语法不亚于一种脚本语言,比如if…elseif…else…endif等条件控制。
可以参考官网,或者用到的时候再学习下。
官网开发文档
C/C++ 开发工具推荐CLion
上面的代码都是手写的,太烦了,而且CMakeLists.txt
文件也不好写。
最后,推荐CLion,CLion 是 JetBrains 推出的全新的 C/C++ 跨平台集成开发环境。有Mac版本的,界面像Android studio。长这样,是不是很喜欢: