|
1 | 1 | package org.duckdb; |
2 | 2 |
|
3 | | -import java.io.File; |
| 3 | +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; |
| 4 | + |
4 | 5 | import java.io.IOException; |
5 | 6 | import java.io.InputStream; |
6 | 7 | import java.math.BigDecimal; |
| 8 | +import java.net.URI; |
7 | 9 | import java.net.URL; |
8 | 10 | import java.nio.ByteBuffer; |
9 | 11 | import java.nio.file.Files; |
10 | 12 | import java.nio.file.Path; |
11 | 13 | import java.nio.file.Paths; |
12 | | -import java.nio.file.StandardCopyOption; |
| 14 | +import java.security.CodeSource; |
| 15 | +import java.security.ProtectionDomain; |
13 | 16 | import java.sql.SQLException; |
14 | 17 | import java.util.Properties; |
15 | 18 |
|
16 | 19 | final class DuckDBNative { |
| 20 | + |
| 21 | + private static final String ARCH_X86_64 = "amd64"; |
| 22 | + private static final String ARCH_AARCH64 = "arm64"; |
| 23 | + private static final String ARCH_UNIVERSAL = "universal"; |
| 24 | + |
| 25 | + private static final String OS_WINDOWS = "windows"; |
| 26 | + private static final String OS_MACOS = "osx"; |
| 27 | + private static final String OS_LINUX = "linux"; |
| 28 | + |
17 | 29 | static { |
18 | 30 | try { |
19 | | - String os_name = ""; |
20 | | - String os_arch; |
21 | | - String os_name_detect = System.getProperty("os.name").toLowerCase().trim(); |
22 | | - String os_arch_detect = System.getProperty("os.arch").toLowerCase().trim(); |
23 | | - switch (os_arch_detect) { |
24 | | - case "x86_64": |
25 | | - case "amd64": |
26 | | - os_arch = "amd64"; |
27 | | - break; |
28 | | - case "aarch64": |
29 | | - case "arm64": |
30 | | - os_arch = "arm64"; |
31 | | - break; |
32 | | - case "i386": |
33 | | - os_arch = "i386"; |
34 | | - break; |
35 | | - default: |
36 | | - throw new IllegalStateException("Unsupported system architecture"); |
37 | | - } |
38 | | - if (os_name_detect.startsWith("windows")) { |
39 | | - os_name = "windows"; |
40 | | - } else if (os_name_detect.startsWith("mac")) { |
41 | | - os_name = "osx"; |
42 | | - os_arch = "universal"; |
43 | | - } else if (os_name_detect.startsWith("linux")) { |
44 | | - os_name = "linux"; |
45 | | - } |
46 | | - String lib_res_name = "/libduckdb_java.so" |
47 | | - + "_" + os_name + "_" + os_arch; |
48 | | - |
49 | | - Path lib_file = Files.createTempFile("libduckdb_java", ".so"); |
50 | | - URL lib_res = DuckDBNative.class.getResource(lib_res_name); |
51 | | - if (lib_res == null) { |
52 | | - System.load(Paths.get("build/debug", lib_res_name).normalize().toAbsolutePath().toString()); |
53 | | - } else { |
54 | | - try (final InputStream lib_res_input_stream = lib_res.openStream()) { |
55 | | - Files.copy(lib_res_input_stream, lib_file, StandardCopyOption.REPLACE_EXISTING); |
56 | | - } |
57 | | - new File(lib_file.toString()).deleteOnExit(); |
58 | | - System.load(lib_file.toAbsolutePath().toString()); |
59 | | - } |
60 | | - } catch (IOException e) { |
| 31 | + loadNativeLibrary(); |
| 32 | + } catch (Exception e) { |
61 | 33 | throw new RuntimeException(e); |
62 | 34 | } |
63 | 35 | } |
| 36 | + |
| 37 | + private static void loadNativeLibrary() throws Exception { |
| 38 | + String libName = nativeLibName(); |
| 39 | + URL libRes = DuckDBNative.class.getResource("/" + libName); |
| 40 | + |
| 41 | + // The current JAR has a native library bundled, in this case we unpack and load it. |
| 42 | + // There is no fallback if the unpacking or loading fails. We expect that only |
| 43 | + // the '-nolib' JAR can be used with external native lib. |
| 44 | + if (null != libRes) { |
| 45 | + unpackAndLoad(libRes); |
| 46 | + return; |
| 47 | + } |
| 48 | + |
| 49 | + // External native library must be loaded, first we try to load it from the |
| 50 | + // same directory where this JAR resides. |
| 51 | + Path dir = currentJarDir(); |
| 52 | + if (null != dir) { |
| 53 | + Path libPath = dir.resolve(libName); |
| 54 | + if (Files.exists(libPath)) { |
| 55 | + // Native lib is found next to this JAR, we expect it to be loadable |
| 56 | + // and do NOT fall back to load-by-name if the loading fails. |
| 57 | + System.load(libPath.toAbsolutePath().toString()); |
| 58 | + return; |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + // There is no native library next to the JAR file, so we try to load it by name. |
| 63 | + System.loadLibrary("duckdb_java"); |
| 64 | + } |
| 65 | + |
| 66 | + private static String cpuArch() throws SQLException { |
| 67 | + String prop = System.getProperty("os.arch").toLowerCase().trim(); |
| 68 | + switch (prop) { |
| 69 | + case "x86_64": |
| 70 | + case "amd64": |
| 71 | + return ARCH_X86_64; |
| 72 | + case "aarch64": |
| 73 | + case "arm64": |
| 74 | + return ARCH_AARCH64; |
| 75 | + default: |
| 76 | + throw new SQLException("Unsupported system architecture: '" + prop + "'"); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + static String osName() throws SQLException { |
| 81 | + String prop = System.getProperty("os.name").toLowerCase().trim(); |
| 82 | + if (prop.startsWith("windows")) { |
| 83 | + return OS_WINDOWS; |
| 84 | + } else if (prop.startsWith("mac")) { |
| 85 | + return OS_MACOS; |
| 86 | + } else if (prop.startsWith("linux")) { |
| 87 | + return OS_LINUX; |
| 88 | + } else { |
| 89 | + throw new SQLException("Unsupported OS: '" + prop + "'"); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + static String nativeLibName() throws SQLException { |
| 94 | + String os = osName(); |
| 95 | + final String arch; |
| 96 | + if (OS_MACOS.equals(os)) { |
| 97 | + arch = ARCH_UNIVERSAL; |
| 98 | + } else { |
| 99 | + arch = cpuArch(); |
| 100 | + } |
| 101 | + return "libduckdb_java.so_" + os + "_" + arch; |
| 102 | + } |
| 103 | + |
| 104 | + static Path currentJarDir() { |
| 105 | + try { |
| 106 | + ProtectionDomain pd = DuckDBNative.class.getProtectionDomain(); |
| 107 | + CodeSource cs = pd.getCodeSource(); |
| 108 | + URL loc = cs.getLocation(); |
| 109 | + URI uri = loc.toURI(); |
| 110 | + Path jarPath = Paths.get(uri); |
| 111 | + Path dirPath = jarPath.getParent(); |
| 112 | + return dirPath.toRealPath(); |
| 113 | + } catch (Exception e) { |
| 114 | + return null; |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + private static void unpackAndLoad(URL nativeLibRes) throws IOException { |
| 119 | + Path tmpFile = Files.createTempFile("libduckdb_java", ".so"); |
| 120 | + try (InputStream is = nativeLibRes.openStream()) { |
| 121 | + Files.copy(is, tmpFile, REPLACE_EXISTING); |
| 122 | + } |
| 123 | + tmpFile.toFile().deleteOnExit(); |
| 124 | + System.load(tmpFile.toAbsolutePath().toString()); |
| 125 | + } |
| 126 | + |
64 | 127 | // We use zero-length ByteBuffer-s as a hacky but cheap way to pass C++ pointers |
65 | 128 | // back and forth |
66 | 129 |
|
|
0 commit comments