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.BufferedReader;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.*;
31 import java.util.regex.Pattern;
32
33 import org.apache.bcel.classfile.ClassParser;
34 import org.apache.bcel.classfile.JavaClass;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.apache.maven.plugin.MojoFailureException;
37 import org.apache.maven.plugin.logging.Log;
38 import org.apache.maven.plugins.annotations.Parameter;
39 import org.apache.maven.project.MavenProject;
40 import org.codehaus.plexus.util.FileUtils;
41 import org.codehaus.plexus.util.cli.Commandline;
42
43
44
45
46 public final class NarUtil {
47 private static final class StreamGobbler extends Thread {
48 private final InputStream is;
49
50 private final TextStream ts;
51
52 private StreamGobbler(final InputStream is, final TextStream ts) {
53 this.is = is;
54 this.ts = ts;
55 }
56
57 @Override
58 public void run() {
59 try {
60 final BufferedReader reader = new BufferedReader(new InputStreamReader(this.is));
61 String line = null;
62 while ((line = reader.readLine()) != null) {
63 this.ts.println(line);
64 }
65 reader.close();
66 } catch (final IOException e) {
67
68 final StackTraceElement[] stackTrace = e.getStackTrace();
69 for (final StackTraceElement element : stackTrace) {
70 this.ts.println(element.toString());
71 }
72 }
73 }
74 }
75
76 public static final String DEFAULT_EXCLUDES = "**/*~,**/#*#,**/.#*,**/%*%,**/._*,"
77 + "**/CVS,**/CVS/**,**/.cvsignore," + "**/SCCS,**/SCCS/**,**/vssver.scc," + "**/.svn,**/.svn/**,**/.DS_Store";
78
79 public static String addLibraryPathToEnv(final String path, final Map environment, final String os) {
80 String pathName = null;
81 char separator = ' ';
82 switch (os) {
83 case OS.WINDOWS:
84 pathName = "PATH";
85 separator = ';';
86 break;
87 case OS.MACOSX:
88 pathName = "DYLD_LIBRARY_PATH";
89 separator = ':';
90 break;
91 case OS.AIX:
92 pathName = "LIBPATH";
93 separator = ':';
94 break;
95 default:
96 pathName = "LD_LIBRARY_PATH";
97 separator = ':';
98 break;
99 }
100
101 String value = environment != null ? (String) environment.get(pathName) : null;
102 if (value == null) {
103 value = NarUtil.getEnv(pathName, pathName, null);
104 }
105
106 String libPath = path;
107 libPath = libPath.replace(File.pathSeparatorChar, separator);
108 if (value != null) {
109 value += separator + libPath;
110 } else {
111 value = libPath;
112 }
113 if (environment != null) {
114 environment.put(pathName, value);
115 }
116 return pathName + "=" + value;
117 }
118
119
120
121
122
123
124
125
126
127
128 static List collectActiveProfiles(final MavenProject project) {
129 final List profiles = project.getActiveProfiles();
130
131 if (project.hasParent()) {
132 profiles.addAll(collectActiveProfiles(project.getParent()));
133 }
134
135 return profiles;
136 }
137
138 public static int copyDirectoryStructure(final File sourceDirectory, final File destinationDirectory,
139 final String includes, final String excludes) throws IOException {
140 if (!sourceDirectory.exists()) {
141 throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
142 }
143
144 final List files = FileUtils.getFiles(sourceDirectory, includes, excludes);
145 final String sourcePath = sourceDirectory.getAbsolutePath();
146
147 int copied = 0;
148 for (final Object file1 : files) {
149 final File file = (File) file1;
150 String dest = file.getAbsolutePath();
151 dest = dest.substring(sourcePath.length() + 1);
152 final File destination = new File(destinationDirectory, dest);
153 if (file.isFile()) {
154
155
156 org.apache.commons.io.FileUtils.copyFile(file, destination);
157 copied++;
158
159
160 try {
161
162
163 final Method canExecute = file.getClass().getDeclaredMethod("canExecute");
164 final Method setExecutable = destination.getClass()
165 .getDeclaredMethod("setExecutable", boolean.class, boolean.class);
166 setExecutable
167 .invoke(destination, canExecute.invoke(file), Boolean.FALSE);
168 } catch (final SecurityException | InvocationTargetException | IllegalAccessException | IllegalArgumentException | NoSuchMethodException e) {
169
170 }
171 } else if (file.isDirectory()) {
172 if (!destination.exists() && !destination.mkdirs()) {
173 throw new IOException("Could not create destination directory '" + destination.getAbsolutePath() + "'.");
174 }
175 copied += copyDirectoryStructure(file, destination, includes, excludes);
176 } else {
177 throw new IOException("Unknown file type: " + file.getAbsolutePath());
178 }
179 }
180 return copied;
181 }
182
183 public static void deleteDirectory(final File dir) throws MojoExecutionException {
184 int retries = OS.WINDOWS.equalsIgnoreCase(System.getProperty("os.name")) ? 3 : 1;
185 while (retries > 0) {
186 retries--;
187 try {
188 FileUtils.deleteDirectory(dir);
189 retries = 0;
190 } catch (final IOException e) {
191 if (retries > 0) {
192 Thread.yield();
193 } else {
194 throw new MojoExecutionException("Could not delete directory: " + dir, e);
195 }
196 }
197 if (retries > 0) {
198
199 try {
200 Thread.sleep(200);
201 } catch (InterruptedException e) {
202 }
203
204
205
206 }
207 }
208 }
209
210 static Set findInstallNameToolCandidates(final File[] files, final Log log)
211 throws MojoExecutionException, MojoFailureException {
212 final HashSet candidates = new HashSet();
213
214 for (final File file2 : files) {
215 final File file = file2;
216
217 if (!file.exists()) {
218 continue;
219 }
220
221 if (file.isDirectory()) {
222 candidates.addAll(findInstallNameToolCandidates(file.listFiles(), log));
223 }
224
225 final String fileName = file.getName();
226 if (file.isFile() && file.canWrite()
227 && (fileName.endsWith(".so") || fileName.endsWith(".dylib") || fileName.endsWith(".jnilib"))) {
228 candidates.add(file);
229 }
230 }
231
232 return candidates;
233 }
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 public static AOL getAOL(final MavenProject project, final String architecture, final String os, final Linker linker,
250 final String aol, final Log log) throws MojoFailureException, MojoExecutionException {
251
252 return aol == null ? new AOL(getArchitecture(architecture), getOS(os), getLinkerName(project, architecture, os,
253 linker, log)) : new AOL(aol);
254 }
255
256 public static String getAOLKey(final String aol) {
257
258 return replace("-", ".", aol);
259 }
260
261 public static String getArchitecture(final String architecture) {
262 if (architecture == null) {
263 return System.getProperty("os.arch");
264 }
265 return architecture;
266 }
267
268
269
270
271
272
273
274
275
276 public static JavaClass getBcelClass(final String filename) throws IOException {
277 final ClassParser parser = new ClassParser(filename);
278 return parser.parse();
279 }
280
281 public static String getEnv(final String envKey, final String alternateSystemProperty, final String defaultValue) {
282 String envValue = null;
283 try {
284 envValue = System.getenv(envKey);
285 if (envValue == null && alternateSystemProperty != null) {
286 envValue = System.getProperty(alternateSystemProperty);
287 }
288 } catch (final Error e) {
289
290 if (alternateSystemProperty != null) {
291 envValue = System.getProperty(alternateSystemProperty);
292 }
293 }
294
295 if (envValue == null) {
296 envValue = defaultValue;
297 }
298
299 return envValue;
300 }
301
302
303
304
305
306
307
308
309
310 public static String getHeaderName(final String basename, final String filename) {
311 final String base = basename.replaceAll("\\\\", "/");
312 final String file = filename.replaceAll("\\\\", "/");
313 if (!file.startsWith(base)) {
314 throw new IllegalArgumentException("Error " + file + " does not start with " + base);
315 }
316 String header = file.substring(base.length() + 1);
317 header = header.replaceAll("/", "_");
318 header = header.replaceAll("\\.class", ".h");
319 return header;
320 }
321
322 public static File getJavaHome(final File javaHome, final String os) {
323 File home = javaHome;
324
325 if (home == null) {
326 home = new File(System.getProperty("java.home"));
327 if (home.getName().equals("jre")) {
328
329 home = home.getParentFile();
330 }
331 }
332 return home;
333 }
334
335 public static Linker getLinker(final Linker linker, final Log log) {
336 Linker link = linker;
337 if (link == null) {
338 link = new Linker(log);
339 }
340 return link;
341 }
342
343 public static String getLinkerName(final MavenProject project, final String architecture, final String os,
344 final Linker linker, final Log log) throws MojoFailureException, MojoExecutionException {
345 return getLinker(linker, log).getName(NarProperties.getInstance(project),
346 getArchitecture(architecture) + "." + getOS(os) + ".");
347 }
348
349 public static String getOS(final String defaultOs) {
350 String os = defaultOs;
351
352 if (os == null) {
353 os = System.getProperty("os.name");
354 final String name = os.toLowerCase();
355 if (name.startsWith("windows")) {
356 os = OS.WINDOWS;
357 }
358 if (name.startsWith("linux")) {
359 os = OS.LINUX;
360 }
361 if (name.startsWith("freebsd")) {
362 os = OS.FREEBSD;
363 }
364 if (name.equals("mac os x")) {
365 os = OS.MACOSX;
366 }
367 }
368 return os;
369 }
370
371 public static boolean isWindows() {
372 return Objects.equals(getOS(null), OS.WINDOWS);
373 }
374
375 public static void makeExecutable(final File file, final Log log) throws MojoExecutionException, MojoFailureException {
376 if (!file.exists()) {
377 return;
378 }
379
380 if (file.isDirectory()) {
381 final File[] files = file.listFiles();
382 for (final File file2 : files) {
383 makeExecutable(file2, log);
384 }
385 }
386 if (file.isFile() && file.canRead() && file.canWrite() && !file.isHidden()) {
387
388 final int result = runCommand("chmod", new String[] {
389 "+x", file.getPath()
390 }, null, null, log);
391 if (result != 0) {
392 throw new MojoExecutionException("Failed to execute 'chmod +x " + file.getPath() + "'" + " return code: \'"
393 + result + "\'.");
394 }
395 }
396 }
397
398 public static void makeLink(final File file, final Log log) throws MojoExecutionException, MojoFailureException {
399 if (!file.exists()) {
400 return;
401 }
402
403 if (file.isDirectory()) {
404 final File[] files = file.listFiles();
405 for (final File file2 : files) {
406 makeLink(file2, log);
407 }
408 }
409 if (file.isFile() && file.canRead() && file.canWrite() && !file.isHidden()
410 && file.getName().matches(".*\\.so(\\.\\d+)+$")) {
411 final File sofile = new File(file.getParent(), file.getName().substring(0, file.getName().indexOf(".so") + 3));
412 if (!sofile.exists()) {
413
414 final int result = runCommand("ln", new String[] {
415 "-s", file.getName(), sofile.getPath()
416 }, null, null, log);
417 if (result != 0) {
418 throw new MojoExecutionException("Failed to execute 'ln -s " + file.getName() + " " + sofile.getPath() + "'"
419 + " return code: \'" + result + "\'.");
420 }
421 }
422 }
423 }
424
425
426 private static String quote(final String s) {
427 final String escQ = "\\Q";
428 final String escE = "\\E";
429
430 int slashEIndex = s.indexOf(escE);
431 if (slashEIndex == -1) {
432 return escQ + s + escE;
433 }
434
435 final StringBuffer sb = new StringBuffer(s.length() * 2);
436 sb.append(escQ);
437 slashEIndex = 0;
438 int current = 0;
439 while ((slashEIndex = s.indexOf(escE, current)) != -1) {
440 sb.append(s.substring(current, slashEIndex));
441 current = slashEIndex + 2;
442 sb.append(escE);
443 sb.append("\\");
444 sb.append(escE);
445 sb.append(escQ);
446 }
447 sb.append(s.substring(current, s.length()));
448 sb.append(escE);
449 return sb.toString();
450 }
451
452
453 private static String quoteReplacement(final String s) {
454 if (s.indexOf('\\') == -1 && s.indexOf('$') == -1) {
455 return s;
456 }
457 final StringBuffer sb = new StringBuffer();
458 for (int i = 0; i < s.length(); i++) {
459 final char c = s.charAt(i);
460 if (c == '\\') {
461 sb.append('\\');
462 sb.append('\\');
463 } else if (c == '$') {
464 sb.append('\\');
465 sb.append('$');
466 } else {
467 sb.append(c);
468 }
469 }
470 return sb.toString();
471 }
472
473 static void removeNulls(final Collection<?> collection) {
474 for (final Iterator<?> iter = collection.iterator(); iter.hasNext();) {
475 if (iter.next() == null) {
476 iter.remove();
477 }
478 }
479 }
480
481
482
483
484
485
486
487
488
489 public static String replace(final CharSequence target, final CharSequence replacement, final String string) {
490 return Pattern.compile(quote(target.toString())
491
492 ).matcher(string).replaceAll(
493
494 }
495
496 public static int runCommand(final String cmd, final String[] args, final File workingDirectory, final String[] env,
497 final Log log) throws MojoExecutionException, MojoFailureException {
498 if (log.isInfoEnabled()) {
499 final StringBuilder argLine = new StringBuilder();
500 if (args != null) {
501 for (final String arg : args) {
502 argLine.append(" ").append(arg);
503 }
504 }
505 if (workingDirectory != null) {
506 log.info("+ cd " + workingDirectory.getAbsolutePath());
507 }
508 log.info("+ " + cmd + argLine);
509 }
510 return runCommand(cmd, args, workingDirectory, env, new TextStream() {
511 @Override
512 public void println(final String text) {
513 log.info(text);
514 }
515 }, new TextStream() {
516 @Override
517 public void println(final String text) {
518 log.error(text);
519 }
520
521 }, new TextStream() {
522 @Override
523 public void println(final String text) {
524 log.debug(text);
525 }
526 }, log);
527 }
528
529 public static int runCommand(final String cmd, final String[] args, final File workingDirectory, final String[] env,
530 final TextStream out, final TextStream err, final TextStream dbg, final Log log)
531 throws MojoExecutionException, MojoFailureException {
532 return runCommand(cmd, args, workingDirectory, env, out, err, dbg, log, false);
533 }
534
535 public static int runCommand(final String cmd, final String[] args, final File workingDirectory, final String[] env,
536 final TextStream out, final TextStream err, final TextStream dbg, final Log log, final boolean expectFailure)
537 throws MojoExecutionException, MojoFailureException {
538 final Commandline cmdLine = new Commandline();
539
540 try {
541 dbg.println("RunCommand: " + cmd);
542 cmdLine.setExecutable(cmd);
543 if (args != null) {
544 for (final String arg : args) {
545 dbg.println(" '" + arg + "'");
546 }
547 cmdLine.addArguments(args);
548 }
549 if (workingDirectory != null) {
550 dbg.println("in: " + workingDirectory.getPath());
551 cmdLine.setWorkingDirectory(workingDirectory);
552 }
553
554 if (env != null) {
555 dbg.println("with Env:");
556 for (final String element : env) {
557 final String[] nameValue = element.split("=", 2);
558 if (nameValue.length < 2) {
559 throw new MojoFailureException(" Misformed env: '" + element + "'");
560 }
561 dbg.println(" '" + nameValue[0] + "=" + nameValue[1] + "'");
562 cmdLine.addEnvironment(nameValue[0], nameValue[1]);
563 }
564 }
565
566 final Process process = cmdLine.execute();
567 final StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), err);
568 final StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream(), out);
569
570 errorGobbler.start();
571 outputGobbler.start();
572 process.waitFor();
573 final int exitValue = process.exitValue();
574 dbg.println("ExitValue: " + exitValue);
575 final int timeout = 5000;
576 errorGobbler.join(timeout);
577 outputGobbler.join(timeout);
578 if (exitValue != 0 ^ expectFailure) {
579 if (log == null) {
580 System.err.println(err.toString());
581 System.err.println(out.toString());
582 System.err.println(dbg.toString());
583 } else {
584 log.warn(err.toString());
585 log.warn(out.toString());
586 log.warn(dbg.toString());
587 }
588 throw new MojoExecutionException("exit code: " + exitValue);
589 }
590 return exitValue;
591 } catch (final MojoExecutionException e) {
592 throw e;
593 } catch (final Exception e) {
594 throw new MojoExecutionException("Could not launch " + cmdLine, e);
595 }
596 }
597
598 static void runInstallNameTool(final File[] files, final Log log) throws MojoExecutionException, MojoFailureException {
599 final Set libs = findInstallNameToolCandidates(files, log);
600
601 for (final Object lib1 : libs) {
602 final File subjectFile = (File) lib1;
603 final String subjectName = subjectFile.getName();
604 final String subjectPath = subjectFile.getPath();
605
606 final int idResult = runCommand("install_name_tool", new String[] { "-id", subjectPath, subjectPath
607 }, null, null, log);
608
609 if (idResult != 0) {
610 throw new MojoExecutionException(
611 "Failed to execute 'install_name_tool -id " + subjectPath + " " + subjectPath + "'" + " return code: \'"
612 + idResult + "\'.");
613 }
614
615 for (final Object lib : libs) {
616 final File dependentFile = (File) lib;
617 final String dependentPath = dependentFile.getPath();
618
619 if (Objects.equals(dependentPath, subjectPath)) {
620 continue;
621 }
622
623 final int changeResult = runCommand("install_name_tool",
624 new String[] { "-change", subjectName, subjectPath, dependentPath
625 }, null, null, log);
626
627 if (changeResult != 0) {
628 throw new MojoExecutionException(
629 "Failed to execute 'install_name_tool -change " + subjectName + " " + subjectPath + " " + dependentPath
630 + "'" + " return code: \'" + changeResult + "\'.");
631 }
632 }
633 }
634 }
635
636 public static void runRanlib(final File file, final Log log) throws MojoExecutionException, MojoFailureException {
637 if (!file.exists()) {
638 return;
639 }
640
641 if (file.isDirectory()) {
642 final File[] files = file.listFiles();
643 for (final File file2 : files) {
644 runRanlib(file2, log);
645 }
646 }
647 if (file.isFile() && file.canWrite() && !file.isHidden() && file.getName().endsWith(".a")) {
648
649 final int result = runCommand("ranlib", new String[] {
650 file.getPath()
651 }, null, null, log);
652 if (result != 0) {
653 throw new MojoExecutionException("Failed to execute 'ranlib " + file.getPath() + "'" + " return code: \'"
654 + result + "\'.");
655 }
656 }
657 }
658
659
660
661
662
663
664
665
666
667 public static String prettyMavenString(final Object o) {
668 final StringBuilder sb = new StringBuilder();
669 sb.append(o.getClass().getName()).append(":\n");
670 for (final Field f : o.getClass().getDeclaredFields()) {
671 if (f.getAnnotation(Parameter.class) == null) continue;
672 sb.append("\t").append(f.getName()).append("=").append(fieldValue(f, o)).append("\n");
673 }
674 return sb.toString();
675 }
676
677 private static Object fieldValue(final Field f, final Object o) {
678 try {
679 return f.get(o);
680 }
681 catch (final IllegalArgumentException | IllegalAccessException exc) {
682 return "<ERROR>";
683 }
684 }
685
686 private NarUtil() {
687
688 }
689 }