View Javadoc

1   /*
2    * #%L
3    * Native ARchive plugin for Maven
4    * %%
5    * Copyright (C) 2002 - 2014 NAR Maven Plugin developers.
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   * http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package com.github.maven_nar;
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.util.ArrayList;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.maven.model.Dependency;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.MojoFailureException;
34  import org.apache.maven.plugins.annotations.LifecyclePhase;
35  import org.apache.maven.plugins.annotations.Mojo;
36  import org.apache.maven.project.MavenProject;
37  import org.sonatype.plexus.build.incremental.BuildContext;
38  
39  /**
40   * Generates a NarSystem class with static methods to use inside the java part
41   * of the library. Runs in generate-resources rather than generate-sources to
42   * allow the maven-swig-plugin (which runs in generate-sources) to configure the
43   * nar plugin and to let it generate a proper system file.
44   *
45   * @author Mark Donszelmann
46   */
47  @Mojo(name = "nar-system-generate", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresProject = true)
48  public class NarSystemMojo extends AbstractNarMojo {
49  
50    /** @component */
51    private BuildContext buildContext;
52  
53    private String generateExtraMethods() throws MojoFailureException {
54      final StringBuilder builder = new StringBuilder();
55  
56      builder.append("\n    private static String[] getAOLs() {\n");
57      builder
58          .append("        final String ao = System.getProperty(\"os.arch\") + \"-\" + System.getProperty(\"os.name\").replaceAll(\" \", \"\");\n");
59  
60      // build map: AO -> AOLs
61      final Map<String, List<String>> aoMap = new LinkedHashMap<>();
62      for (final String aol : NarProperties.getInstance(getMavenProject()).getKnownAOLs()) {
63        final int dash = aol.lastIndexOf('-');
64        final String ao = aol.substring(0, dash);
65        List<String> list = aoMap.get(ao);
66        if (list == null) {
67          aoMap.put(ao, list = new ArrayList<>());
68        }
69        list.add(aol);
70      }
71  
72      builder.append("\n        // choose the list of known AOLs for the current platform\n");
73      String delimiter = "        ";
74      for (final Map.Entry<String, List<String>> entry : aoMap.entrySet()) {
75        builder.append(delimiter);
76        delimiter = " else ";
77  
78        builder.append("if (ao.startsWith(\"").append(entry.getKey()).append("\")) {\n");
79        builder.append("            return new String[] {\n");
80        String delimiter2 = "              ";
81        for (final String aol : entry.getValue()) {
82          builder.append(delimiter2).append("\"").append(aol).append("\"");
83          delimiter2 = ", ";
84        }
85        builder.append("\n            };");
86        builder.append("\n        }");
87      }
88      builder.append(" else {\n");
89      builder.append("            throw new RuntimeException(\"Unhandled architecture/OS: \" + ao);\n");
90      builder.append("        }\n");
91      builder.append("    }\n");
92      builder.append("\n");
93      builder.append("    private static String[] getMappedLibraryNames(String fileName) {\n");
94      builder.append("        String mapped = System.mapLibraryName(fileName);\n");
95      builder
96      .append("        final String ao = System.getProperty(\"os.arch\") + \"-\" + System.getProperty(\"os.name\").replaceAll(\" \", \"\");\n");
97      builder.append("    	if (ao.startsWith(\"x86_64-MacOSX\")){\n");
98      builder.append("    		// .jnilib or .dylib depends on JDK version\n");
99      builder.append("    		mapped = mapped.substring(0, mapped.lastIndexOf('.'));\n");
100     builder.append("    		return new String[]{mapped+\".dylib\", mapped+\".jnilib\"};\n");
101     builder.append("    	}\n");
102     builder.append("    	return new String[]{mapped};\n");
103     builder.append("    }\n");
104     builder.append("\n");
105     builder
106         .append("    private static File getUnpackedLibPath(final ClassLoader loader, final String[] aols, final String fileName, final String[] mappedNames) {\n");
107     builder.append("        final String classPath = NarSystem.class.getName().replace('.', '/') + \".class\";\n");
108     builder.append("        final URL url = loader.getResource(classPath);\n");
109     builder.append("        if (url == null || !\"file\".equals(url.getProtocol())) return null;\n");
110     builder.append("        final String path = url.getPath();\n");
111     builder
112         .append("        final String prefix = path.substring(0, path.length() - classPath.length()) + \"../nar/\" + fileName + \"-\";\n");
113     builder.append("        for (final String aol : aols) {\n");
114     builder.append("            for(final String mapped : mappedNames) {\n");
115     builder
116         .append("                final File file = new File(prefix + aol + \"-jni/lib/\" + aol + \"/jni/\" + mapped);\n");
117     builder.append("                if (file.isFile()) return file;\n");
118     builder.append("                final File fileShared = new File(prefix + aol + \"-shared/lib/\" + aol + \"/shared/\" + mapped);\n");
119     builder.append("                if (fileShared.isFile()) return fileShared;\n");
120     builder.append("            }\n");
121     builder.append("        }\n");
122     builder.append("        return null;\n");
123     builder.append("    }\n");
124     builder.append("\n");
125     builder
126         .append("    private static String getLibPath(final ClassLoader loader, final String[] aols, final String[] mappedNames) {\n");
127     builder.append("        for (final String aol : aols) {\n");
128     builder.append("            final String libPath = \"lib/\" + aol + \"/jni/\";\n");
129     builder.append("            final String libPathShared = \"lib/\" + aol + \"/shared/\";\n");
130     builder.append("            for(final String mapped : mappedNames) {\n");
131     builder.append("                if (loader.getResource(libPath + mapped) != null) return libPath;\n");
132     builder.append("                if (loader.getResource(libPathShared + mapped) != null) return libPathShared;\n");
133     builder.append("            }\n");
134     builder.append("        }\n");
135     builder.append("        throw new RuntimeException(\"Library '\" + mappedNames[0] + \"' not found!\");\n");
136     builder.append("    }\n");
137 
138     return builder.toString();
139   }
140 
141   private boolean hasNativeLibLoaderAsDependency() {
142     for (MavenProject project = getMavenProject(); project != null; project = project.getParent()) {
143       final List<Dependency> dependencies = project.getDependencies();
144       for (final Dependency dependency : dependencies) {
145         final String artifactId = dependency.getArtifactId();
146         if ("native-lib-loader".equals(artifactId)) {
147           return true;
148         }
149       }
150     }
151     return false;
152   }
153 
154   @Override
155   public final void narExecute() throws MojoExecutionException, MojoFailureException {
156     // get packageName if specified for JNI.
157     String packageName = null;
158     String narSystemName = null;
159     File narSystemDirectory = null;
160     boolean jniFound = false;
161     for (final Library library : getLibraries()) {
162       if (library.getType().equals(Library.JNI) || library.getType().equals(Library.SHARED)) {
163         packageName = library.getNarSystemPackage();
164         narSystemName = library.getNarSystemName();
165         narSystemDirectory = new File(getTargetDirectory(), library.getNarSystemDirectory());
166         jniFound = true;
167       }
168     }
169 
170     if (!jniFound || packageName == null) {
171       if (!jniFound) {
172         getLog().debug("NAR: not building a shared or JNI library, so not generating NarSystem class.");
173       } else {
174         getLog().warn("NAR: no system package specified; unable to generate NarSystem class.");
175       }
176       return;
177     }
178 
179     // make sure destination is there
180     narSystemDirectory.mkdirs();
181 
182     getMavenProject().addCompileSourceRoot(narSystemDirectory.getPath());
183 
184     final File fullDir = new File(narSystemDirectory, packageName.replace('.', '/'));
185     fullDir.mkdirs();
186 
187     final File narSystem = new File(fullDir, narSystemName + ".java");
188     getLog().info("Generating " + narSystem);
189     // initialize string variable to be used in NarSystem.java
190     final String importString, loadLibraryString, extraMethods, output = getOutput(true);
191     if (hasNativeLibLoaderAsDependency()) {
192       getLog().info("Using 'native-lib-loader'");
193       importString = "import java.io.File;\n" + "import java.net.URL;\n"
194           + "import org.scijava.nativelib.DefaultJniExtractor;\n" + "import org.scijava.nativelib.JniExtractor;\n";
195       loadLibraryString = "final String fileName = \""
196           + output
197           + "\";\n"
198           + "        //first try if the library is on the configured library path\n"
199           + "        try {\n"
200           + "            System.loadLibrary(\""
201           + output
202           + "\");\n"
203           + "            return;\n"
204           + "        }\n"
205           + "        catch (Exception e) {\n"
206           + "        }\n"
207           + "        catch (UnsatisfiedLinkError e) {\n"
208           + "        }\n"
209           + "        final String[] mappedNames = getMappedLibraryNames(fileName);\n"
210           + "        final String[] aols = getAOLs();\n"
211           + "        final ClassLoader loader = NarSystem.class.getClassLoader();\n"
212           + "        final File unpacked = getUnpackedLibPath(loader, aols, fileName, mappedNames);\n"
213           + "        if (unpacked != null) {\n"
214           + "            System.load(unpacked.getPath());\n"
215           + "        } else try {\n"
216           + "            final String libPath = getLibPath(loader, aols, mappedNames);\n"
217           + "            final JniExtractor extractor = new DefaultJniExtractor(NarSystem.class, System.getProperty(\"java.io.tmpdir\"));\n"
218           + "            final File extracted = extractor.extractJni(libPath, fileName);\n"
219           + "            System.load(extracted.getPath());\n" + "        } catch (final Exception e) {\n"
220           + "            e.printStackTrace();\n" + "            throw e instanceof RuntimeException ?\n"
221           + "                (RuntimeException) e : new RuntimeException(e);\n" + "        }";
222       extraMethods = generateExtraMethods();
223     } else {
224       getLog().info("Not using 'native-lib-loader' because it is not a dependency)");
225       importString = null;
226       loadLibraryString = "System.loadLibrary(\"" + output + "\");";
227       extraMethods = null;
228     }
229 
230     try {
231       final FileOutputStream fos = new FileOutputStream(narSystem);
232       final PrintWriter p = new PrintWriter(fos);
233       p.println("// DO NOT EDIT: Generated by NarSystemGenerate.");
234       p.println("package " + packageName + ";");
235       p.println("");
236       if (importString != null) {
237         p.println(importString);
238       }
239       p.println("/**");
240       p.println(" * Generated class to load the correct version of the jni library");
241       p.println(" *");
242       p.println(" * @author nar-maven-plugin");
243       p.println(" */");
244       p.println("public final class NarSystem");
245       p.println("{");
246       p.println("");
247       p.println("    private NarSystem() ");
248       p.println("    {");
249       p.println("    }");
250       p.println("");
251       p.println("    /**");
252       p.println("     * Load jni library: " + output);
253       p.println("     *");
254       p.println("     * @author nar-maven-plugin");
255       p.println("     */");
256       p.println("    public static void loadLibrary()");
257       p.println("    {");
258       p.println("        " + loadLibraryString);
259       p.println("    }");
260       p.println("");
261       p.println("    public static int runUnitTests() {");
262       p.println("	       return new NarSystem().runUnitTestsNative();");
263       p.println("    }");
264       p.println("");
265       p.println("    public native int runUnitTestsNative();");
266       if (extraMethods != null) {
267         p.println(extraMethods);
268       }
269       p.println("}");
270       p.close();
271       fos.close();
272     } catch (final IOException e) {
273       throw new MojoExecutionException("Could not write '" + narSystemName + "'", e);
274     }
275 
276     if (this.buildContext != null) {
277       this.buildContext.refresh(narSystem);
278     }
279   }
280 }