From 81144058758bfcf529216d2d9d5139cbcd56ffc4 Mon Sep 17 00:00:00 2001 From: Sam Cao Date: Wed, 6 Mar 2024 16:03:59 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20refactor:=20Add=20lib=20loader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/swc4j_build.yml | 2 +- build.gradle.kts | 1 + .../jni/com_caoccao_javet_swc4j_SWCNative.h | 4 - .../com/caoccao/javet/swc4j/SWCLibLoader.java | 281 ++++++++++++++++++ .../com/caoccao/javet/swc4j/SWCNative.java | 260 +--------------- .../caoccao/javet/swc4j/TestSWCNative.java | 2 +- 6 files changed, 285 insertions(+), 265 deletions(-) create mode 100644 src/main/java/com/caoccao/javet/swc4j/SWCLibLoader.java diff --git a/.github/workflows/swc4j_build.yml b/.github/workflows/swc4j_build.yml index 11f5a903..0dcae13c 100644 --- a/.github/workflows/swc4j_build.yml +++ b/.github/workflows/swc4j_build.yml @@ -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 diff --git a/build.gradle.kts b/build.gradle.kts index ec8e8926..e0c9037e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -108,6 +108,7 @@ task("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", ) diff --git a/rust/src/jni/com_caoccao_javet_swc4j_SWCNative.h b/rust/src/jni/com_caoccao_javet_swc4j_SWCNative.h index abfea2c1..0f582e7d 100644 --- a/rust/src/jni/com_caoccao_javet_swc4j_SWCNative.h +++ b/rust/src/jni/com_caoccao_javet_swc4j_SWCNative.h @@ -7,10 +7,6 @@ #ifdef __cplusplus extern "C" { #endif -#undef com_caoccao_javet_swc4j_SWCNative_BUFFER_LENGTH -#define com_caoccao_javet_swc4j_SWCNative_BUFFER_LENGTH 4096L -#undef com_caoccao_javet_swc4j_SWCNative_MIN_LAST_MODIFIED_GAP_IN_MILLIS -#define com_caoccao_javet_swc4j_SWCNative_MIN_LAST_MODIFIED_GAP_IN_MILLIS 60000i64 /* * Class: com_caoccao_javet_swc4j_SWCNative * Method: getVersion diff --git a/src/main/java/com/caoccao/javet/swc4j/SWCLibLoader.java b/src/main/java/com/caoccao/javet/swc4j/SWCLibLoader.java new file mode 100644 index 00000000..d27e7b55 --- /dev/null +++ b/src/main/java/com/caoccao/javet/swc4j/SWCLibLoader.java @@ -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())); + } + } +} diff --git a/src/main/java/com/caoccao/javet/swc4j/SWCNative.java b/src/main/java/com/caoccao/javet/swc4j/SWCNative.java index 8dd5b34d..4e151f8e 100644 --- a/src/main/java/com/caoccao/javet/swc4j/SWCNative.java +++ b/src/main/java/com/caoccao/javet/swc4j/SWCNative.java @@ -16,269 +16,11 @@ 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 SWCNative { - public static final String LIB_NAME = "swc4j"; - public static final String LIB_VERSION = "0.1.0"; - 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 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"; static { - load(); - } - - private static 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 static 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 static 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 static 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 static 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 static 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 static 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; + new SWCLibLoader().load(); } public static native String getVersion(); - - private static 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 static 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())); - } - } } diff --git a/src/test/java/com/caoccao/javet/swc4j/TestSWCNative.java b/src/test/java/com/caoccao/javet/swc4j/TestSWCNative.java index 4eb33f6e..3132f804 100644 --- a/src/test/java/com/caoccao/javet/swc4j/TestSWCNative.java +++ b/src/test/java/com/caoccao/javet/swc4j/TestSWCNative.java @@ -23,6 +23,6 @@ public class TestSWCNative { @Test public void testGetVersion() { - assertEquals(SWCNative.LIB_VERSION, SWCNative.getVersion()); + assertEquals("0.1.0", SWCNative.getVersion()); } }