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  
40  
41  
42  
43  
44  
45  
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  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86      @ConfigPojo
87      @Parameter
88      protected Proguard proguard;
89  
90      
91  
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 
107 
108     @Parameter( property = "android.proguard.options" )
109     private String[] proguardOptions;
110 
111     @PullParameter( defaultValueGetterMethod = "getDefaultProguardOptions" )
112     private String[] parsedOptions;
113 
114     
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135     @Parameter( property = "android.proguard.proguardJarPath" )
136     private String proguardProguardJarPath;
137 
138     @PullParameter( defaultValueGetterMethod = "getProguardJarPath" )
139     private String parsedProguardJarPath;
140 
141     
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
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 
177 
178 
179     @Parameter( property = "android.proguard.jvmArguments" )
180     private String[] proguardJvmArguments;
181 
182     @PullParameter( defaultValueGetterMethod = "getDefaultJvmArguments" )
183     private String[] parsedJvmArguments;
184 
185     
186 
187 
188     @Parameter( property = "android.proguard.filterMavenDescriptor" )
189     private Boolean proguardFilterMavenDescriptor;
190 
191     @PullParameter( defaultValue = "true" )
192     private Boolean parsedFilterMavenDescriptor;
193 
194     
195 
196 
197     @Parameter( property = "android.proguard.filterManifest" )
198     private Boolean proguardFilterManifest;
199 
200     @PullParameter( defaultValue = "true" )
201     private Boolean parsedFilterManifest;
202 
203     
204 
205 
206 
207 
208     @Parameter( property = "android.proguard.customfilter" )
209     private String proguardCustomFilter;
210 
211     @PullParameter
212     private String parsedCustomFilter;
213 
214     
215 
216 
217 
218     @Parameter( property = "android.proguard.includeJdkLibs" )
219     private Boolean includeJdkLibs;
220 
221     @PullParameter( defaultValue = "true" )
222     private Boolean parsedIncludeJdkLibs;
223 
224     
225 
226 
227     @Parameter( property = "android.proguard.attachMap" )
228     private Boolean attachMap;
229 
230     @PullParameter( defaultValue = "false" )
231     private Boolean parsedAttachMap;
232 
233     
234 
235 
236     @Parameter( defaultValue = "${plugin.artifacts}", required = true, readonly = true )
237     protected List< Artifact > pluginDependencies;
238 
239     
240 
241 
242 
243     @Parameter( property = "skipDependencies", defaultValue = "false" )
244     private boolean skipDependencies;
245 
246     
247 
248 
249 
250 
251 
252 
253 
254 
255 
256 
257 
258 
259 
260 
261     @Parameter( property = "artifactTypeSet" )
262     private IncludeExcludeSet artifactTypeSet;
263 
264     
265 
266 
267 
268 
269 
270 
271 
272 
273 
274 
275 
276 
277 
278 
279 
280 
281 
282 
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 
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             
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                 
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 
522 
523 
524     private void collectJvmArguments( List< String > commands )
525     {
526         if ( parsedJvmArguments != null )
527         {
528             for ( String jvmArgument : parsedJvmArguments )
529             {
530                 
531                 
532                 
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         
545         
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 
578 
579 
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         
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                 
669                 
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             
697             
698             
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             
710             File jsseJar = getJVMLibrary( "jsse.jar" );
711             if ( jsseJar != null )
712             {
713                 libraryJars.add( createProguardInput( jsseJar.getPath() ) );
714             }
715 
716             
717             File jceJar = getJVMLibrary( "jce.jar" );
718             if ( jceJar != null )
719             {
720                 libraryJars.add( createProguardInput( jceJar.getPath() ) );
721             }
722         }
723 
724         
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                     
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 
762 
763     @SuppressWarnings( "unused" ) 
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 
810 
811 
812 
813     @SuppressWarnings( "unused" ) 
814     private String[] getDefaultJvmArguments()
815     {
816         return new String[] { "-Xmx512M" };
817     }
818 
819     
820 
821 
822 
823 
824     @SuppressWarnings( "unused" ) 
825     private String[] getDefaultProguardConfigs()
826     {
827         return new String[0];
828     }
829 
830     
831 
832 
833 
834 
835     @SuppressWarnings( "unused" ) 
836     private String[] getDefaultProguardOptions()
837     {
838         return new String[0];
839     }
840 
841     
842 
843 
844 
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 
862 
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 
875 
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 
888 
889 
890 
891     private File getAltJavaLibDir()
892     {
893         if ( altJavaLibDir == null )
894         {
895             altJavaLibDir = new File( getJavaHomeDir().getParent(), "Classes" );
896         }
897         return altJavaLibDir;
898     }
899 }