Skip to content

Commit

Permalink
NarSystemMojo: use native-lib-loader when it is a dependency
Browse files Browse the repository at this point in the history
When the native-lib-loader (https://github.com/scijava/native-lib-loader,
a library for unpacking and loading native libraries conveniently) is
among the dependencies, we should make use of it.

The current version of native-lib-loader (2.0.2) unfortunately expects
the native library to be bundled in a non-standard place, hence we have
to copy the library there.

To support the file layout produced by the NAR plugin, this uses
DefaultJniExtractor#extractJni directly instead of handing off to
NativeLibraryUtil (which would expect the bundled native libraries
in a different path).

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
  • Loading branch information
hkmoon committed Sep 29, 2014
1 parent b821868 commit e8851c8
Showing 1 changed file with 121 additions and 2 deletions.
123 changes: 121 additions & 2 deletions src/main/java/com/github/maven_nar/NarSystemMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.project.MavenProject;
import org.sonatype.plexus.build.incremental.BuildContext;

/**
Expand Down Expand Up @@ -82,13 +87,48 @@ public final void narExecute() throws MojoExecutionException, MojoFailureExcepti

final File narSystem = new File(fullDir, narSystemName + ".java");
getLog().info("Generating " + narSystem);
// initialize string variable to be used in NarSystem.java
final String importString, loadLibraryString, extraMethods, output = getOutput(true);
if (hasNativeLibLoaderAsDependency()) {
getLog().info("Using 'native-lib-loader'");
importString = "import java.io.File;\n"
+ "import java.net.URL;\n"
+ "import org.scijava.nativelib.DefaultJniExtractor;\n"
+ "import org.scijava.nativelib.JniExtractor;\n";
loadLibraryString = "final String fileName = \"" + output + "\";\n"
+ " final String mapped = System.mapLibraryName(fileName);\n"
+ " final String[] aols = getAOLs();\n"
+ " final ClassLoader loader = NarSystem.class.getClassLoader();\n"
+ " final File unpacked = getUnpackedLibPath(loader, aols, fileName, mapped);\n"
+ " if (unpacked != null) {\n"
+ " System.load(unpacked.getPath());\n"
+ " } else try {\n"
+ " final String libPath = getLibPath(loader, aols, mapped);\n"
+ " final JniExtractor extractor = new DefaultJniExtractor(NarSystem.class, System.getProperty(\"java.io.tmpdir\"));\n"
+ " final File extracted = extractor.extractJni(libPath, fileName);\n"
+ " System.load(extracted.getPath());\n"
+ " } catch (final Exception e) {\n"
+ " e.printStackTrace();\n"
+ " throw e instanceof RuntimeException ?\n"
+ " (RuntimeException) e : new RuntimeException(e);\n"
+ " }";
extraMethods = generateExtraMethods();
} else {
getLog().info("Not using 'native-lib-loader' because it is not a dependency)");
importString = null;
loadLibraryString = "System.loadLibrary(\"" + output + "\");";
extraMethods = null;
}

try {
final String output = getOutput(true);
final FileOutputStream fos = new FileOutputStream(narSystem);
final PrintWriter p = new PrintWriter(fos);
p.println("// DO NOT EDIT: Generated by NarSystemGenerate.");
p.println("package " + packageName + ";");
p.println("");
if (importString != null) {
p.println(importString);
}
p.println("/**");
p.println(" * Generated class to load the correct version of the jni library");
p.println(" *");
Expand All @@ -108,14 +148,17 @@ public final void narExecute() throws MojoExecutionException, MojoFailureExcepti
p.println(" */");
p.println(" public static void loadLibrary()");
p.println(" {");
p.println(" System.loadLibrary(\"" + output + "\");");
p.println(" " + loadLibraryString);
p.println(" }");
p.println("");
p.println(" public static int runUnitTests() {");
p.println(" return new NarSystem().runUnitTestsNative();");
p.println(" }");
p.println("");
p.println(" public native int runUnitTestsNative();");
if (extraMethods != null) {
p.println(extraMethods);
}
p.println("}");
p.close();
fos.close();
Expand All @@ -127,4 +170,80 @@ public final void narExecute() throws MojoExecutionException, MojoFailureExcepti
buildContext.refresh(narSystem);
}
}

private boolean hasNativeLibLoaderAsDependency() {
for (MavenProject project = getMavenProject(); project != null; project = project.getParent()) {
@SuppressWarnings("unchecked")
final List<Dependency> dependencies = project.getDependencies();
for (final Dependency dependency : dependencies) {
final String artifactId = dependency.getArtifactId();
if ("native-lib-loader".equals(artifactId))
return true;
}
}
return false;
}

private String generateExtraMethods() throws MojoFailureException {
final StringBuilder builder = new StringBuilder();

builder.append("\n private static String[] getAOLs() {\n");
builder.append(" final String ao = System.getProperty(\"os.arch\") + \"-\" + System.getProperty(\"os.name\").replaceAll(\" \", \"\");\n");

// build map: AO -> AOLs
final Map<String, List<String>> aoMap = new LinkedHashMap<String, List<String>>();
for (final String aol : NarProperties.getInstance(getMavenProject()).getKnownAOLs()) {
int dash = aol.lastIndexOf('-');
final String ao = aol.substring(0, dash);
List<String> list = aoMap.get(ao);
if (list == null) {
aoMap.put(ao, list = new ArrayList<String>());
}
list.add(aol);
}

builder.append("\n // choose the list of known AOLs for the current platform\n");
String delimiter = " ";
for (final Map.Entry<String, List<String>> entry : aoMap.entrySet()) {
builder.append(delimiter);
delimiter = " else ";

builder.append("if (ao.equals(\"").append(entry.getKey()).append("\")) {\n");
builder.append(" return new String[] {\n");
String delimiter2 = " ";
for (final String aol : entry.getValue()) {
builder.append(delimiter2).append("\"").append(aol).append("\"");
delimiter2 = ", ";
}
builder.append("\n };");
builder.append("\n }");
}
builder.append(" else {\n");
builder.append(" throw new RuntimeException(\"Unhandled architecture/OS: \" + ao);\n");
builder.append(" }\n");
builder.append(" }\n");
builder.append("\n");
builder.append(" private static File getUnpackedLibPath(final ClassLoader loader, final String[] aols, final String fileName, final String mapped) {\n");
builder.append(" final String classPath = NarSystem.class.getName().replace('.', '/') + \".class\";\n");
builder.append(" final URL url = loader.getResource(classPath);\n");
builder.append(" if (url == null || !\"file\".equals(url.getProtocol())) return null;\n");
builder.append(" final String path = url.getPath();\n");
builder.append(" final String prefix = path.substring(0, path.length() - classPath.length()) + \"../nar/\" + fileName + \"-\";\n");
builder.append(" for (final String aol : aols) {\n");
builder.append(" final File file = new File(prefix + aol + \"-jni/lib/\" + aol + \"/jni/\" + mapped);\n");
builder.append(" if (file.isFile()) return file;\n");
builder.append(" }\n");
builder.append(" return null;\n");
builder.append(" }\n");
builder.append("\n");
builder.append(" private static String getLibPath(final ClassLoader loader, final String[] aols, final String mapped) {\n");
builder.append(" for (final String aol : aols) {\n");
builder.append(" final String libPath = \"lib/\" + aol + \"/jni/\";\n");
builder.append(" if (loader.getResource(libPath + mapped) != null) return libPath;\n");
builder.append(" }\n");
builder.append(" throw new RuntimeException(\"Library '\" + mapped + \"' not found!\");\n");
builder.append(" }\n");

return builder.toString();
}
}

0 comments on commit e8851c8

Please sign in to comment.