blob: befd8bfa761f2c9feb463be38b2100539a435d19 [file] [log] [blame]
package org.apache.maven.archiver;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.jar.Manifest;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
* @version $Revision$ $Date$
*/
public class MavenArchiver
{
private JarArchiver archiver;
private File archiveFile;
/**
* Return a pre-configured manifest
*
* @todo Add user attributes list and user groups list
*/
public Manifest getManifest( MavenProject project, MavenArchiveConfiguration config )
throws ManifestException, DependencyResolutionRequiredException
{
boolean hasManifestEntries = !config.isManifestEntriesEmpty();
Map entries = hasManifestEntries ? config.getManifestEntries() : Collections.EMPTY_MAP;
Manifest manifest = getManifest( project, config.getManifest(), entries );
// any custom manifest entries in the archive configuration manifest?
if ( hasManifestEntries )
{
Set keys = entries.keySet();
for ( Iterator iter = keys.iterator(); iter.hasNext(); )
{
String key = (String) iter.next();
String value = (String) entries.get( key );
Manifest.Attribute attr = manifest.getMainSection().getAttribute( key );
if ( key.equals( "Class-Path" ) && attr != null )
{
// Merge the user-supplied Class-Path value with the programmatically
// generated Class-Path. Note that the user-supplied value goes first
// so that resources there will override any in the standard Class-Path.
attr.setValue( value + " " + attr.getValue() );
}
else
{
addManifestAttribute( manifest, key, value );
}
}
}
return manifest;
}
/**
* Return a pre-configured manifest
*
* @todo Add user attributes list and user groups list
*/
public Manifest getManifest( MavenProject project, ManifestConfiguration config )
throws ManifestException, DependencyResolutionRequiredException
{
return getManifest( project, config, Collections.EMPTY_MAP );
}
private void addManifestAttribute( Manifest manifest, Map map, String key, String value )
throws ManifestException
{
if ( map.containsKey( key ) )
{
return; // The map value will be added later
}
addManifestAttribute( manifest, key, value );
}
private void addManifestAttribute( Manifest manifest, String key, String value )
throws ManifestException
{
if ( !StringUtils.isEmpty( value ) )
{
Manifest.Attribute attr = new Manifest.Attribute( key, value );
manifest.addConfiguredAttribute( attr );
}
else
{
// if the value is empty we have create an entry with an empty string
// to prevent null print in the manifest file
Manifest.Attribute attr = new Manifest.Attribute( key, "" );
manifest.addConfiguredAttribute( attr );
}
}
protected Manifest getManifest( MavenProject project, ManifestConfiguration config, Map entries )
throws ManifestException, DependencyResolutionRequiredException
{
// TODO: Should we replace "map" with a copy? Note, that we modify it!
// Added basic entries
Manifest m = new Manifest();
addManifestAttribute( m, entries, "Created-By", "Apache Maven" );
addCustomEntries( m, entries, config );
if ( config.isAddClasspath() )
{
StringBuffer classpath = new StringBuffer();
List artifacts = project.getRuntimeClasspathElements();
String classpathPrefix = config.getClasspathPrefix();
for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
File f = new File( (String) iter.next() );
if ( f.isFile() )
{
if ( classpath.length() > 0 )
{
classpath.append( " " );
}
classpath.append( classpathPrefix );
if ( !config.isClasspathMavenRepositoryLayout() )
{
classpath.append( f.getName() );
}
else
{
// we use layout /$groupId[0]/../${groupId[n]/$artifactId/$version/{fileName}
// here we must find the Artifact in the project Artifacts to generate the maven layout
Artifact artifact = findArtifactWithFile( project.getArtifacts(), f );
StringBuffer classpathElement = new StringBuffer();
if ( !StringUtils.isEmpty( artifact.getGroupId() ) )
{
classpathElement.append( artifact.getGroupId().replace( '.', '/' ) ).append( '/' );
}
classpathElement.append( artifact.getArtifactId() ).append( '/' );
classpathElement.append( artifact.getVersion() ).append( '/' );
classpathElement.append( f.getName() );
classpath.append( classpathElement );
}
}
}
if ( classpath.length() > 0 )
{
// Class-Path is special and should be added to manifest even if
// it is specified in the manifestEntries section
addManifestAttribute( m, "Class-Path", classpath.toString() );
}
}
if ( config.isAddDefaultSpecificationEntries() )
{
addManifestAttribute( m, entries, "Specification-Title", project.getName() );
addManifestAttribute( m, entries, "Specification-Version", project.getVersion() );
if ( project.getOrganization() != null )
{
addManifestAttribute( m, entries, "Specification-Vendor", project.getOrganization().getName() );
}
}
if ( config.isAddDefaultImplementationEntries() )
{
addManifestAttribute( m, entries, "Implementation-Title", project.getName() );
addManifestAttribute( m, entries, "Implementation-Version", project.getVersion() );
// MJAR-5
addManifestAttribute( m, entries, "Implementation-Vendor-Id", project.getGroupId() );
if ( project.getOrganization() != null )
{
addManifestAttribute( m, entries, "Implementation-Vendor", project.getOrganization().getName() );
}
}
String mainClass = config.getMainClass();
if ( mainClass != null && !"".equals( mainClass ) )
{
addManifestAttribute( m, entries, "Main-Class", mainClass );
}
// Added extensions
if ( config.isAddExtensions() )
{
// TODO: this is only for applets - should we distinguish them as a packaging?
StringBuffer extensionsList = new StringBuffer();
Set artifacts = project.getArtifacts();
for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
Artifact artifact = (Artifact) iter.next();
if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
{
if ( "jar".equals( artifact.getType() ) )
{
if ( extensionsList.length() > 0 )
{
extensionsList.append( " " );
}
extensionsList.append( artifact.getArtifactId() );
}
}
}
if ( extensionsList.length() > 0 )
{
addManifestAttribute( m, entries, "Extension-List", extensionsList.toString() );
}
for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
// TODO: the correct solution here would be to have an extension type, and to read
// the real extension values either from the artifact's manifest or some part of the POM
Artifact artifact = (Artifact) iter.next();
if ( "jar".equals( artifact.getType() ) )
{
String ename = artifact.getArtifactId() + "-Extension-Name";
addManifestAttribute( m, entries, ename, artifact.getArtifactId() );
String iname = artifact.getArtifactId() + "-Implementation-Version";
addManifestAttribute( m, entries, iname, artifact.getVersion() );
if ( artifact.getRepository() != null )
{
iname = artifact.getArtifactId() + "-Implementation-URL";
String url = artifact.getRepository().getUrl() + "/" + artifact.toString();
addManifestAttribute( m, entries, iname, url );
}
}
}
}
return m;
}
private void addCustomEntries( Manifest m, Map entries, ManifestConfiguration config )
throws ManifestException
{
addManifestAttribute( m, entries, "Built-By", System.getProperty( "user.name" ) );
addManifestAttribute( m, entries, "Build-Jdk", System.getProperty( "java.version" ) );
/* TODO: rethink this, it wasn't working
Artifact projectArtifact = project.getArtifact();
if ( projectArtifact.isSnapshot() )
{
Manifest.Attribute buildNumberAttr = new Manifest.Attribute( "Build-Number", "" +
project.getSnapshotDeploymentBuildNumber() );
m.addConfiguredAttribute( buildNumberAttr );
}
*/
if ( config.getPackageName() != null )
{
addManifestAttribute( m, entries, "Package", config.getPackageName() );
}
}
public JarArchiver getArchiver()
{
return archiver;
}
public void setArchiver( JarArchiver archiver )
{
this.archiver = archiver;
}
public void setOutputFile( File outputFile )
{
archiveFile = outputFile;
}
public void createArchive( MavenProject project, MavenArchiveConfiguration archiveConfiguration )
throws ArchiverException, ManifestException, IOException, DependencyResolutionRequiredException
{
// we have to clone the project instance so we can write out the pom with the deployment version,
// without impacting the main project instance...
MavenProject workingProject = new MavenProject( project );
boolean forced = archiveConfiguration.isForced();
if ( archiveConfiguration.isAddMavenDescriptor() )
{
// ----------------------------------------------------------------------
// We want to add the metadata for the project to the JAR in two forms:
//
// The first form is that of the POM itself. Applications that wish to
// access the POM for an artifact using maven tools they can.
//
// The second form is that of a properties file containing the basic
// top-level POM elements so that applications that wish to access
// POM information without the use of maven tools can do so.
// ----------------------------------------------------------------------
if ( workingProject.getArtifact().isSnapshot() )
{
workingProject.setVersion( workingProject.getArtifact().getVersion() );
}
String groupId = workingProject.getGroupId();
String artifactId = workingProject.getArtifactId();
archiver.addFile( project.getFile(), "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml" );
// ----------------------------------------------------------------------
// Create pom.properties file
// ----------------------------------------------------------------------
File pomPropertiesFile = archiveConfiguration.getPomPropertiesFile();
if ( pomPropertiesFile == null )
{
File dir = new File( workingProject.getBuild().getDirectory(), "maven-archiver" );
pomPropertiesFile = new File( dir, "pom.properties" );
}
new PomPropertiesUtil().createPomProperties( workingProject, archiver, pomPropertiesFile, forced );
}
// ----------------------------------------------------------------------
// Create the manifest
// ----------------------------------------------------------------------
File manifestFile = archiveConfiguration.getManifestFile();
if ( manifestFile != null )
{
archiver.setManifest( manifestFile );
}
Manifest manifest = getManifest( workingProject, archiveConfiguration );
// any custom manifest sections in the archive configuration manifest?
if ( !archiveConfiguration.isManifestSectionsEmpty() )
{
List sections = archiveConfiguration.getManifestSections();
for ( Iterator iter = sections.iterator(); iter.hasNext(); )
{
ManifestSection section = (ManifestSection) iter.next();
Manifest.Section theSection = new Manifest.Section();
theSection.setName( section.getName() );
if ( !section.isManifestEntriesEmpty() )
{
Map entries = section.getManifestEntries();
Set keys = entries.keySet();
for ( Iterator it = keys.iterator(); it.hasNext(); )
{
String key = (String) it.next();
String value = (String) entries.get( key );
Manifest.Attribute attr = new Manifest.Attribute( key, value );
theSection.addConfiguredAttribute( attr );
}
}
manifest.addConfiguredSection( theSection );
}
}
// Configure the jar
archiver.addConfiguredManifest( manifest );
archiver.setCompress( archiveConfiguration.isCompress() );
archiver.setIndex( archiveConfiguration.isIndex() );
archiver.setDestFile( archiveFile );
// make the archiver index the jars on the classpath, if we are adding that to the manifest
if ( archiveConfiguration.getManifest().isAddClasspath() )
{
List artifacts = project.getRuntimeClasspathElements();
for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
{
File f = new File( (String) iter.next() );
archiver.addConfiguredIndexJars( f );
}
}
archiver.setForced( forced );
if ( !archiveConfiguration.isForced() && archiver.isSupportingForced() )
{
// TODO Should issue a warning here, but how do we get a logger?
// TODO getLog().warn( "Forced build is disabled, but disabling the forced mode isn't supported by the archiver." );
}
// create archive
archiver.createArchive();
}
private Artifact findArtifactWithFile( Set artifacts, File file )
{
for ( Iterator iterator = artifacts.iterator(); iterator.hasNext(); )
{
Artifact artifact = (Artifact) iterator.next();
// normally not null but we can check
if ( artifact.getFile() != null )
{
if ( artifact.getFile().equals( file ) )
{
return artifact;
}
}
}
return null;
}
}