1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.simpligility.maven.plugins.android.phase09package;
18
19 import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
20 import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
21
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.FilenameFilter;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import org.apache.commons.io.FileUtils;
31 import org.apache.commons.io.IOUtils;
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.commons.lang3.SystemUtils;
34 import org.apache.maven.artifact.Artifact;
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 import org.codehaus.plexus.archiver.zip.ZipArchiver;
45
46 import com.android.SdkConstants;
47 import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
48 import com.simpligility.maven.plugins.android.CommandExecutor;
49 import com.simpligility.maven.plugins.android.ExecutionException;
50 import com.simpligility.maven.plugins.android.common.AaptCommandBuilder;
51 import com.simpligility.maven.plugins.android.common.AndroidExtension;
52 import com.simpligility.maven.plugins.android.common.NativeHelper;
53 import com.simpligility.maven.plugins.android.config.PullParameter;
54
55
56
57
58
59 @Mojo(
60 name = "aar",
61 defaultPhase = LifecyclePhase.VERIFY,
62 requiresDependencyResolution = ResolutionScope.COMPILE
63 )
64 public class AarMojo extends AbstractAndroidMojo
65 {
66
67
68
69
70
71 public static final String NATIVE_LIBRARIES_FOLDER = "jni";
72
73
74
75
76 @Parameter
77 private String classifier;
78
79
80
81
82 @Parameter
83 @PullParameter
84 private String applicationMakefile;
85
86
87
88
89 @Parameter( property = "android.ndk.build.architecture" )
90 @PullParameter
91 private String ndkArchitecture;
92
93
94
95
96 @Parameter( property = "android.ndk.build.native-classifier" )
97 @PullParameter
98 private String ndkClassifier;
99
100
101
102
103 @Parameter
104 @PullParameter
105 private String[] classesJarIncludes = new String[]{"**/*"};
106
107
108
109
110 @Parameter
111 @PullParameter
112 private String[] classesJarExcludes = new String[]{"**/R.class", "**/R$*.class"};
113
114
115
116
117
118 @Parameter
119 private File[] consumerProguardFiles;
120
121 @Parameter(
122 property = "android.proguard.obfuscatedJar",
123 defaultValue = "${project.build.directory}/${project.build.finalName}_obfuscated.jar"
124 )
125 private String obfuscatedJar;
126
127 private List<String> sourceFolders = new ArrayList<String>();
128
129
130
131
132
133 public void execute() throws MojoExecutionException, MojoFailureException
134 {
135 String out = targetDirectory.getPath();
136 for ( String src : project.getCompileSourceRoots() )
137 {
138 if ( !src.startsWith( out ) )
139 {
140 sourceFolders.add( src );
141 }
142 }
143
144 getLog().info( "Generating AAR file : " + project.getArtifactId() );
145 generateIntermediateApk();
146
147 final File outputFile = createAarLibraryFile( createAarClassesJar() );
148
149 if ( classifier == null )
150 {
151
152 project.getArtifact().setFile( outputFile );
153 }
154 else
155 {
156
157 projectHelper.attachArtifact( project, AndroidExtension.AAR, classifier, outputFile );
158 }
159 }
160
161
162
163
164
165
166
167 protected File createAarClassesJar() throws MojoExecutionException
168 {
169 final File obfuscatedJarFile = new File( obfuscatedJar );
170 if ( obfuscatedJarFile.exists() )
171 {
172 attachJar( obfuscatedJarFile );
173 return obfuscatedJarFile;
174 }
175
176 final File classesJar = new File( targetDirectory, finalName + ".aar.classes.jar" );
177 try
178 {
179 JarArchiver jarArchiver = new JarArchiver();
180 jarArchiver.setDestFile( classesJar );
181 jarArchiver.addDirectory( projectOutputDirectory,
182 classesJarIncludes,
183 classesJarExcludes );
184 jarArchiver.createArchive();
185 attachJar( classesJar );
186 return classesJar;
187 }
188 catch ( ArchiverException e )
189 {
190 throw new MojoExecutionException( "ArchiverException while creating ." + classesJar + " file.", e );
191 }
192 catch ( IOException e )
193 {
194 throw new MojoExecutionException( "IOException while creating ." + classesJar + " file.", e );
195 }
196
197 }
198
199 private void attachJar( File jarFile )
200 {
201 if ( attachJar )
202 {
203 projectHelper.attachArtifact( project, "jar", project.getArtifact().getClassifier(), jarFile );
204 }
205 }
206
207
208
209
210
211 protected File createAarLibraryFile( File classesJar ) throws MojoExecutionException
212 {
213 final File aarLibrary = new File( targetDirectory,
214 finalName + "." + AAR );
215 FileUtils.deleteQuietly( aarLibrary );
216
217 try
218 {
219 final ZipArchiver zipArchiver = new ZipArchiver();
220 zipArchiver.setDestFile( aarLibrary );
221
222 zipArchiver.addFile( destinationManifestFile, "AndroidManifest.xml" );
223 addDirectory( zipArchiver, assetsDirectory, "assets", false );
224
225
226 if ( !resourceDirectory.exists() )
227 {
228 resourceDirectory.mkdir();
229 }
230 addDirectory( zipArchiver, resourceDirectory, "res", true );
231
232 zipArchiver.addFile( classesJar, SdkConstants.FN_CLASSES_JAR );
233
234 final File[] overlayDirectories = getResourceOverlayDirectories();
235 for ( final File resOverlayDir : overlayDirectories )
236 {
237 if ( resOverlayDir != null && resOverlayDir.exists() )
238 {
239 addDirectory( zipArchiver, resOverlayDir, "res", false );
240 }
241 }
242
243 if ( consumerProguardFiles != null )
244 {
245 final File mergedConsumerProguardFile = new File( targetDirectory, "consumer-proguard.txt" );
246 if ( mergedConsumerProguardFile.exists() )
247 {
248 FileUtils.forceDelete( mergedConsumerProguardFile );
249 }
250 mergedConsumerProguardFile.createNewFile();
251 StringBuilder mergedConsumerProguardFileBuilder = new StringBuilder();
252 for ( File consumerProguardFile : consumerProguardFiles )
253 {
254 if ( consumerProguardFile.exists() )
255 {
256 getLog().info( "Adding consumer proguard file " + consumerProguardFile );
257 FileInputStream consumerProguardFileInputStream = null;
258 try
259 {
260 consumerProguardFileInputStream = new FileInputStream( consumerProguardFile );
261 mergedConsumerProguardFileBuilder.append(
262 IOUtils.toString( consumerProguardFileInputStream ) );
263 mergedConsumerProguardFileBuilder.append( SystemUtils.LINE_SEPARATOR );
264 }
265 catch ( IOException e )
266 {
267 throw new MojoExecutionException( "Error writing consumer proguard file ", e );
268 }
269 finally
270 {
271 IOUtils.closeQuietly( consumerProguardFileInputStream );
272 }
273 }
274 }
275 FileOutputStream mergedConsumerProguardFileOutputStream = null;
276 try
277 {
278 mergedConsumerProguardFileOutputStream = new FileOutputStream( mergedConsumerProguardFile );
279 IOUtils.write( mergedConsumerProguardFileBuilder, mergedConsumerProguardFileOutputStream );
280 }
281 catch ( IOException e )
282 {
283 throw new MojoExecutionException( "Error writing consumer proguard file ", e );
284 }
285 finally
286 {
287 IOUtils.closeQuietly( mergedConsumerProguardFileOutputStream );
288 }
289
290 zipArchiver.addFile( mergedConsumerProguardFile, "proguard.txt" );
291 }
292
293 addR( zipArchiver );
294
295
296 addNativeLibraries( zipArchiver );
297
298 zipArchiver.createArchive();
299 }
300 catch ( ArchiverException e )
301 {
302 throw new MojoExecutionException( "ArchiverException while creating ." + AAR + " file.", e );
303 }
304 catch ( IOException e )
305 {
306 throw new MojoExecutionException( "IOException while creating ." + AAR + " file.", e );
307 }
308
309 return aarLibrary;
310 }
311
312 private void addR( ZipArchiver zipArchiver ) throws MojoExecutionException, IOException
313 {
314 final File rFile = new File( targetDirectory, "R.txt" );
315 if ( !rFile.exists() )
316 {
317 getLog().debug( "No resources - creating empty R.txt" );
318 if ( !rFile.createNewFile() )
319 {
320 getLog().warn( "Unable to create R.txt in AAR" );
321 }
322 }
323 zipArchiver.addFile( rFile, "R.txt" );
324 getLog().debug( "Packaging R.txt in AAR" );
325 }
326
327 private void addNativeLibraries( final ZipArchiver zipArchiver ) throws MojoExecutionException
328 {
329 try
330 {
331 if ( nativeLibrariesDirectory.exists() )
332 {
333 getLog().info( nativeLibrariesDirectory + " exists, adding libraries." );
334 addDirectory( zipArchiver, nativeLibrariesDirectory, NATIVE_LIBRARIES_FOLDER, false );
335 }
336 else
337 {
338 getLog().info( nativeLibrariesDirectory
339 + " does not exist, looking for libraries in target directory." );
340
341 String[] ndkArchitectures = NativeHelper.getNdkArchitectures( ndkArchitecture,
342 applicationMakefile,
343 project.getBasedir() );
344 for ( String architecture : ndkArchitectures )
345 {
346 final File ndkLibsDirectory = new File( ndkOutputDirectory, architecture );
347 addSharedLibraries( zipArchiver, ndkLibsDirectory, architecture );
348
349
350
351
352
353
354 }
355 }
356 }
357 catch ( ArchiverException e )
358 {
359 throw new MojoExecutionException( "IOException while creating ." + AAR + " file.", e );
360 }
361
362
363
364
365
366 }
367
368
369
370
371
372
373
374 protected String endWithSlash( String prefix )
375 {
376 prefix = StringUtils.defaultIfEmpty( prefix, "/" );
377 if ( ! prefix.endsWith( "/" ) )
378 {
379 prefix = prefix + "/";
380 }
381 return prefix;
382 }
383
384
385
386
387
388
389
390
391
392 protected void addDirectory( ZipArchiver zipArchiver, File directory, String prefix, boolean includeEmptyFolders )
393 {
394 if ( directory != null && directory.exists() )
395 {
396 final DefaultFileSet fileSet = new DefaultFileSet();
397 fileSet.setPrefix( endWithSlash( prefix ) );
398 fileSet.setDirectory( directory );
399 fileSet.setIncludingEmptyDirectories( includeEmptyFolders );
400 zipArchiver.addFileSet( fileSet );
401 getLog().debug( "Added files from " + directory );
402 }
403 }
404
405
406
407
408
409
410
411
412 protected void addSharedLibraries( ZipArchiver zipArchiver, File directory, String architecture )
413 {
414 getLog().debug( "Searching for shared libraries in " + directory );
415 File[] libFiles = directory.listFiles( new FilenameFilter()
416 {
417 public boolean accept( final File dir, final String name )
418 {
419 return name.startsWith( "lib" ) && name.endsWith( ".so" );
420 }
421 } );
422
423 if ( libFiles != null )
424 {
425 for ( File libFile : libFiles )
426 {
427 String dest = NATIVE_LIBRARIES_FOLDER + "/" + architecture + "/" + libFile.getName();
428 getLog().debug( "Adding " + libFile + " as " + dest );
429 zipArchiver.addFile( libFile, dest );
430 }
431 }
432 }
433
434
435
436
437
438
439 private void generateIntermediateApk() throws MojoExecutionException
440 {
441
442
443 List<File> dependenciesResDirectories = new ArrayList<File>();
444 for ( Artifact libraryArtifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) )
445 {
446 final File apkLibResDir = getUnpackedLibResourceFolder( libraryArtifact );
447 if ( apkLibResDir.exists() )
448 {
449 dependenciesResDirectories.add( apkLibResDir );
450 }
451 }
452
453 final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
454 executor.setLogger( this.getLog() );
455
456 File outputFile = new File( targetDirectory, finalName + ".ap_" );
457
458 final AaptCommandBuilder commandBuilder = AaptCommandBuilder
459 .packageResources( getLog() )
460 .makePackageDirectories()
461 .forceOverwriteExistingFiles()
462 .setPathToAndroidManifest( destinationManifestFile )
463 .addResourceDirectoriesIfExists( getResourceOverlayDirectories() )
464 .addResourceDirectoryIfExists( resourceDirectory )
465 .addResourceDirectoriesIfExists( dependenciesResDirectories )
466 .autoAddOverlay()
467 .addRawAssetsDirectoryIfExists( combinedAssets )
468 .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() )
469 .setOutputApkFile( outputFile )
470 .addConfigurations( configurations )
471 .setResourceConstantsFolder( genDirectory )
472 .makeResourcesNonConstant()
473 .generateRTextFile( targetDirectory )
474 .setVerbose( aaptVerbose );
475
476 getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
477 getLog().info( "Generating aar" );
478 try
479 {
480 executor.setCaptureStdOut( true );
481 final List<String> commands = commandBuilder.build();
482 executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
483 }
484 catch ( ExecutionException e )
485 {
486 throw new MojoExecutionException( "", e );
487 }
488 }
489 }