diff --git a/pom.xml b/pom.xml index 73c2a17f8..953aba2ca 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,12 @@ 3.0 true + + org.eclipse.aether + aether-api + 1.1.0 + true + junit junit diff --git a/src/main/java/org/bytedeco/javacpp/Loader.java b/src/main/java/org/bytedeco/javacpp/Loader.java index 094270b4e..398f530fc 100644 --- a/src/main/java/org/bytedeco/javacpp/Loader.java +++ b/src/main/java/org/bytedeco/javacpp/Loader.java @@ -23,15 +23,22 @@ package org.bytedeco.javacpp; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -42,7 +49,9 @@ import java.util.WeakHashMap; import org.bytedeco.javacpp.annotation.Platform; import org.bytedeco.javacpp.tools.Builder; +import org.bytedeco.javacpp.tools.Coordinates; import org.bytedeco.javacpp.tools.Logger; +import org.bytedeco.javacpp.tools.Repository; /** * The Loader contains functionality to load native libraries, but also has a bit @@ -361,8 +370,8 @@ public static File extractResource(URL resourceURL, File directoryOrFile, /** User-specified cache directory set and returned by {@link #getCacheDir()}. */ static File cacheDir = null; - /** Temporary directory set and returned by {@link #getTempDir()}. */ - static File tempDir = null; + /** JavaCPP repository */ + static Repository repository = null; /** Contains all the native libraries that we have loaded to avoid reloading them. */ static Map loadedLibraries = Collections.synchronizedMap(new HashMap()); @@ -385,25 +394,19 @@ public static File getCacheDir() { } /** - * Creates a unique name for {@link #tempDir} out of - * {@code System.getProperty("java.io.tmpdir")} and {@code System.nanoTime()}. - * - * @return {@link #tempDir} + * Creates and returns the repository in which native libraries are stored. */ - public static File getTempDir() { - if (tempDir == null) { - File tmpdir = new File(System.getProperty("java.io.tmpdir")); - File f; - for (int i = 0; i < 1000; i++) { - f = new File(tmpdir, "javacpp" + System.nanoTime()); - if (f.mkdir()) { - tempDir = f; - tempDir.deleteOnExit(); - break; - } - } - } - return tempDir; + private static Repository getRepository() { + if (repository == null) { + Path root; + File cache = getCacheDir(); + if (cache != null) + root = cache.toPath(); + else + root = Paths.get(System.getProperty("user.home")); + repository = new Repository(root); + } + return repository; } /** Returns {@code System.getProperty("org.bytedeco.javacpp.loadlibraries")}. @@ -433,6 +436,7 @@ public static String load(boolean pathsFirst) { public static String load(Class cls) { return load(cls, loadProperties(), false); } + /** * Loads native libraries associated with the given {@link Class}. * @@ -443,8 +447,8 @@ public static String load(Class cls) { * (but {@code if (!isLoadLibraries() || cls == null) { return null; }}) * @throws NoClassDefFoundError on Class initialization failure * @throws UnsatisfiedLinkError on native library loading failure - * @see #findLibrary(Class, ClassProperties, String, boolean) - * @see #loadLibrary(URL[], String) + * @see #findLibrary(Class, ClassProperties, String, boolean, Coordinates) + * @see #loadLibrary(URL[], String, Coordinates) */ public static String load(Class cls, Properties properties, boolean pathsFirst) { if (!isLoadLibraries() || cls == null) { @@ -455,6 +459,15 @@ public static String load(Class cls, Properties properties, boolean pathsFirst) cls = getEnclosingClass(cls); ClassProperties p = loadProperties(cls, properties, true); + // Get library coordinates if available + Coordinates coordinates = null; + if (cls.isAnnotationPresent(org.bytedeco.javacpp.annotation.Coordinates.class)) { + org.bytedeco.javacpp.annotation.Coordinates c = + (org.bytedeco.javacpp.annotation.Coordinates) + cls.getAnnotation(org.bytedeco.javacpp.annotation.Coordinates.class); + coordinates = new Coordinates(c.group(), c.id(), c.version()); + } + // Force initialization of all the target classes in case they need it List targets = p.get("target"); if (targets.isEmpty()) { @@ -488,8 +501,8 @@ public static String load(Class cls, Properties properties, boolean pathsFirst) UnsatisfiedLinkError preloadError = null; for (String preload : preloads) { try { - URL[] urls = findLibrary(cls, p, preload, pathsFirst); - loadLibrary(urls, preload); + URL[] urls = findLibrary(cls, p, preload, pathsFirst, coordinates); + loadLibrary(urls, preload, coordinates); } catch (UnsatisfiedLinkError e) { preloadError = e; } @@ -497,8 +510,8 @@ public static String load(Class cls, Properties properties, boolean pathsFirst) try { String library = p.getProperty("platform.library"); - URL[] urls = findLibrary(cls, p, library, pathsFirst); - return loadLibrary(urls, library); + URL[] urls = findLibrary(cls, p, library, pathsFirst, coordinates); + return loadLibrary(urls, library, coordinates); } catch (UnsatisfiedLinkError e) { if (preloadError != null && e.getCause() == null) { e.initCause(preloadError); @@ -519,9 +532,11 @@ public static String load(Class cls, Properties properties, boolean pathsFirst) * @param libnameversion the name of the library + "@" + optional version tag * + "#" + a second optional name used at extraction (or empty to prevent it) * @param pathsFirst search the paths first before bundled resources + * @param coordinates maven coordinates * @return URLs that point to potential locations of the library */ - public static URL[] findLibrary(Class cls, ClassProperties properties, String libnameversion, boolean pathsFirst) { + public static URL[] findLibrary(Class cls, ClassProperties properties, String libnameversion, + boolean pathsFirst, Coordinates coordinates) { if (libnameversion.trim().endsWith("#")) { return new URL[0]; } @@ -629,7 +644,7 @@ public static URL[] findLibrary(Class cls, ClassProperties properties, String li * (but {@code if (!isLoadLibraries) { return null; }}) * @throws UnsatisfiedLinkError on failure */ - public static String loadLibrary(URL[] urls, String libnameversion) { + public static String loadLibrary(URL[] urls, String libnameversion, Coordinates coordinates) { if (!isLoadLibraries()) { return null; } @@ -640,7 +655,6 @@ public static String loadLibrary(URL[] urls, String libnameversion) { return filename; } - File tempFile = null; UnsatisfiedLinkError loadError = null; try { for (URL url : urls) { @@ -674,46 +688,39 @@ public static String loadLibrary(URL[] urls, String libnameversion) { // ... get the URL fragment to let users rename library files ... name = url.getRef(); } - // ... then check if it has not already been extracted, and if not ... - file = new File(getCacheDir() != null ? getCacheDir() : getTempDir(), name); - if (!file.exists()) { - if (tempFile != null && tempFile.exists()) { - tempFile.deleteOnExit(); - } - // ... then extract it from our resources ... - if (logger.isDebugEnabled()) { - logger.debug("Extracting " + url); - } - extractResource(url, file, null, null); - if (getCacheDir() == null) { - tempFile = file; - } - } else while (System.currentTimeMillis() - file.lastModified() < 1000) { - // ... else wait until the file is at least 1 second old ... - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - // ... reset interrupt to be nice ... - Thread.currentThread().interrupt(); - } - } + + // ... then get it from repo ... + + JarURLConnection c = (JarURLConnection) url.openConnection(); + Path jar = Paths.get(c.getJarFileURL().getFile()); + + // For legacy jars without coordinates + if (coordinates == null) + coordinates = new Coordinates( + "legacy", + jar.getFileName().toString(), + Long.toString(Files.size(jar)) + "-" + + Long.toString(jar.toFile().lastModified())); + + Path repo = getRepository().getPath( + new Coordinates(coordinates, getPlatform(), jar)); + file = new File(repo.toFile(), name); } - if (file != null && file.exists()) { - filename = file.getAbsolutePath(); - try { - // ... and load it! - if (logger.isDebugEnabled()) { - logger.debug("Loading " + filename); - } - loadedLibraries.put(libnameversion, filename); - System.load(filename); - return filename; - } catch (UnsatisfiedLinkError e) { - loadError = e; - loadedLibraries.remove(libnameversion); - if (logger.isDebugEnabled()) { - logger.debug("Failed to load " + filename + ": " + e); - } + + filename = file.getAbsolutePath(); + try { + // ... and load it! + if (logger.isDebugEnabled()) { + logger.debug("Loading " + filename); + } + loadedLibraries.put(libnameversion, filename); + System.load(filename); + return filename; + } catch (UnsatisfiedLinkError e) { + loadError = e; + loadedLibraries.remove(libnameversion); + if (logger.isDebugEnabled()) { + logger.debug("Failed to load " + filename + ": " + e); } } } @@ -745,59 +752,9 @@ public static String loadLibrary(URL[] urls, String libnameversion) { logger.debug("Failed to extract for " + libnameversion + ": " + e); } throw e; - } finally { - if (tempFile != null && tempFile.exists()) { - tempFile.deleteOnExit(); - } - // But under Windows, it won't get deleted! - } - } - - // So, let's use a shutdown hook... - static { - if (getPlatform().startsWith("windows")) { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override public void run() { - if (tempDir == null) { - return; - } - try { - // ... to launch a separate process ... - List command = new ArrayList(); - command.add(System.getProperty("java.home") + "/bin/java"); - command.add("-classpath"); - command.add((new File(Loader.class.getProtectionDomain().getCodeSource().getLocation().toURI())).toString()); - command.add(Loader.class.getName()); - command.add(tempDir.getAbsolutePath()); - new ProcessBuilder(command).start(); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - }); - } - } - - // ... that makes sure to delete all our files. - public static void main(String[] args) throws InterruptedException { - File tmpdir = new File(System.getProperty("java.io.tmpdir")); - File tempDir = new File(args[0]); - if (!tmpdir.equals(tempDir.getParentFile()) || - !tempDir.getName().startsWith("javacpp")) { - // Someone is trying to break us ... ? - return; } - for (File file : tempDir.listFiles()) { - while (file.exists() && !file.delete()) { - Thread.sleep(100); - } - } - tempDir.delete(); } - /** * Contains {@code offsetof()} and {@code sizeof()} values of native types * of {@code struct}, {@code class}, and {@code union}. A {@link WeakHashMap} diff --git a/src/main/java/org/bytedeco/javacpp/annotation/Coordinates.java b/src/main/java/org/bytedeco/javacpp/annotation/Coordinates.java new file mode 100644 index 000000000..74b33abe0 --- /dev/null +++ b/src/main/java/org/bytedeco/javacpp/annotation/Coordinates.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014-2016 Samuel Audet + * + * Licensed either under the Apache License, Version 2.0, or (at your option) + * under the terms of the GNU General Public License as published by + * the Free Software Foundation (subject to the "Classpath" exception), + * either version 2, or any later version (collectively, the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.gnu.org/licenses/ + * http://www.gnu.org/software/classpath/license.html + * + * or as provided in the LICENSE.txt file that accompanied this code. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bytedeco.javacpp.annotation; + +import java.lang.annotation.*; + +/** + * Uniquely identifies a library in Maven and the local JavaCPP repository. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Coordinates { + String group(); + String id(); + String version(); +} \ No newline at end of file diff --git a/src/main/java/org/bytedeco/javacpp/tools/BuildMojo.java b/src/main/java/org/bytedeco/javacpp/tools/BuildMojo.java index b562d0bfa..e247c8b35 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/BuildMojo.java +++ b/src/main/java/org/bytedeco/javacpp/tools/BuildMojo.java @@ -194,6 +194,12 @@ String[] merge(String[] ss, String s) { @Override public void warn (String s) { log.warn(s); } @Override public void error(String s) { log.error(s); } }; + + Coordinates coordinates = new Coordinates( + project.getGroupId(), + project.getArtifactId(), + project.getVersion() + ); Builder builder = new Builder(logger) .classPaths(classPaths) .outputDirectory(outputDirectory) @@ -208,10 +214,12 @@ String[] merge(String[] ss, String s) { .properties(propertyKeysAndValues) .classesOrPackages(classOrPackageNames) .environmentVariables(environmentVariables) - .compilerOptions(compilerOptions); + .compilerOptions(compilerOptions) + .coordinates(coordinates); Properties properties = builder.properties; log.info("Detected platform \"" + Loader.getPlatform() + "\""); log.info("Building for platform \"" + properties.get("platform") + "\""); + log.info("Coordinates for builder: " + coordinates.canonical()); String separator = properties.getProperty("platform.path.separator"); for (String s : merge(includePaths, includePath)) { String v = properties.getProperty("platform.includepath", ""); diff --git a/src/main/java/org/bytedeco/javacpp/tools/Builder.java b/src/main/java/org/bytedeco/javacpp/tools/Builder.java index 2f5a2c54a..6b3c20cac 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Builder.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Builder.java @@ -56,7 +56,8 @@ public class Builder { /** - * Calls {@link Parser#parse(File, String[], Class)} after creating an instance of the Class. + * Calls {@link Parser#parse(File, String[], Class, Coordinates)} after creating an + * instance of the Class. * * @param classPath an array of paths to try to load header files from * @param cls The class annotated with {@link org.bytedeco.javacpp.annotation.Properties} @@ -66,7 +67,7 @@ public class Builder { * @throws ParserException on C/C++ header file parsing error */ File parse(String[] classPath, Class cls) throws IOException, ParserException { - return new Parser(logger, properties).parse(outputDirectory, classPath, cls); + return new Parser(logger, properties).parse(outputDirectory, classPath, cls, coordinates); } /** @@ -503,6 +504,8 @@ public Builder(Logger logger) { Map environmentVariables = null; /** Contains additional command line options from the user for the native compiler. */ Collection compilerOptions = null; + /** Coordinates of the library. */ + Coordinates coordinates = null; /** Splits argument with {@link File#pathSeparator} and appends result to paths of the {@link #classScanner}. */ public Builder classPaths(String classPaths) { @@ -629,6 +632,11 @@ public Builder compilerOptions(String ... options) { } return this; } + /** Sets the library coordinates. */ + public Builder coordinates(Coordinates coordinates) { + this.coordinates = coordinates; + return this; + } /** * Starts the build process and returns an array of {@link File} produced. @@ -689,7 +697,7 @@ public File[] build() throws IOException, InterruptedException, ParserException File directory = f.getParentFile(); for (String s : preloads) { - URL[] urls = Loader.findLibrary(null, p, s, true); + URL[] urls = Loader.findLibrary(null, p, s, true, coordinates); File fi; try { fi = new File(urls[0].toURI()); @@ -757,6 +765,7 @@ public static void printHelp() { System.out.println(" -jarprefix Also create a JAR file named \"-.jar\""); System.out.println(" -properties Load all properties from resource"); System.out.println(" -propertyfile Load all properties from file"); + System.out.println(" -coordinates Library identifier (group:id:version)."); System.out.println(" -D= Set property to value"); System.out.println(" -Xcompiler