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