View Javadoc
1   package com.simpligility.maven.plugins.androidndk.phase05compile;
2   
3   import com.simpligility.maven.plugins.androidndk.common.AndroidExtension;
4   import com.simpligility.maven.plugins.androidndk.common.ArtifactResolverHelper;
5   import com.simpligility.maven.plugins.androidndk.common.Const;
6   import com.simpligility.maven.plugins.androidndk.common.JarHelper;
7   import com.simpligility.maven.plugins.androidndk.common.MavenToPlexusLogAdapter;
8   import com.simpligility.maven.plugins.androidndk.common.NativeHelper;
9   import com.simpligility.maven.plugins.androidndk.common.UnpackedLibHelper;
10  import com.simpligility.maven.plugins.androidndk.configuration.IgnoreHeaderFilesArchive;
11  import org.apache.commons.io.FileUtils;
12  import org.apache.commons.io.FilenameUtils;
13  import org.apache.commons.io.filefilter.TrueFileFilter;
14  import org.apache.maven.artifact.Artifact;
15  import org.apache.maven.artifact.DefaultArtifact;
16  import org.apache.maven.artifact.handler.ArtifactHandler;
17  import org.apache.maven.plugin.MojoExecutionException;
18  import org.apache.maven.plugin.logging.Log;
19  import org.apache.maven.project.MavenProject;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  import java.util.jar.JarEntry;
29  import java.util.jar.JarFile;
30  
31  /**
32   * Various helper methods for dealing with Android Native makefiles.
33   *
34   * @author Johan Lindquist
35   */
36  public class MakefileHelper
37  {
38      public static class MakefileRequest
39      {
40          Set<Artifact> artifacts;
41          String defaultNDKArchitecture;
42          boolean useHeaderArchives;
43          boolean leaveTemporaryBuildArtifacts;
44          String[] architectures;
45          List<IgnoreHeaderFilesArchive> ignoreHeaderFilesArchives;
46      }
47  
48  
49      private class LibraryDetails
50      {
51          Artifact artifact;
52          Artifact harArtifact;
53  
54          String architecture;
55          String localModule;
56          File libraryPath;
57          String localSrcFiles;
58          String localModuleFileName;
59  
60          boolean useHeaderArchives;
61          boolean leaveTemporaryBuildArtifacts;
62  
63          List<File> includeDirectories;
64          MakefileResponse makefileResponse;
65      }
66  
67      public static final String MAKEFILE_CAPTURE_FILE = "ANDROID_MAVEN_PLUGIN_LOCAL_C_INCLUDES_FILE";
68      
69      /**
70       * Holder for the result of creating a makefile.  This in particular keep tracks of all directories created
71       * for extracted header files.
72       */
73      public static class MakefileResponse
74      {
75          final boolean leaveTemporaryBuildArtifacts;
76          final StringBuilder makeFile;
77          final List<File> includeDirectories;
78          private Set<String> staticLibraryList = new HashSet<String> (  );
79          private Set<String> sharedLibraryList = new HashSet<String> (  );
80  
81          public MakefileResponse ( List<File> includeDirectories, StringBuilder makeFile, boolean leaveTemporaryBuildArtifacts )
82          {
83              this.includeDirectories = includeDirectories;
84              this.makeFile = makeFile;
85              this.leaveTemporaryBuildArtifacts = leaveTemporaryBuildArtifacts;
86          }
87  
88          public List<File> getIncludeDirectories()
89          {
90              return includeDirectories;
91          }
92  
93          public String getMakeFile()
94          {
95              return makeFile.toString ();
96          }
97  
98          public boolean isLeaveTemporaryBuildArtifacts()
99          {
100             return leaveTemporaryBuildArtifacts;
101         }
102 
103         public boolean hasStaticLibraryDepdendencies ()
104         {
105             return !staticLibraryList.isEmpty ();
106         }
107 
108         public String getStaticLibraryList ()
109         {
110             StringBuilder sb = new StringBuilder (  );
111             for ( String staticLibraryName : staticLibraryList )
112             {
113                 sb.append ( staticLibraryName );
114                 sb.append ( " " );
115             }
116             return sb.toString ();
117         }
118 
119         public void addStaticLibraryName( final String staticLibraryName )
120         {
121             staticLibraryList.add ( staticLibraryName );
122         }
123 
124         public boolean hasSharedLibraryDepdendencies ()
125         {
126             return !sharedLibraryList.isEmpty ();
127         }
128 
129         public String getSharedLibraryList ()
130         {
131             StringBuilder sb = new StringBuilder (  );
132             for ( String sharedLibraryName : sharedLibraryList )
133             {
134                 sb.append ( sharedLibraryName );
135                 sb.append ( " " );
136             }
137             return sb.toString ();
138         }
139 
140         public void addSharedLibraryName( final String sharedLibraryName )
141         {
142             sharedLibraryList.add ( sharedLibraryName );
143         }
144 
145     }
146 
147     private final MavenProject project;
148     private final Log log;
149     private final ArtifactResolverHelper artifactResolverHelper;
150     private final ArtifactHandler harArtifactHandler;
151     private final File unpackedApkLibsDirectory;
152     private final File ndkBuildDirectory;
153 
154     /**
155      * Initialize the MakefileHelper by storing the supplied parameters to local variables.
156      * @param log                       Log to which to write log output.
157      * @param artifactResolverHelper    ArtifactResolverHelper to use to resolve the artifacts.
158      * @param harHandler                ArtifactHandler for har files.
159      * @param unpackedApkLibsDirectory  Folder in which apklibs are unpacked.
160      */
161     public MakefileHelper( final MavenProject project, final Log log, final ArtifactResolverHelper artifactResolverHelper,
162                            final ArtifactHandler harHandler, final File unpackedApkLibsDirectory, final File ndkBuildDirectory )
163     {
164         this.project = project;
165         this.log = log;
166         this.artifactResolverHelper = artifactResolverHelper;
167         this.harArtifactHandler = harHandler;
168         this.unpackedApkLibsDirectory = unpackedApkLibsDirectory;
169         this.ndkBuildDirectory = ndkBuildDirectory;
170     }
171     
172     /**
173      * Cleans up all include directories created in the temp directory during the build.
174      *
175      * @param makefileResponse The holder produced by the
176      * {@link MakefileHelper#createMakefileFromArtifacts(Set, String, String, boolean)}
177      */
178     public static void cleanupAfterBuild( final MakefileResponse makefileResponse )
179     {
180         if ( !makefileResponse.isLeaveTemporaryBuildArtifacts() )
181         {
182             if ( makefileResponse.getIncludeDirectories() != null )
183             {
184                 for ( File file : makefileResponse.getIncludeDirectories() )
185                 {
186                     try
187                     {
188                         FileUtils.deleteDirectory( file );
189                     }
190                     catch ( IOException e )
191                     {
192                         e.printStackTrace();
193                     }
194                 }
195             }
196         }
197     }
198 
199     /**
200      * Creates an Android Makefile based on the specified set of static library dependency artifacts.
201      *
202      * @param artifacts         The list of (static library) dependency artifacts to create the Makefile from
203      * @param useHeaderArchives If true, the Makefile should include a LOCAL_EXPORT_C_INCLUDES statement, pointing to
204      *                          the location where the header archive was expanded
205      * @return The created Makefile
206      */
207     public MakefileResponse createMakefileFromArtifacts ( MakefileRequest makefileRequest )
208             throws IOException, MojoExecutionException
209     {
210         final List<File> includeDirectories = new ArrayList<File>();
211         final StringBuilder makeFile = new StringBuilder( "# Generated by Android Maven Plugin\n" );
212 
213         final MakefileResponse makefileResponse = new MakefileResponse ( includeDirectories, makeFile, makefileRequest.leaveTemporaryBuildArtifacts );
214 
215         final Set<Artifact> artifacts = makefileRequest.artifacts;
216 
217         // Add now output - allows us to somewhat intelligently determine the include paths to use for the header
218         // archive
219         makeFile.append( "$(shell echo \"LOCAL_C_INCLUDES=$(LOCAL_C_INCLUDES)\" > $(" + MAKEFILE_CAPTURE_FILE + "))" );
220         makeFile.append( '\n' );
221         makeFile.append( "$(shell echo \"LOCAL_PATH=$(LOCAL_PATH)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
222         makeFile.append( '\n' );
223         makeFile.append( "$(shell echo \"LOCAL_MODULE=$(LOCAL_MODULE)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
224         makeFile.append( '\n' );
225         makeFile.append( "$(shell echo \"LOCAL_MODULE_FILENAME=$(LOCAL_MODULE_FILENAME)\" >> $("
226                 + MAKEFILE_CAPTURE_FILE + "))" );
227         makeFile.append( '\n' );
228         makeFile.append( "$(shell echo \"LOCAL_CFLAGS=$(LOCAL_CFLAGS)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
229         makeFile.append( '\n' );
230         makeFile.append( "$(shell echo \"LOCAL_SHARED_LIBRARIES=$(LOCAL_SHARED_LIBRARIES)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
231         makeFile.append( '\n' );
232         makeFile.append( "$(shell echo \"LOCAL_STATIC_LIBRARIES=$(LOCAL_STATIC_LIBRARIES)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
233         makeFile.append( '\n' );
234         makeFile.append( "$(shell echo \"LOCAL_EXPORT_C_INCLUDES=$(LOCAL_EXPORT_C_INCLUDES)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
235         makeFile.append( '\n' );
236         makeFile.append( "$(shell echo \"LOCAL_SRC_FILES=$(LOCAL_SRC_FILES)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
237         makeFile.append( '\n' );
238 
239         if ( ! artifacts.isEmpty() )
240         {
241             for ( Artifact artifact : artifacts )
242             {
243                 // If we are dealing with bundled artifacts or not (in an APKLIB or AAR for example)
244                 if ( !isLibraryBundle( artifact ) )
245                 {
246                     final String architecture = NativeHelper.extractArchitectureFromArtifact ( artifact, makefileRequest.defaultNDKArchitecture );
247 
248                     final LibraryDetails libraryDetails = new LibraryDetails ();
249 
250                     libraryDetails.makefileResponse = makefileResponse;
251                     libraryDetails.artifact = artifact;
252 
253                     libraryDetails.architecture = architecture;
254                     libraryDetails.localModule = artifact.getArtifactId ();
255                     libraryDetails.libraryPath = artifact.getFile ();
256 
257                     libraryDetails.useHeaderArchives = useHeaderArchives( artifact, makefileRequest.useHeaderArchives, makefileRequest.ignoreHeaderFilesArchives );
258                     libraryDetails.leaveTemporaryBuildArtifacts = makefileRequest.leaveTemporaryBuildArtifacts;
259 
260                     libraryDetails.includeDirectories = includeDirectories;
261 
262                     libraryDetails.harArtifact = new DefaultArtifact ( artifact.getGroupId (), artifact.getArtifactId (),
263                                           artifact.getVersion (), artifact.getScope (),
264                                           Const.ArtifactType.NATIVE_HEADER_ARCHIVE, artifact.getClassifier (), harArtifactHandler );
265 
266                     addLocalModule( libraryDetails );
267                 }
268                 else
269                 {
270                     final LibraryDetails libraryDetails = new LibraryDetails ();
271 
272                     libraryDetails.makefileResponse = makefileResponse;
273                     libraryDetails.artifact = artifact;
274                     libraryDetails.useHeaderArchives = makefileRequest.useHeaderArchives;
275                     libraryDetails.leaveTemporaryBuildArtifacts = makefileRequest.leaveTemporaryBuildArtifacts;
276 
277                     // Collect added include directorie
278                     libraryDetails.includeDirectories = includeDirectories;
279 
280 
281                     addLibraryBundleDetails ( libraryDetails, makefileRequest.architectures );
282                 }
283             }
284         }
285         return makefileResponse;
286     }
287 
288     private boolean useHeaderArchives ( final Artifact artifact, final boolean useHeaderArchives, final List<IgnoreHeaderFilesArchive> ignoreHeaderFilesArchives )
289     {
290         if ( !useHeaderArchives )
291         {
292             return false;
293         }
294 
295         if ( ignoreHeaderFilesArchives != null )
296         {
297             for ( IgnoreHeaderFilesArchive directive : ignoreHeaderFilesArchives )
298             {
299                 if ( directive.getGroupId ().equals ( artifact.getGroupId () ) && directive.getArtifactId ().equals ( artifact.getArtifactId () ) )
300                 {
301                     return false;
302                 }
303             }
304         }
305         return true;
306     }
307 
308 
309     private void addLocalModule ( final LibraryDetails libraryDetails ) throws MojoExecutionException, IOException
310     {
311         final Artifact artifact = libraryDetails.artifact;
312         final StringBuilder makeFile = libraryDetails.makefileResponse.makeFile;
313         final boolean isStaticLibrary = Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals ( libraryDetails.artifact.getType () );
314 
315 
316         makeFile.append ( '\n' );
317         makeFile.append ( "ifeq ($(TARGET_ARCH_ABI)," ).append ( libraryDetails.architecture ).append ( ")\n" );
318 
319         makeFile.append ( "#\n" );
320         makeFile.append ( "# Group ID: " );
321         makeFile.append ( artifact.getGroupId () );
322         makeFile.append ( '\n' );
323         makeFile.append ( "# Artifact ID: " );
324         makeFile.append ( artifact.getArtifactId () );
325         makeFile.append ( '\n' );
326         makeFile.append ( "# Artifact Type: " );
327         makeFile.append ( artifact.getType () );
328         makeFile.append ( '\n' );
329         makeFile.append ( "# Version: " );
330         makeFile.append ( artifact.getVersion () );
331         makeFile.append ( '\n' );
332         makeFile.append ( "include $(CLEAR_VARS)" );
333         makeFile.append ( '\n' );
334         makeFile.append ( "LOCAL_MODULE    := " );
335         makeFile.append ( libraryDetails.localModule );
336         makeFile.append ( '\n' );
337 
338         if ( isStaticLibrary )
339         {
340             libraryDetails.makefileResponse.addStaticLibraryName ( libraryDetails.localModule );
341         }
342         else
343         {
344             libraryDetails.makefileResponse.addSharedLibraryName ( libraryDetails.localModule );
345         }
346 
347 
348         addLibraryDetails ( makeFile,  libraryDetails.libraryPath, artifact.getArtifactId() );
349 
350         if ( libraryDetails.useHeaderArchives )
351         {
352             try
353             {
354                 final String classifier = artifact.getClassifier ();
355 
356                 File resolvedHarArtifactFile = artifactResolverHelper.resolveArtifactToFile ( libraryDetails.harArtifact );
357                 log.debug ( "Resolved har artifact file : " + resolvedHarArtifactFile );
358 
359                 final File includeDir = new File ( ndkBuildDirectory, "android_maven_plugin_native_includes" + System.currentTimeMillis () + "_"
360                         + libraryDetails.harArtifact.getArtifactId () );
361 
362                 if ( !libraryDetails.leaveTemporaryBuildArtifacts )
363                 {
364                     includeDir.deleteOnExit ();
365                 }
366 
367                 libraryDetails.includeDirectories.add ( includeDir );
368 
369                 JarHelper.unjar ( new JarFile ( resolvedHarArtifactFile ), includeDir,
370                         new JarHelper.UnjarListener ()
371                         {
372                             @Override
373                             public boolean include ( JarEntry jarEntry )
374                             {
375                                 return !jarEntry.getName ().startsWith ( "META-INF" );
376                             }
377                         } );
378 
379                 makeFile.append ( "LOCAL_EXPORT_C_INCLUDES := " );
380                 makeFile.append ( includeDir.getAbsolutePath () );
381                 makeFile.append ( '\n' );
382 
383                 if ( log.isDebugEnabled () )
384                 {
385                     Collection<File> includes = FileUtils.listFiles ( includeDir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE );
386                     log.debug ( "Listing LOCAL_EXPORT_C_INCLUDES for " + artifact.getId () + ": " + includes );
387                 }
388             }
389             catch ( RuntimeException e )
390             {
391                 throw new MojoExecutionException ( "Error while resolving header archive file for: " + artifact.getArtifactId (), e );
392             }
393         }
394         if ( isStaticLibrary )
395         {
396             makeFile.append ( "include $(PREBUILT_STATIC_LIBRARY)\n" );
397         }
398         else
399         {
400             makeFile.append ( "include $(PREBUILT_SHARED_LIBRARY)\n" );
401         }
402 
403         makeFile.append ( "endif #" ).append ( artifact.getClassifier () ).append ( '\n' );
404         makeFile.append ( '\n' );
405 
406 
407 
408     }
409 
410     private boolean isLibraryBundle( Artifact artifact )
411     {
412         return artifact.getType ().equals ( AndroidExtension.APKLIB ) || artifact.getType ().equals ( AndroidExtension.AAR );
413     }
414 
415     private void addLibraryBundleDetails( final LibraryDetails libraryDetails, final String[] architectures ) throws MojoExecutionException, IOException
416     {
417         final Artifact artifact = libraryDetails.artifact;
418 
419         // At this point, we should peek at the files in the APK/AAR to see if
420         // there are any native files.  If so, we should add those to the make file.
421         // Also, from the list of architectures in the
422 
423         // So, extract the artifact, if need be
424         UnpackedLibHelper unpackedLibHelper = new UnpackedLibHelper ( artifactResolverHelper, project, new MavenToPlexusLogAdapter ( log ), unpackedApkLibsDirectory );
425 
426         if ( artifact.getType ().equals ( AndroidExtension.AAR ) )
427         {
428             unpackedLibHelper.extractAarLib ( artifact );
429         }
430         else if ( artifact.getType ().equals ( AndroidExtension.APKLIB ) )
431         {
432             unpackedLibHelper.extractApklib ( artifact );
433         }
434 
435         for ( int i = 0; i < architectures.length; i++ )
436         {
437             String architecture = architectures[ i ];
438 
439             final File[] staticLibs = NativeHelper.listNativeFiles ( artifact, unpackedLibHelper.getUnpackedLibNativesFolder ( artifact ), true, architecture );
440             processBundledLibraries( architecture, libraryDetails, staticLibs );
441 
442             final File[] sharedLibs = NativeHelper.listNativeFiles ( artifact, unpackedLibHelper.getUnpackedLibNativesFolder ( artifact ), false, architecture );
443             processBundledLibraries( architecture, libraryDetails, sharedLibs );
444 
445         }
446 
447     }
448 
449     private void processBundledLibraries ( final String architecture, final LibraryDetails libraryDetails, final File[] staticLibs ) throws IOException, MojoExecutionException
450     {
451         final Artifact artifact = libraryDetails.artifact;
452 
453         for ( File staticLib : staticLibs )
454         {
455             // For each static file, find the HAR artifca
456             libraryDetails.architecture = architecture;
457             libraryDetails.localModule = artifact.getArtifactId ();
458 
459             libraryDetails.libraryPath = staticLib;
460 
461             final String classifier = artifact.getClassifier () == null ? architecture : architecture + "-" + artifact.getClassifier ();
462 
463             libraryDetails.harArtifact = new DefaultArtifact ( artifact.getGroupId (), artifact.getArtifactId (),
464                     artifact.getVersion (), artifact.getScope (),
465                     Const.ArtifactType.NATIVE_HEADER_ARCHIVE, classifier, harArtifactHandler );
466 
467             libraryDetails.localModuleFileName = artifact.getArtifactId ();
468 
469 
470             addLocalModule ( libraryDetails );
471 
472         }
473     }
474 
475     private void addLibraryDetails( StringBuilder makeFile, File libFile, String outputName ) throws IOException
476     {
477         makeFile.append( "LOCAL_PATH := " );
478         makeFile.append( libFile.getParentFile().getAbsolutePath() );
479         makeFile.append( '\n' );
480         makeFile.append( "LOCAL_SRC_FILES := " );
481         makeFile.append( libFile.getName() );
482         makeFile.append( '\n' );
483         makeFile.append( "LOCAL_MODULE_FILENAME := " );
484         if ( "".equals( outputName ) )
485         {
486             makeFile.append( FilenameUtils.removeExtension( libFile.getName() ) );
487         }
488         else
489         {
490             makeFile.append( outputName );
491         }
492         makeFile.append( '\n' );
493     }
494 
495 }