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}