All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.simpligility.maven.plugins.android.phase_prebuild.ClasspathModifierLifecycleParticipant Maven / Gradle / Ivy

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;
        
    }
    
}
  




© 2015 - 2024 Weber Informatics LLC | Privacy Policy