1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
41
42
43
44
45
46
47 @Mojo(name = "nar-system-generate", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresProject = true)
48 public class NarSystemMojo extends AbstractNarMojo {
49
50
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
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
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
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
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 }