1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.simpligility.maven.plugins.android.phase01generatesources;
18
19 import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
20 import com.simpligility.maven.plugins.android.CommandExecutor;
21 import com.simpligility.maven.plugins.android.ExecutionException;
22 import com.simpligility.maven.plugins.android.common.AaptCommandBuilder;
23 import com.simpligility.maven.plugins.android.common.AaptCommandBuilder.AaptPackageCommandBuilder;
24 import com.simpligility.maven.plugins.android.common.DependencyResolver;
25 import com.simpligility.maven.plugins.android.common.FileRetriever;
26 import com.simpligility.maven.plugins.android.configuration.BuildConfigConstant;
27 import org.apache.commons.io.FileUtils;
28 import org.apache.commons.io.IOUtils;
29 import org.apache.commons.lang3.StringUtils;
30 import org.apache.maven.artifact.Artifact;
31 import org.apache.maven.artifact.DependencyResolutionRequiredException;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugin.MojoFailureException;
34 import org.apache.maven.plugins.annotations.Component;
35 import org.apache.maven.plugins.annotations.LifecyclePhase;
36 import org.apache.maven.plugins.annotations.Mojo;
37 import org.apache.maven.plugins.annotations.Parameter;
38 import org.apache.maven.plugins.annotations.ResolutionScope;
39 import org.apache.maven.repository.RepositorySystem;
40 import org.codehaus.plexus.archiver.ArchiverException;
41 import org.codehaus.plexus.archiver.UnArchiver;
42 import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
43 import org.codehaus.plexus.logging.Logger;
44 import org.codehaus.plexus.logging.console.ConsoleLogger;
45 import org.w3c.dom.Document;
46
47 import javax.xml.parsers.DocumentBuilder;
48 import javax.xml.parsers.DocumentBuilderFactory;
49 import javax.xml.transform.OutputKeys;
50 import javax.xml.transform.Result;
51 import javax.xml.transform.Source;
52 import javax.xml.transform.Transformer;
53 import javax.xml.transform.TransformerFactory;
54 import javax.xml.transform.dom.DOMSource;
55 import javax.xml.transform.stream.StreamResult;
56 import java.io.File;
57 import java.io.FileWriter;
58 import java.io.IOException;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.jar.JarEntry;
68 import java.util.jar.JarFile;
69
70 import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
71 import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;
72 import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
73 import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKSOURCES;
74
75
76
77
78
79
80
81
82
83
84 @Mojo(
85 name = "generate-sources",
86 defaultPhase = LifecyclePhase.GENERATE_SOURCES,
87 requiresDependencyResolution = ResolutionScope.COMPILE
88 )
89 public class GenerateSourcesMojo extends AbstractAndroidMojo
90 {
91
92
93
94
95
96
97
98
99
100 @Parameter( defaultValue = "true" )
101 protected boolean warnOnApklibDependencies;
102
103
104
105
106
107
108
109
110 @Parameter( defaultValue = "true" )
111 private boolean failOnConflictingLayouts;
112
113
114
115
116
117
118
119
120 @Parameter( defaultValue = "true" )
121 private boolean failOnDuplicatePackages;
122
123
124
125
126 @Parameter(
127 property = "android.genDirectoryAidl",
128 defaultValue = "${project.build.directory}/generated-sources/aidl"
129 )
130 protected File genDirectoryAidl;
131
132
133
134
135 @Parameter( property = "android.aidlSourceDirectory", defaultValue = "${project.basedir}/src/main/aidl" )
136 protected File aidlSourceDirectory;
137
138
139
140
141 @Parameter( property = "android.buildConfigConstants" )
142 protected BuildConfigConstant[] buildConfigConstants;
143
144
145
146 @Component
147 private RepositorySystem repositorySystem;
148
149
150
151
152 @Parameter( readonly = true, defaultValue = "${project.basedir}/AndroidManifest.xml" )
153 private File androidManifestFilePre4;
154
155
156
157
158 @Parameter( readonly = true, defaultValue = "${project.basedir}/res" )
159 private File resourceDirectoryPre4;
160
161
162
163
164 @Parameter( readonly = true, defaultValue = "${project.basedir}/assets" )
165 private File assetsDirectoryPre4;
166
167
168
169
170 @Parameter( readonly = true, defaultValue = "${project.basedir}/libs" )
171 private File nativeLibrariesDirectoryPre4;
172
173
174
175
176 @Parameter( defaultValue = "true" )
177 private boolean failOnNonStandardStructure;
178
179
180
181
182 protected static final List<String> EXCLUDED_DEPENDENCY_SCOPES_FOR_EXTRACTION = Arrays.asList(
183 Artifact.SCOPE_SYSTEM, Artifact.SCOPE_IMPORT
184 );
185
186
187
188
189
190
191
192 public void execute() throws MojoExecutionException, MojoFailureException
193 {
194
195
196 if ( !isCurrentProjectAndroid() )
197 {
198 return;
199 }
200
201 validateStandardLocations();
202
203 try
204 {
205 targetDirectory.mkdirs();
206 copyManifest();
207
208
209 if ( warnOnApklibDependencies )
210 {
211 checkForApklibDependencies();
212 }
213
214
215 extractSourceDependencies();
216
217
218 extractLibraryDependencies();
219
220
221 copyFolder( assetsDirectory, combinedAssets );
222
223 final String[] relativeAidlFileNames1 = findRelativeAidlFileNames( aidlSourceDirectory );
224 final String[] relativeAidlFileNames2 = findRelativeAidlFileNames( extractedDependenciesJavaSources );
225 final Map<String, String[]> relativeApklibAidlFileNames = new HashMap<String, String[]>();
226
227 if ( !isInstrumentationTest() )
228 {
229
230 for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB ) )
231 {
232 final File libSourceFolder = getUnpackedApkLibSourceFolder( artifact );
233 final String[] apklibAidlFiles = findRelativeAidlFileNames( libSourceFolder );
234 relativeApklibAidlFileNames.put( artifact.getId(), apklibAidlFiles );
235 }
236 }
237
238 checkPackagesForDuplicates();
239 checkForConflictingLayouts();
240 generateR();
241 generateBuildConfig();
242
243
244
245
246
247 final Map<File, String[]> files = new HashMap<File, String[]>();
248 files.put( aidlSourceDirectory, relativeAidlFileNames1 );
249 files.put( extractedDependenciesJavaSources, relativeAidlFileNames2 );
250
251 if ( !isInstrumentationTest() )
252 {
253
254 for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB ) )
255 {
256 final File unpackedLibSourceFolder = getUnpackedApkLibSourceFolder( artifact );
257 files.put( unpackedLibSourceFolder, relativeApklibAidlFileNames.get( artifact.getId() ) );
258 }
259 }
260 generateAidlFiles( files );
261 }
262 catch ( MojoExecutionException e )
263 {
264 getLog().error( "Error when generating sources.", e );
265 throw e;
266 }
267 }
268
269
270
271
272
273
274
275
276 private void validateStandardLocations() throws MojoExecutionException
277 {
278 boolean hasNonStandardStructure = false;
279 if ( androidManifestFilePre4.exists() && !androidManifestFilePre4.equals( androidManifestFile ) )
280 {
281 getLog().warn( "Non-standard location of AndroidManifest.xml file found, but not configured:\n "
282 + androidManifestFilePre4 + "\nMove to the standard location src/main/AndroidManifest.xml\n"
283 + "Or configure androidManifestFile. \n" );
284 hasNonStandardStructure = true;
285 }
286 if ( resourceDirectoryPre4.exists() && !resourceDirectoryPre4.equals( resourceDirectory ) )
287 {
288 getLog().warn( "Non-standard location of Android res folder found, but not configured:\n "
289 + resourceDirectoryPre4 + "\nMove to the standard location src/main/res/\n"
290 + "Or configure resourceDirectory. \n" );
291 hasNonStandardStructure = true;
292 }
293 if ( assetsDirectoryPre4.exists() && !assetsDirectoryPre4.equals( assetsDirectory ) )
294 {
295 getLog().warn( "Non-standard location assets folder found, but not configured:\n "
296 + assetsDirectoryPre4 + "\nMove to the standard location src/main/assets/\n"
297 + "Or configure assetsDirectory. \n" );
298 hasNonStandardStructure = true;
299 }
300 if ( nativeLibrariesDirectoryPre4.exists() && !nativeLibrariesDirectoryPre4.equals( nativeLibrariesDirectory ) )
301 {
302 getLog().warn( "Non-standard location native libs folder found, but not configured:\n "
303 + nativeLibrariesDirectoryPre4 + "\nMove to the standard location src/main/libs/\n"
304 + "Or configure nativeLibrariesDirectory. \n" );
305 hasNonStandardStructure = true;
306 }
307
308 if ( hasNonStandardStructure && failOnNonStandardStructure )
309 {
310 throw new MojoExecutionException(
311 "\n\nFound files or folders in non-standard locations in the project!\n"
312 + "....This might be a side-effect of a migration to Android Maven Plugin 4+.\n"
313 + "....Please observe the warnings for specific files and folders above.\n"
314 + "....Ideally you should restructure your project.\n"
315 + "....Alternatively add explicit configuration overrides for files or folders.\n"
316 + "....Finally you could set failOnNonStandardStructure to false, potentially "
317 + "resulting in other failures.\n\n\n"
318 );
319 }
320 }
321
322
323
324
325
326
327 protected void copyManifest() throws MojoExecutionException
328 {
329 getLog().debug( "copyManifest: " + androidManifestFile + " -> " + destinationManifestFile );
330 if ( androidManifestFile == null )
331 {
332 getLog().debug( "Manifest copying disabled. Using default manifest only" );
333 return;
334 }
335
336 try
337 {
338 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
339 DocumentBuilder db = dbf.newDocumentBuilder();
340 Document doc = db.parse( androidManifestFile );
341 Source source = new DOMSource( doc );
342
343 TransformerFactory xfactory = TransformerFactory.newInstance();
344 Transformer xformer = xfactory.newTransformer();
345 xformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" );
346
347 FileWriter writer = null;
348 try
349 {
350 destinationManifestFile.getParentFile().mkdirs();
351
352 writer = new FileWriter( destinationManifestFile, false );
353 if ( doc.getXmlEncoding() != null && doc.getXmlVersion() != null )
354 {
355 String xmldecl = String.format( "<?xml version=\"%s\" encoding=\"%s\"?>%n",
356 doc.getXmlVersion(), doc.getXmlEncoding() );
357
358 writer.write( xmldecl );
359 }
360 Result result = new StreamResult( writer );
361 xformer.transform( source, result );
362 getLog().info( "Manifest copied from " + androidManifestFile + " to " + destinationManifestFile );
363 }
364 finally
365 {
366 IOUtils.closeQuietly( writer );
367 }
368 }
369 catch ( Exception e )
370 {
371 getLog().error( "Error during copyManifest" );
372 throw new MojoExecutionException( "Error during copyManifest", e );
373 }
374 }
375
376
377
378
379
380
381 protected void extractSourceDependencies() throws MojoExecutionException
382 {
383 for ( Artifact artifact : getDirectDependencyArtifacts() )
384 {
385 String type = artifact.getType();
386 if ( type.equals( APKSOURCES ) )
387 {
388 getLog().debug( "Detected apksources dependency " + artifact + " with file " + artifact.getFile()
389 + ". Will resolve and extract..." );
390
391 final File apksourcesFile = resolveArtifactToFile( artifact );
392 getLog().debug( "Extracting " + apksourcesFile + "..." );
393 extractApksources( apksourcesFile );
394 }
395 }
396
397 if ( extractedDependenciesJavaResources.exists() )
398 {
399 projectHelper.addResource( project, extractedDependenciesJavaResources.getAbsolutePath(), null, null );
400 project.addCompileSourceRoot( extractedDependenciesJavaSources.getAbsolutePath() );
401 }
402 }
403
404
405
406
407 @Deprecated
408 private void extractApksources( File apksourcesFile ) throws MojoExecutionException
409 {
410 if ( apksourcesFile.isDirectory() )
411 {
412 getLog().warn( "The apksources artifact points to '" + apksourcesFile
413 + "' which is a directory; skipping unpacking it." );
414 return;
415 }
416 final UnArchiver unArchiver = new ZipUnArchiver( apksourcesFile )
417 {
418 @Override
419 protected Logger getLogger()
420 {
421 return new ConsoleLogger( Logger.LEVEL_DEBUG, "dependencies-unarchiver" );
422 }
423 };
424 extractedDependenciesDirectory.mkdirs();
425 unArchiver.setDestDirectory( extractedDependenciesDirectory );
426 try
427 {
428 unArchiver.extract();
429 }
430 catch ( ArchiverException e )
431 {
432 throw new MojoExecutionException( "ArchiverException while extracting " + apksourcesFile.getAbsolutePath()
433 + ". Message: " + e.getLocalizedMessage(), e );
434 }
435 }
436
437 private void extractLibraryDependencies() throws MojoExecutionException
438 {
439 final Collection<Artifact> artifacts = getTransitiveDependencyArtifacts(
440 EXCLUDED_DEPENDENCY_SCOPES_FOR_EXTRACTION );
441
442 getLog().info( "Extracting libs" );
443
444
445
446 final boolean instrumentationTest = isInstrumentationTest();
447
448 for ( Artifact artifact : artifacts )
449 {
450 final String type = artifact.getType();
451 if ( type.equals( APKLIB ) && !instrumentationTest )
452 {
453 getLog().info( "Extracting apklib " + artifact.getArtifactId() + "..." );
454 extractApklib( artifact );
455 }
456 else if ( type.equals( AAR ) )
457 {
458 getLog().info( "Extracting aar " + artifact.getArtifactId() + "..." );
459 extractAarLib( artifact );
460 }
461 else if ( type.equals( APK ) )
462 {
463 getLog().info( "Extracting apk " + artifact.getArtifactId() + "..." );
464 extractApkClassesJar( artifact );
465 }
466 else
467 {
468 getLog().debug( "Not extracting " + artifact.getArtifactId() + "..." );
469 }
470 }
471 }
472
473
474
475
476 private void extractApklib( Artifact apklibArtifact ) throws MojoExecutionException
477 {
478 getUnpackedLibHelper().extractApklib( apklibArtifact );
479
480
481
482
483
484 copyFolder( getUnpackedLibAssetsFolder( apklibArtifact ), combinedAssets );
485
486 final File apklibSourceFolder = getUnpackedApkLibSourceFolder( apklibArtifact );
487 final List<String> resourceExclusions = Arrays.asList( "**/*.java", "**/*.aidl" );
488 projectHelper.addResource( project, apklibSourceFolder.getAbsolutePath(), null, resourceExclusions );
489 project.addCompileSourceRoot( apklibSourceFolder.getAbsolutePath() );
490 }
491
492
493
494
495 private void extractAarLib( Artifact aarArtifact ) throws MojoExecutionException
496 {
497 getUnpackedLibHelper().extractAarLib( aarArtifact );
498
499
500
501
502 if ( isAPKBuild() )
503 {
504 copyFolder( getUnpackedLibAssetsFolder( aarArtifact ), combinedAssets );
505 }
506
507
508
509 if ( isAPKBuild() )
510 {
511
512
513 getLog().debug( "Not adding AAR resources to resource classpath : " + aarArtifact );
514 }
515 }
516
517
518
519
520
521
522
523
524
525 private void extractApkClassesJar( Artifact artifact ) throws MojoExecutionException
526 {
527 final File apkClassesJar = getUnpackedLibHelper().getJarFileForApk( artifact );
528 final File unpackedClassesJar = getUnpackedLibHelper().getUnpackedClassesJar( artifact );
529 try
530 {
531 FileUtils.copyFile( apkClassesJar, unpackedClassesJar );
532 }
533 catch ( IOException e )
534 {
535 throw new MojoExecutionException(
536 "Could not copy APK classes jar " + apkClassesJar + " to " + unpackedClassesJar, e );
537 }
538 getLog().debug( "Copied APK classes jar into compile classpath : " + unpackedClassesJar );
539
540 }
541
542
543
544
545
546
547
548
549
550 private void checkForApklibDependencies() throws MojoExecutionException
551 {
552 final boolean isAarBuild = project.getPackaging().equals( AAR );
553
554 final DependencyResolver dependencyResolver = getDependencyResolver();
555
556 final Set<Artifact> allArtifacts = project.getArtifacts();
557 Set<Artifact> dependencyArtifacts = getArtifactResolverHelper().getFilteredArtifacts( allArtifacts );
558
559 boolean foundApklib = false;
560
561 for ( Artifact artifact : dependencyArtifacts )
562 {
563 final String type = artifact.getType();
564 if ( type.equals( APKLIB ) && isAarBuild )
565 {
566 getLog().warn( "Detected APKLIB transitive dependency: " + artifact.getId() );
567 foundApklib = true;
568 }
569 else if ( type.equals( AAR ) )
570 {
571 final Set<Artifact> dependencies = dependencyResolver
572 .getLibraryDependenciesFor( session, repositorySystem, artifact );
573 for ( Artifact dependency : dependencies )
574 {
575 if ( dependency.getType().equals( APKLIB ) )
576 {
577 getLog().warn( "Detected " + artifact.getId() + " that depends on APKLIB: "
578 + dependency.getId() );
579 foundApklib = true;
580 }
581 }
582 }
583 }
584
585 if ( foundApklib )
586 {
587 getLog().warn( "AAR libraries should not depend or include APKLIB artifacts.\n"
588 + "APKLIBs have been deprecated and the combination of the two may yield unexpected results.\n"
589 + "Check the problematic AAR libraries for newer versions that use AAR packaging." );
590 }
591 }
592
593
594
595
596
597
598
599
600 private void checkPackagesForDuplicates() throws MojoExecutionException
601 {
602 Set<Artifact> dependencyArtifacts = getTransitiveDependencyArtifacts( AAR, APKLIB );
603
604 if ( dependencyArtifacts.isEmpty() )
605 {
606
607 return;
608 }
609
610 Map<String, Set<Artifact>> packageCompareMap = getPackageCompareMap( dependencyArtifacts );
611
612 List<String> duplicatesMessageList = new ArrayList<String>();
613 for ( Map.Entry<String, Set<Artifact>> entry : packageCompareMap.entrySet() )
614 {
615 Set<Artifact> artifacts = entry.getValue();
616 if ( artifacts != null && artifacts.size() > 1 )
617 {
618 StringBuilder messageBuilder = new StringBuilder();
619 for ( Artifact item : artifacts )
620 {
621 messageBuilder
622 .append( messageBuilder.length() > 0 ? ", " : " [" )
623 .append( item.getArtifactId() );
624 }
625 messageBuilder
626 .append( "] have similar package='" )
627 .append( entry.getKey() )
628 .append( "'" );
629 duplicatesMessageList.add( messageBuilder.toString() );
630 }
631 }
632 if ( !duplicatesMessageList.isEmpty() )
633 {
634 List<String> messageList = new ArrayList<String>();
635 messageList.add( "" );
636 messageList.add( "Duplicate packages detected in AndroidManifest.xml files" );
637 messageList.add( "" );
638 messageList.add( "Such scenario generally means that the build will fail with a compilation error due to"
639 + " missing resources in R file." );
640 messageList.add( "You should consider renaming some of the duplicate packages listed below"
641 + " to avoid the conflict." );
642 messageList.add( "" );
643 messageList.add( "Conflicting artifacts:" );
644 messageList.addAll( duplicatesMessageList );
645 messageList.add( "" );
646
647 if ( failOnDuplicatePackages )
648 {
649 StringBuilder builder = new StringBuilder();
650 for ( String line : messageList )
651 {
652 builder.append( line );
653 builder.append( "\n" );
654 }
655 builder.append( "\n" );
656 builder.append( "You can downgrade the failure to a warning " );
657 builder.append( "by setting the 'failOnDuplicatePackages' plugin property to false." );
658 throw new MojoExecutionException( builder.toString() );
659 }
660 for ( String messageLine : messageList )
661 {
662 getLog().warn( messageLine );
663 }
664 }
665 }
666
667
668
669
670
671
672
673
674 private void checkForConflictingLayouts() throws MojoExecutionException
675 {
676 final ConflictingLayoutDetector detector = new ConflictingLayoutDetector();
677
678
679 final FileRetriever retriever = new FileRetriever( "layout*/*.xml" );
680 detector.addLayoutFiles( getAndroidManifestPackageName(), retriever.getFileNames( resourceDirectory ) );
681
682
683 for ( final Artifact dependency : getTransitiveDependencyArtifacts( AAR, APKLIB ) )
684 {
685 final String packageName = extractPackageNameFromAndroidArtifact( dependency );
686 final String[] layoutFiles = retriever.getFileNames( getUnpackedLibResourceFolder( dependency ) );
687 detector.addLayoutFiles( packageName, layoutFiles );
688 }
689
690 final Collection<ConflictingLayout> conflictingLayouts = detector.getConflictingLayouts();
691 getLog().debug( "checkConflictingLayouts - conflicts : " + conflictingLayouts );
692 if ( !conflictingLayouts.isEmpty() )
693 {
694 final List<String> sb = new ArrayList<String>();
695 sb.add( "" );
696 sb.add( "" );
697 sb.add( "Duplicate layout files have been detected across more than one Android package." );
698 sb.add( "" );
699 sb.add( "Such a scenario generally means that the build will fail with a compilation error due to" );
700 sb.add( "missing resource files. You should consider renaming some of the layout files listed below" );
701 sb.add( "to avoid the conflict." );
702 sb.add( "" );
703 sb.add( "However, if you believe you know better, then you can downgrade the failure to a warning" );
704 sb.add( "by setting the failOnConflictingLayouts plugin property to false." );
705 sb.add( "But you really don't want to do that." );
706 sb.add( "" );
707 sb.add( "Conflicting Layouts:" );
708 for ( final ConflictingLayout layout : conflictingLayouts )
709 {
710 sb.add( " " + layout.getLayoutFileName() + " packages=" + layout.getPackageNames().toString() );
711 }
712 sb.add( "" );
713
714 if ( failOnConflictingLayouts )
715 {
716 final StringBuilder builder = new StringBuilder();
717 for ( final String line : sb )
718 {
719 builder.append( line );
720 builder.append( "\n" );
721 }
722 throw new MojoExecutionException( builder.toString() );
723 }
724
725 for ( final String line : sb )
726 {
727 getLog().warn( line );
728 }
729 }
730 }
731
732
733
734
735
736
737
738
739
740 Map<String, Set<Artifact>> getPackageCompareMap( Set<Artifact> dependencyArtifacts ) throws MojoExecutionException
741 {
742 if ( dependencyArtifacts == null )
743 {
744 throw new IllegalArgumentException( "dependencies must be initialized" );
745 }
746
747 Map<String, Set<Artifact>> packageCompareMap = new HashMap<String, Set<Artifact>>();
748
749 Set<Artifact> artifactSet = new HashSet<Artifact>();
750 artifactSet.add( project.getArtifact() );
751 packageCompareMap.put( getAndroidManifestPackageName(), artifactSet );
752
753 for ( Artifact artifact : dependencyArtifacts )
754 {
755 String libPackage = extractPackageNameFromAndroidArtifact( artifact );
756
757 Set<Artifact> artifacts = packageCompareMap.get( libPackage );
758 if ( artifacts == null )
759 {
760 artifacts = new HashSet<Artifact>();
761 packageCompareMap.put( libPackage, artifacts );
762 }
763 artifacts.add( artifact );
764 }
765 return packageCompareMap;
766 }
767
768 private void generateR() throws MojoExecutionException
769 {
770 getLog().info( "Generating R file for " + project.getArtifact() );
771
772 genDirectory.mkdirs();
773
774 final AaptPackageCommandBuilder commandBuilder = AaptCommandBuilder
775 .packageResources( getLog() )
776 .makePackageDirectories()
777 .setResourceConstantsFolder( genDirectory )
778 .forceOverwriteExistingFiles()
779 .disablePngCrunching()
780 .generateRIntoPackage( customPackage )
781 .setPathToAndroidManifest( destinationManifestFile )
782 .addResourceDirectoriesIfExists( getResourceOverlayDirectories() )
783 .addResourceDirectoryIfExists( resourceDirectory )
784
785
786 .addResourceDirectoriesIfExists( getLibraryResourceFolders() )
787 .autoAddOverlay()
788 .addRawAssetsDirectoryIfExists( combinedAssets )
789 .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() )
790 .addConfigurations( configurations )
791 .setVerbose( aaptVerbose )
792
793
794 .generateRTextFile( targetDirectory )
795
796 .setProguardOptionsOutputFile( proguardFile )
797 .makeResourcesNonConstant( AAR.equals( project.getArtifact().getType() ) )
798 .addExtraArguments( aaptExtraArgs );
799
800 getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
801 try
802 {
803 final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
804 executor.setLogger( getLog() );
805 executor.setCaptureStdOut( true );
806 final List<String> commands = commandBuilder.build();
807 executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
808 }
809 catch ( ExecutionException e )
810 {
811 throw new MojoExecutionException( "", e );
812 }
813
814 final ClassLoader compileClassLoader = getCompileClassLoader();
815 final ResourceClassGenerator resGenerator = new ResourceClassGenerator(
816 this,
817 targetDirectory,
818 genDirectory,
819 compileClassLoader
820 );
821 generateCorrectRJavaForApklibDependencies( resGenerator );
822 generateCorrectRJavaForAarDependencies( resGenerator );
823
824 getLog().info( "Adding R gen folder to compile classpath: " + genDirectory );
825 project.addCompileSourceRoot( genDirectory.getAbsolutePath() );
826 }
827
828
829
830
831 private ClassLoader getCompileClassLoader()
832 {
833 try
834 {
835 final List<String> runtimeClasspathElements = project.getCompileClasspathElements();
836 final ClassLoaderFactory factory = new ClassLoaderFactory( runtimeClasspathElements );
837 return factory.create();
838 }
839 catch ( DependencyResolutionRequiredException e )
840 {
841 throw new IllegalStateException( "Mojo should have resolved dependencies", e );
842 }
843 }
844
845
846
847
848
849
850 private void generateCorrectRJavaForApklibDependencies( ResourceClassGenerator resourceGenerator )
851 throws MojoExecutionException
852 {
853 getLog().debug( "" );
854 getLog().debug( "#generateCorrectRJavaFoApklibDeps" );
855
856
857
858 getLog().debug( "Generating Rs for apklib deps of project " + project.getArtifact() );
859 final Set<Artifact> apklibDependencies = getTransitiveDependencyArtifacts( APKLIB );
860 for ( final Artifact artifact : apklibDependencies )
861 {
862 getLog().debug( "Generating apklib R.java for " + artifact.getArtifactId() + "..." );
863 generateRForApkLibDependency( artifact );
864 }
865
866
867 if ( !apklibDependencies.isEmpty() && APK.equals( project.getArtifact().getType() ) )
868 {
869
870 getLog().debug( "" );
871 getLog().debug( "Rewriting R files for APKLIB dependencies : " + apklibDependencies );
872 resourceGenerator.generateLibraryRs( apklibDependencies );
873 }
874 }
875
876
877
878
879
880
881 private void generateCorrectRJavaForAarDependencies( ResourceClassGenerator resourceGenerator )
882 throws MojoExecutionException
883 {
884
885 final Set<Artifact> aarLibraries = getTransitiveDependencyArtifacts( AAR );
886 if ( !aarLibraries.isEmpty() )
887 {
888
889 getLog().debug( "Generating R file for AAR dependencies" );
890 resourceGenerator.generateLibraryRs( aarLibraries );
891 }
892 }
893
894 private List<File> getLibraryResourceFolders()
895 {
896 final List<File> resourceFolders = new ArrayList<File>();
897 for ( Artifact artifact : getTransitiveDependencyArtifacts( AAR, APKLIB ) )
898 {
899 getLog().debug( "Considering dep artifact : " + artifact );
900 final File resourceFolder = getUnpackedLibResourceFolder( artifact );
901 if ( resourceFolder.exists() )
902 {
903 getLog().debug( "Adding apklib or aar resource folder : " + resourceFolder );
904 resourceFolders.add( resourceFolder );
905 }
906 }
907 return resourceFolders;
908 }
909
910
911
912
913
914
915
916 private void generateRForApkLibDependency( Artifact apklibArtifact ) throws MojoExecutionException
917 {
918 final File unpackDir = getUnpackedLibFolder( apklibArtifact );
919 getLog().debug( "Generating incomplete R file for apklib: " + apklibArtifact.getGroupId()
920 + ":" + apklibArtifact.getArtifactId() );
921 final File apklibManifest = new File( unpackDir, "AndroidManifest.xml" );
922 final File apklibResDir = new File( unpackDir, "res" );
923
924 List<File> dependenciesResDirectories = new ArrayList<File>();
925 final Set<Artifact> apklibDeps = getDependencyResolver()
926 .getLibraryDependenciesFor( this.session, this.repositorySystem, apklibArtifact );
927 getLog().debug( "apklib=" + apklibArtifact + " dependencies=" + apklibDeps );
928 for ( Artifact dependency : apklibDeps )
929 {
930
931 final String extension = dependency.getType();
932 final File dependencyResDir = getUnpackedLibResourceFolder( dependency );
933 if ( ( extension.equals( APKLIB ) || extension.equals( AAR ) ) && dependencyResDir.exists() )
934 {
935 dependenciesResDirectories.add( dependencyResDir );
936 }
937 }
938
939
940 final File apklibCombAssets = new File( getUnpackedLibFolder( apklibArtifact ), "combined-assets" );
941 for ( Artifact dependency : apklibDeps )
942 {
943
944 final String extension = dependency.getType();
945 final File dependencyAssetsDir = getUnpackedLibAssetsFolder( dependency );
946 if ( ( extension.equals( APKLIB ) || extension.equals( AAR ) ) )
947 {
948 copyFolder( dependencyAssetsDir, apklibCombAssets );
949 }
950 }
951
952 final File apkLibAssetsDir = getUnpackedLibAssetsFolder( apklibArtifact );
953 copyFolder( apkLibAssetsDir, apklibCombAssets );
954
955 final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
956 executor.setLogger( getLog() );
957
958 final AaptCommandBuilder commandBuilder = AaptCommandBuilder
959 .packageResources( getLog() )
960 .makeResourcesNonConstant()
961 .makePackageDirectories()
962 .setResourceConstantsFolder( genDirectory )
963 .generateRIntoPackage( extractPackageNameFromAndroidManifest( apklibManifest ) )
964 .setPathToAndroidManifest( apklibManifest )
965 .addResourceDirectoryIfExists( apklibResDir )
966 .addResourceDirectoriesIfExists( dependenciesResDirectories )
967 .autoAddOverlay()
968 .addRawAssetsDirectoryIfExists( apklibCombAssets )
969 .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() )
970 .addConfigurations( configurations )
971 .setVerbose( aaptVerbose )
972 .addExtraArguments( aaptExtraArgs )
973
974
975 .generateRTextFile( unpackDir );
976
977 getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
978 try
979 {
980 executor.setCaptureStdOut( true );
981 final List<String> commands = commandBuilder.build();
982 executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
983 }
984 catch ( ExecutionException e )
985 {
986 throw new MojoExecutionException( "", e );
987 }
988 }
989
990 private void generateBuildConfig() throws MojoExecutionException
991 {
992 getLog().debug( "Generating BuildConfig file" );
993
994
995 String packageName = extractPackageNameFromAndroidManifest( destinationManifestFile );
996 if ( StringUtils.isNotBlank( customPackage ) )
997 {
998 packageName = customPackage;
999 }
1000 generateBuildConfigForPackage( packageName );
1001
1002
1003 if ( project.getPackaging().equals( AAR ) )
1004 {
1005 return;
1006 }
1007
1008
1009
1010 for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) )
1011 {
1012 if ( skipBuildConfigGeneration( artifact ) )
1013 {
1014 getLog().info( "Skip BuildConfig.java generation for "
1015 + artifact.getGroupId() + " " + artifact.getArtifactId() );
1016 continue;
1017 }
1018
1019 final String depPackageName = extractPackageNameFromAndroidArtifact( artifact );
1020
1021 generateBuildConfigForPackage( depPackageName );
1022 }
1023 }
1024
1025 private boolean skipBuildConfigGeneration( Artifact artifact ) throws MojoExecutionException
1026 {
1027 if ( artifact.getType().equals( AAR ) )
1028 {
1029 String depPackageName = extractPackageNameFromAndroidArtifact( artifact );
1030
1031 if ( isBuildConfigPresent( artifact, depPackageName ) )
1032 {
1033 return true;
1034 }
1035
1036 Set<Artifact> transitiveDep = getArtifactResolverHelper()
1037 .getFilteredArtifacts( project.getArtifacts(), AAR );
1038
1039 for ( Artifact transitiveArtifact : transitiveDep )
1040 {
1041 if ( isBuildConfigPresent( transitiveArtifact, depPackageName ) )
1042 {
1043 return true;
1044 }
1045 }
1046 }
1047 return false;
1048 }
1049
1050
1051
1052
1053
1054
1055 private boolean isBuildConfigPresent( Artifact artifact ) throws MojoExecutionException
1056 {
1057 String depPackageName = extractPackageNameFromAndroidArtifact( artifact );
1058
1059 return isBuildConfigPresent( artifact, depPackageName );
1060 }
1061
1062
1063
1064
1065
1066
1067
1068
1069 private boolean isBuildConfigPresent( Artifact artifact, String packageName ) throws MojoExecutionException
1070 {
1071 try
1072 {
1073 JarFile jar = new JarFile( getUnpackedAarClassesJar( artifact ) );
1074 JarEntry entry = jar.getJarEntry( packageName.replace( '.', '/' ) + "/BuildConfig.class" );
1075
1076 return ( entry != null );
1077 }
1078 catch ( IOException e )
1079 {
1080 getLog().error( "Error generating BuildConfig ", e );
1081 throw new MojoExecutionException( "Error generating BuildConfig", e );
1082 }
1083 }
1084
1085 private void generateBuildConfigForPackage( String packageName ) throws MojoExecutionException
1086 {
1087 getLog().debug( "Creating BuildConfig for " + packageName );
1088
1089 File outputFolder = new File( genDirectory, packageName.replace( ".", File.separator ) );
1090 outputFolder.mkdirs();
1091
1092 StringBuilder buildConfig = new StringBuilder();
1093 buildConfig.append( "package " ).append( packageName ).append( ";\n\n" );
1094 buildConfig.append( "public final class BuildConfig {\n" );
1095 buildConfig.append( " public static final boolean DEBUG = " ).append( !release ).append( ";\n" );
1096 for ( BuildConfigConstant constant : buildConfigConstants )
1097 {
1098 String value = constant.getValue();
1099 if ( "String".equals( constant.getType() ) )
1100 {
1101 value = "\"" + value + "\"";
1102 }
1103
1104 buildConfig.append( " public static final " )
1105 .append( constant.getType() )
1106 .append( " " )
1107 .append( constant.getName() )
1108 .append( " = " )
1109 .append( value )
1110 .append( ";\n" );
1111 }
1112 buildConfig.append( "}\n" );
1113
1114 File outputFile = new File( outputFolder, "BuildConfig.java" );
1115 try
1116 {
1117 FileUtils.writeStringToFile( outputFile, buildConfig.toString() );
1118 }
1119 catch ( IOException e )
1120 {
1121 getLog().error( "Error generating BuildConfig ", e );
1122 throw new MojoExecutionException( "Error generating BuildConfig", e );
1123 }
1124 }
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134 private void generateAidlFiles( Map<File , String[] > files )
1135 throws MojoExecutionException
1136 {
1137 List<String> protoCommands = new ArrayList<String>();
1138 protoCommands.add( "-p" + getAndroidSdk().getPathForFrameworkAidl() );
1139
1140 genDirectoryAidl.mkdirs();
1141 getLog().info( "Adding AIDL gen folder to compile classpath: " + genDirectoryAidl );
1142 project.addCompileSourceRoot( genDirectoryAidl.getPath() );
1143 Set<File> sourceDirs = files.keySet();
1144 for ( File sourceDir : sourceDirs )
1145 {
1146 protoCommands.add( "-I" + sourceDir );
1147 }
1148 for ( File sourceDir : sourceDirs )
1149 {
1150 for ( String relativeAidlFileName : files.get( sourceDir ) )
1151 {
1152 File targetDirectory = new File( genDirectoryAidl, new File( relativeAidlFileName ).getParent() );
1153 targetDirectory.mkdirs();
1154
1155 final String shortAidlFileName = new File( relativeAidlFileName ).getName();
1156 final String shortJavaFileName = shortAidlFileName.substring( 0, shortAidlFileName.lastIndexOf( "." ) )
1157 + ".java";
1158 final File aidlFileInSourceDirectory = new File( sourceDir, relativeAidlFileName );
1159
1160 List<String> commands = new ArrayList<String>( protoCommands );
1161 commands.add( aidlFileInSourceDirectory.getAbsolutePath() );
1162 commands.add( new File( targetDirectory, shortJavaFileName ).getAbsolutePath() );
1163 try
1164 {
1165 CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
1166 executor.setLogger( this.getLog() );
1167 executor.setCaptureStdOut( true );
1168 executor.executeCommand( getAndroidSdk().getAidlPath(), commands, project.getBasedir(),
1169 false );
1170 }
1171 catch ( ExecutionException e )
1172 {
1173 throw new MojoExecutionException( "", e );
1174 }
1175 }
1176 }
1177 }
1178
1179 private String[] findRelativeAidlFileNames( File sourceDirectory )
1180 {
1181 final FileRetriever retriever = new FileRetriever( "**/*.aidl" );
1182 final String[] relativeAidlFileNames = retriever.getFileNames( sourceDirectory );
1183 if ( relativeAidlFileNames.length == 0 )
1184 {
1185 getLog().debug( "ANDROID-904-002: No aidl files found" );
1186 }
1187 else
1188 {
1189 getLog().info( "ANDROID-904-002: Found aidl files: Count = " + relativeAidlFileNames.length );
1190 }
1191 return relativeAidlFileNames;
1192 }
1193
1194
1195
1196
1197 private boolean isCurrentProjectAndroid()
1198 {
1199 Set<String> androidArtifacts = new HashSet<String>()
1200 {
1201 {
1202 addAll( Arrays.asList( APK, APKLIB, APKSOURCES, AAR ) );
1203 }
1204 };
1205 return androidArtifacts.contains( project.getArtifact().getType() );
1206 }
1207
1208 }