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.*; 020import java.net.URL; 021import java.util.List; 022 023import org.apache.maven.artifact.Artifact; 024import org.apache.maven.artifact.factory.ArtifactFactory; 025import org.apache.maven.artifact.repository.ArtifactRepository; 026import org.apache.maven.artifact.resolver.ArtifactNotFoundException; 027import org.apache.maven.artifact.resolver.ArtifactResolutionException; 028import org.apache.maven.artifact.resolver.ArtifactResolver; 029import org.apache.maven.model.Dependency; 030import org.apache.maven.model.Resource; 031import org.apache.maven.plugin.AbstractMojo; 032import org.apache.maven.plugin.MojoExecutionException; 033import org.apache.maven.plugins.annotations.Component; 034import org.apache.maven.plugins.annotations.LifecyclePhase; 035import org.apache.maven.plugins.annotations.Mojo; 036import org.apache.maven.plugins.annotations.Parameter; 037import org.apache.maven.project.MavenProject; 038import org.codehaus.plexus.archiver.UnArchiver; 039import org.codehaus.plexus.archiver.manager.ArchiverManager; 040import org.codehaus.plexus.util.FileUtils; 041import org.codehaus.plexus.util.IOUtil; 042import org.codehaus.plexus.util.cli.CommandLineException; 043import org.fusesource.hawtjni.runtime.Library; 044 045/** 046 * This goal builds the JNI module which was previously 047 * generated with the generate goal. It adds the JNI module 048 * to the test resource path so that unit tests can load 049 * the freshly built JNI library. 050 * 051 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 052 */ 053@Mojo(name = "build", defaultPhase = LifecyclePhase.GENERATE_TEST_RESOURCES) 054public class BuildMojo extends AbstractMojo { 055 056 /** 057 * The maven project. 058 */ 059 @Parameter(defaultValue = "${project}", readonly = true) 060 protected MavenProject project; 061 062 /** 063 * Remote repositories 064 */ 065 @Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true) 066 protected List remoteArtifactRepositories; 067 068 /** 069 * Local maven repository. 070 */ 071 @Parameter(defaultValue = "${localRepository}", readonly = true) 072 protected ArtifactRepository localRepository; 073 074 /** 075 * Artifact factory, needed to download the package source file 076 */ 077 @Component 078 protected ArtifactFactory artifactFactory; 079 080 /** 081 * Artifact resolver, needed to download the package source file 082 */ 083 @Component 084 protected ArtifactResolver artifactResolver; 085 086 /** 087 */ 088 @Component 089 private ArchiverManager archiverManager; 090 091 /** 092 * The base name of the library, used to determine generated file names. 093 */ 094 @Parameter(defaultValue = "${project.artifactId}") 095 private String name; 096 097 /** 098 * Where the unpacked build package is located. 099 */ 100 @Parameter(defaultValue = "${project.build.directory}/generated-sources/hawtjni/native-package") 101 private File packageDirectory; 102 103 /** 104 * The output directory where the built JNI library will placed. This directory will be added 105 * to as a test resource path so that unit tests can verify the built JNI library. 106 * 107 * The library will placed under the META-INF/native/${platform} directory that the HawtJNI 108 * Library uses to find JNI libraries as classpath resources. 109 */ 110 @Parameter(defaultValue = "${project.build.directory}/generated-sources/hawtjni/lib") 111 private File libDirectory; 112 113 /** 114 * The directory where the build will be produced. It creates a native-build and native-dist directory 115 * under the specified directory. 116 */ 117 @Parameter(defaultValue = "${project.build.directory}") 118 private File buildDirectory; 119 120 /** 121 * Should we skip executing the autogen.sh file. 122 */ 123 @Parameter(defaultValue = "${skip-autogen}") 124 private boolean skipAutogen; 125 126 /** 127 * Should we force executing the autogen.sh file. 128 */ 129 @Parameter(defaultValue = "${force-autogen}") 130 private boolean forceAutogen; 131 132 /** 133 * Extra arguments you want to pass to the autogen.sh command. 134 */ 135 @Parameter 136 private List<String> autogenArgs; 137 138 /** 139 * Should we skip executing the configure command. 140 */ 141 @Parameter(defaultValue = "${skip-configure}") 142 private boolean skipConfigure; 143 144 /** 145 * Should we force executing the configure command. 146 */ 147 @Parameter(defaultValue = "${force-configure}") 148 private boolean forceConfigure; 149 150 /** 151 * Should we display all the native build output? 152 */ 153 @Parameter(defaultValue = "${hawtjni-verbose}") 154 private boolean verbose; 155 156 /** 157 * Extra arguments you want to pass to the configure command. 158 */ 159 @Parameter 160 private List<String> configureArgs; 161 162 /** 163 * The platform identifier of this build. If not specified, 164 * it will be automatically detected. 165 */ 166 @Parameter 167 private String platform; 168 169 /** 170 * The classifier of the package archive that will be created. 171 */ 172 @Parameter(defaultValue = "native-src") 173 private String sourceClassifier; 174 175 /** 176 * If the source build could not be fully generated, perhaps the autotools 177 * were not available on this platform, should we attempt to download 178 * a previously deployed source package and build that? 179 */ 180 @Parameter(defaultValue = "true") 181 private boolean downloadSourcePackage = true; 182 183 /** 184 * The dependency to download to get the native sources. 185 */ 186 @Parameter 187 private Dependency nativeSrcDependency; 188 189 /** 190 * URL to where we can down the source package 191 */ 192 @Parameter(defaultValue = "${native-src-url}") 193 private String nativeSrcUrl; 194 195 /** 196 * The build tool to use on Windows systems. Set 197 * to 'msbuild', 'vcbuild', or 'detect' 198 */ 199 @Parameter(defaultValue = "detect") 200 private String windowsBuildTool; 201 202 /** 203 * The name of the msbuild/vcbuild project to use. 204 * Defaults to 'vs2010' for 'msbuild' 205 * and 'vs2008' for 'vcbuild'. 206 */ 207 @Parameter 208 private String windowsProjectName; 209 210 private final CLI cli = new CLI(); 211 212 public void execute() throws MojoExecutionException { 213 cli.verbose = verbose; 214 cli.log = getLog(); 215 try { 216 File buildDir = new File(buildDirectory, "native-build"); 217 buildDir.mkdirs(); 218 if ( CLI.IS_WINDOWS ) { 219 vsBasedBuild(buildDir); 220 } else { 221 configureBasedBuild(buildDir); 222 } 223 224 getLog().info("Adding test resource root: "+libDirectory.getAbsolutePath()); 225 Resource testResource = new Resource(); 226 testResource.setDirectory(libDirectory.getAbsolutePath()); 227 this.project.addTestResource(testResource); //(); 228 229 } catch (Exception e) { 230 throw new MojoExecutionException("build failed: "+e, e); 231 } 232 } 233 234 private void vsBasedBuild(File buildDir) throws CommandLineException, MojoExecutionException, IOException { 235 236 FileUtils.copyDirectoryStructureIfModified(packageDirectory, buildDir); 237 238 Library library = new Library(name); 239 String libPlatform = this.platform != null ? this.platform : Library.getPlatform(); 240 String platform; 241 String configuration="release"; 242 if( "windows32".equals(libPlatform) ) { 243 platform = "Win32"; 244 } else if( "windows64".equals(libPlatform) ) { 245 platform = "x64"; 246 } else { 247 throw new MojoExecutionException("Unsupported platform: "+libPlatform); 248 } 249 250 boolean useMSBuild = false; 251 String tool = windowsBuildTool.toLowerCase().trim(); 252 if( "detect".equals(tool) ) { 253 String toolset = System.getenv("PlatformToolset"); 254 if( "Windows7.1SDK".equals(toolset) ) { 255 useMSBuild = true; 256 } else { 257 String vcinstalldir = System.getenv("VCINSTALLDIR"); 258 if( vcinstalldir!=null ) { 259 if( vcinstalldir.contains("Microsoft Visual Studio 10") || 260 vcinstalldir.contains("Microsoft Visual Studio 11") || 261 vcinstalldir.contains("Microsoft Visual Studio 12") 262 ) { 263 useMSBuild = true; 264 } 265 } 266 } 267 } else if( "msbuild".equals(tool) ) { 268 useMSBuild = true; 269 } else if( "vcbuild".equals(tool) ) { 270 useMSBuild = false; 271 } else { 272 throw new MojoExecutionException("Invalid setting for windowsBuildTool: "+windowsBuildTool); 273 } 274 275 if( useMSBuild ) { 276 // vcbuild was removed.. use the msbuild tool instead. 277 int rc = cli.system(buildDir, new String[]{"msbuild", (windowsProjectName != null ? windowsProjectName : "vs2010") + ".vcxproj", "/property:Platform="+platform, "/property:Configuration="+configuration}); 278 if( rc != 0 ) { 279 throw new MojoExecutionException("vcbuild failed with exit code: "+rc); 280 } 281 } else { 282 // try to use a vcbuild.. 283 int rc = cli.system(buildDir, new String[]{"vcbuild", "/platform:"+platform, (windowsProjectName != null ? windowsProjectName : "vs2008") + ".vcproj", configuration}); 284 if( rc != 0 ) { 285 throw new MojoExecutionException("vcbuild failed with exit code: "+rc); 286 } 287 } 288 289 290 291 File libFile=FileUtils.resolveFile(buildDir, "target/"+platform+"-"+configuration+"/lib/"+library.getLibraryFileName()); 292 if( !libFile.exists() ) { 293 throw new MojoExecutionException("vcbuild did not generate: "+libFile); 294 } 295 296 File target=FileUtils.resolveFile(libDirectory, library.getPlatformSpecificResourcePath(libPlatform)); 297 FileUtils.copyFile(libFile, target); 298 299 } 300 301 302 private void configureBasedBuild(File buildDir) throws IOException, MojoExecutionException, CommandLineException { 303 304 File configure = new File(packageDirectory, "configure"); 305 if( configure.exists() ) { 306 FileUtils.copyDirectoryStructureIfModified(packageDirectory, buildDir); 307 } else if (downloadSourcePackage) { 308 downloadNativeSourcePackage(buildDir); 309 } else { 310 if( !buildDir.exists() ) { 311 throw new MojoExecutionException("The configure script is missing from the generated native source package and downloadSourcePackage is disabled: "+configure); 312 } 313 } 314 315 configure = new File(buildDir, "configure"); 316 File autogen = new File(buildDir, "autogen.sh"); 317 File makefile = new File(buildDir, "Makefile"); 318 319 File distDirectory = new File(buildDir, "target"); 320 File distLibDirectory = new File(distDirectory, "lib"); 321 distLibDirectory.mkdirs(); 322 323 if( autogen.exists() && !skipAutogen ) { 324 if( (!configure.exists() && !CLI.IS_WINDOWS) || forceAutogen ) { 325 cli.setExecutable(autogen); 326 int rc = cli.system(buildDir, new String[] {"./autogen.sh"}, autogenArgs); 327 if( rc != 0 ) { 328 throw new MojoExecutionException("./autogen.sh failed with exit code: "+rc); 329 } 330 } 331 } 332 333 if( configure.exists() && !skipConfigure ) { 334 if( !makefile.exists() || forceConfigure ) { 335 336 File autotools = new File(buildDir, "autotools"); 337 File[] listFiles = autotools.listFiles(); 338 if( listFiles!=null ) { 339 for (File file : listFiles) { 340 cli.setExecutable(file); 341 } 342 } 343 344 cli.setExecutable(configure); 345 int rc = cli.system(buildDir, new String[]{"./configure", "--disable-ccache", "--prefix="+distDirectory.getCanonicalPath(), "--libdir="+distDirectory.getCanonicalPath()+"/lib"}, configureArgs); 346 if( rc != 0 ) { 347 throw new MojoExecutionException("./configure failed with exit code: "+rc); 348 } 349 } 350 } 351 352 int rc = cli.system(buildDir, new String[]{"make", "install"}); 353 if( rc != 0 ) { 354 throw new MojoExecutionException("make based build failed with exit code: "+rc); 355 } 356 357 Library library = new Library(name); 358 359 File libFile = new File(distLibDirectory, library.getLibraryFileName()); 360 if( !libFile.exists() ) { 361 throw new MojoExecutionException("Make based build did not generate: "+libFile); 362 } 363 364 if( platform == null ) { 365 platform = library.getPlatform(); 366 } 367 368 File target=FileUtils.resolveFile(libDirectory, library.getPlatformSpecificResourcePath(platform)); 369 FileUtils.copyFile(libFile, target); 370 } 371 372 public void downloadNativeSourcePackage(File buildDir) throws MojoExecutionException { 373 File packageZipFile; 374 if( nativeSrcUrl ==null || nativeSrcUrl.trim().length()==0 ) { 375 Artifact artifact=null; 376 if( nativeSrcDependency==null ) { 377 artifact = artifactFactory.createArtifactWithClassifier(project.getGroupId(), project.getArtifactId(), project.getVersion(), "zip", sourceClassifier); 378 } else { 379 artifact = artifactFactory.createArtifactWithClassifier(nativeSrcDependency.getGroupId(), nativeSrcDependency.getArtifactId(), nativeSrcDependency.getVersion(), nativeSrcDependency.getType(), nativeSrcDependency.getClassifier()); 380 } 381 try { 382 artifactResolver.resolveAlways(artifact, remoteArtifactRepositories, localRepository); 383 } catch (ArtifactResolutionException e) { 384 throw new MojoExecutionException("Error downloading.", e); 385 } catch (ArtifactNotFoundException e) { 386 throw new MojoExecutionException("Requested download does not exist.", e); 387 } 388 389 packageZipFile = artifact.getFile(); 390 if( packageZipFile.isDirectory() ) { 391 // Yep. looks like we are running on mvn 3, seem like 392 // mvn 3 does not actually download the artifact. it just points us 393 // to our own build. 394 throw new MojoExecutionException("Add a '-Dnative-src-url=file:...' to have maven download the native package"); 395 } 396 } else { 397 try { 398 packageZipFile = new File(buildDirectory, "native-build.zip"); 399 URL url = new URL(nativeSrcUrl.trim()); 400 InputStream is = url.openStream(); 401 try { 402 FileOutputStream os = new FileOutputStream(packageZipFile); 403 try { 404 IOUtil.copy(is, os); 405 } finally { 406 IOUtil.close(is); 407 } 408 409 } finally { 410 IOUtil.close(is); 411 } 412 } catch (Exception e) { 413 throw new MojoExecutionException("Error downloading: "+ nativeSrcUrl, e); 414 } 415 } 416 417 try { 418 File dest = new File(buildDirectory, "native-build-extracted"); 419 getLog().info("Extracting "+packageZipFile+" to "+dest); 420 421 UnArchiver unArchiver = archiverManager.getUnArchiver("zip"); 422 unArchiver.setSourceFile(packageZipFile); 423 unArchiver.extract("", dest); 424 425 426 File source = findSourceRoot(dest); 427 if( source==null ) { 428 throw new MojoExecutionException("Extracted package did not look like it contained a native source build."); 429 } 430 FileUtils.copyDirectoryStructureIfModified(source, buildDir); 431 432 } catch (MojoExecutionException e) { 433 throw e; 434 } catch (Throwable e) { 435 throw new MojoExecutionException("Could not extract the native source package.", e); 436 } 437 } 438 439 private File findSourceRoot(File dest) { 440 if(dest.isDirectory()) { 441 if( new File(dest, "configure").exists() ) { 442 return dest; 443 } else { 444 for (File file : dest.listFiles()) { 445 File root = findSourceRoot(file); 446 if( root!=null ) { 447 return root; 448 } 449 } 450 return null; 451 } 452 } else { 453 return null; 454 } 455 } 456 457}