blob: ea14b731ce771a3bf6f1b4a78ae300f6c570e8b5 [file] [log] [blame]
package org.apache.maven.plugins.dependency.fromDependencies;
/*
* 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.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.dependency.utils.DependencyUtil;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
import org.apache.maven.shared.transfer.repository.RepositoryManager;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
/**
* This goal will output a classpath string of dependencies from the local repository to a file or log.
*
* @author ankostis
* @since 2.0-alpha-2
*/
// CHECKSTYLE_OFF: LineLength
@Mojo( name = "build-classpath", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true )
// CHECKSTYLE_ON: LineLength
public class BuildClasspathMojo
extends AbstractDependencyFilterMojo
implements Comparator<Artifact>
{
/**
* Strip artifact version during copy (only works if prefix is set)
*/
@Parameter( property = "mdep.stripVersion", defaultValue = "false" )
private boolean stripVersion = false;
/**
* Strip artifact classifier during copy (only works if prefix is set)
*/
@Parameter( property = "mdep.stripClassifier", defaultValue = "false" )
private boolean stripClassifier = false;
/**
* The prefix to prepend on each dependent artifact. If undefined, the paths refer to the actual files store in the
* local repository (the stripVersion parameter does nothing then).
*/
@Parameter( property = "mdep.prefix" )
private String prefix;
/**
* A property to set to the content of the classpath string.
*/
@Parameter( property = "mdep.outputProperty" )
private String outputProperty;
/**
* The file to write the classpath string. If undefined, it just prints the classpath as [INFO].
*/
@Parameter( property = "mdep.outputFile" )
private File outputFile;
/**
* If 'true', it skips the up-to-date-check, and always regenerates the classpath file.
*/
@Parameter( property = "mdep.regenerateFile", defaultValue = "false" )
private boolean regenerateFile;
/**
* Override the char used between the paths. This field is initialized to contain the first character of the value
* of the system property file.separator. On UNIX systems the value of this field is '/'; on Microsoft Windows
* systems it is '\'. The default is File.separator
*
* @since 2.0
*/
@Parameter( property = "mdep.fileSeparator", defaultValue = "" )
private String fileSeparator;
/**
* Override the char used between path folders. The system-dependent path-separator character. This field is
* initialized to contain the first character of the value of the system property path.separator. This character is
* used to separate filenames in a sequence of files given as a path list. On UNIX systems, this character is ':';
* on Microsoft Windows systems it is ';'.
*
* @since 2.0
*/
@Parameter( property = "mdep.pathSeparator", defaultValue = "" )
private String pathSeparator;
/**
* Replace the absolute path to the local repo with this property. This field is ignored it prefix is declared. The
* value will be forced to "${M2_REPO}" if no value is provided AND the attach flag is true.
*
* @since 2.0
*/
@Parameter( property = "mdep.localRepoProperty", defaultValue = "" )
private String localRepoProperty;
/**
* Attach the classpath file to the main artifact so it can be installed and deployed.
*
* @since 2.0
*/
@Parameter( defaultValue = "false" )
private boolean attach;
/**
* Write out the classpath in a format compatible with filtering (classpath=xxxxx)
*
* @since 2.0
*/
@Parameter( property = "mdep.outputFilterFile", defaultValue = "false" )
private boolean outputFilterFile;
/**
* Either append the artifact's baseVersion or uniqueVersion to the filename. Will only be used if
* {@link #isStripVersion()} is {@code false}.
*
* @since 2.6
*/
@Parameter( property = "mdep.useBaseVersion", defaultValue = "true" )
private boolean useBaseVersion = true;
/**
* Maven ProjectHelper
*/
@Component
private MavenProjectHelper projectHelper;
@Component
private RepositoryManager repositoryManager;
/**
* Main entry into mojo. Gets the list of dependencies and iterates to create a classpath.
*
* @throws MojoExecutionException with a message if an error occurs.
* @see #getResolvedDependencies(boolean)
*/
@Override
protected void doExecute()
throws MojoExecutionException
{
// initialize the separators.
boolean isFileSepSet = StringUtils.isNotEmpty( fileSeparator );
boolean isPathSepSet = StringUtils.isNotEmpty( pathSeparator );
// don't allow them to have absolute paths when they attach.
if ( attach && StringUtils.isEmpty( localRepoProperty ) )
{
localRepoProperty = "${M2_REPO}";
}
Set<Artifact> artifacts = getResolvedDependencies( true );
if ( artifacts == null || artifacts.isEmpty() )
{
getLog().info( "No dependencies found." );
}
List<Artifact> artList = new ArrayList<>( artifacts );
StringBuilder sb = new StringBuilder();
Iterator<Artifact> i = artList.iterator();
if ( i.hasNext() )
{
appendArtifactPath( i.next(), sb );
while ( i.hasNext() )
{
sb.append( isPathSepSet ? this.pathSeparator : File.pathSeparator );
appendArtifactPath( i.next(), sb );
}
}
String cpString = sb.toString();
// if file separator is set, I need to replace the default one from all
// the file paths that were pulled from the artifacts
if ( isFileSepSet )
{
// Escape file separators to be used as literal strings
final String pattern = Pattern.quote( File.separator );
final String replacement = Matcher.quoteReplacement( fileSeparator );
cpString = cpString.replaceAll( pattern, replacement );
}
// make the string valid for filtering
if ( outputFilterFile )
{
cpString = "classpath=" + cpString;
}
if ( outputProperty != null )
{
getProject().getProperties().setProperty( outputProperty, cpString );
if ( getLog().isDebugEnabled() )
{
getLog().debug( outputProperty + " = " + cpString );
}
}
if ( outputFile == null )
{
getLog().info( "Dependencies classpath:" + System.lineSeparator() + cpString );
}
else
{
if ( regenerateFile || !isUpdToDate( cpString ) )
{
storeClasspathFile( cpString, outputFile );
}
else
{
this.getLog().info( "Skipped writing classpath file '" + outputFile + "'. No changes found." );
}
}
if ( attach )
{
attachFile( cpString );
}
}
/**
* @param cpString The classpath.
* @throws MojoExecutionException in case of an error.
*/
protected void attachFile( String cpString )
throws MojoExecutionException
{
File attachedFile = new File( getProject().getBuild().getDirectory(), "classpath" );
storeClasspathFile( cpString, attachedFile );
projectHelper.attachArtifact( getProject(), attachedFile, "classpath" );
}
/**
* Appends the artifact path into the specified StringBuilder.
*
* @param art {@link Artifact}
* @param sb {@link StringBuilder}
*/
protected void appendArtifactPath( Artifact art, StringBuilder sb )
{
if ( prefix == null )
{
String file = art.getFile().getPath();
// substitute the property for the local repo path to make the classpath file portable.
if ( StringUtils.isNotEmpty( localRepoProperty ) )
{
File localBasedir = repositoryManager.getLocalRepositoryBasedir( session.getProjectBuildingRequest() );
file = StringUtils.replace( file, localBasedir.getAbsolutePath(), localRepoProperty );
}
sb.append( file );
}
else
{
// TODO: add param for prepending groupId and version.
sb.append( prefix );
sb.append( File.separator );
sb.append( DependencyUtil.getFormattedFileName( art, this.stripVersion, this.prependGroupId,
this.useBaseVersion, this.stripClassifier ) );
}
}
/**
* Checks that new classpath differs from that found inside the old classpathFile.
*
* @param cpString
* @return true if the specified classpath equals to that found inside the file, false otherwise (including when
* file does not exists but new classpath does).
*/
private boolean isUpdToDate( String cpString )
{
try
{
String oldCp = readClasspathFile();
return ( cpString == null ? oldCp == null : cpString.equals( oldCp ) );
}
catch ( Exception ex )
{
this.getLog().warn( "Error while reading old classpath file '" + outputFile + "' for up-to-date check: "
+ ex );
return false;
}
}
/**
* It stores the specified string into that file.
*
* @param cpString the string to be written into the file.
* @throws MojoExecutionException
*/
private void storeClasspathFile( String cpString, File out )
throws MojoExecutionException
{
// make sure the parent path exists.
out.getParentFile().mkdirs();
Writer w = null;
try
{
w = new BufferedWriter( new FileWriter( out ) );
w.write( cpString );
w.close();
w = null;
getLog().info( "Wrote classpath file '" + out + "'." );
}
catch ( IOException ex )
{
throw new MojoExecutionException( "Error while writing to classpath file '" + out + "': " + ex.toString(),
ex );
}
finally
{
IOUtil.close( w );
}
}
/**
* Reads into a string the file specified by the mojo param 'outputFile'. Assumes, the instance variable
* 'outputFile' is not null.
*
* @return the string contained in the classpathFile, if exists, or null otherwise.
* @throws IOException in case of an error.
*/
protected String readClasspathFile()
throws IOException
{
if ( outputFile == null )
{
throw new IllegalArgumentException( "The outputFile parameter "
+ "cannot be null if the file is intended to be read." );
}
if ( !outputFile.isFile() )
{
return null;
}
StringBuilder sb = new StringBuilder();
BufferedReader r = null;
try
{
r = new BufferedReader( new FileReader( outputFile ) );
for ( String line = r.readLine(); line != null; line = r.readLine() )
{
sb.append( line );
}
r.close();
r = null;
return sb.toString();
}
finally
{
IOUtil.close( r );
}
}
/**
* Compares artifacts lexicographically, using pattern [group_id][artifact_id][version].
*
* @param art1 first object
* @param art2 second object
* @return the value <code>0</code> if the argument string is equal to this string; a value less than <code>0</code>
* if this string is lexicographically less than the string argument; and a value greater than
* <code>0</code> if this string is lexicographically greater than the string argument.
*/
@Override
public int compare( Artifact art1, Artifact art2 )
{
if ( art1 == art2 )
{
return 0;
}
else if ( art1 == null )
{
return -1;
}
else if ( art2 == null )
{
return +1;
}
String s1 = art1.getGroupId() + art1.getArtifactId() + art1.getVersion();
String s2 = art2.getGroupId() + art2.getArtifactId() + art2.getVersion();
return s1.compareTo( s2 );
}
@Override
protected ArtifactsFilter getMarkedArtifactFilter()
{
return null;
}
/**
* @param outputFile the outputFile to set
*/
public void setOutputFile( File outputFile )
{
this.outputFile = outputFile;
}
/**
* @param theOutputProperty the outputProperty to set
*/
public void setOutputProperty( String theOutputProperty )
{
this.outputProperty = theOutputProperty;
}
/**
* @param theFileSeparator the fileSeparator to set
*/
public void setFileSeparator( String theFileSeparator )
{
this.fileSeparator = theFileSeparator;
}
/**
* @param thePathSeparator the pathSeparator to set
*/
public void setPathSeparator( String thePathSeparator )
{
this.pathSeparator = thePathSeparator;
}
/**
* @param thePrefix the prefix to set
*/
public void setPrefix( String thePrefix )
{
this.prefix = thePrefix;
}
/**
* @param theRegenerateFile the regenerateFile to set
*/
public void setRegenerateFile( boolean theRegenerateFile )
{
this.regenerateFile = theRegenerateFile;
}
/**
* @return the stripVersion
*/
public boolean isStripVersion()
{
return this.stripVersion;
}
/**
* @param theStripVersion the stripVersion to set
*/
public void setStripVersion( boolean theStripVersion )
{
this.stripVersion = theStripVersion;
}
/**
* @param localRepoProperty {@link #localRepoProperty}
*/
public void setLocalRepoProperty( String localRepoProperty )
{
this.localRepoProperty = localRepoProperty;
}
}