blob: 72c475a5d8e2c2143818072041fda313668418f2 [file] [log] [blame]
package org.apache.maven.plugins.source;
/*
* 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 java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.archiver.Archiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.util.FileUtils;
/**
* Base class for bundling sources into a jar archive.
*
* @since 2.0.3
*/
public abstract class AbstractSourceJarMojo
extends AbstractMojo
{
private static final String[] DEFAULT_INCLUDES = new String[] { "**/**" };
private static final String[] DEFAULT_EXCLUDES = new String[] {};
/**
* List of files to include. Specified as fileset patterns which are relative to the input directory whose contents
* is being packaged into the JAR.
*
* @since 2.1
*/
@Parameter
private String[] includes;
/**
* List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents
* is being packaged into the JAR.
*
* @since 2.1
*/
@Parameter
private String[] excludes;
/**
* Exclude commonly excluded files such as SCM configuration. These are defined in the plexus
* FileUtils.getDefaultExcludes()
*
* @since 2.1
*/
@Parameter( property = "maven.source.useDefaultExcludes", defaultValue = "true" )
private boolean useDefaultExcludes;
/**
* The Maven Project Object
*/
@Parameter( defaultValue = "${project}", readonly = true, required = true )
private MavenProject project;
/**
* The Jar archiver.
*/
@Component( role = Archiver.class, hint = "jar" )
private JarArchiver jarArchiver;
/**
* The archive configuration to use. See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven
* Archiver Reference</a>. <br/>
* <b>Note: Since 3.0.0 the resulting archives contain a maven descriptor. If you need to suppress the generation of
* the maven descriptor you can simply achieve this by using the
* <a href="http://maven.apache.org/shared/maven-archiver/index.html#archive">archiver configuration</a>.</b>.
*
* @since 2.1
*/
@Parameter
private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
/**
* Path to the default MANIFEST file to use. It will be used if <code>useDefaultManifestFile</code> is set to
* <code>true</code>.
*
* @since 2.1
*/
// CHECKSTYLE_OFF: LineLength
@Parameter( defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF", readonly = false, required = true )
// CHECKSTYLE_ON: LineLength
private File defaultManifestFile;
/**
* Set this to <code>true</code> to enable the use of the <code>defaultManifestFile</code>. <br/>
*
* @since 2.1
*/
@Parameter( property = "maven.source.useDefaultManifestFile", defaultValue = "false" )
private boolean useDefaultManifestFile;
/**
* Specifies whether or not to attach the artifact to the project
*/
@Parameter( property = "maven.source.attach", defaultValue = "true" )
private boolean attach;
/**
* Specifies whether or not to exclude resources from the sources-jar. This can be convenient if your project
* includes large resources, such as images, and you don't want to include them in the sources-jar.
*
* @since 2.0.4
*/
@Parameter( property = "maven.source.excludeResources", defaultValue = "false" )
protected boolean excludeResources;
/**
* Specifies whether or not to include the POM file in the sources-jar.
*
* @since 2.1
*/
@Parameter( property = "maven.source.includePom", defaultValue = "false" )
protected boolean includePom;
/**
* Used for attaching the source jar to the project.
*/
@Component
private MavenProjectHelper projectHelper;
/**
* The directory where the generated archive file will be put.
*/
@Parameter( defaultValue = "${project.build.directory}" )
protected File outputDirectory;
/**
* The filename to be used for the generated archive file. For the source:jar goal, "-sources" is appended to this
* filename. For the source:test-jar goal, "-test-sources" is appended.
*/
@Parameter( defaultValue = "${project.build.finalName}" )
protected String finalName;
/**
* Contains the full list of projects in the reactor.
*/
@Parameter( defaultValue = "${reactorProjects}", readonly = true )
protected List<MavenProject> reactorProjects;
/**
* Whether creating the archive should be forced. If set to true, the jar will always be created. If set to false,
* the jar will only be created when the sources are newer than the jar.
*
* @since 2.1
*/
@Parameter( property = "maven.source.forceCreation", defaultValue = "false" )
private boolean forceCreation;
/**
* A flag used to disable the source procedure. This is primarily intended for usage from the command line to
* occasionally adjust the build.
*
* @since 2.2
*/
@Parameter( property = "maven.source.skip", defaultValue = "false" )
private boolean skipSource;
/**
* The Maven session.
*/
@Parameter( defaultValue = "${session}", readonly = true, required = true )
private MavenSession session;
/**
* Timestamp for reproducible output archive entries, either formatted as ISO 8601
* <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
* <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
*
* @since 3.2.0
*/
@Parameter( defaultValue = "${project.build.outputTimestamp}" )
private String outputTimestamp;
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public void execute()
throws MojoExecutionException
{
if ( skipSource )
{
getLog().info( "Skipping source per configuration." );
return;
}
packageSources( project );
}
// ----------------------------------------------------------------------
// Protected methods
// ----------------------------------------------------------------------
/**
* @return the wanted classifier, ie <code>sources</code> or <code>test-sources</code>
*/
protected abstract String getClassifier();
/**
* @param p {@link MavenProject} not null
* @return the compile or test sources
* @throws MojoExecutionException in case of an error.
*/
protected abstract List<String> getSources( MavenProject p )
throws MojoExecutionException;
/**
* @param p {@link MavenProject} not null
* @return the compile or test resources
* @throws MojoExecutionException in case of an error.
*/
protected abstract List<Resource> getResources( MavenProject p )
throws MojoExecutionException;
/**
* @param p {@link MavenProject}
* @throws MojoExecutionException in case of an error.
*/
protected void packageSources( MavenProject p )
throws MojoExecutionException
{
if ( !"pom".equals( p.getPackaging() ) )
{
packageSources( Collections.singletonList( p ) );
}
}
/**
* @param theProjects {@link MavenProject}
* @throws MojoExecutionException in case of an error.
*/
protected void packageSources( List<MavenProject> theProjects )
throws MojoExecutionException
{
if ( project.getArtifact().getClassifier() != null )
{
getLog().warn( "NOT adding sources to artifacts with classifier as Maven only supports one classifier "
+ "per artifact. Current artifact [" + project.getArtifact().getId() + "] has a ["
+ project.getArtifact().getClassifier() + "] classifier." );
return;
}
MavenArchiver archiver = createArchiver();
for ( MavenProject pItem : theProjects )
{
MavenProject subProject = getProject( pItem );
if ( "pom".equals( subProject.getPackaging() ) )
{
continue;
}
archiveProjectContent( subProject, archiver.getArchiver() );
}
if ( archiver.getArchiver().getResources().hasNext() || forceCreation )
{
if ( useDefaultManifestFile && defaultManifestFile.exists() && archive.getManifestFile() == null )
{
getLog().info( "Adding existing MANIFEST to archive. Found under: " + defaultManifestFile.getPath() );
archive.setManifestFile( defaultManifestFile );
}
File outputFile = new File( outputDirectory, finalName + "-" + getClassifier() + getExtension() );
try
{
archiver.setOutputFile( outputFile );
archive.setForced( forceCreation );
getLog().debug( "create archive " + outputFile );
archiver.createArchive( session, project, archive );
}
catch ( IOException | ArchiverException | DependencyResolutionRequiredException | ManifestException e )
{
throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
}
if ( attach )
{
for ( Artifact attachedArtifact : project.getAttachedArtifacts() )
{
if ( isAlreadyAttached( attachedArtifact, project, getClassifier() ) )
{
getLog().error( "We have duplicated artifacts attached." );
throw new MojoExecutionException( "Presumably you have configured maven-source-plugn "
+ "to execute twice times in your build. You have to configure a classifier "
+ "for at least on of them." );
}
}
projectHelper.attachArtifact( project, getType(), getClassifier(), outputFile );
}
else
{
getLog().info( "NOT adding java-sources to attached artifacts list." );
}
}
else
{
getLog().info( "No sources in project. Archive not created." );
}
}
private boolean isAlreadyAttached( Artifact artifact, MavenProject checkProject, String classifier )
{
return artifact.getType().equals( getType() )
&& artifact.getGroupId().equals( checkProject.getGroupId() )
&& artifact.getArtifactId().equals( checkProject.getArtifactId() )
&& artifact.getVersion().equals( checkProject.getVersion() )
&& ( artifact.getClassifier() != null
? artifact.getClassifier().equals( classifier ) : false );
}
/**
* @param p {@link MavenProject}
* @param archiver {@link Archiver}
* @throws MojoExecutionException in case of an error.
*/
protected void archiveProjectContent( MavenProject p, Archiver archiver )
throws MojoExecutionException
{
if ( includePom )
{
try
{
archiver.addFile( p.getFile(), p.getFile().getName() );
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "Error adding POM file to target jar file.", e );
}
}
for ( String s : getSources( p ) )
{
File sourceDirectory = new File( s );
if ( sourceDirectory.exists() )
{
addDirectory( archiver, sourceDirectory, getCombinedIncludes( null ), getCombinedExcludes( null ) );
}
}
// MAPI: this should be taken from the resources plugin
for ( Resource resource : getResources( p ) )
{
File sourceDirectory = new File( resource.getDirectory() );
if ( !sourceDirectory.exists() )
{
continue;
}
List<String> resourceIncludes = resource.getIncludes();
String[] combinedIncludes = getCombinedIncludes( resourceIncludes );
List<String> resourceExcludes = resource.getExcludes();
String[] combinedExcludes = getCombinedExcludes( resourceExcludes );
String targetPath = resource.getTargetPath();
if ( targetPath != null )
{
if ( !targetPath.trim().endsWith( "/" ) )
{
targetPath += "/";
}
addDirectory( archiver, sourceDirectory, targetPath, combinedIncludes, combinedExcludes );
}
else
{
addDirectory( archiver, sourceDirectory, combinedIncludes, combinedExcludes );
}
}
}
/**
* @return {@link MavenArchiver}
* @throws MojoExecutionException in case of an error.
*/
protected MavenArchiver createArchiver()
throws MojoExecutionException
{
MavenArchiver archiver = new MavenArchiver();
archiver.setArchiver( jarArchiver );
archiver.setCreatedBy( "Maven Source Plugin", "org.apache.maven.plugins", "maven-source-plugin" );
archiver.setBuildJdkSpecDefaultEntry( false );
// configure for Reproducible Builds based on outputTimestamp value
archiver.configureReproducible( outputTimestamp );
if ( project.getBuild() != null )
{
List<Resource> resources = project.getBuild().getResources();
for ( Resource r : resources )
{
if ( r.getDirectory().endsWith( "maven-shared-archive-resources" ) )
{
addDirectory( archiver.getArchiver(), new File( r.getDirectory() ), getCombinedIncludes( null ),
getCombinedExcludes( null ) );
}
}
}
return archiver;
}
/**
* @param archiver {@link Archiver}
* @param sourceDirectory {@link File}
* @param pIncludes The list of includes.
* @param pExcludes The list of excludes.
* @throws MojoExecutionException in case of an error.
*/
protected void addDirectory( Archiver archiver, File sourceDirectory, String[] pIncludes, String[] pExcludes )
throws MojoExecutionException
{
try
{
getLog().debug( "add directory " + sourceDirectory + " to archiver" );
// archiver.addFileSet( fileSet );
archiver.addDirectory( sourceDirectory, pIncludes, pExcludes );
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "Error adding directory to source archive.", e );
}
}
/**
* @param archiver {@link Archiver}
* @param sourceDirectory {@link File}
* @param prefix The prefix.
* @param pIncludes the includes.
* @param pExcludes the excludes.
* @throws MojoExecutionException in case of an error.
*/
protected void addDirectory( Archiver archiver, File sourceDirectory, String prefix, String[] pIncludes,
String[] pExcludes )
throws MojoExecutionException
{
try
{
getLog().debug( "add directory " + sourceDirectory + " to archiver with prefix " + prefix );
archiver.addDirectory( sourceDirectory, prefix, pIncludes, pExcludes );
}
catch ( ArchiverException e )
{
throw new MojoExecutionException( "Error adding directory to source archive.", e );
}
}
/**
* @return The extension {@code .jar}
*/
protected String getExtension()
{
return ".jar";
}
/**
* @param p {@link MavenProject}
* @return The execution projet.
*/
protected MavenProject getProject( MavenProject p )
{
if ( p.getExecutionProject() != null )
{
return p.getExecutionProject();
}
return p;
}
/**
* @return The type {@code java-source}
*/
protected String getType()
{
return "java-source";
}
/**
* Combines the includes parameter and additional includes. Defaults to {@link #DEFAULT_INCLUDES} If the
* additionalIncludes parameter is null, it is not added to the combined includes.
*
* @param additionalIncludes The includes specified in the pom resources section
* @return The combined array of includes.
*/
private String[] getCombinedIncludes( List<String> additionalIncludes )
{
List<String> combinedIncludes = new ArrayList<>();
if ( includes != null && includes.length > 0 )
{
combinedIncludes.addAll( Arrays.asList( includes ) );
}
if ( additionalIncludes != null && additionalIncludes.size() > 0 )
{
combinedIncludes.addAll( additionalIncludes );
}
// If there are no other includes, use the default.
if ( combinedIncludes.size() == 0 )
{
combinedIncludes.addAll( Arrays.asList( DEFAULT_INCLUDES ) );
}
return combinedIncludes.toArray( new String[0] );
}
/**
* Combines the user parameter {@link #excludes}, the default excludes from plexus FileUtils, and the contents of
* the parameter addionalExcludes.
*
* @param additionalExcludes Additional excludes to add to the array
* @return The combined list of excludes.
*/
private String[] getCombinedExcludes( List<String> additionalExcludes )
{
List<String> combinedExcludes = new ArrayList<>();
if ( useDefaultExcludes )
{
combinedExcludes.addAll( FileUtils.getDefaultExcludesAsList() );
}
if ( excludes != null && excludes.length > 0 )
{
combinedExcludes.addAll( Arrays.asList( excludes ) );
}
if ( additionalExcludes != null && additionalExcludes.size() > 0 )
{
combinedExcludes.addAll( additionalExcludes );
}
if ( combinedExcludes.size() == 0 )
{
combinedExcludes.addAll( Arrays.asList( DEFAULT_EXCLUDES ) );
}
return combinedExcludes.toArray( new String[0] );
}
/**
* @return The current project.
*/
protected MavenProject getProject()
{
return project;
}
/**
* @param project {@link MavenProject}
*/
protected void setProject( MavenProject project )
{
this.project = project;
}
}