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' or 'none'
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    /**
165     * Set this value to true to include the import of a custom properties file in your vcxproj (not applicable
166     * to vs2008). This greatly simplifies the configurability of your project.
167     */
168    @Parameter(defaultValue = "false")
169    private boolean windowsCustomProps;
170    
171    /**
172     * The tools version used in the header of your vcxproj (not applicable to vs2008).
173     */
174    @Parameter(defaultValue = "4.0")
175    private String windowsToolsVersion;
176    
177    /**
178     * The target platform version used in your vcxproj (not applicable to vs2008). 
179     * Not supplied by default.
180     */
181    @Parameter
182    private String windowsTargetPlatformVersion;
183    
184    /**
185     * The platform toolset version used in your vcxproj (not applicable to vs2008). 
186     * Not supplied by default.
187     */
188    @Parameter
189    private String windowsPlatformToolset;
190
191    private File targetSrcDir;
192    
193    private CLI cli = new CLI();
194
195    public void execute() throws MojoExecutionException {
196        cli.verbose = verbose;
197        cli.log = getLog();
198        if (nativeSourceDirectory == null) {
199            generateNativeSourceFiles();
200        } else {
201            copyNativeSourceFiles();
202        }
203        generateBuildSystem();
204    }
205
206    private void copyNativeSourceFiles() throws MojoExecutionException {
207        try {
208            FileUtils.copyDirectory(nativeSourceDirectory, generatedNativeSourceDirectory);
209        } catch (Exception e) {
210            throw new MojoExecutionException("Copy of Native source failed: "+e, e);
211        }
212    }
213
214    private void generateNativeSourceFiles() throws MojoExecutionException {
215        HawtJNI generator = new HawtJNI();
216        generator.setClasspaths(getClasspath());
217        generator.setName(name);
218        generator.setCopyright(copyright);
219        generator.setNativeOutput(generatedNativeSourceDirectory);
220        generator.setPackages(packages);
221        generator.setCallbacks(callbacks);
222        generator.setProgress(new ProgressMonitor() {
223            public void step() {
224            }
225            public void setTotal(int total) {
226            }
227            public void setMessage(String message) {
228                getLog().info(message);
229            }
230        });
231        try {
232            generator.generate();
233        } catch (Exception e) {
234            throw new MojoExecutionException("Native source code generation failed: "+e, e);
235        }
236    }
237
238    private void generateBuildSystem() throws MojoExecutionException {
239        try {
240            packageDirectory.mkdirs();
241            new File(packageDirectory, "m4").mkdirs();
242            targetSrcDir = new File(packageDirectory, "src");
243            targetSrcDir.mkdirs();
244
245            if( customPackageDirectory!=null && customPackageDirectory.isDirectory() ) {
246                FileUtils.copyDirectoryStructureIfModified(customPackageDirectory, packageDirectory);
247            }
248
249            if( generatedNativeSourceDirectory!=null && generatedNativeSourceDirectory.isDirectory() ) {
250                FileUtils.copyDirectoryStructureIfModified(generatedNativeSourceDirectory, targetSrcDir);
251            }
252            
253            copyTemplateResource("readme.md", false);
254            copyTemplateResource("configure.ac", true);
255            copyTemplateResource("Makefile.am", true);
256            copyTemplateResource("m4/custom.m4", false);
257            copyTemplateResource("m4/jni.m4", false);
258            copyTemplateResource("m4/osx-universal.m4", false);
259
260            // To support windows based builds..
261            String tool = windowsBuildTool.toLowerCase().trim();
262            if( "detect".equals(tool) ) {
263                copyTemplateResource("vs2008.vcproj", (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", true);
264                copyTemplateResource("vs2010.vcxproj", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", true);
265                if (windowsCustomProps) {
266                    copyTemplateResource("vs2010.custom.props", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".custom.props", true);
267                }
268            } else if( "msbuild".equals(tool) ) {
269                copyTemplateResource("vs2010.vcxproj", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", true);
270                if (windowsCustomProps) {
271                    copyTemplateResource("vs2010.custom.props", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".custom.props", true);
272                }
273            } else if( "vcbuild".equals(tool) ) {
274                copyTemplateResource("vs2008.vcproj", (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", true);
275            } else if( "none".equals(tool) ) {
276            } else {
277                throw new MojoExecutionException("Invalid setting for windowsBuildTool: "+windowsBuildTool);
278            }
279
280            File autogen = new File(packageDirectory, "autogen.sh");
281            File configure = new File(packageDirectory, "configure");
282            if( !autogen.exists() ) {
283                copyTemplateResource("autogen.sh", false);
284                cli.setExecutable(autogen);
285            }
286            if( !skipAutogen ) {
287                if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) {
288                    try {
289                        cli.system(packageDirectory, new String[] {"./autogen.sh"}, autogenArgs);
290                    } catch (Exception e) {
291                        e.printStackTrace();
292                    }
293                }
294            }
295            
296            
297        } catch (Exception e) {
298            throw new MojoExecutionException("Native build system generation failed: "+e, e);
299        }
300    }
301
302    @SuppressWarnings("unchecked")
303    private ArrayList<String> getClasspath() throws MojoExecutionException {
304        ArrayList<String> artifacts = new ArrayList<String>();
305        try {
306            artifacts.add(classesDirectory.getCanonicalPath());
307            for (Artifact artifact : (Set<Artifact>) project.getArtifacts()) {
308                File file = artifact.getFile();
309                getLog().debug("Including: " + file);
310                artifacts.add(file.getCanonicalPath());
311            }
312        } catch (IOException e) {
313            throw new MojoExecutionException("Could not determine project classath.", e);
314        }
315        return artifacts;
316    }
317
318    private void copyTemplateResource(String file, boolean filter) throws MojoExecutionException {
319        copyTemplateResource(file, file, filter);
320    }
321
322    private void copyTemplateResource(String file, String output, boolean filter) throws MojoExecutionException {
323        try {
324            File target = FileUtils.resolveFile(packageDirectory, output);
325            if( target.isFile() && target.canRead() ) {
326                return;
327            }
328            URL source = getClass().getClassLoader().getResource("project-template/" + file);
329            File tmp = FileUtils.createTempFile("tmp", "txt", new File(project.getBuild().getDirectory()));
330            try {
331                FileUtils.copyURLToFile(source, tmp);
332                FileUtils.copyFile(tmp, target, encoding, filters(filter), true);
333            } finally {
334                tmp.delete();
335            }
336        } catch (IOException e) {
337            throw new MojoExecutionException("Could not extract template resource: "+file, e);
338        }
339    }
340
341    @SuppressWarnings("unchecked")
342    private FilterWrapper[] filters(boolean filter) throws IOException {
343        if( !filter ) {
344            return new FilterWrapper[0];
345        }
346
347        final String startExp = "@";
348        final String endExp = "@";
349        final String escapeString = "\\";
350        final Map<String,String> values = new HashMap<String,String>();
351        values.put("PROJECT_NAME", name);
352        values.put("PROJECT_NAME_UNDER_SCORE", name.replaceAll("\\W", "_"));
353        values.put("VERSION", project.getVersion());
354        
355        List<String> cpp_files = new ArrayList<String>();
356        cpp_files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cpp", null, false));
357        cpp_files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cxx", null, false));
358
359        List<String> files = new ArrayList<String>();
360        files.addAll(cpp_files);
361        files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.c", null, false));
362        files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.m", null, false));
363        String sources = "";
364        String xml_sources = "";
365        String vs10_sources = "";
366        boolean first = true;
367        for (String f : files) {
368            if( !first ) {
369                sources += "\\\n";
370            } else {
371                values.put("FIRST_SOURCE_FILE", "src/"+f.replace('\\', '/'));
372                first=false;
373            }
374            sources += "  src/"+f;
375            
376            xml_sources+="      <File RelativePath=\".\\src\\"+ (f.replace('/', '\\')) +"\" />\n";
377            vs10_sources+="    <ClCompile Include=\".\\src\\"+ (f.replace('/', '\\')) +"\" />\n";  //VS adds trailing space and eases compares
378        }
379
380        if( cpp_files.isEmpty() ) {
381            values.put("AC_PROG_CHECKS", "AC_PROG_CC");
382        } else {
383            values.put("AC_PROG_CHECKS", "AC_PROG_CXX");
384        }
385
386        values.put("PROJECT_SOURCES", sources);
387        values.put("PROJECT_XML_SOURCES", xml_sources);
388        values.put("PROJECT_VS10_SOURCES", vs10_sources);
389        
390        values.put("CUSTOM_PROPS", windowsCustomProps ? "<Import Project=\"" + 
391                        (windowsProjectName != null ? windowsProjectName : "vs2010") + ".custom.props\" />" : "");
392        values.put("TOOLS_VERSION", windowsToolsVersion);
393        values.put("TARGET_PLATFORM_VERSION", windowsTargetPlatformVersion != null ? 
394                        "<WindowsTargetPlatformVersion>" + windowsTargetPlatformVersion + "</WindowsTargetPlatformVersion>" : "");
395        values.put("PLATFORM_TOOLSET", windowsPlatformToolset != null ? 
396                        "<PlatformToolset>" + windowsPlatformToolset + "</PlatformToolset>" : "");
397
398        FileUtils.FilterWrapper wrapper = new FileUtils.FilterWrapper() {
399            public Reader getReader(Reader reader) {
400                StringSearchInterpolator propertiesInterpolator = new StringSearchInterpolator(startExp, endExp);
401                propertiesInterpolator.addValueSource(new MapBasedValueSource(values));
402                propertiesInterpolator.setEscapeString(escapeString);
403                InterpolatorFilterReader interpolatorFilterReader = new InterpolatorFilterReader(reader, propertiesInterpolator, startExp, endExp);
404                interpolatorFilterReader.setInterpolateWithPrefixPattern(false);
405                return interpolatorFilterReader;
406            }
407        };
408        return new FilterWrapper[] { wrapper };
409    }
410    
411
412}