package org.codehaus.mojo.rpm;
/*
* 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.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
/**
* Utility to interact with files (source, dependencies, artifacts, etc.).
*
* @author Brett Okken
* @version $Id$
* @since 2.0
*/
final class FileHelper
{
/**
* Message for exception indicating that a {@link Source} has a {@link Source#getDestination() destination}, but
* refers to a {@link File#isDirectory() directory}.
*/
private static final String DESTINATION_DIRECTORY_ERROR_MSG =
"Source has a destination [{0}], but the location [{1}] does not refer to a file.";
/**
* {@code Pattern} to identify macros.
* @since 2.1-alpha-1
*/
private static final Pattern MACRO_PATTERN = Pattern.compile( "%\\{([^}]*)\\}" );
/**
* A Plexus component to copy files and directories. Using our own custom version of the DirectoryArchiver to allow
* filtering of files.
*/
private final FilteringDirectoryArchiver copier;
private final AbstractRPMMojo mojo;
/**
* @param mojo
* @param copier
*/
public FileHelper( AbstractRPMMojo mojo, FilteringDirectoryArchiver copier )
{
super();
this.mojo = mojo;
this.copier = copier;
}
/**
* Copy the files from the various mapping sources into the build root.
*
* @throws MojoExecutionException if a problem occurs
* @throws MojoFailureException
*/
public void installFiles()
throws MojoExecutionException, MojoFailureException
{
final File workarea = mojo.getWorkarea();
final File buildroot = mojo.getBuildroot();
final File icon = mojo.getIcon();
// Copy icon, if specified
if ( icon != null )
{
File icondest = new File( workarea, "SOURCES" );
copySource( icon, null, icondest, null, null, false );
}
final Log log = mojo.getLog();
// Process each mapping
for ( Iterator it = mojo.getMappings().iterator(); it.hasNext(); )
{
Mapping map = (Mapping) it.next();
final String destinationString = map.getDestination();
final String macroEvaluatedDestination = evaluateMacros( destinationString );
File dest = new File( buildroot, macroEvaluatedDestination );
map.setAbsoluteDestination( dest );
if ( map.isDirOnly() )
{
// Build the output directory if it doesn't exist
if ( !dest.exists() )
{
log.info( "Creating empty directory " + dest.getAbsolutePath() );
if ( !dest.mkdirs() )
{
throw new MojoExecutionException( "Unable to create " + dest.getAbsolutePath() );
}
}
}
else
{
processSources( map, dest );
ArtifactMap art = map.getArtifact();
if ( art != null )
{
List artlist = selectArtifacts( art );
for ( Iterator ait = artlist.iterator(); ait.hasNext(); )
{
Artifact artifactInstance = (Artifact) ait.next();
copyArtifact( artifactInstance, dest, false );
map.addCopiedFileNameRelativeToDestination( artifactInstance.getFile().getName() );
}
}
Dependency dep = map.getDependency();
if ( dep != null )
{
List deplist = selectDependencies( dep );
for ( Iterator dit = deplist.iterator(); dit.hasNext(); )
{
Artifact artifactInstance = (Artifact) dit.next();
// pass in dependency stripVersion parameter
String outputFileName = copyArtifact( artifactInstance, dest, dep.getStripVersion() );
map.addCopiedFileNameRelativeToDestination( outputFileName );
}
}
if ( map.getCopiedFileNamesRelativeToDestination().isEmpty() )
{
log.info( "Mapping empty with destination: " + dest.getName() );
// Build the output directory if it doesn't exist
if ( !dest.exists() )
{
log.info( "Creating empty directory " + dest.getAbsolutePath() );
if ( !dest.mkdirs() )
{
throw new MojoExecutionException( "Unable to create " + dest.getAbsolutePath() );
}
}
}
}
}
}
/**
* Copy a set of files.
*
* @param src The file or directory to start copying from
* @param srcName The src file name to be used in the copy, only used if the src is not a directory.
* @param dest The destination directory
* @param incl The list of inclusions
* @param excl The list of exclusions
* @param filter Indicates if the file(s) being copied should be filtered.
* @return List of file names, relative to <i>dest</i>, copied to <i>dest</i>.
* @throws MojoExecutionException if a problem occurs
*/
private List copySource( File src, String srcName, File dest, List incl, List excl, boolean filter )
throws MojoExecutionException
{
try
{
// Set the destination
copier.setDestFile( dest );
// Set the source
if ( src.isDirectory() )
{
String[] ia = null;
if ( incl != null )
{
ia = (String[]) incl.toArray( new String[0] );
}
String[] ea = null;
if ( excl != null )
{
ea = (String[]) excl.toArray( new String[0] );
}
copier.addDirectory( src, "", ia, ea );
}
else
{
// set srcName to default if null
srcName = srcName != null ? srcName : src.getName();
copier.addFile( src, srcName );
}
copier.setFilter( filter );
copier.setFilterWrappers( mojo.getFilterWrappers() );
// Perform the copy
copier.createArchive();
Map copiedFilesMap = copier.getFiles();
List copiedFiles = new ArrayList( copiedFilesMap.size() );
for ( Iterator i = copiedFilesMap.keySet().iterator(); i.hasNext(); )
{
String key = (String) i.next();
if ( key != null && key.length() > 0 )
{
copiedFiles.add( key );
}
}
// Clear the list for the next mapping
copier.resetArchiver();
return copiedFiles;
}
catch ( Throwable t )
{
throw new MojoExecutionException( "Unable to copy files for packaging: " + t.getMessage(), t );
}
}
/**
* Copy an artifact.
*
* @param art The artifact to copy
* @param dest The destination directory
* @param stripVersion Whether or not to strip the artifact version from the filename
* @return Artifact file name
* @throws MojoExecutionException if a problem occurs
*/
private String copyArtifact( Artifact art, File dest, boolean stripVersion )
throws MojoExecutionException
{
if ( art.getFile() == null )
{
final Log log = mojo.getLog();
log.warn( "Artifact " + art + " requested in configuration." );
log.warn( "Plugin must run in standard lifecycle for this to work." );
return null;
}
String outputFileName;
if ( stripVersion )
{
final String classifier = art.getClassifier();
// strip the version from the file name
outputFileName = art.getArtifactId();
if ( classifier != null )
{
outputFileName += '-';
outputFileName += classifier;
}
outputFileName += '.';
outputFileName += art.getType();
}
else
{
outputFileName = art.getFile().getName();
}
copySource( art.getFile(), outputFileName, dest, null, null, false );
return outputFileName;
}
/**
* Make a list of the artifacts to package in this mapping.
*
* @param am The artifact mapping information
* @return The list of artifacts to package
*/
private List selectArtifacts( ArtifactMap am )
{
final List retval = new ArrayList();
final List clist = am.getClassifiers();
final Artifact artifact = mojo.getArtifact();
final List attachedArtifacts = mojo.getAttachedArtifacts();
if ( clist == null )
{
retval.add( artifact );
retval.addAll( attachedArtifacts );
}
else
{
if ( clist.contains( null ) )
{
retval.add( artifact );
}
for ( Iterator ait = attachedArtifacts.iterator(); ait.hasNext(); )
{
Artifact aa = (Artifact) ait.next();
if ( ( aa.hasClassifier() ) && ( clist.contains( aa.getClassifier() ) ) )
{
retval.add( aa );
}
}
}
return retval;
}
/**
* Make a list of the dependencies to package in this mapping.
*
* @param d The artifact mapping information
* @return The list of artifacts to package
*/
private List selectDependencies( Dependency d )
{
List retval = new ArrayList();
List inc = d.getIncludes();
List exc = d.getExcludes();
Collection deps = mojo.project.getArtifacts();
if ( deps == null || deps.isEmpty() )
{
return retval;
}
final Log log = mojo.getLog();
for ( Iterator it = deps.iterator(); it.hasNext(); )
{
Artifact pdep = (Artifact) it.next();
log.debug( "Dependency is " + pdep + " at " + pdep.getFile() );
if ( !depMatcher( pdep, exc ) )
{
log.debug( "--> not excluded" );
if ( ( inc == null ) || ( depMatcher( pdep, inc ) ) )
{
log.debug( "--> included" );
retval.add( pdep );
}
}
}
return retval;
}
/**
* Installs the {@link Mapping#getSources() sources} to <i>dest</i>
*
* @param map The <tt>Mapping</tt> to process the {@link Source sources} for.
* @param dest The destination directory for the sources.
* @throws MojoExecutionException
* @throws MojoFailureException
*/
private void processSources( Mapping map, File dest )
throws MojoExecutionException, MojoFailureException
{
if ( !dest.exists() )
{
if ( !dest.mkdirs() )
{
throw new MojoExecutionException( "unable to create directory: " + dest.getAbsolutePath() );
}
}
String relativeDestination = map.getDestination();
if ( !relativeDestination.endsWith( File.separator ) )
{
relativeDestination += File.separatorChar;
}
List srcs = map.getSources();
if ( srcs != null )
{
// for passivity, we will always use lowercase representation of architecture
// for comparison purposes.
final String targetArchComparison = mojo.getTargetArch().toLowerCase( Locale.ENGLISH );
final String targetOS = mojo.getTargetOS();
final Map/* <String, List<Source>> */linkTargetToSources = mojo.getLinkTargetToSources();
// it is important that for each Source we set the files that are "installed".
for ( Iterator sit = srcs.iterator(); sit.hasNext(); )
{
Source src = (Source) sit.next();
if ( !src.matchesArchitecture( targetArchComparison ) )
{
mojo.getLog().debug( "Source does not match target architecture: " + src.toString() );
continue;
}
if ( !src.matchesOSName( targetOS ) )
{
mojo.getLog().debug( "Source does not match target os name: " + src.toString() );
continue;
}
final String macroEvaluatedLocation = evaluateMacros( src.getLocation() );
src.setMacroEvaluatedLocation( macroEvaluatedLocation );
final File locationFile =
macroEvaluatedLocation.startsWith( "/" ) ? new File( macroEvaluatedLocation )
: new File( mojo.project.getBasedir(), macroEvaluatedLocation );
// it is important that we check if softlink source first as the "location" may
// exist in the filesystem of the build machine
if ( src instanceof SoftlinkSource )
{
List sources = (List) linkTargetToSources.get( relativeDestination );
if ( sources == null )
{
sources = new LinkedList();
linkTargetToSources.put( relativeDestination, sources );
}
sources.add( src );
( (SoftlinkSource) src ).setSourceMapping( map );
map.setHasSoftLinks( true );
}
else if ( locationFile.exists() )
{
final String destination = src.getDestination();
if ( destination == null )
{
List elist = src.getExcludes();
if ( !src.getNoDefaultExcludes() )
{
if ( elist == null )
{
elist = new ArrayList();
}
elist.addAll( FileUtils.getDefaultExcludesAsList() );
}
map.addCopiedFileNamesRelativeToDestination( copySource( locationFile, null, dest,
src.getIncludes(), elist,
src.isFilter() ) );
}
else
{
if ( !locationFile.isFile() )
{
throw new MojoExecutionException( MessageFormat.format( DESTINATION_DIRECTORY_ERROR_MSG,
new Object[] { destination,
macroEvaluatedLocation } ) );
}
copySource( locationFile, destination, dest, Collections.EMPTY_LIST, Collections.EMPTY_LIST,
src.isFilter() );
map.addCopiedFileNameRelativeToDestination( destination );
}
}
else
{
throw new MojoExecutionException( "Source location " + macroEvaluatedLocation + " does not exist" );
}
}
}
}
/**
* Determine if there are any macros in the <i>value</i> and replace any/all occurrences with the
* {@link AbstractRPMMojo#evaluateMacro(String) evaluated} value.
* @param value String to replace macros in.
* @return Result of evaluating all macros in <i>value</i>.
* @throws MojoExecutionException
* @since 2.1-alpha-1
*/
private String evaluateMacros(String value) throws MojoExecutionException
{
final Matcher matcher = MACRO_PATTERN.matcher( value );
final StringBuffer newValue = new StringBuffer( value.length() );
while ( matcher.find() )
{
final String macro = matcher.group( 1 );
final String evaluatedValue = mojo.evaluateMacro( macro );
matcher.appendReplacement( newValue, evaluatedValue.replaceAll( "\\\\", "\\\\\\\\" ) );
}
matcher.appendTail( newValue );
return newValue.toString();
}
/**
* Determine if the dependency matches an include or exclude list.
*
* @param dep The dependency to check
* @param list The list to check against
* @return <code>true</code> if the dependency was found on the list
*/
private boolean depMatcher( Artifact dep, List list )
{
if ( list == null )
{
// No list, not possible to match
return false;
}
final Log log = mojo.getLog();
for ( Iterator it = list.iterator(); it.hasNext(); )
{
final Artifact item = (Artifact) it.next();
log.debug( "Compare " + dep + " to " + item );
final String groupId = item.getGroupId();
if ( StringUtils.isEmpty( groupId ) || "*".equals( groupId ) || groupId.equals( dep.getGroupId() ) )
{
log.debug( "... Group matches" );
final String artifactId = item.getArtifactId();
if ( StringUtils.isEmpty( artifactId ) || "*".equals( artifactId )
|| artifactId.equals( dep.getArtifactId() ) )
{
log.debug( "... Artifact matches" );
// ArtifactVersion av = item.getVersionRange().matchVersion( dep.getAvailableVersions() );
try
{
if ( item.getVersionRange().containsVersion( dep.getSelectedVersion() ) )
{
log.debug( "... Version matches" );
return true;
}
}
catch ( OverConstrainedVersionException ocve )
{
log.debug( "... caught OverConstrainedVersionException" );
}
}
}
}
// Not found
return false;
}
}