Fangjun Kuang
Committed by GitHub

Simplify the usage of our non-Android Java API (#2533)

This PR simplifies the usage of the non-Android Java API by providing platform-specific JAR files that include native shared libraries, eliminating the need for users to manually manage native dependencies.

- Refactored LibraryUtils.java to support multiple library loading methods including extracting from JAR resources
- Added build infrastructure to create platform-specific native library JAR files
- Introduced debug capabilities and improved error handling for library loading
  1 +name: jar
  2 +
  3 +on:
  4 + push:
  5 + branches:
  6 + - refactor-jar
  7 + tags:
  8 + - 'v[0-9]+.[0-9]+.[0-9]+*'
  9 +
  10 + workflow_dispatch:
  11 +
  12 +concurrency:
  13 + group: jar-${{ github.ref }}
  14 + cancel-in-progress: true
  15 +
  16 +permissions:
  17 + contents: write
  18 +jobs:
  19 + jar:
  20 + runs-on: ${{ matrix.os }}
  21 + name: ${{ matrix.os }} ${{ matrix.arch }}
  22 + strategy:
  23 + fail-fast: false
  24 + matrix:
  25 + include:
  26 + - os: ubuntu-24.04-arm
  27 + arch: "arm64"
  28 +
  29 + - os: ubuntu-latest
  30 + arch: "x64"
  31 +
  32 + - os: macos-latest
  33 + arch: "arm64"
  34 +
  35 + - os: macos-13
  36 + arch: "x64"
  37 +
  38 + - os: windows-latest
  39 + arch: "x64"
  40 +
  41 + steps:
  42 + - uses: actions/checkout@v4
  43 + with:
  44 + fetch-depth: 0
  45 +
  46 + - uses: actions/setup-java@v4
  47 + with:
  48 + distribution: 'temurin' # See 'Supported distributions' for available options
  49 + java-version: '21'
  50 +
  51 + - name: Show java version
  52 + shell: bash
  53 + run: |
  54 + java --version
  55 +
  56 + - name: Download libs ${{ matrix.os }} ${{ matrix.arch }}
  57 + if: ${{ matrix.os == 'ubuntu-24.04-arm' && matrix.arch == 'arm64' }}
  58 + shell: bash
  59 + run: |
  60 + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  61 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/v$SHERPA_ONNX_VERSION/sherpa-onnx-v$SHERPA_ONNX_VERSION-linux-aarch64-jni.tar.bz2
  62 + tar xvf ./*.tar.bz2
  63 +
  64 + src=sherpa-onnx-v$SHERPA_ONNX_VERSION-linux-aarch64-jni
  65 + dst=sherpa-onnx/java-api/resources/sherpa-onnx/native/linux-aarch64
  66 +
  67 + mkdir -p $dst
  68 + cp -v $src/lib/libsherpa-onnx-jni.so $dst/
  69 + cp -v $src/lib/libonnxruntime.so $dst/
  70 +
  71 + ls -lh $dst
  72 + rm -rf $src*
  73 +
  74 + - name: Download libs ${{ matrix.os }} ${{ matrix.arch }}
  75 + if: ${{ matrix.os == 'ubuntu-latest' && matrix.arch == 'x64' }}
  76 + shell: bash
  77 + run: |
  78 + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  79 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/v$SHERPA_ONNX_VERSION/sherpa-onnx-v$SHERPA_ONNX_VERSION-linux-x64-jni.tar.bz2
  80 + tar xvf ./*.tar.bz2
  81 +
  82 + src=sherpa-onnx-v$SHERPA_ONNX_VERSION-linux-x64-jni
  83 + dst=sherpa-onnx/java-api/resources/sherpa-onnx/native/linux-x64
  84 +
  85 + mkdir -p $dst
  86 + cp -v $src/lib/libsherpa-onnx-jni.so $dst/
  87 + cp -v $src/lib/libonnxruntime.so $dst/
  88 +
  89 + ls -lh $dst
  90 + rm -rf $src*
  91 +
  92 + - name: Download libs ${{ matrix.os }} ${{ matrix.arch }}
  93 + if: ${{ matrix.os == 'macos-latest' && matrix.arch == 'arm64' }}
  94 + shell: bash
  95 + run: |
  96 + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  97 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/v$SHERPA_ONNX_VERSION/sherpa-onnx-v$SHERPA_ONNX_VERSION-osx-arm64-jni.tar.bz2
  98 + tar xvf ./*.tar.bz2
  99 +
  100 + src=sherpa-onnx-v$SHERPA_ONNX_VERSION-osx-arm64-jni
  101 + dst=sherpa-onnx/java-api/resources/sherpa-onnx/native/osx-aarch64
  102 +
  103 + mkdir -p $dst
  104 + cp -v $src/lib/libonnxruntime.1.17.1.dylib $dst/
  105 + cp -v $src/lib/libsherpa-onnx-jni.dylib $dst/
  106 +
  107 + ls -lh $dst
  108 + rm -rf $src*
  109 +
  110 + - name: Download libs ${{ matrix.os }} ${{ matrix.arch }}
  111 + if: ${{ matrix.os == 'macos-13' && matrix.arch == 'x64' }}
  112 + shell: bash
  113 + run: |
  114 + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  115 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/v$SHERPA_ONNX_VERSION/sherpa-onnx-v$SHERPA_ONNX_VERSION-osx-x86_64-jni.tar.bz2
  116 + tar xvf ./*.tar.bz2
  117 +
  118 + src=sherpa-onnx-v$SHERPA_ONNX_VERSION-osx-x86_64-jni
  119 + dst=sherpa-onnx/java-api/resources/sherpa-onnx/native/osx-x64
  120 +
  121 + mkdir -p $dst
  122 + cp -v $src/lib/libonnxruntime.1.17.1.dylib $dst/
  123 + cp -v $src/lib/libsherpa-onnx-jni.dylib $dst/
  124 +
  125 + ls -lh $dst
  126 + rm -rf $src*
  127 +
  128 + - name: Download libs ${{ matrix.os }} ${{ matrix.arch }}
  129 + if: ${{ matrix.os == 'windows-latest' && matrix.arch == 'x64' }}
  130 + shell: bash
  131 + run: |
  132 + SHERPA_ONNX_VERSION=$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  133 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/v$SHERPA_ONNX_VERSION/sherpa-onnx-v$SHERPA_ONNX_VERSION-win-x64-jni.tar.bz2
  134 + tar xvf ./*.tar.bz2
  135 +
  136 + src=sherpa-onnx-v$SHERPA_ONNX_VERSION-win-x64-jni
  137 + ls -lh $src
  138 + ls -lh $src/lib
  139 + dst=sherpa-onnx/java-api/resources/sherpa-onnx/native/win-x64
  140 +
  141 + mkdir -p $dst
  142 + cp -v $src/lib/onnxruntime.dll $dst/
  143 + cp -v $src/lib/sherpa-onnx-jni.dll $dst/
  144 +
  145 + ls -lh $dst
  146 + rm -rf $src*
  147 +
  148 + - name: Create java jar (source code)
  149 + shell: bash
  150 + run: |
  151 + cd sherpa-onnx/java-api
  152 + make
  153 +
  154 + ls -lh build
  155 +
  156 + - name: Create java jar (native lib)
  157 + shell: bash
  158 + run: |
  159 + SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  160 +
  161 + cd sherpa-onnx/java-api
  162 +
  163 + ls -lh resources/sherpa-onnx/native
  164 +
  165 + echo "--"
  166 +
  167 + ls -lh resources/sherpa-onnx/native/*/
  168 +
  169 + jar cfvm ./sherpa-onnx-native.jar MANIFEST.MF -C ./resources .
  170 +
  171 + ls -lh *.jar
  172 +
  173 + os=${{ matrix.os }}
  174 + arch=${{ matrix.arch }}
  175 +
  176 + if [[ $os == "ubuntu-24.04-arm" && $arch == "arm64" ]]; then
  177 + mv -v sherpa-onnx-native.jar sherpa-onnx-native-lib-linux-aarch64-$SHERPA_ONNX_VERSION.jar
  178 + elif [[ $os == "ubuntu-latest" && $arch == "x64" ]]; then
  179 + mv -v sherpa-onnx-native.jar sherpa-onnx-native-lib-linux-x64-$SHERPA_ONNX_VERSION.jar
  180 + elif [[ $os == "macos-latest" && $arch == "arm64" ]]; then
  181 + mv -v sherpa-onnx-native.jar sherpa-onnx-native-lib-osx-aarch64-$SHERPA_ONNX_VERSION.jar
  182 + elif [[ $os == "macos-13" && $arch == "x64" ]]; then
  183 + mv -v sherpa-onnx-native.jar sherpa-onnx-native-lib-osx-x64-$SHERPA_ONNX_VERSION.jar
  184 + elif [[ $os == "windows-latest" && $arch == "x64" ]]; then
  185 + mv -v sherpa-onnx-native.jar sherpa-onnx-native-lib-win-x64-$SHERPA_ONNX_VERSION.jar
  186 + else
  187 + echo "Unknown os $os with arch $arch"
  188 + fi
  189 +
  190 + - name: Show java jar (source code)
  191 + shell: bash
  192 + run: |
  193 + cd sherpa-onnx/java-api
  194 +
  195 + unzip -l build/sherpa-onnx.jar
  196 +
  197 + - name: Show java jar (native lib)
  198 + shell: bash
  199 + run: |
  200 + cd sherpa-onnx/java-api
  201 +
  202 + unzip -l sherpa-onnx*.jar
  203 +
  204 + - name: Release jar
  205 + if: github.repository_owner == 'k2-fsa' && github.event_name == 'push' && contains(github.ref, 'refs/tags/')
  206 + uses: svenstaro/upload-release-action@v2
  207 + with:
  208 + file_glob: true
  209 + overwrite: true
  210 + file: ./sherpa-onnx/java-api/sherpa-onnx-native-*.jar
  211 +
  212 + - name: Release jar
  213 + if: github.repository_owner == 'csukuangfj' && github.event_name == 'push' && contains(github.ref, 'refs/tags/')
  214 + uses: svenstaro/upload-release-action@v2
  215 + with:
  216 + file_glob: true
  217 + overwrite: true
  218 + file: ./sherpa-onnx/java-api/sherpa-onnx-native-*.jar
  219 + repo_name: k2-fsa/sherpa-onnx
  220 + repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }}
  221 + tag: v1.12.10
  222 +
  223 + - name: Test KittenTTS
  224 + shell: bash
  225 + run: |
  226 + SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
  227 +
  228 + os=${{ matrix.os }}
  229 + arch=${{ matrix.arch }}
  230 +
  231 + if [[ $os == "ubuntu-24.04-arm" && $arch == "arm64" ]]; then
  232 + native_jar=sherpa-onnx-native-lib-linux-aarch64-$SHERPA_ONNX_VERSION.jar
  233 + elif [[ $os == "ubuntu-latest" && $arch == "x64" ]]; then
  234 + native_jar=sherpa-onnx-native-lib-linux-x64-$SHERPA_ONNX_VERSION.jar
  235 + elif [[ $os == "macos-latest" && $arch == "arm64" ]]; then
  236 + native_jar=sherpa-onnx-native-lib-osx-aarch64-$SHERPA_ONNX_VERSION.jar
  237 + elif [[ $os == "macos-13" && $arch == "x64" ]]; then
  238 + native_jar=sherpa-onnx-native-lib-osx-x64-$SHERPA_ONNX_VERSION.jar
  239 + elif [[ $os == "windows-latest" && $arch == "x64" ]]; then
  240 + native_jar=sherpa-onnx-native-lib-win-x64-$SHERPA_ONNX_VERSION.jar
  241 + else
  242 + echo "Unknown os $os with arch $arch"
  243 + fi
  244 +
  245 + echo "native_jar: $native_jar"
  246 + ls -lh sherpa-onnx/java-api/$native_jar
  247 +
  248 + if [[ ${{ matrix.os }} == "windows-latest" ]]; then
  249 + SEP=";"
  250 + else
  251 + SEP=":"
  252 + fi
  253 + cd java-api-examples
  254 +
  255 + curl -SL -O https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/kitten-nano-en-v0_1-fp16.tar.bz2
  256 + tar xf kitten-nano-en-v0_1-fp16.tar.bz2
  257 + rm kitten-nano-en-v0_1-fp16.tar.bz2
  258 +
  259 + java \
  260 + -cp "../sherpa-onnx/java-api/build/sherpa-onnx.jar${SEP}../sherpa-onnx/java-api/$native_jar" \
  261 + NonStreamingTtsKittenEn.java
