区长

Mac 生成 Linux 交叉编译工具链

需要在Mac上编译出Linux服务器上可用的动态库,使用crosstool-ng生成交叉编译工具链

大小写敏感的磁盘

创建

1
hdiutil create -volname "crosstool-ng" -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 20g crosstool-ng.dmg

挂载

1
hdiutil attach crosstool-ng.dmg.sparseimage -mountpoint /Volumes/crosstool-ng

卸载(使用完后进行卸载)

1
hdiutil detach /Volumes/crosstool-ng

安装依赖

1
brew install autoconf binutils gawk gmp gnu-sed help2man mpfr openssl pcre readline wget xz

如果后续报什么错,提示没有什么包继续安装对应包即可。

编译crosstool-ng

crosstool-ng的文档地址在 http://crosstool-ng.github.io/docs/

github地址在 https://github.com/crosstool-ng/crosstool-ng

下载源码

1
wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.23.0.tar.bz2

解压

1
tar -jxvf crosstool-ng-1.23.0.tar.bz2

进入源码目录

1
cd crosstool-ng-1.23.0

执行bootstrap脚本(可选,如果该文件存在的话需要执行,不存在该文件则跳过此步骤)

1
./bootstrap

执行configure

1
./configure

执行编译

1
make -j4

执行安装

1
make install

之后就可以直接使用ct-ng命令了

配置交叉编译工具链

创建工作目录并进入

1
2
mkdir workdir
cd workdir

拷贝配置文件。这里基于源码目录crosstool-ng-1.23.0/sample/x86_64-unknown-linux-gnu下的config进行自定义

1
cp path/to/crosstool-ng-1.23.0/sample/x86_64-unknown-linux-gnu/crosstool.config .config

配置配置文件,需要将一些目录指向我们最开始创建的大小写敏感的磁盘

1
ct-ng menuconfig

选择Paths and misc options,找到Paths,下面有三个路径需要修改

修改Local tarballs dictory指向大小写敏感的磁盘,如

1
/Volumes/crosstool-ng/src

修改Working dictory指向大小写敏感的磁盘,如

1
/Volumes/crosstool-ng/.build

修改x-tools指向大小写敏感的磁盘,如

1
CT_PREFIX:-/Volumes/crosstool-ng/x-tools}/${CT_HOST:+HOST-${CT_HOST}/}${CT_TARGET}

因为需要用到从历史构建断点继续构建的功能,将Paths and misc options->crosstool-NG behavior选项下的Debug crosstool-NG勾选,然后继续勾选Save intermediate steps

修改完成后保存然后退出

修改binutils版本

因为我们拷贝的config使用的是2.28的binutils,而gnu的ftp上不存在该版本对应xy后缀的文件,所以需要修改版本为2.28.1,而2.28.1版本是存在xy后缀的文件的

还是一样进入menuconfig,找到Binary utilities->binutils version,修改版本号为2.28.1,但是发现这里没有2.28.1的选项,因此需要修改源码,然后重新执行编译安装步骤

打开源码crosstool-ng-1.23.0/config/binutils/binutils.in文件,找到如下代码

1
2
3
4
config BINUTILS_V_2_28
bool
prompt "2.28"
select BINUTILS_2_27_or_later

在前面加入

1
2
3
4
config BINUTILS_V_2_28_1
bool
prompt "2.28.1"
select BINUTILS_2_27_or_later

然后往下找到如下代码

1
2
3
4
5
6
7
8
9
10
# CT_INSERT_VERSION_STRING_BELOW
default "2.28" if BINUTILS_V_2_28
default "2.27" if BINUTILS_V_2_27
default "2.26" if BINUTILS_V_2_26
default "2.25.1" if BINUTILS_V_2_25_1
default "linaro-2.25.0-2015.01-2" if BINUTILS_LINARO_V_2_25
default "linaro-2.24.0-2014.11-2" if BINUTILS_LINARO_V_2_24
default "2.24" if BINUTILS_V_2_24
default "linaro-2.23.2-2013.10-4" if BINUTILS_LINARO_V_2_23_2
default "2.23.2" if BINUTILS_V_2_23_2

在前面加入下面的代码

1
default "2.28.1" if BINUTILS_V_2_28_1

然后重新执行./configure、make -j4、make install等命令重新安装,然后再次进入menuconfig,会发现可以选择2.29了

生成交叉编译工具链

在workdir下执行命令构建

1
ct-ng build

然后会下载一些zip文件到/Volumes/crosstool-ng/.build/tarballs下,文件有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
binutils-2.28.1.tar.xz
expat-2.2.0.tar.bz2
gcc-6.3.0.tar.bz2
gdb-7.12.1.tar.xz
gettext-0.19.8.1.tar.xz
glibc-2.25.tar.xz
gmp-6.1.2.tar.xz
isl-0.16.1.tar.xz
libiconv-1.15.tar.gz
linux-4.10.8.tar.xz
m4-1.4.18.tar.xz
mpc-1.0.3.tar.gz
mpfr-3.1.5.tar.xz
ncurses-6.0.tar.gz

如果你觉得下载慢,可以手动下载这些文件,将其放入/Volumes/crosstool-ng/.build/tarballs,注意名字保持和上面一样。

之后会执行解压,构建,安装等操作,大约需要2-3小时后编译完成。

因为开启了debug相关的功能,如果中断了构建,可以随时从中断的地方继续构建,只需要找到对应的step即可,step可以从控制台的输出log中找到。

假设log中出现了如下字样,需要从该步骤恢复

1
Saving state to restart at step 'companion_tools_for_build'...

只需要执行下面的代码即可

1
ct-ng companion_tools_for_build+

或者

1
ct-ng build RESTART=companion_tools_for_build

编写cmake toolchain交叉编译工具链文件

关于cmake toolchain的编写,见cmake文档 cross-compiling

创建linux-x86_64.toolchain.cmake文件,加入如下内容

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(LINUX_TOOLCHAIN_NAME x86_64-unknown-linux-gnu-)
set(LINUX_TOOLCHAIN_ROOT /Volumes/crosstool-ng/x-tools/x86_64-unknown-linux-gnu)
set(CMAKE_SYSROOT ${LINUX_TOOLCHAIN_ROOT}/x86_64-unknown-linux-gnu/sysroot)

set(CMAKE_C_COMPILER ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}gcc)
set(CMAKE_CXX_COMPILER ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}g++)
set(CMAKE_AR ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}ar CACHE FILEPATH "Archiver")
set(CMAKE_RANLIB ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}ranlib CACHE FILEPATH "Ranlib")

set(CMAKE_ASM_COMPILER ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}as)
set(CMAKE_LINKER ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}ld)
set(CMAKE_NM ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}nm)
set(CMAKE_OBJCOPY ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}objcopy)
set(CMAKE_OBJDUMP ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}objdump)
set(CMAKE_STRIP ${LINUX_TOOLCHAIN_ROOT}/bin/${LINUX_TOOLCHAIN_NAME}strip)

message(STATUS "CMAKE_SYSROOT = ${CMAKE_SYSROOT}")
message(STATUS "CMAKE_C_COMPILER = ${CMAKE_C_COMPILER}")
message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}")
message(STATUS "CMAKE_AR = ${CMAKE_AR}")
message(STATUS "CMAKE_RANLIB = ${CMAKE_RANLIB}")
message(STATUS "CMAKE_ASM_COMPILER = ${CMAKE_ASM_COMPILER}")
message(STATUS "CMAKE_LINKER = ${CMAKE_LINKER}")
message(STATUS "CMAKE_NM = ${CMAKE_NM}")
message(STATUS "CMAKE_OBJCOPY = ${CMAKE_OBJCOPY}")
message(STATUS "CMAKE_OBJDUMP = ${CMAKE_OBJDUMP}")
message(STATUS "CMAKE_STRIP = ${CMAKE_STRIP}")

# Set or retrieve the cached flags.
# This is necessary in case the user sets/changes flags in subsequent
# configures. If we included the flags in here, they would get
# overwritten.

set(CMAKE_C_FLAGS ""
CACHE STRING "Flags used by the compiler during all build types.")
set(CMAKE_CXX_FLAGS ""
CACHE STRING "Flags used by the compiler during all build types.")
set(CMAKE_ASM_FLAGS ""
CACHE STRING "Flags used by the compiler during all build types.")
set(CMAKE_C_FLAGS_DEBUG ""
CACHE STRING "Flags used by the compiler during debug builds.")
set(CMAKE_CXX_FLAGS_DEBUG ""
CACHE STRING "Flags used by the compiler during debug builds.")
set(CMAKE_ASM_FLAGS_DEBUG ""
CACHE STRING "Flags used by the compiler during debug builds.")
set(CMAKE_C_FLAGS_RELEASE ""
CACHE STRING "Flags used by the compiler during release builds.")
set(CMAKE_CXX_FLAGS_RELEASE ""
CACHE STRING "Flags used by the compiler during release builds.")
set(CMAKE_ASM_FLAGS_RELEASE ""
CACHE STRING "Flags used by the compiler during release builds.")
set(CMAKE_MODULE_LINKER_FLAGS ""
CACHE STRING "Flags used by the linker during the creation of modules.")
set(CMAKE_SHARED_LINKER_FLAGS ""
CACHE STRING "Flags used by the linker during the creation of dll's.")
set(CMAKE_EXE_LINKER_FLAGS ""
CACHE STRING "Flags used by the linker.")

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS}")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
set(CMAKE_ASM_FLAGS_DEBUG "${CMAKE_ASM_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
set(CMAKE_ASM_FLAGS_RELEASE "${CMAKE_ASM_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")

#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas")
# or
#add_definitions("-Wno-unknown-pragmas")


set(CMAKE_FIND_ROOT_PATH ${LINUX_TOOLCHAIN_ROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLYONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)

#make VERVOSE=1 to output the log

值得注意的是CMAKE_AR和CMAKE_RANLIB后面加了CACHE FILEPATH,这是必须的,不然可能会出错。

交叉编译

通过CMAKE_TOOLCHAIN_FILE参数指定toolchain文件

1
cmake -DCMAKE_TOOLCHAIN_FILE=../linux-x86_64.toolchain.cmake ..

然后执行make,执行make过程中如果发生错误,可以设置VERVOSE=1将一些信息输出,便于调试

1
make VERVOSE=1

测试

将编译出来的动态库拷到linux上去测试,看是否可以正常使用,如果出现了类似 libstdc++.so.6: version `CXXABI_ARM_1.3.8’ not found 的错误,是因为生成的交叉编译工具链和服务器环境的gcc版本不一致导致的,要么修改交叉编译工具链的gcc版本和服务器上的gcc版本一样,要么修改服务器上的gcc版本和交叉编译工具链的gcc版本一样,无论哪种方式都可以。

关于jni

因为用到了jni,需要用到相关的头文件,而crosstool-ng其实是可以配置java的,但是测试下来发现宿主机Mac需要用到gcj,找了一大圈发现gcj老早已经从gcc中被移除了,如果需要用,则需要自己编译,实在麻烦。所以干脆简单点,直接拷贝头文件就可以解决问题,有两种解决方法

  1. 到服务器的JAVA_HOME目录,将include文件拷贝下来直接用
  2. 下载linux的jdk压缩包,直接提取出来用

编译的时候指定jni的头文件目录即可,这里将include文件夹中的内容放在/Volumes/crosstool-ng/java-linux下的include目录

关于gradle

gradle里用cmake编译c/c++代码,需要自己写gradle脚本,这里提供一个自己写的脚本

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
apply plugin: JNIPlugin

class JNIPlugin implements Plugin<Project> {
static def currentOS() {
final String p = System.getProperty("os.name").toLowerCase();
if (p.contains("linux")) {
return "linux"
} else if (p.contains("os x") || p.contains("darwin")) {
return "darwin"
} else if (p.contains("windows")) {
return "windows"
} else {
return p.replaceAll("\\s", "")
}
}

static String currentArchitecture() {
final String arch = System.getProperty("os.arch").toLowerCase()
return (arch.equals("amd64")) ? "x86_64" : arch
}

void apply(Project project) {
project.afterEvaluate {

// def osList = ["darwin", "linux"]
// def architectureList = ["x86_64"]

//当前环境为mac
def osList = ["${currentOS()}", "linux"]
def architectureList = ["${currentArchitecture()}"]
def javaHomeList = [
"${currentOS()}": "${System.getenv('JAVA_HOME')}",
"linux" : "/Volumes/crosstool-ng/java-linux"
]

osList.each {os ->
architectureList.each {architecture ->
def javaHome = javaHomeList.get(os)

File staticWorkingDir = project.file("jni/demo-static/${os}-${architecture}")
File dynamicWorkingDir = project.file("jni/demo-dynamic/${os}-${architecture}")

def demoStaticCleanTask = project.task("cleanDemoStaticFor${os.capitalize()}${architecture.capitalize()}")
demoStaticCleanTask.setGroup("jni")
demoStaticCleanTask.doLast {
project.println "delete demo static cmake files ${demoStaticCleanTask}"
GFileUtils.deleteDirectory(demoStaticCleanTask)
}

def demoDynamicCleanTask = project.task("cleanDemoDynamicFor${os.capitalize()}${architecture.capitalize()}")
demoDynamicCleanTask.setGroup("jni")
demoDynamicCleanTask.doLast {
project.println "delete demo dynamic cmake files ${dynamicWorkingDir}"
GFileUtils.deleteDirectory(dynamicWorkingDir)
}

def cleanTask = project.tasks.findByName("clean")
if (cleanTask) {
cleanTask.dependsOn demoStaticCleanTask
cleanTask.dependsOn demoDynamicCleanTask
}

def demoStaticTask = project.task("demoStaticFor${os.capitalize()}${architecture.capitalize()}")
demoStaticTask.setGroup("jni")

demoStaticTask.doLast {

File staticLibrary = new File(staticWorkingDir, "/install/lib/libdemostatic.a")
if (!staticLibrary.exists()) {
project.println "staticLibrary not exist."
GFileUtils.deleteDirectory(staticWorkingDir)
GFileUtils.mkdirs(staticWorkingDir)

//generate cmake files
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir staticWorkingDir
execSpec.executable "cmake"
if (os == "linux" && currentOS() != "linux") {
execSpec.args "-DCMAKE_TOOLCHAIN_FILE=${project.file('jni/linux-x86_64.toolchain.cmake')}"
}
execSpec.args("..")
}
})

//clean
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir staticWorkingDir
execSpec.executable "make"
execSpec.args("clean")
}
})
}

//make
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir staticWorkingDir
execSpec.executable "make"
execSpec.args("-j4")
}
})

//install
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir staticWorkingDir
execSpec.executable "make"
execSpec.args("install")
}
})
}


def demoDynamicTask = project.task("demoDynamicFor${os.capitalize()}${architecture.capitalize()}")
demoDynamicTask.setGroup("jni")
demoDynamicTask.dependsOn demoStaticTask
demoDynamicTask.doLast {

File dynamicLibraryDir = new File(dynamicWorkingDir, "/install/lib")
File demoStaticInstallDir = new File(staticWorkingDir, "/install")
String demoVersion = "0.0.1"

File[] dynamicLibrary = dynamicLibraryDir.listFiles(new FileFilter() {
@Override
boolean accept(File pathname) {
return pathname.isFile() && pathname.getName().startsWith("lib")
}
})
if (!dynamicLibraryDir.exists() || dynamicLibrary == null || dynamicLibrary.length == 0) {
project.println "dynamicLibrary not exist."
GFileUtils.deleteDirectory(dynamicWorkingDir)
GFileUtils.mkdirs(dynamicWorkingDir)

//generate cmake files
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir dynamicWorkingDir
execSpec.executable "cmake"
execSpec.args("-DDEMO_INSTALL_PATH=${demoStaticInstallDir.absolutePath}")
execSpec.args("-DVERSION=${demoVersion}")
execSpec.args("-DJAVA_INCLUDE=${javaHome}/include")
execSpec.args("-DJAVA_OS_INCLUDE=${javaHome}/include/${os}")
if (os == "linux" && currentOS() != "linux") {
execSpec.args "-DCMAKE_TOOLCHAIN_FILE=${project.file('jni/linux-x86_64.toolchain.cmake')}"
}
execSpec.args("..")
}
})

//clean
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir dynamicWorkingDir
execSpec.executable "make"
execSpec.args("clean")
}
})
}

//make
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir dynamicWorkingDir
execSpec.executable "make"
execSpec.args("VERBOSE=1")
execSpec.args("-j4")
}
})

//install
project.exec(new Action<ExecSpec>() {
@Override
void execute(ExecSpec execSpec) {
execSpec.workingDir dynamicWorkingDir
execSpec.executable "make"
execSpec.args("install")
}
})
}

def compileJavaTask = project.tasks.findByName("compileJava")
if (compileJavaTask) {
compileJavaTask.dependsOn demoStaticTask
compileJavaTask.dependsOn demoDynamicTask
}
}
}
}
}
}

当然打包jar的时候,还需要将动态库打到jar包里

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
apply plugin: 'java'
sourceCompatibility = 1.8

static def currentOS() {
final String p = System.getProperty("os.name").toLowerCase()
if (p.contains("linux")) {
return "linux"
} else if (p.contains("os x") || p.contains("darwin")) {
return "darwin"
} else if (p.contains("windows")) {
return "windows"
} else {
return p.replaceAll("\\s", "")
}
}

static def currentArchitecture() {
final String arch = System.getProperty("os.arch").toLowerCase()
return (arch.equals("amd64")) ? "x86_64" : arch
}

jar {
//def osList = ["darwin", "linux"]
//def architectureList = ["x86_64"]
def osList = ["${currentOS()}", "linux"]
def architectureList = ["${currentArchitecture()}"]

osList.each {os ->
architectureList.each {architecture ->
//copy shared library to classpath when assemble a jar
from(project.file("jni/demoDynamic/${os}-${architecture}/install/lib")) {
into "com/lizhangqu/lib/dynamic/${os}-${architecture}"
}
}
}

}

cmake中需要指定安装目录

1
2
3
4
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Installation Directory")
endif()
message(STATUS "CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}")

cmake 中install动态库

1
install(TARGETS demodynamic LIBRARY DESTINATION lib)

关于java加载

参考tensorflow的动态库加载方式,代码如下

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
final class NativeLibrary {
private static final boolean DEBUG = true;
private static final String PACKAGENAME = "com/lizhangqu/lib/dynamic";
private static final String LIBNAME = "demodynamic";

public static void load() {
if (isLoaded() || tryLoadLibrary()) {
// Either:
// (1) The native library has already been statically loaded, OR
// (2) The required native code has been statically linked (through a custom launcher), OR
// (3) The native code is part of another library (such as an an application-level libraryh)
// that has already been loaded.
//
// Doesn't matter how, but it seems the native code is loaded, so nothing else to do.
return;
}
// Native code is not present, perhaps it has been packaged into the .jar file containing this.
final String resourceName = makeResourceName();
log("resourceName: " + resourceName);
InputStream resource =
NativeLibrary.class.getClassLoader().getResourceAsStream(resourceName);

//retry
if (resource == null) {
String abiName = makeABILibName();
log("abiName: " + abiName);
resource = NativeLibrary.class.getResourceAsStream(abiName);
}

if (resource == null) {
throw new UnsatisfiedLinkError(
String.format(
"Cannot find bridge native library for OS: %s, architecture: %s. ",
os(), architecture()));
}
try {
System.load(extractResource(resource));
} catch (IOException e) {
throw new UnsatisfiedLinkError(
String.format(
"Unable to extract native library into a temporary file (%s)", e.toString()));
}
}

private static boolean tryLoadLibrary() {
try {
System.loadLibrary(LIBNAME);
return true;
} catch (UnsatisfiedLinkError e) {
log("tryLoadLibraryFailed: " + e.getMessage());
return false;
}
}

private static boolean isLoaded() {
try {
//提供getVersion方法用于测试是否加载成功
String version = DemoDynamic.getVersion();
log("version: " + version);
log("isLoaded: true");
return true;
} catch (UnsatisfiedLinkError e) {
return false;
}
}

private static String extractResource(InputStream resource) throws IOException {
final String sampleFilename = System.mapLibraryName(LIBNAME);
final int dot = sampleFilename.indexOf(".");
final String prefix = (dot < 0) ? sampleFilename : sampleFilename.substring(0, dot);
final String suffix = (dot < 0) ? null : sampleFilename.substring(dot);

final File dst = File.createTempFile(prefix, suffix);
final String dstPath = dst.getAbsolutePath();
dst.deleteOnExit();
log("extracting native library to: " + dstPath);
final long nbytes = copy(resource, dst);
log(String.format("copied %d bytes to %s", nbytes, dstPath));
return dstPath;
}

private static String os() {
final String p = System.getProperty("os.name").toLowerCase();
if (p.contains("linux")) {
return "linux";
} else if (p.contains("os x") || p.contains("darwin")) {
return "darwin";
} else if (p.contains("windows")) {
return "windows";
} else {
return p.replaceAll("\\s", "");
}
}

private static String architecture() {
final String arch = System.getProperty("os.arch").toLowerCase();
return (arch.equals("amd64")) ? "x86_64" : arch;
}

private static void log(String msg) {
if (DEBUG) {
System.err.println("com.lizhangqu.lib.dynamic.NativeLibrary: " + msg);
}
}

private static String makeResourceName() {
return PACKAGENAME
+ String.format("/%s-%s/", os(), architecture())
+ System.mapLibraryName(LIBNAME);
}

private static String makeABILibName() {
return String.format("%s-%s/", os(), architecture())
+ System.mapLibraryName(LIBNAME);
}

private static long copy(InputStream src, File dstFile) throws IOException {
FileOutputStream dst = new FileOutputStream(dstFile);
try {
byte[] buffer = new byte[1 << 20]; // 1MB
long ret = 0;
int n = 0;
while ((n = src.read(buffer)) >= 0) {
dst.write(buffer, 0, n);
ret += n;
}
return ret;
} finally {
dst.close();
src.close();
}
}
}
坚持原创技术分享,您的支持将鼓励我继续创作!
区长 WeChat Pay

微信打赏