/* * #%L * Native ARchive plugin for Maven * %% * Copyright (C) 2002 - 2014 NAR Maven Plugin developers. * %% * Licensed under the Apache License, Version 2.0 (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 * * 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. * #L% */ package com.github.maven_nar; import java.io.File; 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; /** * Generates a NarSystem class with static methods to use inside the java part * of the library. Runs in generate-resources rather than generate-sources to * allow the maven-swig-plugin (which runs in generate-sources) to configure the * nar plugin and to let it generate a proper system file. * * @author Mark Donszelmann */ @Mojo(name = "nar-system-generate", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresProject = true) public class NarSystemMojo extends AbstractNarMojo { /** @component */ private BuildContext buildContext; 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<>(); for (final String aol : NarProperties.getInstance(getMavenProject()).getKnownAOLs()) { final 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<>()); } 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.startsWith(\"").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 String[] getMappedLibraryNames(String fileName) {\n"); builder.append(" String mapped = System.mapLibraryName(fileName);\n"); builder .append(" final String ao = System.getProperty(\"os.arch\") + \"-\" + System.getProperty(\"os.name\").replaceAll(\" \", \"\");\n"); builder.append(" if (ao.startsWith(\"x86_64-MacOSX\")){\n"); builder.append(" // .jnilib or .dylib depends on JDK version\n"); builder.append(" mapped = mapped.substring(0, mapped.lastIndexOf('.'));\n"); builder.append(" return new String[]{mapped+\".dylib\", mapped+\".jnilib\"};\n"); builder.append(" }\n"); builder.append(" return new String[]{mapped};\n"); builder.append(" }\n"); builder.append("\n"); builder .append(" private static File getUnpackedLibPath(final ClassLoader loader, final String[] aols, final String fileName, final String[] mappedNames) {\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(" for(final String mapped : mappedNames) {\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(" final File fileShared = new File(prefix + aol + \"-shared/lib/\" + aol + \"/shared/\" + mapped);\n"); builder.append(" if (fileShared.isFile()) return fileShared;\n"); builder.append(" }\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[] mappedNames) {\n"); builder.append(" for (final String aol : aols) {\n"); builder.append(" final String libPath = \"lib/\" + aol + \"/jni/\";\n"); builder.append(" final String libPathShared = \"lib/\" + aol + \"/shared/\";\n"); builder.append(" for(final String mapped : mappedNames) {\n"); builder.append(" if (loader.getResource(libPath + mapped) != null) return libPath;\n"); builder.append(" if (loader.getResource(libPathShared + mapped) != null) return libPathShared;\n"); builder.append(" }\n"); builder.append(" }\n"); builder.append(" throw new RuntimeException(\"Library '\" + mappedNames[0] + \"' not found!\");\n"); builder.append(" }\n"); return builder.toString(); } private boolean hasNativeLibLoaderAsDependency() { for (MavenProject project = getMavenProject(); project != null; project = project.getParent()) { 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; } @Override public final void narExecute() throws MojoExecutionException, MojoFailureException { // get packageName if specified for JNI. String packageName = null; String narSystemName = null; File narSystemDirectory = null; boolean jniFound = false; for (final Library library : getLibraries()) { if (library.getType().equals(Library.JNI) || library.getType().equals(Library.SHARED)) { packageName = library.getNarSystemPackage(); narSystemName = library.getNarSystemName(); narSystemDirectory = new File(getTargetDirectory(), library.getNarSystemDirectory()); jniFound = true; } } if (!jniFound || packageName == null) { if (!jniFound) { getLog().debug("NAR: not building a shared or JNI library, so not generating NarSystem class."); } else { getLog().warn("NAR: no system package specified; unable to generate NarSystem class."); } return; } // make sure destination is there narSystemDirectory.mkdirs(); getMavenProject().addCompileSourceRoot(narSystemDirectory.getPath()); final File fullDir = new File(narSystemDirectory, packageName.replace('.', '/')); fullDir.mkdirs(); 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" + " //first try if the library is on the configured library path\n" + " try {\n" + " System.loadLibrary(\"" + output + "\");\n" + " return;\n" + " }\n" + " catch (Exception e) {\n" + " }\n" + " catch (UnsatisfiedLinkError e) {\n" + " }\n" + " final String[] mappedNames = getMappedLibraryNames(fileName);\n" + " final String[] aols = getAOLs();\n" + " final ClassLoader loader = NarSystem.class.getClassLoader();\n" + " final File unpacked = getUnpackedLibPath(loader, aols, fileName, mappedNames);\n" + " if (unpacked != null) {\n" + " System.load(unpacked.getPath());\n" + " } else try {\n" + " final String libPath = getLibPath(loader, aols, mappedNames);\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 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(" *"); p.println(" * @author nar-maven-plugin"); p.println(" */"); p.println("public final class NarSystem"); p.println("{"); p.println(""); p.println(" private NarSystem() "); p.println(" {"); p.println(" }"); p.println(""); p.println(" /**"); p.println(" * Load jni library: " + output); p.println(" *"); p.println(" * @author nar-maven-plugin"); p.println(" */"); p.println(" public static void loadLibrary()"); p.println(" {"); 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(); } catch (final IOException e) { throw new MojoExecutionException("Could not write '" + narSystemName + "'", e); } if (this.buildContext != null) { this.buildContext.refresh(narSystem); } } }