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