com.simpligility.maven.plugins.android.phase_prebuild.ClasspathModifierLifecycleParticipant Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-maven-plugin Show documentation
Show all versions of android-maven-plugin Show documentation
Maven Plugin for Android Development
The newest version!
/*******************************************************************************
* Copyright (c) 2008, 2011 Sonatype Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sonatype Inc. - initial API and implementation
*******************************************************************************/
package com.simpligility.maven.plugins.android.phase_prebuild;
import com.simpligility.maven.plugins.android.common.AndroidExtension;
import com.simpligility.maven.plugins.android.common.ArtifactResolverHelper;
import com.simpligility.maven.plugins.android.common.DependencyResolver;
import com.simpligility.maven.plugins.android.common.PomConfigurationHelper;
import com.simpligility.maven.plugins.android.common.UnpackedLibHelper;
import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* Adds classes from AAR and APK dependencies to the project compile classpath.
*
* @author William Ferguson
* @author Benoit Billington
* @author Manfred Moser
*/
@Component( role = AbstractMavenLifecycleParticipant.class, hint = "default" )
public final class ClasspathModifierLifecycleParticipant extends AbstractMavenLifecycleParticipant
{
/**
* Mojo configuration parameter to determine if jar files found inside an apklib are
* pulled onto the classpath and into the resulting apk, defaults to false
* @see INCLUDE_FROM_APKLIB_DEFAULT
*/
private static final String INCLUDE_FROM_APKLIB_PARAM = "includeLibsJarsFromApklib";
/**
* Mojo configuration parameter to determine if jar files found inside an aar are
* pulled onto the classpath and into the resulting apk, defaults to false
* @see INCLUDE_FROM_AAR_DEFAULT
*/
private static final String INCLUDE_FROM_AAR_PARAM = "includeLibsJarsFromAar";
/**
* Mojo configuration parameter to determine if we should warn about dependency conflicts with the provided
* dependencies.
*
* @see DISABLE_CONFLICTING_DEPENDENCIES_WARNING_DEFAULT
*/
private static final String DISABLE_CONFLICTING_DEPENDENCIES_WARNING_PARAM
= "disableConflictingDependenciesWarning";
private static final boolean INCLUDE_FROM_APKLIB_DEFAULT = false;
private static final boolean INCLUDE_FROM_AAR_DEFAULT = true;
private static final boolean DISABLE_CONFLICTING_DEPENDENCIES_WARNING_DEFAULT = false;
/**
* Mojo configuration parameter that defines where AAR files should be unpacked.
* Default is /target/unpacked-libs
*/
private static final String UNPACKED_LIBS_FOLDER_PARAM = "unpackedLibsFolder";
@Requirement
private ArtifactResolver artifactResolver;
@Requirement( hint = "default" )
private DependencyGraphBuilder dependencyGraphBuilder;
@Requirement
private Logger log;
private boolean addedJarFromLibs = false;
@Override
public void afterProjectsRead( MavenSession session ) throws MavenExecutionException
{
log.debug( "" );
log.debug( "ClasspathModifierLifecycleParticipant#afterProjectsRead - start" );
log.debug( "" );
log.debug( "CurrentProject=" + session.getCurrentProject() );
final List projects = session.getProjects();
final DependencyResolver dependencyResolver = new DependencyResolver( log, dependencyGraphBuilder );
final ArtifactResolverHelper artifactResolverHelper = new ArtifactResolverHelper( artifactResolver, log );
for ( MavenProject project : projects )
{
log.debug( "" );
log.debug( "project=" + project.getArtifact() );
if ( ! AndroidExtension.isAndroidPackaging( project.getPackaging() ) )
{
continue; // do not modify classpath if not an android project.
}
final String unpackedLibsFolder
= getMojoConfigurationParameter( project, UNPACKED_LIBS_FOLDER_PARAM, null );
final UnpackedLibHelper helper = new UnpackedLibHelper( artifactResolverHelper, project, log,
unpackedLibsFolder == null ? null : new File( unpackedLibsFolder )
);
final Set artifacts;
// If there is an extension ClassRealm loaded for this project then use that
// as the ContextClassLoader so that Wagon extensions can be used to resolves dependencies.
final ClassLoader projectClassLoader = ( project.getClassRealm() != null )
? project.getClassRealm()
: Thread.currentThread().getContextClassLoader();
final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader( projectClassLoader );
artifacts = dependencyResolver.getProjectDependenciesFor( project, session );
}
catch ( DependencyGraphBuilderException e )
{
// Nothing to do. The resolution failure will be displayed by the standard resolution mechanism.
continue;
}
finally
{
Thread.currentThread().setContextClassLoader( originalClassLoader );
}
boolean includeFromAar = getMojoConfigurationParameter( project, INCLUDE_FROM_AAR_PARAM,
INCLUDE_FROM_AAR_DEFAULT );
boolean includeFromApklib = getMojoConfigurationParameter( project, INCLUDE_FROM_APKLIB_PARAM,
INCLUDE_FROM_APKLIB_DEFAULT );
boolean disableConflictingDependenciesWarning = getMojoConfigurationParameter( project,
DISABLE_CONFLICTING_DEPENDENCIES_WARNING_PARAM, DISABLE_CONFLICTING_DEPENDENCIES_WARNING_DEFAULT );
log.debug( "projects deps: : " + artifacts );
if ( !disableConflictingDependenciesWarning )
{
ProvidedDependencyChecker checker = new ProvidedDependencyChecker();
checker.checkProvidedDependencies( artifacts, log );
}
for ( Artifact artifact : artifacts )
{
final String type = artifact.getType();
if ( type.equals( AndroidExtension.AAR ) )
{
// An AAR lib contains a classes jar that needs to be added to the classpath.
// Create a placeholder classes.jar and add it to the compile classpath.
// It will replaced with the real classes.jar by GenerateSourcesMojo.
addClassesToClasspath( helper, project, artifact );
// An AAR may also contain zero or more internal libs in the libs folder.
// If 'includeLibsJarsFromAar' config param is true then include them too.
if ( includeFromAar )
{
// Add jar files in 'libs' into classpath.
addLibsJarsToClassPath( helper, project, artifact );
}
}
else if ( type.equals( AndroidExtension.APK ) )
{
// The only time that an APK will likely be a dependency is when this an an APK test project.
// So add a placeholder (we cannot resolve the actual dep pre build) to the compile classpath.
// The placeholder will be replaced with the real APK jar later.
addClassesToClasspath( helper, project, artifact );
}
else if ( type.equals( AndroidExtension.APKLIB ) )
{
if ( includeFromApklib )
{
// Add jar files in 'libs' into classpath.
addLibsJarsToClassPath( helper, project, artifact );
}
}
}
}
if ( addedJarFromLibs )
{
log.warn(
"Transitive dependencies should really be provided by Maven dependency management.\n"
+ " We suggest you to ask the above providers to package their component properly.\n"
+ " Things may break at compile and/or runtime due to multiple copies of incompatible libraries." );
}
log.debug( "" );
log.debug( "ClasspathModifierLifecycleParticipant#afterProjectsRead - finish" );
}
private String getMojoConfigurationParameter( MavenProject project, String name, String defaultValue )
{
String value = PomConfigurationHelper.getPluginConfigParameter( project,
name, defaultValue );
log.debug( name + " set to " + value );
return value;
}
private boolean getMojoConfigurationParameter( MavenProject project, String name, boolean defaultValue )
{
return Boolean.valueOf( getMojoConfigurationParameter( project, name, Boolean.toString( defaultValue ) ) );
}
/**
* Add jar files in libs into the project classpath.
*/
private void addLibsJarsToClassPath( UnpackedLibHelper helper, MavenProject project, Artifact artifact )
throws MavenExecutionException
{
try
{
final File unpackLibFolder = helper.getUnpackedLibFolder( artifact );
final File artifactFile = helper.getArtifactToFile( artifact );
ZipFile zipFile = new ZipFile( artifactFile );
Enumeration enumeration = zipFile.entries();
while ( enumeration.hasMoreElements() )
{
ZipEntry entry = ( ZipEntry ) enumeration.nextElement();
String entryName = entry.getName();
// Only jar files under 'libs' directory to be processed.
if ( Pattern.matches( "^libs/.+\\.jar$", entryName ) )
{
final File libsJarFile = new File( unpackLibFolder, entryName );
log.warn( "Adding jar from libs folder to classpath: " + libsJarFile );
// In order to satisfy the LifecycleDependencyResolver on execution up to a phase that
// has a Mojo requiring dependency resolution I need to create a dummy classesJar here.
if ( !libsJarFile.getParentFile().exists() )
{
libsJarFile.getParentFile().mkdirs();
}
libsJarFile.createNewFile();
// Add the jar to the classpath.
final Dependency dependency =
createSystemScopeDependency( artifact, libsJarFile, libsJarFile.getName() );
project.getModel().addDependency( dependency );
addedJarFromLibs = true;
}
}
}
catch ( MojoExecutionException e )
{
log.debug( "Error extract jars" );
}
catch ( ZipException e )
{
log.debug( "Error" );
}
catch ( IOException e )
{
log.debug( "Error" );
}
}
/**
* Add the dependent library classes to the project classpath.
*/
private void addClassesToClasspath( UnpackedLibHelper helper, MavenProject project, Artifact artifact )
throws MavenExecutionException
{
// Work out where the dep will be extracted and calculate the file path to the classes jar.
// This is location where the GenerateSourcesMojo will extract the classes.
final File classesJar = helper.getUnpackedClassesJar( artifact );
log.debug( "Adding to classpath : " + classesJar );
// In order to satisfy the LifecycleDependencyResolver on execution up to a phase that
// has a Mojo requiring dependency resolution I need to create a dummy classesJar here.
if ( !classesJar.exists() )
{
classesJar.getParentFile().mkdirs();
try
{
final ZipOutputStream zipOutputStream = new ZipOutputStream( new FileOutputStream( classesJar ) );
zipOutputStream.putNextEntry( new ZipEntry( "dummy" ) );
zipOutputStream.close();
log.debug( "Created dummy " + classesJar.getName() + " exist=" + classesJar.exists() );
}
catch ( IOException e )
{
throw new MavenExecutionException( "Could not add " + classesJar.getName() + " as dependency", e );
}
}
// Modify the classpath to use an extracted dex file. This will overwrite
// any exisiting dependencies with the same information.
final Dependency dependency = createSystemScopeDependency( artifact, classesJar, null );
final Dependency providedJar = findProvidedDependencies( dependency, project );
if ( providedJar != null )
{
project.getModel().removeDependency( providedJar );
}
project.getModel().addDependency( dependency );
}
private Dependency createSystemScopeDependency( Artifact artifact, File location, String suffix )
{
String artifactId = artifact.getArtifactId();
if ( suffix != null )
{
artifactId += "_" + suffix;
}
final Dependency dependency = new Dependency();
dependency.setGroupId( artifact.getGroupId() );
dependency.setArtifactId( artifactId );
dependency.setVersion( artifact.getBaseVersion() );
dependency.setScope( Artifact.SCOPE_SYSTEM );
dependency.setSystemPath( location.getAbsolutePath() );
return dependency;
}
private Dependency findProvidedDependencies( Dependency dexDependency, MavenProject project )
{
for ( Dependency dependency : project.getDependencies() )
{
if ( dependency.getScope().equals( Artifact.SCOPE_PROVIDED ) )
{
if ( dependency.getArtifactId().equals( dexDependency.getArtifactId() )
&& dependency.getGroupId().equals( dexDependency.getGroupId() )
&& dependency.getType().equals( dexDependency.getType() )
&& dependency.getVersion().equals( dexDependency.getVersion() ) )
{
return dependency;
}
}
}
return null;
}
}