View Javadoc
1   package org.apache.maven.archiver;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.DependencyResolutionRequiredException;
24  import org.apache.maven.artifact.versioning.ArtifactVersion;
25  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
26  import org.apache.maven.execution.MavenSession;
27  import org.apache.maven.project.MavenProject;
28  import org.codehaus.plexus.archiver.jar.JarArchiver;
29  import org.codehaus.plexus.archiver.jar.Manifest;
30  import org.codehaus.plexus.archiver.jar.ManifestException;
31  import org.codehaus.plexus.interpolation.InterpolationException;
32  import org.codehaus.plexus.interpolation.Interpolator;
33  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
34  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
35  import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
36  import org.codehaus.plexus.interpolation.RecursionInterceptor;
37  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
38  import org.codehaus.plexus.interpolation.ValueSource;
39  import org.apache.maven.shared.utils.StringUtils;
40  
41  import javax.lang.model.SourceVersion;
42  import java.io.File;
43  import java.io.IOException;
44  import java.util.ArrayList;
45  import java.util.Collections;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.Properties;
49  import java.util.Set;
50  
51  /**
52   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
53   * @author kama
54   * @version $Revision$ $Date$
55   */
56  public class MavenArchiver
57  {
58  
59      /**
60       * The simply layout.
61       */
62      public static final String SIMPLE_LAYOUT =
63          "${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension}";
64  
65      /**
66       * Repository layout.
67       */
68      public static final String REPOSITORY_LAYOUT =
69          "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
70              + "${artifact.version}${dashClassifier?}.${artifact.extension}";
71  
72      /**
73       * simple layout non unique.
74       */
75      public static final String SIMPLE_LAYOUT_NONUNIQUE =
76          "${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
77  
78      /**
79       * Repository layout non unique.
80       */
81      public static final String REPOSITORY_LAYOUT_NONUNIQUE =
82          "${artifact.groupIdPath}/${artifact.artifactId}/" + "${artifact.baseVersion}/${artifact.artifactId}-"
83              + "${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
84  
85      private static final List<String> ARTIFACT_EXPRESSION_PREFIXES;
86  
87      static
88      {
89          List<String> artifactExpressionPrefixes = new ArrayList<String>();
90          artifactExpressionPrefixes.add( "artifact." );
91  
92          ARTIFACT_EXPRESSION_PREFIXES = artifactExpressionPrefixes;
93      }
94  
95      static boolean isValidModuleName( String name )
96      {
97          return SourceVersion.isName( name );
98      }
99  
100     private JarArchiver archiver;
101 
102     private File archiveFile;
103 
104     /**
105      * @param session The Maven Session.
106      * @param project The Maven Project.
107      * @param config The MavenArchiveConfiguration
108      * @return The {@link Manifest}
109      * @throws ManifestException In case of a failure.
110      * @throws DependencyResolutionRequiredException Resolution failure.
111      */
112     public Manifest getManifest( MavenSession session, MavenProject project, MavenArchiveConfiguration config )
113         throws ManifestException, DependencyResolutionRequiredException
114     {
115         boolean hasManifestEntries = !config.isManifestEntriesEmpty();
116         Map<String, String> entries =
117             hasManifestEntries ? config.getManifestEntries() : Collections.<String, String>emptyMap();
118 
119         Manifest manifest = getManifest( session, project, config.getManifest(), entries );
120 
121         // any custom manifest entries in the archive configuration manifest?
122         if ( hasManifestEntries )
123         {
124 
125             for ( Map.Entry<String, String> entry : entries.entrySet() )
126             {
127                 String key = entry.getKey();
128                 String value = entry.getValue();
129                 Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
130                 if ( key.equals( "ClassPath" ) && attr != null )
131                 {
132                     // Merge the user-supplied Class-Path value with the programmatically
133                     // created Class-Path. Note that the user-supplied value goes first
134                     // so that resources there will override any in the standard Class-Path.
135                     attr.setValue( value + " " + attr.getValue() );
136                 }
137                 else
138                 {
139                     addManifestAttribute( manifest, key, value );
140                 }
141             }
142         }
143 
144         // any custom manifest sections in the archive configuration manifest?
145         if ( !config.isManifestSectionsEmpty() )
146         {
147             for ( ManifestSection section : config.getManifestSections() )
148             {
149                 Manifest.Section theSection = new Manifest.Section();
150                 theSection.setName( section.getName() );
151 
152                 if ( !section.isManifestEntriesEmpty() )
153                 {
154                     Map<String, String> sectionEntries = section.getManifestEntries();
155 
156                     for ( Map.Entry<String, String> entry : sectionEntries.entrySet() )
157                     {
158                         String key = entry.getKey();
159                         String value = entry.getValue();
160                         Manifest.Attribute attr = new Manifest.Attribute( key, value );
161                         theSection.addConfiguredAttribute( attr );
162                     }
163                 }
164 
165                 manifest.addConfiguredSection( theSection );
166             }
167         }
168 
169         return manifest;
170     }
171 
172     /**
173      * Return a pre-configured manifest
174      *
175      * @param project {@link MavenProject}
176      * @param config {@link ManifestConfiguration}
177      * @return {@link Manifest}
178      * @throws ManifestException Manifest exception.
179      * @throws DependencyResolutionRequiredException Dependency resolution exception.
180      */
181     // TODO Add user attributes list and user groups list
182     public Manifest getManifest( MavenProject project, ManifestConfiguration config )
183         throws ManifestException, DependencyResolutionRequiredException
184     {
185         return getManifest( null, project, config, Collections.<String, String>emptyMap() );
186     }
187 
188     /**
189      * @param mavenSession {@link MavenSession}
190      * @param project {@link MavenProject}
191      * @param config {@link ManifestConfiguration}
192      * @return {@link Manifest}
193      * @throws ManifestException The manifest exception.
194      * @throws DependencyResolutionRequiredException The dependency resolution required exception.
195      */
196     public Manifest getManifest( MavenSession mavenSession, MavenProject project, ManifestConfiguration config )
197         throws ManifestException, DependencyResolutionRequiredException
198     {
199         return getManifest( mavenSession, project, config, Collections.<String, String>emptyMap() );
200     }
201 
202     private void addManifestAttribute( Manifest manifest, Map<String, String> map, String key, String value )
203         throws ManifestException
204     {
205         if ( map.containsKey( key ) )
206         {
207             return; // The map value will be added later
208         }
209         addManifestAttribute( manifest, key, value );
210     }
211 
212     private void addManifestAttribute( Manifest manifest, String key, String value )
213         throws ManifestException
214     {
215         if ( !StringUtils.isEmpty( value ) )
216         {
217             Manifest.Attribute attr = new Manifest.Attribute( key, value );
218             manifest.addConfiguredAttribute( attr );
219         }
220         else
221         {
222             // if the value is empty we have create an entry with an empty string
223             // to prevent null print in the manifest file
224             Manifest.Attribute attr = new Manifest.Attribute( key, "" );
225             manifest.addConfiguredAttribute( attr );
226         }
227     }
228 
229     /**
230      * @param session {@link MavenSession}
231      * @param project {@link MavenProject}
232      * @param config {@link ManifestConfiguration}
233      * @param entries The entries.
234      * @return {@link Manifest}
235      * @throws ManifestException The manifest exception.
236      * @throws DependencyResolutionRequiredException The dependency resolution required exception.
237      */
238     protected Manifest getManifest( MavenSession session, MavenProject project, ManifestConfiguration config,
239                                     Map<String, String> entries )
240                                         throws ManifestException, DependencyResolutionRequiredException
241     {
242         // TODO: Should we replace "map" with a copy? Note, that we modify it!
243 
244         // Added basic entries
245         Manifest m = new Manifest();
246         addCreatedByEntry( session, m, entries );
247 
248         addCustomEntries( m, entries, config );
249 
250         if ( config.isAddClasspath() )
251         {
252             StringBuilder classpath = new StringBuilder();
253 
254             List<String> artifacts = project.getRuntimeClasspathElements();
255             String classpathPrefix = config.getClasspathPrefix();
256             String layoutType = config.getClasspathLayoutType();
257             String layout = config.getCustomClasspathLayout();
258 
259             Interpolator interpolator = new StringSearchInterpolator();
260 
261             for ( String artifactFile : artifacts )
262             {
263                 File f = new File( artifactFile );
264                 if ( f.getAbsoluteFile().isFile() )
265                 {
266                     Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
267 
268                     if ( classpath.length() > 0 )
269                     {
270                         classpath.append( " " );
271                     }
272                     classpath.append( classpathPrefix );
273 
274                     // NOTE: If the artifact or layout type (from config) is null, give up and use the file name by
275                     // itself.
276                     if ( artifact == null || layoutType == null )
277                     {
278                         classpath.append( f.getName() );
279                     }
280                     else
281                     {
282                         List<ValueSource> valueSources = new ArrayList<ValueSource>();
283 
284                         handleExtraExpression( artifact, valueSources );
285 
286                         for ( ValueSource vs : valueSources )
287                         {
288                             interpolator.addValueSource( vs );
289                         }
290 
291                         RecursionInterceptor recursionInterceptor =
292                             new PrefixAwareRecursionInterceptor( ARTIFACT_EXPRESSION_PREFIXES );
293 
294                         try
295                         {
296                             if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_SIMPLE.equals( layoutType ) )
297                             {
298                                 if ( config.isUseUniqueVersions() )
299                                 {
300                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT, recursionInterceptor ) );
301                                 }
302                                 else
303                                 {
304                                     classpath.append( interpolator.interpolate( SIMPLE_LAYOUT_NONUNIQUE,
305                                                                                 recursionInterceptor ) );
306                                 }
307                             }
308                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_REPOSITORY.equals( layoutType ) )
309                             {
310                                 // we use layout /$groupId[0]/../${groupId[n]/$artifactId/$version/{fileName}
311                                 // here we must find the Artifact in the project Artifacts to create the maven layout
312                                 if ( config.isUseUniqueVersions() )
313                                 {
314                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT,
315                                                                                 recursionInterceptor ) );
316                                 }
317                                 else
318                                 {
319                                     classpath.append( interpolator.interpolate( REPOSITORY_LAYOUT_NONUNIQUE,
320                                                                                 recursionInterceptor ) );
321                                 }
322                             }
323                             else if ( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM.equals( layoutType ) )
324                             {
325                                 if ( layout == null )
326                                 {
327                                     throw new ManifestException( ManifestConfiguration.CLASSPATH_LAYOUT_TYPE_CUSTOM
328                                         + " layout type was declared, but custom layout expression was not"
329                                         + " specified. Check your <archive><manifest><customLayout/> element." );
330                                 }
331 
332                                 classpath.append( interpolator.interpolate( layout, recursionInterceptor ) );
333                             }
334                             else
335                             {
336                                 throw new ManifestException( "Unknown classpath layout type: '" + layoutType
337                                     + "'. Check your <archive><manifest><layoutType/> element." );
338                             }
339                         }
340                         catch ( InterpolationException e )
341                         {
342                             ManifestException error =
343                                 new ManifestException( "Error interpolating artifact path for classpath entry: "
344                                     + e.getMessage() );
345 
346                             error.initCause( e );
347                             throw error;
348                         }
349                         finally
350                         {
351                             for ( ValueSource vs : valueSources )
352                             {
353                                 interpolator.removeValuesSource( vs );
354                             }
355                         }
356                     }
357                 }
358             }
359 
360             if ( classpath.length() > 0 )
361             {
362                 // Class-Path is special and should be added to manifest even if
363                 // it is specified in the manifestEntries section
364                 addManifestAttribute( m, "Class-Path", classpath.toString() );
365             }
366         }
367 
368         if ( config.isAddDefaultSpecificationEntries() )
369         {
370             handleSpecificationEntries( project, entries, m );
371         }
372 
373         if ( config.isAddDefaultImplementationEntries() )
374         {
375             handleImplementationEntries( project, entries, m );
376         }
377 
378         String mainClass = config.getMainClass();
379         if ( mainClass != null && !"".equals( mainClass ) )
380         {
381             addManifestAttribute( m, entries, "Main-Class", mainClass );
382         }
383 
384         if ( config.isAddExtensions() )
385         {
386             handleExtensions( project, entries, m );
387         }
388 
389         return m;
390     }
391 
392     private void handleExtraExpression( Artifact artifact, List<ValueSource> valueSources )
393     {
394         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES, artifact,
395                                                          true ) );
396         valueSources.add( new PrefixedObjectValueSource( ARTIFACT_EXPRESSION_PREFIXES,
397                                                          artifact.getArtifactHandler(), true ) );
398 
399         Properties extraExpressions = new Properties();
400         // FIXME: This query method SHOULD NOT affect the internal
401         // state of the artifact version, but it does.
402         if ( !artifact.isSnapshot() )
403         {
404             extraExpressions.setProperty( "baseVersion", artifact.getVersion() );
405         }
406 
407         extraExpressions.setProperty( "groupIdPath", artifact.getGroupId().replace( '.', '/' ) );
408         if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
409         {
410             extraExpressions.setProperty( "dashClassifier", "-" + artifact.getClassifier() );
411             extraExpressions.setProperty( "dashClassifier?", "-" + artifact.getClassifier() );
412         }
413         else
414         {
415             extraExpressions.setProperty( "dashClassifier", "" );
416             extraExpressions.setProperty( "dashClassifier?", "" );
417         }
418         valueSources.add( new PrefixedPropertiesValueSource( ARTIFACT_EXPRESSION_PREFIXES,
419                                                              extraExpressions, true ) );
420     }
421 
422     private void handleExtensions( MavenProject project, Map<String, String> entries, Manifest m )
423         throws ManifestException
424     {
425         // TODO: this is only for applets - should we distinguish them as a packaging?
426         StringBuilder extensionsList = new StringBuilder();
427         Set<Artifact> artifacts = (Set<Artifact>) project.getArtifacts();
428 
429         for ( Artifact artifact : artifacts )
430         {
431             if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
432             {
433                 if ( "jar".equals( artifact.getType() ) )
434                 {
435                     if ( extensionsList.length() > 0 )
436                     {
437                         extensionsList.append( " " );
438                     }
439                     extensionsList.append( artifact.getArtifactId() );
440                 }
441             }
442         }
443 
444         if ( extensionsList.length() > 0 )
445         {
446             addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
447         }
448 
449         for ( Object artifact1 : artifacts )
450         {
451             // TODO: the correct solution here would be to have an extension type, and to read
452             // the real extension values either from the artifact's manifest or some part of the POM
453             Artifact artifact = (Artifact) artifact1;
454             if ( "jar".equals( artifact.getType() ) )
455             {
456                 String artifactId = artifact.getArtifactId().replace( '.', '_' );
457                 String ename = artifactId + "-Extension-Name";
458                 addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
459                 String iname = artifactId + "-Implementation-Version";
460                 addManifestAttribute( m, entries, iname, artifact.getVersion() );
461 
462                 if ( artifact.getRepository() != null )
463                 {
464                     iname = artifactId + "-Implementation-URL";
465                     String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
466                     addManifestAttribute( m, entries, iname, url );
467                 }
468             }
469         }
470     }
471 
472     private void handleImplementationEntries( MavenProject project, Map<String, String> entries, Manifest m )
473         throws ManifestException
474     {
475         addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
476         addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
477         // MJAR-5
478         addManifestAttribute( m, entries, "Implementation-Vendor-Id", project.getGroupId() );
479 
480         if ( project.getOrganization() != null )
481         {
482             addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
483         }
484 
485         if ( project.getUrl() != null )
486         {
487             addManifestAttribute( m, entries, "Implementation-URL", project.getUrl() );
488         }
489     }
490 
491     private void handleSpecificationEntries( MavenProject project, Map<String, String> entries, Manifest m )
492         throws ManifestException
493     {
494         addManifestAttribute( m, entries, "Specification-Title", project.getName() );
495 
496         try
497         {
498             ArtifactVersion version = project.getArtifact().getSelectedVersion();
499             String specVersion = String.format( "%s.%s", version.getMajorVersion(), version.getMinorVersion() );
500             addManifestAttribute( m, entries, "Specification-Version", specVersion );
501         }
502         catch ( OverConstrainedVersionException e )
503         {
504             throw new ManifestException( "Failed to get selected artifact version to calculate"
505                 + " the specification version: " + e.getMessage() );
506         }
507 
508         if ( project.getOrganization() != null )
509         {
510             addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
511         }
512     }
513 
514     private void addCustomEntries( Manifest m, Map<String, String> entries, ManifestConfiguration config )
515         throws ManifestException
516     {
517         addManifestAttribute( m, entries, "Built-By", System.getProperty( "user.name" ) );
518         addManifestAttribute( m, entries, "Build-Jdk", System.getProperty( "java.version" ) );
519 
520         /*
521          * TODO: rethink this, it wasn't working Artifact projectArtifact = project.getArtifact(); if (
522          * projectArtifact.isSnapshot() ) { Manifest.Attribute buildNumberAttr = new Manifest.Attribute( "Build-Number",
523          * "" + project.getSnapshotDeploymentBuildNumber() ); m.addConfiguredAttribute( buildNumberAttr ); }
524          */
525         if ( config.getPackageName() != null )
526         {
527             addManifestAttribute( m, entries, "Package", config.getPackageName() );
528         }
529     }
530 
531     /**
532      * @return {@link JarArchiver}
533      */
534     public JarArchiver getArchiver()
535     {
536         return archiver;
537     }
538 
539     /**
540      * @param archiver {@link JarArchiver}
541      */
542     public void setArchiver( JarArchiver archiver )
543     {
544         this.archiver = archiver;
545     }
546 
547     /**
548      * @param outputFile Set output file.
549      */
550     public void setOutputFile( File outputFile )
551     {
552         archiveFile = outputFile;
553     }
554 
555     /**
556      * @param session {@link MavenSession}
557      * @param project {@link MavenProject}
558      * @param archiveConfiguration {@link MavenArchiveConfiguration}
559      * @throws org.codehaus.plexus.archiver.ArchiverException Archiver Exception.
560      * @throws ManifestException Manifest Exception.
561      * @throws IOException IO Exception.
562      * @throws DependencyResolutionRequiredException Dependency resolution exception.
563      */
564     public void createArchive( MavenSession session, MavenProject project,
565                                MavenArchiveConfiguration archiveConfiguration )
566                                    throws ManifestException, IOException,
567                                    DependencyResolutionRequiredException
568     {
569         // we have to clone the project instance so we can write out the pom with the deployment version,
570         // without impacting the main project instance...
571         MavenProject workingProject = null;
572         workingProject = (MavenProject) project.clone();
573 
574         boolean forced = archiveConfiguration.isForced();
575         if ( archiveConfiguration.isAddMavenDescriptor() )
576         {
577             // ----------------------------------------------------------------------
578             // We want to add the metadata for the project to the JAR in two forms:
579             //
580             // The first form is that of the POM itself. Applications that wish to
581             // access the POM for an artifact using maven tools they can.
582             //
583             // The second form is that of a properties file containing the basic
584             // top-level POM elements so that applications that wish to access
585             // POM information without the use of maven tools can do so.
586             // ----------------------------------------------------------------------
587 
588             if ( workingProject.getArtifact().isSnapshot() )
589             {
590                 workingProject.setVersion( workingProject.getArtifact().getVersion() );
591             }
592 
593             String groupId = workingProject.getGroupId();
594 
595             String artifactId = workingProject.getArtifactId();
596 
597             archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
598 
599             // ----------------------------------------------------------------------
600             // Create pom.properties file
601             // ----------------------------------------------------------------------
602 
603             File customPomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
604             File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
605             File pomPropertiesFile = new File( dir, "pom.properties" );
606 
607             new PomPropertiesUtil().createPomProperties( session, workingProject, archiver,
608                 customPomPropertiesFile, pomPropertiesFile, forced );
609         }
610 
611         // ----------------------------------------------------------------------
612         // Create the manifest
613         // ----------------------------------------------------------------------
614 
615         File manifestFile = archiveConfiguration.getManifestFile();
616 
617         if ( manifestFile != null )
618         {
619             archiver.setManifest( manifestFile );
620         }
621 
622         Manifest manifest = getManifest( session, workingProject, archiveConfiguration );
623 
624         // Configure the jar
625         archiver.addConfiguredManifest( manifest );
626 
627         archiver.setCompress( archiveConfiguration.isCompress() );
628 
629         archiver.setRecompressAddedZips( archiveConfiguration.isRecompressAddedZips() );
630 
631         archiver.setIndex( archiveConfiguration.isIndex() );
632 
633         archiver.setDestFile( archiveFile );
634 
635         // make the archiver index the jars on the classpath, if we are adding that to the manifest
636         if ( archiveConfiguration.getManifest().isAddClasspath() )
637         {
638             List<String> artifacts = project.getRuntimeClasspathElements();
639             for ( String artifact : artifacts )
640             {
641                 File f = new File( artifact );
642                 archiver.addConfiguredIndexJars( f );
643             }
644         }
645 
646         archiver.setForced( forced );
647         if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
648         {
649             // TODO Should issue a warning here, but how do we get a logger?
650             // TODO getLog().warn(
651             // "Forced build is disabled, but disabling the forced mode isn't supported by the archiver." );
652         }
653 
654         String automaticModuleName = manifest.getMainSection().getAttributeValue( "Automatic-Module-Name" );
655         if ( automaticModuleName != null )
656         {
657             if ( !isValidModuleName( automaticModuleName ) )
658             {
659                 throw new ManifestException( "Invalid automatic module name: '" + automaticModuleName + "'" );
660             }
661         }
662 
663         // create archive
664         archiver.createArchive();
665     }
666 
667     private void addCreatedByEntry( MavenSession session, Manifest m, Map<String, String> entries )
668         throws ManifestException
669     {
670         String createdBy = "Apache Maven";
671         if ( session != null ) // can be null due to API backwards compatibility
672         {
673             String mavenVersion = session.getSystemProperties().getProperty( "maven.version" );
674             if ( mavenVersion != null )
675             {
676                 createdBy += " " + mavenVersion;
677             }
678         }
679         addManifestAttribute( m, entries, "Created-By", createdBy );
680     }
681 
682     private Artifact findArtifactWithFile( Set<Artifact> artifacts, File file )
683     {
684         for ( Artifact artifact : artifacts )
685         {
686             // normally not null but we can check
687             if ( artifact.getFile() != null )
688             {
689                 if ( artifact.getFile().equals( file ) )
690                 {
691                     return artifact;
692                 }
693             }
694         }
695         return null;
696     }
697 }