ManifestPlugin xref

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.felix.bundleplugin;
    20
    21
    22  import java.io.File;
    23  import java.io.FileNotFoundException;
    24  import java.io.FileOutputStream;
    25  import java.io.IOException;
    26  import java.io.OutputStream;
    27  import java.util.Iterator;
    28  import java.util.LinkedHashMap;
    29  import java.util.Map;
    30  import java.util.Map.Entry;
    31  import java.util.Properties;
    32  import java.util.jar.Manifest;
    33
    34  import org.apache.maven.plugin.MojoExecutionException;
    35  import org.apache.maven.plugin.MojoFailureException;
    36  import org.apache.maven.project.MavenProject;
    37
    38  import aQute.lib.osgi.Analyzer;
    39  import aQute.lib.osgi.Builder;
    40  import aQute.lib.osgi.Jar;
    41  import aQute.lib.osgi.Resource;
    42
    43
    44  /**
    45   * Generate an OSGi manifest for this project
    46   *
    47   * @goal manifest
    48   * @phase process-classes
    49   * @requiresDependencyResolution test
    50   * @threadSafe
    51   */
    52  public class ManifestPlugin extends BundlePlugin
    53  {
    54      /**
    55       * When true, generate the manifest by rebuilding the full bundle in memory
    56       *
    57       * @parameter expression="${rebuildBundle}"
    58       */
    59      protected boolean rebuildBundle;
    60
    61
    62      @Override
    63      protected void execute( MavenProject project, Map instructions, Properties properties, Jar[] classpath )
    64          throws MojoExecutionException
    65      {
    66          Manifest manifest;
    67          try
    68          {
    69              manifest = getManifest( project, instructions, properties, classpath );
    70          }
    71          catch ( FileNotFoundException e )
    72          {
    73              throw new MojoExecutionException( "Cannot find " + e.getMessage()
    74                  + " (manifest goal must be run after compile phase)", e );
    75          }
    76          catch ( IOException e )
    77          {
    78              throw new MojoExecutionException( "Error trying to generate Manifest", e );
    79          }
    80          catch ( MojoFailureException e )
    81          {
    82              getLog().error( e.getLocalizedMessage() );
    83              throw new MojoExecutionException( "Error(s) found in manifest configuration", e );
    84          }
    85          catch ( Exception e )
    86          {
    87              getLog().error( "An internal error occurred", e );
    88              throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
    89          }
    90
    91          File outputFile = new File( manifestLocation, "MANIFEST.MF" );
    92
    93          try
    94          {
    95              writeManifest( manifest, outputFile );
    96          }
    97          catch ( IOException e )
    98          {
    99              throw new MojoExecutionException( "Error trying to write Manifest to file " + outputFile, e );
    100         }
    101     }
    102
    103
    104     public Manifest getManifest( MavenProject project, Jar[] classpath ) throws IOException, MojoFailureException,
    105         MojoExecutionException, Exception
    106     {
    107         return getManifest( project, new LinkedHashMap(), new Properties(), classpath );
    108     }
    109
    110
    111     public Manifest getManifest( MavenProject project, Map instructions, Properties properties, Jar[] classpath )
    112         throws IOException, MojoFailureException, MojoExecutionException, Exception
    113     {
    114         Analyzer analyzer = getAnalyzer( project, instructions, properties, classpath );
    115         boolean hasErrors = reportErrors( "Manifest " + project.getArtifact(), analyzer );
    116         if ( hasErrors )
    117         {
    118             String failok = analyzer.getProperty( "-failok" );
    119             if ( null == failok || "false".equalsIgnoreCase( failok ) )
    120             {
    121                 throw new MojoFailureException( "Error(s) found in manifest configuration" );
    122             }
    123         }
    124
    125         Jar jar = analyzer.getJar();
    126
    127         if ( unpackBundle )
    128         {
    129             File outputFile = getOutputDirectory();
    130             for ( Entry<String, Resource> entry : jar.getResources().entrySet() )
    131             {
    132                 File entryFile = new File( outputFile, entry.getKey() );
    133                 if ( !entryFile.exists() || entry.getValue().lastModified() == 0 )
    134                 {
    135                     entryFile.getParentFile().mkdirs();
    136                     OutputStream os = new FileOutputStream( entryFile );
    137                     entry.getValue().write( os );
    138                     os.close();
    139                 }
    140             }
    141         }
    142
    143         Manifest manifest = jar.getManifest();
    144
    145         // cleanup...
    146         analyzer.close();
    147
    148         return manifest;
    149     }
    150
    151
    152     protected Analyzer getAnalyzer( MavenProject project, Jar[] classpath ) throws IOException, MojoExecutionException,
    153         Exception
    154     {
    155         return getAnalyzer( project, new LinkedHashMap(), new Properties(), classpath );
    156     }
    157
    158
    159     protected Analyzer getAnalyzer( MavenProject project, Map instructions, Properties properties, Jar[] classpath )
    160         throws IOException, MojoExecutionException, Exception
    161     {
    162         if ( rebuildBundle && supportedProjectTypes.contains( project.getArtifact().getType() ) )
    163         {
    164             return buildOSGiBundle( project, instructions, properties, classpath );
    165         }
    166
    167         File file = project.getArtifact().getFile();
    168         if ( file == null )
    169         {
    170             file = getOutputDirectory();
    171         }
    172
    173         if ( !file.exists() )
    174         {
    175             throw new FileNotFoundException( file.getPath() );
    176         }
    177
    178         Builder analyzer = getOSGiBuilder( project, instructions, properties, classpath );
    179
    180         analyzer.setJar( file );
    181
    182         // calculateExportsFromContents when we have no explicit instructions defining
    183         // the contents of the bundle *and* we are not analyzing the output directory,
    184         // otherwise fall-back to addMavenInstructions approach
    185
    186         boolean isOutputDirectory = file.equals( getOutputDirectory() );
    187
    188         if ( analyzer.getProperty( Analyzer.EXPORT_PACKAGE ) == null
    189             && analyzer.getProperty( Analyzer.EXPORT_CONTENTS ) == null
    190             && analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) == null && !isOutputDirectory )
    191         {
    192             String export = calculateExportsFromContents( analyzer.getJar() );
    193             analyzer.setProperty( Analyzer.EXPORT_PACKAGE, export );
    194         }
    195
    196         addMavenInstructions( project, analyzer );
    197
    198         // if we spot Embed-Dependency and the bundle is "target/classes", assume we need to rebuild
    199         if ( analyzer.getProperty( DependencyEmbedder.EMBED_DEPENDENCY ) != null && isOutputDirectory )
    200         {
    201             analyzer.build();
    202         }
    203         else
    204         {
    205             analyzer.mergeManifest( analyzer.getJar().getManifest() );
    206             analyzer.calcManifest();
    207         }
    208
    209         mergeMavenManifest( project, analyzer );
    210
    211         return analyzer;
    212     }
    213
    214
    215     public static void writeManifest( Manifest manifest, File outputFile ) throws IOException
    216     {
    217         outputFile.getParentFile().mkdirs();
    218
    219         FileOutputStream os;
    220         os = new FileOutputStream( outputFile );
    221         try
    222         {
    223             Jar.writeManifest( manifest, os );
    224         }
    225         finally
    226         {
    227             try
    228             {
    229                 os.close();
    230             }
    231             catch ( IOException e )
    232             {
    233                 // nothing we can do here
    234             }
    235         }
    236     }
    237
    238
    239     /*
    240      * Patched version of bnd's Analyzer.calculateExportsFromContents
    241      */
    242     public static String calculateExportsFromContents( Jar bundle )
    243     {
    244         String ddel = "";
    245         StringBuffer sb = new StringBuffer();
    246         Map<String, Map<String, Resource>> map = bundle.getDirectories();
    247         for ( Iterator<Entry<String, Map<String, Resource>>> i = map.entrySet().iterator(); i.hasNext(); )
    248         {
    249             //----------------------------------------------------
    250             // should also ignore directories with no resources
    251             //----------------------------------------------------
    252             Entry<String, Map<String, Resource>> entry = i.next();
    253             if ( entry.getValue() == null || entry.getValue().isEmpty() )
    254                 continue;
    255             //----------------------------------------------------
    256             String directory = entry.getKey();
    257             if ( directory.equals( "META-INF" ) || directory.startsWith( "META-INF/" ) )
    258                 continue;
    259             if ( directory.equals( "OSGI-OPT" ) || directory.startsWith( "OSGI-OPT/" ) )
    260                 continue;
    261             if ( directory.equals( "/" ) )
    262                 continue;
    263
    264             if ( directory.endsWith( "/" ) )
    265                 directory = directory.substring( 0, directory.length() - 1 );
    266
    267             directory = directory.replace( '/', '.' );
    268             sb.append( ddel );
    269             sb.append( directory );
    270             ddel = ",";
    271         }
    272         return sb.toString();
    273     }
    274 }