Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maven-like repository for JavaCPP resources #120

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@
<version>3.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-api</artifactId>
<version>1.1.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
193 changes: 75 additions & 118 deletions src/main/java/org/bytedeco/javacpp/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<String,String> loadedLibraries = Collections.synchronizedMap(new HashMap<String,String>());

Expand All @@ -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")}.
Expand Down Expand Up @@ -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}.
*
Expand All @@ -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) {
Expand All @@ -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<String> targets = p.get("target");
if (targets.isEmpty()) {
Expand Down Expand Up @@ -488,17 +501,17 @@ 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;
}
}

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);
Expand All @@ -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];
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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<String> command = new ArrayList<String>();
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}
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/org/bytedeco/javacpp/annotation/Coordinates.java
Original file line number Diff line number Diff line change
@@ -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();
}
10 changes: 9 additions & 1 deletion src/main/java/org/bytedeco/javacpp/tools/BuildMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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", "");
Expand Down
Loading