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 }