Skip to content

Discussion: Adaptable NativeLibraryLoader #32

@BjoernAkAManf

Description

@BjoernAkAManf

Hi,

as a User of this Library i want to be able to control access of IO. In particular i want to be able to specify the path the native library is beeing written to.

For example i attached the following Class "Native" that emulates the behavior i want to implement.
Unfortunately this requires alot of copy-paste and hackish workarounds.

I would propose extracting the necessary functionality into atleast two parts:

  1. General Utility Class providing init() and load() delegating to implementation
  2. Strategy API -> Allows to implement a System.load() strategy

By default i suggest implementing the following Strategies:

  1. tryLoadFromLibraryPath -> Same as the corresponding method
  2. loadFromTempFile -> Same as current Implementation provided by libraryPath()

Order is provided by each Strategy. As such tryLoadFromLibraryPath would return -1 and loadFromTempfile 1. Other Implementations can then use 0 easily. ServiceLoader would allow to reduce implementation.

The API should provide access to atleast fileName and extension in order to allow writing correctly.

I did not yet start working on a Pull Request, because i feel like this RfC is disruptive to the current implementation and as such I feel @kawamuray you as a Maintainer should first give your Opinion.

Alternatively one could use an Environment Variable, but i feel that is too restrictive for other use cases. Especially when one wants to validate DLL using an out of band signing process.

import io.github.kawamuray.wasmtime.NativeLibraryLoader;
import lombok.AllArgsConstructor;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.Properties;

/**
 * This is a copy of {@link NativeLibraryLoader} that is needed to extract to a well known location instead of a temp file.
 */
@Slf4j
@UtilityClass
public final class Native {
    private static final String SANDBOX_NATIVE_OVERRIDE = "SANDBOX_NATIVE_OVERRIDE";
    private static final String LOCATION = ".native";
    private boolean loaded = false;

    public void load() {
        if (loaded) {
            return;
        }

        if (Native.isAutoLoadDisabled()) {
            log.error("Please set Environment Variable {} to a Value", DISABLE_AUTO_LOAD_ENV);
            System.exit(1);
        }

        try {
            final var nativeLibraryDir = Paths.get(LOCATION);
            if (!Files.exists(nativeLibraryDir)) {
                Files.createDirectory(nativeLibraryDir);
            }
            final var libraryPath = libraryPath(nativeLibraryDir);
            log.debug("Loading Wasmtime JNI library from {}", libraryPath);
            System.load(libraryPath);
            loaded = true;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private boolean isAutoLoadDisabled() {
        return System.getenv(DISABLE_AUTO_LOAD_ENV) == null;
    }

    private boolean isOverridingNative() {
        return System.getenv(SANDBOX_NATIVE_OVERRIDE) != null;
    }

    // Copied and changed
    private static String libraryPath(final Path root) throws IOException {
        final var platform = detectPlatform();
        String version = libVersion();
        String ext = platform.ext;
        String fileName = platform.prefix + NATIVE_LIBRARY_NAME + '_' + version + '_' + platform.classifier;
        final var name = fileName + ext;
        final var p = root.resolve(name);
        final var ovr = isOverridingNative();
        if (ovr) {
            log.warn("Overriding existing stuff yadda yaadda");
        }
        if (ovr || !Files.exists(p)) {
            try (final var in = NativeLibraryLoader.class.getResourceAsStream('/' + name)) {
                // Added to copied struct
                Objects.requireNonNull(in, "Could not find Library");
                final var options = ovr
                    ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING}
                    : new CopyOption[]{};
                Files.copy(in, p, options);
            }
        }
        return p.toRealPath().toAbsolutePath().toString();
    }

    // Rest is copied from Version 0.9.0
    private static final String DISABLE_AUTO_LOAD_ENV = "WASMTIME_JNI_LOAD_DISABLED";
    private static final String NATIVE_LIBRARY_NAME = "wasmtime_jni";
    private static final String META_PROPS_FILE = "wasmtime-java-meta.properties";

    private static final String JNI_LIB_VERSION_PROP = "jnilib.version";

    @AllArgsConstructor
    private enum Platform {
        LINUX("linux", "lib", ".so"),
        MACOS("macos", "lib", ".dylib"),
        WINDOWS("windows", "", ".dll");
        final String classifier;
        final String prefix;
        final String ext;

    }

    private static Platform detectPlatform() {
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains("linux")) {
            return Platform.LINUX;
        }
        if (os.contains("mac os") || os.contains("darwin")) {
            return Platform.MACOS;
        }
        if (os.toLowerCase().contains("windows")) {
            return Platform.WINDOWS;
        }
        throw new RuntimeException("platform not supported: " + os);
    }

    private static String libVersion() throws IOException {
        final Properties props;
        try (InputStream in = NativeLibraryLoader.class.getResourceAsStream('/' + META_PROPS_FILE)) {
            props = new Properties();
            props.load(in);
        }
        return props.getProperty(JNI_LIB_VERSION_PROP);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions