001/**
002 * Copyright (C) 2009-2011 FuseSource Corp.
003 * http://fusesource.com
004 * 
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 * 
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.fusesource.hawtjni.maven;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.Reader;
022import java.net.URL;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.maven.artifact.Artifact;
030import org.apache.maven.plugin.AbstractMojo;
031import org.apache.maven.plugin.MojoExecutionException;
032import org.apache.maven.plugins.annotations.LifecyclePhase;
033import org.apache.maven.plugins.annotations.Mojo;
034import org.apache.maven.plugins.annotations.Parameter;
035import org.apache.maven.project.MavenProject;
036import org.codehaus.plexus.interpolation.InterpolatorFilterReader;
037import org.codehaus.plexus.interpolation.MapBasedValueSource;
038import org.codehaus.plexus.interpolation.StringSearchInterpolator;
039import org.codehaus.plexus.util.FileUtils;
040import org.codehaus.plexus.util.FileUtils.FilterWrapper;
041import org.fusesource.hawtjni.generator.HawtJNI;
042import org.fusesource.hawtjni.generator.ProgressMonitor;
043
044/**
045 * This goal generates the native source code and a
046 * autoconf/msbuild based build system needed to 
047 * build a JNI library for any HawtJNI annotated
048 * classes in your maven project.
049 * 
050 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
051 */
052@Mojo(name = "generate", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
053public class GenerateMojo extends AbstractMojo {
054
055    /**
056     * The maven project.
057     */
058    @Parameter(defaultValue = "${project}", readonly = true)
059    protected MavenProject project;
060
061    /**
062     * The directory where the native source files are located.
063     */
064    @Parameter
065    private File nativeSourceDirectory;
066
067    /**
068     * The directory where the generated native source files are located.
069     */
070    @Parameter(defaultValue = "${project.build.directory}/generated-sources/hawtjni/native-src")
071    private File generatedNativeSourceDirectory;
072
073    /**
074     * The base name of the library, used to determine generated file names.
075     */
076    @Parameter(defaultValue = "${project.artifactId}")
077    private String name;
078
079    /**
080     * The copyright header template that will be added to the generated source files.
081     * Use the '%END_YEAR%' token to have it replaced with the current year.  
082     */
083    @Parameter(defaultValue = "")
084    private String copyright;
085
086    /**
087     * Restrict looking for JNI classes to the specified package.
088     */
089    @Parameter
090    private List<String> packages = new ArrayList<String>();
091
092    /**
093     * The directory where the java classes files are located.
094     */
095    @Parameter(defaultValue = "${project.build.outputDirectory}")
096    private File classesDirectory;
097    
098    /**
099     * The directory where the generated build package is located..
100     */
101    @Parameter(defaultValue = "${project.build.directory}/generated-sources/hawtjni/native-package")
102    private File packageDirectory;
103    
104    /**
105     * The list of additional files to be included in the package will be
106     * placed.
107     */
108    @Parameter(defaultValue = "${basedir}/src/main/native-package")
109    private File customPackageDirectory;
110
111    /**
112     * The text encoding of the files.
113     */
114    @Parameter(defaultValue = "UTF-8")
115    private String encoding;
116
117    /**
118     * Should we skip executing the autogen.sh file.
119     */
120    @Parameter(defaultValue = "${skip-autogen}")
121    private boolean skipAutogen;
122    
123    /**
124     * Should we force executing the autogen.sh file.
125     */
126    @Parameter(defaultValue = "${force-autogen}")
127    private boolean forceAutogen;
128
129    /**
130     * Should we display all the native build output?
131     */
132    @Parameter(defaultValue = "${hawtjni-verbose}")
133    private boolean verbose;
134
135    /**
136     * Extra arguments you want to pass to the autogen.sh command.
137     */
138    @Parameter
139    private List<String> autogenArgs;
140    
141    /**
142     * Set this value to false to disable the callback support in HawtJNI.
143     * Disabling callback support can substantially reduce the size
144     * of the generated native library.  
145     */
146    @Parameter(defaultValue = "true")
147    private boolean callbacks;
148    
149    /**
150     * The build tool to use on Windows systems.  Set
151     * to 'msbuild', 'vcbuild', or 'detect'
152     */
153    @Parameter(defaultValue = "detect")
154    private String windowsBuildTool;
155
156    /**
157     * The name of the msbuild/vcbuild project to use.
158     * Defaults to 'vs2010' for 'msbuild'
159     * and 'vs2008' for 'vcbuild'.
160     */
161    @Parameter
162    private String windowsProjectName;
163
164    private File targetSrcDir;
165    
166    private CLI cli = new CLI();
167
168    public void execute() throws MojoExecutionException {
169        cli.verbose = verbose;
170        cli.log = getLog();
171        if (nativeSourceDirectory == null) {
172            generateNativeSourceFiles();
173        } else {
174            copyNativeSourceFiles();
175        }
176        generateBuildSystem();
177    }
178
179    private void copyNativeSourceFiles() throws MojoExecutionException {
180        try {
181            FileUtils.copyDirectory(nativeSourceDirectory, generatedNativeSourceDirectory);
182        } catch (Exception e) {
183            throw new MojoExecutionException("Copy of Native source failed: "+e, e);
184        }
185    }
186
187    private void generateNativeSourceFiles() throws MojoExecutionException {
188        HawtJNI generator = new HawtJNI();
189        generator.setClasspaths(getClasspath());
190        generator.setName(name);
191        generator.setCopyright(copyright);
192        generator.setNativeOutput(generatedNativeSourceDirectory);
193        generator.setPackages(packages);
194        generator.setCallbacks(callbacks);
195        generator.setProgress(new ProgressMonitor() {
196            public void step() {
197            }
198            public void setTotal(int total) {
199            }
200            public void setMessage(String message) {
201                getLog().info(message);
202            }
203        });
204        try {
205            generator.generate();
206        } catch (Exception e) {
207            throw new MojoExecutionException("Native source code generation failed: "+e, e);
208        }
209    }
210
211    private void generateBuildSystem() throws MojoExecutionException {
212        try {
213            packageDirectory.mkdirs();
214            new File(packageDirectory, "m4").mkdirs();
215            targetSrcDir = new File(packageDirectory, "src");
216            targetSrcDir.mkdirs();
217
218            if( customPackageDirectory!=null && customPackageDirectory.isDirectory() ) {
219                FileUtils.copyDirectoryStructureIfModified(customPackageDirectory, packageDirectory);
220            }
221
222            if( generatedNativeSourceDirectory!=null && generatedNativeSourceDirectory.isDirectory() ) {
223                FileUtils.copyDirectoryStructureIfModified(generatedNativeSourceDirectory, targetSrcDir);
224            }
225            
226            copyTemplateResource("readme.md", false);
227            copyTemplateResource("configure.ac", true);
228            copyTemplateResource("Makefile.am", true);
229            copyTemplateResource("m4/custom.m4", false);
230            copyTemplateResource("m4/jni.m4", false);
231            copyTemplateResource("m4/osx-universal.m4", false);
232
233            // To support windows based builds..
234            String tool = windowsBuildTool.toLowerCase().trim();
235            if( "detect".equals(tool) ) {
236                copyTemplateResource("vs2008.vcproj", (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", true);
237                copyTemplateResource("vs2010.vcxproj", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", true);
238            } else if( "msbuild".equals(tool) ) {
239                copyTemplateResource("vs2010.vcxproj", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", true);
240            } else if( "vcbuild".equals(tool) ) {
241                copyTemplateResource("vs2008.vcproj", (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", true);
242            } else if( "none".equals(tool) ) {
243            } else {
244                throw new MojoExecutionException("Invalid setting for windowsBuildTool: "+windowsBuildTool);
245            }
246
247            File autogen = new File(packageDirectory, "autogen.sh");
248            File configure = new File(packageDirectory, "configure");
249            if( !autogen.exists() ) {
250                copyTemplateResource("autogen.sh", false);
251                cli.setExecutable(autogen);
252            }
253            if( !skipAutogen ) {
254                if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) {
255                    try {
256                        cli.system(packageDirectory, new String[] {"./autogen.sh"}, autogenArgs);
257                    } catch (Exception e) {
258                        e.printStackTrace();
259                    }
260                }
261            }
262            
263            
264        } catch (Exception e) {
265            throw new MojoExecutionException("Native build system generation failed: "+e, e);
266        }
267    }
268
269    @SuppressWarnings("unchecked")
270    private ArrayList<String> getClasspath() throws MojoExecutionException {
271        ArrayList<String> artifacts = new ArrayList<String>();
272        try {
273            artifacts.add(classesDirectory.getCanonicalPath());
274            for (Artifact artifact : (Set<Artifact>) project.getArtifacts()) {
275                File file = artifact.getFile();
276                getLog().debug("Including: " + file);
277                artifacts.add(file.getCanonicalPath());
278            }
279        } catch (IOException e) {
280            throw new MojoExecutionException("Could not determine project classath.", e);
281        }
282        return artifacts;
283    }
284
285    private void copyTemplateResource(String file, boolean filter) throws MojoExecutionException {
286        copyTemplateResource(file, file, filter);
287    }
288
289    private void copyTemplateResource(String file, String output, boolean filter) throws MojoExecutionException {
290        try {
291            File target = FileUtils.resolveFile(packageDirectory, output);
292            if( target.isFile() && target.canRead() ) {
293                return;
294            }
295            URL source = getClass().getClassLoader().getResource("project-template/" + file);
296            File tmp = FileUtils.createTempFile("tmp", "txt", new File(project.getBuild().getDirectory()));
297            try {
298                FileUtils.copyURLToFile(source, tmp);
299                FileUtils.copyFile(tmp, target, encoding, filters(filter), true);
300            } finally {
301                tmp.delete();
302            }
303        } catch (IOException e) {
304            throw new MojoExecutionException("Could not extract template resource: "+file, e);
305        }
306    }
307
308    @SuppressWarnings("unchecked")
309    private FilterWrapper[] filters(boolean filter) throws IOException {
310        if( !filter ) {
311            return new FilterWrapper[0];
312        }
313
314        final String startExp = "@";
315        final String endExp = "@";
316        final String escapeString = "\\";
317        final Map<String,String> values = new HashMap<String,String>();
318        values.put("PROJECT_NAME", name);
319        values.put("PROJECT_NAME_UNDER_SCORE", name.replaceAll("\\W", "_"));
320        values.put("VERSION", project.getVersion());
321        
322        List<String> cpp_files = new ArrayList<String>();
323        cpp_files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cpp", null, false));
324        cpp_files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cxx", null, false));
325
326        List<String> files = new ArrayList<String>();
327        files.addAll(cpp_files);
328        files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.c", null, false));
329        files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.m", null, false));
330        String sources = "";
331        String xml_sources = "";
332        String vs10_sources = "";
333        boolean first = true;
334        for (String f : files) {
335            if( !first ) {
336                sources += "\\\n";
337            } else {
338                values.put("FIRST_SOURCE_FILE", "src/"+f.replace('\\', '/'));
339                first=false;
340            }
341            sources += "  src/"+f;
342            
343            xml_sources+="      <File RelativePath=\".\\src\\"+ (f.replace('/', '\\')) +"\"/>\n";
344            vs10_sources+="    <ClCompile Include=\".\\src\\"+ (f.replace('/', '\\')) +"\"/>\n";
345        }
346
347        if( cpp_files.isEmpty() ) {
348            values.put("AC_PROG_CHECKS", "AC_PROG_CC");
349        } else {
350            values.put("AC_PROG_CHECKS", "AC_PROG_CXX");
351        }
352
353        values.put("PROJECT_SOURCES", sources);
354        values.put("PROJECT_XML_SOURCES", xml_sources);
355        values.put("PROJECT_VS10_SOURCES", vs10_sources);
356
357        FileUtils.FilterWrapper wrapper = new FileUtils.FilterWrapper() {
358            public Reader getReader(Reader reader) {
359                StringSearchInterpolator propertiesInterpolator = new StringSearchInterpolator(startExp, endExp);
360                propertiesInterpolator.addValueSource(new MapBasedValueSource(values));
361                propertiesInterpolator.setEscapeString(escapeString);
362                InterpolatorFilterReader interpolatorFilterReader = new InterpolatorFilterReader(reader, propertiesInterpolator, startExp, endExp);
363                interpolatorFilterReader.setInterpolateWithPrefixPattern(false);
364                return interpolatorFilterReader;
365            }
366        };
367        return new FilterWrapper[] { wrapper };
368    }
369    
370
371}