@@ -20,7 +20,7 @@ jobs: @@ -20,7 +20,7 @@ jobs:
20 fail-fast: false 20 fail-fast: false
21 matrix: 21 matrix:
22 os: [ubuntu-latest] 22 os: [ubuntu-latest]
23 - java-version: ['8', '11', '16', '17', '18', '19', '20', '21', '22', '23', '24'] 23 + java-version: ['24']
24 24
25 steps: 25 steps:
26 - uses: actions/checkout@v4 26 - uses: actions/checkout@v4
@@ -46,7 +46,6 @@ jobs: @@ -46,7 +46,6 @@ jobs:
46 du -h -d1 . 46 du -h -d1 .
47 47
48 - name: Build jar ${{ matrix.java-version }} 48 - name: Build jar ${{ matrix.java-version }}
49 - if: matrix.java-version == '23'  
50 shell: bash 49 shell: bash
51 run: | 50 run: |
52 SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2) 51 SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)
@@ -57,17 +56,6 @@ jobs: @@ -57,17 +56,6 @@ jobs:
57 cd ../.. 56 cd ../..
58 ls -lh *.jar 57 ls -lh *.jar
59 58
60 - - name: Build jar ${{ matrix.java-version }}  
61 - shell: bash  
62 - run: |  
63 - SHERPA_ONNX_VERSION=v$(grep "SHERPA_ONNX_VERSION" ./CMakeLists.txt | cut -d " " -f 2 | cut -d '"' -f 2)  
64 - cd sherpa-onnx/java-api  
65 - make  
66 - ls -lh build/  
67 - cp build/sherpa-onnx.jar ../../sherpa-onnx-$SHERPA_ONNX_VERSION-java${{ matrix.java-version }}.jar  
68 - cd ../..  
69 - ls -lh *.jar  
70 -  
71 - uses: actions/upload-artifact@v4 59 - uses: actions/upload-artifact@v4
72 with: 60 with:
73 name: release-jni-linux-jar-${{ matrix.java-version }} 61 name: release-jni-linux-jar-${{ matrix.java-version }}
@@ -80,12 +68,11 @@ jobs: @@ -80,12 +68,11 @@ jobs:
80 file_glob: true 68 file_glob: true
81 overwrite: true 69 overwrite: true
82 file: ./*.jar 70 file: ./*.jar
83 - # repo_name: k2-fsa/sherpa-onnx  
84 - # repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }}  
85 - # tag: v1.12.1 71 + repo_name: k2-fsa/sherpa-onnx
  72 + repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }}
  73 + tag: v1.12.10
86 74
87 - name: Build sherpa-onnx 75 - name: Build sherpa-onnx
88 - if: matrix.java-version == '23'  
89 uses: addnab/docker-run-action@v3 76 uses: addnab/docker-run-action@v3
90 with: 77 with:
91 image: quay.io/pypa/manylinux2014_x86_64 78 image: quay.io/pypa/manylinux2014_x86_64
@@ -151,7 +138,6 @@ jobs: @@ -151,7 +138,6 @@ jobs:
151 ls -lh install/bin 138 ls -lh install/bin
152 139
153 - name: Display dependencies of sherpa-onnx for linux 140 - name: Display dependencies of sherpa-onnx for linux
154 - if: matrix.java-version == '23'  
155 shell: bash 141 shell: bash
156 run: | 142 run: |
157 du -h -d1 . 143 du -h -d1 .
@@ -170,13 +156,11 @@ jobs: @@ -170,13 +156,11 @@ jobs:
170 readelf -d build/bin/sherpa-onnx 156 readelf -d build/bin/sherpa-onnx
171 157
172 - uses: actions/upload-artifact@v4 158 - uses: actions/upload-artifact@v4
173 - if: matrix.java-version == '23'  
174 with: 159 with:
175 name: release-jni-linux-${{ matrix.java-version }} 160 name: release-jni-linux-${{ matrix.java-version }}
176 path: build/install/* 161 path: build/install/*
177 162
178 - name: Copy files 163 - name: Copy files
179 - if: matrix.java-version == '23'  
180 shell: bash 164 shell: bash
181 run: | 165 run: |
182 du -h -d1 . 166 du -h -d1 .
@@ -194,8 +178,19 @@ jobs: @@ -194,8 +178,19 @@ jobs:
194 tar cjvf ${dst}.tar.bz2 $dst 178 tar cjvf ${dst}.tar.bz2 $dst
195 du -h -d1 . 179 du -h -d1 .
196 180
  181 + - name: Release pre-compiled binaries and libs for linux x64
  182 + if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && github.event_name == 'push' && contains(github.ref, 'refs/tags/')
  183 + uses: svenstaro/upload-release-action@v2
  184 + with:
  185 + file_glob: true
  186 + overwrite: true
  187 + file: sherpa-onnx-*.tar.bz2
  188 + # repo_name: k2-fsa/sherpa-onnx
  189 + # repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }}
  190 + # tag: v1.12.10
  191 +
197 - name: Publish to huggingface 192 - name: Publish to huggingface
198 - if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && matrix.java-version == '23' 193 + if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
199 env: 194 env:
200 HF_TOKEN: ${{ secrets.HF_TOKEN }} 195 HF_TOKEN: ${{ secrets.HF_TOKEN }}
201 uses: nick-fields/retry@v3 196 uses: nick-fields/retry@v3
@@ -215,6 +210,7 @@ jobs: @@ -215,6 +210,7 @@ jobs:
215 cd huggingface 210 cd huggingface
216 dst=jni/$SHERPA_ONNX_VERSION 211 dst=jni/$SHERPA_ONNX_VERSION
217 mkdir -p $dst 212 mkdir -p $dst
  213 + git lfs track "*.jar"
218 214
219 cp -v ../sherpa-onnx-*.tar.bz2 $dst/ 215 cp -v ../sherpa-onnx-*.tar.bz2 $dst/
220 cp -v ../*.jar $dst/ 216 cp -v ../*.jar $dst/
@@ -227,14 +223,3 @@ jobs: @@ -227,14 +223,3 @@ jobs:
227 git commit -m "add more files" 223 git commit -m "add more files"
228 224
229 git push https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-libs main 225 git push https://csukuangfj:$HF_TOKEN@huggingface.co/csukuangfj/sherpa-onnx-libs main
230 -  
231 - - name: Release pre-compiled binaries and libs for linux x64  
232 - if: (github.repository_owner == 'csukuangfj' || github.repository_owner == 'k2-fsa') && github.event_name == 'push' && contains(github.ref, 'refs/tags/') && matrix.java-version == '23'  
233 - uses: svenstaro/upload-release-action@v2  
234 - with:  
235 - file_glob: true  
236 - overwrite: true  
237 - file: sherpa-onnx-*.tar.bz2  
238 - # repo_name: k2-fsa/sherpa-onnx  
239 - # repo_token: ${{ secrets.UPLOAD_GH_SHERPA_ONNX_TOKEN }}  
240 - # tag: v1.12.0  
@@ -147,3 +147,4 @@ dict @@ -147,3 +147,4 @@ dict
147 voices.bin 147 voices.bin
148 kitten-nano-en-v0_1-fp16 148 kitten-nano-en-v0_1-fp16
149 *.egg-info 149 *.egg-info
  150 +*.jar
@@ -6,6 +6,7 @@ import com.k2fsa.sherpa.onnx.*; @@ -6,6 +6,7 @@ import com.k2fsa.sherpa.onnx.*;
6 6
7 public class NonStreamingTtsKittenEn { 7 public class NonStreamingTtsKittenEn {
8 public static void main(String[] args) { 8 public static void main(String[] args) {
  9 + LibraryUtils.enableDebug();
9 // please visit 10 // please visit
10 // https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/kitten.html 11 // https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/kitten.html
11 // to download model files 12 // to download model files
  1 +Manifest-Version: 1.0
@@ -109,17 +109,28 @@ $(info -- java files $(java_files)) @@ -109,17 +109,28 @@ $(info -- java files $(java_files))
109 $(info --) 109 $(info --)
110 $(info -- class files $(class_files)) 110 $(info -- class files $(class_files))
111 111
112 -.phony: all clean 112 +.PHONY: all clean native
113 113
114 all: $(out_jar) 114 all: $(out_jar)
115 115
  116 +# macos x86_x64 -> osx-x64
  117 +# macos arm64 -> osx-aarch64
  118 +# linux x86_x64 -> linux-x64
  119 +# linux arm64 -> linux-aarch64
  120 +# windows x86_x64 -> win-x64
  121 +# windows arm64 -> win-aarch64
  122 +# windows x86 -> win-x86
  123 +native:
  124 + jar cfvm ./sherpa-onnx-native.jar MANIFEST.MF -C ./resources .
  125 +
116 $(out_jar): $(class_files) 126 $(out_jar): $(class_files)
117 # jar --create --verbose --file $(out_jar) -C $(out_dir) ./ 127 # jar --create --verbose --file $(out_jar) -C $(out_dir) ./
118 - jar cvf $(out_jar) -C $(out_dir) ./ 128 + # jar cvf $(out_jar) -C $(out_dir) ./
  129 + jar cfvm $@ MANIFEST.MF -C $(out_dir) .
119 130
120 clean: 131 clean:
121 $(RM) -rfv $(out_dir) 132 $(RM) -rfv $(out_dir)
122 133
123 $(class_files): $(out_dir)/$(package_dir)/%.class: src/main/java/$(package_dir)/%.java 134 $(class_files): $(out_dir)/$(package_dir)/%.class: src/main/java/$(package_dir)/%.java
124 mkdir -p build 135 mkdir -p build
125 - javac -d $(out_dir) -cp $(out_dir) $< 136 + javac --release 8 -Xlint:-options -d $(out_dir) -cp $(out_dir) $<
@@ -4,42 +4,170 @@ import java.io.File; @@ -4,42 +4,170 @@ import java.io.File;
4 import java.io.IOException; 4 import java.io.IOException;
5 import java.io.InputStream; 5 import java.io.InputStream;
6 import java.nio.file.Files; 6 import java.nio.file.Files;
  7 +import java.nio.file.Path;
7 import java.nio.file.StandardCopyOption; 8 import java.nio.file.StandardCopyOption;
8 import java.util.Locale; 9 import java.util.Locale;
  10 +import java.util.Objects;
  11 +
  12 +/*
  13 +# We support the following loading methods
  14 +
  15 +## Method 1 Specify the property sherpa_onnx.native.path
  16 +
  17 +We assume the path contains the libraries sherpa-onnx-jni and onnxruntime.
  18 +
  19 +java \
  20 + -Dsherpa_onnx.native.path=/Users/fangjun/sherpa-onnx/build/install/lib \
  21 + -cp /Users/fangjun/sherpa-onnx/sherpa-onnx/java-api/build/sherpa-onnx.jar
  22 + xxx.java
  23 +
  24 +## Method 2 Specify the native jar library
  25 +
  26 +java \
  27 + -cp /Users/fangjun/sherpa-onnx/sherpa-onnx/java-api/build/sherpa-onnx.jar:/path/to/sherpa-onnx-osx-x64.jar
  28 + xxx.java
  29 +
  30 +Note that you need to replace : in -cp with ; on windows.
  31 +
  32 +## Method 3 Specify the property java.library.path
  33 +
  34 +We assume the path contains the libraries sherpa-onnx-jni and onnxruntime.
  35 +
  36 +java \
  37 + -Djava.library.path=/Users/fangjun/sherpa-onnx/build/install/lib \
  38 + -cp /Users/fangjun/sherpa-onnx/sherpa-onnx/java-api/build/sherpa-onnx.jar
  39 + xxx.java
  40 +
  41 + */
