package org.codehaus.mojo.shade.mojo;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.model.Model;
import org.apache.maven.model.Dependency;
import org.codehaus.mojo.shade.Shader;
import org.codehaus.mojo.shade.pom.PomWriter;
import org.codehaus.mojo.shade.relocation.SimpleRelocator;
import org.codehaus.mojo.shade.resource.ResourceTransformer;
/**
* @author Jason van Zyl
* @author Mauro Talevi
* @goal shade
* @phase package
* @requiresDependencyResolution runtime
*/
public class ShadeMojo
extends AbstractMojo
{
/**
* @parameter expression="${project}"
* @readonly
*/
private MavenProject project;
/** @component */
private MavenProjectHelper projectHelper;
/** @component */
private Shader shader;
/**
* Artifacts to include/exclude from the final artifact.
*
* @parameter
*/
private ArtifactSet artifactSet;
/**
* Packages to be relocated.
*
* @parameter
*/
private PackageRelocation[] relocations;
/**
* Resource transformers to be used.
*
* @parameter
*/
private ResourceTransformer[] transformers;
/** @parameter expression="${project.build.directory}" */
private File outputDirectory;
/**
* The name of the shaded artifactId
*
* @parameter expression="${finalName}"
*/
private String finalName;
/**
* The name of the shaded artifactId. So you may want to use a different artifactId and keep
* the standard version. If the original artifactId was "foo" then the final artifact would
* be something like foo-1.0.jar. So if you change the artifactId you might have something
* like foo-special-1.0.jar.
*
* @parameter expression="${shadedArtifactId}" default-value="${project.artifactId}"
*/
private String shadedArtifactId;
/**
* If specified, this will include only artifacts which have groupIds which
* start with this.
*
* @parameter expression="${shadedGroupFilter}"
*/
private String shadedGroupFilter;
/**
* Defines whether the shaded artifact should be attached as classifier to
* the original artifact. If false, the shaded jar will be the main artifact
* of the project
*
* @parameter expression="${shadedArtifactAttached}" default-value="false"
*/
private boolean shadedArtifactAttached;
/**
* @parameter expression="${createDependencyReducedPom}" default-value="true"
*/
private boolean createDependencyReducedPom;
/**
* When true, dependencies are kept in the pom but with scope 'provided'; when false,
* the dependency is removed.
*
* @parameter expression="${keepDependenciesWithProvidedScope}" default-value="false"
*/
private boolean keepDependenciesWithProvidedScope;
/**
* The name of the classifier used in case the shaded artifact is attached.
*
* @parameter expression="${shadedClassifierName}" default-value="shaded"
*/
private String shadedClassifierName;
/** @throws MojoExecutionException */
public void execute()
throws MojoExecutionException
{
Set artifacts = new HashSet();
Set artifactIds = new HashSet();
for ( Iterator it = project.getArtifacts().iterator(); it.hasNext(); )
{
Artifact artifact = (Artifact) it.next();
if ( excludeArtifact( artifact ) )
{
getLog().info( "Excluding " + artifact.getId() + " from the shaded jar." );
continue;
}
getLog().info( "Including " + artifact.getId() + " in the shaded jar." );
artifacts.add( artifact.getFile() );
artifactIds.add( getId( artifact ) );
}
artifacts.add( project.getArtifact().getFile() );
File outputJar = shadedArtifactFileWithClassifier();
// Now add our extra resources
try
{
List relocators = getRelocators();
List resourceTransformers = getResourceTrasformers();
shader.shade( artifacts, outputJar, relocators, resourceTransformers );
if ( shadedArtifactAttached )
{
getLog().info( "Attaching shaded artifact." );
projectHelper.attachArtifact( getProject(), "jar", shadedClassifierName, outputJar );
}
else
{
getLog().info( "Replacing original artifact with shaded artifact." );
File file = shadedArtifactFile();
file.renameTo( new File( outputDirectory, "original-" + file.getName() ) );
if ( !outputJar.renameTo( file ) )
{
getLog().warn( "Could not replace original artifact with shaded artifact!" );
}
if ( createDependencyReducedPom )
{
createDependencyReducedPom( artifactIds );
}
}
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error creating shaded jar.", e );
}
}
private boolean excludeArtifact( Artifact artifact )
{
String id = getId( artifact );
// This is the case where we have only stated artifacts to include and no exclusions
// have been listed. We just want what we have asked to include.
if ( artifactSet != null && ( artifactSet.getExcludes() == null && artifactSet.getIncludes() != null ) && !includedArtifacts().contains( id ) )
{
return true;
}
if ( excludedArtifacts().contains( id ) )
{
return true;
}
if ( shadedGroupFilter != null && !artifact.getGroupId().startsWith( shadedGroupFilter ) )
{
return true;
}
return false;
}
private Set excludedArtifacts()
{
if ( artifactSet != null && artifactSet.getExcludes() != null )
{
return artifactSet.getExcludes();
}
return Collections.EMPTY_SET;
}
private Set includedArtifacts()
{
if ( artifactSet != null && artifactSet.getIncludes() != null )
{
return artifactSet.getIncludes();
}
return Collections.EMPTY_SET;
}
private List getRelocators()
{
List relocators = new ArrayList();
if ( relocations == null )
{
return relocators;
}
for ( int i = 0; i < relocations.length; i++ )
{
PackageRelocation r = relocations[i];
if ( r.getExcludes() != null )
{
relocators.add( new SimpleRelocator( r.getPattern(), r.getExcludes() ) );
}
else
{
relocators.add( new SimpleRelocator( r.getPattern(), null ) );
}
}
return relocators;
}
private List getResourceTrasformers()
{
if ( transformers == null )
{
return Collections.EMPTY_LIST;
}
return Arrays.asList( transformers );
}
private File shadedArtifactFileWithClassifier()
{
Artifact artifact = project.getArtifact();
final String shadedName =
shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "." + artifact.getType();
return new File( outputDirectory, shadedName );
}
private File shadedArtifactFile()
{
Artifact artifact = project.getArtifact();
String shadedName;
if ( finalName != null )
{
shadedName = finalName + "." + artifact.getType();
}
else
{
shadedName = shadedArtifactId + "-" + artifact.getVersion() + "." + artifact.getType();
}
return new File( outputDirectory, shadedName );
}
protected MavenProject getProject()
{
if ( project.getExecutionProject() != null )
{
return project.getExecutionProject();
}
else
{
return project;
}
}
// We need to find the direct dependencies that have been included in the uber JAR so that we can modify the
// POM accordingly.
private void createDependencyReducedPom( Set artifactsToRemove )
throws IOException
{
Model model = getProject().getOriginalModel();
List dependencies = new ArrayList();
boolean modified = false;
for ( Iterator i = model.getDependencies().iterator(); i.hasNext(); )
{
Dependency d = (Dependency) i.next();
dependencies.add( d );
String id = d.getGroupId() + ":" + d.getArtifactId();
if ( artifactsToRemove.contains( id ) )
{
modified = true;
if ( keepDependenciesWithProvidedScope )
{
d.setScope( "provided" );
}
else
{
dependencies.remove( d );
}
}
}
// Check to see if we have a reduction and if so rewrite the POM.
if ( modified )
{
model.setDependencies( dependencies );
File f = new File( getProject().getFile().getParentFile(), "dependency-reduced-pom.xml" );
f.deleteOnExit();
Writer w = new FileWriter( f );
PomWriter.write( w, model, true );
w.close();
getProject().setFile( f );
}
}
private String getId( Artifact artifact )
{
return artifact.getGroupId() + ":" + artifact.getArtifactId();
}
}