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 }