9 42
10 public class LibraryUtils { 43 public class LibraryUtils {
11 // System property to override native library path 44 // System property to override native library path
12 private static final String NATIVE_PATH_PROP = "sherpa_onnx.native.path"; 45 private static final String NATIVE_PATH_PROP = "sherpa_onnx.native.path";
13 private static final String LIB_NAME = "sherpa-onnx-jni"; 46 private static final String LIB_NAME = "sherpa-onnx-jni";
14 47
  48 + private static boolean debug = false;
  49 +
  50 + private static String detectedOS;
  51 +
  52 + public static void enableDebug() {
  53 + debug = true;
  54 + }
  55 +
  56 + public static void disableDebug() {
  57 + debug = false;
  58 + }
  59 +
15 public static void load() { 60 public static void load() {
16 - String libFileName = System.mapLibraryName(LIB_NAME); 61 + // 1. Try to load from external directory specified by -Dsherpa_onnx.native.path if provided
  62 + if (loadFromSherpaOnnxNativePath()) {
  63 + return;
  64 + }
17 65
  66 + // 2. Load from resources contains in some jar file
18 try { 67 try {
19 - // 1. Try loading from external directory if provided 68 + if (loadFromResourceInJar()) {
  69 + return;
  70 + }
  71 + } catch (IOException e) {
  72 + // pass
  73 + }
  74 +
  75 + // 3. fallback to -Djava.library.path
  76 + // java -Djava.library.path=C:\mylibs;D:\otherlibs -cp sherpa-onnx.jar xxx.java
  77 + //
  78 + // It throws if it cannot load the lib sherpa-onnx-jni
  79 + System.loadLibrary(LIB_NAME);
  80 + }
  81 +
  82 + // You specify -Dsherpa_onnx.native.path=/path/to/some/dir
  83 + // where /path/to/some/dir contains the sherpa-onnx-jni and onnxruntime libs
  84 + private static boolean loadFromSherpaOnnxNativePath() {
  85 + String libFileName = System.mapLibraryName(LIB_NAME);
20 String nativePath = System.getProperty(NATIVE_PATH_PROP); 86 String nativePath = System.getProperty(NATIVE_PATH_PROP);
  87 +
21 if (nativePath != null) { 88 if (nativePath != null) {
22 File nativeDir = new File(nativePath); 89 File nativeDir = new File(nativePath);
23 File libInDir = new File(nativeDir, libFileName); 90 File libInDir = new File(nativeDir, libFileName);
24 if (nativeDir.isDirectory() && libInDir.exists()) { 91 if (nativeDir.isDirectory() && libInDir.exists()) {
25 - System.out.println("Loading native lib from external directory: " + libInDir.getAbsolutePath()); 92 + if (debug) {
  93 + System.out.printf("Loading from: %s\n", libInDir.getAbsolutePath());
  94 + }
  95 +
26 System.load(libInDir.getAbsolutePath()); 96 System.load(libInDir.getAbsolutePath());
27 - return; 97 + return true;
28 } 98 }
29 } 99 }
30 100
31 - // 2. Fallback to extracting and loading from the JAR  
32 - File libFile = init(libFileName);  
33 - System.out.println("Loading native lib from: " + libFile.getAbsolutePath());  
34 - System.load(libFile.getAbsolutePath());  
35 - } catch (RuntimeException ex) {  
36 - System.loadLibrary(LIB_NAME); 101 + if (debug) {
  102 + System.out.println("nativePath is null");
37 } 103 }
  104 +
  105 + return false;
38 } 106 }
39 107
40 - /* Computes and initializes OS_ARCH_STR (such as linux-x64) */  
41 - private static String initOsArch() {  
42 - String detectedOS = null; 108 + private static boolean loadFromResourceInJar() throws IOException {
  109 +
  110 + String libFileName = System.mapLibraryName(LIB_NAME);
  111 + String sherpaOnnxJniPath = "sherpa-onnx/native/" + getOsArch() + '/' + libFileName;
  112 +
  113 + Path tempDirectory = null;
  114 + try {
  115 +
  116 + if (!resourceExists(sherpaOnnxJniPath)) {
  117 + if (debug) {
  118 + System.out.printf("%s does not exist\n", sherpaOnnxJniPath);
  119 + }
  120 +
  121 + return false;
  122 + }
  123 +
  124 + tempDirectory = Files.createTempDirectory("sherpa-onnx-java");
  125 +
  126 + if (Objects.equals(detectedOS, "osx")) {
  127 + // for macos, we need to first load libonnxruntime.1.17.1.dylib
  128 + String onnxruntimePath = "sherpa-onnx/native/" + getOsArch() + '/' + "libonnxruntime.1.17.1.dylib";
  129 + if (!resourceExists(onnxruntimePath)) {
  130 + if (debug) {
  131 + System.out.printf("%s does not exist\n", onnxruntimePath);
  132 + }
  133 +
  134 + return false;
  135 + }
  136 +
  137 + File tempFile = tempDirectory.resolve("libonnxruntime.1.17.1.dylib").toFile();
  138 + extractResource(onnxruntimePath, tempFile);
  139 + System.load(tempFile.getAbsolutePath());
  140 + } else {
  141 + String onnxLibFileName = System.mapLibraryName("onnxruntime");
  142 + String onnxruntimePath = "sherpa-onnx/native/" + getOsArch() + '/' + onnxLibFileName;
  143 + if (!resourceExists(onnxruntimePath)) {
  144 + if (debug) {
  145 + System.out.printf("%s does not exist\n", onnxruntimePath);
  146 + }
  147 +
  148 + return false;
  149 + }
  150 +
  151 + File tempFile = tempDirectory.resolve(onnxLibFileName).toFile();
  152 + extractResource(onnxruntimePath, tempFile);
  153 + System.load(tempFile.getAbsolutePath());
  154 + }
  155 +
  156 + File tempFile = tempDirectory.resolve(libFileName).toFile();
  157 + extractResource(sherpaOnnxJniPath, tempFile);
  158 + System.load(tempFile.getAbsolutePath());
  159 + } finally {
  160 + if (tempDirectory != null) {
  161 + cleanUpTempDir(tempDirectory.toFile());
  162 + }
  163 + }
  164 +
  165 + return true;
  166 + }
  167 +
  168 + // this method is copied and modified from
  169 + // https://github.com/microsoft/onnxruntime/blob/main/java/src/main/java/ai/onnxruntime/OnnxRuntime.java#L118
  170 + private static String getOsArch() {
43 String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); 171 String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
44 if (os.contains("mac") || os.contains("darwin")) { 172 if (os.contains("mac") || os.contains("darwin")) {
45 detectedOS = "osx"; 173 detectedOS = "osx";
@@ -50,58 +178,60 @@ public class LibraryUtils { @@ -50,58 +178,60 @@ public class LibraryUtils {
50 } else { 178 } else {
51 throw new IllegalStateException("Unsupported os:" + os); 179 throw new IllegalStateException("Unsupported os:" + os);
52 } 180 }
53 - String detectedArch = null;  
54 - String arch = System.getProperty("os.arch", "generic").toLowerCase(Locale.ENGLISH); 181 +
  182 + String detectedArch;
  183 + String arch = System.getProperty("os.arch", "generic")
  184 + .toLowerCase(Locale.ENGLISH);
55 if (arch.startsWith("amd64") || arch.startsWith("x86_64")) { 185 if (arch.startsWith("amd64") || arch.startsWith("x86_64")) {
56 detectedArch = "x64"; 186 detectedArch = "x64";
57 } else if (arch.startsWith("x86")) { 187 } else if (arch.startsWith("x86")) {
58 // 32-bit x86 is not supported by the Java API 188 // 32-bit x86 is not supported by the Java API
59 detectedArch = "x86"; 189 detectedArch = "x86";
60 - } else if (arch.startsWith("aarch64")) { 190 + } else if (arch.startsWith("aarch64") || arch.startsWith("arm64")) {
61 detectedArch = "aarch64"; 191 detectedArch = "aarch64";
62 } else { 192 } else {
63 throw new IllegalStateException("Unsupported arch:" + arch); 193 throw new IllegalStateException("Unsupported arch:" + arch);
64 } 194 }
65 - return detectedOS + '-' + detectedArch;  
66 - }  
67 195
68 - private static File init(String libFileName) {  
69 - String osName = System.getProperty("os.name").toLowerCase();  
70 - String osArch = System.getProperty("os.arch").toLowerCase();  
71 - String userHome = System.getProperty("user.home");  
72 - System.out.printf("Detected OS=%s, ARCH=%s, HOME=%s%n", osName, osArch, userHome);  
73 -  
74 - String archName = initOsArch();  
75 -  
76 - // Prepare destination directory under ~/lib/<archName>/  
77 - String dstDir = userHome + File.separator + "lib" + File.separator + archName;  
78 - File libFile = new File(dstDir, libFileName);  
79 - File parentDir = libFile.getParentFile();  
80 - if (!parentDir.exists() && !parentDir.mkdirs()) {  
81 - throw new RuntimeException("Unable to create directory: " + parentDir); 196 + return detectedOS + '-' + detectedArch;
82 } 197 }
83 198
84 - // Extract the native library from JAR  
85 - extractResource("/native/" + archName + "/" + libFileName, libFile);  
86 - return libFile; 199 + private static void extractResource(String resourcePath, File destination) {
  200 + if (debug) {
  201 + System.out.printf("Copying from resource path %s to %s\n", resourcePath, destination.toPath());
87 } 202 }
88 203
89 - /**  
90 - * Copies a resource file from the jar to the specified destination.  
91 - *  
92 - * @param resourcePath The resource path inside the jar, e.g.:  
93 - * /native/linux_x64/libonnxruntime.so  
94 - * @param destination The destination file on disk  
95 - */  
96 - private static void extractResource(String resourcePath, File destination) {  
97 - try (InputStream in = LibraryUtils.class.getResourceAsStream(resourcePath)) { 204 + try (InputStream in = LibraryUtils.class.getClassLoader().getResourceAsStream(resourcePath)) {
98 if (in == null) { 205 if (in == null) {
99 throw new RuntimeException("Resource not found: " + resourcePath); 206 throw new RuntimeException("Resource not found: " + resourcePath);
100 } 207 }
101 Files.copy(in, destination.toPath(), StandardCopyOption.REPLACE_EXISTING); 208 Files.copy(in, destination.toPath(), StandardCopyOption.REPLACE_EXISTING);
102 } catch (IOException e) { 209 } catch (IOException e) {
103 - throw new RuntimeException("Failed to extract resource " + resourcePath + " to " + destination.getAbsolutePath(),  
104 - e); 210 + throw new RuntimeException("Failed to extract resource " + resourcePath + " to " + destination.getAbsolutePath(), e);
  211 + }
  212 + }
  213 +
  214 + // From ChatGPT:
  215 + // Class.getResourceAsStream(String path) behaves differently than ClassLoader
  216 + // - No leading slash → relative to the package of LibraryUtils
  217 + // - Leading slash → absolute path relative to classpath root
  218 + //
  219 + // ClassLoader.getResourceAsStream always uses absolute paths relative to classpath root,
  220 + // no leading slash needed
  221 +
  222 + private static boolean resourceExists(String path) {
  223 + return LibraryUtils.class.getClassLoader().getResource(path) != null;
  224 + }
  225 +
  226 + private static void cleanUpTempDir(File dir) {
  227 + if (!dir.exists()) return;
  228 +
  229 + File[] files = dir.listFiles();
  230 + if (files != null) {
  231 + for (File f : files) {
  232 + f.deleteOnExit(); // schedule each .so for deletion
  233 + }
105 } 234 }
  235 + dir.deleteOnExit(); // schedule the directory itself
106 } 236 }
107 } 237 }