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}