Skip to content

Commit 32e6bf9

Browse files
authored
feat(android): build native libs via CMake presets into jniLibs (wenet-e2e#374)
Fixes wenet-e2e#373
1 parent b292022 commit 32e6bf9

28 files changed

Lines changed: 367 additions & 177 deletions

File tree

.github/workflows/android.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Android Build
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
paths:
7+
# Android project itself, plus the shared C++ runtime it compiles from source.
8+
- "runtime/android/**"
9+
- "runtime/bindings/android/**"
10+
- "runtime/CMakeLists.txt"
11+
- "runtime/CMakePresets.json"
12+
- "runtime/processor/**"
13+
- "runtime/utils/**"
14+
- "runtime/cmake/**"
15+
- ".github/workflows/android.yml"
16+
17+
jobs:
18+
build-android:
19+
name: build (android)
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- uses: actions/setup-java@v4
25+
with:
26+
distribution: temurin
27+
java-version: '21'
28+
29+
# Caches downloaded Gradle distribution and dependencies across runs.
30+
- name: Setup Gradle
31+
uses: gradle/actions/setup-gradle@v4
32+
with:
33+
build-root-directory: runtime/android
34+
35+
# Puts sdkmanager on PATH, accepts licenses, installs platform + build-tools.
36+
- name: Setup Android SDK
37+
uses: android-actions/setup-android@v3
38+
with:
39+
packages: 'platforms;android-34 build-tools;34.0.0'
40+
41+
# sdkmanager has no bare "ndk" package — must install "ndk;X.Y.Z". Pick the
42+
# newest side-by-side NDK from the package list.
43+
- name: Install NDK (latest)
44+
run: |
45+
NDK_PKG=$(sdkmanager --list 2>/dev/null \
46+
| grep -oE 'ndk;[0-9]+\.[0-9]+\.[0-9]+' \
47+
| sort -t';' -k2 -V \
48+
| tail -1)
49+
if [ -z "$NDK_PKG" ]; then
50+
echo "ERROR: no ndk;* package found in sdkmanager --list" >&2
51+
sdkmanager --list 2>/dev/null | grep -i ndk | head -20 >&2 || true
52+
exit 1
53+
fi
54+
echo "Installing $NDK_PKG"
55+
sdkmanager --install "$NDK_PKG"
56+
NDK_VER="${NDK_PKG#ndk;}"
57+
echo "Using NDK $NDK_VER"
58+
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$NDK_VER" >> "$GITHUB_ENV"
59+
60+
# Build the native lib with CMake presets and install it into jniLibs/<abi>/.
61+
# Gradle does NOT drive CMake; it only packages the prebuilt .so.
62+
- name: Build native lib
63+
working-directory: runtime
64+
run: |
65+
cmake --preset android-arm64-v8a
66+
cmake --build --preset android-arm64-v8a
67+
cmake --install build/aarch64-linux-android --component jni
68+
69+
- uses: actions/setup-python@v5
70+
with:
71+
python-version: '3.x'
72+
73+
# Generate the four FST models straight into assets/ so the APK ships with
74+
# them (the app loads zh_tn_* and zh_itn_* at runtime).
75+
- name: Generate model assets
76+
run: |
77+
pip install pynini importlib_resources
78+
assets=runtime/android/app/src/main/assets
79+
python -m tn --language zh --overwrite_cache --cache_dir "$assets"
80+
python -m itn --language zh --overwrite_cache --cache_dir "$assets"
81+
82+
- name: Build APK
83+
working-directory: runtime/android
84+
run: ./gradlew :app:assembleDebug -PabiFilters=arm64-v8a --no-daemon
85+
86+
- name: Upload APK
87+
uses: actions/upload-artifact@v4
88+
with:
89+
name: app-debug-arm64-v8a
90+
path: runtime/android/app/build/outputs/apk/debug/app-debug.apk
91+
if-no-files-found: error

.github/workflows/runtime.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
pull_request:
66
paths:
77
- "runtime/**"
8+
# Android has its own workflow; don't rebuild the desktop runtime for
9+
# Android-only changes (shared processor/utils/cmake still match above).
10+
- "!runtime/android/**"
11+
- "!runtime/bindings/android/**"
812
- ".github/workflows/runtime.yml"
913

1014
jobs:

runtime/CMakeLists.txt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ option(BUILD_TESTING "whether to build unit test" OFF)
99

1010
include(FetchContent)
1111
set(FETCHCONTENT_QUIET OFF)
12-
get_filename_component(fc_base "fc_base-${CMAKE_CXX_COMPILER_ID}" REALPATH BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
13-
set(FETCHCONTENT_BASE_DIR ${fc_base})
1412
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
1513

1614
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -25,14 +23,27 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
2523
set(CMAKE_MACOSX_RPATH 1)
2624
endif()
2725

26+
if(ANDROID)
27+
set(BUILD_SHARED_LIBS OFF)
28+
endif()
29+
2830
include(openfst)
31+
include(glog)
32+
include(gflags)
2933
include_directories(${PROJECT_SOURCE_DIR})
3034

3135
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
3236

3337
add_subdirectory(utils)
3438
add_subdirectory(processor)
35-
add_subdirectory(bin)
39+
40+
if(ANDROID)
41+
# JNI shared library; only meaningful when cross-compiling with the NDK.
42+
add_subdirectory(bindings/android)
43+
else()
44+
# Command-line tools are host-only; skip them for Android.
45+
add_subdirectory(bin)
46+
endif()
3647

3748
if(BUILD_TESTING)
3849
include(gtest)

runtime/CMakePresets.json

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"version": 10,
3+
"cmakeMinimumRequired": {
4+
"major": 3,
5+
"minor": 23,
6+
"patch": 0
7+
},
8+
"configurePresets": [
9+
{
10+
"name": "default",
11+
"binaryDir": "${sourceDir}/build/default.release",
12+
"cacheVariables": {
13+
"CMAKE_BUILD_TYPE": "Release"
14+
}
15+
},
16+
{
17+
"name": "android-base",
18+
"hidden": true,
19+
"generator": "Ninja",
20+
"cacheVariables": {
21+
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake",
22+
"ANDROID_PLATFORM": "android-21",
23+
"CMAKE_BUILD_TYPE": "Release"
24+
}
25+
},
26+
{
27+
"name": "android-armeabi-v7a",
28+
"inherits": "android-base",
29+
"binaryDir": "${sourceDir}/build/arm-linux-androideabi",
30+
"cacheVariables": {
31+
"ANDROID_ABI": "armeabi-v7a",
32+
"CMAKE_INSTALL_PREFIX": "${sourceDir}/android/app/src/main/jniLibs/armeabi-v7a"
33+
}
34+
},
35+
{
36+
"name": "android-arm64-v8a",
37+
"inherits": "android-base",
38+
"binaryDir": "${sourceDir}/build/aarch64-linux-android",
39+
"cacheVariables": {
40+
"ANDROID_ABI": "arm64-v8a",
41+
"CMAKE_INSTALL_PREFIX": "${sourceDir}/android/app/src/main/jniLibs/arm64-v8a"
42+
}
43+
},
44+
{
45+
"name": "android-x86",
46+
"inherits": "android-base",
47+
"binaryDir": "${sourceDir}/build/i686-linux-android",
48+
"cacheVariables": {
49+
"ANDROID_ABI": "x86",
50+
"CMAKE_INSTALL_PREFIX": "${sourceDir}/android/app/src/main/jniLibs/x86"
51+
}
52+
},
53+
{
54+
"name": "android-x86_64",
55+
"inherits": "android-base",
56+
"binaryDir": "${sourceDir}/build/x86_64-linux-android",
57+
"cacheVariables": {
58+
"ANDROID_ABI": "x86_64",
59+
"CMAKE_INSTALL_PREFIX": "${sourceDir}/android/app/src/main/jniLibs/x86_64"
60+
}
61+
}
62+
],
63+
"buildPresets": [
64+
{ "name": "android-armeabi-v7a", "configurePreset": "android-armeabi-v7a" },
65+
{ "name": "android-arm64-v8a", "configurePreset": "android-arm64-v8a" },
66+
{ "name": "android-x86", "configurePreset": "android-x86" },
67+
{ "name": "android-x86_64", "configurePreset": "android-x86_64" }
68+
]
69+
}

runtime/android/.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@
1010
.DS_Store
1111
/build
1212
/captures
13-
.externalNativeBuild
14-
.cxx
1513
local.properties
14+
app/src/main/jniLibs/

runtime/android/app/build.gradle

Lines changed: 33 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
plugins {
2-
id 'com.android.application'
2+
alias(libs.plugins.android.application)
33
}
44

5-
repositories {
6-
jcenter()
7-
maven {
8-
url "https://oss.sonatype.org/content/repositories/snapshots"
9-
}
5+
def nativeAbis() {
6+
return (project.findProperty('abiFilters') ?: 'armeabi-v7a,arm64-v8a,x86,x86_64')
7+
.split(',').collect { it.trim() }.findAll { it }
108
}
119

1210
android {
13-
lintOptions {
14-
abortOnError false
11+
namespace = "com.wenet.WeTextProcessing"
12+
lint {
13+
abortOnError = false
1514
}
1615
signingConfigs {
1716
release {
@@ -21,85 +20,49 @@ android {
2120
keyPassword '123456'
2221
}
2322
}
24-
packagingOptions {
25-
jniLibs {
26-
pickFirsts += ['lib/arm64-v8a/libc++_shared.so']
27-
}
28-
}
29-
configurations {
30-
extractForNativeBuild
31-
}
32-
compileSdkVersion 30
33-
buildToolsVersion "30.0.3"
23+
compileSdk = 34
3424

3525
defaultConfig {
36-
applicationId "com.mobvoi.WeTextProcessing"
37-
minSdkVersion 21
38-
targetSdkVersion 30
39-
versionCode 1
40-
versionName "1.0"
26+
applicationId = "com.wenet.WeTextProcessing"
27+
minSdk = 21
28+
targetSdk = 34
29+
versionCode = 1
30+
versionName = "1.0"
4131

42-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
43-
externalNativeBuild {
44-
cmake {
45-
targets "wetextprocessing"
46-
cppFlags "-std=c++14", "-DC10_USE_GLOG", "-DC10_USE_MINIMAL_GLOG", "-DANDROID", "-Wno-c++11-narrowing", "-fexceptions"
47-
}
48-
}
32+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
4933

50-
ndkVersion '21.1.6352462'
5134
ndk {
52-
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
35+
abiFilters.addAll(nativeAbis())
5336
}
5437
}
5538

5639
buildTypes {
5740
release {
58-
minifyEnabled false
59-
signingConfig signingConfigs.release
41+
minifyEnabled = false
42+
signingConfig = signingConfigs.release
6043
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
6144
}
6245
}
63-
externalNativeBuild {
64-
cmake {
65-
path "src/main/cpp/CMakeLists.txt"
66-
}
67-
}
6846
compileOptions {
69-
sourceCompatibility JavaVersion.VERSION_1_8
70-
targetCompatibility JavaVersion.VERSION_1_8
47+
sourceCompatibility = JavaVersion.VERSION_17
48+
targetCompatibility = JavaVersion.VERSION_17
7149
}
7250
}
7351

7452
dependencies {
75-
76-
implementation 'androidx.appcompat:appcompat:1.2.0'
77-
implementation 'com.google.android.material:material:1.2.1'
78-
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
79-
testImplementation 'junit:junit:4.+'
80-
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
81-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
82-
83-
implementation 'com.github.pengzhendong:wenet-openfst-android:1.0.2'
84-
extractForNativeBuild 'com.github.pengzhendong:wenet-openfst-android:1.0.2'
53+
implementation libs.androidx.appcompat
54+
implementation libs.material
55+
implementation libs.androidx.constraintlayout
56+
testImplementation libs.junit
57+
androidTestImplementation libs.androidx.junit
58+
androidTestImplementation libs.androidx.espresso.core
8559
}
8660

87-
task extractAARForNativeBuild {
88-
doLast {
89-
configurations.extractForNativeBuild.files.each {
90-
def file = it.absoluteFile
91-
copy {
92-
from zipTree(file)
93-
into "$buildDir/$file.name"
94-
include "headers/**"
95-
include "jni/**"
96-
}
97-
}
98-
}
99-
}
100-
101-
tasks.whenTaskAdded { task ->
102-
if (task.name.contains('externalNativeBuild')) {
103-
task.dependsOn(extractAARForNativeBuild)
104-
}
105-
}
61+
// Native libs are NOT built by Gradle. Build them beforehand with CMake presets
62+
// (from repo root) so the .so files exist in app/src/main/jniLibs/<abi>/:
63+
// export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/<version>
64+
// cd runtime
65+
// cmake --preset android-arm64-v8a
66+
// cmake --build --preset android-arm64-v8a
67+
// cmake --install build/aarch64-linux-android --component jni
68+
// Gradle only packages whatever is already present under jniLibs.

runtime/android/app/src/androidTest/java/com/mobvoi/WeTextProcessing/ExampleInstrumentedTest.java renamed to runtime/android/app/src/androidTest/java/com/wenet/WeTextProcessing/ExampleInstrumentedTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mobvoi.WeTextProcessing;
1+
package com.wenet.WeTextProcessing;
22

33
import android.content.Context;
44

@@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
2121
public void useAppContext() {
2222
// Context of the app under test.
2323
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24-
assertEquals("com.mobvoi.WeTextProcessing", appContext.getPackageName());
24+
assertEquals("com.wenet.WeTextProcessing", appContext.getPackageName());
2525
}
26-
}
26+
}

runtime/android/app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools"
4-
package="com.mobvoi.WeTextProcessing">
3+
xmlns:tools="http://schemas.android.com/tools">
54
<application
65
android:allowBackup="true"
76
android:icon="@mipmap/ic_launcher"
@@ -10,7 +9,9 @@
109
android:supportsRtl="true"
1110
tools:replace="android:theme"
1211
android:theme="@style/Theme.Wenet">
13-
<activity android:name=".MainActivity">
12+
<activity
13+
android:name=".MainActivity"
14+
android:exported="true">
1415
<intent-filter>
1516
<action android:name="android.intent.action.MAIN" />
1617

0 commit comments

Comments
 (0)