1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  package com.simpligility.maven.plugins.android.phase08preparepackage;
18  
19  import static com.simpligility.maven.plugins.android.InclusionExclusionResolver.filterArtifacts;
20  import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
21  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;
22  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  
31  import org.apache.commons.io.FileUtils;
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.maven.artifact.Artifact;
34  import org.apache.maven.model.Resource;
35  import org.apache.maven.plugin.MojoExecutionException;
36  import org.apache.maven.plugin.MojoFailureException;
37  import org.apache.maven.plugins.annotations.LifecyclePhase;
38  import org.apache.maven.plugins.annotations.Mojo;
39  import org.apache.maven.plugins.annotations.Parameter;
40  import org.apache.maven.plugins.annotations.ResolutionScope;
41  import org.codehaus.plexus.archiver.ArchiverException;
42  import org.codehaus.plexus.archiver.jar.JarArchiver;
43  import org.codehaus.plexus.archiver.util.DefaultFileSet;
44  
45  import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
46  import com.simpligility.maven.plugins.android.CommandExecutor;
47  import com.simpligility.maven.plugins.android.ExecutionException;
48  import com.simpligility.maven.plugins.android.IncludeExcludeSet;
49  import com.simpligility.maven.plugins.android.common.Const;
50  import com.simpligility.maven.plugins.android.common.ZipExtractor;
51  import com.simpligility.maven.plugins.android.configuration.Dex;
52  
53  
54  
55  
56  
57  
58  @Mojo(
59      name = "dex",
60      defaultPhase = LifecyclePhase.PREPARE_PACKAGE,
61      requiresDependencyResolution = ResolutionScope.COMPILE
62  )
63  public class DexMojo extends AbstractAndroidMojo
64  {
65  
66      
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93      
94  
95  
96      @Parameter( property = "android.dex.compiler", defaultValue = "dex" )
97      private String dexCompiler;
98  
99      @Parameter
100     private Dex dex;
101 
102     
103 
104 
105     @Parameter( property = "android.dex.jvmArguments", defaultValue = "-Xmx1024M" )
106     private String[] dexJvmArguments;
107 
108     
109 
110 
111     @Parameter( property = "android.dex.coreLibrary", defaultValue = "false" )
112     private boolean dexCoreLibrary;
113 
114     
115 
116 
117     @Parameter( property = "android.dex.noLocals", defaultValue = "false" )
118     private boolean dexNoLocals;
119 
120     
121 
122 
123     @Parameter( property = "android.dex.optimize", defaultValue = "true" )
124     private boolean dexOptimize;
125 
126     
127 
128 
129     @Parameter( property = "android.dex.predex", defaultValue = "false" )
130     private boolean dexPreDex;
131 
132     
133 
134 
135     @Parameter( property = "android.dex.forcejumbo", defaultValue = "false" )
136     private boolean dexForceJumbo;
137 
138     
139 
140 
141     @Parameter(
142         property = "android.dex.dexPreDexLibLocation",
143         defaultValue = "${project.build.directory}${file.separator}dexedLibs"
144     )
145     private String dexPreDexLibLocation;
146 
147     
148 
149 
150     @Parameter( property = "android.dex.incremental", defaultValue = "false" )
151     private boolean dexIncremental;
152 
153     
154 
155 
156     @Parameter( property = "android.proguard.obfuscatedJar" )
157     private File obfuscatedJar;
158 
159     
160 
161 
162     @Parameter( property = "android.dex.multidex", defaultValue = "false" )
163     private boolean dexMultiDex;
164 
165     
166 
167 
168     @Parameter( property = "android.dex.maindexlist" )
169     private String dexMainDexList;
170 
171     
172 
173 
174     @Parameter( property = "android.dex.minimalmaindex", defaultValue = "false" )
175     private boolean dexMinimalMainDex;
176 
177     
178 
179 
180 
181 
182 
183 
184     @Parameter( property = "android.dex.generatemaindexlist", defaultValue = "false" )
185     private boolean dexGenerateMainDexList;
186 
187     
188 
189 
190     @Parameter( property = "android.dex.dexarguments" )
191     private String dexArguments;
192 
193     
194 
195 
196 
197     @Parameter( property = "skipDependencies", defaultValue = "false" )
198     private boolean skipDependencies;
199 
200     
201 
202 
203 
204 
205 
206 
207 
208 
209 
210 
211 
212 
213 
214 
215     @Parameter( property = "artifactTypeSet" )
216     private IncludeExcludeSet artifactTypeSet;
217 
218     
219 
220 
221 
222 
223 
224 
225 
226 
227 
228 
229 
230 
231 
232 
233 
234 
235 
236 
237 
238     @Parameter( property = "artifactSet" )
239     private IncludeExcludeSet artifactSet;
240 
241     private String[] parsedJvmArguments;
242     private boolean parsedCoreLibrary;
243     private boolean parsedNoLocals;
244     private boolean parsedOptimize;
245     private boolean parsedPreDex;
246     private boolean parsedForceJumbo;
247     private String parsedPreDexLibLocation;
248     private boolean parsedIncremental;
249     private boolean parsedMultiDex;
250     private String parsedMainDexList;
251     private boolean parsedMinimalMainDex;
252     private boolean parsedGenerateMainDexList;
253     private String parsedDexArguments;
254     private DexCompiler parsedDexCompiler;
255 
256     
257 
258 
259 
260     @Override
261     public void execute() throws MojoExecutionException, MojoFailureException
262     {
263         parseConfiguration();
264 
265         getLog().debug( "DexCompiler set to " + parsedDexCompiler );
266         if ( parsedDexCompiler != DexCompiler.DEX )
267         {
268             getLog().info( "Not executing DexMojo because DEX compiler is set to " + parsedDexCompiler );
269             return;
270         }
271 
272         if ( getJack().isEnabled() )
273         {
274             
275             return;
276         }
277 
278         CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
279         executor.setLogger( getLog() );
280 
281         File outputFile;
282         if ( parsedMultiDex )
283         {
284             outputFile = targetDirectory;
285             if ( parsedGenerateMainDexList )
286             {
287                 getAndroidSdk().assertThatBuildToolsVersionIsAtLeast(
288                     "22.0.0", "generate main dex list" );
289                 File generatedMainDexClassesList = generateMainDexClassesList( executor );
290                 parsedMainDexList = generatedMainDexClassesList.getAbsolutePath();
291                 parsedMinimalMainDex = true;
292             }
293         }
294         else
295         {
296             outputFile = new File( targetDirectory, "classes.dex" );
297         }
298         if ( generateApk )
299         {
300             runDex( executor, outputFile );
301         }
302 
303         if ( attachJar )
304         {
305             File jarFile = new File( targetDirectory + File.separator
306                 + finalName + ".jar" );
307             projectHelper.attachArtifact( project, "jar", project.getArtifact().getClassifier(), jarFile );
308         }
309 
310         if ( attachSources )
311         {
312             
313             final File apksources = createApkSourcesFile();
314             projectHelper.attachArtifact( project, "apksources", apksources );
315         }
316     }
317 
318     
319 
320 
321 
322 
323     private Set< File > getDexInputFiles() throws MojoExecutionException
324     {
325         Set< File > inputs = new HashSet< File >();
326 
327         if ( obfuscatedJar != null && obfuscatedJar.exists() )
328         {
329             
330             getLog().debug( "Adding dex input (obfuscatedJar) : " + obfuscatedJar );
331             inputs.add( obfuscatedJar );
332         }
333         else
334         {
335             getLog().debug( "Using non-obfuscated input" );
336             
337             inputs.add( projectOutputDirectory );
338             getLog().debug( "Adding dex input : " + project.getBuild().getOutputDirectory() );
339             for ( Artifact artifact : filterArtifacts( getTransitiveDependencyArtifacts(), skipDependencies,
340                 artifactTypeSet.getIncludes(), artifactTypeSet.getExcludes(), artifactSet.getIncludes(),
341                 artifactSet.getExcludes() ) )
342             {
343                 if ( artifact.getType().equals( Const.ArtifactType.NATIVE_SYMBOL_OBJECT )
344                     || artifact.getType().equals( Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE ) )
345                 {
346                     
347                 }
348                 else if ( artifact.getType().equals( APKLIB ) )
349                 {
350                     
351                     
352                 }
353                 else if ( artifact.getType().equals( AAR ) )
354                 {
355                     
356                     
357                 }
358                 else if ( artifact.getType().equals( APK ) )
359                 {
360                     
361                     
362                     
363                     
364                     
365                     getLog().debug( "Extracting APK classes to target/classes : " + artifact.getArtifactId() );
366                     final File apkClassesJar = getUnpackedLibHelper().getJarFileForApk( artifact );
367                     getLog().debug( "Extracting APK : " + apkClassesJar + " to " + targetDirectory );
368                     final ZipExtractor extractor = new ZipExtractor( getLog() );
369                     extractor.extract( apkClassesJar, targetDirectory, ".class" );
370                 }
371                 else
372                 {
373                     getLog().debug( "Adding dex input : " + artifact.getFile() );
374                     inputs.add( artifact.getFile().getAbsoluteFile() );
375                 }
376             }
377         }
378 
379         return inputs;
380     }
381 
382     private void parseConfiguration()
383     {
384         
385         if ( dex != null )
386         {
387             
388             
389             
390             if ( dex.getJvmArguments() == null )
391             {
392                 parsedJvmArguments = dexJvmArguments;
393             }
394             else
395             {
396                 parsedJvmArguments = dex.getJvmArguments();
397             }
398             if ( dex.isCoreLibrary() == null )
399             {
400                 parsedCoreLibrary = dexCoreLibrary;
401             }
402             else
403             {
404                 parsedCoreLibrary = dex.isCoreLibrary();
405             }
406             if ( dex.isNoLocals() == null )
407             {
408                 parsedNoLocals = dexNoLocals;
409             }
410             else
411             {
412                 parsedNoLocals = dex.isNoLocals();
413             }
414             if ( dex.isOptimize() == null )
415             {
416                 parsedOptimize = dexOptimize;
417             }
418             else
419             {
420                 parsedOptimize = dex.isOptimize();
421             }
422             if ( dex.isPreDex() == null )
423             {
424                 parsedPreDex = dexPreDex;
425             }
426             else
427             {
428                 parsedPreDex = dex.isPreDex();
429             }
430             if ( dex.getPreDexLibLocation() == null )
431             {
432                 parsedPreDexLibLocation = dexPreDexLibLocation;
433             }
434             else
435             {
436                 parsedPreDexLibLocation = dex.getPreDexLibLocation();
437             }
438             if ( dex.isIncremental() == null )
439             {
440                 parsedIncremental = dexIncremental;
441             }
442             else
443             {
444                 parsedIncremental = dex.isIncremental();
445             }
446             if ( dex.isForceJumbo() == null )
447             {
448                 parsedForceJumbo = dexForceJumbo;
449             }
450             else
451             {
452                 parsedForceJumbo = dex.isForceJumbo();
453             }
454             if ( dex.isMultiDex() == null )
455             {
456                 parsedMultiDex = dexMultiDex;
457             }
458             else
459             {
460                 parsedMultiDex = dex.isMultiDex();
461             }
462             if ( dex.getMainDexList() == null )
463             {
464                 parsedMainDexList = dexMainDexList;
465             }
466             else
467             {
468                 parsedMainDexList = dex.getMainDexList();
469             }
470             if ( dex.isMinimalMainDex() == null )
471             {
472                 parsedMinimalMainDex = dexMinimalMainDex;
473             }
474             else
475             {
476                 parsedMinimalMainDex = dex.isMinimalMainDex();
477             }
478             if ( dex.isGenerateMainDexList() == null )
479             {
480                 parsedGenerateMainDexList = dexGenerateMainDexList;
481             }
482             else
483             {
484                 parsedGenerateMainDexList = dex.isGenerateMainDexList();
485             }
486             if ( dex.getDexArguments() == null )
487             {
488                 parsedDexArguments = dexArguments;
489             }
490             else
491             {
492                 parsedDexArguments = dex.getDexArguments();
493             }
494             parsedDexCompiler = DexCompiler.valueOfIgnoreCase( dexCompiler );
495 
496         }
497         else
498         {
499             parsedJvmArguments = dexJvmArguments;
500             parsedCoreLibrary = dexCoreLibrary;
501             parsedNoLocals = dexNoLocals;
502             parsedOptimize = dexOptimize;
503             parsedPreDex = dexPreDex;
504             parsedPreDexLibLocation = dexPreDexLibLocation;
505             parsedIncremental = dexIncremental;
506             parsedForceJumbo = dexForceJumbo;
507             parsedMultiDex = dexMultiDex;
508             parsedMainDexList = dexMainDexList;
509             parsedMinimalMainDex = dexMinimalMainDex;
510             parsedGenerateMainDexList = dexGenerateMainDexList;
511             parsedDexArguments = dexArguments;
512             parsedDexCompiler = DexCompiler.valueOfIgnoreCase( dexCompiler );
513         }
514     }
515 
516     private Set< File > preDex( CommandExecutor executor, Set< File > inputFiles ) throws MojoExecutionException
517     {
518         Set< File > filtered = new HashSet< File >();
519         getLog().info( "Pre dex-ing libraries for faster dex-ing of the final application." );
520 
521         for ( File inputFile : inputFiles )
522         {
523             if ( inputFile.getName().matches( ".*\\.jar$" ) )
524             {
525                 List< String > commands = dexDefaultCommands();
526 
527                 File predexJar = predexJarPath( inputFile );
528                 commands.add( "--output=" + predexJar.getAbsolutePath() );
529                 commands.add( inputFile.getAbsolutePath() );
530                 filtered.add( predexJar );
531 
532                 if ( !predexJar.isFile() || predexJar.lastModified() < inputFile.lastModified() )
533                 {
534                     getLog().info( "Pre-dex ing jar: " + inputFile.getAbsolutePath() );
535                     executeJava( commands, executor );
536                 }
537             }
538             else
539             {
540                 filtered.add( inputFile );
541             }
542         }
543 
544         return filtered;
545     }
546 
547     private File predexJarPath( File inputFile )
548     {
549         final File predexLibsDirectory = new File( parsedPreDexLibLocation.trim() );
550         predexLibsDirectory.mkdirs();
551         return new File( predexLibsDirectory, inputFile.getName() );
552     }
553 
554     private List< String > dexDefaultCommands() throws MojoExecutionException
555     {
556         List< String > commands = jarDefaultCommands();
557         commands.add( getAndroidSdk().getDxJarPath() );
558         commands.add( "--dex" );
559         return commands;
560     }
561 
562     private List<String> jarDefaultCommands()
563     {
564         List< String > commands = javaDefaultCommands();
565         commands.add( "-jar" );
566         return commands;
567     }
568 
569     private List<String> javaDefaultCommands()
570     {
571         List< String > commands = new ArrayList< String > ();
572         if ( parsedJvmArguments != null )
573         {
574             for ( String jvmArgument : parsedJvmArguments )
575             {
576                 
577                 
578                 
579                 
580                 if ( !jvmArgument.startsWith( "-" ) )
581                 {
582                     jvmArgument = "-" + jvmArgument;
583                 }
584                 getLog().debug( "Adding jvm argument " + jvmArgument );
585                 commands.add( jvmArgument );
586             }
587         }
588         return commands;
589     }
590 
591     private void runDex( CommandExecutor executor, File outputFile )
592         throws MojoExecutionException
593     {
594         final List< String > commands = dexDefaultCommands();
595         final Set< File > inputFiles = getDexInputFiles();
596         Set< File > filteredFiles = inputFiles;
597         if ( parsedPreDex )
598         {
599             filteredFiles = preDex( executor, inputFiles );
600         }
601         if ( !parsedOptimize )
602         {
603             commands.add( "--no-optimize" );
604         }
605         if ( parsedCoreLibrary )
606         {
607             commands.add( "--core-library" );
608         }
609         if ( parsedIncremental )
610         {
611             commands.add( "--incremental" );
612         }
613         if ( parsedNoLocals )
614         {
615             commands.add( "--no-locals" );
616         }
617         if ( parsedForceJumbo )
618         {
619             commands.add( "--force-jumbo" );
620         }
621         if ( parsedMultiDex )
622         {
623             commands.add( "--multi-dex" );
624             if ( parsedMainDexList != null )
625             {
626                 commands.add( "--main-dex-list=" + parsedMainDexList );
627             }
628             if ( parsedMinimalMainDex )
629             {
630                 commands.add( "--minimal-main-dex" );
631             }
632         }
633         if ( parsedDexArguments != null )
634         {
635             commands.add( parsedDexArguments );
636         }
637         commands.add( "--output=" + outputFile.getAbsolutePath() );
638         for ( File inputFile : filteredFiles )
639         {
640             commands.add( inputFile.getAbsolutePath() );
641         }
642 
643         getLog().info( "Convert classes to Dex : " + outputFile );
644         executeJava( commands, executor );
645     }
646 
647     private String executeJava( final List<String> commands, CommandExecutor executor ) throws MojoExecutionException
648     {
649         final String javaExecutable = getJavaExecutable().getAbsolutePath();
650         getLog().debug( javaExecutable + " " + commands.toString() );
651         try
652         {
653             executor.setCaptureStdOut( true );
654             executor.executeCommand( javaExecutable, commands, project.getBasedir(), false );
655             return executor.getStandardOut();
656         }
657         catch ( ExecutionException e )
658         {
659             throw new MojoExecutionException( "", e );
660         }
661     }
662 
663     
664 
665 
666 
667 
668     private static File getJavaExecutable()
669     {
670         final String javaHome = System.getProperty( "java.home" );
671         final String slash = File.separator;
672         return new File( javaHome + slash + "bin" + slash + "java" );
673     }
674 
675     private File generateMainDexClassesJar( CommandExecutor executor ) throws MojoExecutionException
676     {
677         List< String> commands = jarDefaultCommands();
678         commands.add( getAndroidSdk().getProguardJarPath() );
679         commands.add( "-dontnote" );
680         commands.add( "-dontwarn" );
681         commands.add( "-forceprocessing" );
682         commands.add( "-dontoptimize" );
683         commands.add( "-dontpreverify" );
684         commands.add( "-dontobfuscate" );
685 
686         Set< File> inputFiles = getDexInputFiles();
687         for ( File inputFile : inputFiles )
688         {
689             commands.add( "-injars" );
690             commands.add( inputFile.getAbsolutePath() + "(!META-INF/**)" );
691         }
692 
693         commands.add( "-libraryjars" );
694         commands.add( getAndroidSdk().getShrinkedAndroidJarPath() );
695 
696         commands.add( "-include" );
697         commands.add( getAndroidSdk().getMainDexClassesRulesPath() );
698 
699         commands.add( "-outjars" );
700         File mainDexClassesJar = new File( targetDirectory, "mainDexClasses.jar" );
701         commands.add( mainDexClassesJar.getAbsolutePath() );
702 
703         getLog().info( "Generating main dex classes jar : " + mainDexClassesJar );
704         executeJava( commands, executor );
705 
706         return mainDexClassesJar;
707     }
708 
709     private File generateMainDexClassesList( CommandExecutor executor ) throws MojoExecutionException
710     {
711         File mainDexClassesJar = generateMainDexClassesJar( executor );
712         List< String> commands = javaDefaultCommands();
713 
714         commands.add( "-Djava.ext.dirs=" + getAndroidSdk().getBuildToolsLibDirectoryPath() );
715         commands.add( "com.android.multidex.MainDexListBuilder" );
716         commands.add( mainDexClassesJar.getAbsolutePath() );
717 
718         Set< File> inputFiles = getDexInputFiles();
719         StringBuilder sb = new StringBuilder();
720         sb.append( StringUtils.join( inputFiles, File.pathSeparatorChar ) );
721         commands.add( sb.toString() );
722 
723         File mainDexClassesList = new File( targetDirectory, "mainDexClasses.txt" );
724 
725         getLog().info( "Generating main dex classes list : " + mainDexClassesList );
726 
727         String output = executeJava( commands, executor );
728         try
729         {
730             FileUtils.writeStringToFile( mainDexClassesList, output );
731         }
732         catch ( IOException ex )
733         {
734             throw new MojoExecutionException( "Failed to write command output with main dex classes list to "
735                 + mainDexClassesList, ex );
736         }
737         return mainDexClassesList;
738     }
739 
740     
741 
742 
743 
744     protected File createApkSourcesFile() throws MojoExecutionException
745     {
746         final File apksources = new File( targetDirectory, finalName
747             + ".apksources" );
748         FileUtils.deleteQuietly( apksources );
749 
750         try
751         {
752             JarArchiver jarArchiver = new JarArchiver();
753             jarArchiver.setDestFile( apksources );
754 
755             addDirectory( jarArchiver, assetsDirectory, "assets" );
756             addDirectory( jarArchiver, resourceDirectory, "res" );
757             addDirectory( jarArchiver, sourceDirectory, "src/main/java" );
758             addJavaResources( jarArchiver, resources );
759 
760             jarArchiver.createArchive();
761         }
762         catch ( ArchiverException e )
763         {
764             throw new MojoExecutionException( "ArchiverException while creating .apksource file.", e );
765         }
766         catch ( IOException e )
767         {
768             throw new MojoExecutionException( "IOException while creating .apksource file.", e );
769         }
770 
771         return apksources;
772     }
773 
774     
775 
776 
777 
778 
779 
780 
781     protected String endWithSlash( String prefix )
782     {
783         prefix = StringUtils.defaultIfEmpty( prefix, "/" );
784         if ( !prefix.endsWith( "/" ) )
785         {
786             prefix = prefix + "/";
787         }
788         return prefix;
789     }
790 
791     
792 
793 
794 
795 
796 
797 
798 
799 
800     protected void addDirectory( JarArchiver jarArchiver, File directory, String prefix )
801     {
802         if ( directory != null && directory.exists() )
803         {
804             final DefaultFileSet fileSet = new DefaultFileSet();
805             fileSet.setPrefix( endWithSlash( prefix ) );
806             fileSet.setDirectory( directory );
807             jarArchiver.addFileSet( fileSet );
808         }
809     }
810 
811     
812 
813 
814 
815     protected void addJavaResources( JarArchiver jarArchiver, List< Resource > javaResources )
816     {
817         for ( Resource javaResource : javaResources )
818         {
819             addJavaResource( jarArchiver, javaResource );
820         }
821     }
822 
823     
824 
825 
826 
827 
828 
829 
830     protected void addJavaResource( JarArchiver jarArchiver, Resource javaResource )
831     {
832         if ( javaResource != null )
833         {
834             final File javaResourceDirectory = new File( javaResource.getDirectory() );
835             if ( javaResourceDirectory.exists() )
836             {
837                 final DefaultFileSet javaResourceFileSet = new DefaultFileSet();
838                 javaResourceFileSet.setDirectory( javaResourceDirectory );
839                 javaResourceFileSet.setPrefix( endWithSlash( "src/main/resources" ) );
840                 jarArchiver.addFileSet( javaResourceFileSet );
841             }
842         }
843     }
844 }