View Javadoc
1   
2   package com.simpligility.maven.plugins.android.phase04processclasses;
3   
4   import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
5   import com.simpligility.maven.plugins.android.CommandExecutor;
6   import com.simpligility.maven.plugins.android.ExecutionException;
7   import com.simpligility.maven.plugins.android.IncludeExcludeSet;
8   import com.simpligility.maven.plugins.android.config.ConfigHandler;
9   import com.simpligility.maven.plugins.android.config.ConfigPojo;
10  import com.simpligility.maven.plugins.android.config.PullParameter;
11  import com.simpligility.maven.plugins.android.configuration.Proguard;
12  
13  import org.apache.commons.io.IOUtils;
14  import org.apache.commons.lang3.SystemUtils;
15  import org.apache.maven.artifact.Artifact;
16  import org.apache.maven.plugin.MojoExecutionException;
17  import org.apache.maven.plugin.MojoFailureException;
18  import org.apache.maven.plugins.annotations.LifecyclePhase;
19  import org.apache.maven.plugins.annotations.Mojo;
20  import org.apache.maven.plugins.annotations.Parameter;
21  import org.apache.maven.plugins.annotations.ResolutionScope;
22  import org.codehaus.plexus.interpolation.os.Os;
23  
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.LinkedList;
33  import java.util.List;
34  
35  import static com.simpligility.maven.plugins.android.InclusionExclusionResolver.filterArtifacts;
36  import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
37  
38  /**
39   * Processes both application and dependency classes using the ProGuard byte code obfuscator,
40   * minimzer, and optimizer. For more information, see https://proguard.sourceforge.net.
41   *
42   * @author Jonson
43   * @author Matthias Kaeppler
44   * @author Manfred Moser
45   * @author Michal Harakal
46   */
47  @Mojo(
48          name = "proguard",
49          defaultPhase = LifecyclePhase.PROCESS_CLASSES,
50          requiresDependencyResolution = ResolutionScope.COMPILE
51  )
52  public class ProguardMojo extends AbstractAndroidMojo
53  {
54  
55      /**
56       * <p>
57       * ProGuard configuration. ProGuard is disabled by default. Set the skip parameter to false to activate proguard.
58       * A complete configuration can include any of the following:
59       * </p>
60       * 
61       * <pre>
62       * &lt;proguard&gt;
63       *    &lt;skip&gt;true|false&lt;/skip&gt;
64       *    &lt;config&gt;proguard.cfg&lt;/config&gt;
65       *    &lt;configs&gt;
66       *      &lt;config&gt;${env.ANDROID_HOME}/tools/proguard/proguard-android.txt&lt;/config&gt;
67       *    &lt;/configs&gt;
68       *    &lt;proguardJarPath&gt;someAbsolutePathToProguardJar&lt;/proguardJarPath&gt;
69       *    &lt;filterMavenDescriptor&gt;true|false&lt;/filterMavenDescriptor&gt;
70       *    &lt;filterManifest&gt;true|false&lt;/filterManifest&gt;
71       *    &lt;customFilter&gt;filter1,filter2&lt;/customFilter&gt;
72       *    &lt;jvmArguments&gt;
73       *     &lt;jvmArgument&gt;-Xms256m&lt;/jvmArgument&gt;
74       *     &lt;jvmArgument&gt;-Xmx512m&lt;/jvmArgument&gt;
75       *   &lt;/jvmArguments&gt;
76       * &lt;/proguard&gt;
77       * </pre>
78       * <p>
79       * A good practice is to create a release profile in your POM, in which you enable ProGuard.
80       * ProGuard should be disabled for development builds, since it obfuscates class and field
81       * names, and it may interfere with test projects that rely on your application classes.
82       * All parameters can be overridden in profiles or the the proguard* properties. Default values apply and are
83       * documented with these properties.
84       * </p>
85       */
86      @ConfigPojo
87      @Parameter
88      protected Proguard proguard;
89  
90      /**
91       * Whether ProGuard is enabled or not. Defaults to true.
92       */
93      @Parameter( property = "android.proguard.skip" )
94      private Boolean proguardSkip;
95  
96      @PullParameter( defaultValue = "true" )
97      private Boolean parsedSkip;
98  
99      @PullParameter( defaultValue = "${project.basedir}/proguard.cfg" )
100     private File parsedConfig;
101 
102     @PullParameter( defaultValueGetterMethod = "getDefaultProguardConfigs" )
103     private String[] parsedConfigs;
104 
105     /**
106      * Additional ProGuard options
107      */
108     @Parameter( property = "android.proguard.options" )
109     private String[] proguardOptions;
110 
111     @PullParameter( defaultValueGetterMethod = "getDefaultProguardOptions" )
112     private String[] parsedOptions;
113 
114     /**
115      * Path to the proguard jar and therefore version of proguard to be used. By default this will use Proguard
116      * from the Central Repository. Overriding it with an absolute path allows you to use a newer or custom proguard
117      * version..
118      * 
119      * You can also reference an external Proguard version as a plugin dependency like this:
120      * <pre>
121      * &lt;plugin&gt;
122      *   &lt;groupId&gt;com.jayway.maven.plugins.android.generation2&lt;/groupId&gt;
123      *   &lt;artifactId&gt;android-maven-plugin&lt;/artifactId&gt;
124      *     &lt;dependencies&gt;
125      *       &lt;dependency&gt;
126      *         &lt;groupId&gt;net.sf.proguard&lt;/groupId&gt;
127      *         &lt;artifactId&gt;proguard-base&lt;/artifactId&gt;
128      *         &lt;version&gt;4.7&lt;/version&gt;
129      *       &lt;/dependency&gt;
130      *     &lt;/dependencies&gt;
131      * </pre>
132      * 
133      * which will download and use Proguard 4.7 as deployed to the Central Repository.
134      */
135     @Parameter( property = "android.proguard.proguardJarPath" )
136     private String proguardProguardJarPath;
137 
138     @PullParameter( defaultValueGetterMethod = "getProguardJarPath" )
139     private String parsedProguardJarPath;
140 
141     /**
142      * Path relative to the project's build directory (target) where proguard puts folowing files:
143      * 
144      * <ul>
145      *   <li>dump.txt</li>
146      *   <li>seeds.txt</li>
147      *   <li>usage.txt</li>
148      *   <li>mapping.txt</li>
149      * </ul>
150      * 
151      * You can define the directory like this:
152      * <pre>
153      * &lt;proguard&gt;
154      *   &lt;skip&gt;false&lt;/skip&gt;
155      *   &lt;config&gt;proguard.cfg&lt;/config&gt;
156      *   &lt;outputDirectory&gt;my_proguard&lt;/outputDirectory&gt;
157      * &lt;/proguard&gt;
158      * </pre>
159      * 
160      * Output directory is defined relatively so it could be also outside of the target directory.
161      * 
162      */
163     @Parameter( property = "android.proguard.outputDirectory" )
164     private File outputDirectory;
165 
166     @PullParameter( defaultValue = "${project.build.directory}/proguard" )
167     private File parsedOutputDirectory;
168 
169     @Parameter(
170             property = "android.proguard.obfuscatedJar",
171             defaultValue = "${project.build.directory}/${project.build.finalName}_obfuscated.jar"
172     )
173     private String obfuscatedJar;
174 
175     /**
176      * Extra JVM Arguments. Using these you can e.g. increase memory for the jvm running the build.
177      * Defaults to "-Xmx512M".
178      */
179     @Parameter( property = "android.proguard.jvmArguments" )
180     private String[] proguardJvmArguments;
181 
182     @PullParameter( defaultValueGetterMethod = "getDefaultJvmArguments" )
183     private String[] parsedJvmArguments;
184 
185     /**
186      * If set to true will add a filter to remove META-INF/maven/* files. Defaults to false.
187      */
188     @Parameter( property = "android.proguard.filterMavenDescriptor" )
189     private Boolean proguardFilterMavenDescriptor;
190 
191     @PullParameter( defaultValue = "true" )
192     private Boolean parsedFilterMavenDescriptor;
193 
194     /**
195      * If set to true will add a filter to remove META-INF/MANIFEST.MF files.  Defaults to false.
196      */
197     @Parameter( property = "android.proguard.filterManifest" )
198     private Boolean proguardFilterManifest;
199 
200     @PullParameter( defaultValue = "true" )
201     private Boolean parsedFilterManifest;
202 
203     /**
204      * You can specify a custom filter which will be used to filter out unnecessary files from ProGuard input.
205      *
206      * @see http://proguard.sourceforge.net/manual/usage.html#filefilters
207      */
208     @Parameter( property = "android.proguard.customfilter" )
209     private String proguardCustomFilter;
210 
211     @PullParameter
212     private String parsedCustomFilter;
213 
214     /**
215      * If set to true JDK jars will be included as library jars and corresponding filters
216      * will be applied to android.jar.
217      */
218     @Parameter( property = "android.proguard.includeJdkLibs" )
219     private Boolean includeJdkLibs;
220 
221     @PullParameter( defaultValue = "true" )
222     private Boolean parsedIncludeJdkLibs;
223 
224     /**
225      * If set to true the mapping.txt file will be attached as artifact of type <code>map</code>
226      */
227     @Parameter( property = "android.proguard.attachMap" )
228     private Boolean attachMap;
229 
230     @PullParameter( defaultValue = "false" )
231     private Boolean parsedAttachMap;
232 
233     /**
234      * The plugin dependencies.
235      */
236     @Parameter( defaultValue = "${plugin.artifacts}", required = true, readonly = true )
237     protected List< Artifact > pluginDependencies;
238 
239     /**
240      * Skips transitive dependencies. May be useful if the target classes directory is populated with the
241      * {@code maven-dependency-plugin} and already contains all dependency classes.
242      */
243     @Parameter( property = "skipDependencies", defaultValue = "false" )
244     private boolean skipDependencies;
245 
246     /**
247      * Allows to include or exclude artifacts by type. The {@code include} parameter has higher priority than the
248      * {@code exclude} parameter. These two parameters can be overridden by the {@code artifactSet} parameter. Empty
249      * strings are ignored. Example:
250      * <pre>
251      *     &lt;artifactTypeSet&gt;
252      *         &lt;includes&gt;
253      *             &lt;include&gt;aar&lt;/include&gt;
254      *         &lt;includes&gt;
255      *         &lt;excludes&gt;
256      *             &lt;exclude&gt;jar&lt;/exclude&gt;
257      *         &lt;excludes&gt;
258      *     &lt;/artifactTypeSet&gt;
259      * </pre>
260      */
261     @Parameter( property = "artifactTypeSet" )
262     private IncludeExcludeSet artifactTypeSet;
263 
264     /**
265      * Allows to include or exclude artifacts by {@code groupId}, {@code artifactId}, and {@code versionId}. The
266      * {@code include} parameter has higher priority than the {@code exclude} parameter. These two parameters can
267      * override the {@code artifactTypeSet} and {@code skipDependencies} parameters. Artifact {@code groupId},
268      * {@code artifactId}, and {@code versionId} are specified by a string with the respective values separated using
269      * a colon character {@code :}. {@code artifactId} and {@code versionId} can be optional covering an artifact
270      * range. Empty strings are ignored. Example:
271      * <pre>
272      *     &lt;artifactTypeSet&gt;
273      *         &lt;includes&gt;
274      *             &lt;include&gt;foo-group:foo-artifact:1.0-SNAPSHOT&lt;/include&gt;
275      *             &lt;include&gt;bar-group:bar-artifact:1.0-SNAPSHOT&lt;/include&gt;
276      *             &lt;include&gt;baz-group:*&lt;/include&gt;
277      *         &lt;includes&gt;
278      *         &lt;excludes&gt;
279      *             &lt;exclude&gt;qux-group:qux-artifact:*&lt;/exclude&gt;
280      *         &lt;excludes&gt;
281      *     &lt;/artifactTypeSet&gt;
282      * </pre>
283      */
284     @Parameter( property = "artifactSet" )
285     private IncludeExcludeSet artifactSet;
286 
287     private static final Collection< String > ANDROID_LIBRARY_EXCLUDED_FILTER = Arrays
288         .asList( "org/xml/**", "org/w3c/**", "java/**", "javax/**" );
289 
290     private static final Collection< String > MAVEN_DESCRIPTOR = Arrays.asList( "META-INF/maven/**" );
291 
292     private static final Collection< String > META_INF_MANIFEST = Arrays.asList( "META-INF/MANIFEST.MF" );
293 
294     /**
295      * For Proguard is required only jar type dependencies, all other like .so or .apklib can be skipped.
296      */
297     private static final String JAR_DEPENDENCY_TYPE = "jar";
298 
299     private static class ArtifactPrototype
300     {
301         private final String groupId;
302         private final String artifactId;
303 
304         private ArtifactPrototype( String groupId, String artifactId )
305         {
306             this.groupId = groupId;
307             this.artifactId = artifactId;
308         }
309     }
310 
311     private List< ArtifactPrototype > artifactBlacklist = new LinkedList< ArtifactPrototype>();
312 
313     private List< ArtifactPrototype > artifactsToShift = new LinkedList< ArtifactPrototype>();
314 
315     private File javaHomeDir;
316 
317     private File javaLibDir;
318 
319     private File altJavaLibDir;
320 
321     private static class ProGuardInput
322     {
323 
324         private String path;
325 
326         private Collection< String > excludedFilter;
327 
328         ProGuardInput( String path, Collection< String > excludedFilter )
329         {
330             this.path = path;
331             this.excludedFilter = excludedFilter;
332         }
333 
334         public String toPath()
335         {
336             if ( excludedFilter != null && !excludedFilter.isEmpty() )
337             {
338                 String middleQuote, endQuote;
339 
340                 if ( !Os.isFamily( Os.FAMILY_WINDOWS ) )
341                 {
342                     middleQuote = "(";
343                     endQuote = ")";
344                 }
345                 else
346                 {
347                     middleQuote = "(";
348                     endQuote = ")";
349                 }
350 
351                 StringBuilder sb = new StringBuilder();
352                 sb.append( path );
353                 sb.append( middleQuote );
354                 for ( Iterator< String > it = excludedFilter.iterator(); it.hasNext(); )
355                 {
356                     sb.append( '!' ).append( it.next() );
357                     if ( it.hasNext() )
358                     {
359                         sb.append( ',' );
360                     }
361                 }
362                 sb.append( endQuote );
363                 return sb.toString();
364             }
365             else
366             {
367                 return path;
368             }
369         }
370 
371         @Override
372         public String toString()
373         {
374             return "ProGuardInput{"
375                     + "path='" + path + '\''
376                     + ", excludedFilter=" + excludedFilter
377                     + '}';
378         }
379     }
380 
381     @Override
382     public void execute() throws MojoExecutionException, MojoFailureException
383     {
384         if ( getJack().isEnabled() ) 
385         {
386             //proguard is handled by Jack
387             return;
388         }
389         
390         ConfigHandler configHandler = new ConfigHandler( this, this.session, this.execution );
391         configHandler.parseConfiguration();
392 
393         if ( !parsedSkip )
394         {
395             if ( parsedConfig.exists() )
396             {
397                 // TODO: make the property name a constant sometime after switching to @Mojo
398                 project.getProperties().setProperty( "android.proguard.obfuscatedJar", obfuscatedJar );
399 
400                 executeProguard();
401             }
402             else
403             {
404                 getLog().info( String
405                     .format( "Proguard skipped because the configuration file doesn't exist: %s", parsedConfig ) );
406             }
407         }
408     }
409 
410     private void executeProguard() throws MojoExecutionException
411     {
412         final File proguardDir = this.parsedOutputDirectory;
413 
414         if ( !proguardDir.exists() && !proguardDir.mkdir() )
415         {
416             throw new MojoExecutionException( "Cannot create proguard output directory" );
417         }
418         else
419         {
420             if ( proguardDir.exists() && !proguardDir.isDirectory() )
421             {
422                 throw new MojoExecutionException( "Non-directory exists at " + proguardDir.getAbsolutePath() );
423             }
424         }
425 
426         getLog().info( "Proguarding output" );
427         CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
428         executor.setLogger( this.getLog() );
429         List< String > commands = new ArrayList< String >();
430 
431         collectJvmArguments( commands );
432 
433         commands.add( "-jar" );
434         commands.add( parsedProguardJarPath );
435 
436         List<String> proguardCommands = new ArrayList<String>();
437 
438         proguardCommands.add( "@" + parsedConfig + "" );
439 
440         for ( String config : parsedConfigs )
441         {
442             proguardCommands.add( "@" + config );
443         }
444 
445         if ( proguardFile != null )
446         {
447             proguardCommands.add( "@" + proguardFile.getAbsolutePath() );
448         }
449 
450         for ( Artifact artifact : getTransitiveDependencyArtifacts( AAR ) )
451         {
452             File unpackedLibFolder = getUnpackedLibFolder( artifact );
453             File proguardFile = new File( unpackedLibFolder, "proguard.txt" );
454             if ( proguardFile.exists() )
455             {
456                 proguardCommands.add( "@" + proguardFile.getAbsolutePath() );
457             }
458         }
459 
460         collectInputFiles( proguardCommands );
461 
462         proguardCommands.add( "-outjars" );
463         proguardCommands.add( obfuscatedJar );
464 
465         proguardCommands.add( "-dump" );
466         proguardCommands.add( proguardDir + File.separator + "dump.txt" );
467         proguardCommands.add( "-printseeds" );
468         proguardCommands.add( proguardDir + File.separator + "seeds.txt" );
469         proguardCommands.add( "-printusage" );
470         proguardCommands.add( proguardDir + File.separator + "usage.txt" );
471 
472         File mapFile = new File( proguardDir, "mapping.txt" );
473 
474         proguardCommands.add( "-printmapping" );
475         proguardCommands.add( mapFile.toString() );
476 
477         proguardCommands.addAll( Arrays.asList( parsedOptions ) );
478 
479         final String javaExecutable = getJavaExecutable().getAbsolutePath();
480 
481         getLog().debug( javaExecutable + " " + commands.toString() + proguardCommands.toString() );
482 
483         FileOutputStream tempConfigFileOutputStream = null;
484         try
485         {
486             File tempConfigFile = new File ( proguardDir , "temp_config.cfg" );
487 
488             StringBuilder commandStringBuilder = new StringBuilder();
489             for ( String command : proguardCommands )
490             {
491                 commandStringBuilder.append( command );
492                 commandStringBuilder.append( SystemUtils.LINE_SEPARATOR );
493             }
494             tempConfigFileOutputStream = new FileOutputStream( tempConfigFile );
495             IOUtils.write( commandStringBuilder, tempConfigFileOutputStream );
496 
497             executor.setCaptureStdOut( true );
498             commands.add( "@" + tempConfigFile.getAbsolutePath() + "" );
499             executor.executeCommand( javaExecutable, commands, project.getBasedir(), false );
500         }
501         catch ( ExecutionException e )
502         {
503             throw new MojoExecutionException( "", e );
504         }
505         catch ( IOException e )
506         {
507             throw new MojoExecutionException( "Error writing proguard commands to temporary file", e );
508         }
509         finally
510         {
511             IOUtils.closeQuietly( tempConfigFileOutputStream );
512         }
513 
514         if ( parsedAttachMap )
515         {
516             projectHelper.attachArtifact( project, "map", mapFile );
517         }
518     }
519 
520     /**
521      * Convert the jvm arguments in parsedJvmArguments as populated by the config in format as needed by the java
522      * command. Also preserve backwards compatibility in terms of dashes required or not..
523      */
524     private void collectJvmArguments( List< String > commands )
525     {
526         if ( parsedJvmArguments != null )
527         {
528             for ( String jvmArgument : parsedJvmArguments )
529             {
530                 // preserve backward compatibility allowing argument with or without dash (e.g.
531                 // Xmx512m as well as -Xmx512m should work) (see
532                 // http://code.google.com/p/maven-android-plugin/issues/detail?id=153)
533                 if ( !jvmArgument.startsWith( "-" ) )
534                 {
535                     jvmArgument = "-" + jvmArgument;
536                 }
537                 commands.add( jvmArgument );
538             }
539         }
540     }
541 
542     private void collectInputFiles( List< String > commands ) throws MojoExecutionException
543     {
544         // commons-logging breaks everything horribly, so we skip it from the program
545         // dependencies and declare it to be a library dependency instead
546         skipArtifact( "commons-logging", "commons-logging", true );
547 
548         final List< ProGuardInput > inJars = getProgramInputFiles();
549         if ( isAPKBuild() )
550         {
551             inJars.addAll( getProjectDependencyFiles() );
552         }
553 
554         for ( final ProGuardInput injar : inJars )
555         {
556             getLog().debug( "Added injar : " + injar );
557             commands.add( "-injars" );
558             commands.add( injar.toPath() );
559         }
560 
561         final List< ProGuardInput > libraryJars = getLibraryInputFiles();
562         if ( !isAPKBuild() )
563         {
564             getLog().info( "Library project - not adding project dependencies to the obfuscated JAR" );
565             libraryJars.addAll( getProjectDependencyFiles() );
566         }
567 
568         for ( final ProGuardInput libraryjar : libraryJars )
569         {
570             getLog().debug( "Added libraryJar : " + libraryjar );
571             commands.add( "-libraryjars" );
572             commands.add( libraryjar.toPath() );
573         }
574     }
575 
576     /**
577      * Figure out the full path to the current java executable.
578      *
579      * @return the full path to the current java executable.
580      */
581     private static File getJavaExecutable()
582     {
583         final String javaHome = System.getProperty( "java.home" );
584         final String slash = File.separator;
585         return new File( javaHome + slash + "bin" + slash + "java" );
586     }
587 
588     private void skipArtifact( String groupId, String artifactId, boolean shiftToLibraries )
589             throws MojoExecutionException
590     {
591         final ArtifactPrototype artifact = new ArtifactPrototype( groupId, artifactId );
592         artifactBlacklist.add( artifact );
593         if ( shiftToLibraries )
594         {
595             artifactsToShift.add( artifact );
596         }
597     }
598 
599     private boolean isBlacklistedArtifact( Artifact artifact )
600     {
601         for ( ArtifactPrototype artifactToSkip : artifactBlacklist )
602         {
603             if ( artifactToSkip.groupId.equals( artifact.getGroupId() )
604                     && artifactToSkip.artifactId.equals( artifact.getArtifactId() ) )
605             {
606                 return true;
607             }
608         }
609         return false;
610     }
611 
612     private boolean isShiftedArtifact( Artifact artifact )
613     {
614         for ( ArtifactPrototype artifactToShift : artifactsToShift )
615         {
616             if ( artifactToShift.groupId.equals( artifact.getGroupId() )
617                     && artifactToShift.artifactId.equals( artifact.getArtifactId() ) )
618             {
619                 return true;
620             }
621         }
622         return false;
623     }
624 
625     private List< ProGuardInput > getProgramInputFiles()
626     {
627         final List< ProGuardInput > inJars = new LinkedList< ProguardMojo.ProGuardInput >();
628         inJars.add( createProguardInput( projectOutputDirectory.getAbsolutePath() ) );
629         return inJars;
630     }
631 
632     private List< ProGuardInput > getProjectDependencyFiles()
633     {
634         final Collection< String > globalInJarExcludes = new HashSet< String >();
635         final List< ProGuardInput > inJars = new LinkedList< ProguardMojo.ProGuardInput >();
636 
637         if ( parsedFilterManifest )
638         {
639             globalInJarExcludes.addAll( META_INF_MANIFEST );
640         }
641         if ( parsedFilterMavenDescriptor )
642         {
643             globalInJarExcludes.addAll( MAVEN_DESCRIPTOR );
644         }
645         if ( parsedCustomFilter != null )
646         {
647             globalInJarExcludes.addAll( Arrays.asList( parsedCustomFilter.split( "," ) ) );
648         }
649 
650         // we then add all its dependencies (incl. transitive ones), unless they're blacklisted
651         for ( Artifact artifact : filterArtifacts( getTransitiveDependencyArtifacts(), skipDependencies,
652                     artifactTypeSet.getIncludes(), artifactTypeSet.getExcludes(), artifactSet.getIncludes(),
653                     artifactSet.getExcludes() ) )
654         {
655             if ( isBlacklistedArtifact( artifact ) )
656             {
657                 getLog().debug( "Excluding (blacklisted) dependency as input jar : " + artifact );
658                 continue;
659             }
660 
661             if ( JAR_DEPENDENCY_TYPE.equals( artifact.getType() ) )
662             {
663                 getLog().debug( "Including dependency as input jar : " + artifact );
664                 inJars.add( createProguardInput( artifact.getFile().getAbsolutePath(), globalInJarExcludes ) );
665             }
666             else if ( AAR.equals( artifact.getType() ) )
667             {
668                 // The Aar classes.jar should now be automatically included
669                 // because it will be a transitive dependency. As should any jars in the libs folder.
670             }
671             else
672             {
673                 getLog().debug( "Excluding dependency as input jar : " + artifact );
674             }
675         }
676 
677         return inJars;
678     }
679 
680     private ProGuardInput createProguardInput( String path, Collection< String > filterExpression )
681     {
682         return new ProGuardInput( path, filterExpression );
683     }
684 
685     private ProGuardInput createProguardInput( String path )
686     {
687         return createProguardInput( path, null );
688     }
689 
690     private List< ProGuardInput > getLibraryInputFiles()
691     {
692         final List< ProGuardInput > libraryJars = new LinkedList< ProguardMojo.ProGuardInput >();
693 
694         if ( parsedIncludeJdkLibs )
695         {
696             // we have to add the Java framework classes to the library JARs, since they are not
697             // distributed with the JAR on Central, and since we'll strip them out of the android.jar
698             // that is shipped with the SDK (since that is not a complete Java distribution)
699             File rtJar = getJVMLibrary( "rt.jar" );
700             if ( rtJar == null )
701             {
702                 rtJar = getJVMLibrary( "classes.jar" );
703             }
704             if ( rtJar != null )
705             {
706                 libraryJars.add( createProguardInput( rtJar.getPath() ) );
707             }
708 
709             // we also need to add the JAR containing e.g. javax.servlet
710             File jsseJar = getJVMLibrary( "jsse.jar" );
711             if ( jsseJar != null )
712             {
713                 libraryJars.add( createProguardInput( jsseJar.getPath() ) );
714             }
715 
716             // and the javax.crypto stuff
717             File jceJar = getJVMLibrary( "jce.jar" );
718             if ( jceJar != null )
719             {
720                 libraryJars.add( createProguardInput( jceJar.getPath() ) );
721             }
722         }
723 
724         // we treat any dependencies with provided scope as library JARs
725         for ( Artifact artifact : project.getArtifacts() )
726         {
727             if ( artifact.getScope().equals( Artifact.SCOPE_PROVIDED ) )
728             {
729                 if ( artifact.getArtifactId().equals( "android" ) && parsedIncludeJdkLibs )
730                 {
731                     getLog().debug( "Including dependency as (android) library jar : " + artifact );
732                     libraryJars.add(
733                             createProguardInput( artifact.getFile().getAbsolutePath(), ANDROID_LIBRARY_EXCLUDED_FILTER )
734                     );
735                 }
736                 else
737                 {
738                     getLog().debug( "Including dependency as (provided) library jar : " + artifact );
739                     libraryJars.add( createProguardInput( artifact.getFile().getAbsolutePath() ) );
740                 }
741             }
742             else
743             {
744                 if ( isShiftedArtifact( artifact ) )
745                 {
746                     // this is a blacklisted artifact that should be processed as a library instead
747                     getLog().debug( "Including dependency as (shifted) library jar : " + artifact );
748                     libraryJars.add( createProguardInput( artifact.getFile().getAbsolutePath() ) );
749                 }
750                 else
751                 {
752                     getLog().debug( "Excluding dependency as library jar : " + artifact );
753                 }
754             }
755         }
756 
757         return libraryJars;
758     }
759 
760     /**
761      * Get the path to the proguard jar.
762      */
763     @SuppressWarnings( "unused" ) // NB Used to populate the parsedProguardJarPath attribute via reflection.
764     private String getProguardJarPath() throws MojoExecutionException
765     {
766         return getProguardJarPathFromDependencies();
767     }
768 
769     private String getProguardJarPathFromDependencies() throws MojoExecutionException
770     {
771         Artifact proguardArtifact = null;
772         int proguardArtifactDistance = -1;
773         for ( Artifact artifact : pluginDependencies )
774         {
775             getLog().debug( "pluginArtifact: " + artifact.getFile() );
776             if ( ( "proguard".equals( artifact.getArtifactId() ) ) || ( "proguard-base"
777                 .equals( artifact.getArtifactId() ) ) )
778             {
779                 int distance = artifact.getDependencyTrail().size();
780                 getLog().debug( "proguard DependencyTrail: " + distance );
781                 if ( proguardArtifactDistance == -1 )
782                 {
783                     proguardArtifact = artifact;
784                     proguardArtifactDistance = distance;
785                 }
786                 else
787                 {
788                     if ( distance < proguardArtifactDistance )
789                     {
790                         proguardArtifact = artifact;
791                         proguardArtifactDistance = distance;
792                     }
793                 }
794             }
795         }
796         if ( proguardArtifact != null )
797         {
798             getLog().debug( "proguardArtifact: " + proguardArtifact.getFile() );
799             return proguardArtifact.getFile().getAbsoluteFile().toString();
800         }
801         else
802         {
803             return null;
804         }
805 
806     }
807 
808     /**
809      * Get the default JVM arguments for the proguard invocation.
810      *
811      * @see #parsedJvmArguments
812      */
813     @SuppressWarnings( "unused" ) // Provides default value for parsedJvmArguments attribute
814     private String[] getDefaultJvmArguments()
815     {
816         return new String[] { "-Xmx512M" };
817     }
818 
819     /**
820      * Get the default ProGuard config files.
821      *
822      * @see #parsedConfigs
823      */
824     @SuppressWarnings( "unused" ) // Provides default value for parsedConfigs attribute
825     private String[] getDefaultProguardConfigs()
826     {
827         return new String[0];
828     }
829 
830     /**
831      * Get the default ProGuard options.
832      *
833      * @see #parsedOptions
834      */
835     @SuppressWarnings( "unused" ) // Provides default value for parsedOptions attribute
836     private String[] getDefaultProguardOptions()
837     {
838         return new String[0];
839     }
840 
841     /**
842      * Finds a library file in either the primary or alternate lib directory.
843      * @param fileName The base name of the file.
844      * @return Either a canonical filename, or {@code null} if not found.
845      */
846     private File getJVMLibrary( String fileName )
847     {
848         File libFile = new File( getJavaLibDir(), fileName );
849         if ( !libFile.exists() )
850         {
851             libFile = new File( getAltJavaLibDir(), fileName );
852             if ( !libFile.exists() )
853             {
854                 libFile = null;
855             }
856         }
857         return libFile;
858     }
859 
860     /**
861      * Determines the java.home directory.
862      * @return The java.home directory, as a File.
863      */
864     private File getJavaHomeDir()
865     {
866         if ( javaHomeDir == null )
867         {
868             javaHomeDir = new File( System.getProperty( "java.home" ) );
869         }
870         return javaHomeDir;
871     }
872 
873     /**
874      * Determines the primary JVM library location.
875      * @return The primary library directory, as a File.
876      */
877     private File getJavaLibDir()
878     {
879         if ( javaLibDir == null )
880         {
881             javaLibDir = new File( getJavaHomeDir(), "lib" );
882         }
883         return javaLibDir;
884     }
885 
886     /**
887      * Determines the alternate JVM library location (applies with older
888      * MacOSX JVMs).
889      * @return The alternate JVM library location, as a File.
890      */
891     private File getAltJavaLibDir()
892     {
893         if ( altJavaLibDir == null )
894         {
895             altJavaLibDir = new File( getJavaHomeDir().getParent(), "Classes" );
896         }
897         return altJavaLibDir;
898     }
899 }