blob: be323b201622437152ad01aacb789a4526e82c3d [file] [log] [blame]
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +00001/*
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 */
Richard S. Hall01a56ce2007-05-20 22:31:11 +000019package org.apache.felix.bundleplugin;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000020
Stuart McCulloch5ae59142008-01-29 06:21:05 +000021
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000022import java.io.File;
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +000023import java.io.FileInputStream;
Stuart McCulloch12b1d702007-09-11 07:48:10 +000024import java.io.FileNotFoundException;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000025import java.io.FileOutputStream;
26import java.io.IOException;
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +000027import java.io.InputStream;
Stuart McCulloch8cefad52011-10-16 17:53:16 +000028import java.io.OutputStream;
Stuart McCulloch07025e12012-02-11 17:28:48 +000029import java.util.Iterator;
Stuart McCullochd00f9712009-07-13 10:06:47 +000030import java.util.LinkedHashMap;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000031import java.util.Map;
Stuart McCulloch8cefad52011-10-16 17:53:16 +000032import java.util.Map.Entry;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000033import java.util.Properties;
34import java.util.jar.Manifest;
35
Guillaume Nodet88239df2015-07-09 19:45:59 +000036import aQute.bnd.header.Parameters;
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +000037import aQute.bnd.osgi.Instructions;
Guillaume Nodet88239df2015-07-09 19:45:59 +000038import aQute.bnd.osgi.Processor;
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +000039import aQute.lib.collections.ExtList;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000040import org.apache.maven.plugin.MojoExecutionException;
Stuart McCulloch034a1472008-07-07 09:38:32 +000041import org.apache.maven.plugin.MojoFailureException;
Carsten Ziegeler318c2cb2015-03-09 13:57:23 +000042import org.apache.maven.plugins.annotations.LifecyclePhase;
43import org.apache.maven.plugins.annotations.Mojo;
44import org.apache.maven.plugins.annotations.Parameter;
45import org.apache.maven.plugins.annotations.ResolutionScope;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000046import org.apache.maven.project.MavenProject;
47
Stuart McCulloch42151ee2012-07-16 13:43:38 +000048import aQute.bnd.osgi.Analyzer;
49import aQute.bnd.osgi.Builder;
50import aQute.bnd.osgi.Jar;
51import aQute.bnd.osgi.Resource;
Guillaume Nodeted7b1102015-07-09 19:45:09 +000052import org.apache.maven.shared.dependency.graph.DependencyNode;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000053
Stuart McCulloch5ae59142008-01-29 06:21:05 +000054
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000055/**
56 * Generate an OSGi manifest for this project
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000057 */
Carsten Ziegeler0a4eff12015-05-12 06:04:41 +000058@Mojo( name = "manifest", requiresDependencyResolution = ResolutionScope.TEST,
59 threadSafe = true,
60 defaultPhase = LifecyclePhase.PROCESS_CLASSES)
Stuart McCulloch5ae59142008-01-29 06:21:05 +000061public class ManifestPlugin extends BundlePlugin
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000062{
Stuart McCulloch9811fea2011-11-28 15:35:44 +000063 /**
Carsten Ziegeler82d87402015-03-04 13:26:21 +000064 * When true, generate the manifest by rebuilding the full bundle in memory
Stuart McCulloch9811fea2011-11-28 15:35:44 +000065 */
Carsten Ziegeler318c2cb2015-03-09 13:57:23 +000066 @Parameter( property = "rebuildBundle" )
Stuart McCulloch9811fea2011-11-28 15:35:44 +000067 protected boolean rebuildBundle;
68
Guillaume Nodet88239df2015-07-09 19:45:59 +000069 /**
70 * Directory where the SCR files will be written
71 *
72 * @parameter expression="${scrLocation}" default-value="${project.build.outputDirectory}"
73 */
74 protected File scrLocation;
75
76 /**
77 * When true, dump the generated SCR files
78 * @parameter
79 */
80 protected boolean exportScr;
Stuart McCulloch9811fea2011-11-28 15:35:44 +000081
Stuart McCulloch8cefad52011-10-16 17:53:16 +000082 @Override
Guillaume Nodeted7b1102015-07-09 19:45:09 +000083 protected void execute( MavenProject project, DependencyNode dependencyGraph, Map<String, String> instructions, Properties properties, Jar[] classpath )
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000084 throws MojoExecutionException
85 {
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +000086 Analyzer analyzer;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000087 try
88 {
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +000089 analyzer = getAnalyzer(project, dependencyGraph, instructions, properties, classpath);
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000090 }
Stuart McCulloch12b1d702007-09-11 07:48:10 +000091 catch ( FileNotFoundException e )
92 {
Stuart McCulloch5ae59142008-01-29 06:21:05 +000093 throw new MojoExecutionException( "Cannot find " + e.getMessage()
94 + " (manifest goal must be run after compile phase)", e );
Stuart McCulloch12b1d702007-09-11 07:48:10 +000095 }
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +000096 catch ( IOException e )
97 {
98 throw new MojoExecutionException( "Error trying to generate Manifest", e );
99 }
Stuart McCulloch034a1472008-07-07 09:38:32 +0000100 catch ( MojoFailureException e )
101 {
102 getLog().error( e.getLocalizedMessage() );
103 throw new MojoExecutionException( "Error(s) found in manifest configuration", e );
104 }
Stuart McCullocha2f57592008-01-30 08:03:57 +0000105 catch ( Exception e )
106 {
107 getLog().error( "An internal error occurred", e );
108 throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
109 }
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000110
Stuart McCulloch9366a822008-01-29 07:45:57 +0000111 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000112
113 try
114 {
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +0000115 writeManifest( analyzer, outputFile, niceManifest );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000116 }
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +0000117 catch ( Exception e )
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000118 {
119 throw new MojoExecutionException( "Error trying to write Manifest to file " + outputFile, e );
120 }
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +0000121 finally
122 {
123 analyzer.close();
124 }
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000125 }
126
Stuart McCulloch5ae59142008-01-29 06:21:05 +0000127
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000128 public Manifest getManifest( MavenProject project, DependencyNode dependencyGraph, Jar[] classpath ) throws IOException, MojoFailureException,
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000129 MojoExecutionException, Exception
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000130 {
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000131 return getManifest( project, dependencyGraph, new LinkedHashMap<String, String>(), new Properties(), classpath );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000132 }
133
Stuart McCulloch5ae59142008-01-29 06:21:05 +0000134
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000135 public Manifest getManifest( MavenProject project, DependencyNode dependencyGraph, Map<String, String> instructions, Properties properties, Jar[] classpath )
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000136 throws IOException, MojoFailureException, MojoExecutionException, Exception
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000137 {
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +0000138 Analyzer analyzer = getAnalyzer(project, dependencyGraph, instructions, properties, classpath);
Stuart McCulloch034a1472008-07-07 09:38:32 +0000139
Guillaume Nodet88239df2015-07-09 19:45:59 +0000140 Jar jar = analyzer.getJar();
141 Manifest manifest = jar.getManifest();
142
143 if (exportScr)
144 {
145 scrLocation.mkdirs();
146
147 String bpHeader = analyzer.getProperty(Analyzer.SERVICE_COMPONENT);
148 Parameters map = Processor.parseHeader(bpHeader, null);
149 for (String root : map.keySet())
150 {
151 Map<String, Resource> dir = jar.getDirectories().get(root);
152 File location = new File(scrLocation, root);
153 if (dir == null || dir.isEmpty())
154 {
155 Resource resource = jar.getResource(root);
156 if (resource != null)
157 {
158 writeSCR(resource, location);
159 }
160 }
161 else
162 {
163 for (Map.Entry<String, Resource> entry : dir.entrySet())
164 {
165 String path = entry.getKey();
166 Resource resource = entry.getValue();
167 writeSCR(resource, new File(location, path));
168 }
169 }
170 }
171 }
Stuart McCullochd8a04c52008-08-06 16:05:23 +0000172
173 // cleanup...
174 analyzer.close();
175
176 return manifest;
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000177 }
178
Guillaume Nodet88239df2015-07-09 19:45:59 +0000179 protected void writeSCR(Resource resource, File destination) throws Exception
180 {
181 destination.getParentFile().mkdirs();
182 OutputStream os = new FileOutputStream(destination);
183 try
184 {
185 resource.write(os);
186 }
187 finally
188 {
189 os.close();
190 }
191 }
Stuart McCulloch5ae59142008-01-29 06:21:05 +0000192
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000193 protected Analyzer getAnalyzer( MavenProject project, DependencyNode dependencyGraph, Jar[] classpath ) throws IOException, MojoExecutionException,
Stuart McCullochd98b02e2011-06-28 23:20:37 +0000194 Exception
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000195 {
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000196 return getAnalyzer( project, dependencyGraph, new LinkedHashMap<String, String>(), new Properties(), classpath );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000197 }
198
Stuart McCulloch5ae59142008-01-29 06:21:05 +0000199
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000200 protected Analyzer getAnalyzer( MavenProject project, DependencyNode dependencyGraph, Map<String, String> instructions, Properties properties, Jar[] classpath )
Richard S. Hall2c9e5922010-10-25 19:07:06 +0000201 throws IOException, MojoExecutionException, Exception
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000202 {
Stuart McCulloch9811fea2011-11-28 15:35:44 +0000203 if ( rebuildBundle && supportedProjectTypes.contains( project.getArtifact().getType() ) )
Stuart McCulloch8cefad52011-10-16 17:53:16 +0000204 {
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000205 return buildOSGiBundle( project, dependencyGraph, instructions, properties, classpath );
Stuart McCulloch8cefad52011-10-16 17:53:16 +0000206 }
207
Guillaume Nodet09194e62014-06-16 07:07:12 +0000208 File file = getOutputDirectory();
Karl Paulse816f1e2007-07-13 13:31:25 +0000209 if ( file == null )
Richard S. Hall47a18162007-07-05 19:02:51 +0000210 {
Guillaume Nodet09194e62014-06-16 07:07:12 +0000211 file = project.getArtifact().getFile();
Richard S. Hall47a18162007-07-05 19:02:51 +0000212 }
Stuart McCulloch12b1d702007-09-11 07:48:10 +0000213
214 if ( !file.exists() )
Karl Pauls9eb62552007-07-11 14:58:35 +0000215 {
Stuart McCulloch2ae5d3d2012-07-26 17:49:28 +0000216 if ( file.equals( getOutputDirectory() ) )
217 {
218 file.mkdirs();
219 }
220 else
221 {
222 throw new FileNotFoundException( file.getPath() );
223 }
Karl Pauls9eb62552007-07-11 14:58:35 +0000224 }
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000225
Stuart McCulloch5fcbe4a2011-06-28 18:26:45 +0000226 Builder analyzer = getOSGiBuilder( project, instructions, properties, classpath );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000227
Stuart McCulloch62cf56b2008-07-07 13:04:51 +0000228 analyzer.setJar( file );
229
Stuart McCulloch07025e12012-02-11 17:28:48 +0000230 // calculateExportsFromContents when we have no explicit instructions defining
231 // the contents of the bundle *and* we are not analyzing the output directory,
232 // otherwise fall-back to addMavenInstructions approach
233
Stuart McCulloch417fe902012-02-11 18:31:45 +0000234 boolean isOutputDirectory = file.equals( getOutputDirectory() );
235
Stuart McCullochd98b02e2011-06-28 23:20:37 +0000236 if ( analyzer.getProperty( Analyzer.EXPORT_PACKAGE ) == null
237 && analyzer.getProperty( Analyzer.EXPORT_CONTENTS ) == null
Stuart McCulloch417fe902012-02-11 18:31:45 +0000238 && analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) == null && !isOutputDirectory )
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000239 {
Stuart McCulloch07025e12012-02-11 17:28:48 +0000240 String export = calculateExportsFromContents( analyzer.getJar() );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000241 analyzer.setProperty( Analyzer.EXPORT_PACKAGE, export );
242 }
243
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000244 addMavenInstructions( project, dependencyGraph, analyzer );
Stuart McCulloch598d2bd2009-09-02 22:06:58 +0000245
Stuart McCulloch417fe902012-02-11 18:31:45 +0000246 // if we spot Embed-Dependency and the bundle is "target/classes", assume we need to rebuild
247 if ( analyzer.getProperty( DependencyEmbedder.EMBED_DEPENDENCY ) != null && isOutputDirectory )
248 {
249 analyzer.build();
250 }
251 else
252 {
253 analyzer.mergeManifest( analyzer.getJar().getManifest() );
Stuart McCulloch2ae5d3d2012-07-26 17:49:28 +0000254 analyzer.getJar().setManifest( analyzer.calcManifest() );
Stuart McCulloch417fe902012-02-11 18:31:45 +0000255 }
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000256
Guillaume Nodeted7b1102015-07-09 19:45:09 +0000257 mergeMavenManifest( project, dependencyGraph, analyzer );
Stuart McCulloch598d2bd2009-09-02 22:06:58 +0000258
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +0000259 boolean hasErrors = reportErrors( "Manifest " + project.getArtifact(), analyzer );
260 if ( hasErrors )
261 {
262 String failok = analyzer.getProperty( "-failok" );
263 if ( null == failok || "false".equalsIgnoreCase( failok ) )
264 {
265 throw new MojoFailureException( "Error(s) found in manifest configuration" );
266 }
267 }
268
269 Jar jar = analyzer.getJar();
270
271 if ( unpackBundle )
272 {
273 File outputFile = getOutputDirectory();
274 for ( Entry<String, Resource> entry : jar.getResources().entrySet() )
275 {
276 File entryFile = new File( outputFile, entry.getKey() );
277 if ( !entryFile.exists() || entry.getValue().lastModified() == 0 )
278 {
279 entryFile.getParentFile().mkdirs();
280 OutputStream os = new FileOutputStream( entryFile );
281 entry.getValue().write( os );
282 os.close();
283 }
284 }
285 }
286
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000287 return analyzer;
288 }
289
Stuart McCulloch5ae59142008-01-29 06:21:05 +0000290
Guillaume Nodetdfe0fe72015-07-09 19:45:35 +0000291 public static void writeManifest( Analyzer analyzer, File outputFile, boolean niceManifest ) throws Exception
292 {
293 Properties properties = analyzer.getProperties();
294 Manifest manifest = analyzer.getJar().getManifest();
295 if ( outputFile.exists() && properties.containsKey( "Merge-Headers" ) )
296 {
297 Manifest analyzerManifest = manifest;
298 manifest = new Manifest();
299 InputStream inputStream = new FileInputStream( outputFile );
300 try
301 {
302 manifest.read( inputStream );
303 }
304 finally
305 {
306 inputStream.close();
307 }
308 Instructions instructions = new Instructions( ExtList.from( analyzer.getProperty("Merge-Headers") ) );
309 mergeManifest( instructions, manifest, analyzerManifest );
310 }
311 else
312 {
313 File parentFile = outputFile.getParentFile();
314 parentFile.mkdirs();
315 }
316 writeManifest( manifest, outputFile, niceManifest );
317 }
318
319
Stuart McCulloch9a710e52014-06-23 12:01:53 +0000320 public static void writeManifest( Manifest manifest, File outputFile, boolean niceManifest ) throws IOException
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000321 {
322 outputFile.getParentFile().mkdirs();
323
324 FileOutputStream os;
325 os = new FileOutputStream( outputFile );
326 try
327 {
Stuart McCulloch9a710e52014-06-23 12:01:53 +0000328 ManifestWriter.outputManifest( manifest, os, niceManifest );
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000329 }
330 finally
331 {
Stuart McCullochf806f862008-02-04 07:57:09 +0000332 try
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000333 {
Stuart McCullochf806f862008-02-04 07:57:09 +0000334 os.close();
335 }
336 catch ( IOException e )
337 {
338 // nothing we can do here
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000339 }
340 }
341 }
Stuart McCulloch07025e12012-02-11 17:28:48 +0000342
343
344 /*
345 * Patched version of bnd's Analyzer.calculateExportsFromContents
346 */
347 public static String calculateExportsFromContents( Jar bundle )
348 {
349 String ddel = "";
350 StringBuffer sb = new StringBuffer();
351 Map<String, Map<String, Resource>> map = bundle.getDirectories();
352 for ( Iterator<Entry<String, Map<String, Resource>>> i = map.entrySet().iterator(); i.hasNext(); )
353 {
354 //----------------------------------------------------
355 // should also ignore directories with no resources
356 //----------------------------------------------------
357 Entry<String, Map<String, Resource>> entry = i.next();
358 if ( entry.getValue() == null || entry.getValue().isEmpty() )
359 continue;
360 //----------------------------------------------------
361 String directory = entry.getKey();
362 if ( directory.equals( "META-INF" ) || directory.startsWith( "META-INF/" ) )
363 continue;
364 if ( directory.equals( "OSGI-OPT" ) || directory.startsWith( "OSGI-OPT/" ) )
365 continue;
366 if ( directory.equals( "/" ) )
367 continue;
368
369 if ( directory.endsWith( "/" ) )
370 directory = directory.substring( 0, directory.length() - 1 );
371
372 directory = directory.replace( '/', '.' );
373 sb.append( ddel );
374 sb.append( directory );
375 ddel = ",";
376 }
377 return sb.toString();
378 }
Richard S. Hall6cf6f0b2007-04-10 16:50:31 +0000379}