Skip to content

Commit

Permalink
🦄 refactor: Add lib loader
Browse files Browse the repository at this point in the history
  • Loading branch information
caoccao committed Mar 6, 2024
1 parent a176f20 commit 8114405
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 265 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/swc4j_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ jobs:
- name: Cargo Build and Test
run: |
cd rust
cargo test
cargo build -r
cargo test -r
deno run --allow-all ../scripts/ts/copy_swc4j_lib.ts -a ${{ matrix.arch }}
- name: Setup JDK 8
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ task<Exec>("buildJNIHeaders") {
"-d",
"$buildDir/generated/tmp/jni",
"src/main/java/com/caoccao/javet/swc4j/SWCNative.java",
"src/main/java/com/caoccao/javet/swc4j/SWCLibLoader.java",
"src/main/java/com/caoccao/javet/swc4j/utils/OSUtils.java",
"src/main/java/com/caoccao/javet/swc4j/utils/StringUtils.java",
)
Expand Down
4 changes: 0 additions & 4 deletions rust/src/jni/com_caoccao_javet_swc4j_SWCNative.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

281 changes: 281 additions & 0 deletions src/main/java/com/caoccao/javet/swc4j/SWCLibLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*
* Copyright (c) 2024. caoccao.com Sam Cao
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.caoccao.javet.swc4j;

import com.caoccao.javet.swc4j.utils.OSUtils;
import com.caoccao.javet.swc4j.utils.StringUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.text.MessageFormat;

