All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lwjgl.system.Library Maven / Gradle / Ivy

The newest version!
/*
 * Copyright LWJGL. All rights reserved.
 * License terms: https://www.lwjgl.org/license
 */
package org.lwjgl.system;

import org.lwjgl.*;

import javax.annotation.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.security.*;
import java.util.*;
import java.util.function.*;
import java.util.jar.*;
import java.util.regex.*;

import static org.lwjgl.system.APIUtil.*;
import static org.lwjgl.system.Checks.*;

/**
 * Initializes the LWJGL shared library and handles loading additional shared libraries.
 *
 * @see Configuration#LIBRARY_NAME
 * @see Configuration#LIBRARY_PATH
 */
public final class Library {

    /** The LWJGL shared library name. */
    public static final String JNI_LIBRARY_NAME = Configuration.LIBRARY_NAME.get(Platform.mapLibraryNameBundled("lwjgl"));

    static final String JAVA_LIBRARY_PATH = "java.library.path";

    private static final Pattern PATH_SEPARATOR = Pattern.compile(File.pathSeparator);

    private static final Pattern NATIVES_JAR = Pattern.compile("/[\\w-]+?-natives-\\w+.jar!/");

    static {
        if (DEBUG) {
            apiLog("Version: " + Version.getVersion());
            apiLog("\t OS: " + System.getProperty("os.name") + " v" + System.getProperty("os.version"));
            apiLog("\tJRE: " + Platform.get().getName() + " " + System.getProperty("os.arch") + " " + System.getProperty("java.version"));
            apiLog(
                "\tJVM: " + System.getProperty("java.vm.name") + " v" + System.getProperty("java.vm.version") + " by " + System.getProperty("java.vm.vendor")
            );
        }

        loadSystem("org.lwjgl", JNI_LIBRARY_NAME);
    }

    private Library() {}

    /** Ensures that the LWJGL shared library has been loaded. */
    public static void initialize() {
        // intentionally empty to trigger static initializer
    }

    /** Calls {@link #loadSystem(Consumer, Consumer, Class, String, String)} using {@code Library.class} as the context parameter. */
    public static void loadSystem(String module, String name) throws UnsatisfiedLinkError {
        loadSystem(System::load, System::loadLibrary, Library.class, module, name);
    }

    /**
     * Loads a JNI shared library.
     *
     * @param load        should be the {@code System::load} expression. This ensures that {@code System.load} has the same caller as this method.
     * @param loadLibrary should be the {@code System::loadLibrary} expression. This ensures that {@code System.loadLibrary} has the same caller as this
     *                    method.
     * @param context     the class to use to discover the shared library in the classpath
     * @param module      the module to which the shared library belongs
     * @param name        the library name. If not an absolute path, it must be the plain library name, without an OS specific prefix or file extension (e.g.
     *                    GL, not libGL.so)
     *
     * @throws UnsatisfiedLinkError if the library could not be loaded
     */
    @SuppressWarnings("try")
    public static void loadSystem(
        Consumer load,
        Consumer loadLibrary,
        Class context,
        String module,
        String name
    ) throws UnsatisfiedLinkError {
        apiLog("Loading JNI library: " + name);
        apiLog("\tModule: " + module);

        // METHOD 1: absolute path
        if (Paths.get(name).isAbsolute()) {
            load.accept(name);
            apiLog("\tSuccess");
            return;
        }

        String libName = Platform.get().mapLibraryName(name);

        boolean bundledWithLWJGL = name.contains("lwjgl");

        // METHOD 2: org.lwjgl.librarypath
        URL libURL = findResource(context, module, libName, bundledWithLWJGL);
        if (libURL == null) {
            if (loadSystemFromLibraryPath(load, context, module, libName, bundledWithLWJGL)) {
                return;
            }
        } else {
            // Always use the SLL if the library is found in the classpath,
            // so that newer versions can be detected.
            boolean debugLoader = Configuration.DEBUG_LOADER.get(false);
            try {
                String regular = getRegularFilePath(libURL);
                if (regular != null) {
                    load.accept(regular);
                    apiLog("\tLoaded from classpath: " + regular);
                    return;
                }

                if (debugLoader) {
                    apiLog("\tUsing SharedLibraryLoader...");
                }
                // Extract from classpath and try org.lwjgl.librarypath
                try (FileChannel ignored = SharedLibraryLoader.load(name, libName, libURL, load)) {
                    if (loadSystemFromLibraryPath(load, context, module, libName, bundledWithLWJGL)) {
                        return;
                    }
                }
            } catch (Exception e) {
                if (debugLoader) {
                    e.printStackTrace(DEBUG_STREAM);
                }
            }
        }

        String javaLibraryPath = System.getProperty(JAVA_LIBRARY_PATH);

        // METHOD 3: java.library.path (bundled only)
        if (bundledWithLWJGL && javaLibraryPath != null) {
            if (loadSystem(load, context, module, getBundledPath(module, libName), false, JAVA_LIBRARY_PATH, javaLibraryPath)) {
                return;
            }
        }

        // METHOD 4: System.loadLibrary
        try {
            loadLibrary.accept(name);

            // Success, but java.library.path might be empty, or not include the library.
            // In that case, ClassLoader::findLibrary was used to return the library path (e.g. OSGi does this with native libraries in bundles).
            Path libFile = javaLibraryPath == null ? null : findFile(javaLibraryPath, module, libName, bundledWithLWJGL);
            if (libFile != null) {
                apiLog(String.format("\tLoaded from %s: %s", JAVA_LIBRARY_PATH, libFile));
                checkHash(context, libFile);
            } else {
                apiLog("\tLoaded from a ClassLoader provided path.");
            }
            return;
        } catch (Throwable t) {
            apiLog(String.format("\t%s not found in %s", libName, JAVA_LIBRARY_PATH));
        }

        detectPlatformMismatch(context, module);
        printError(true);
        throw new UnsatisfiedLinkError("Failed to locate library: " + libName);
    }

