-
Notifications
You must be signed in to change notification settings - Fork 314
Introducing NativeLoader #9625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing NativeLoader #9625
Changes from 30 commits
3ceefd3
bf8a301
67fc018
e59a283
77b5c42
972f5c3
8534a4d
cfb4285
63eefcd
814e4c1
621c6ab
d564cb5
7aadebc
ad2866f
7655ce0
92a0def
840d32a
34ca3b3
bf4f1fe
2b899aa
4b3ba71
8d6b2d0
3c12ae6
e0b7b85
ae4aad6
0e8c405
dadb822
fe7ae49
45a71f3
ccf73f0
d04985d
6b79e46
8d04747
065b3cf
6d66b39
79f0c89
a76f79d
00fa98a
6540109
d4743f1
0336fd0
8a747b2
7ac11d7
ef880c5
87800f0
eab2dbb
589a1d2
28d3578
65e47bf
940a69a
36a219b
8e3280a
17627f1
6b586ba
dcb5749
818a1be
8401635
25af560
fa0f8a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
plugins { | ||
`java-library` | ||
} | ||
|
||
apply(from = "$rootDir/gradle/java.gradle") | ||
|
||
dependencies { | ||
implementation(project(":components:environment")) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package datadog.nativeloader; | ||
|
||
import java.net.URL; | ||
import java.util.Objects; | ||
|
||
/** ClassLoaderResourcePathLocator locates library paths inside a {@link ClassLoader} */ | ||
public final class ClassLoaderResourcePathLocator implements PathLocator { | ||
private final ClassLoader classLoader; | ||
private final String baseResource; | ||
|
||
public ClassLoaderResourcePathLocator(final ClassLoader classLoader, final String baseResource) { | ||
this.classLoader = classLoader; | ||
this.baseResource = baseResource; | ||
} | ||
|
||
@Override | ||
public URL locate(String component, String path) { | ||
String fullPath = component == null ? "" : component; | ||
fullPath = this.baseResource == null ? fullPath : fullPath + "/" + this.baseResource; | ||
fullPath = fullPath.isEmpty() ? path : fullPath + "/" + path; | ||
|
||
return this.classLoader.getResource(fullPath); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(this.classLoader, this.baseResource); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (!(obj instanceof ClassLoaderResourcePathLocator)) return false; | ||
|
||
ClassLoaderResourcePathLocator that = (ClassLoaderResourcePathLocator) obj; | ||
return this.classLoader.equals(that.classLoader) | ||
&& Objects.equals(this.baseResource, that.baseResource); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package datadog.nativeloader; | ||
|
||
import java.net.URL; | ||
|
||
/** | ||
* FlatDirLibraryResolver - uses flat directories to provide more specific libraries to load | ||
* {os}-{arch}-{libc/musl} | ||
*/ | ||
public final class FlatDirLibraryResolver implements LibraryResolver { | ||
public static final FlatDirLibraryResolver INSTANCE = new FlatDirLibraryResolver(); | ||
|
||
private FlatDirLibraryResolver() {} | ||
|
||
@Override | ||
public final URL resolve( | ||
PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) | ||
throws Exception { | ||
PathLocatorHelper pathLocatorHelper = new PathLocatorHelper(libName, pathLocator); | ||
|
||
String libFileName = PathUtils.libFileName(platformSpec, libName); | ||
|
||
String osPath = PathUtils.osPartOf(platformSpec); | ||
String archPath = PathUtils.archPartOf(platformSpec); | ||
String libcPath = PathUtils.libcPartOf(platformSpec); | ||
|
||
URL url; | ||
String regularPath = osPath + "-" + archPath; | ||
|
||
if (libcPath != null) { | ||
String specializedPath = regularPath + "-" + libcPath; | ||
url = pathLocatorHelper.locate(component, specializedPath + "/" + libFileName); | ||
if (url != null) return url; | ||
} | ||
|
||
url = pathLocatorHelper.locate(component, regularPath + "/" + libFileName); | ||
if (url != null) return url; | ||
|
||
url = pathLocatorHelper.locate(component, osPath + "/" + libFileName); | ||
if (url != null) return url; | ||
|
||
// fallback to searching at top-level, mostly concession to good out-of-box behavior | ||
// with java.library.path | ||
url = pathLocatorHelper.locate(component, libFileName); | ||
if (url != null) return url; | ||
|
||
if (component != null) { | ||
url = pathLocatorHelper.locate(null, libFileName); | ||
if (url != null) return url; | ||
} | ||
|
||
pathLocatorHelper.tryThrow(); | ||
|
||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package datadog.nativeloader; | ||
|
||
import java.io.File; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.util.Arrays; | ||
|
||
/** LibDirBasedPathLocator locates libraries inside a list of library directories */ | ||
public final class LibDirBasedPathLocator implements PathLocator { | ||
private final File[] libDirs; | ||
|
||
public LibDirBasedPathLocator(File... libDirs) { | ||
this.libDirs = libDirs; | ||
} | ||
|
||
@Override | ||
public URL locate(String component, String path) { | ||
String fullPath = component == null ? path : component + "/" + path; | ||
|
||
for (File libDir : this.libDirs) { | ||
File libFile = new File(libDir, fullPath); | ||
if (libFile.exists()) return toUrl(libFile); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
@SuppressWarnings("deprecation") | ||
private static final URL toUrl(File file) { | ||
try { | ||
return file.toURL(); | ||
} catch (MalformedURLException e) { | ||
return null; | ||
} | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Arrays.hashCode(this.libDirs); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (!(obj instanceof LibDirBasedPathLocator)) return false; | ||
|
||
LibDirBasedPathLocator that = (LibDirBasedPathLocator) obj; | ||
return Arrays.equals(this.libDirs, that.libDirs); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package datadog.nativeloader; | ||
|
||
import java.io.File; | ||
|
||
/** | ||
* Represents a resolved library | ||
* | ||
* <ul> | ||
* <li>library may be preloaded - with no backing file | ||
* <li>regular file - that doesn't require clean-up | ||
* <li>temporary file - copying from another source - that does require clean-up | ||
* </ul> | ||
*/ | ||
public final class LibFile implements AutoCloseable { | ||
static final boolean NO_CLEAN_UP = false; | ||
static final boolean CLEAN_UP = true; | ||
|
||
static final LibFile preloaded(String libName) { | ||
return new LibFile(libName, null, NO_CLEAN_UP); | ||
} | ||
|
||
static final LibFile fromFile(String libName, File file) { | ||
return new LibFile(libName, file, NO_CLEAN_UP); | ||
} | ||
|
||
static final LibFile fromTempFile(String libName, File file) { | ||
return new LibFile(libName, file, CLEAN_UP); | ||
} | ||
|
||
final String libName; | ||
|
||
final File file; | ||
final boolean needsCleanup; | ||
|
||
LibFile(String libName, File file, boolean needsCleanup) { | ||
this.libName = libName; | ||
|
||
this.file = file; | ||
this.needsCleanup = needsCleanup; | ||
} | ||
|
||
/** Indicates if this library was "preloaded" */ | ||
public boolean isPreloaded() { | ||
return (this.file == null); | ||
} | ||
|
||
/** Loads the underlying library into the JVM */ | ||
public void load() throws LibraryLoadException { | ||
if (this.isPreloaded()) return; | ||
|
||
try { | ||
Runtime.getRuntime().load(this.getAbsolutePath()); | ||
} catch (Throwable t) { | ||
throw new LibraryLoadException(this.libName, t); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thought: Does it make sense to investigate later an FFM API version of the native part for post JDK22 users? This might, however, require to deal with Arena / Scope. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't really looked into it, but yeah, I could see that making sense at some point. |
||
} | ||
|
||
/** Provides the an absolute path to the library -- returns null for pre-loaded libraries */ | ||
public final String getAbsolutePath() { | ||
return this.file == null ? null : this.file.getAbsolutePath(); | ||
} | ||
|
||
/** Schedules clean-up of underlying file -- if the file is a temp file */ | ||
@Override | ||
public void close() { | ||
if (this.needsCleanup) { | ||
NativeLoader.delete(this.file); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package datadog.nativeloader; | ||
|
||
/** Exception raised when NativeLoader fails to resolve or load a library */ | ||
public class LibraryLoadException extends Exception { | ||
static final String UNSUPPORTED_OS = "Unsupported OS"; | ||
static final String UNSUPPORTED_ARCH = "Unsupported arch"; | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public LibraryLoadException(String libName) { | ||
super(message(libName)); | ||
} | ||
|
||
public LibraryLoadException(String libName, Throwable cause) { | ||
this(message(libName), cause.getMessage(), cause); | ||
} | ||
|
||
public LibraryLoadException(String libName, String message) { | ||
super(message(libName) + " - " + message); | ||
} | ||
|
||
public LibraryLoadException(String libName, String message, Throwable cause) { | ||
super(message(libName) + " - " + message, cause); | ||
} | ||
|
||
static final String message(String libName) { | ||
return "Unable to resolve library " + libName; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package datadog.nativeloader; | ||
|
||
import java.net.URL; | ||
|
||
/** | ||
* LibraryResolver encapsulates a library resolution strategy | ||
* | ||
* <p>The LibraryResolver should use the provided {@link PathLocator} to locate the desired | ||
* resources. The LibraryResolver may try multiple locations to find the best possible library to | ||
* use. | ||
*/ | ||
@FunctionalInterface | ||
public interface LibraryResolver { | ||
default boolean isPreloaded(PlatformSpec platform, String libName) { | ||
return false; | ||
} | ||
|
||
URL resolve(PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) | ||
throws Exception; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package datadog.nativeloader; | ||
|
||
import java.net.URL; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
public final class LibraryResolvers { | ||
dougqh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private LibraryResolvers() {} | ||
|
||
public static final LibraryResolver defaultLibraryResolver() { | ||
return flatDirs(); | ||
} | ||
|
||
public static final LibraryResolver withPreloaded( | ||
LibraryResolver baseResolver, String... preloadedLibNames) { | ||
dougqh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return withPreloaded(baseResolver, new HashSet<>(Arrays.asList(preloadedLibNames))); | ||
} | ||
|
||
public static final LibraryResolver withPreloaded( | ||
LibraryResolver baseResolver, Set<String> preloadedLibNames) { | ||
return new LibraryResolver() { | ||
@Override | ||
public boolean isPreloaded(PlatformSpec platform, String libName) { | ||
return preloadedLibNames.contains(libName); | ||
} | ||
|
||
@Override | ||
public URL resolve( | ||
PathLocator pathLocator, String component, PlatformSpec platformSpec, String libName) | ||
throws Exception { | ||
return baseResolver.resolve(pathLocator, component, platformSpec, libName); | ||
} | ||
}; | ||
} | ||
|
||
public static final LibraryResolver flatDirs() { | ||
return FlatDirLibraryResolver.INSTANCE; | ||
} | ||
|
||
public static final LibraryResolver nestedDirs() { | ||
return NestedDirLibraryResolver.INSTANCE; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.