com.cjoop.maven.proguard.ProGuardMojo Maven / Gradle / Ivy
package com.cjoop.maven.proguard;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.archiver.jar.JarArchiver;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import proguard.Configuration;
import proguard.ConfigurationParser;
import proguard.ProGuard;
/**
*
*
* The Obfuscate task provides a stand-alone obfuscation task
*
*
* @goal proguard
* @phase package
* @description Create small jar files using ProGuard
* @requiresDependencyResolution compile
* @threadSafe
* @author chenjun
*/
public class ProGuardMojo extends AbstractMojo{
private static final String WEB_INF = "WEB-INF";
private Log log;
/**
* Set this to 'true' to bypass ProGuard processing entirely.
*
* @parameter property="proguard.skip"
*/
private boolean skip;
/**
* The Maven project reference where the plugin is currently being executed. The default value is populated from
* maven.
*
* @parameter property="project"
* @readonly
* @required
*/
protected MavenProject mavenProject;
/**
* Directory containing the input and generated JAR.
*
* @parameter property="project.build.directory"
* @required
*/
protected File outputDirectory;
/**
* @parameter property="project.build.finalName"
*/
protected String finalName;;
/**
* Specifies the input jar name (or wars, ears, zips) of the application to be
* processed.
*
* You may specify a classes directory e.g. 'classes'. This way plugin will processed
* the classes instead of jar. You would need to bind the execution to phase 'compile'
* or 'process-classes' in this case.
*
*/
protected String injar;
/**
* Specifies the names of the output jars. If attach=true the value ignored and name constructed base on classifier
* If empty input jar would be overdriven.
*
* @parameter
*/
protected String outjar;
/**
* Specifies whether or not to attach the created artifact to the project
*
* @parameter default-value="false"
*/
private boolean attach;
protected boolean sameArtifact;
/**
* Specifies attach artifact type
*
* @parameter default-value="jar"
*/
private String attachArtifactType;
/**
* Specifies attach artifact Classifier, Ignored if attach=false
*
* @parameter default-value="small"
*/
private String attachArtifactClassifier;
/**
* Set to false to exclude the attachArtifactClassifier from the Artifact final name. Default value is true.
*
* @parameter default-value="true"
*/
private boolean appendClassifier;
/**
* Set to true to include META-INF/maven/** maven descriptord
*
* @parameter default-value="false"
*/
private boolean addMavenDescriptor;
/**
* Specifies not to obfuscate the input class files.
*
* @parameter default-value="true"
*/
private boolean obfuscate;
/**
* Recursively reads configuration options from the given file filename
*
* @parameter default-value="${basedir}/proguard.conf"
*/
private File proguardInclude;
/**
* Apply ProGuard classpathentry Filters to input jar. e.g. !**.gif,!**/tests/**'
*
* @parameter
*/
protected String inFilter;
/**
* Apply ProGuard classpathentry Filters to output jar. e.g. !**.gif,!**/tests/**'
*
* @parameter
*/
protected String outFilter;
/**
* Additional -libraryjars e.g. ${java.home}/lib/rt.jar Project compile dependency are added automatically. See
* exclusions
*
* @parameter
*/
private List libs;
/**
* Sets the name of the ProGuard mapping file.
*
* @parameter default-value="proguard_map.txt"
*/
protected String mappingFileName = "proguard_map.txt";
/**
* Sets the name of the ProGuard seed file.
*
* @parameter default-value="proguard_seed.txt"
*/
protected String seedFileName = "proguard_seeds.txt";
/**
* ProGuard configuration options
*
* @parameter
*/
private String[] options;
File inFile,outFile,jarFile,proguardJarFile;
/**
* 标记war是否被修改过
*/
protected boolean modifyWar = false;
/**
* 混淆命令集合
*/
List args = new ArrayList();
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
log = getLog();
if (skip) {
log.info("Bypass ProGuard processing because \"proguard.skip=true\"");
return;
}
jarFile = new File(outputDirectory,
mavenProject.getArtifactId() + "-" + mavenProject.getVersion() + ".jar");
proguardJarFile = new File(outputDirectory,
FilenameUtils.getBaseName(jarFile.getName()) + "_proguard.jar");
checkInOut();
buildInJars();
buildOutJars();
modifyWarStructure();
buildLibraryJars();
buildOptions();
executeProGuard();
restoreWarStructure();
}
/**
* 执行混淆
*/
protected void executeProGuard(){
log.info("-------------ProGuard Config----------------");
String cmd = StringUtils.join(args,System.lineSeparator());
System.out.println(cmd);
if (args.size() == 0) {
System.out.println(ProGuard.VERSION);
System.out.println("Usage: java proguard.ProGuard [options ...]");
System.exit(1);
}
// Create the default options.
Configuration configuration = new Configuration();
try {
// Parse the options specified in the command line arguments.
ConfigurationParser parser = new ConfigurationParser(args.toArray(new String[args.size()]),
System.getProperties());
try {
parser.parse(configuration);
} finally {
parser.close();
}
// Execute ProGuard with these options.
new ProGuard(configuration).execute();
} catch (Exception ex) {
if (configuration.verbose) {
// Print a verbose stack trace.
ex.printStackTrace();
} else {
// Print just the stack trace message.
System.err.println("Error: " + ex.getMessage());
}
System.exit(1);
}
}
/**
* 构造其他参数信息
*/
protected void buildOptions(){
if (!obfuscate) {
args.add("-dontobfuscate");
}
if (proguardInclude != null) {
if (proguardInclude.exists()) {
args.add("-include");
args.add(fileToString(proguardInclude));
log.debug("proguardInclude " + proguardInclude);
} else {
log.debug("proguardInclude config does not exists " + proguardInclude);
}
}
args.add("-printmapping");
args.add(fileToString((new File(outputDirectory, mappingFileName))));
args.add("-printseeds");
args.add(fileToString((new File(outputDirectory,seedFileName))));
if (log.isDebugEnabled()) {
args.add("-verbose");
}
if (options != null) {
Collections.addAll(args, options);
}
}
/**
* 构建输入文件的参数信息
*/
protected void buildInJars(){
StringBuilder sb = new StringBuilder("-injars ");
sb.append(fileToString(inFile));
List inFilters = new ArrayList();
if(StringUtils.isNotBlank(inFilter)){
for (String filterItem : inFilter.split(",")) {
inFilters.add(filterItem);
}
}
if(!addMavenDescriptor){
inFilters.add("!META-INF/maven/**");
}
if(inFilters.size()>0){
sb.append("(");
sb.append(StringUtils.join(inFilters, ","));
sb.append(")");
}
args.add(sb.toString());
}
/**
* 构建输出文件的参数信息
*/
protected void buildOutJars(){
StringBuilder sb = new StringBuilder("-outjars ");
sb.append(fileToString(outFile));
if(StringUtils.isNotBlank(outFilter)){
sb.append("(").append(outFilter).append(")");
}
args.add(sb.toString());
}
/**
* 是否是war文件
* @return true|false
*/
protected boolean mainIsWar(){
return mavenProject.getPackaging().equals("war");
}
/**
* 如果输入的文件是一个war包,对其进行改造
* @throws MojoExecutionException
*/
protected void modifyWarStructure() throws MojoExecutionException{
if (mainIsWar()) {
File warDir = new File(outputDirectory,finalName);
File webInfDir = new File(warDir,WEB_INF);
File classesDir = new File(webInfDir,"classes");
if(classesDir.list().length>0){
modifyWar = true;
File jarFile = new File(outputDirectory,
mavenProject.getArtifactId() + "-" + mavenProject.getVersion() + ".jar");
if (!jarFile.exists()) {
try {
JarArchiver jarArchiver = new JarArchiver();
jarArchiver.addDirectory(classesDir);
jarArchiver.setDestFile(jarFile);
jarArchiver.createArchive();
} catch (Exception e) {
throw new MojoExecutionException("Error assembling JAR", e);
}
}
try {
ZipFile zipFile = new ZipFile(inFile);
List removeList = new ArrayList();
for (Object item : zipFile.getFileHeaders()) {
FileHeader fileHeader = (FileHeader) item;
if(fileHeader.getFileName().startsWith("WEB-INF/classes")){
removeList.add(fileHeader);
}
}
for (FileHeader fileHeader : removeList) {
zipFile.removeFile(fileHeader);
}
} catch (ZipException e) {
throw new MojoExecutionException("Error remove WAR classes", e);
}
args.add("-injars " + fileToString(jarFile));
args.add("-outjars " + fileToString(proguardJarFile));
}
}
}
/**
* 如果输入的文件是一个war包,对原始war其进行恢复,混淆的war进行完善
* @throws MojoExecutionException
*/
protected void restoreWarStructure() throws MojoExecutionException{
if (mainIsWar()) {
try {
ZipParameters parameters = new ZipParameters();
parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_FASTEST);
if(!sameArtifact){
ZipFile zipFile = new ZipFile(inFile);
File warDirectory = new File(outputDirectory,finalName);
File webInfDirectory = new File(warDirectory,WEB_INF);
zipFile.addFolder(webInfDirectory, parameters);
}else{
inFile.delete();
}
if(modifyWar){
ZipFile zipFile = new ZipFile(proguardJarFile);
File proguardClassesDir = new File(outputDirectory,"proguard-classes");
File webInfDir=new File(proguardClassesDir,WEB_INF);
File destPath = new File(webInfDir,"classes");
destPath.mkdirs();
for (Object item : zipFile.getFileHeaders()) {
FileHeader fileHeader = (FileHeader)item;
if(!fileHeader.getFileName().startsWith("META-INF")){
zipFile.extractFile(fileHeader, destPath.getAbsolutePath());
}
}
proguardJarFile.delete();
zipFile = new ZipFile(outFile);
zipFile.addFolder(webInfDir, parameters);
}
} catch (ZipException e) {
throw new MojoExecutionException("Error restore WAR classes", e);
}
}
}
/**
* 构建依赖库参数信息
* @throws MojoExecutionException
*/
protected void buildLibraryJars() throws MojoExecutionException {
Set artifacts = mavenProject.getArtifacts();
for (Artifact artifact : artifacts) {
log.debug("--- artifact " + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
+ artifact.getType() + ":" + artifact.getClassifier() + " Scope:" + artifact.getScope());
File file = getClasspathElement(artifact, mavenProject);
args.add("-libraryjars " + fileToString(file));
}
if (libs != null) {
for (String lib : libs) {
args.add("-libraryjars " + fileNameToString(lib));
}
}
}
/**
* 检查输入输出环境是否满足条件
* @throws MojoFailureException
*/
protected void checkInOut() throws MojoFailureException{
if(StringUtils.isBlank(injar)){
injar = finalName + "." + mavenProject.getPackaging();
}
inFile = new File(outputDirectory, injar);
if (!inFile.exists()) {
throw new MojoFailureException("Can't find file " + inFile);
}
if (!outputDirectory.exists()) {
if (!outputDirectory.mkdirs()) {
throw new MojoFailureException("Can't create " + outputDirectory);
}
}
if (attach) {
outjar = nameNoType(injar);
if (useArtifactClassifier()) {
outjar += "-" + attachArtifactClassifier;
}
outjar += "." + attachArtifactType;
}
if ((outjar != null) && (!outjar.equals(injar))) {
sameArtifact = false;
outFile = (new File(outputDirectory, outjar)).getAbsoluteFile();
if (outFile.exists()) {
if (!deleteFileOrDirectory(outFile)) {
throw new MojoFailureException("Can't delete " + outFile);
}
}
} else {
sameArtifact = true;
outFile = inFile.getAbsoluteFile();
File baseFile;
if (inFile.isDirectory()) {
baseFile = new File(outputDirectory, nameNoType(injar) + "_proguard_base");
} else {
baseFile = new File(outputDirectory, nameNoType(injar) + "_proguard_base." + mavenProject.getPackaging());
}
if (baseFile.exists()) {
if (!deleteFileOrDirectory(baseFile)) {
throw new MojoFailureException("Can't delete " + baseFile);
}
}
if (inFile.exists()) {
if (!inFile.renameTo(baseFile)) {
throw new MojoFailureException("Can't rename " + inFile);
}
}
inFile = baseFile;
}
}
private boolean useArtifactClassifier() {
return appendClassifier && ((attachArtifactClassifier != null) && (attachArtifactClassifier.length() > 0));
}
private static boolean deleteFileOrDirectory(File path) throws MojoFailureException {
if (path.isDirectory()) {
File[] files = path.listFiles();
if (null != files) {
for (File file : files) {
if (file.isDirectory()) {
if (!deleteFileOrDirectory(file)) {
throw new MojoFailureException("Can't delete dir " + file);
}
} else {
if (!file.delete()) {
throw new MojoFailureException("Can't delete file " + file);
}
}
}
}
return path.delete();
} else {
return path.delete();
}
}
private static String nameNoType(String fileName) {
int extStart = fileName.lastIndexOf('.');
if (extStart == -1) {
return fileName;
}
return fileName.substring(0, extStart);
}
/**
* ProGuard docs: Names with special characters like spaces and parentheses must be quoted with single or double
* quotes.
*/
private static String fileNameToString(String fileName) {
return "'" + fileName + "'";
}
private static String fileToString(File file) {
return fileNameToString(file.toString());
}
private static File getClasspathElement(Artifact artifact, MavenProject mavenProject) throws MojoExecutionException {
if (artifact.getClassifier() != null) {
return artifact.getFile();
}
String refId = artifact.getGroupId() + ":" + artifact.getArtifactId();
MavenProject project = (MavenProject) mavenProject.getProjectReferences().get(refId);
if (project != null) {
return new File(project.getBuild().getOutputDirectory());
} else {
File file = artifact.getFile();
if ((file == null) || (!file.exists())) {
throw new MojoExecutionException("Dependency Resolution Required " + artifact);
}
return file;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy