/*-
* Copyright (c) 2012-2015 Red Hat, Inc.
*
* Licensed 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.
*/
package org.fedoraproject.xmvn.mojo;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.inject.Inject;
import javax.inject.Named;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.maven.lifecycle.mapping.Lifecycle;
import org.apache.maven.lifecycle.mapping.LifecycleMapping;
import org.apache.maven.lifecycle.mapping.LifecycleMojo;
import org.apache.maven.lifecycle.mapping.LifecyclePhase;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.Model;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
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.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.MXSerializer;
import org.codehaus.plexus.util.xml.pull.XmlSerializer;
import org.fedoraproject.xmvn.artifact.Artifact;
import org.fedoraproject.xmvn.artifact.DefaultArtifact;
import org.fedoraproject.xmvn.model.ModelProcessor;
import org.fedoraproject.xmvn.utils.ArtifactTypeRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* @author Mikolaj Izdebski
*/
@Mojo( name = "builddep", aggregator = true, requiresDependencyResolution = ResolutionScope.NONE )
@Named
public class BuilddepMojo
extends AbstractMojo
{
static class NamespacedArtifact
{
String namespace;
Artifact artifact;
public NamespacedArtifact( String namespace, Artifact artifact )
{
this.namespace = namespace != null ? namespace : "";
this.artifact = artifact;
}
@Override
public int hashCode()
{
return artifact.hashCode() ^ namespace.hashCode();
}
@Override
public boolean equals( Object rhs )
{
NamespacedArtifact other = (NamespacedArtifact) rhs;
return namespace.equals( other.namespace ) && artifact.equals( other.artifact );
}
}
private final Logger logger = LoggerFactory.getLogger( BuilddepMojo.class );
@Parameter( defaultValue = "xmvn.builddep.skip" )
private boolean skip;
@Parameter( defaultValue = "${reactorProjects}", readonly = true, required = true )
private List<MavenProject> reactorProjects;
@Inject
private Map<String, LifecycleMapping> lifecycleMappings;
private final ModelProcessor modelProcessor;
// Injected through reflection by XMvn lifecycle participant
private List<String[]> resolutions;
private Set<Artifact> commonDeps = new LinkedHashSet<>();
@Inject
public BuilddepMojo( ModelProcessor modelProcessor )
{
this.modelProcessor = modelProcessor;
try (InputStream xmlStream = ArtifactTypeRegistry.class.getResourceAsStream( "/common-deps.xml" ))
{
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse( xmlStream );
NodeList dependencies = doc.getElementsByTagName( "dependency" );
for ( int i = 0; i < dependencies.getLength(); i++ )
{
Element dependency = (Element) dependencies.item( i );
String groupId = dependency.getAttribute( "groupId" );
String artifactId = dependency.getAttribute( "artifactId" );
commonDeps.add( new DefaultArtifact( groupId, artifactId ) );
}
}
catch ( ParserConfigurationException | IOException | SAXException ex )
{
throw new RuntimeException( "Couldnt load resource 'common-deps.xml'", ex );
}
}
private static void addOptionalChild( Xpp3Dom parent, String tag, String value, String defaultValue )
{
if ( defaultValue == null || !value.equals( defaultValue ) )
{
Xpp3Dom child = new Xpp3Dom( tag );
child.setValue( value );
parent.addChild( child );
}
}
private static Xpp3Dom toXpp3Dom( NamespacedArtifact namespacedArtifact, String tag )
{
Artifact artifact = namespacedArtifact.artifact;
Xpp3Dom parent = new Xpp3Dom( tag );
addOptionalChild( parent, "namespace", namespacedArtifact.namespace, "" );
addOptionalChild( parent, "groupId", artifact.getGroupId(), null );
addOptionalChild( parent, "artifactId", artifact.getArtifactId(), null );
addOptionalChild( parent, "extension", artifact.getExtension(), "jar" );
addOptionalChild( parent, "classifier", artifact.getClassifier(), "" );
addOptionalChild( parent, "version", artifact.getVersion(), "SYSTEM" );
return parent;
}
private static void serialize( NamespacedArtifact artifact, XmlSerializer serializer, String namespace, String tag )
throws IOException
{
Xpp3Dom dom = toXpp3Dom( artifact, tag );
dom.writeToSerializer( namespace, serializer );
}
private Set<Artifact> getModelDependencies( Model model )
{
Function<InputLocation, Boolean> isExternalLocation =
location -> !reactorProjects.stream() //
.map( project -> project.getModel().getLocation( "" ).getSource().getModelId() ) //
.filter( modelId -> modelId.equals( location.getSource().getModelId() ) ) //
.findAny().isPresent();
BuildDependencyVisitor visitor = new BuildDependencyVisitor( isExternalLocation );
modelProcessor.processModel( model.clone(), visitor );
return visitor.getArtifacts();
}
private void addLifecycleDependencies( Set<Artifact> artifacts, String packaging )
{
LifecycleMapping lifecycleMapping = lifecycleMappings.get( packaging != null ? packaging : "jar" );
Lifecycle defaultLifecycle = lifecycleMapping.getLifecycles().get( "default" );
if ( defaultLifecycle == null )
return;
for ( LifecyclePhase phase : defaultLifecycle.getLifecyclePhases().values() )
{
if ( phase.getMojos() == null )
continue;
for ( LifecycleMojo mojo : phase.getMojos() )
{
String[] goalCoords = mojo.getGoal().split( ":" );
if ( goalCoords.length == 4 )
{
artifacts.add( new DefaultArtifact( goalCoords[0], goalCoords[1] ) );
}
}
}
}
@Override
public void execute()
throws MojoExecutionException
{
if ( skip )
{
logger.info( "Skipping buiddep: xmvn.builddep.skip property was set" );
return;
}
if ( resolutions == null )
{
logger.warn( "Skipping buiddep: XMvn lifecycle participant is absent" );
return;
}
Set<Artifact> artifacts = new LinkedHashSet<>();
Set<Artifact> lifecycleArtifacts = new LinkedHashSet<>();
for ( MavenProject project : reactorProjects )
{
artifacts.addAll( getModelDependencies( project.getModel() ) );
addLifecycleDependencies( lifecycleArtifacts, project.getPackaging() );
}
artifacts.removeIf( dep -> commonDeps.contains( dep.setVersion( Artifact.DEFAULT_VERSION ) ) );
lifecycleArtifacts.removeIf( dep -> commonDeps.contains( dep ) );
Set<NamespacedArtifact> deps = new LinkedHashSet<>();
for ( String[] resolution : resolutions )
{
if ( resolution == null )
continue;
Artifact artifact = new DefaultArtifact( resolution[0] );
Artifact versionlessArtifact = artifact.setVersion( Artifact.DEFAULT_VERSION );
String compatVersion = resolution[1];
String namespace = resolution[2];
if ( artifacts.contains( artifact ) || lifecycleArtifacts.contains( versionlessArtifact ) )
{
deps.add( new NamespacedArtifact( namespace, artifact.setVersion( compatVersion ) ) );
}
}
serializeArtifacts( deps );
}
private void serializeArtifacts( Set<NamespacedArtifact> artifacts )
throws MojoExecutionException
{
try (Writer writer = Files.newBufferedWriter( Paths.get( ".xmvn-builddep" ), StandardCharsets.UTF_8 ))
{
XmlSerializer s = new MXSerializer();
s.setProperty( "http://xmlpull.org/v1/doc/properties.html#serializer-indentation", " " );
s.setProperty( "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator", "\n" );
s.setOutput( writer );
s.startDocument( "US-ASCII", null );
s.comment( " Build dependencies generated by XMvn " );
s.text( "\n" );
s.startTag( null, "dependencies" );
for ( NamespacedArtifact dependencyArtifact : artifacts )
serialize( dependencyArtifact, s, null, "dependency" );
s.endTag( null, "dependencies" );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Unable to write builddep file", e );
}
}
}