View Javadoc

1   /*
2    * Copyright 2012 Frantisek Mantlik <frantisek at mantlik.cz>.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   * under the License.
16   */
17  package org.codehaus.mojo.nbm;
18  
19  import java.io.*;
20  import java.net.JarURLConnection;
21  import java.net.URL;
22  import java.net.URLConnection;
23  import java.util.*;
24  import java.util.jar.JarEntry;
25  import java.util.jar.JarFile;
26  import org.apache.maven.plugin.MojoExecutionException;
27  import org.apache.maven.plugin.MojoFailureException;
28  import org.apache.maven.plugins.annotations.Component;
29  import org.apache.maven.plugins.annotations.LifecyclePhase;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.plugins.annotations.ResolutionScope;
33  import org.apache.maven.project.MavenProject;
34  import org.apache.maven.project.MavenProjectHelper;
35  import org.apache.tools.ant.Project;
36  import org.apache.tools.ant.ProjectHelper;
37  import org.apache.tools.ant.util.StringUtils;
38  
39  /**
40   * Build installers for Mavenized NetBeans application.
41   * Creates installers for supported operating systems
42   * and packages each installer as a deployable artifact.
43   * <p>See a <a href="http://mojo.codehaus.org/nbm-maven/nbm-maven-plugin/buildinstexample.html">how-to</a> on customizing the installer.
44   * @author <a href="mailto:frantisek@mantlik.cz">Frantisek Mantlik</a>
45   */
46  @Mojo(name="build-installers", 
47          requiresProject=true, 
48          requiresDependencyResolution=ResolutionScope.RUNTIME,
49          threadSafe = true,
50          defaultPhase=LifecyclePhase.PACKAGE )
51  public class BuildInstallersMojo
52          extends AbstractNbmMojo
53  {
54  
55      /**
56      * output directory.
57      */
58      @Parameter(defaultValue="${project.build.directory}", required=true)
59      protected File outputDirectory;
60      /**
61      * The branding token for the application based on NetBeans platform.
62      */
63      @Parameter(property="netbeans.branding.token", required=true)
64      protected String brandingToken;
65      /**
66      * Installation directory name at the destination system
67      * Deprecated, to be removed, was never actually used.
68      */
69      @Parameter(property="netbeans.branding.token")
70      protected String installDirName;
71      /**
72      * Prefix of all generated installers files
73      */
74      @Parameter(defaultValue="${project.build.finalName}")
75      private String installersFilePrefix;
76      /**
77       * Create installer for Windows
78       */
79      @Parameter(defaultValue="true")
80      private boolean installerOsWindows;
81      /**
82       * Create installer for Solaris
83       */
84      @Parameter(defaultValue="true")
85      private boolean installerOsSolaris;
86      /**
87       * Create installer for Linux
88       */
89      @Parameter(defaultValue="true")    
90      private boolean installerOsLinux;
91      /**
92       * Create installer for MacOSx
93       */
94      @Parameter(defaultValue="true")    
95      private boolean installerOsMacosx;
96      /**
97       * Enable Pack200 compression
98       */
99      @Parameter(defaultValue="true")
100     private boolean installerPack200Enable;
101     /**
102      * License file
103      */
104     @Parameter(defaultValue="${basedir}/license.txt")
105     private File installerLicenseFile;
106     /**
107      * Custom installer template.
108      * This file, if provided, will replace default template from
109      * &lt;NetBeansInstallation&gt;/harness/nbi/stub/template.xml
110      */
111     @Parameter
112     private File templateFile;
113     /**
114      * Parameters passed to templateFile 
115      * or to installer/nbi/stub/template.xml 
116      * to customize generated installers.
117      *
118      */
119     @Parameter
120     private Map<String, String> userSettings;
121     
122     /**
123      * Name of the zip artifact used to produce installers from (without .zip extension)
124      */
125     @Parameter(defaultValue="${project.build.finalName}")
126     private String finalName;
127 
128     // <editor-fold defaultstate="collapsed" desc="Component parameters">
129     /**
130      * Used for attaching the artifact in the project
131      */
132     @Component
133     private MavenProjectHelper projectHelper;
134         
135     @Parameter(readonly=true, required=true, property="basedir")
136     private File basedir;
137     /**
138     * The Maven Project.
139     */
140     @Parameter(required=true, readonly=true, property="project")    
141     private MavenProject project;    
142 
143     // </editor-fold>
144     @Override
145     public void execute()
146         throws MojoExecutionException, MojoFailureException
147     {
148         Project antProject = antProject();
149 
150         if ( !"nbm-application".equals( project.getPackaging() ) )
151         {
152             throw new MojoExecutionException(
153                     "This goal only makes sense on project with 'nbm-application' packaging." );
154         }
155         
156         if (!installerOsLinux && !installerOsMacosx && !installerOsSolaris && !installerOsWindows) {
157             getLog().warn( "None of the Operating System Installers selected, skipping 'build-installers' goal.");
158             return;
159         }
160 
161         String zipName = finalName + ".zip";
162         File zipFile = new File( outputDirectory, zipName );
163         getLog().info( String.format( "Running Build Installers action for (existing=%2$s) zip file %1$s",
164                 zipFile, zipFile.exists() ) );
165 
166 
167         File appIconIcnsFile;
168 
169         // Copy Netbeans Installer resources
170         FileUrlUtils fu = new FileUrlUtils();
171         File harnessDir = new File( outputDirectory, "installer" );
172         fu.copyResourcesRecursively( getClass().getClassLoader().getResource( "harness" ), harnessDir );
173 
174         // Overwrite template file with modified version to accept branded images etc.
175         if ( templateFile != null )
176         {
177             File template = new File( harnessDir, "nbi/stub/template.xml" );
178             fu.copyFile( templateFile, template );
179         }
180 
181         appIconIcnsFile = new File( harnessDir, "etc" + File.separatorChar + "applicationIcon.icns" );
182         getLog().info( "Application icon:" + appIconIcnsFile.getAbsolutePath() );
183 
184         Map<String, String> props = new HashMap<String, String> ();
185 
186         props.put( "suite.location", basedir.getAbsolutePath().replace( "\\", "/" ) );
187         props.put( "suite.props.app.name", brandingToken);
188         props.put( "suite.dist.zip", zipFile.getAbsolutePath().replace( "\\", "/" ) );
189         props.put( "suite.dist.directory", outputDirectory.getAbsolutePath().replace( "\\", "/" ) );
190         props.put( "installer.build.dir", new File( outputDirectory, "installerbuild" ).getAbsolutePath().replace( "\\", "/" ) );
191         
192         props.put( "installers.file.prefix", installersFilePrefix );
193 
194 //        props.put( "install.dir.name", installDirName );
195 
196         //mkleint: this is a flawed pattern! cannot make any assumption on multimodule layout
197         String appName = project.getParent().getArtifactId().replace( ".", "" ).replace( "-", "" ).replace( "_", "" ).replaceAll( "[0-9]+", "" );
198         props.put( "suite.nbi.product.uid", appName.toLowerCase( Locale.ENGLISH ) );
199 
200         props.put( "suite.props.app.title", ( project.getName() + " " + project.getVersion() ).replaceAll( "-SNAPSHOT", "" ) );
201 
202         String appVersion = project.getVersion().replaceAll( "-SNAPSHOT", "" );
203         props.put( "suite.nbi.product.version.short", appVersion );
204         while ( appVersion.split( "\\." ).length < 5 )
205         {
206             appVersion += ".0";
207         }
208         props.put( "suite.nbi.product.version", appVersion );
209 
210         props.put( "nbi.stub.location", new File( harnessDir, "nbi/stub" ).getAbsolutePath().replace( "\\", "/" ) );
211 
212         props.put( "nbi.stub.common.location", new File( harnessDir, "nbi/.common" ).getAbsolutePath().replace( "\\", "/" ) );
213 
214         props.put( "nbi.ant.tasks.jar", new File( harnessDir, "modules/ext/nbi-ant-tasks.jar" ).getAbsolutePath().replace( "\\", "/" ) );
215 
216         props.put( "nbi.registries.management.jar", new File( harnessDir, "modules/ext/nbi-registries-management.jar" ).getAbsolutePath().replace( "\\", "/" ) );
217 
218         props.put( "nbi.engine.jar", new File( harnessDir, "modules/ext/nbi-engine.jar" ).getAbsolutePath().replace( "\\", "/" ) );
219 
220         if ( installerLicenseFile != null )
221         {
222             getLog().info( String.format( "License file is at %1s, exist = %2$s", installerLicenseFile, installerLicenseFile.exists() ) );
223             props.put( "nbi.license.file", installerLicenseFile.getAbsolutePath() ); //mkleint: no path replacement here??
224         }
225 
226         List<String> platforms = new ArrayList<String>();
227 
228         if ( this.installerOsLinux )
229         {
230             platforms.add( "linux" );
231             File linuxFile = new File( outputDirectory, installersFilePrefix + "-linux.sh" );
232             projectHelper.attachArtifact( project, "sh", "linux", linuxFile );
233         }
234         if ( this.installerOsSolaris )
235         {
236             platforms.add( "solaris" );
237             File solarisFile = new File( outputDirectory, installersFilePrefix + "-solaris.sh" );
238             projectHelper.attachArtifact( project, "sh", "solaris", solarisFile );
239         }
240         if ( this.installerOsWindows )
241         {
242             platforms.add( "windows" );
243             File windowsFile = new File( outputDirectory, installersFilePrefix + "-windows.exe" );
244             projectHelper.attachArtifact( project, "exe", "windows", windowsFile );
245         }
246         if ( this.installerOsMacosx )
247         {
248             platforms.add( "macosx" );
249             File macosxFile = new File( outputDirectory, installersFilePrefix + "-macosx.tgz" );
250             projectHelper.attachArtifact( project, "tgz", "macosx", macosxFile );
251         }
252 
253         StringBuilder sb = new StringBuilder();
254         for ( int i = 0; i < platforms.size(); i++ )
255         {
256             if ( i != 0 )
257             {
258                 sb.append( " " );
259             }
260             sb.append( platforms.get( i ) );
261         }
262         if ( sb.length() == 0 )
263         {
264             //nothing to build
265             getLog().warn( "Nothing to build." );
266         }
267 
268         props.put( "generate.installer.for.platforms", sb.toString() );
269 
270         File javaHome = new File( System.getProperty( "java.home" ) );
271         if ( new File( javaHome, "lib/rt.jar" ).exists() && javaHome.getName().equals( "jre" ) ) //mkleint: does this work on mac? no rt.jar there
272         {
273             javaHome = javaHome.getParentFile();
274         }
275         props.put( "generator-jdk-location-forward-slashes", javaHome.getAbsolutePath().replace( "\\", "/" ) );
276 
277         props.put( "pack200.enabled", "" + installerPack200Enable );
278 
279         if ( appIconIcnsFile != null )
280         {
281             props.put( "nbi.dock.icon.file", appIconIcnsFile.getAbsolutePath() );
282         }
283 
284         try
285         {
286             antProject.setUserProperty( "ant.file", new File( harnessDir, "nbi/stub/template.xml" ).getAbsolutePath().replace( "\\", "/" ) );
287             ProjectHelper helper = ProjectHelper.getProjectHelper();
288             antProject.addReference( "ant.projectHelper", helper );
289             helper.parse( antProject, new File( harnessDir, "nbi/stub/template.xml" ) );
290             for ( Map.Entry<String, String> e : props.entrySet() )
291             {
292                 antProject.setProperty( e.getKey(), e.getValue() );
293             }
294             if ( userSettings != null )
295             {
296                 for ( Map.Entry<String, String> e : userSettings.entrySet() )
297                 {
298                     antProject.setProperty( e.getKey(), e.getValue() );
299                 }
300             }
301             antProject.executeTarget( "build" );
302         }
303         catch ( Exception ex )
304         {
305             throw new MojoExecutionException( "Installers creation failed: " + ex, ex );
306         }
307     }
308 
309     //mkleint: could this be replaced by something from plexus-utils?
310     private class FileUrlUtils
311     {
312 
313         boolean copyFile( final File toCopy, final File destFile )
314             throws MojoExecutionException
315         {
316             try
317             {
318                 return copyStream( new FileInputStream( toCopy ), new FileOutputStream( destFile ) );
319             }
320             catch ( final FileNotFoundException e )
321             {
322                 throw new MojoExecutionException( "Installers creation failed: " + e, e );
323             }
324         }
325 
326         boolean copyFilesRecusively( final File toCopy, final File destDir )
327             throws MojoExecutionException
328         {
329             assert destDir.isDirectory();
330 
331             if ( !toCopy.isDirectory() )
332             {
333                 return copyFile( toCopy, new File( destDir, toCopy.getName() ) );
334             }
335             else
336             {
337                 final File newDestDir = new File( destDir, toCopy.getName() );
338                 if ( !newDestDir.exists() && !newDestDir.mkdir() )
339                 {
340                     return false;
341                 }
342                 for ( final File child : toCopy.listFiles() )
343                 {
344                     if ( !copyFilesRecusively( child, newDestDir ) )
345                     {
346                         return false;
347                     }
348                 }
349             }
350             return true;
351         }
352 
353         boolean copyJarResourcesRecursively( final File destDir, final JarURLConnection jarConnection )
354             throws IOException, MojoExecutionException
355         {
356 
357             final JarFile jarFile = jarConnection.getJarFile();
358 
359             for ( final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements(); )
360             {
361                 final JarEntry entry = e.nextElement();
362                 if ( entry.getName().startsWith( jarConnection.getEntryName() ) )
363                 {
364                     final String filename = StringUtils.removePrefix( entry.getName(), //
365                             jarConnection.getEntryName() );
366 
367                     final File f = new File( destDir, filename );
368                     if ( !entry.isDirectory() )
369                     {
370                         final InputStream entryInputStream = jarFile.getInputStream( entry );
371                         if ( !copyStream( entryInputStream, f ) )
372                         {
373                             return false;
374                         }
375                         entryInputStream.close();
376                     }
377                     else
378                     {
379                         if ( !ensureDirectoryExists( f ) )
380                         {
381                             throw new IOException( "Could not create directory: "
382                                     + f.getAbsolutePath() );
383                         }
384                     }
385                 }
386             }
387             return true;
388         }
389 
390         boolean copyResourcesRecursively( final URL originUrl, final File destination )
391             throws MojoExecutionException
392         {
393             try
394             {
395                 final URLConnection urlConnection = originUrl.openConnection();
396                 if ( urlConnection instanceof JarURLConnection )
397                 {
398                     return copyJarResourcesRecursively( destination, (JarURLConnection) urlConnection );
399                 }
400                 else
401                 {
402                     return copyFilesRecusively( new File( originUrl.getPath() ), destination );
403                 }
404             }
405             catch ( final IOException e )
406             {
407                 throw new MojoExecutionException( "Installers creation failed: " + e, e );
408             }
409         }
410 
411         boolean copyStream( final InputStream is, final File f )
412             throws MojoExecutionException
413         {
414             try
415             {
416                 return copyStream( is, new FileOutputStream( f ) );
417             }
418             catch ( final FileNotFoundException e )
419             {
420                 throw new MojoExecutionException( "Installers creation failed: " + e, e );
421             }
422         }
423 
424         boolean copyStream( final InputStream is, final OutputStream os )
425             throws MojoExecutionException
426         {
427             try
428             {
429                 final byte[] buf = new byte[1024];
430 
431                 int len;
432                 while ( ( len = is.read( buf ) ) > 0 )
433                 {
434                     os.write( buf, 0, len );
435                 }
436                 is.close();
437                 os.close();
438                 return true;
439             }
440             catch ( final IOException e )
441             {
442                 throw new MojoExecutionException( "Installers creation failed: " + e, e );
443             }
444         }
445 
446         boolean ensureDirectoryExists( final File f )
447         {
448             return f.exists() || f.mkdir();
449         }
450     }
451 }