    private static boolean loadSystemFromLibraryPath(Consumer load, Class context, String module, String libName, boolean bundledWithLWJGL) {
        String paths = Configuration.LIBRARY_PATH.get();
        return paths != null && loadSystem(load, context, module, libName, bundledWithLWJGL, Configuration.LIBRARY_PATH.getProperty(), paths);
    }

    private static boolean loadSystem(Consumer load, Class context, String module, String libName, boolean bundledWithLWJGL, String property, String paths) {
        Path libFile = findFile(paths, module, libName, bundledWithLWJGL);
        if (libFile == null) {
            apiLog(String.format("\t%s not found in %s=%s", libName, property, paths));
            return false;
        }

        load.accept(libFile.toAbsolutePath().toString());
        apiLog(String.format("\tLoaded from %s: %s", property, libFile));
        checkHash(context, libFile);
        return true;
    }

    /** Calls {@link #loadNative(Class, String, String)} using {@code Library.class} as the context parameter. */
    public static SharedLibrary loadNative(String module, String name) {
        return loadNative(Library.class, module, name);
    }

    /**
     * Loads a shared library using OS-specific APIs (e.g. {@link org.lwjgl.system.windows.WinBase#LoadLibrary LoadLibrary} or
     * {@link org.lwjgl.system.linux.DynamicLinkLoader#dlopen dlopen}).
     *
     * @param context the class to use to discover the shared library in the classpath
     * @param module  the module to which the shared library belongs
     * @param name    the library name. OS-specific prefixes and file extensions are optional (e.g. both {@code "GL"} and {@code "libGL.so.1"} are
     *                valid on Linux)
     *
     * @return the shared library
     *
     * @throws UnsatisfiedLinkError if the library could not be loaded
     */
    @SuppressWarnings("try")
    public static SharedLibrary loadNative(Class context, String module, String name) {
        return loadNative(context, module, name, false);
    }

    /**
     * Loads a shared library using OS-specific APIs (e.g. {@link org.lwjgl.system.windows.WinBase#LoadLibrary LoadLibrary} or
     * {@link org.lwjgl.system.linux.DynamicLinkLoader#dlopen dlopen}).
     *
     * @param context          the class to use to discover the shared library in the classpath
     * @param module           the module to which the shared library belongs
     * @param name             the library name. OS-specific prefixes and file extensions are optional (e.g. both {@code "GL"} and {@code "libGL.so.1"} are
     *                         valid on Linux)
     * @param bundledWithLWJGL whether the default LWJGL distribution includes the shared library. If true, LWJGL will also try to find the shared library under
     *                         the {@code //} subfolder.
     *
     * @return the shared library
     *
     * @throws UnsatisfiedLinkError if the library could not be loaded
     */
    public static SharedLibrary loadNative(Class context, String module, String name, boolean bundledWithLWJGL) {
        return loadNative(context, module, name, bundledWithLWJGL, true);
    }

