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 }