Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
3ceefd3
Introducing NativeLoader
dougqh Sep 26, 2025
bf8a301
spotless
dougqh Sep 26, 2025
67fc018
Eliminate var-args warning
dougqh Sep 26, 2025
e59a283
Remove unused import
dougqh Sep 26, 2025
77b5c42
Added preloaded test
dougqh Sep 26, 2025
972f5c3
Javadoc and misc clean-up
dougqh Sep 26, 2025
8534a4d
Removed errant comment
dougqh Sep 26, 2025
cfb4285
Tweaking Javadoc
dougqh Sep 26, 2025
63eefcd
More tests & javadoc
dougqh Sep 29, 2025
814e4c1
Solidifying exception handling
dougqh Sep 29, 2025
621c6ab
CapturingPathResolver -> CapturingPathLocator
dougqh Sep 29, 2025
d564cb5
nativeloader -> native-loader
dougqh Sep 30, 2025
7aadebc
Adding FunctionalInterface
dougqh Sep 30, 2025
ad2866f
Removing public from function
dougqh Sep 30, 2025
7655ce0
Merge branch 'master' into dougqh/library-loader
dougqh Sep 30, 2025
92a0def
Forbidden API fix
dougqh Sep 30, 2025
840d32a
Merge branch 'dougqh/library-loader' of github.com:DataDog/dd-trace-j…
dougqh Sep 30, 2025
34ca3b3
Overloading withPreloaded to take a Set
dougqh Oct 1, 2025
bf4f1fe
Merge branch 'master' into dougqh/library-loader
dougqh Oct 1, 2025
2b899aa
Hooking into arch detection code relocated to :components:environment
dougqh Oct 1, 2025
4b3ba71
Code coverage - PathUtils
dougqh Oct 1, 2025
8d6b2d0
spotless
dougqh Oct 1, 2025
3c12ae6
More PathUtils coverage
dougqh Oct 1, 2025
e0b7b85
PathLocatorHelper coverage
dougqh Oct 1, 2025
ae4aad6
Coverage PlatformSpec
dougqh Oct 1, 2025
0e8c405
Adding ability to record multiple locate requests to CapturingPathLoc…
dougqh Oct 1, 2025
dadb822
Coverage of LibraryResolvers
dougqh Oct 1, 2025
fe7ae49
Coverage PathLocators
dougqh Oct 1, 2025
45a71f3
Coverage LibDirBasedPathLocator
dougqh Oct 1, 2025
ccf73f0
PathLocators coverage
dougqh Oct 2, 2025
d04985d
Fixed bug with subResource loading in ClassLoaderResourcePathLocator
dougqh Oct 2, 2025
6b79e46
More coverage
dougqh Oct 2, 2025
8d04747
NativeLoader.Builder coverage
dougqh Oct 2, 2025
065b3cf
spotless
dougqh Oct 2, 2025
6d66b39
ClassLoaderBasedPathLocator coverage
dougqh Oct 2, 2025
79f0c89
LibFile coverage
dougqh Oct 2, 2025
a76f79d
Coverage NativeLoader.Builder - added tempDir tests
dougqh Oct 2, 2025
00fa98a
Testing tempDir functionality
dougqh Oct 2, 2025
6540109
Coverage - Testing PlatformSpec override variations of resolveDynamic
dougqh Oct 2, 2025
d4743f1
Temp file coverage
dougqh Oct 2, 2025
0336fd0
spotless
dougqh Oct 3, 2025
8a747b2
Update components/native-loader/src/main/java/datadog/nativeloader/Fl…
dougqh Oct 3, 2025
7ac11d7
Addressing review comments - lowering visibility on a few classes
dougqh Oct 3, 2025
ef880c5
Addressing review comments - explicit equals call in test
dougqh Oct 3, 2025
87800f0
Addressing review comments - be more explicit about equivalence tests
dougqh Oct 3, 2025
eab2dbb
Merge branch 'dougqh/library-loader' of github.com:DataDog/dd-trace-j…
dougqh Oct 3, 2025
589a1d2
fixing merge conflict
dougqh Oct 3, 2025
28d3578
spotless
dougqh Oct 3, 2025
65e47bf
Addressing review comments - LibraryResolver test clean up
dougqh Oct 3, 2025
940a69a
Addressing review comments - removing redundant final on methods
dougqh Oct 3, 2025
36a219b
Addressing review comments - removing final on methods
dougqh Oct 3, 2025
8e3280a
spotless
dougqh Oct 3, 2025
17627f1
Addressing review comments - check a not preloaded case for Builder
dougqh Oct 3, 2025
6b586ba
spotless
dougqh Oct 3, 2025
dcb5749
Adding explanatory comment for review question
dougqh Oct 3, 2025
818a1be
Tweaking assertions for readability
dougqh Oct 3, 2025
8401635
Improving path concatenation handling
dougqh Oct 3, 2025
25af560
Removed test jar file
dougqh Oct 7, 2025
fa0f8a8
Merge branch 'master' into dougqh/library-loader
dougqh Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions components/native-loader/build.gradle.kts
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,34 @@
package datadog.nativeloader;

import java.net.URL;
import java.util.Objects;

/** ClassLoaderResourcePathLocator locates library paths inside a {@link ClassLoader} */
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) {
return this.classLoader.getResource(PathUtils.concatPath(component, this.baseResource, path));
}

@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 <code>
* {os}-{arch}-{libc/musl}</code>
*/
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,65 @@
package datadog.nativeloader;

import datadog.environment.OperatingSystem;
import datadog.environment.OperatingSystem.Architecture;

/*
* Default PlatformSpec used in dd-trace-java -- wraps detection code in component:environment
*/
final class IntrospectPlatformSpec extends PlatformSpec {
static final PlatformSpec INSTANCE = new IntrospectPlatformSpec();

@Override
public boolean isLinux() {
return OperatingSystem.isLinux();
}

@Override
public boolean isMac() {
return OperatingSystem.isMacOs();
}

@Override
public boolean isWindows() {
return OperatingSystem.isWindows();
}

@Override
public boolean isMusl() {
return OperatingSystem.isMusl();
}

@Override
public boolean isAarch64() {
return isArch(Architecture.ARM64);
}

@Override
public boolean isArm32() {
return isArch(Architecture.ARM);
}

@Override
public boolean isX86_32() {
return isArch(Architecture.X86);
}

@Override
public boolean isX86_64() {
return isArch(Architecture.X64);
}

static final boolean isArch(OperatingSystem.Architecture arch) {
return (OperatingSystem.architecture() == arch);
}

@Override
public int hashCode() {
return IntrospectPlatformSpec.class.hashCode();
}

@Override
public boolean equals(Object obj) {
return (obj instanceof IntrospectPlatformSpec);
}
}
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 */
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 = PathUtils.concatPath(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,81 @@
package datadog.nativeloader;

import java.io.File;
import java.nio.file.Path;

/**
* 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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 a File to the library -- returns null for pre-loaded libraries */
public final File toFile() {
return this.file;
}

/** Provides a Path to the library -- return null for pre-loaded libraries */
public final Path toPath() {
return this.file == null ? null : this.file.toPath();
}

/** 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 {
private LibraryResolvers() {}

public static final LibraryResolver defaultLibraryResolver() {
return flatDirs();
}

public static final LibraryResolver withPreloaded(
LibraryResolver baseResolver, String... preloadedLibNames) {
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;
}
}
Loading