final class SWCLibLoader {
private static final String ANDROID_ABI_ARM = "armeabi-v7a";
private static final String ANDROID_ABI_ARM64 = "arm64-v8a";
private static final String ANDROID_ABI_X86 = "x86";
private static final String ANDROID_ABI_X86_64 = "x86_64";
private static final String ARCH_ARM = "arm";
private static final String ARCH_ARM64 = "arm64";
private static final String ARCH_X86 = "x86";
private static final String ARCH_X86_64 = "x86_64";
private static final int BUFFER_LENGTH = 4096;
private static final String CHMOD = "chmod";
private static final String LIB_FILE_EXTENSION_ANDROID = "so";
private static final String LIB_FILE_EXTENSION_LINUX = "so";
private static final String LIB_FILE_EXTENSION_MACOS = "dylib";
private static final String LIB_FILE_EXTENSION_WINDOWS = "dll";
private static final String LIB_FILE_NAME_FORMAT = "libswc4j-{0}-{1}.v.{2}.{3}";
private static final String LIB_FILE_NAME_PREFIX = "lib";
private static final String LIB_NAME = "swc4j";
private static final String LIB_VERSION = "0.1.0";
private static final long MIN_LAST_MODIFIED_GAP_IN_MILLIS = 60L * 1000L; // 1 minute
private static final String OS_ANDROID = "android";
private static final String OS_LINUX = "linux";
private static final String OS_MACOS = "macos";
private static final String OS_WINDOWS = "windows";
private static final String RESOURCE_NAME_FORMAT = "/{0}";
private static final String XRR = "755";

SWCLibLoader() {
}

private void deployLibFile(String resourceFileName, File libFile) {
boolean isLibFileLocked = false;
if (libFile.exists() && libFile.canWrite()) {
try {
//noinspection ResultOfMethodCallIgnored
libFile.delete();
} catch (Throwable t) {
isLibFileLocked = true;
System.err.println(MessageFormat.format(
"Failed to delete {0} because it is locked.",
libFile.getAbsolutePath()));
}
}
if (!isLibFileLocked) {
byte[] buffer = new byte[BUFFER_LENGTH];
try (InputStream inputStream = SWCNative.class.getResourceAsStream(resourceFileName);
FileOutputStream outputStream = new FileOutputStream(libFile.getAbsolutePath())) {
if (inputStream != null) {
while (true) {
int length = inputStream.read(buffer);
if (length == -1) {
break;
}
outputStream.write(buffer, 0, length);
}
if (OSUtils.IS_LINUX || OSUtils.IS_MACOS || OSUtils.IS_ANDROID) {
try {
Runtime.getRuntime().exec(new String[]{CHMOD, XRR, libFile.getAbsolutePath()}).waitFor();
} catch (Throwable ignored) {
}
}
}
} catch (Throwable t) {
System.err.println(MessageFormat.format(
"Failed to write to {0} because it is locked.",
libFile.getAbsolutePath()));
}
}
}

private String getAndroidABI() {
if (OSUtils.IS_ANDROID) {
if (OSUtils.IS_ARM) {
return ANDROID_ABI_ARM;
} else if (OSUtils.IS_ARM64) {
return ANDROID_ABI_ARM64;
} else if (OSUtils.IS_X86) {
return ANDROID_ABI_X86;
} else if (OSUtils.IS_X86_64) {
return ANDROID_ABI_X86_64;
}
}
return null;
}

private String getFileExtension() {
if (OSUtils.IS_WINDOWS) {
return LIB_FILE_EXTENSION_WINDOWS;
} else if (OSUtils.IS_LINUX) {
return LIB_FILE_EXTENSION_LINUX;
} else if (OSUtils.IS_MACOS) {
return LIB_FILE_EXTENSION_MACOS;
} else if (OSUtils.IS_ANDROID) {
return LIB_FILE_EXTENSION_ANDROID;
}
return null;
}

private String getLibFileName() {
String fileExtension = getFileExtension();
String osName = getOSName();
if (fileExtension == null || osName == null || OSUtils.IS_ANDROID) {
throw new RuntimeException(MessageFormat.format("OS {0} is not supported", OSUtils.OS_NAME));
}
String osArch = getOSArch();
if (osArch == null) {
throw new RuntimeException(MessageFormat.format("Arch {0} is not supported", OSUtils.OS_ARCH));
}
return MessageFormat.format(
LIB_FILE_NAME_FORMAT,
osName,
osArch,
LIB_VERSION,
fileExtension);
}

private String getOSArch() {
if (OSUtils.IS_WINDOWS) {
return ARCH_X86_64;
} else if (OSUtils.IS_LINUX) {
return OSUtils.IS_ARM64 ? ARCH_ARM64 : ARCH_X86_64;
} else if (OSUtils.IS_MACOS) {
return OSUtils.IS_ARM64 ? ARCH_ARM64 : ARCH_X86_64;
} else if (OSUtils.IS_ANDROID) {
if (OSUtils.IS_ARM) {
return ARCH_ARM;
} else if (OSUtils.IS_ARM64) {
return ARCH_ARM64;
} else if (OSUtils.IS_X86) {
return ARCH_X86;
} else if (OSUtils.IS_X86_64) {
return ARCH_X86_64;
}
}
return null;
}

private String getOSName() {
if (OSUtils.IS_WINDOWS) {
return OS_WINDOWS;
} else if (OSUtils.IS_LINUX) {
return OS_LINUX;
} else if (OSUtils.IS_MACOS) {
return OS_MACOS;
} else if (OSUtils.IS_ANDROID) {
return OS_ANDROID;
}
return null;
}

private String getResourceFileName() {
String resourceFileName = MessageFormat.format(RESOURCE_NAME_FORMAT, OSUtils.IS_ANDROID
? StringUtils.join("/", LIB_FILE_NAME_PREFIX, getAndroidABI(), getLibFileName())
: getLibFileName());
if (SWCNative.class.getResource(resourceFileName) == null) {
throw new RuntimeException(MessageFormat.format("Lib {0} is not found", resourceFileName));
}
return resourceFileName;
}

void load() {
String libFilePath = null;
try {
File libPath = new File(OSUtils.TEMP_DIRECTORY, LIB_NAME);
purge(libPath);
File rootLibPath;
if (OSUtils.IS_ANDROID) {
rootLibPath = libPath;
} else {
rootLibPath = new File(libPath, Long.toString(OSUtils.PROCESS_ID));
}
if (!rootLibPath.exists()) {
if (!rootLibPath.mkdirs()) {
throw new RuntimeException(
MessageFormat.format("Failed to create {0}.", rootLibPath.getAbsolutePath()));
}
}
String resourceFileName = getResourceFileName();
File libFile = new File(rootLibPath, getLibFileName()).getAbsoluteFile();
libFilePath = libFile.getAbsolutePath();
deployLibFile(resourceFileName, libFile);
System.load(libFilePath);
} catch (Throwable t) {
t.printStackTrace(System.err);
throw new RuntimeException(MessageFormat.format("Failed to load {0}", libFilePath));
}
}

private void purge(File rootLibPath) {
try {
if (rootLibPath.exists()) {
if (rootLibPath.isDirectory()) {
File[] files = rootLibPath.listFiles();
if (files != null && files.length > 0) {
for (File libFileOrPath : files) {
if (libFileOrPath.lastModified() + MIN_LAST_MODIFIED_GAP_IN_MILLIS > System.currentTimeMillis()) {
continue;
}
boolean toBeDeleted = false;
if (libFileOrPath.isDirectory()) {
try {
File[] libFiles = libFileOrPath.listFiles();
if (libFiles != null && libFiles.length > 0) {
for (File libFile : libFiles) {
if (libFile.delete()) {
System.out.println(MessageFormat.format(
"Deleted {0}.",
libFile.getAbsolutePath()));
} else {
System.out.println(MessageFormat.format(
"{0} is locked.",
libFile.getAbsolutePath()));
toBeDeleted = true;
break;
}
}
} else {
toBeDeleted = true;
}
} catch (Throwable t) {
System.err.println(MessageFormat.format(
"Failed to delete {0}.",
libFileOrPath.getAbsolutePath()));
}
} else if (libFileOrPath.isFile()) {
toBeDeleted = true;
}
if (toBeDeleted) {
if (libFileOrPath.delete()) {
System.out.println(MessageFormat.format(
"Deleted {0}.",
libFileOrPath.getAbsolutePath()));
} else {
System.out.println(MessageFormat.format(
"{0} is locked.",
libFileOrPath.getAbsolutePath()));
}
}
}
}
} else {
if (!rootLibPath.delete()) {
System.err.println(MessageFormat.format(
"Failed to delete {0}.",
rootLibPath.getAbsolutePath()));
}
}
}
} catch (Throwable t) {
System.err.println(MessageFormat.format(
"Failed to clean up {0}.",
rootLibPath.getAbsolutePath()));
}
}
}
Loading

0 comments on commit 8114405

Please sign in to comment.