    @SuppressWarnings("try")
    private static SharedLibrary loadNative(Class context, String module, String name, boolean bundledWithLWJGL, boolean printError) {
        apiLog("Loading library: " + name);
        apiLog("\tModule: " + module);

        // METHOD 1: absolute path
        if (Paths.get(name).isAbsolute()) {
            SharedLibrary lib = apiCreateLibrary(name);
            apiLog("\tSuccess");
            return lib;
        }

        String        libName = Platform.get().mapLibraryName(name);
        SharedLibrary lib;

        // METHOD 2: org.lwjgl.librarypath
        URL libURL = findResource(context, module, libName, bundledWithLWJGL);
        if (libURL == null) {
            lib = loadNativeFromLibraryPath(context, module, libName, bundledWithLWJGL);
            if (lib != null) {
                return lib;
            }
        } else {
            boolean debugLoader = Configuration.DEBUG_LOADER.get(false);
            try {
                String regular = getRegularFilePath(libURL);
                if (regular != null) {
                    lib = apiCreateLibrary(regular);
                    apiLog("\tLoaded from classpath: " + regular);
                    return lib;
                }

                // Always use the SLL if the library is found in the classpath,
                // so that newer versions can be detected.
                if (debugLoader) {
                    apiLog("\tUsing SharedLibraryLoader...");
                }
                // Extract from classpath and try org.lwjgl.librarypath
                try (FileChannel ignored = SharedLibraryLoader.load(name, libName, libURL, null)) {
                    lib = loadNativeFromLibraryPath(context, module, libName, bundledWithLWJGL);
                    if (lib != null) {
                        return lib;
                    }
                }
            } catch (Exception e) {
                if (debugLoader) {
                    e.printStackTrace(DEBUG_STREAM);
                }
            }
        }

        // Use method 4 before 3 for libraries not bundled with LWJGL.
        if (!bundledWithLWJGL) {
            lib = loadNativeFromSystem(libName);
            if (lib != null) {
                return lib;
            }
        }

        // METHOD 3: System.loadLibrary (emulated)
        {
            if (Configuration.EMULATE_SYSTEM_LOADLIBRARY.get(false)) {
                // Try ClassLoader::findLibrary (e.g. OSGi bundle)
                try {
                    Method findLibrary = ClassLoader.class.getDeclaredMethod("findLibrary", String.class);
                    findLibrary.setAccessible(true);

                    String libPath = (String)findLibrary.invoke(context.getClassLoader(), name);
                    if (libPath != null) {
                        lib = apiCreateLibrary(libPath);
                        apiLog(String.format("\tLoaded from ClassLoader provided path: %s", libPath));
                        return lib;
                    }
                } catch (Exception ignored) {
                    // This will fail on JDK 9 without --add-opens java.base/java.lang=ALL-UNNAMED
                }
            }

            // Then java.library.path
            String paths = System.getProperty(JAVA_LIBRARY_PATH);
            if (paths != null) {
                lib = loadNative(context, module, libName, bundledWithLWJGL, JAVA_LIBRARY_PATH, paths);
                if (lib != null) {
                    return lib;
                }
            }
        }

        // METHOD 4: system-specific
        if (bundledWithLWJGL) {
            lib = loadNativeFromSystem(libName);
            if (lib != null) {
                return lib;
            }
        }

        if (printError) {
            detectPlatformMismatch(context, module);
            printError(bundledWithLWJGL);
        }
        throw new UnsatisfiedLinkError("Failed to locate library: " + libName);
    }

    @Nullable
    private static SharedLibrary loadNativeFromSystem(String libName) {
        SharedLibrary lib;
        try {
            lib = apiCreateLibrary(libName);
            String path = lib.getPath();
            apiLog(path == null
                ? "\tLoaded from system paths"
                : "\tLoaded from system paths: " + path);
        } catch (UnsatisfiedLinkError e) {
            lib = null;
            apiLog(String.format("\t%s not found in system paths", libName));
        }
        return lib;
    }

    @Nullable
    private static SharedLibrary loadNativeFromLibraryPath(Class context, String module, String libName, boolean bundledWithLWJGL) {
        String paths = Configuration.LIBRARY_PATH.get();
        if (paths == null) {
            return null;
        }
        return loadNative(context, module, libName, bundledWithLWJGL, Configuration.LIBRARY_PATH.getProperty(), paths);
    }

    @Nullable
    private static SharedLibrary loadNative(Class context, String module, String libName, boolean bundledWithLWJGL, String property, String paths) {
        Path libFile = findFile(paths, module, libName, bundledWithLWJGL);
        if (libFile == null) {
            apiLog(String.format("\t%s not found in %s=%s", libName, property, paths));
            return null;
        }

        SharedLibrary lib = apiCreateLibrary(libFile.toAbsolutePath().toString());
        apiLog(String.format("\tLoaded from %s: %s", property, libFile));
        checkHash(context, libFile);
        return lib;
    }

