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

io.qt.internal.NativeLibraryManager Maven / Gradle / Ivy

The newest version!
/****************************************************************************
**
** Copyright (C) 1992-2009 Nokia. All rights reserved.
** Copyright (C) 2009-2022 Dr. Peter Droste, Omix Visualization GmbH & Co. KG. All rights reserved.
**
** This file is part of Qt Jambi.
**
** ** $BEGIN_LICENSE$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
** 
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
** 
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
** $END_LICENSE$

**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

package io.qt.internal;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import io.qt.NativeAccess;
import io.qt.QNoSuchSlotException;
import io.qt.QLibraryNotFoundError;
import io.qt.QLibraryNotLoadedError;
import io.qt.QtUninvokable;
import io.qt.core.QDeclarableSignals;

/**
 * The NativeLibraryManager class is responsible for handling native
 * libraries in Qt Jambi. Native libraries can be loaded either
 * directly from the file system using
 * -Djava.library.path or indirectly JAR-file that
 * contain a deployment descriptor. For normal deployment, the JAR
 * approach is recommended.
 *
 * Loading libraries is done by calling the methods
 * loadQtLibrary and loadLibrary.
 *
 * When the indirect .jar file approach is taken, the .jar file will
 * be opened and the native library manager will search for a file
 * named "qtjambi-deployment.xml". This file contains a list of native
 * libraries that should unpacked to a temporary directory and loaded,
 * either right away or at a later time. There are three types of
 * libraries.
 *
 * 
    * *
  • System libraries; such as the system runtime * libraries. These libraries are usually loaded automatically by * the native library loader. * *
  • Normal libraries; such as the QtCore and * io_qt_core libraries. These are loaded at runtime on * demand. * *
  • Plugins; such as qjpeg. These are never loaded explicitly by * the native library manager, but are unpacked into the temporary * folder so that Qt can find and load them from the file system. * *
* * There are three possible deployment scenarios. The simplest and * most straightforward approach is when deploying a Pure Java * application based on Qt Jambi. In this case the prebuilt binaries * from the binary package can just be deployed as part of the * classpath and the rest will solve itself. * * When deploying a Qt Jambi application that is using native code * other than Qt Jambi, we recommend building a new .jar file with a * custom qtjambi-deployment.xml which contais the Qt Jambi libraries * and the custom native libraries. Deployment can then be done by * making sure that this new .jar file is available in the classpath. * * The final option for deployment is when users have a C++ * application which starts and makes use of Qt Jambi. In this case we * suggest that all dependent libraries are available in the file * system and via -Djava.library.path * * To get runtime information about how library loading works, specify * the -Dio.qt.verbose-loading system property * to the Virtual Machine. It possible to specify that the native * library manager should load debug versions of libraries as * well. This is done by specifying the system property * -Dio.qt.debug=debug * */ final class NativeLibraryManager { private NativeLibraryManager() { throw new RuntimeException();} private static final boolean VERBOSE_LOADING = Boolean.getBoolean("io.qt.verbose-loading") || Boolean.getBoolean("qtjambi.verbose-loading"); private static final int[] qtVersionArray = {QtJambiVersion.qtMajorVersion, QtJambiVersion.qtMinorVersion}; private static final int[] qtjambiVersionArray = {QtJambiVersion.qtMajorVersion, QtJambiVersion.qtMinorVersion, QtJambiVersion.qtJambiPatch}; private static final String qtjambiVersion = QtJambiVersion.qtMajorVersion + "." + QtJambiVersion.qtMinorVersion + "." + QtJambiVersion.qtJambiPatch; public static final String ICU_VERSION; public static final String LIBINFIX; // We use a List<> to make the collection read-only an array would not be suitable private static final List systemLibrariesList; private static final List jniLibdirBeforeList; private static final List jniLibdirList; private static boolean isMinGWBuilt = false; static final OperatingSystem operatingSystem = decideOperatingSystem(); private static final String osArchName; private static Configuration configuration = null; private static String qtJambiLibraryPath = null; private static String qtLibraryPath = null; private static Boolean dontUseFrameworks = operatingSystem==OperatingSystem.MacOSX ? null : Boolean.FALSE; private static boolean dontSearchDeploymentSpec = false; private static final Set loadedDeploymentSpecUrls = Collections.synchronizedSet(new HashSet<>()); private static final List deploymentSpec = Collections.synchronizedList(new ArrayList<>()); private static final String qtjambiVersionJarSuffix = String.format("-%1$s.%2$s.%3$s.jar", QtJambiVersion.qtMajorVersion, QtJambiVersion.qtMinorVersion, QtJambiVersion.qtJambiPatch); private static final Map libraryMap = Collections.synchronizedMap(new HashMap()); private static final Reporter reporter = new Reporter(); private static final File jambiDeploymentDir; private static final File jambiJarDir; private static final boolean isJava8Bundle; private static final List pluginLibraries = Collections.synchronizedList(new ArrayList<>()); public static final String K_SUNOS_X86 = "sunos-x86"; public static final String K_SUNOS_X64 = "sunos-x64"; public static final String K_WIN_X86 = "windows-x86"; public static final String K_WIN_X64 = "windows-x64"; public static final String K_WIN_ARM32 = "windows-arm32"; public static final String K_WIN_ARM64 = "windows-arm64"; public static final String K_LINUX_X86 = "linux-x86"; public static final String K_LINUX_X64 = "linux-x64"; public static final String K_LINUX_ARM32 = "linux-arm32"; public static final String K_LINUX_ARM64 = "linux-arm64"; public static final String K_FREEBSD_X86 = "freebsd-x86"; public static final String K_FREEBSD_X64 = "freebsd-x64"; private static final boolean deleteTmpDeployment; static { { File tmpDir = new File(System.getProperty("java.io.tmpdir")); String deploymentdir = System.getProperty("io.qt.deploymentdir", ""); boolean keepDeployment = Boolean.getBoolean("io.qt.keep-temp-deployment"); if(deploymentdir!=null && !deploymentdir.isEmpty() && !"tmp".equalsIgnoreCase(deploymentdir) && !"temp".equalsIgnoreCase(deploymentdir)) { keepDeployment = true; if("user".equalsIgnoreCase(deploymentdir)) { switch (operatingSystem) { case Windows: tmpDir = new File(System.getProperty("user.home"), "AppData\\Local\\QtJambi"); break; case Android: case Linux: tmpDir = new File(System.getProperty("user.home"), ".local/share/QtJambi"); break; case MacOSX: tmpDir = new File(System.getProperty("user.home"), "Library/Application Support/QtJambi"); break; default: break; } }else if("common".equalsIgnoreCase(deploymentdir)) { switch (operatingSystem) { case Windows: tmpDir = new File("C:\\ProgramData\\QtJambi"); break; case Android: case Linux: tmpDir = new File("/usr/local/share/QtJambi"); break; case MacOSX: tmpDir = new File("/Library/Application Support/QtJambi"); break; default: break; } }else { File deploymentDir = new File(deploymentdir); if(deploymentDir.isAbsolute()) { tmpDir = deploymentDir; }else { tmpDir = new File(System.getProperty("user.home"), deploymentdir); } } jambiDeploymentDir = new File(tmpDir, "v" + QtJambiVersion.qtMajorVersion + "." + QtJambiVersion.qtMinorVersion + "." + QtJambiVersion.qtJambiPatch); }else { if(keepDeployment) { jambiDeploymentDir = new File(tmpDir, "QtJambi" + QtJambiVersion.qtMajorVersion + "." + QtJambiVersion.qtMinorVersion + "." + QtJambiVersion.qtJambiPatch + "_" + System.getProperty("user.name")); }else { jambiDeploymentDir = new File(tmpDir, "QtJambi" + QtJambiVersion.qtMajorVersion + "." + QtJambiVersion.qtMinorVersion + "." + QtJambiVersion.qtJambiPatch + "_" + System.getProperty("user.name") + "_" + RetroHelper.processName()); } } deleteTmpDeployment = !keepDeployment; if(deleteTmpDeployment) Logger.getLogger("io.qt.internal").log(Level.FINEST, ()->"Requires deletion of tmp deployment directory "+jambiDeploymentDir.getAbsolutePath()+" during pogram termination."); } boolean _isJava8Bundle = false; { File _jambiJarDir = null; String classURL = ""+NativeLibraryManager.class.getResource("NativeLibraryManager.class"); int index; if(classURL.startsWith("jar:file:") && (index = classURL.indexOf("!/"))>0) { String jarFileURL = classURL.substring(4, index); File jarFile = null; try { jarFile = new File(new URL(jarFileURL).toURI()); } catch (URISyntaxException | MalformedURLException e) { } if(jarFile!=null && jarFile.exists()) { _jambiJarDir = jarFile.getParentFile(); _isJava8Bundle = jarFile.getName().startsWith("qtjambi-jre8-"); } } isJava8Bundle = _isJava8Bundle; jambiJarDir = _jambiJarDir; } String tmpICUVERSION_STRING = null; String tmpINFIX_STRING = null; List tmpSystemLibrariesList = null; List tmpJniLibdirBeforeList = null; List tmpJniLibdirList = null; try { tmpINFIX_STRING = QtJambiVersion.properties.getProperty("qtjambi.libinfix"); tmpICUVERSION_STRING = QtJambiVersion.properties.getProperty("qtjambi.icu.version", "56"); SortedMap tmpSystemLibrariesMap = new TreeMap(); SortedMap tmpJniLibdirBeforeMap = new TreeMap(); SortedMap tmpJniLibdirMap = new TreeMap(); Enumeration e = QtJambiVersion.properties.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); String value = QtJambiVersion.properties.getProperty(key); if (key.equals("qtjambi.system.libraries") || key.startsWith("qtjambi.system.libraries.")) { tmpSystemLibrariesMap.put(key, value); } else if(key.equals("qtjambi.jni.libdir.before") || key.startsWith("qtjambi.jni.libdir.before.")) { tmpJniLibdirBeforeMap.put(key, value); } else if(key.equals("qtjambi.jni.libdir") || key.startsWith("qtjambi.jni.libdir.")) { tmpJniLibdirMap.put(key, value); } } // Map will automatically sort the lists { "", ".0", ".01", ".1", ".10", ".2", ".A", ".a" } tmpSystemLibrariesList = new ArrayList(); for (String v : tmpSystemLibrariesMap.values()) tmpSystemLibrariesList.add(v); if (tmpSystemLibrariesList.size() > 0) tmpSystemLibrariesList = resolveSystemLibraries(tmpSystemLibrariesList); else tmpSystemLibrariesList = null; tmpJniLibdirBeforeList = new ArrayList(); for (String v : tmpJniLibdirBeforeMap.values()) tmpJniLibdirBeforeList.add(v); if (tmpJniLibdirBeforeList.size() > 0) tmpJniLibdirBeforeList = Collections.unmodifiableList(tmpJniLibdirBeforeList); else tmpJniLibdirBeforeList = null; tmpJniLibdirList = new ArrayList(); for (String v : tmpJniLibdirMap.values()) tmpJniLibdirList.add(v); if (tmpJniLibdirList.size() > 0) tmpJniLibdirList = Collections.unmodifiableList(tmpJniLibdirList); else tmpJniLibdirList = null; } catch(Throwable e) { java.util.logging.Logger.getLogger("io.qt.internal").log(java.util.logging.Level.SEVERE, "", e); }finally { ICU_VERSION = tmpICUVERSION_STRING; systemLibrariesList = tmpSystemLibrariesList!=null ? Collections.unmodifiableList(tmpSystemLibrariesList) : Collections.emptyList(); jniLibdirBeforeList = tmpJniLibdirBeforeList!=null ? Collections.unmodifiableList(tmpJniLibdirBeforeList) : Collections.emptyList(); jniLibdirList = tmpJniLibdirList!=null ? Collections.unmodifiableList(tmpJniLibdirList) : Collections.emptyList(); LIBINFIX = tmpINFIX_STRING; } switch (operatingSystem) { case Windows: switch(System.getProperty("os.arch").toLowerCase()) { case "arm": case "arm32": osArchName = K_WIN_ARM32; break; case "arm64": case "aarch64": osArchName = K_WIN_ARM64; break; case "x86_64": case "x64": case "amd64": osArchName = K_WIN_X64; break; default: osArchName = K_WIN_X86; break; } break; case Linux: switch(System.getProperty("os.arch").toLowerCase()) { case "arm": case "arm32": osArchName = K_LINUX_ARM32; break; case "arm64": case "aarch64": osArchName = K_LINUX_ARM64; break; case "x86_64": case "x64": case "amd64": osArchName = K_LINUX_X64; break; default: osArchName = K_LINUX_X86; break; } break; case Android: osArchName = "android"; break; case MacOSX: osArchName = "macos"; break; default: osArchName = "unknown"; break; } reporter.setReportEnabled(VERBOSE_LOADING); try{ Preferences preferences = Preferences.userNodeForPackage(NativeLibraryManager.class); String dirs = preferences.get("qtjambi.previous.deployment.dir", null); if(dirs!=null && !dirs.isEmpty()) { preferences.remove("qtjambi.previous.deployment.dir"); for(String dir : dirs.split("\\"+File.pathSeparator)) { File jambiTempDir = new File(dir); if(jambiTempDir.exists() && jambiTempDir.isDirectory()) clearAndDelete(jambiTempDir); } } }catch(Throwable t) {} if(!(Boolean.getBoolean("io.qt.no-deployment-spec") || Boolean.getBoolean("qtjambi.no-deployment-spec"))) { ClassLoader loader = classLoader(); // Multiple descriptors maybe found Enumeration specsFound = Collections.emptyEnumeration(); try { specsFound = loader.getResources("qtjambi-deployment.xml"); } catch (IOException e) { Logger.getLogger("io.qt.internal").log(Level.WARNING, "", e); } // FIXME: Want searchForDescriptors/parse/resolve/unpack/load phases separated dontSearchDeploymentSpec = specsFound.hasMoreElements(); if(!dontSearchDeploymentSpec && jambiJarDir!=null) { List foundURLs = new ArrayList<>(); String postfix; if("debug".equals(System.getProperty("io.qt.debug"))) { postfix = "-native-" + osArchName + "-debug" + qtjambiVersionJarSuffix; File nativeFile = new File(jambiJarDir, "qtjambi"+postfix); if(nativeFile.exists()) { try { foundURLs.add(new URL("jar:"+nativeFile.toURI()+"!/qtjambi-deployment.xml")); } catch (IOException e) { } } }else { postfix = "-native-" + osArchName + qtjambiVersionJarSuffix; File nativeFile = new File(jambiJarDir, "qtjambi"+postfix); if(nativeFile.exists()) { try { foundURLs.add(new URL("jar:"+nativeFile.toURI()+"!/qtjambi-deployment.xml")); } catch (IOException e) { } }else { postfix = "-native-" + osArchName + "-debug" + qtjambiVersionJarSuffix; nativeFile = new File(jambiJarDir, "qtjambi"+postfix); if(nativeFile.exists()) { try { foundURLs.add(new URL("jar:"+nativeFile.toURI()+"!/qtjambi-deployment.xml")); } catch (IOException e) { } } } } String notPostfix = null; if(_isJava8Bundle) postfix = "-jre8" + postfix; else notPostfix = "-jre8" + postfix; for(String f : jambiJarDir.list()) { if(f.startsWith("qtjambi-plugin-") && f.endsWith(postfix) && (_isJava8Bundle || !f.endsWith(notPostfix))) { try { foundURLs.add(new URL("jar:"+new File(jambiJarDir, f).toURI()+"!/qtjambi-deployment.xml")); } catch (IOException e) { } } } if(!foundURLs.isEmpty()) specsFound = Collections.enumeration(foundURLs); } while (specsFound.hasMoreElements()) { URL url = specsFound.nextElement(); if(loadedDeploymentSpecUrls.contains(url)) continue; loadedDeploymentSpecUrls.add(url); reporter.report("Found ", url.toString()); DeploymentSpec spec = null; String protocol = url.getProtocol(); if (protocol.equals("jar")) { // Using toExternalForm() will convert a space character into %20 which breaks java.lang.File use of the resulting path. String eform = url.toExternalForm(); // Try to decide the name of the .jar file to have a // reference point for later.. int start = 4; //"jar:".length(); int end = eform.indexOf("!/", start); // eform has the "jar:url!/entry" format if (end != -1) { try { URL jarUrl = new URL(eform.substring(start, end)); String jarName = new File(jarUrl.getFile()).getName(); reporter.report("Loading ", jarName, " from ", eform); spec = unpackDeploymentSpec(url, jarName, null); } catch (ParserConfigurationException | SAXException | IOException e) { Logger.getLogger("io.qt.internal").log(Level.WARNING, "", e); } } } else if (protocol.equals("file")) { // No unpack since we presume we are already unpacked try { spec = unpackDeploymentSpec(url, null, Boolean.FALSE); } catch (ParserConfigurationException | SAXException | IOException e) { Logger.getLogger("io.qt.internal").log(Level.WARNING, "", e); } } if(spec != null) { if("qtjambi".equals(spec.getModule())) deploymentSpec.add(0, spec); else deploymentSpec.add(spec); } } if(!deploymentSpec.isEmpty()) { DeploymentSpec qtjambiSpec = deploymentSpec.get(0); if(Configuration.Release.toString().compareToIgnoreCase(qtjambiSpec.getConfiguration()) == 0) configuration = Configuration.Release; else if(Configuration.Debug.toString().compareToIgnoreCase(qtjambiSpec.getConfiguration()) == 0) configuration = Configuration.Debug; else if(Configuration.Test.toString().compareToIgnoreCase(qtjambiSpec.getConfiguration()) == 0) configuration = Configuration.Test; else configuration = Configuration.Release; switch (operatingSystem) { case Windows: qtJambiLibraryPath = new File(qtjambiSpec.getBaseDir(), "bin").getAbsolutePath(); break; default: qtJambiLibraryPath = new File(qtjambiSpec.getBaseDir(), "lib").getAbsolutePath(); break; } for(DeploymentSpec spec : deploymentSpec) { if(!spec.getCompiler().equals(qtjambiSpec.getCompiler())) { throw new DeploymentSpecException(String.format("Native deployments of different builts: %1$s and %2$s", qtjambiSpec.getCompiler(), spec.getCompiler())); }else if(!spec.getConfiguration().equals(qtjambiSpec.getConfiguration())) { throw new DeploymentSpecException(String.format("Native deployments of different configurations: %1$s and %2$s", qtjambiSpec.getConfiguration(), spec.getConfiguration())); } } }else { configuration = decideConfiguration(); } }else { configuration = decideConfiguration(); } if(VERBOSE_LOADING) System.out.println(reporter.recentReports()); } private static void clearAndDelete(File directory) { if(directory!=null) { if(directory.isDirectory()) { for(File file : directory.listFiles()) { if(file.getName().equals(".") || file.getName().equals("..")) continue; if(file.isDirectory() && !Files.isSymbolicLink(file.toPath())) { clearAndDelete(file); }else { file.delete(); } } } directory.delete(); } } // The purpose of this method is to resolve the names provided in the list into DSO paths relative to the project. private static List resolveSystemLibraries(List tmpSystemLibrariesList) { if(tmpSystemLibrariesList == null) return null; List resolvedList = new ArrayList(); for(String original : tmpSystemLibrariesList) { String s = original; if(operatingSystem==OperatingSystem.Windows) // convert "/" into "\" s = stringCharReplace(s, "/", File.separator); String resolvedPath = null; for(DeploymentSpec deploymentSpec : deploymentSpec) { File f = new File(deploymentSpec.getBaseDir(), s); if(f.isFile()) { resolvedPath = s; break; } File libDir = new File(deploymentSpec.getBaseDir(), "lib"); f = new File(libDir, s); if(f.isFile()) { resolvedPath = "lib" + File.separator + s; break; } File binDir = new File(deploymentSpec.getBaseDir(), "bin"); f = new File(binDir, s); if(f.isFile()) { resolvedPath = "bin" + File.separator + s; break; } } if(resolvedPath != null) resolvedList.add(resolvedPath); else System.err.println("IGNORED version.properties qtjambi.system.libraries entry \"" + original + "\": file could not be found"); } return Collections.unmodifiableList(resolvedList); } private static String stringCharReplace(String s, CharSequence fromCharSequence, CharSequence toCharSequence) { final int len = s.length(); StringBuilder sb = new StringBuilder(); int i = 0; while(i < len) { char c = s.charAt(i); if(c == fromCharSequence.charAt(0) && stringCompareAt(s, i, fromCharSequence, 0)) { // FIXME needle/haystack sb.append(toCharSequence); i += fromCharSequence.length(); // skip input sequence } else { sb.append(c); i++; } } return sb.toString(); } // a - haystack, b - needle private static boolean stringCompareAt(CharSequence a, int aOffset, CharSequence b, int bOffset) { final int aLen = a.length(); // used as end range final int bLen = b.length(); // used as end range while(aOffset < aLen && bOffset < bLen) { char ca = a.charAt(aOffset++); char ba = b.charAt(bOffset++); if(ca != ba) return false; } // we are only allowed to run out of chars on 'a' side if(bOffset < bLen) return false; // ran out of chars on 'b' side return true; } private static OperatingSystem decideOperatingSystem() { String osName = System.getProperty("os.name").toLowerCase(); if (osName.startsWith("windows")) return OperatingSystem.Windows; if (osName.startsWith("mac")) return OperatingSystem.MacOSX; if (osName.startsWith("android")) return OperatingSystem.Android; return OperatingSystem.Linux; } static Configuration configuration() { return configuration; } private static Configuration decideConfiguration() { Configuration configuration = null; String debugString = System.getProperty("io.qt.debug"); try { if(debugString != null) { // This was added to allow unit tests to specify Configuration (as no MANIFEST.MF is available) if(Configuration.Release.toString().compareToIgnoreCase(debugString) == 0) configuration = Configuration.Release; else if(Configuration.Debug.toString().compareToIgnoreCase(debugString) == 0) configuration = Configuration.Debug; else if(Configuration.Test.toString().compareToIgnoreCase(debugString) == 0) configuration = Configuration.Test; if(configuration == null) { Boolean booleanValue = Boolean.valueOf(debugString); if((booleanValue != null && booleanValue.booleanValue()) || debugString.length() == 0) { configuration = Configuration.Debug; // FIXME: When we can unambigiously auto-detect this from the MANIFEST we don't need to // emit this, we'd only emit it when both non-debug and debug were found and we selected // the debug kind. java.util.logging.Logger.getLogger("io.qt.internal").log(java.util.logging.Level.WARNING, "-Dio.qt.debug=" + Boolean.TRUE + "; is set instead of =debug."); } } } } catch(Exception e) { java.util.logging.Logger.getLogger("io.qt.internal").log(java.util.logging.Level.SEVERE, "-Dio.qt.debug=" + Boolean.FALSE + "; is assumed default", e); // only because Configuration.Release is assumed } if(configuration==null) configuration = Configuration.Release; return configuration; } static File jambiDeploymentDir() { return jambiDeploymentDir; } @NativeAccess @QtUninvokable static void resetDeploymentSpecs() { deploymentSpec.clear(); if(deleteTmpDeployment) { if(jambiDeploymentDir.exists() && jambiDeploymentDir.isDirectory()) { Logger.getLogger("io.qt.internal").log(Level.FINEST, ()->"Deleting tmp deployment directory "+jambiDeploymentDir.getAbsolutePath()+"."); clearAndDelete(jambiDeploymentDir); if(jambiDeploymentDir.exists() && jambiDeploymentDir.isDirectory()) { Logger.getLogger("io.qt.internal").log(Level.FINEST, "Preparing pending deletion..."); Preferences preferences = Preferences.userNodeForPackage(NativeLibraryManager.class); String dirs = preferences.get("qtjambi.previous.deployment.dir", null); if(dirs!=null && !dirs.isEmpty()) { preferences.put("qtjambi.previous.deployment.dir", dirs + File.pathSeparator + jambiDeploymentDir.getAbsolutePath()); }else { preferences.put("qtjambi.previous.deployment.dir", jambiDeploymentDir.getAbsolutePath()); } } }else { Logger.getLogger("io.qt.internal").log(Level.FINEST, ()->"Tmp deployment directory "+jambiDeploymentDir.getAbsolutePath()+" does not exist."); } } } private static class XMLHandler extends DefaultHandler { public DeploymentSpec spec; private final List direntCurrentPathList = new ArrayList(); public void startElement(String uri, String localName, String name, org.xml.sax.Attributes attributes) { if (name.equals("symlink")) { SymlinkEntry e = new SymlinkEntry(); e.setName(attributes.getValue("name")); e.setTarget(attributes.getValue("target")); if (e.getName() == null) { throw new DeploymentSpecException(" element missing required attribute \"name\""); } if (e.getTarget() == null) { throw new DeploymentSpecException(" element missing required attribute \"target\""); } e.setDeploymentSpec(spec); if(e.getName().startsWith("qml/")) { spec.setHasQmlPaths(true); }else if(e.getName().startsWith("plugins/")) { spec.setHasPluginPaths(true); }else if(e.getName().startsWith("translations/")) { spec.setHasTrPaths(true); }else{ String fileName = e.getName(); if(fileName.startsWith("lib/") || fileName.startsWith("bin/")) fileName = fileName.substring(4); // Add library name to the global map of libraries... LibraryEntry old = libraryMap.get(fileName); if (old == null) { reporter.report(" - adding '", fileName, "' to library map"); libraryMap.put(fileName, e); } } spec.addLibraryEntry(e); reporter.report(" - symlink: name='", e.getName(), "'"); } else if (name.equals("library") || name.equals("syslibrary")) { LibraryEntry e = new LibraryEntry(); e.setName(attributes.getValue("name")); if (e.getName() == null) { throw new DeploymentSpecException(" element missing required attribute \"name\""); } e.setDeploymentSpec(spec); if(e.getName().startsWith("qml/")) { spec.setHasQmlPaths(true); }else if(e.getName().startsWith("plugins/")) { spec.setHasPluginPaths(true); }else if(e.getName().startsWith("translations/")) { spec.setHasTrPaths(true); }else{ String fileName = e.getName(); if(fileName.startsWith("lib/") || fileName.startsWith("bin/")) fileName = fileName.substring(4); // Add library name to the global map of libraries... LibraryEntry old = libraryMap.get(fileName); if (old != null && !old.getDeploymentSpec().getSourceUrl().equals(spec.getSourceUrl())) { throw new DeploymentSpecException(" '" + e.getName() + "' is duplicated. Present in both '" + spec.getSourceUrl() + "' and '" + old.getDeploymentSpec().getSourceUrl() + "'."); } reporter.report(" - adding '", fileName, "' to library map"); libraryMap.put(fileName, e); } spec.addLibraryEntry(e); reporter.report(" - library: name='", e.getName(), "'"); } else if (name.equals("qtjambi-deploy")) { String system = attributes.getValue("system"); if (system == null || system.length() == 0) { throw new DeploymentSpecException(" element missing required attribute 'system'"); } else if (!system.equals(osArchName)) { throw new WrongSystemException(String.format("Expected version: %1$s, found: %2$s.", osArchName, system)); } spec.setSystem(system); String version = attributes.getValue("version"); if (version == null || version.length() == 0) { throw new DeploymentSpecException(" element missing required attribute 'version'"); } else if(!qtjambiVersion.equals(version)) throw new WrongVersionException(String.format("Expected version: %1$s, found: %2$s.", qtjambiVersion, version)); spec.setVersion(version); spec.setModule(attributes.getValue("module")); spec.setCompiler(attributes.getValue("compiler")); spec.setConfiguration(attributes.getValue("configuration")); spec.setDate(attributes.getValue("date")); spec.setTime(attributes.getValue("time")); } else if (name.equals("directory")) { String attrName = attributes.getValue("name"); if (attrName == null) { throw new DeploymentSpecException(" element missing required attribute \"name\""); } while(attrName.length() > 0 && attrName.charAt(0) == '/') attrName = attrName.substring(1); while(attrName.length() > 0 && attrName.charAt(attrName.length() - 1) == '/') attrName = attrName.substring(0, attrName.length() - 1 - 1); direntCurrentPathList.add(attrName); } else if (name.equals("file")) { String attrName = attributes.getValue("name"); if (attrName == null) { throw new DeploymentSpecException(" element missing required attribute \"name\""); } StringBuilder sb = new StringBuilder(); for(String s : direntCurrentPathList) { if(sb.length() > 0) sb.append('/'); sb.append(s); } if(sb.length() > 0) sb.append('/'); sb.append(attrName); spec.addDirentPath(sb.toString()); reporter.report(" - dirent path='", sb.toString(), "'"); } } public void endElement(String uri, String localName, String name) { if (name.equals("directory")) { if (direntCurrentPathList.isEmpty() == false) direntCurrentPathList.remove(direntCurrentPathList.size() - 1); // remove last } } } //no-one used this so I commented it out /*private static class ChecksumFileFilter implements FilenameFilter { public boolean accept(File dir, String name) { return name.endsWith(".chk"); } }*/ /** * Returns a file that is used for caching native libraries. The * path is a subdirectory of java.io.tmpdir, named * QtJambi_{user}_{architecture}_{version}_{key}. The * key is the same as the cache key used in the deployment * descriptor. The purpose of using different keys is to avoid * binary compatibility when various configurations of Qt and Qt * Jambi are used on the same system. * * When deployment descriptors are not used, this location will * not be used. * * @param key The cache key to. * @return A File representing the location directory for * temporary content. The directory is not explicitly created in * this here. */ private static File jambiTempDirBase(DeploymentSpec spec) { File tmpDir = new File(System.getProperty("java.io.tmpdir")); String user = System.getProperty("user.name"); return new File(tmpDir, "QtJambi_" + spec.getVersion() + "_" + spec.getSystem() + "_" + spec.getCompiler() + "_" + spec.getConfiguration() + "_" + spec.getDate() + "_" + spec.getTime() + "_" + user); } /** * Returns the list of all plugin paths that are specified in the * deployment descriptors. If deployment descriptors are not used, * this list will be an empty list. * * @return The list of plugin paths */ static List pluginPaths() { pluginLibraries.removeIf( l -> { try { l.extract(); }catch(Throwable t) { java.util.logging.Logger.getLogger("io.qt.internal").log(java.util.logging.Level.SEVERE, "Error while extracting library", t); } return true; }); List paths = new ArrayList(); for (DeploymentSpec spec : deploymentSpec) { File root = spec.getBaseDir(); if(spec.isHasPluginPaths()) paths.add(new File(root, "plugins").getAbsolutePath()); } return paths; } static void loadLibrary(String library) { switch (operatingSystem) { case Windows: loadNativeLibrary(Object.class, library + ".dll", Arrays.asList(""), false); break; case MacOSX: try { loadNativeLibrary(Object.class, "lib" + library + ".jnilib", Arrays.asList(""), false); } catch (Throwable e) { loadNativeLibrary(Object.class, "lib" + library + ".dylib", Arrays.asList(""), false); } break; case Android: case Linux: loadNativeLibrary(Object.class, "lib" + library + ".so", Arrays.asList(""), false); break; default: break; } } static void loadQtJambiLibrary() { loadQtJambiLibrary(NativeLibraryManager.class, null, "QtJambi"); } static void loadQtJambiLibrary(Class callerClass, String library) { loadQtJambiLibrary(callerClass, "QtJambi", library); } static void loadJambiLibrary(Class callerClass, String library) { loadQtJambiLibrary(callerClass, null, library); } private static void loadQtJambiLibrary(Class callerClass, String prefix, String library) { List replacements = new ArrayList<>(); String lib = jambiLibraryName(prefix, library, qtjambiVersionArray, replacements); loadNativeLibrary(callerClass, lib, replacements, true); } /** * Loads a library with name specified in library. * The library name will be expanded to the default shared library * name for a given platform, so the name "QtCore" and version "4" and "5" will be * expanded like this: * *
    *
  • Windows: QtCore4.dll *
  • Linux / Unix: libQtCore.so.4 *
  • Mac OS X: libQtCore.4.dylib *
*
    *
  • Windows: Qt5Core.dll *
  • Linux / Unix: libQt5Core.so.5 *
  • Mac OS X: libQt5Core.5.dylib *
* * When using loading libraries from the filesystem, this method * simply calls System.loadLibrary. * @param library The name of the library.. */ static void loadQtLibrary(String library) { findAndLoadLibrary(Object.class, "Qt", library, LIBINFIX, qtVersionArray); } static void loadUtilityLibrary(String library, String version) { findAndLoadLibrary(Object.class, library, version); } static File loadQtCore() { loadSystemLibraries(); if(isAvailableLibrary("icudata", NativeLibraryManager.ICU_VERSION)){ findAndLoadLibrary(Object.class, "icudata", NativeLibraryManager.ICU_VERSION); } if(isAvailableLibrary("icuuc", NativeLibraryManager.ICU_VERSION)){ findAndLoadLibrary(Object.class, "icuuc", NativeLibraryManager.ICU_VERSION); } if(isAvailableLibrary("icui18n", NativeLibraryManager.ICU_VERSION)){ findAndLoadLibrary(Object.class, "icui18n", NativeLibraryManager.ICU_VERSION); } try{ File lib = findAndLoadLibrary(Object.class, "Qt", "Core", LIBINFIX, qtVersionArray); qtLibraryPath = lib.getParentFile().getAbsolutePath(); return lib; } catch (RuntimeException e) { if(configuration==Configuration.Release) { // try to run qtjambi with debug libraries instead configuration=Configuration.Debug; try { return findAndLoadLibrary(Object.class, "Qt", "Core", LIBINFIX, qtVersionArray); } catch (Throwable e1) { e.addSuppressed(e1); configuration=Configuration.Release; if(dontUseFrameworks==null) { dontUseFrameworks = Boolean.TRUE; try { return findAndLoadLibrary(Object.class, "Qt", "Core", LIBINFIX, qtVersionArray); } catch (Throwable e2) { e.addSuppressed(e2); configuration=Configuration.Debug; try { return findAndLoadLibrary(Object.class, "Qt", "Core", LIBINFIX, qtVersionArray); } catch (Throwable e3) { e.addSuppressed(e3); configuration=Configuration.Release; throw e; } } }else{ throw e; } } }else { if(dontUseFrameworks==null) { dontUseFrameworks = Boolean.TRUE; try { return findAndLoadLibrary(Object.class, "Qt", "Core", LIBINFIX, qtVersionArray); } catch (Throwable e1) { e.addSuppressed(e1); throw e; } }else{ if(!isMinGWBuilt && operatingSystem==OperatingSystem.Windows) { Availability availability = getLibraryAvailability(null, "libstdc++-6", null, null, null, Configuration.Release); if(availability.entry!=null){ isMinGWBuilt = true; }else if(availability.file!=null){ isMinGWBuilt = new File(availability.file.getParentFile(), "Qt"+QtJambiVersion.qtMajorVersion+"Core.dll").isFile(); } if(isMinGWBuilt) { return findAndLoadLibrary(Object.class, "Qt", "Core", LIBINFIX, qtVersionArray); } } throw e; } } }finally { if(dontUseFrameworks==null) { dontUseFrameworks = Boolean.FALSE; } } } public static boolean isMinGWBuilt() { return isMinGWBuilt; } private static File findAndLoadLibrary(Class callerClass, String library, String version) { List replacements = new ArrayList<>(); String lib = qtLibraryName(null, library, null, null, version, configuration, replacements); return loadNativeLibrary(callerClass, lib, replacements, false); } private static File findAndLoadLibrary(Class callerClass, String qtprefix, String library, String libInfix, int[] version) { List replacements = new ArrayList<>(); String lib = qtLibraryName(qtprefix, library, libInfix, version, null, configuration, replacements); return loadNativeLibrary(callerClass, lib, replacements, false); } static boolean isAvailableQtLibrary(String library) { return getLibraryAvailability("Qt", library, LIBINFIX, qtVersionArray, null, configuration).isAvailable(); } static boolean isAvailableLibrary(String library, String version) { return getLibraryAvailability(null, library, null, null, version, configuration).isAvailable(); } private static class Availability{ public Availability(File file) { super(); this.file = file; this.entry = null; } public Availability() { super(); this.file = null; this.entry = null; } public Availability(LibraryEntry entry) { super(); this.file = null; this.entry = entry; } final File file; final LibraryEntry entry; boolean isAvailable() { return file!=null || entry!=null; } } private static Availability getLibraryAvailability(String qtprefix, String library, String libInfix, int[] version, String versionStrg, Configuration configuration) { List replacements = new ArrayList<>(); String libFormat = qtLibraryName(qtprefix, library, libInfix, version, versionStrg, configuration, replacements); // "QtDBus" => "libQtDBus.so.4" String libPaths = System.getProperty("io.qt.library-path-override"); if (libPaths == null || libPaths.length() == 0) { libPaths = System.getProperty("java.library.path"); } List paths = new ArrayList<>(); if (libPaths != null) paths.addAll(Arrays.asList(libPaths.split("\\"+File.pathSeparator))); switch (operatingSystem) { case Windows: libPaths = System.getenv("PATH"); break; case MacOSX: libPaths = System.getenv("DYLD_LIBRARY_PATH"); break; default: libPaths = System.getenv("LD_LIBRARY_PATH"); } if (libPaths != null) paths.addAll(Arrays.asList(libPaths.split("\\"+File.pathSeparator))); paths = mergeJniLibdir(paths); // search active deploymentSpec for existance of library for(String replacement : replacements) { String lib = String.format(libFormat, replacement); for(DeploymentSpec deploymentSpec : deploymentSpec) { List libraries = deploymentSpec.getLibraries(); for(LibraryEntry libraryEntry : libraries) { String name = libraryEntry.getName(); // name="lib/libQtFoo.so.4" if(name == null) continue; if(lib.equals(name)) // lib=="lib/libQtFoo.so.4" return new Availability(libraryEntry); String[] pathA = name.split("\\/"); if(pathA == null || pathA.length == 0) continue; String lastPart = pathA[pathA.length - 1]; if(lib.equals(lastPart)) // lib=="libQtFoo.so.4" return new Availability(libraryEntry); } } for (String path : paths) { File f = new File(path, lib); if (f.exists()) { return new Availability(f); } } } return new Availability(); } /** * Returns a classloader for current context... * @return The classloader */ private static ClassLoader classLoader() { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if (loader == null) { loader = NativeLibraryManager.class.getClassLoader(); assert(loader != null); } return loader; } /** * Tries to load the specified library. It will first try to load * the library using the deploy spec, and if that fails, it will * try to load the library using a standard System.loadLibrary() * call. * @param lib The full name of the library to load, such as libQtCore.so.4 */ private static File loadNativeLibrary(Class callerClass, String lib, List replacements, boolean searchSpec) { File result = null; try { result = loadLibrary_helper(callerClass, lib, replacements, searchSpec); if (VERBOSE_LOADING) System.out.println(reporter.recentReports()); } catch (RuntimeException | Error e) { if(VERBOSE_LOADING) System.err.println(reporter.recentReports()); throw e; } catch (Throwable e) { if(reporter.toString().isEmpty()) { if(e.getMessage().isEmpty()) { throw new QLibraryNotLoadedError("Loading library failed.", e); }else { throw new QLibraryNotLoadedError(e.getMessage(), e); } }else { throw new QLibraryNotLoadedError("Loading library failed. Progress so far: " + reporter, e); } } return result; } private static BiConsumer, String> libraryLoader; static{ libraryLoader = (callerClass, lib) -> { synchronized(NativeLibraryManager.class) { @SuppressWarnings({ "rawtypes", "unchecked" }) QDeclarableSignals.Signal2, String> loadLibrary = new QDeclarableSignals.Signal2(Class.class, String.class); try { loadLibrary.connect(Runtime.getRuntime(), "load0(Class, String)"); libraryLoader = loadLibrary::emit; } catch (QNoSuchSlotException e) { libraryLoader = (_callerClass, _lib)->Runtime.getRuntime().load(_lib); } } libraryLoader.accept(callerClass, lib); }; } private static final Map> javaLibraryPaths = new HashMap<>(); private static final Map> ldLibraryPaths = new HashMap<>(); private static final Map> javaLibraryPathOverrides = new HashMap<>(); private static List splitPath(String path){ if(path!=null) { return Arrays.asList(path.split("\\"+File.pathSeparator)); }else { return Collections.emptyList(); } } private static final Set loadedLibraries = new TreeSet<>(); private static boolean autoExtractQtJambiLibs; private static File loadLibrary_helper(Class callerClass, final String libFormat, List replacements, boolean searchSpec) { if(loadedLibraries.contains(libFormat)) return null; File result = null; ClassLoader callerClassLoader = callerClass.getClassLoader(); boolean loaded = false; loop1: for(String replacement : replacements) { String library = String.format(libFormat, replacement); LibraryEntry e; // Try to load via deploy spec... e = libraryMap.get(library); if (e != null) { if (e.isLoaded()) { return result; } try { e.extract(); } catch (Exception e1) { throw new QLibraryNotLoadedError("Unable to extract library "+e.getName(), e1); } File libFile = e.getDeploymentSpec().buildPath(e.getName()); if(libFile!=null && libFile.isFile()) { reporter.report("Loading library: '", library, "' from deployment spec at ", libFile.getAbsolutePath()); if(callerClassLoader==NativeLibraryManager.class.getClassLoader() || callerClassLoader==Object.class.getClassLoader()) { Runtime.getRuntime().load(libFile.getAbsolutePath()); }else { libraryLoader.accept(callerClass, libFile.getAbsolutePath()); } loadedLibraries.add(libFormat); result = libFile; if(operatingSystem==OperatingSystem.MacOSX && (dontUseFrameworks==null || !dontUseFrameworks) && qtLibraryPath==null) { result = result.getParentFile().getParentFile().getParentFile(); } e.setLoaded(true); loaded = true; break loop1; } // Load via System.load() using default paths.. } } if(!loaded && (!autoExtractQtJambiLibs || !searchSpec)) { loop2: for(String replacement : replacements) { String library = String.format(libFormat, replacement); List libraryPaths = new ArrayList<>(); if(qtJambiLibraryPath!=null) libraryPaths.add(qtJambiLibraryPath); switch (operatingSystem) { case Windows: libraryPaths.add(new File(jambiDeploymentDir, "bin").getAbsolutePath()); if(jambiJarDir!=null) libraryPaths.add(new File(jambiJarDir, "bin").getAbsolutePath()); break; default: libraryPaths.add(new File(jambiDeploymentDir, "lib").getAbsolutePath()); if(jambiJarDir!=null) libraryPaths.add(new File(jambiJarDir, "lib").getAbsolutePath()); break; } if (System.getProperties().contains("io.qt.library-path-override")) { // reporter.report(" - using 'io.qt.library-path-override'"); libraryPaths.addAll(javaLibraryPathOverrides.computeIfAbsent(System.getProperty("io.qt.library-path-override"), NativeLibraryManager::splitPath)); } else { // reporter.report(" - using 'java.library.path'"); libraryPaths.addAll(javaLibraryPaths.computeIfAbsent(System.getProperty("java.library.path"), NativeLibraryManager::splitPath)); } libraryPaths = mergeJniLibdir(libraryPaths); for (String path : libraryPaths) { File f = new File(path, library); if (f.exists()) { if(callerClassLoader==NativeLibraryManager.class.getClassLoader() || callerClassLoader==Object.class.getClassLoader()) { Runtime.getRuntime().load(f.getAbsolutePath()); }else { libraryLoader.accept(callerClass, f.getAbsolutePath()); } loadedLibraries.add(libFormat); reporter.report("Loading library: '", library, "'. Path was: ", f.getAbsolutePath()); result = f; if(operatingSystem==OperatingSystem.MacOSX && (dontUseFrameworks==null || !dontUseFrameworks) && qtLibraryPath==null) { result = result.getParentFile().getParentFile().getParentFile(); } loaded = true; break loop2; } } } } if(!loaded && searchSpec && !dontSearchDeploymentSpec && !(Boolean.getBoolean("io.qt.no-deployment-spec") || Boolean.getBoolean("qtjambi.no-deployment-spec"))) { String className = callerClass.getName(); int idx = className.lastIndexOf('.'); className = className.substring(idx+1); String classURL = ""+callerClass.getResource(className+".class"); int index; if(classURL.startsWith("jar:file:") && (index = classURL.indexOf("!/"))>0) { String jarFileURL = classURL.substring(4, index); File jarFile = null; try { jarFile = new File(new URL(jarFileURL).toURI()); } catch (URISyntaxException | MalformedURLException e) { } if(jarFile!=null && jarFile.exists()) { File directory = jarFile.getParentFile(); String fileName = jarFile.getName(); if(isJava8Bundle) index = fileName.indexOf("-jre8"+qtjambiVersionJarSuffix); else index = fileName.indexOf(qtjambiVersionJarSuffix); if(index>0) { String moduleName = fileName.substring(0, index); String postfix = "-native-" + osArchName; if(configuration==Configuration.Debug) postfix += "-debug"; postfix += qtjambiVersionJarSuffix; File nativeFile = new File(directory, moduleName + postfix); boolean found = false; if(nativeFile.exists()) { try { URL nativeFileURL = new URL("jar:"+nativeFile.toURI()+"!/qtjambi-deployment.xml"); if(!loadedDeploymentSpecUrls.contains(nativeFileURL)) { loadedDeploymentSpecUrls.add(nativeFileURL); DeploymentSpec spec = unpackDeploymentSpec(nativeFileURL, nativeFile.getName(), null); if(spec != null && spec.getConfiguration().equalsIgnoreCase(configuration.name()) && (deploymentSpec.isEmpty() || spec.getCompiler().equals(deploymentSpec.get(0).getCompiler()))) { if("qtjambi".equals(spec.getModule())) deploymentSpec.add(0, spec); else deploymentSpec.add(spec); if(qtJambiLibraryPath==null) { switch (operatingSystem) { case Windows: qtJambiLibraryPath = new File(spec.getBaseDir(), "bin").getAbsolutePath(); break; default: qtJambiLibraryPath = new File(spec.getBaseDir(), "lib").getAbsolutePath(); break; } } found = true; } } } catch (ParserConfigurationException | SAXException | IOException e) { } } if(found) { loop3: for(String replacement : replacements) { String library = String.format(libFormat, replacement); LibraryEntry e; // Try to load via deploy spec... e = libraryMap.get(library); if (e != null) { if (e.isLoaded()) { return result; } try { e.extract(); } catch (Exception e1) { throw new QLibraryNotLoadedError("Unable to extract library "+e.getName(), e1); } File libFile = e.getDeploymentSpec().buildPath(e.getName()); if(libFile!=null && libFile.isFile()) { reporter.report("Loading library: '", library, "' from deployment spec at ", libFile.getAbsolutePath()); if(callerClassLoader==NativeLibraryManager.class.getClassLoader() || callerClassLoader==Object.class.getClassLoader()) { Runtime.getRuntime().load(libFile.getAbsolutePath()); }else { libraryLoader.accept(callerClass, libFile.getAbsolutePath()); } autoExtractQtJambiLibs = true; loadedLibraries.add(libFormat); result = libFile; if(operatingSystem==OperatingSystem.MacOSX && (dontUseFrameworks==null || !dontUseFrameworks) && qtLibraryPath==null) { result = result.getParentFile().getParentFile().getParentFile(); } e.setLoaded(true); loaded = true; break loop3; } // Load via System.load() using default paths.. } } } } } } } if(!loaded && (!autoExtractQtJambiLibs || !searchSpec)) { for(String replacement : replacements) { String library = String.format(libFormat, replacement); List libraryPaths = new ArrayList<>(); switch (operatingSystem) { case Windows: libraryPaths.addAll(ldLibraryPaths.computeIfAbsent(System.getenv("PATH"), NativeLibraryManager::splitPath)); break; case MacOSX: libraryPaths.addAll(ldLibraryPaths.computeIfAbsent(System.getenv("DYLD_LIBRARY_PATH"), NativeLibraryManager::splitPath)); break; default: libraryPaths.addAll(ldLibraryPaths.computeIfAbsent(System.getenv("LD_LIBRARY_PATH"), NativeLibraryManager::splitPath)); } libraryPaths = mergeJniLibdir(libraryPaths); for (String path : libraryPaths) { File f = new File(path, library); if (f.exists()) { if(callerClassLoader==NativeLibraryManager.class.getClassLoader() || callerClassLoader==Object.class.getClassLoader()) { Runtime.getRuntime().load(f.getAbsolutePath()); }else { libraryLoader.accept(callerClass, f.getAbsolutePath()); } loadedLibraries.add(libFormat); reporter.report("Loading library: '", library, "'. Path was: ", f.getAbsolutePath()); result = f; if(operatingSystem==OperatingSystem.MacOSX && (dontUseFrameworks==null || !dontUseFrameworks) && qtLibraryPath==null) { result = result.getParentFile().getParentFile().getParentFile(); } loaded = true; break; } } } } if (!loaded) { String lib = String.format(libFormat, replacements.get(0)); throw new QLibraryNotFoundError("Library '" + lib +"' was not found in 'java.library.path=" + System.getProperty("java.library.path") + "'"); } return result; } /** * * @param url This maybe "file" or "jar" protocol. "http" is untested. * @return * @throws SAXException * @throws ParserConfigurationException * @throws IOException * @throws Exception */ private static DeploymentSpec readDeploySpec(URL url) throws ParserConfigurationException, SAXException, IOException { reporter.report("Checking Archive '", url.toString(), "'"); DeploymentSpec spec = new DeploymentSpec(); spec.setSourceUrl(url); SAXParserFactory fact = SAXParserFactory.newInstance(); SAXParser parser = fact.newSAXParser(); XMLHandler handler = new XMLHandler(); handler.spec = spec; InputStream inStream = null; try { inStream = url.openStream(); parser.parse(inStream, handler); return spec; } finally { if(inStream != null) { inStream.close(); inStream = null; } } } private static class ExecutorThread extends Thread implements Executor{ private final List commands = new ArrayList<>(); private boolean noMoreExpected = false; private ExecutorThread() { super(); this.setDaemon(true); this.setName("QtJambi_LibraryExtractor"); } @Override public void execute(Runnable command) { synchronized(this.commands) { this.commands.add(command); this.commands.notifyAll(); } } @Override public void run() { try { while(true) { synchronized(this.commands) { if(this.commands.isEmpty()) { if(noMoreExpected) break; this.commands.wait(); } } List commands = new ArrayList<>(); synchronized(this.commands) { commands.addAll(this.commands); this.commands.clear(); } for (Runnable runnable : commands) { try { runnable.run(); } catch (Throwable e) { if(e instanceof InterruptedException) throw e; java.util.logging.Logger.getLogger("io.qt.internal").log(java.util.logging.Level.SEVERE, "", e); } } } } catch (InterruptedException e) { } } void setNoMoreExpected() { synchronized(this.commands) { noMoreExpected = true; this.commands.notifyAll(); } } } private static DeploymentSpec unpackDeploymentSpec(URL deploymentSpec, String jarName, Boolean shouldUnpack) throws ParserConfigurationException, SAXException, IOException{ if(jarName!=null) reporter.report("Unpacking .jar file: '", jarName, "'"); DeploymentSpec spec = null; try { spec = readDeploySpec(deploymentSpec); } catch (DeploymentSpecException e1) { Logger.getLogger("io.qt.internal").warning(String.format("Unable to load native libraries from %1$s: %2$s", (jarName==null ? deploymentSpec : jarName), e1.getMessage())); } if (spec==null) return null; File tmpDir = spec.getModule()==null ? jambiTempDirBase(spec) : jambiDeploymentDir; spec.setBaseDir(tmpDir); spec.setBaseUrl(new URL(convertAbsolutePathStringToFileUrlString(tmpDir))); reporter.report(" - using cache directory: '", tmpDir.getAbsolutePath(), "'"); File dummyFile = null; if(shouldUnpack == null) { if(spec.getModule()!=null) { shouldUnpack = Boolean.TRUE; }else { // If the dir exists and contains .dummy, sanity check the contents... dummyFile = new File(tmpDir, ".dummy"); if(spec.getModule()!=null) dummyFile = new File(tmpDir, "."+spec.getModule()+".dummy"); if (dummyFile.exists()) { reporter.report(" - cache directory exists"); shouldUnpack = Boolean.FALSE; } else { shouldUnpack = Boolean.TRUE; } } } // If the dir doesn't exists or it was only half completed, copy the files over... // FIXME: This should be a separate API call to cause loading, we want to be able to load up multiple DeploymentSpec // so we can choose which one we are going to activate (and allow enumeration of them). if (shouldUnpack.booleanValue()) { reporter.report(" - starting to copy content to cache directory..."); String urlBase = deploymentSpec.toString(); int idx = urlBase.indexOf("!/"); if(idx>0) { urlBase = urlBase.substring(0, idx+2); } for (String path : spec.getDirents()) { File outFile = new File(tmpDir, path.replace('/', File.separatorChar)); if(!outFile.exists()) { reporter.report(" - copying over: '", path, "'..."); InputStream in = null; OutputStream out = null; try { URL entryURL = new URL(urlBase+path); try { in = entryURL.openStream(); } catch (Exception e1) { continue; } reporter.report(" - matched url: ", entryURL.toExternalForm()); File outFileDir = outFile.getParentFile(); if (!outFileDir.exists()) { reporter.report(" - creating directory: ", outFileDir.getAbsolutePath()); outFileDir.mkdirs(); } out = new FileOutputStream(outFile); try { reporter.report(" - copying to ", outFile.getAbsolutePath()); copy(in, out); } finally { in = null; // copy() ALWAYS closes it for us out = null; // copy() ALWAYS closes it for us } } finally { if(in != null) { try { in.close(); } catch(IOException eat) { } } } } } Map entries = new TreeMap<>(); ExecutorThread executor = null; switch(spec.getModule()) { case "qtjambi": executor = new ExecutorThread(); executor.start(); break; } try { for (LibraryEntry e : spec.getLibraries()) { entries.put(e.getName(), e); if(!(e instanceof SymlinkEntry)) { String libName = e.getName(); File outFile = new File(tmpDir, libName.replace('/', File.separatorChar)); if(!outFile.exists()) { boolean isplugin = libName.startsWith("plugins/") || libName.startsWith("qml/") || libName.startsWith("translations/"); String _urlBase = urlBase; if(isplugin) { outFile.getParentFile().mkdirs(); pluginLibraries.add(e); } if(executor!=null && ((!libName.contains("QtJambiGui") && !libName.contains("QtJambiWidgets")) || isplugin)) { CompletableFuture future = CompletableFuture.supplyAsync(()->{ reporter.report(" - copying over: '", libName, "'..."); InputStream in = null; OutputStream out = null; try { URL entryURL = new URL(_urlBase+libName); try { in = entryURL.openStream(); } catch (Exception e1) { return null; } File outFileDir = outFile.getParentFile(); if (!outFileDir.exists()) { reporter.report(" - creating directory: ", outFileDir.getAbsolutePath()); outFileDir.mkdirs(); } out = new FileOutputStream(outFile); try { reporter.report(" - copying to ", outFile.getAbsolutePath()); copy(in, out); } finally { in = null; // copy() ALWAYS closes it for us out = null; // copy() ALWAYS closes it for us } } catch (IOException e1) { return e1; } finally { if(in != null) { try { in.close(); } catch(IOException eat) { } } } return null; }, executor); e.addExtractionFunction(()->{ Exception exn = future.get(); if(exn!=null) throw exn; }); }else { e.addExtractionFunction(()->{ reporter.report(" - copying over: '", libName, "'..."); InputStream in = null; OutputStream out = null; try { URL entryURL = new URL(_urlBase+libName); try { in = entryURL.openStream(); } catch (Exception e1) { return; } File outFileDir = outFile.getParentFile(); if (!outFileDir.exists()) { reporter.report(" - creating directory: ", outFileDir.getAbsolutePath()); outFileDir.mkdirs(); } out = new FileOutputStream(outFile); try { reporter.report(" - copying to ", outFile.getAbsolutePath()); copy(in, out); } finally { in = null; // copy() ALWAYS closes it for us out = null; // copy() ALWAYS closes it for us } } finally { if(in != null) { try { in.close(); } catch(IOException eat) { } } } }); } } } } List shiftedLinks = new ArrayList<>(); for (LibraryEntry e : spec.getLibraries()) { if(e instanceof SymlinkEntry) { File outFile = new File(tmpDir, e.getName().replace('/', File.separatorChar)); if(!outFile.exists()) { SymlinkEntry s = (SymlinkEntry)e; File outFileDir = outFile.getParentFile(); if (!outFileDir.exists()) { reporter.report(" - creating directory: ", outFileDir.getAbsolutePath()); outFileDir.mkdirs(); } File target = new File(tmpDir, s.getTarget().replace('/', File.separatorChar)); if(target.exists()) { reporter.report(" - creating symbolic link ", outFile.getAbsolutePath()); Files.createSymbolicLink(outFile.toPath(), outFile.getParentFile().toPath().relativize(target.toPath())); }else { LibraryEntry linkedEntry = entries.get(s.getTarget()); if(linkedEntry!=null) { linkedEntry.addExtractionFunction(()->{ reporter.report(" - creating symbolic link ", outFile.getAbsolutePath()); Files.createSymbolicLink(outFile.toPath(), outFile.getParentFile().toPath().relativize(target.toPath())); s.extract(); }); }else { shiftedLinks.add(s); } } } } } if(!shiftedLinks.isEmpty()) { List _shiftedLinks = new ArrayList<>(); while(!shiftedLinks.isEmpty()) { _shiftedLinks.clear(); for (SymlinkEntry s : shiftedLinks) { File outFile = new File(tmpDir, s.getName().replace('/', File.separatorChar)); File outFileDir = outFile.getParentFile(); if (!outFileDir.exists()) { reporter.report(" - creating directory: ", outFileDir.getAbsolutePath()); outFileDir.mkdirs(); } File target = new File(tmpDir, s.getTarget().replace('/', File.separatorChar)); if(target.exists()) { reporter.report(" - creating symbolic link ", outFile.getAbsolutePath()); Files.createSymbolicLink(outFile.toPath(), outFile.getParentFile().toPath().relativize(target.toPath())); }else { LibraryEntry linkedEntry = entries.get(s.getTarget()); if(linkedEntry!=null) { linkedEntry.addExtractionFunction(()->{ reporter.report(" - creating symbolic link ", outFile.getAbsolutePath()); Files.createSymbolicLink(outFile.toPath(), outFile.getParentFile().toPath().relativize(target.toPath())); s.extract(); }); }else { _shiftedLinks.add(s); } } } if(_shiftedLinks.size()==shiftedLinks.size()) { break; } shiftedLinks.clear(); shiftedLinks.addAll(_shiftedLinks); } } if (dummyFile != null && !dummyFile.createNewFile()) { throw new DeploymentSpecException("Can't create dummy file in cache directory"); } spec.setBaseDir(tmpDir); spec.setBaseUrl(new URL(convertAbsolutePathStringToFileUrlString(tmpDir))); }finally { if(executor!=null) executor.setNoMoreExpected(); } } else if(spec.getBaseUrl() == null) { String path = deploymentSpec.getPath(); int i = path.lastIndexOf('/'); // URL path if(i >= 0) path = path.substring(0, i); spec.setBaseDir(new File(path)); spec.setBaseUrl(new URL(deploymentSpec, path)); } return spec; } private static boolean loadSystemLibrary(String name) { reporter.report(" - trying to load: ", name); File foundFile = null; for(DeploymentSpec deploymentSpec : deploymentSpec) { File f = new File(deploymentSpec.getBaseDir(), name); if(f.isFile()) { foundFile = f; break; } } if(foundFile == null) return false; Runtime rt = Runtime.getRuntime(); rt.load(foundFile.getAbsolutePath()); reporter.report(" - ok!"); return true; } private static String jambiLibraryName(String prefix, String lib, int[] version, List replacements) { if(prefix!=null) lib = prefix + lib; switch (operatingSystem) { case Windows: if(version.length>0) replacements.add(""+version[0]); replacements.add(""); break; case MacOSX: switch(version.length) { default: replacements.add("."+version[0]); replacements.add("."+version[0]+"."+version[1]); replacements.add("."+version[0]+"."+version[1]+"."+version[2]); replacements.add(""); break; case 2: replacements.add("."+version[0]); replacements.add("."+version[0]+"."+version[1]); replacements.add(""); break; case 1: replacements.add("."+version[0]); case 0: replacements.add(""); break; } break; default: switch(version.length) { default: replacements.add("."+version[0]+"."+version[1]+"."+version[2]); case 2: replacements.add("."+version[0]+"."+version[1]); case 1: replacements.add("."+version[0]); case 0: replacements.add(""); break; } break; } switch (operatingSystem) { case Windows: { if (configuration == Configuration.Debug) lib += "d"; return lib + "%1$s.dll"; // "foobar1.dll" } case MacOSX: if (configuration == Configuration.Debug) lib += "_debug"; return "lib" + lib + "%1$s.jnilib"; case Linux: case Android: if (configuration == Configuration.Debug) lib += "_debug"; return "lib" + lib + ".so%1$s"; default: break; } throw new RuntimeException("Unreachable statement"); } private static String qtLibraryName(String qtprefix, String lib, String libInfix, int[] version, String versionStrg, Configuration configuration, List replacements) { if(libInfix==null){ libInfix = ""; } String _lib = lib; if(qtprefix!=null && version!=null && version.length>0){ _lib = qtprefix + version[0] + lib; } String prefix = _lib.startsWith("lib") ? "" : "lib"; switch (operatingSystem) { case Windows: replacements.add(""); break; default: if(version!=null) { switch(version.length) { default: replacements.add("."+version[0]+"."+version[1]+"."+version[2]); case 2: replacements.add("."+version[0]+"."+version[1]); case 1: replacements.add("."+version[0]); case 0: replacements.add(""); break; } }else if(versionStrg!=null) { replacements.add("."+versionStrg); replacements.add(""); }else { replacements.add(""); } break; } switch (operatingSystem) { case Windows: return configuration == Configuration.Debug && !isMinGWBuilt ? _lib + libInfix + "d.dll" // "QtFood4.dll" : _lib + libInfix + ".dll"; // "QtFoo4.dll" case MacOSX: if(dontUseFrameworks==null || !dontUseFrameworks) { if(version!=null && version.length>0 && version[0]<6) return String.format("%1$s%2$s.framework/Versions/%3$s/%1$s%2$s", qtprefix, lib, version[0]); else return String.format("%1$s%2$s.framework/Versions/A/%1$s%2$s", qtprefix, lib); }else { if(version!=null && version.length>1 && version[0]>=5 && version[1]>=14) { return prefix + _lib + libInfix + "%1$s.dylib"; }else { return configuration == Configuration.Debug ? prefix + _lib + libInfix + "_debug%1$s.dylib" // "libQtFoo_debug.4.dylib" : prefix + _lib + libInfix + "%1$s.dylib"; // "libQtFoo.4.dylib" } } case Linux: case Android: // Linux doesn't have a dedicated "debug" library since 4.2 return prefix + _lib + libInfix + ".so%1$s"; // "libQtFoo.so.4" default: break; } throw new RuntimeException("Unreachable statement"); } /** * Copies the data in the inputstream into the output stream. * @param in The source. * @param out The destination. * * @throws IOException when there is a problem... */ private static boolean copy(InputStream in, OutputStream out) throws IOException { boolean bf = false; try { byte buffer[] = new byte[1024 * 8]; int n; while((n = in.read(buffer)) > 0) { out.write(buffer, 0, n); } out.close(); out = null; in.close(); in = null; bf = true; } finally { if(out != null) { try { out.close(); } catch(IOException eat) { } out = null; } if(in != null) { try { in.close(); } catch(IOException eat) { } in = null; } } return bf; } static boolean isUsingDeploymentSpec() { return !deploymentSpec.isEmpty(); } // This is implemented so that String#replace(char, char) is not used as this is regexp based // and sounds heavy weight for us. Maybe I should performance test this point before just // implementing this but bleh! private static String stringCharReplace(String s, char fromChar, char toChar) { final int len = s.length(); StringBuilder sb = new StringBuilder(); for(int i = 0; i < len; i++) { char c = s.charAt(i); if(c == fromChar) sb.append(toChar); else sb.append(c); } return sb.toString(); } private static String convertAbsolutePathStringToFileUrlString(String pathString) { if(File.separatorChar == '\\') pathString = stringCharReplace(pathString, '\\', '/'); // windows String schemePrefix; if(pathString.length() > 0 && pathString.charAt(0) != '/') schemePrefix = "file:///"; // empty authority part, ensure leading / in path part else schemePrefix = "file://"; // empty authority part, pathString already has leading / return schemePrefix + pathString; } private static String convertAbsolutePathStringToFileUrlString(File file) { return convertAbsolutePathStringToFileUrlString(file.getAbsolutePath()); } private static List mergeJniLibdir(List middle) { if((jniLibdirBeforeList != null && !jniLibdirBeforeList.isEmpty()) || (jniLibdirList != null && !jniLibdirList.isEmpty())) { List newList = new ArrayList(); if(jniLibdirBeforeList != null) newList.addAll(jniLibdirBeforeList); newList.addAll(middle); if(jniLibdirList != null) newList.addAll(jniLibdirList); middle = newList; } Set set = new TreeSet<>(); for (int i = 0; i < middle.size(); ++i) { String a = middle.get(i); if(!set.contains(a)) set.add(a); else { middle.remove(i); --i; } } return middle; } static void loadSystemLibraries() { if (systemLibrariesList != null) { for (String s : systemLibrariesList) { // FIXME: We want only append the suffix (no prefix, no Qt version, no debug extra) // currently is does add a prefix (maybe also debug extra). NativeLibraryManager.loadSystemLibrary(s); } } } /** * Converts the string version number into an array of * @param versionString Version string such as "1.2.3" * @return Array of new int[] { 1, 2, 3 }; * @throws NumberFormatException */ static int[] getVersion(String versionString) { String[] sA = versionString.split("\\."); int[] vA = new int[sA.length]; int i = 0; for(String s : sA) vA[i++] = Integer.valueOf(s); return vA; } /** * Enum for defining the operation system. */ enum OperatingSystem { Windows, MacOSX, Linux, Android } /** * Enum for defining whether Qt is build in Release or Debug. */ enum Configuration { Release, Debug, Test; } static String qtJambiLibraryPath() { return qtJambiLibraryPath; } static String qtLibraryPath() { return qtLibraryPath; } } //!!NOTE!! This class can have no dependencies on Qt since //it is required for loading the libraries. class DeploymentSpecException extends RuntimeException { private static final long serialVersionUID = 1L; public DeploymentSpecException(String msg) { super(msg); } } class WrongSystemException extends DeploymentSpecException { private static final long serialVersionUID = 1L; public WrongSystemException(String msg) { super(msg); } } class WrongVersionException extends DeploymentSpecException { private static final long serialVersionUID = 1L; public WrongVersionException(String msg) { super(msg); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy