blob: 26e92b97bd24381acaa5c7361d6fc0168739c2c7 [file] [log] [blame]
/*
* Copyright 2012 Frantisek Mantlik <frantisek at mantlik.cz>.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package org.netbeans.nbm;
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.util.StringUtils;
/**
* Build installers for Mavenized NetBeans application.
* Creates installers for supported operating systems
* and packages each installer as a deployable artifact.
* <p>See a <a href="http://www.mojohaus.org/nbm-maven-plugin/buildinstexample.html">how-to</a> on customizing the installer.
* @author <a href="mailto:frantisek@mantlik.cz">Frantisek Mantlik</a>
*/
@Mojo(name="build-installers",
requiresProject=true,
requiresDependencyResolution=ResolutionScope.RUNTIME,
threadSafe = true,
defaultPhase=LifecyclePhase.PACKAGE )
public class BuildInstallersMojo
extends AbstractNbmMojo
{
/**
* output directory.
*/
@Parameter(defaultValue="${project.build.directory}", required=true)
protected File outputDirectory;
/**
* The branding token for the application based on NetBeans platform.
*/
@Parameter(property="netbeans.branding.token", required=true)
protected String brandingToken;
/**
* Installation directory name at the destination system
* Deprecated, to be removed, was never actually used.
*/
@Parameter(property="netbeans.branding.token")
protected String installDirName;
/**
* Prefix of all generated installers files
*/
@Parameter(defaultValue="${project.build.finalName}")
private String installersFilePrefix;
/**
* Create installer for Windows
*/
@Parameter(defaultValue="true")
private boolean installerOsWindows;
/**
* Create installer for Solaris
*/
@Parameter(defaultValue="true")
private boolean installerOsSolaris;
/**
* Create installer for Linux
*/
@Parameter(defaultValue="true")
private boolean installerOsLinux;
/**
* Create installer for MacOSx
*/
@Parameter(defaultValue="true")
private boolean installerOsMacosx;
/**
* Enable Pack200 compression
*/
@Parameter(defaultValue="true")
private boolean installerPack200Enable;
/**
* License file
*/
@Parameter(defaultValue="${basedir}/license.txt")
private File installerLicenseFile;
/**
* Custom installer template.
* This file, if provided, will replace default template from
* &lt;NetBeansInstallation&gt;/harness/nbi/stub/template.xml
*/
@Parameter
private File templateFile;
/**
* Parameters passed to templateFile
* or to installer/nbi/stub/template.xml
* to customize generated installers.
*
*/
@Parameter
private Map<String, String> userSettings;
/**
* Name of the zip artifact used to produce installers from (without .zip extension)
*/
@Parameter(defaultValue="${project.build.finalName}")
private String finalName;
// <editor-fold defaultstate="collapsed" desc="Component parameters">
/**
* Used for attaching the artifact in the project
*/
@Component
private MavenProjectHelper projectHelper;
@Parameter(readonly=true, required=true, property="basedir")
private File basedir;
/**
* The Maven Project.
*/
@Parameter(required=true, readonly=true, property="project")
private MavenProject project;
// </editor-fold>
@Override
public void execute()
throws MojoExecutionException, MojoFailureException
{
Project antProject = antProject();
if ( !"nbm-application".equals( project.getPackaging() ) )
{
throw new MojoExecutionException(
"This goal only makes sense on project with 'nbm-application' packaging." );
}
if (!installerOsLinux && !installerOsMacosx && !installerOsSolaris && !installerOsWindows) {
getLog().warn( "None of the Operating System Installers selected, skipping 'build-installers' goal.");
return;
}
String zipName = finalName + ".zip";
File zipFile = new File( outputDirectory, zipName );
getLog().info( String.format( "Running Build Installers action for (existing=%2$s) zip file %1$s",
zipFile, zipFile.exists() ) );
File appIconIcnsFile;
// Copy Netbeans Installer resources
FileUrlUtils fu = new FileUrlUtils();
File harnessDir = new File( outputDirectory, "installer" );
fu.copyResourcesRecursively( getClass().getClassLoader().getResource( "harness" ), harnessDir );
// Overwrite template file with modified version to accept branded images etc.
if ( templateFile != null )
{
File template = new File( harnessDir, "nbi/stub/template.xml" );
fu.copyFile( templateFile, template );
}
appIconIcnsFile = new File( harnessDir, "etc" + File.separatorChar + "applicationIcon.icns" );
getLog().info( "Application icon:" + appIconIcnsFile.getAbsolutePath() );
Map<String, String> props = new HashMap<String, String> ();
props.put( "suite.location", basedir.getAbsolutePath().replace( "\\", "/" ) );
props.put( "suite.props.app.name", brandingToken);
props.put( "suite.dist.zip", zipFile.getAbsolutePath().replace( "\\", "/" ) );
props.put( "suite.dist.directory", outputDirectory.getAbsolutePath().replace( "\\", "/" ) );
props.put( "installer.build.dir", new File( outputDirectory, "installerbuild" ).getAbsolutePath().replace( "\\", "/" ) );
props.put( "installers.file.prefix", installersFilePrefix );
// props.put( "install.dir.name", installDirName );
//mkleint: this is a flawed pattern! cannot make any assumption on multimodule layout
String appName = project.getParent().getArtifactId().replace( ".", "" ).replace( "-", "" ).replace( "_", "" ).replaceAll( "[0-9]+", "" );
props.put( "suite.nbi.product.uid", appName.toLowerCase( Locale.ENGLISH ) );
props.put( "suite.props.app.title", ( project.getName() + " " + project.getVersion() ).replaceAll( "-SNAPSHOT", "" ) );
String appVersion = project.getVersion().replaceAll( "-SNAPSHOT", "" );
props.put( "suite.nbi.product.version.short", appVersion );
while ( appVersion.split( "\\." ).length < 5 )
{
appVersion += ".0";
}
props.put( "suite.nbi.product.version", appVersion );
props.put( "nbi.stub.location", new File( harnessDir, "nbi/stub" ).getAbsolutePath().replace( "\\", "/" ) );
props.put( "nbi.stub.common.location", new File( harnessDir, "nbi/.common" ).getAbsolutePath().replace( "\\", "/" ) );
props.put( "nbi.ant.tasks.jar", new File( harnessDir, "modules/ext/nbi-ant-tasks.jar" ).getAbsolutePath().replace( "\\", "/" ) );
props.put( "nbi.registries.management.jar", new File( harnessDir, "modules/ext/nbi-registries-management.jar" ).getAbsolutePath().replace( "\\", "/" ) );
props.put( "nbi.engine.jar", new File( harnessDir, "modules/ext/nbi-engine.jar" ).getAbsolutePath().replace( "\\", "/" ) );
if ( installerLicenseFile != null )
{
getLog().info( String.format( "License file is at %1s, exist = %2$s", installerLicenseFile, installerLicenseFile.exists() ) );
props.put( "nbi.license.file", installerLicenseFile.getAbsolutePath() ); //mkleint: no path replacement here??
}
List<String> platforms = new ArrayList<String>();
if ( this.installerOsLinux )
{
platforms.add( "linux" );
File linuxFile = new File( outputDirectory, installersFilePrefix + "-linux.sh" );
projectHelper.attachArtifact( project, "sh", "linux", linuxFile );
}
if ( this.installerOsSolaris )
{
platforms.add( "solaris" );
File solarisFile = new File( outputDirectory, installersFilePrefix + "-solaris.sh" );
projectHelper.attachArtifact( project, "sh", "solaris", solarisFile );
}
if ( this.installerOsWindows )
{
platforms.add( "windows" );
File windowsFile = new File( outputDirectory, installersFilePrefix + "-windows.exe" );
projectHelper.attachArtifact( project, "exe", "windows", windowsFile );
}
if ( this.installerOsMacosx )
{
platforms.add( "macosx" );
File macosxFile = new File( outputDirectory, installersFilePrefix + "-macosx.tgz" );
projectHelper.attachArtifact( project, "tgz", "macosx", macosxFile );
}
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < platforms.size(); i++ )
{
if ( i != 0 )
{
sb.append( " " );
}
sb.append( platforms.get( i ) );
}
if ( sb.length() == 0 )
{
//nothing to build
getLog().warn( "Nothing to build." );
}
props.put( "generate.installer.for.platforms", sb.toString() );
File javaHome = new File( System.getProperty( "java.home" ) );
if ( new File( javaHome, "lib/rt.jar" ).exists() && javaHome.getName().equals( "jre" ) ) //mkleint: does this work on mac? no rt.jar there
{
javaHome = javaHome.getParentFile();
}
props.put( "generator-jdk-location-forward-slashes", javaHome.getAbsolutePath().replace( "\\", "/" ) );
props.put( "pack200.enabled", "" + installerPack200Enable );
if ( appIconIcnsFile != null )
{
props.put( "nbi.dock.icon.file", appIconIcnsFile.getAbsolutePath() );
}
try
{
antProject.setUserProperty( "ant.file", new File( harnessDir, "nbi/stub/template.xml" ).getAbsolutePath().replace( "\\", "/" ) );
ProjectHelper helper = ProjectHelper.getProjectHelper();
antProject.addReference( "ant.projectHelper", helper );
helper.parse( antProject, new File( harnessDir, "nbi/stub/template.xml" ) );
for ( Map.Entry<String, String> e : props.entrySet() )
{
antProject.setProperty( e.getKey(), e.getValue() );
}
if ( userSettings != null )
{
for ( Map.Entry<String, String> e : userSettings.entrySet() )
{
antProject.setProperty( e.getKey(), e.getValue() );
}
}
antProject.executeTarget( "build" );
}
catch ( Exception ex )
{
throw new MojoExecutionException( "Installers creation failed: " + ex, ex );
}
}
//mkleint: could this be replaced by something from plexus-utils?
private class FileUrlUtils
{
boolean copyFile( final File toCopy, final File destFile )
throws MojoExecutionException
{
try
{
return copyStream( new FileInputStream( toCopy ), new FileOutputStream( destFile ) );
}
catch ( final FileNotFoundException e )
{
throw new MojoExecutionException( "Installers creation failed: " + e, e );
}
}
boolean copyFilesRecusively( final File toCopy, final File destDir )
throws MojoExecutionException
{
assert destDir.isDirectory();
if ( !toCopy.isDirectory() )
{
return copyFile( toCopy, new File( destDir, toCopy.getName() ) );
}
else
{
final File newDestDir = new File( destDir, toCopy.getName() );
if ( !newDestDir.exists() && !newDestDir.mkdir() )
{
return false;
}
for ( final File child : toCopy.listFiles() )
{
if ( !copyFilesRecusively( child, newDestDir ) )
{
return false;
}
}
}
return true;
}
boolean copyJarResourcesRecursively( final File destDir, final JarURLConnection jarConnection )
throws IOException, MojoExecutionException
{
final JarFile jarFile = jarConnection.getJarFile();
for ( final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); )
{
final JarEntry entry = e.nextElement();
if ( entry.getName().startsWith( jarConnection.getEntryName() ) )
{
final String filename = StringUtils.removePrefix( entry.getName(), //
jarConnection.getEntryName() );
final File f = new File( destDir, filename );
if ( !entry.isDirectory() )
{
final InputStream entryInputStream = jarFile.getInputStream( entry );
if ( !copyStream( entryInputStream, f ) )
{
return false;
}
entryInputStream.close();
}
else
{
if ( !ensureDirectoryExists( f ) )
{
throw new IOException( "Could not create directory: "
+ f.getAbsolutePath() );
}
}
}
}
return true;
}
boolean copyResourcesRecursively( final URL originUrl, final File destination )
throws MojoExecutionException
{
try
{
final URLConnection urlConnection = originUrl.openConnection();
if ( urlConnection instanceof JarURLConnection )
{
return copyJarResourcesRecursively( destination, (JarURLConnection) urlConnection );
}
else
{
return copyFilesRecusively( new File( originUrl.getPath() ), destination );
}
}
catch ( final IOException e )
{
throw new MojoExecutionException( "Installers creation failed: " + e, e );
}
}
boolean copyStream( final InputStream is, final File f )
throws MojoExecutionException
{
try
{
return copyStream( is, new FileOutputStream( f ) );
}
catch ( final FileNotFoundException e )
{
throw new MojoExecutionException( "Installers creation failed: " + e, e );
}
}
boolean copyStream( final InputStream is, final OutputStream os )
throws MojoExecutionException
{
try
{
final byte[] buf = new byte[1024];
int len;
while ( ( len = is.read( buf ) ) > 0 )
{
os.write( buf, 0, len );
}
is.close();
os.close();
return true;
}
catch ( final IOException e )
{
throw new MojoExecutionException( "Installers creation failed: " + e, e );
}
}
boolean ensureDirectoryExists( final File f )
{
return f.exists() || f.mkdir();
}
}
}