Gradle Android NDK构建外部非JNI库(用于AAR)

wyyhbhjk  于 2023-04-10  发布在  Android
关注(0)|答案(1)|浏览(198)

Android NDK docs主要专注于构建C/C++ JNI代码。我想做的是构建一个用Go编写的外部动态库,它不使用CMake,也没有JNI Package 器。
对于这个问题,库是用Go写的并不重要,重要的是它不是用C写的。cgo用于为动态(.so)库生成C绑定,我必须将CC=<path/to/android/ndk/clang>传递给它,以便它可以在内部使用NDK编译器。我不使用JNI。相反,我使用JNA访问libstuff.so中的C导出方法。
目前,我做以下工作:
1.使用cgo手动构建共享库,并向其传递正确的Android编译器路径
1.将库复制到myapp/src/main/jniLibs/<arch>/libstuff.so
1.构建项目
(我还必须在步骤1中传递CGO_LDFLAGS="-Wl,-soname,libstuff.so",否则JNA无法找到它...)

目标

我想做的是在构建Android Gradle项目时自动构建共享库,将正确的编译器二进制文件和架构传递给Go编译器,并将相关架构的libstuff.so包含在APK中,或者实际上是AAR中,因为我正在构建Android库。

我所尝试的

我尝试了以下CMake脚本:

cmake_minimum_required(VERSION 3.18.1)
project(stuff_project)
include(ExternalProject)

# Test
message("================")
message(ANDROID_ARCH_NAME=${ANDROID_ARCH_NAME})
message("================")

ExternalProject_Add(external_proj
    PREFIX "path/to/go/library"
    SOURCE_DIR "path/to/go/library"
    BUILD_IN_SOURCE 1
    CONFIGURE_COMMAND ""
    BUILD_COMMAND "<command to build go library>"
    INSTALL_COMMAND ""
)

BUILD_COMMAND中的命令使用带有额外标志${CMAKE_C_FLAGS}${CMAKE_SHARED_LINKER_FLAGS}${ANDROID_C_COMPILER}${ANDROID_ARCH_NAME}/${ANDROID_LLVM_TRIPLE}构建库,并将其复制到${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
这是受到A/B/WireGuard的启发,在从命令行运行cmakecmake --build时可以正常工作(我没有使用Android编译器或任何东西),但是当使用android { ... externalNativeBuild { cmake { version "3.18.1"; path 'CMakeLists.txt' } } }运行Gradle时,它只会在配置时打印上面的测试消息,而不会实际构建项目。(也许我必须使external_proj依赖于某个东西?但依赖于什么呢?)
我该怎么解决这个问题?

相关

WireGuard Android应用程序也使用Go库和uses CMake的add_custom_targetCOMMAND来构建库。然而,这个库有JNI wrapper并且不使用JNA。我也尝试了add_custom_target方法,但该命令从未执行。

x8goxv8g

x8goxv8g1#

可能有一种方法可以让它与ExternalProject_Add一起工作,但我最终所做的是使用add_custom_target,并将此目标的名称添加到lib/build.gradle
模块文件夹中的CMakeLists.txt看起来有点像这样:
lib/CMakeLists.txt

cmake_minimum_required(VERSION 3.18.1)
project(my_project) #TODO
set(libname "libmycoollibrary.so") #TODO
set(go_source "${CMAKE_CURRENT_SOURCE_DIR}/../../go_source/") #TODO

# Android -> Go architecture map
set(arch_map_x86    386)
set(arch_map_x86_64 amd64)
set(arch_map_arm    arm)
set(arch_map_arm64  arm64)

set(GOARCH ${arch_map_${ANDROID_ARCH_NAME}})

# --target has to be specified to compiler & linker as e.g. ANDROID_C_COMPILER may just be 'clang' without prefixes
# CGO_CPPFLAGS are concatenated to CGO_CFLAGS and CGO_CXXFLAGS
# Setting SONAME is required for JNA to work
add_custom_target(shared-lib
    WORKING_DIRECTORY ${go_source}
    COMMENT "Building shared library for ${ANDROID_LLVM_TRIPLE}"
    VERBATIM
    COMMAND ${CMAKE_COMMAND} -E env
        CGO_ENABLED=1 GOOS=android GOARCH=${GOARCH}
        CC=${ANDROID_C_COMPILER} CXX=${ANDROID_CXX_COMPILER}
        CGO_CPPFLAGS=--target=${ANDROID_LLVM_TRIPLE} CGO_CFLAGS=${CMAKE_C_FLAGS} CGO_CXXFLAGS=${CMAKE_CXX_FLAGS}
        CGO_LDFLAGS=${CMAKE_SHARED_LINKER_FLAGS}\ --target=${ANDROID_LLVM_TRIPLE}\ -Wl,-soname,${libname}
        go build -buildmode=c-shared -o ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${libname}
            cgo_exports.go #TODO
            implementation.go #TODO
)

你将不得不改变#TODO指示的路径和源代码文件名。我个人使用GNU Make来调用go编译器,但为了这个答案我将其改为直接调用go,使用cmake -E env以跨平台的方式设置环境变量。然而,使用GNU Make的好处是,当源代码没有改变时,你的库不会被重新构建。源代码的,go可以被其他编译器替代,只要你适当地调整这个编译器的环境变量。-Wl,-soname,${libname}链接器标志很重要,因为没有它JNA will not find your library。代码最初是受code for the WireGuard app的启发,但它使用JNI而不是JNA。
现在需要在模块的build.gradle中添加shared-libadd_custom_target命令中自定义目标的名称)作为依赖项。请注意externalNativeBuilddefaultConfig.externalNativeBuild
lib/build.gradle

plugins {
    id 'com.android.library'  // Build AAR
}

android {
    //TODO... compileSdk, buildTypes, compileOptions, etc.

    defaultConfig {
        //TODO... minSdk, targetSdk, versionCode, versionName

        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

        externalNativeBuild {
            cmake {
                // Specify which target we want to build,
                // corresponds to name in add_custom_target CMake command
                targets 'shared-lib'
            }
        }
    }

    externalNativeBuild {
        cmake {
            version '3.18.1'  // See cmake_minimum_required in CMakeLists.txt
            path 'CMakeLists.txt'
        }
    }

    // Do not cache unit test results as the shared library may have changed
    tasks.matching { t -> t.name in ['testDebugUnitTest', 'testReleaseUnitTest'] }.all {
        outputs.upToDateWhen { false }
    }
}

dependencies {  //TODO You may want to update some of these
    implementation 'net.java.dev.jna:jna:5.10.0@aar'

    testImplementation 'net.java.dev.jna:jna:5.10.0'  // Include jnidispatch library in unit tests
    testImplementation 'junit:junit:4.+'

    androidTestImplementation 'androidx.test:runner:1.4.0'
}

这个build.gradle是为了构建一个lib/build/outputs/aar的AAR库而调整的,如果你想在另一个应用程序中包含这个库作为一个单独的“包”(带有一个围绕原生API的 Package 器),这很有用。我还添加了一些你可能需要用于单元测试和仪器测试的代码。对于单元测试,你需要为你自己的PC构建共享库,并将其添加到PATH
现在您应该能够在Java代码中调用JNA的Native.load("mycoollibrary", NativeApi.class),其中mycoollibrary对应于您在CMakeLists.txt中定义的${libname}的一部分,NativeApi是您定义的库接口的名称(interface NativeApi extends Library)。

相关问题