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.IOException;
24  import java.util.ArrayList;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  
29  import org.apache.bcel.classfile.ClassFormatException;
30  import org.apache.bcel.classfile.JavaClass;
31  import org.apache.bcel.classfile.Method;
32  import org.apache.maven.artifact.DependencyResolutionRequiredException;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Parameter;
36  import org.apache.maven.toolchain.Toolchain;
37  import org.apache.maven.toolchain.ToolchainManager;
38  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
39  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
40  import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
41  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
42  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
43  import org.codehaus.plexus.util.FileUtils;
44  import org.codehaus.plexus.util.StringUtils;
45  
46  /**
47   * Sets up the javah configuration
48   *
49   * @author Mark Donszelmann
50   */
51  public class Javah {
52  
53    /**
54     * Javah command to run.
55     */
56    @Parameter(defaultValue = "javah")
57    private String name = "javah";
58  
59    /**
60     * Add boot class paths. By default none.
61     */
62    @Parameter
63    private List/* <File> */bootClassPaths = new ArrayList();
64  
65    /**
66     * Add class paths. By default the classDirectory directory is included and
67     * all dependent classes.
68     */
69    @Parameter
70    private List/* <File> */classPaths = new ArrayList();
71  
72    /**
73     * The target directory into which to generate the output.
74     */
75    @Parameter(defaultValue = "${project.build.directory}/nar/javah-include", required = true)
76    private File jniDirectory;
77  
78    /**
79     * The class directory to scan for class files with native interfaces.
80     */
81    @Parameter(defaultValue = "${project.build.directory}/classes", required = true)
82    private File classDirectory;
83  
84    /**
85     * The set of files/patterns to include Defaults to "**\/*.class"
86     */
87    @Parameter
88    private Set includes = new HashSet();
89  
90    /**
91     * A list of exclusion filters.
92     */
93    @Parameter
94    private Set excludes = new HashSet();
95  
96    /**
97     * A list of class names e.g. from java.sql.* that are also passed to javah.
98     */
99    @Parameter
100   private Set extraClasses = new HashSet();
101 
102   /**
103    * The granularity in milliseconds of the last modification date for testing
104    * whether a source needs recompilation
105    */
106   @Parameter(defaultValue = "0", required = true)
107   private int staleMillis = 0;
108 
109   /**
110    * The directory to store the timestampfile for the processed aid files.
111    * Defaults to jniDirectory.
112    */
113   @Parameter
114   private File timestampDirectory;
115 
116   /**
117    * The timestampfile for the processed class files. Defaults to name of javah.
118    */
119   @Parameter
120   private File timestampFile;
121 
122   private AbstractNarMojo mojo;
123 
124   public Javah() {
125   }
126 
127   public final void execute() throws MojoExecutionException, MojoFailureException {
128     getClassDirectory().mkdirs();
129 
130     try {
131       final SourceInclusionScanner scanner = new StaleSourceScanner(this.staleMillis, getIncludes(), this.excludes);
132       if (getTimestampDirectory().exists()) {
133         scanner.addSourceMapping(new SingleTargetSourceMapping(".class", getTimestampFile().getPath()));
134       } else {
135         scanner.addSourceMapping(new SuffixMapping(".class", ".dummy"));
136       }
137 
138       final Set classes = scanner.getIncludedSources(getClassDirectory(), getTimestampDirectory());
139 
140       if (!classes.isEmpty()) {
141         final Set files = new HashSet();
142         for (final Object aClass : classes) {
143           final String file = ((File) aClass).getPath();
144           final JavaClass clazz = NarUtil.getBcelClass(file);
145           final Method[] method = clazz.getMethods();
146           for (final Method element : method) {
147             if (element.isNative()) {
148               files.add(clazz.getClassName());
149             }
150           }
151         }
152 
153         if (!files.isEmpty()) {
154           getJniDirectory().mkdirs();
155           getTimestampDirectory().mkdirs();
156 
157           final String javah = getJavah();
158 
159           this.mojo.getLog().info("Running " + javah + " compiler on " + files.size() + " classes...");
160           final int result = NarUtil.runCommand(javah, generateArgs(files), null, null, this.mojo.getLog());
161           if (result != 0) {
162             throw new MojoFailureException(javah + " failed with exit code " + result + " 0x"
163                 + Integer.toHexString(result));
164           }
165           FileUtils.fileWrite(getTimestampDirectory() + "/" + getTimestampFile(), "");
166         }
167       }
168     } catch (final InclusionScanException e) {
169       throw new MojoExecutionException("JAVAH: Class scanning failed", e);
170     } catch (final IOException e) {
171       throw new MojoExecutionException("JAVAH: IO Exception", e);
172     } catch (final ClassFormatException e) {
173       throw new MojoExecutionException("JAVAH: Class could not be inspected", e);
174     }
175   }
176 
177   private String[] generateArgs(final Set/* <String> */classes) throws MojoExecutionException {
178 
179     final List args = new ArrayList();
180 
181     if (!this.bootClassPaths.isEmpty()) {
182       args.add("-bootclasspath");
183       args.add(StringUtils.join(this.bootClassPaths.iterator(), File.pathSeparator));
184     }
185 
186     args.add("-classpath");
187     args.add(StringUtils.join(getClassPaths().iterator(), File.pathSeparator));
188 
189     args.add("-d");
190     args.add(getJniDirectory().getPath());
191 
192     if (this.mojo.getLog().isDebugEnabled()) {
193       args.add("-verbose");
194     }
195 
196     if (classes != null) {
197       for (final Object aClass : classes) {
198         args.add(aClass);
199       }
200     }
201 
202     if (this.extraClasses != null) {
203       for (final Object extraClass : this.extraClasses) {
204         args.add(extraClass);
205       }
206     }
207 
208     return (String[]) args.toArray(new String[args.size()]);
209   }
210 
211   protected final File getClassDirectory() {
212     if (this.classDirectory == null) {
213       this.classDirectory = new File(this.mojo.getMavenProject().getBuild().getDirectory(), "classes");
214     }
215     return this.classDirectory;
216   }
217 
218   protected final List getClassPaths() throws MojoExecutionException {
219     if (this.classPaths.isEmpty()) {
220       try {
221         this.classPaths.addAll(this.mojo.getMavenProject().getCompileClasspathElements());
222       } catch (final DependencyResolutionRequiredException e) {
223         throw new MojoExecutionException("JAVAH, cannot get classpath", e);
224       }
225     }
226     return this.classPaths;
227   }
228 
229   protected final Set getIncludes() {
230     NarUtil.removeNulls(this.includes);
231     if (this.includes.isEmpty()) {
232       this.includes.add("**/*.class");
233     }
234     return this.includes;
235   }
236 
237   private String getJavah() throws MojoExecutionException, MojoFailureException {
238     String javah = null;
239 
240     // try toolchain
241     final Toolchain toolchain = getToolchain();
242     if (toolchain != null) {
243       javah = toolchain.findTool("javah");
244     }
245 
246     // try java home
247     if (javah == null) {
248       final File javahFile = new File(this.mojo.getJavaHome(this.mojo.getAOL()), "bin");
249       javah = new File(javahFile, this.name).getAbsolutePath();
250     }
251 
252     // forget it...
253     if (javah == null) {
254       throw new MojoExecutionException("NAR: Cannot find 'javah' in Toolchain or on JavaHome");
255     }
256 
257     return javah;
258   }
259 
260   protected final File getJniDirectory() {
261     if (this.jniDirectory == null) {
262       this.jniDirectory = new File(this.mojo.getMavenProject().getBuild().getDirectory(), "nar/javah-include");
263     }
264     return this.jniDirectory;
265   }
266 
267   protected final File getTimestampDirectory() {
268     if (this.timestampDirectory == null) {
269       this.timestampDirectory = getJniDirectory();
270     }
271     return this.timestampDirectory;
272   }
273 
274   protected final File getTimestampFile() {
275     if (this.timestampFile == null) {
276       this.timestampFile = new File(this.name);
277     }
278     return this.timestampFile;
279   }
280 
281   // TODO remove the part with ToolchainManager lookup once we depend on
282   // 2.0.9 (have it as prerequisite). Define as regular component field then.
283   private Toolchain getToolchain() {
284     Toolchain toolChain = null;
285     final ToolchainManager toolchainManager = ((NarJavahMojo) this.mojo).getToolchainManager();
286 
287     if (toolchainManager != null) {
288       toolChain = toolchainManager.getToolchainFromBuildContext("jdk", ((NarJavahMojo) this.mojo).getSession());
289     }
290     return toolChain;
291   }
292 
293   public final void setAbstractCompileMojo(final AbstractNarMojo abstractNarMojo) {
294     this.mojo = abstractNarMojo;
295   }
296 }