    /**
     * Loads a shared library using {@link #loadNative(String, String)} with the name specified by {@code name}. If {@code name} is not set,
     * {@link #loadNative(String, String)} will be called with the names specified by {@code defaultNames}. The first successful will be returned.
     *
     * @param name         a {@link Configuration} that specifies the library name
     * @param defaultNames the default library name(s)
     *
     * @return the shared library
     *
     * @throws UnsatisfiedLinkError if the library could not be loaded
     */
    public static SharedLibrary loadNative(Class context, String module, @Nullable Configuration name, String... defaultNames) {
        return loadNative(context, module, name, null, defaultNames);
    }

    /**
     * Loads a shared library using {@link #loadNative(String, String)} with the name specified by {@code name}. If {@code name} is not set,
     * {@link #loadNative(String, String)} will be called with the names specified by {@code defaultNames}. The first successful will be returned. If the library could
     * not be loaded, the {@code fallback} will be called.
     *
     * @param name         a {@link Configuration} that specifies the library name
     * @param fallback     fallback to use if everything else fails
     * @param defaultNames the default library name(s)
     *
     * @return the shared library
     *
     * @throws UnsatisfiedLinkError if the library could not be loaded
     */
    public static SharedLibrary loadNative(Class context, String module, @Nullable Configuration name, @Nullable Supplier fallback, String... defaultNames) {
        if (defaultNames.length == 0) {
            throw new IllegalArgumentException("No default names specified.");
        }

        if (name != null) {
            String libraryName = name.get();
            if (libraryName != null) {
                return loadNative(context, module, libraryName);
            }
        }

        if (fallback == null && defaultNames.length <= 1) {
            return loadNative(context, module, defaultNames[0]);
        }

        try {
            return loadNative(context, module, defaultNames[0], false, false); // try first
        } catch (Throwable t) {
            for (int i = 1; i < defaultNames.length; i++) { // try alternatives
                try {
                    return loadNative(context, module, defaultNames[i], false, fallback == null && i == defaultNames.length - 1);
                } catch (Throwable ignored) {
                }
            }
            if (fallback != null) {
                return fallback.get();
            }
            throw t; // original error
        }
    }

    private static String getBundledPath(String module, String resource) {
        return Platform.mapLibraryPathBundled(module.replace('.', '/') + "/" + resource);
    }

    @Nullable
    static URL findResource(Class context, String module, String resource, boolean bundledWithLWJGL) {
        URL url = null;
        if (bundledWithLWJGL) {
            String bundledResource = getBundledPath(module, resource);
            if (!bundledResource.equals(resource)) {
                url = context.getClassLoader().getResource(bundledResource);
            }
        }
        return url == null ? context.getClassLoader().getResource(resource) : url;
    }

    @Nullable
    static String getRegularFilePath(URL url) {
        if (url.getProtocol().equals("file")) {
            try {
                Path path = Paths.get(url.toURI());
                if (path.isAbsolute() && Files.isReadable(path)) {
                    return path.toString();
                }
            } catch (URISyntaxException ignored) {
            }
        }
        return null;
    }

    @Nullable
    static Path findFile(String path, String module, String file, boolean bundledWithLWJGL) {
        if (bundledWithLWJGL) {
            String bundledFile = getBundledPath(module, file);
            if (!bundledFile.equals(file)) {
                Path p = findFile(path, bundledFile);
                if (p != null) {
                    return p;
                }
            }
        }
        return findFile(path, file);
    }

    @Nullable
    private static Path findFile(String path, String file) {
        for (String directory : PATH_SEPARATOR.split(path)) {
            Path p = Paths.get(directory, file);
            if (Files.isReadable(p)) {
                return p;
            }
        }
        return null;
    }

