shell脚本生成安卓全abi动态库与静态库

1,004 阅读4分钟
原文链接: rangaofei.github.io

以jrtplib的依赖库jthread的编译为例,介绍cmake的交叉编译,因为这个库相对简单,构建完成后就可以直接使用,将动态库或者静态库放置到安卓指定的文件夹下,通过引用头文件来编写jni即可。

这篇文章用到了一点cmake交叉编译的东西,在我的掘金主页有关于如何使用cmake的教程。
juejin.cn/post/684490…

文章废话少,干货多,请静下心来看:

关于ndk的东西网上已经烂大街了,我这里就不多介绍了。这篇文章主要是关于如何使用bash控制cmake来cross-compile。

  1. 创建build文件夹用于外部构建
  2. 创建target及架构平台文件夹用于存放构建好的动态库
  3. cmake外部编译、交叉编译
  4. 将构建好的项目导出至target文件夹下

ndk-build生成多abi动态库相对简单,安卓环境的cmake环境生成动态库也很简单,不多说,但是在非安卓cmake环境中的cmake中我目前还没找到如何一次生成多个平台架构,所以写了一个脚本来实现。

由于脚本相对来说比较简单,这文章篇幅不会很长:

android架构

armeabi
armeabi­v7a
arm64­v8a
x86
x86_64
mips
mips64
这七种是我们今天将要编译的abi,这七个文件夹会在target文件夹中生成,并在对应的文件夹下生成动态库

一些变量

使用cmake交叉编译时必须找到ndk路径,否则不能编译,定义如下,将NDK_PATH替换为自己的路径即可。

#!/bin/bash

#ndk的路径,替换为自己的路径
export NDK_PATH=/Users/rangaofei/Library/Android/sdk/ndk-bundle

#将要构建的架构
TARGETS=(armeabi arm64-v8a armeabi-v7a mips mips64 x86 x86_64)

准备build文件夹

采用外部构建的方式,build文件夹是构建的文件夹,所有的生成文件都会在这边,所以先检测是否有这个文件夹,没有就创建,有就清除里边的内容。

#清除build文件夹下的内容
function clean_build() {
	if ([ -d build ]); then
		echo "prepare to clean cache"
		(rm -rf ./build/*)
		echo "complete"
	else
		echo "build is not a directory"
		exit 0
	fi
}

function prepare_build() {
	# 检测是否有Build文件夹,有的话删除文件夹,没有的话创建文件夹
	if ([ -e build ]); then
		echo "you already have build dir"
		clean_build
	else
		echo "prepare to create dir build"
		mkdir build
	fi
}

准备target文件夹

target文件夹是我们用来存放各种abi动态库的文件夹,我们会将build文件夹中生成的动态库和静态库复制到target对应的文件夹下

function prepare_target() {
	#检测是否有所有的target文件夹,有则删除,没有则创建
	if ([ -e target ] && [ -d target ]); then
		echo "prepare to clean target"
		rm -rf ./target/*
		echo "clean target complete"
	else
		echo "you not have target_dir,we will create it"
		mkdir target
	fi
}
function create_child_dir() {
	if ([ -e target ]); then
		(
			cd target
			mkdir $1
		)
	else
		echo "target is not a dir"
	fi
}

function move_to_target() {
	if ([ -e ./build/src/libjthread.a ] && [ -e ./build/src/libjthread.so ]); then
		echo "prepare move target to ./target/$1"
		mv ./build/src/libjthread.a ./target/$1
		mv ./build/src/libjthread.so ./target/$1
		echo "move to ./target/$1 finished"
	else
		echo "move error"$()
	fi
}

这里边有三个函数,第一个用来创建target文件夹,第二个用来创建abi架构的文件夹,第三个用于复制库到创建好的文件夹下。

构建函数

function build_lib() {
	clean_build
	cd build
	cmake .. \
		-DCMAKE_SYSTEM_NAME=Android \
		-DCMAKE_SYSTEM_VERSION=21 \
		-DCMAKE_ANDROID_ARCH_ABI=$1 \
		-DCMAKE_ANDROID_NDK=$NDK_PATH \
		-DCMAKE_ANDROID_STL_TYPE=gnustl_static
}

function create_all_child_dir() {
	for dir in ${TARGETS[@]}; do
		create_child_dir $dir
		echo "$dir created"
	done
}

function build_armeabi() {
	prepare_build
	prepare_target
	create_child_dir armeabi
	(
		build_lib armeabi
		make
	)
	move_to_target armeabi
}

function build_armeabi-v7a() {
	prepare_build
	prepare_target
	create_child_dir armeabi-v7a
	(
		build_lib armeabi_v7a
		make
	)
	move_to_target armeabi-v7a
}

function build_arm64-v8a() {
	prepare_build
	prepare_target
	create_child_dir arm64-8a
	(
		build_lib arm64-v8a
		make
	)
	move_to_target arm64-8a
}

function build_mips() {
	prepare_build
	prepare_target
	create_child_dir mips
	(
		build_lib mips
		make
	)
	move_to_target mips
}

function build_mips64() {
	prepare_build
	prepare_target
	create_child_dir mips64
	(
		build_lib mips64
		make
	)
	move_to_target mips64
}

function build_x86() {
	prepare_build
	prepare_target
	create_child_dir x86
	(
		build_lib x86
		make
	)
	move_to_target x86
}

function build_x86_64() {
	prepare_build
	prepare_target
	create_child_dir x86_64
	(
		build_lib x86_64
		make
	)
	move_to_target x86_64
}

function create_all_target() {
	prepare_build
	prepare_target
	create_all_child_dir
	for target in ${TARGETS[@]}; do
		(
			build_lib $target
			make
		)
		move_to_target $target
	done

看起来很长的一段函数,其实没毛的内容,第一个函数是用来交叉编译的主要函数,他接受一个平台参数,也就是我们定义好的TARGETS数组中的元素,后边的七个函数是分别构建单一abi架构。最后一个就牛逼了,他会遍历数组,构建所有的abi平台。
到这里主要功能完成了。

自动补全

为了让程序更完美,我做了一个自动补全的命令,在bash中安tab即可自动补全你想要的架构。

function sbuild() {
	echo "-------$1"
	case $1 in
	"all")
		create_all_target
		;;
	"armeabi")
		build_armeabi
		;;
	"v7a")
		build_armeabi-v7a
		;;
	"v8a")
		build_arm64-v8a
		;;
	"mips")
		build_mips
		;;
	"mips64")
		build_mips64
		;;
	"x86")
		build_x86
		;;
	"x86_64")
		build_x86_64
		;;
	"*") ;;

	esac
}

sbuild_list=(${TARGETS[@]} "all")
function _sbuild() {
	local cur
	COMPREPLY=()
	cur="${COMP_WORDS[COMP_CWORD]}"
	COMPREPLY=($(compgen -W "${sbuild_list[*]}" -- ${cur}))
	return 0
}
complete -o filenames -F _sbuild sbuild

在这里我们扩展了一下TARGETS数组,添加了一个all,这个参数就是用来构建全平台abi。

执行命令

最后执行一下,最好在bash中执行,zsh好像没有自动补全的complete命令。

source build.sh

这个命令是用来输出执行脚本,然后我们就可以用构建命令了

sbuild all

构建全平台

sbuild armeabi

构建armeabi平台。七种架构都能单独构建。
单独构建后只有一种平台架构:

.
└── armeabi
    ├── libjthread.a
    └── libjthread.so

1 directory, 2 files

全部构建后:

.
├── arm64-v8a
│   ├── libjthread.a
│   └── libjthread.so
├── armeabi
│   ├── libjthread.a
│   └── libjthread.so
├── armeabi-v7a
│   ├── libjthread.a
│   └── libjthread.so
├── mips
│   ├── libjthread.a
│   └── libjthread.so
├── mips64
│   ├── libjthread.a
│   └── libjthread.so
├── x86
│   ├── libjthread.a
│   └── libjthread.so
└── x86_64
    ├── libjthread.a
    └── libjthread.so

7 directories, 14 files

最后说明一下,这个方式是用cmake来构建安卓平台的动态库和静态库的方式,生成好的文件最后还是要放进安卓指定的文件夹中,这些库的调用必须是用jni编程来实现的,不能直接来调用。