diff --git a/README.md b/README.md index 19b41de5..f6738dd5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,94 @@ -ProGuard Maven Plugin ---------------------- +ProGuard Maven Plugin (with WAR support) +---------------------------------------- + +This project is a fork of [wvengen's proguard maven plugin](https://github.com/wvengen/proguard-maven-plugin) +with focus on easier _war_ file processing with [ProGuard]. + +If plugin's input jar is a war file, the plugin uses the following workflow: + + 1. The war file is extracted to a temporary directory + (in build directory, with _'_war_proguard_expanded'_ appended to base war file name) + 1. Artifacts referenced by assembly inclusions section (with wildcard supported for artifactId) + are used as ProGuard _injars_. All other artifacts are used as _libraryjars_. + Input artifacts files that exist in _WEB-INF/lib_, are referenced using that location + rather than location within user's maven repository. + 1. Proguarded classes and resources will be out to a jar file in _WEB-INF/lib_ named by the artifact. + Processed input jars located in _WEB-INF/lib_ are deleted. + 1. If _processWarClassesDir_ option is enabled, _WEB-INF/classes_ will be processed as _injars_ + and output separately (to replace existing _WEB-INF/classes_ directory). + 1. Finally, output war archive is created. + +Additional configuration parameters supported: + + - processWarClassesDir - if enabled, WEB-INF/classes will be processed as _injars_ + - attachMap - whether or not to attach proguard map file as an artifact + - attachMapArtifactType - defaults to _txt_ + - attachMapArtifactClassifier - defaults to _proguard-map_ + - attachSeed - whether or not to attach proguard seed file as an artifact + - attachSeedArtifactType - defaults to _txt_ + - attachSeedArtifactClassifier - defaults to _proguard-seed_ + + +### Configuration example for war + + + com.github.radomirml + proguard-maven-plugin + 2.0.9 + + + package + + proguard + + + + + 4.8 + true + 1024m + true + true + + + + + + + + + ${project.groupId}* + + + + + + commons-logging* + + + log4j* + + + ${project.build.finalName}.war + + ${project.build.directory} + true + true + proguarded + war + true + true + ${basedir}/proguard.conf + + ${java.home}/lib/rt.jar + + false + false + + + + +## Base project description Run [ProGuard] as part of your [Maven] build. For usage, please read the generated [Documentation](http://wvengen.github.io/proguard-maven-plugin/). diff --git a/pom.xml b/pom.xml index a0f6bf20..74ac63a8 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ GitHub - https://github.com/wvengen/proguard-maven-plugin/issues + https://github.com/radomirml/proguard-maven-plugin/issues @@ -49,6 +49,11 @@ cmorty morty@gmx.net + + radomirml + radomirml + radomirml@gmail.com + @@ -87,6 +92,12 @@ 3.1.1 + + org.codehaus.plexus + plexus-utils + 1.5.15 + + diff --git a/src/main/java/com/github/wvengen/maven/proguard/ProGuardMojo.java b/src/main/java/com/github/wvengen/maven/proguard/ProGuardMojo.java index 7413b15e..a2af570f 100644 --- a/src/main/java/com/github/wvengen/maven/proguard/ProGuardMojo.java +++ b/src/main/java/com/github/wvengen/maven/proguard/ProGuardMojo.java @@ -20,15 +20,6 @@ */ package com.github.wvengen.maven.proguard; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.apache.maven.archiver.MavenArchiveConfiguration; import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.artifact.Artifact; @@ -41,10 +32,18 @@ import org.apache.maven.project.MavenProjectHelper; import org.apache.tools.ant.DefaultLogger; import org.apache.tools.ant.Project; +import org.apache.tools.ant.Target; +import org.apache.tools.ant.taskdefs.Expand; import org.apache.tools.ant.taskdefs.Java; import org.codehaus.plexus.archiver.jar.JarArchiver; import org.codehaus.plexus.util.FileUtils; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.URL; +import java.util.*; + /** * *

@@ -238,6 +237,36 @@ public class ProGuardMojo extends AbstractMojo { */ private boolean appendClassifier; + /** + * Specifies attach proguard map artifact type + * + * @parameter default-value="txt" + */ + private String attachMapArtifactType = "txt"; + + /** + * Specifies attach proguard seed artifact type + * + * @parameter default-value="txt" + */ + private String attachSeedArtifactType = "txt"; + + /** + * Specifies attach artifact Classifier, ignored if attachMap=false + * + * @parameter default-value="proguard-map" + */ + private String attachMapArtifactClassifier = "proguard-map"; + + /** + * Specifies attach artifact Classifier, ignored if attachSeed=false + * + * @parameter default-value="proguard-seed" + */ + private String attachSeedArtifactClassifier = "proguard-seed"; + + + /** * Set to true to include META-INF/maven/** maven descriptord * @@ -307,6 +336,13 @@ public class ProGuardMojo extends AbstractMojo { */ protected String proguardMainClass = "proguard.ProGuard"; + /** + * Specifies whether or not to pass war's WEB-INF/classes/ directory to Proguard. + * + * @parameter default-value="true" + */ + private boolean processWarClassesDir = true; + /** * Sets the name of the ProGuard mapping file. * @@ -354,6 +390,14 @@ private boolean useArtifactClassifier() { return appendClassifier && ((attachArtifactClassifier != null) && (attachArtifactClassifier.length() > 0)); } + private boolean useMapArtifactClassifier() { + return ((attachMapArtifactClassifier != null) && (attachMapArtifactClassifier.length() > 0)); + } + + private boolean useSeedArtifactClassifier() { + return ((attachSeedArtifactClassifier != null) && (attachSeedArtifactClassifier.length() > 0)); + } + public void execute() throws MojoExecutionException, MojoFailureException { log = getLog(); @@ -375,6 +419,35 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } + // check and extract war + boolean processingWar = false; + boolean mainIsWar = mavenProject.getPackaging().equals("war"); + File expandedDir = null; + File priorityLibsDir = null; + if (mainIsWar) { + processingWar = injar.endsWith(".war"); + if (processingWar) { + expandedDir = (new File(outputDirectory, nameNoType(injar) + "_war_proguard_expanded")).getAbsoluteFile(); + if (expandedDir.exists()) { + if (!deleteFileOrDirectory(expandedDir)) { + throw new MojoFailureException("Can't delete " + expandedDir); + } + } + if (!expandedDir.mkdirs()) { + throw new MojoFailureException("Can't create " + outputDirectory); + } + + try { + unzip(inJarFile, expandedDir); + } catch (IOException e) { + throw new MojoFailureException("Can't extract " + inJarFile, e); + } + + priorityLibsDir = new File(expandedDir, "WEB-INF/lib"); + } + } + + if (!outputDirectory.exists()) { if (!outputDirectory.mkdirs()) { throw new MojoFailureException("Can't create " + outputDirectory); @@ -392,7 +465,20 @@ public void execute() throws MojoExecutionException, MojoFailureException { outjar += "." + attachArtifactType; } - if ((outjar != null) && (!outjar.equals(injar))) { + if (processingWar) { + // When processing war, we update outJarFile to point to proguarded jar + if (outjar == null) { + outjar = injar; + } + String outJarName = nameNoType(outjar) + ".jar"; + outJarFile = (new File(priorityLibsDir, outJarName)).getAbsoluteFile(); + if (outJarFile.exists()) { + if (!deleteFileOrDirectory(outJarFile)) { + throw new MojoFailureException("Can't delete " + outJarFile); + } + } + sameArtifact = (outjar != null) && (outjar.equals(injar)); + } else if ((outjar != null) && (!outjar.equals(injar))) { sameArtifact = false; outJarFile = (new File(outputDirectory, outjar)).getAbsoluteFile(); if (outJarFile.exists()) { @@ -447,66 +533,80 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } + Set inFiles = new HashSet(); + + if (processingWar) { + File classesDir = new File(expandedDir, "WEB-INF/classes"); + if (processWarClassesDir) { + File classesDirInput = new File(expandedDir, "WEB-INF/classes_input"); + classesDir.renameTo(classesDirInput); + + args.add("-injars"); + args.add(fileToString(classesDirInput)); + inFiles.add(classesDirInput); + + // class files are processes separately! + args.add("-outjars"); + args.add(fileToString(classesDir)); + + } else { + args.add("-libraryjars"); + args.add(fileToString(classesDir)); + } + } + Set inPath = new HashSet(); boolean hasInclusionLibrary = false; if (assembly != null && assembly.inclusions != null) { @SuppressWarnings("unchecked") final List inclusions = assembly.inclusions; for (Inclusion inc : inclusions) { - if (!inc.library) { - File file = getClasspathElement(getDependency(inc, mavenProject), mavenProject); - inPath.add(file.toString()); - log.debug("--- ADD injars:" + inc.artifactId); - StringBuilder filter = new StringBuilder(fileToString(file)); - filter.append("(!META-INF/MANIFEST.MF"); - if (!addMavenDescriptor) { - filter.append(","); - filter.append("!META-INF/maven/**"); - } - if (inc.filter != null) { - filter.append(",").append(inc.filter); - } - filter.append(")"); - args.add("-injars"); - args.add(filter.toString()); - } else { - hasInclusionLibrary = true; - log.debug("--- ADD libraryjars:" + inc.artifactId); - // This may not be CompileArtifacts, maven 2.0.6 bug - File file = getClasspathElement(getDependency(inc, mavenProject), mavenProject); - inPath.add(file.toString()); - if(putLibraryJarsInTempDir){ - libraryJars.add(file); + Set deps = getDependancies(inc, mavenProject); // get all matching dependencies as wildcard may have been used + for (Artifact artifact : deps) { + if (!inc.library) { + File file = getClasspathElement(artifact, mavenProject, priorityLibsDir); + if (processingWar && "*".equals(inc.artifactId) && !priorityLibsDir.equals(file.getParentFile())) { + log.debug("Wildcard matching artifact will be included as a library (as does not belong to enclosed libs): " + file); + inPath.add(file.toString()); + args.add("-libraryjars"); + args.add(fileToString(file)); + continue; + } + inPath.add(file.toString()); + log.debug("--- ADD injars:" + inc.artifactId); + StringBuffer filter = new StringBuffer(fileToString(file)); + filter.append("(!META-INF/MANIFEST.MF"); + if (!addMavenDescriptor) { + filter.append(","); + filter.append("!META-INF/maven/**"); + } + if (inc.filter != null) { + filter.append(",").append(inc.filter); + } + filter.append(")"); + inFiles.add(file); + args.add("-injars"); + args.add(filter.toString()); } else { - args.add("-libraryjars"); - args.add(fileToString(file)); + hasInclusionLibrary = true; + log.debug("--- ADD libraryjars:" + inc.artifactId); + // This may not be CompileArtifacts, maven 2.0.6 bug + File file = getClasspathElement(artifact, mavenProject, priorityLibsDir); + inPath.add(file.toString()); + if(putLibraryJarsInTempDir){ + libraryJars.add(file); + } else { + args.add("-libraryjars"); + args.add(fileToString(file)); + } } } } } - if (inJarFile.exists()) { + if (inJarFile.exists() && !processingWar) { args.add("-injars"); - StringBuilder filter = new StringBuilder(fileToString(inJarFile)); - if ((inFilter != null) || (!addMavenDescriptor)) { - filter.append("("); - boolean coma = false; - - if (!addMavenDescriptor) { - coma = true; - filter.append("!META-INF/maven/**"); - } - - if (inFilter != null) { - if (coma) { - filter.append(","); - } - filter.append(inFilter); - } - - filter.append(")"); - } - args.add(filter.toString()); + args.add(buildJarReference(inJarFile, inFilter)); } @@ -518,7 +618,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (isExclusion(artifact)) { continue; } - File file = getClasspathElement(artifact, mavenProject); + File file = getClasspathElement(artifact, mavenProject, priorityLibsDir); if (inPath.contains(file.toString())) { log.debug("--- ignore library since one in injar:" + artifact.getArtifactId()); @@ -526,6 +626,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { } if (includeDependencyInjar) { log.debug("--- ADD library as injars:" + artifact.getArtifactId()); + inFiles.add(file); args.add("-injars"); args.add(fileToString(file)); } else { @@ -542,12 +643,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (args.contains("-injars")) { args.add("-outjars"); - StringBuilder filter = new StringBuilder(fileToString(outJarFile)); - if (outFilter != null) { - filter.append("(").append(outFilter).append(")"); - } - args.add(filter.toString()); - } + args.add(buildJarReference(outJarFile, outFilter)); + } if (!obfuscate) { args.add("-dontobfuscate"); @@ -599,11 +696,13 @@ public void execute() throws MojoExecutionException, MojoFailureException { args.add(fileToString(tempLibraryjarsDir)); } + File proguardMapFile = (new File(outputDirectory, mappingFileName).getAbsoluteFile()); args.add("-printmapping"); - args.add(fileToString((new File(outputDirectory, mappingFileName).getAbsoluteFile()))); + args.add(fileToString(proguardMapFile)); + File proguardSeedFile = (new File(outputDirectory, seedFileName).getAbsoluteFile()); args.add("-printseeds"); - args.add(fileToString((new File(outputDirectory,seedFileName).getAbsoluteFile()))); + args.add(fileToString(proguardSeedFile)); if (log.isDebugEnabled()) { args.add("-verbose"); @@ -613,9 +712,24 @@ public void execute() throws MojoExecutionException, MojoFailureException { Collections.addAll(args, options); } - log.info("execute ProGuard " + args.toString()); + log.debug("Run Proguard with options" + args.toString()); proguardMain(getProguardJar(this), args, this); + if (processingWar) { + for (File f : inFiles) { + if (f.isDirectory()) { + log.info("Removing proguarded: " + f); + if (!deleteFileOrDirectory(f)) { + throw new MojoFailureException("Can't delete " + f); + } + } else if (priorityLibsDir.equals(f.getParentFile())) { + log.info("Removing proguarded: " + f); + if (!deleteFileOrDirectory(f)) { + throw new MojoFailureException("Can't delete " + f); + } + } + } + } if (!libraryJars.isEmpty()) { deleteFileOrDirectory(tempLibraryjarsDir); @@ -648,8 +762,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { for (Inclusion inc : inclusions) { if (inc.library) { File file; - Artifact artifact = getDependency(inc, mavenProject); - file = getClasspathElement(artifact, mavenProject); + Artifact artifact = getDependancy(inc, mavenProject); + file = getClasspathElement(artifact, mavenProject, priorityLibsDir); if (file.isDirectory()) { getLog().info("merge project: " + artifact.getArtifactId() + " " + file); jarArchiver.addDirectory(file); @@ -662,12 +776,37 @@ public void execute() throws MojoExecutionException, MojoFailureException { archiver.createArchive(mavenProject, archive); + // delete baseFile right away so we don't include it in war + if (!baseFile.delete()) { + throw new MojoFailureException("Can't delete " + baseFile); + } + } catch (Exception e) { throw new MojoExecutionException("Unable to create jar", e); } } + if (processingWar) { + File outputWar = new File(outputDirectory, outjar); + if (outputWar.exists()) { + if (!outputWar.delete()) { + throw new MojoFailureException("Can't delete " + outputWar); + } + } + MavenArchiver archiver = new MavenArchiver(); + archiver.setArchiver(jarArchiver); + archiver.setOutputFile(outputWar); + archive.setAddMavenDescriptor(addMavenDescriptor); + try { + jarArchiver.addDirectory(expandedDir); + archiver.createArchive(mavenProject, archive); + } catch (Exception e) { + throw new MojoExecutionException("Unable to create war", e); + } + outJarFile = outputWar; + } + if (attach) { if (!sameArtifact) { final String classifier; @@ -689,6 +828,28 @@ public void execute() throws MojoExecutionException, MojoFailureException { attachTextFile(new File(buildOutput, seedFileName), mainClassifier, "seed"); } } + + if (attachMap && attach) { + if (!proguardMapFile.exists()) { + log.warn("Cannot attach proguard map artifact as file does nto exist."); + } else if (useMapArtifactClassifier()) { + projectHelper.attachArtifact(mavenProject, attachMapArtifactType, attachMapArtifactClassifier, proguardMapFile); + } else { + throw new MojoExecutionException("Map artifact classifier cannot be empty"); +// projectHelper.attachArtifact(mavenProject, attachMapArtifactType, null, proguardMapFile); + } + } + + if (attachSeed && attach) { + if (!proguardSeedFile.exists()) { + log.warn("Cannot attach proguard seed artifact as file does nto exist."); + } else if (useSeedArtifactClassifier()) { + projectHelper.attachArtifact(mavenProject, attachSeedArtifactType, attachSeedArtifactClassifier, proguardSeedFile); + } else { + throw new MojoExecutionException("Seed artifact classifier cannot be empty"); +// projectHelper.attachArtifact(mavenProject, attachSeedArtifactType, null, proguardSeedFile); + } + } } private void attachTextFile(File theFile, String mainClassifier, String suffix) { @@ -845,8 +1006,21 @@ private boolean deleteFileOrDirectory(File path) throws MojoFailureException { } } + private static Set getDependancies(Inclusion inc, MavenProject mavenProject) throws MojoExecutionException { + @SuppressWarnings("unchecked") + Set artifacts = mavenProject.getArtifacts(); + Set dependencies = new HashSet(); + for (Artifact artifact: artifacts) { + if (inc.match(artifact)) { + dependencies.add(artifact); + } + } + if (dependencies.size() == 0) + throw new MojoExecutionException("artifactId Not found " + inc.artifactId); + return dependencies; + } - private Artifact getDependency(Inclusion inc, MavenProject mavenProject) throws MojoExecutionException { + private static Artifact getDependancy(Inclusion inc, MavenProject mavenProject) throws MojoExecutionException { @SuppressWarnings("unchecked") Set dependency = mavenProject.getArtifacts(); for (Artifact artifact : dependency) { @@ -869,7 +1043,7 @@ private boolean isExclusion(Artifact artifact) { return false; } - private File getClasspathElement(Artifact artifact, MavenProject mavenProject) throws MojoExecutionException { + private File getClasspathElement(Artifact artifact, MavenProject mavenProject, File libsDir) throws MojoExecutionException { if (artifact.getClassifier() != null) { return artifact.getFile(); } @@ -878,6 +1052,25 @@ private File getClasspathElement(Artifact artifact, MavenProject mavenProject) t if (project != null) { return new File(project.getBuild().getOutputDirectory()); } else { + + if (libsDir != null) { + final String filenameBase = artifact.getArtifactId() + "-"; + File[] artifactFilesInLib = libsDir.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + if (name.startsWith(filenameBase) && name.length() > filenameBase.length() + 4) { + // check if the first char after base name is a digit (assuming version number) + boolean digitFound = Character.isDigit(name.charAt(filenameBase.length())); + return digitFound; + } + return false; + } + }); + if (artifactFilesInLib.length > 1) + System.out.println("Warn: Found more than one library for artifact " + artifact + ": " + artifactFilesInLib); + if (artifactFilesInLib.length > 0) + return artifactFilesInLib[0]; + } + File file = artifact.getFile(); if ((file == null) || (!file.exists())) { throw new MojoExecutionException("Dependency Resolution Required " + artifact); @@ -885,4 +1078,46 @@ private File getClasspathElement(Artifact artifact, MavenProject mavenProject) t return file; } } + + /** + * Unzips archive from the given path to the given destination dir. + */ + private static void unzip(File archiveFile, File destDir) throws IOException, MojoFailureException { + final class Expander extends Expand { + public Expander() { + project = new Project(); + project.init(); + taskType = "unzip"; + taskName = "unzip"; + target = new Target(); + } + } + Expander expander = new Expander(); + expander.setSrc(archiveFile); + expander.setDest(destDir); + expander.execute(); + } + + private String buildJarReference(File jarFile, String jarFilter) { + StringBuffer filter = new StringBuffer(fileToString(jarFile)); + if ((jarFilter != null) || (!addMavenDescriptor)) { + filter.append("("); + boolean coma = false; + + if (!addMavenDescriptor) { + coma = true; + filter.append("!META-INF/maven/**"); + } + + if (jarFilter != null) { + if (coma) { + filter.append(","); + } + filter.append(jarFilter); + } + + filter.append(")"); + } + return filter.toString(); + } }