    private static void detectPlatformMismatch(Class context, String module) {
        String moduleTitle = module.equals("org.lwjgl") ? "lwjgl" : "lwjgl-" + module.substring("org.lwjgl.".length());

        List platforms = new ArrayList<>(8);
        try {
            Enumeration manifests = context.getClassLoader().getResources(JarFile.MANIFEST_NAME);
            while (manifests.hasMoreElements()) {
                try (InputStream is = manifests.nextElement().openStream()) {
                    Manifest   manifest = new Manifest(is);
                    Attributes attribs  = manifest.getMainAttributes();

                    if (moduleTitle.equals(attribs.getValue("Implementation-Title"))) {
                        String jarPlatform = attribs.getValue("LWJGL-Platform");
                        if (jarPlatform != null) {
                            platforms.add(jarPlatform);
                        }
                    }
                }
            }
        } catch (IOException ignored) {
        }

        if (!platforms.isEmpty()) {
            DEBUG_STREAM.println("[LWJGL] Platform/architecture mismatch detected for module: " + module);
            DEBUG_STREAM.println("\tJVM platform:");
            DEBUG_STREAM.format(
                "\t\t%s %s %s\n",
                Platform.get().getName(),
                System.getProperty("os.arch"),
                System.getProperty("java.version")
            );
            DEBUG_STREAM.format(
                "\t\t%s v%s by %s\n",
                System.getProperty("java.vm.name"),
                System.getProperty("java.vm.version"),
                System.getProperty("java.vm.vendor")
            );
            DEBUG_STREAM.format(
                "\tPlatform%s available on classpath:\n\t\t%s\n",
                (platforms.size() == 1 ? "" : "s"),
                String.join("\n\t\t", platforms)
            );
        }
    }

    private static void printError(boolean bundledWithLWJGL) {
        printError(
            "[LWJGL] Failed to load a library. Possible solutions:\n" + (bundledWithLWJGL
                ? "\ta) Add the directory that contains the shared library to -Djava.library.path or -Dorg.lwjgl.librarypath.\n" +
                  "\tb) Add the JAR that contains the shared library to the classpath."
                : "\ta) Install the library or the driver that provides the library.\n" +
                  "\tb) Ensure that the library is accessible from the system library paths."
            )
        );
    }

    static void printError(String message) {
        DEBUG_STREAM.println(message);
        if (!DEBUG) {
            DEBUG_STREAM.println("[LWJGL] Enable debug mode with -Dorg.lwjgl.util.Debug=true for better diagnostics.");
            if (!Configuration.DEBUG_LOADER.get(false)) {
                DEBUG_STREAM.println("[LWJGL] Enable the SharedLibraryLoader debug mode with -Dorg.lwjgl.util.DebugLoader=true for better diagnostics.");
            }
        }
    }

    /**
     * Compares the shared library hash stored in the classpath, with the hash of the actual library loaded at runtime.
     *
     * 

This check prints a simple warning when there's a hash mismatch, to help diagnose installation/classpath issues. It is not a security feature.

* * @param context the class to use to discover the shared library hash in the classpath * @param libFile the library file loaded */ private static void checkHash(Class context, Path libFile) { if (!CHECKS) { return; } try { URL classesURL = null; URL nativesURL = null; Enumeration resources = context.getClassLoader().getResources(libFile.getFileName() + ".sha1"); while (resources.hasMoreElements()) { URL url = resources.nextElement(); if (NATIVES_JAR.matcher(url.toExternalForm()).find()) { nativesURL = url; } else { classesURL = url; } } if (classesURL == null) { return; } byte[] expected = getSHA1(classesURL); byte[] actual = DEBUG || nativesURL == null ? getSHA1(libFile) : getSHA1(nativesURL); if (!Arrays.equals(expected, actual)) { DEBUG_STREAM.println( "[LWJGL] [ERROR] Incompatible Java and native library versions detected.\n" + "Possible reasons:\n" + "\ta) -Djava.library.path is set to a folder containing shared libraries of an older LWJGL version.\n" + "\tb) The classpath contains jar files of an older LWJGL version.\n" + "Possible solutions:\n" + "\ta) Make sure to not set -Djava.library.path (it is not needed for developing with LWJGL 3) or make\n" + "\t sure the folder it points to contains the shared libraries of the correct LWJGL version.\n" + "\tb) Check the classpath and make sure to only have jar files of the same LWJGL version in it."); } } catch (Throwable t) { if (DEBUG) { apiLog("Failed to verify native library."); t.printStackTrace(); } } } private static byte[] getSHA1(URL hashURL) throws IOException { byte[] hash = new byte[20]; try (InputStream sha1 = hashURL.openStream()) { for (int i = 0; i < 20; i++) { hash[i] = (byte)((Character.digit(sha1.read(), 16) << 4) | Character.digit(sha1.read(), 16)); } } return hash; } private static byte[] getSHA1(Path libFile) throws NoSuchAlgorithmException, IOException { MessageDigest digest = MessageDigest.getInstance("SHA-1"); try (InputStream input = Files.newInputStream(libFile)) { byte[] buffer = new byte[8 * 1024]; for (int n; (n = input.read(buffer)) != -1; ) { digest.update(buffer, 0, n); } } return digest.digest(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy