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.project.MavenProject; 033import org.codehaus.plexus.interpolation.InterpolatorFilterReader; 034import org.codehaus.plexus.interpolation.MapBasedValueSource; 035import org.codehaus.plexus.interpolation.StringSearchInterpolator; 036import org.codehaus.plexus.util.FileUtils; 037import org.codehaus.plexus.util.FileUtils.FilterWrapper; 038import org.fusesource.hawtjni.generator.HawtJNI; 039import 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 */ 051public 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 native source files are located. 064 * 065 * @parameter 066 */ 067 private File nativeSourceDirectory; 068 069 /** 070 * The directory where the generated native source files are located. 071 * 072 * @parameter default-value="${project.build.directory}/generated-sources/hawtjni/native-src" 073 */ 074 private File generatedNativeSourceDirectory; 075 076 /** 077 * The base name of the library, used to determine generated file names. 078 * 079 * @parameter default-value="${project.artifactId}" 080 */ 081 private String name; 082 083 /** 084 * The copyright header template that will be added to the generated source files. 085 * Use the '%END_YEAR%' token to have it replaced with the current year. 086 * 087 * @parameter default-value="" 088 */ 089 private String copyright; 090 091 /** 092 * Restrict looking for JNI classes to the specified package. 093 * 094 * @parameter 095 */ 096 private List<String> packages = new ArrayList<String>(); 097 098 /** 099 * The directory where the java classes files are located. 100 * 101 * @parameter default-value="${project.build.outputDirectory}" 102 */ 103 private File classesDirectory; 104 105 /** 106 * The directory where the generated build package is located.. 107 * 108 * @parameter default-value="${project.build.directory}/generated-sources/hawtjni/native-package" 109 */ 110 private File packageDirectory; 111 112 /** 113 * The list of additional files to be included in the package will be 114 * placed. 115 * 116 * @parameter default-value="${basedir}/src/main/native-package" 117 */ 118 private File customPackageDirectory; 119 120 /** 121 * The text encoding of the files. 122 * 123 * @parameter default-value="UTF-8" 124 */ 125 private String encoding; 126 127 /** 128 * Should we skip executing the autogen.sh file. 129 * 130 * @parameter default-value="${skip-autogen}" 131 */ 132 private boolean skipAutogen; 133 134 /** 135 * Should we force executing the autogen.sh file. 136 * 137 * @parameter default-value="${force-autogen}" 138 */ 139 private boolean forceAutogen; 140 141 /** 142 * Should we display all the native build output? 143 * 144 * @parameter default-value="${hawtjni-verbose}" 145 */ 146 private boolean verbose; 147 148 /** 149 * Extra arguments you want to pass to the autogen.sh command. 150 * 151 * @parameter 152 */ 153 private List<String> autogenArgs; 154 155 /** 156 * Set this value to false to disable the callback support in HawtJNI. 157 * Disabling callback support can substantially reduce the size 158 * of the generated native library. 159 * 160 * @parameter default-value="true" 161 */ 162 private boolean callbacks; 163 164 /** 165 * The build tool to use on Windows systems. Set 166 * to 'msbuild', 'vcbuild', or 'detect' 167 * 168 * @parameter default-value="detect" 169 */ 170 private String windowsBuildTool; 171 172 /** 173 * The name of the msbuild/vcbuild project to use. 174 * Defaults to 'vs2010' for 'msbuild' 175 * and 'vs2008' for 'vcbuild'. 176 * 177 * @parameter 178 */ 179 private String windowsProjectName; 180 181 private File targetSrcDir; 182 183 private CLI cli = new CLI(); 184 185 public void execute() throws MojoExecutionException { 186 cli.verbose = verbose; 187 cli.log = getLog(); 188 if (nativeSourceDirectory == null) { 189 generateNativeSourceFiles(); 190 } else { 191 copyNativeSourceFiles(); 192 } 193 generateBuildSystem(); 194 } 195 196 private void copyNativeSourceFiles() throws MojoExecutionException { 197 try { 198 FileUtils.copyDirectory(nativeSourceDirectory, generatedNativeSourceDirectory); 199 } catch (Exception e) { 200 throw new MojoExecutionException("Copy of Native source failed: "+e, e); 201 } 202 } 203 204 private void generateNativeSourceFiles() throws MojoExecutionException { 205 HawtJNI generator = new HawtJNI(); 206 generator.setClasspaths(getClasspath()); 207 generator.setName(name); 208 generator.setCopyright(copyright); 209 generator.setNativeOutput(generatedNativeSourceDirectory); 210 generator.setPackages(packages); 211 generator.setCallbacks(callbacks); 212 generator.setProgress(new ProgressMonitor() { 213 public void step() { 214 } 215 public void setTotal(int total) { 216 } 217 public void setMessage(String message) { 218 getLog().info(message); 219 } 220 }); 221 try { 222 generator.generate(); 223 } catch (Exception e) { 224 throw new MojoExecutionException("Native source code generation failed: "+e, e); 225 } 226 } 227 228 private void generateBuildSystem() throws MojoExecutionException { 229 try { 230 packageDirectory.mkdirs(); 231 new File(packageDirectory, "m4").mkdirs(); 232 targetSrcDir = new File(packageDirectory, "src"); 233 targetSrcDir.mkdirs(); 234 235 if( customPackageDirectory!=null && customPackageDirectory.isDirectory() ) { 236 FileUtils.copyDirectoryStructureIfModified(customPackageDirectory, packageDirectory); 237 } 238 239 if( generatedNativeSourceDirectory!=null && generatedNativeSourceDirectory.isDirectory() ) { 240 FileUtils.copyDirectoryStructureIfModified(generatedNativeSourceDirectory, targetSrcDir); 241 } 242 243 copyTemplateResource("readme.md", false); 244 copyTemplateResource("configure.ac", true); 245 copyTemplateResource("Makefile.am", true); 246 copyTemplateResource("m4/custom.m4", false); 247 copyTemplateResource("m4/jni.m4", false); 248 copyTemplateResource("m4/osx-universal.m4", false); 249 250 // To support windows based builds.. 251 String tool = windowsBuildTool.toLowerCase().trim(); 252 if( "detect".equals(tool) ) { 253 copyTemplateResource("vs2008.vcproj", (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", true); 254 copyTemplateResource("vs2010.vcxproj", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", true); 255 } else if( "msbuild".equals(tool) ) { 256 copyTemplateResource("vs2010.vcxproj", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", true); 257 } else if( "vcbuild".equals(tool) ) { 258 copyTemplateResource("vs2008.vcproj", (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", true); 259 } else if( "none".equals(tool) ) { 260 } else { 261 throw new MojoExecutionException("Invalid setting for windowsBuildTool: "+windowsBuildTool); 262 } 263 264 File autogen = new File(packageDirectory, "autogen.sh"); 265 File configure = new File(packageDirectory, "configure"); 266 if( !autogen.exists() ) { 267 copyTemplateResource("autogen.sh", false); 268 cli.setExecutable(autogen); 269 } 270 if( !skipAutogen ) { 271 if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) { 272 try { 273 cli.system(packageDirectory, new String[] {"./autogen.sh"}, autogenArgs); 274 } catch (Exception e) { 275 e.printStackTrace(); 276 } 277 } 278 } 279 280 281 } catch (Exception e) { 282 throw new MojoExecutionException("Native build system generation failed: "+e, e); 283 } 284 } 285 286 @SuppressWarnings("unchecked") 287 private ArrayList<String> getClasspath() throws MojoExecutionException { 288 ArrayList<String> artifacts = new ArrayList<String>(); 289 try { 290 artifacts.add(classesDirectory.getCanonicalPath()); 291 for (Artifact artifact : (Set<Artifact>) project.getArtifacts()) { 292 File file = artifact.getFile(); 293 getLog().debug("Including: " + file); 294 artifacts.add(file.getCanonicalPath()); 295 } 296 } catch (IOException e) { 297 throw new MojoExecutionException("Could not determine project classath.", e); 298 } 299 return artifacts; 300 } 301 302 private void copyTemplateResource(String file, boolean filter) throws MojoExecutionException { 303 copyTemplateResource(file, file, filter); 304 } 305 306 private void copyTemplateResource(String file, String output, boolean filter) throws MojoExecutionException { 307 try { 308 File target = FileUtils.resolveFile(packageDirectory, output); 309 if( target.isFile() && target.canRead() ) { 310 return; 311 } 312 URL source = getClass().getClassLoader().getResource("project-template/" + file); 313 File tmp = FileUtils.createTempFile("tmp", "txt", new File(project.getBuild().getDirectory())); 314 try { 315 FileUtils.copyURLToFile(source, tmp); 316 FileUtils.copyFile(tmp, target, encoding, filters(filter), true); 317 } finally { 318 tmp.delete(); 319 } 320 } catch (IOException e) { 321 throw new MojoExecutionException("Could not extract template resource: "+file, e); 322 } 323 } 324 325 @SuppressWarnings("unchecked") 326 private FilterWrapper[] filters(boolean filter) throws IOException { 327 if( !filter ) { 328 return new FilterWrapper[0]; 329 } 330 331 final String startExp = "@"; 332 final String endExp = "@"; 333 final String escapeString = "\\"; 334 final Map<String,String> values = new HashMap<String,String>(); 335 values.put("PROJECT_NAME", name); 336 values.put("PROJECT_NAME_UNDER_SCORE", name.replaceAll("\\W", "_")); 337 values.put("VERSION", project.getVersion()); 338 339 List<String> cpp_files = new ArrayList<String>(); 340 cpp_files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cpp", null, false)); 341 cpp_files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.cxx", null, false)); 342 343 List<String> files = new ArrayList<String>(); 344 files.addAll(cpp_files); 345 files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.c", null, false)); 346 files.addAll(FileUtils.getFileNames(targetSrcDir, "**/*.m", null, false)); 347 String sources = ""; 348 String xml_sources = ""; 349 String vs10_sources = ""; 350 boolean first = true; 351 for (String f : files) { 352 if( !first ) { 353 sources += "\\\n"; 354 } else { 355 values.put("FIRST_SOURCE_FILE", "src/"+f.replace('\\', '/')); 356 first=false; 357 } 358 sources += " src/"+f; 359 360 xml_sources+=" <File RelativePath=\".\\src\\"+ (f.replace('/', '\\')) +"\"/>\n"; 361 vs10_sources+=" <ClCompile Include=\".\\src\\"+ (f.replace('/', '\\')) +"\"/>\n"; 362 } 363 364 if( cpp_files.isEmpty() ) { 365 values.put("AC_PROG_CHECKS", "AC_PROG_CC"); 366 } else { 367 values.put("AC_PROG_CHECKS", "AC_PROG_CXX"); 368 } 369 370 values.put("PROJECT_SOURCES", sources); 371 values.put("PROJECT_XML_SOURCES", xml_sources); 372 values.put("PROJECT_VS10_SOURCES", vs10_sources); 373 374 FileUtils.FilterWrapper wrapper = new FileUtils.FilterWrapper() { 375 public Reader getReader(Reader reader) { 376 StringSearchInterpolator propertiesInterpolator = new StringSearchInterpolator(startExp, endExp); 377 propertiesInterpolator.addValueSource(new MapBasedValueSource(values)); 378 propertiesInterpolator.setEscapeString(escapeString); 379 InterpolatorFilterReader interpolatorFilterReader = new InterpolatorFilterReader(reader, propertiesInterpolator, startExp, endExp); 380 interpolatorFilterReader.setInterpolateWithPrefixPattern(false); 381 return interpolatorFilterReader; 382 } 383 }; 384 return new FilterWrapper[] { wrapper }; 385 } 386 387 388}