View Javadoc
1   /*
2    * Copyright (C) 2009 Jayway AB
3    * Copyright (C) 2007-2008 JVending Masa
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.simpligility.maven.plugins.android.phase09package;
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.AndroidExtension;
24  import com.simpligility.maven.plugins.android.common.NativeHelper;
25  import com.simpligility.maven.plugins.android.config.PullParameter;
26  
27  import org.apache.commons.io.FileUtils;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.model.Resource;
31  import org.apache.maven.plugin.MojoExecutionException;
32  import org.apache.maven.plugin.MojoFailureException;
33  import org.apache.maven.plugins.annotations.LifecyclePhase;
34  import org.apache.maven.plugins.annotations.Mojo;
35  import org.apache.maven.plugins.annotations.Parameter;
36  import org.apache.maven.plugins.annotations.ResolutionScope;
37  import org.codehaus.plexus.archiver.ArchiverException;
38  import org.codehaus.plexus.archiver.jar.JarArchiver;
39  import org.codehaus.plexus.archiver.util.DefaultFileSet;
40  
41  import java.io.File;
42  import java.io.FilenameFilter;
43  import java.io.IOException;
44  import java.util.ArrayList;
45  import java.util.List;
46  
47  import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
48  import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
49  
50  
51  /**
52   * Creates the apklib file.<br>
53   * apklib files do not generate deployable artifacts.
54   *
55   * @author nmaiorana@gmail.com
56   * @deprecated Use Aar instead see {@link com.simpligility.maven.plugins.android.phase09package.AarMojo}
57   */
58  @Deprecated
59  @Mojo( name = "apklib", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE )
60  public class ApklibMojo extends AbstractAndroidMojo
61  {
62      /**
63       * The name of the top level folder in the APKLIB where native libraries are found.
64       * NOTE: This is inconsistent with APK where the folder is called "lib"
65       */
66      public static final String NATIVE_LIBRARIES_FOLDER = "libs";
67  
68      /**
69       * <p>Classifier to add to the artifact generated. If given, the artifact will be an attachment instead.</p>
70       */
71      @Parameter
72      private String classifier;
73  
74      /**
75       * Specifies the application makefile to use for the build (if other than the default Application.mk).
76       */
77      @Parameter
78      @PullParameter
79      private String applicationMakefile;
80  
81      /**
82       * Defines the architecture for the NDK build
83       */
84      @Parameter( property = "android.ndk.build.architecture" )
85      @PullParameter
86      private String ndkArchitecture;
87  
88      /**
89       * Specifies the classifier with which the artifact should be stored in the repository
90       */
91      @Parameter( property = "android.ndk.build.native-classifier" )
92      @PullParameter
93      private String ndkClassifier;
94  
95      private List<String> sourceFolders = new ArrayList<String>();
96  
97      /**
98       * @throws MojoExecutionException
99       * @throws MojoFailureException
100      */
101     public void execute() throws MojoExecutionException, MojoFailureException
102     {
103         String out = targetDirectory.getPath();
104         for ( String src : project.getCompileSourceRoots() )
105         {
106             if ( !src.startsWith( out ) )
107             {
108                 sourceFolders.add( src );
109             }
110         }
111 
112         generateIntermediateApk();
113 
114         File outputFile = createApkLibraryFile();
115 
116         if ( classifier == null )
117         {
118             // Set the generated file as the main artifact (because the pom states <packaging>apklib</packaging>)
119             project.getArtifact().setFile( outputFile );
120         }
121         else
122         {
123             // If there is a classifier specified, attach the artifact using that
124             projectHelper.attachArtifact( project, AndroidExtension.APKLIB, classifier, outputFile );
125         }
126 
127         if ( attachJar )
128         {
129             final File jarFile = new File( targetDirectory,
130                     finalName + ".jar" );
131             projectHelper.attachArtifact( project, "jar", project.getArtifact().getClassifier(), jarFile );
132         }
133     }
134 
135     private File createApkLibraryFile() throws MojoExecutionException
136     {
137         final File apklibrary = new File( targetDirectory,
138                 finalName + "." + APKLIB );
139         FileUtils.deleteQuietly( apklibrary );
140 
141         try
142         {
143             JarArchiver jarArchiver = new JarArchiver();
144             jarArchiver.setDestFile( apklibrary );
145 
146             jarArchiver.addFile( destinationManifestFile, "AndroidManifest.xml" );
147             addDirectory( jarArchiver, assetsDirectory, "assets" );
148             addDirectory( jarArchiver, resourceDirectory, "res" );
149 
150             for ( String src : sourceFolders )
151             {
152                 addDirectory( jarArchiver, new File( src ), "src" );
153             }
154 
155             File[] overlayDirectories = getResourceOverlayDirectories();
156             for ( File resOverlayDir : overlayDirectories )
157             {
158                 if ( resOverlayDir != null && resOverlayDir.exists() )
159                 {
160                     addDirectory( jarArchiver, resOverlayDir, "res" );
161                 }
162             }
163 
164             addJavaResources( jarArchiver, resources, "src" );
165 
166             // Lastly, add any native libraries
167             addNativeLibraries( jarArchiver );
168 
169             jarArchiver.createArchive();
170         }
171         catch ( ArchiverException e )
172         {
173             throw new MojoExecutionException( "ArchiverException while creating ." + APKLIB + " file.", e );
174         }
175         catch ( IOException e )
176         {
177             throw new MojoExecutionException( "IOException while creating ." + APKLIB + " file.", e );
178         }
179 
180         return apklibrary;
181     }
182 
183     private void addNativeLibraries( final JarArchiver jarArchiver ) throws MojoExecutionException
184     {
185 
186         try
187         {
188             if ( nativeLibrariesDirectory.exists() )
189             {
190                 getLog().info( nativeLibrariesDirectory + " exists, adding libraries." );
191                 addDirectory( jarArchiver, nativeLibrariesDirectory, NATIVE_LIBRARIES_FOLDER );
192             }
193             else
194             {
195                 getLog().info( nativeLibrariesDirectory
196                         + " does not exist, looking for libraries in target directory." );
197                 // Add native libraries built and attached in this build
198                 String[] ndkArchitectures = NativeHelper.getNdkArchitectures( ndkArchitecture,
199                                                                               applicationMakefile,
200                                                                               project.getBasedir() );
201                 for ( String architecture : ndkArchitectures )
202                 {
203                     final File ndkLibsDirectory = new File( ndkOutputDirectory, architecture );
204                     addSharedLibraries( jarArchiver, ndkLibsDirectory, architecture );
205 
206                     // Add native library dependencies
207                     // FIXME: Remove as causes duplicate libraries when building final APK if this set includes
208                     //        libraries from dependencies of the APKLIB
209                     //final File dependentLibs = new File( ndkOutputDirectory.getAbsolutePath(), ndkArchitecture );
210                     //addSharedLibraries( jarArchiver, dependentLibs, prefix );
211                 }
212             }
213 
214             // Removing this as the APK is now able to pull the native libs from the chained apklibs
215             // get native libs from other apklibs
216 //            for ( Artifact apkLibraryArtifact : getTransitiveDependencyArtifacts( APKLIB ) )
217 //            {
218 //                final File apklibLibsDirectory = getUnpackedLibNativesFolder( apkLibraryArtifact );
219 //                if ( apklibLibsDirectory.exists() )
220 //                {
221 //                    addDirectory( jarArchiver, apklibLibsDirectory, NATIVE_LIBRARIES_FOLDER );
222 //                }
223 //            }
224         }
225         catch ( ArchiverException e )
226         {
227             throw new MojoExecutionException( "IOException while creating ." + APKLIB + " file.", e );
228         }
229         // TODO: Next is to check for any:
230         // TODO: - compiled in (as part of this build) libs
231         // TODO:    - That is of course easy if the artifact is indeed attached
232         // TODO:    - If not attached, it gets a little trickier  - check the target dir for any compiled .so files (generated by NDK mojo)
233         // TODO:        - But where is that directory configured?
234     }
235 
236     protected void addJavaResources( JarArchiver jarArchiver, List<Resource> javaResources, String prefix )
237             throws IOException
238     {
239         for ( Resource javaResource : javaResources )
240         {
241             addJavaResource( jarArchiver, javaResource, prefix );
242         }
243     }
244 
245     /**
246      * Adds a Java Resources directory (typically "src/main/resources") to a {@link JarArchiver}.
247      *
248      * @param jarArchiver
249      * @param javaResource The Java resource to add.
250      * @param prefix       An optional prefix for where in the Jar file the directory's contents should go.
251      * @throws IOException in case the resource path can not be resolved
252      */
253     protected void addJavaResource( JarArchiver jarArchiver, Resource javaResource, String prefix )
254             throws IOException
255     {
256         if ( javaResource != null )
257         {
258             final File javaResourceDirectory = new File( javaResource.getDirectory() );
259             if ( javaResourceDirectory.exists() )
260             {
261                 final String resourcePath = javaResourceDirectory.getCanonicalPath();
262                 final String apkLibUnpackBasePath = getUnpackedLibsDirectory().getCanonicalPath();
263                 // Don't include our dependencies' resource dirs.
264                 if ( ! resourcePath.startsWith( apkLibUnpackBasePath ) )
265                 {
266                     final DefaultFileSet javaResourceFileSet = new DefaultFileSet();
267                     javaResourceFileSet.setDirectory( javaResourceDirectory );
268                     javaResourceFileSet.setPrefix( endWithSlash( prefix ) );
269                     jarArchiver.addFileSet( javaResourceFileSet );
270                 }
271             }
272         }
273     }
274 
275     /**
276      * Makes sure the string ends with "/"
277      *
278      * @param prefix any string, or null.
279      * @return the prefix with a "/" at the end, never null.
280      */
281     protected String endWithSlash( String prefix )
282     {
283         prefix = StringUtils.defaultIfEmpty( prefix, "/" );
284         if ( ! prefix.endsWith( "/" ) )
285         {
286             prefix = prefix + "/";
287         }
288         return prefix;
289     }
290 
291     /**
292      * Adds a directory to a {@link JarArchiver} with a directory prefix.
293      *
294      * @param jarArchiver
295      * @param directory   The directory to add.
296      * @param prefix      An optional prefix for where in the Jar file the directory's contents should go.
297      */
298     protected void addDirectory( JarArchiver jarArchiver, File directory, String prefix )
299     {
300         if ( directory != null && directory.exists() )
301         {
302             final DefaultFileSet fileSet = new DefaultFileSet();
303             fileSet.setPrefix( endWithSlash( prefix ) );
304             fileSet.setDirectory( directory );
305 
306             // XXX: trying to avoid duplicated sources
307             fileSet.setExcludes( new String[] { "**/R.java", "**/BuildConfig.java" } );
308 
309             jarArchiver.addFileSet( fileSet );
310             getLog().debug( "Added files from " + directory );
311         }
312     }
313 
314     /**
315      * Adds all shared libraries (.so) to a {@link JarArchiver} under 'libs'.
316      *
317      * @param jarArchiver The jarArchiver to add files to
318      * @param directory   The directory to scan for .so files
319      * @param architecture      The prefix for where in the jar the .so files will go.
320      */
321     protected void addSharedLibraries( JarArchiver jarArchiver, File directory, String architecture )
322     {
323         getLog().debug( "Searching for shared libraries in " + directory );
324         File[] libFiles = directory.listFiles( new FilenameFilter()
325         {
326             public boolean accept( final File dir, final String name )
327             {
328                 return name.startsWith( "lib" ) && name.endsWith( ".so" );
329             }
330         } );
331 
332         if ( libFiles != null )
333         {
334             for ( File libFile : libFiles )
335             {
336                 String dest = NATIVE_LIBRARIES_FOLDER + "/" + architecture + "/" + libFile.getName();
337                 getLog().debug( "Adding " + libFile + " as " + dest );
338                 jarArchiver.addFile( libFile, dest );
339             }
340         }
341     }
342 
343     /**
344      * Generates an intermediate apk file (actually .ap_) containing the resources and assets.
345      *
346      * @throws MojoExecutionException
347      */
348     private void generateIntermediateApk() throws MojoExecutionException
349     {
350 
351         CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
352         executor.setLogger( this.getLog() );
353         File[] overlayDirectories = getResourceOverlayDirectories();
354 
355         File androidJar = getAndroidSdk().getAndroidJar();
356         File outputFile = new File( targetDirectory, finalName + ".ap_" );
357 
358         List<File> dependencyArtifactResDirectoryList = new ArrayList<File>();
359         for ( Artifact libraryArtifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) )
360         {
361             final File apklibResDirectory = getUnpackedLibResourceFolder( libraryArtifact );
362             if ( apklibResDirectory.exists() )
363             {
364                 dependencyArtifactResDirectoryList.add( apklibResDirectory );
365             }
366         }
367 
368         AaptCommandBuilder commandBuilder = AaptCommandBuilder
369                 .packageResources( getLog() )
370                 .forceOverwriteExistingFiles()
371                 .setPathToAndroidManifest( destinationManifestFile )
372                 .addResourceDirectoriesIfExists( overlayDirectories )
373                 .addResourceDirectoryIfExists( resourceDirectory )
374                 .addResourceDirectoriesIfExists( dependencyArtifactResDirectoryList )
375                 .autoAddOverlay()
376                 // NB aapt only accepts a single assets parameter - combinedAssets is a merge of all assets
377                 .addRawAssetsDirectoryIfExists( combinedAssets )
378                 .addExistingPackageToBaseIncludeSet( androidJar )
379                 .setOutputApkFile( outputFile )
380                 .addConfigurations( configurations )
381                 .setVerbose( aaptVerbose );
382 
383         getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() );
384         getLog().info( "Generating apklib" );
385         try
386         {
387             executor.setCaptureStdOut( true );
388             List<String> commands = commandBuilder.build();
389             executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false );
390         }
391         catch ( ExecutionException e )
392         {
393             throw new MojoExecutionException( "", e );
394         }
395     }
396 
397 }