/*
* ###
* Android Maven Plugin - android-maven-plugin
*
* Copyright (C) 1999 - 2012 Photon Infotech 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.
* ###
*/
/*
* Copyright (C) 2009 Jayway AB
*
* 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 com.photon.maven.plugins.android.phase04processclasses;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import com.photon.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.codehaus.plexus.util.FileUtils;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import org.sonatype.aether.util.artifact.JavaScopes;
import com.photon.maven.plugins.android.AbstractAndroidMojo;
import com.photon.maven.plugins.android.CommandExecutor;
import com.photon.maven.plugins.android.ExecutionException;
import com.photon.maven.plugins.android.common.AndroidExtension;
/**
* Processes both application and dependency classes using the ProGuard byte code obfuscator,
* minimzer, and optimizer. For more information, see https://proguard.sourceforge.net.
*
* @goal proguard
* @phase process-classes
* @requiresDependencyResolution compile
*/
public class ProguardMojo extends AbstractAndroidMojo {
/**
* <p>
* Enables ProGuard for this build. ProGuard is disabled by default, so in order for it to run,
* enable it like so:
* </p>
*
* <pre>
* <proguard>
* <skip>false</skip>
* <config>proguard.cfg</config>
* <proguardJarPath>someAbsolutePathToProguardJar</proguardJarPath>
* <jvmArguments>
* <jvmArgument>-Xms256m</jvmArgument>
* <jvmArgument>-Xmx512m</jvmArgument>
* </jvmArguments>
* </proguard>
* </pre>
* <p>
* 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 since it may interfere with test projects that rely on your application classes.
* </p>
*
* @parameter
*/
protected Proguard proguard;
/**
* Whether ProGuard is enabled or not.
*
* @parameter expression="${android.proguard.skip}" default-value=true
* @optional
*/
private Boolean proguardSkip;
/**
* Path to the ProGuard configuration file (relative to project root).
*
* @parameter expression="${android.proguard.config}" default-value="source/proguard.cfg"
* @optional
*/
private String proguardConfig;
/**
* Path to the proguard jar 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 e.g. located in your local Maven repo.
*
* @parameter expression="${android.proguard.proguardJarPath}
* @optional
*/
private String proguardProguardJarPath;
/**
* Extra JVM Arguments. Using these you can e.g. increase memory for the jvm running the build.
*
* @parameter expression="${android.proguard.jvmArguments}" default-value="-Xmx512M"
* @optional
*/
private String[] proguardJvmArguments;
private Boolean parsedSkip;
private String parsedConfig;
private String parsedProguardJarPath;
private String[] parsedJvmArguments;
public static final String PROGUARD_OBFUSCATED_JAR = "proguard-obfuscated.jar";
private static final String ANDROID_LIBRARY_FILTER = "!org/xml/**,!org/w3c/**,!org/apache/http/**,!java/**,!javax/**,!android/net/http/AndroidHttpClient.class";
private List<Artifact> artifactBlacklist = new LinkedList<Artifact>();
private List<Artifact> artifactsToShift = new LinkedList<Artifact>();
private List<ProGuardInput> inJars = new LinkedList<ProguardMojo.ProGuardInput>();
private List<ProGuardInput> libraryJars = new LinkedList<ProguardMojo.ProGuardInput>();
private static class ProGuardInput {
private String path;
private String filterExpression;
public ProGuardInput(String path, String filterExpression) {
this.path = path;
this.filterExpression = filterExpression;
}
public String toCommandLine() {
if (filterExpression != null) {
return "\'" + path + "\'(" + filterExpression + ")";
}
return "\'" + path + "\'";
}
}
public void execute() throws MojoExecutionException, MojoFailureException {
parseConfiguration();
if (!parsedSkip) {
executeProguard();
}
}
private void parseConfiguration() {
if (proguard != null) {
if (proguard.isSkip() != null) {
parsedSkip = proguard.isSkip();
} else {
parsedSkip = proguardSkip;
}
if (StringUtils.isNotEmpty(proguard.getConfig())) {
parsedConfig = proguard.getConfig();
} else {
parsedConfig = proguardConfig;
}
if (StringUtils.isNotEmpty(proguard.getProguardJarPath())) {
parsedProguardJarPath = proguard.getProguardJarPath();
} else {
parsedProguardJarPath = proguardProguardJarPath;
}
if (proguard.getJvmArguments() == null) {
parsedJvmArguments = proguardJvmArguments;
} else {
parsedJvmArguments = proguard.getJvmArguments();
}
} else {
parsedSkip = proguardSkip;
parsedConfig = proguardConfig;
parsedProguardJarPath = proguardProguardJarPath;
parsedJvmArguments = proguardJvmArguments;
}
}
private void executeProguard() throws MojoExecutionException {
File proguardDir = new File(project.getBuild().getDirectory(), "proguard");
proguardDir.mkdirs();
getLog().info("proguard directory ==> " +proguardDir.getAbsolutePath());
if (!proguardDir.exists() && !proguardDir.mkdirs()) {
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<String> commands = new ArrayList<String>();
collectJvmArguments(commands);
// nothing was configured - set up default
if (StringUtils.isEmpty(parsedProguardJarPath)) {
parsedProguardJarPath = getAndroidSdk().getPathForTool("proguard/lib/proguard.jar");
}
commands.add("-jar");
commands.add(parsedProguardJarPath);
commands.add("@" + parsedConfig);
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);
}
}
private void collectJvmArguments(List<String> 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<String> 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);
// skipArtifact("modules.tech-android-native.files", "mod_googlegson_1.7.1", true);
collectProgramInputFiles();
for (ProGuardInput injar : inJars) {
// don't add android packaging files, these are not input to proguard
if (!AndroidExtension.isAndroidPackaging(FileUtils.extension(injar.path))) {
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() {
// 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)) {
continue;
}
addInJar(artifact.getFile().getAbsolutePath());
}*/
}
private void addInJar(String path, String filterExpression) {
inJars.add(new ProGuardInput(path, filterExpression));
}
private void addInJar(String path) {
addInJar(path, null);
}
private void addLibraryJar(String path, String filterExpression) {
libraryJars.add(new ProGuardInput(path, filterExpression));
}
private void addLibraryJar(String path) {
addLibraryJar(path, null);
}
private void collectLibraryInputFiles() {
final String slash = File.separator;
// 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)
String javaHome = System.getProperty("java.home");
String osname = System.getProperty("os.name");
String jdkLibsPath = null;
if(osname.startsWith("Mac")) {
// MacOS X uses different naming conventions for JDK installations
jdkLibsPath = javaHome + "/../Classes";
addLibraryJar(jdkLibsPath + "/classes.jar");
} else {
jdkLibsPath = javaHome + slash + "lib";
addLibraryJar(jdkLibsPath + slash + "rt.jar");
}
// we also need to add the JAR containing e.g. javax.servlet
addLibraryJar(jdkLibsPath + slash + "jsse.jar");
// and the javax.crypto stuff
addLibraryJar(jdkLibsPath + slash + "jce.jar");
// 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")) {
addLibraryJar(artifact.getFile().getAbsolutePath(), ANDROID_LIBRARY_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());
}
}
}
}