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

com.jayway.maven.plugins.android.phase04processclasses.ProguardMojo Maven / Gradle / Ivy

There is a newer version: 4.0.0-rc.2
Show newest version
package com.jayway.maven.plugins.android.phase04processclasses;

import com.jayway.maven.plugins.android.AbstractAndroidMojo;
import com.jayway.maven.plugins.android.CommandExecutor;
import com.jayway.maven.plugins.android.ExecutionException;
import com.jayway.maven.plugins.android.config.ConfigHandler;
import com.jayway.maven.plugins.android.config.ConfigPojo;
import com.jayway.maven.plugins.android.config.PullParameter;
import com.jayway.maven.plugins.android.configuration.Proguard;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import org.sonatype.aether.util.artifact.JavaScopes;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Processes both application and dependency classes using the ProGuard byte code obfuscator,
 * minimzer, and optimizer. For more information, see https://proguard.sourceforge.net.
 *
 * @author Jonson
 * @author Matthias Kaeppler
 * @author Manfred Moser
 * @author Michal Harakal
 * @goal proguard
 * @phase process-classes
 * @requiresDependencyResolution compile
 */
public class ProguardMojo extends AbstractAndroidMojo
{

    /**
     * 

* ProGuard configuration. ProGuard is disabled by default. Set the skip parameter to false to activate proguard. * A complete configuartion can include any of the following: *

*

*

     * <proguard>
     *    <skip>true|false</skip>
     *    <config>proguard.cfg</config>
     *    <configs>
     *      <config>${env.ANDROID_HOME}/tools/proguard/proguard-android.txt</config>
     *    </configs>
     *    <proguardJarPath>someAbsolutePathToProguardJar</proguardJarPath>
     *    <filterMavenDescriptor>true|false</filterMavenDescriptor>
     *    <filterManifest>true|false</filterManifest>
     *    <jvmArguments>
     *     <jvmArgument>-Xms256m</jvmArgument>
     *     <jvmArgument>-Xmx512m</jvmArgument>
     *   </jvmArguments>
     * </proguard>
     * 
*

* A good practice is to create a release profile in your POM, in which you enable ProGuard. * ProGuard should be disabled for development builds, since it obfuscates class and field * names, and it may interfere with test projects that rely on your application classes. * All parameters can be overridden in profiles or the the proguard* properties. Default values apply and are * documented with these properties. *

* * @parameter */ @ConfigPojo protected Proguard proguard; /** * Whether ProGuard is enabled or not. Defaults to true. * * @parameter expression="${android.proguard.skip}" * @optional */ private Boolean proguardSkip; @PullParameter( defaultValue = "true" ) private Boolean parsedSkip; /** * Path to the ProGuard configuration file (relative to project root). Defaults to "proguard.cfg" * * @parameter expression="${android.proguard.config}" * @optional */ private String proguardConfig; @PullParameter( defaultValue = "proguard.cfg" ) private String parsedConfig; /** * Additional ProGuard configuration files (relative to project root). * * @parameter expression="${android.proguard.configs}" * @optional */ private String[] proguardConfigs; @PullParameter( defaultValueGetterMethod = "getDefaultProguardConfigs" ) private String[] parsedConfigs; /** * Path to the proguard jar and therefore version of proguard to be used. By default this will load the jar from * the Android SDK install. Overriding it with an absolute path allows you to use a newer or custom proguard * version.. *

* You can also reference an external Proguard version as a plugin dependency like this: *

     * <plugin>
     *   <groupId>com.jayway.maven.plugins.android.generation2</groupId>
     *   <artifactId>android-maven-plugin</artifactId>
     *     <dependencies>
     *       <dependency>
     *         <groupId>net.sf.proguard</groupId>
     *         <artifactId>proguard-base</artifactId>
     *         <version>4.7</version>
     *       </dependency>
     *     </dependencies>
     * 
*

* which will download and use Proguard 4.7 as deployed to the Central Repository. * * @parameter expression="${android.proguard.proguardJarPath} * @optional */ private String proguardProguardJarPath; @PullParameter( defaultValueGetterMethod = "getProguardJarPath" ) private String parsedProguardJarPath; /** * Path relative to the project's build directory (target) where proguard puts folowing files: *

*

    *
  • dump.txt
  • *
  • seeds.txt
  • *
  • usage.txt
  • *
  • mapping.txt
  • *
*

* You can define the directory like this: *

     * <proguard>
     *   <skip>false</skip>
     *   <config>proguard.cfg</config>
     *   <outputDirectory>my_proguard</outputDirectory>
     * </proguard> 
     * 
*

* Output directory is defined relatively so it could be also outside of the target directory. *

* * @parameter expression="${android.proguard.outputDirectory}" default-value="proguard" * @optional */ private String outputDirectory; @PullParameter( defaultValue = "proguard" ) private String parsedOutputDirectory; /** * Extra JVM Arguments. Using these you can e.g. increase memory for the jvm running the build. * Defaults to "-Xmx512M". * * @parameter expression="${android.proguard.jvmArguments}" * @optional */ private String[] proguardJvmArguments; @PullParameter( defaultValueGetterMethod = "getDefaultJvmArguments" ) private String[] parsedJvmArguments; /** * If set to true will add a filter to remove META-INF/maven/* files. Defaults to false. * * @parameter expression="${android.proguard.filterMavenDescriptor}" * @optional */ private Boolean proguardFilterMavenDescriptor; @PullParameter( defaultValue = "true" ) private Boolean parsedFilterMavenDescriptor; /** * If set to true will add a filter to remove META-INF/MANIFEST.MF files. Defaults to false. * * @parameter expression="${android.proguard.filterManifest}" * @optional */ private Boolean proguardFilterManifest; @PullParameter( defaultValue = "true" ) private Boolean parsedFilterManifest; /** * If set to true JDK jars will be included as library jars and corresponding filters * will be applied to android.jar. Defaults to true. * @parameter expression="${android.proguard.includeJdkLibs}" */ private Boolean includeJdkLibs; @PullParameter( defaultValue = "true" ) private Boolean parsedIncludeJdkLibs; /** * The plugin dependencies. * * @parameter expression="${plugin.artifacts}" * @required * @readonly */ protected List pluginDependencies; public static final String PROGUARD_OBFUSCATED_JAR = "proguard-obfuscated.jar"; private static final Collection ANDROID_LIBRARY_EXCLUDED_FILTER = Arrays .asList( "org/xml/**", "org/w3c/**", "java/**", "javax/**" ); private static final Collection MAVEN_DESCRIPTOR = Arrays.asList( "META-INF/maven/**" ); private static final Collection META_INF_MANIFEST = Arrays.asList( "META-INF/MANIFEST.MF" ); /** * For Proguard is required only jar type dependencies, all other like .so or .apklib can be skipped. */ private static final String USED_DEPENDENCY_TYPE = "jar"; private Collection globalInJarExcludes = new HashSet(); private List artifactBlacklist = new LinkedList(); private List artifactsToShift = new LinkedList(); private List inJars = new LinkedList(); private List libraryJars = new LinkedList(); private File javaHomeDir; private File javaLibDir; private File altJavaLibDir; private static class ProGuardInput { private String path; private Collection excludedFilter; public ProGuardInput( String path, Collection excludedFilter ) { this.path = path; this.excludedFilter = excludedFilter; } public String toCommandLine() { if ( excludedFilter != null && ! excludedFilter.isEmpty() ) { StringBuilder sb = new StringBuilder( path ); sb.append( '(' ); for ( Iterator it = excludedFilter.iterator(); it.hasNext(); ) { sb.append( '!' ).append( it.next() ); if ( it.hasNext() ) { sb.append( ',' ); } } sb.append( ')' ); return sb.toString(); } else { return "\'" + path + "\'"; } } } @Override public void execute() throws MojoExecutionException, MojoFailureException { ConfigHandler configHandler = new ConfigHandler( this ); configHandler.parseConfiguration(); if ( ! parsedSkip ) { executeProguard(); } } private void executeProguard() throws MojoExecutionException { final File proguardDir = new File( project.getBuild().getDirectory(), parsedOutputDirectory ); if ( ! proguardDir.exists() && ! proguardDir.mkdir() ) { throw new MojoExecutionException( "Cannot create proguard output directory" ); } else { if ( proguardDir.exists() && ! proguardDir.isDirectory() ) { throw new MojoExecutionException( "Non-directory exists at " + proguardDir.getAbsolutePath() ); } } CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger( this.getLog() ); List commands = new ArrayList(); collectJvmArguments( commands ); commands.add( "-jar" ); commands.add( parsedProguardJarPath ); commands.add( "@" + parsedConfig ); for ( String config : parsedConfigs ) { commands.add( "@" + config ); } if ( proguardFile != null ) { commands.add( "@" + proguardFile.getAbsolutePath() ); } collectInputFiles( commands ); commands.add( "-outjars" ); commands.add( "'" + project.getBuild().getDirectory() + File.separator + PROGUARD_OBFUSCATED_JAR + "'" ); commands.add( "-dump" ); commands.add( "'" + proguardDir + File.separator + "dump.txt'" ); commands.add( "-printseeds" ); commands.add( "'" + proguardDir + File.separator + "seeds.txt'" ); commands.add( "-printusage" ); commands.add( "'" + proguardDir + File.separator + "usage.txt'" ); commands.add( "-printmapping" ); commands.add( "'" + proguardDir + File.separator + "mapping.txt'" ); final String javaExecutable = getJavaExecutable().getAbsolutePath(); getLog().info( javaExecutable + " " + commands.toString() ); try { executor.executeCommand( javaExecutable, commands, project.getBasedir(), false ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "", e ); } } /** * Convert the jvm arguments in parsedJvmArguments as populated by the config in format as needed by the java * command. Also preserve backwards compatibility in terms of dashes required or not.. * * @param commands */ private void collectJvmArguments( List commands ) { if ( parsedJvmArguments != null ) { for ( String jvmArgument : parsedJvmArguments ) { // preserve backward compatibility allowing argument with or without dash (e.g. // Xmx512m as well as -Xmx512m should work) (see // http://code.google.com/p/maven-android-plugin/issues/detail?id=153) if ( ! jvmArgument.startsWith( "-" ) ) { jvmArgument = "-" + jvmArgument; } commands.add( jvmArgument ); } } } private void collectInputFiles( List commands ) { // commons-logging breaks everything horribly, so we skip it from the program // dependencies and declare it to be a library dependency instead skipArtifact( "commons-logging", "commons-logging", true ); collectProgramInputFiles(); for ( ProGuardInput injar : inJars ) { commands.add( "-injars" ); commands.add( injar.toCommandLine() ); } collectLibraryInputFiles(); for ( ProGuardInput libraryjar : libraryJars ) { commands.add( "-libraryjars" ); commands.add( libraryjar.toCommandLine() ); } } /** * Figure out the full path to the current java executable. * * @return the full path to the current java executable. */ private static File getJavaExecutable() { final String javaHome = System.getProperty( "java.home" ); final String slash = File.separator; return new File( javaHome + slash + "bin" + slash + "java" ); } private void skipArtifact( String groupId, String artifactId, boolean shiftToLibraries ) { artifactBlacklist.add( RepositoryUtils.toArtifact( new DefaultArtifact( groupId, artifactId, null, null ) ) ); if ( shiftToLibraries ) { artifactsToShift .add( RepositoryUtils.toArtifact( new DefaultArtifact( groupId, artifactId, null, null ) ) ); } } private boolean isBlacklistedArtifact( Artifact artifact ) { for ( Artifact artifactToSkip : artifactBlacklist ) { if ( artifactToSkip.getGroupId().equals( artifact.getGroupId() ) && artifactToSkip.getArtifactId() .equals( artifact.getArtifactId() ) ) { return true; } } return false; } private boolean isShiftedArtifact( Artifact artifact ) { for ( Artifact artifactToShift : artifactsToShift ) { if ( artifactToShift.getGroupId().equals( artifact.getGroupId() ) && artifactToShift.getArtifactId() .equals( artifact.getArtifactId() ) ) { return true; } } return false; } private void collectProgramInputFiles() { if ( parsedFilterManifest ) { globalInJarExcludes.addAll( META_INF_MANIFEST ); } if ( parsedFilterMavenDescriptor ) { globalInJarExcludes.addAll( MAVEN_DESCRIPTOR ); } // we first add the application's own class files addInJar( project.getBuild().getOutputDirectory() ); // we then add all its dependencies (incl. transitive ones), unless they're blacklisted for ( Artifact artifact : getAllRelevantDependencyArtifacts() ) { if ( isBlacklistedArtifact( artifact ) || !USED_DEPENDENCY_TYPE.equals( artifact.getType() ) ) { continue; } addInJar( artifact.getFile().getAbsolutePath(), globalInJarExcludes ); } } private void addInJar( String path, Collection filterExpression ) { inJars.add( new ProGuardInput( path, filterExpression ) ); } private void addInJar( String path ) { addInJar( path, null ); } private void addLibraryJar( String path, Collection filterExpression ) { libraryJars.add( new ProGuardInput( path, filterExpression ) ); } private void addLibraryJar( String path ) { addLibraryJar( path, null ); } private void collectLibraryInputFiles() { if ( parsedIncludeJdkLibs ) { // we have to add the Java framework classes to the library JARs, since they are not // distributed with the JAR on Central, and since we'll strip them out of the android.jar // that is shipped with the SDK (since that is not a complete Java distribution) File rtJar = getJVMLibrary( "rt.jar" ); if ( rtJar == null ) { rtJar = getJVMLibrary( "classes.jar" ); } if ( rtJar != null ) { addLibraryJar( rtJar.getPath() ); } // we also need to add the JAR containing e.g. javax.servlet File jsseJar = getJVMLibrary( "jsse.jar" ); if ( jsseJar != null ) { addLibraryJar( jsseJar.getPath() ); } // and the javax.crypto stuff File jceJar = getJVMLibrary( "jce.jar" ); if ( jceJar != null ) { addLibraryJar( jceJar.getPath() ); } } // we treat any dependencies with provided scope as library JARs for ( Artifact artifact : project.getArtifacts() ) { if ( artifact.getScope().equals( JavaScopes.PROVIDED ) ) { if ( artifact.getArtifactId().equals( "android" ) && parsedIncludeJdkLibs ) { addLibraryJar( artifact.getFile().getAbsolutePath(), ANDROID_LIBRARY_EXCLUDED_FILTER ); } else { addLibraryJar( artifact.getFile().getAbsolutePath() ); } } else { if ( isShiftedArtifact( artifact ) ) { // this is a blacklisted artifact that should be processed as a library instead addLibraryJar( artifact.getFile().getAbsolutePath() ); } } } } /** * Get the path to the proguard jar. * * @return * @throws MojoExecutionException */ private String getProguardJarPath() throws MojoExecutionException { String proguardJarPath = getProguardJarPathFromDependencies(); if ( StringUtils.isEmpty( proguardJarPath ) ) { File proguardJarPathFile = new File( getAndroidSdk().getToolsPath(), "proguard/lib/proguard.jar" ); return proguardJarPathFile.getAbsolutePath(); } return proguardJarPath; } private String getProguardJarPathFromDependencies() throws MojoExecutionException { Artifact proguardArtifact = null; int proguardArtifactDistance = - 1; for ( Artifact artifact : pluginDependencies ) { getLog().debug( "pluginArtifact: " + artifact.getFile() ); if ( ( "proguard".equals( artifact.getArtifactId() ) ) || ( "proguard-base" .equals( artifact.getArtifactId() ) ) ) { int distance = artifact.getDependencyTrail().size(); getLog().debug( "proguard DependencyTrail: " + distance ); if ( proguardArtifactDistance == - 1 ) { proguardArtifact = artifact; proguardArtifactDistance = distance; } else { if ( distance < proguardArtifactDistance ) { proguardArtifact = artifact; proguardArtifactDistance = distance; } } } } if ( proguardArtifact != null ) { getLog().debug( "proguardArtifact: " + proguardArtifact.getFile() ); return proguardArtifact.getFile().getAbsoluteFile().toString(); } else { return null; } } /** * Get the default JVM arguments for the proguard invocation. * * @return * @see #parsedJvmArguments */ private String[] getDefaultJvmArguments() { return new String[]{ "-Xmx512M" }; } /** * Get the default ProGuard config files. * * @return * @see #parsedConfigs */ private String[] getDefaultProguardConfigs() { return new String[0]; } /** * Finds a library file in either the primary or alternate lib directory. * @param fileName The base name of the file. * @return Either a canonical filename, or {@code null} if not found. */ private File getJVMLibrary( String fileName ) { File libFile = new File( getJavaLibDir(), fileName ); if ( !libFile.exists() ) { libFile = new File( getAltJavaLibDir(), fileName ); if ( !libFile.exists() ) { libFile = null; } } return libFile; } /** * Determines the java.home directory. * @return The java.home directory, as a File. */ private File getJavaHomeDir() { if ( javaHomeDir == null ) { javaHomeDir = new File( System.getProperty( "java.home" ) ); } return javaHomeDir; } /** * Determines the primary JVM library location. * @return The primary library directory, as a File. */ private File getJavaLibDir() { if ( javaLibDir == null ) { javaLibDir = new File( getJavaHomeDir(), "lib" ); } return javaLibDir; } /** * Determines the alternate JVM library location (applies with older * MacOSX JVMs). * @return The alternate JVM library location, as a File. */ private File getAltJavaLibDir() { if ( altJavaLibDir == null ) { altJavaLibDir = new File( getJavaHomeDir().getParent(), "Classes" ); } return altJavaLibDir; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy