1 package com.simpligility.maven.plugins.androidndk.common;
2
3 import com.simpligility.maven.plugins.androidndk.AndroidNdk;
4 import org.apache.commons.io.FileUtils;
5 import org.apache.maven.artifact.Artifact;
6 import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
7 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
8 import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
9 import org.apache.maven.artifact.resolver.filter.OrArtifactFilter;
10 import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
11 import org.apache.maven.model.Dependency;
12 import org.apache.maven.model.Exclusion;
13 import org.apache.maven.plugin.MojoExecutionException;
14 import org.apache.maven.plugin.logging.Log;
15 import org.apache.maven.project.MavenProject;
16 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
17 import org.apache.maven.shared.dependency.graph.DependencyNode;
18 import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
19
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FilenameFilter;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Scanner;
28 import java.util.Set;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32
33
34
35 public class NativeHelper
36 {
37
38 public static final int NDK_REQUIRED_VERSION = 7;
39
40 private MavenProject project;
41 private DependencyGraphBuilder dependencyGraphBuilder;
42 private Log log;
43
44 public NativeHelper( MavenProject project, DependencyGraphBuilder dependencyGraphBuilder, Log log )
45 {
46 this.project = project;
47 this.dependencyGraphBuilder = dependencyGraphBuilder;
48 this.log = log;
49 }
50
51 public static boolean hasStaticNativeLibraryArtifact ( Set<Artifact> resolveNativeLibraryArtifacts )
52 {
53 for ( Artifact resolveNativeLibraryArtifact : resolveNativeLibraryArtifacts )
54 {
55 if ( Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( resolveNativeLibraryArtifact.getType() ) )
56 {
57 return true;
58 }
59 }
60 return false;
61 }
62
63 public static boolean hasSharedNativeLibraryArtifact ( Set<Artifact> resolveNativeLibraryArtifacts )
64 {
65 for ( Artifact resolveNativeLibraryArtifact : resolveNativeLibraryArtifacts )
66 {
67 if ( Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( resolveNativeLibraryArtifact.getType() ) )
68 {
69 return true;
70 }
71 }
72 return false;
73 }
74
75 public Set<Artifact> getNativeDependenciesArtifacts( boolean sharedLibraries )
76 throws MojoExecutionException
77 {
78 log.debug( "Finding native dependencies. shared=" + sharedLibraries );
79 final Set<Artifact> filteredArtifacts = new LinkedHashSet<Artifact>();
80 final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>();
81
82
83
84
85 allArtifacts.addAll( project.getDependencyArtifacts() );
86
87
88 allArtifacts.addAll( project.getAttachedArtifacts() );
89
90
91
92 allArtifacts.addAll( project.getArtifacts() );
93
94 for ( Artifact artifact : allArtifacts )
95 {
96 log.debug( "Checking artifact : " + artifact );
97
98
99 if ( isNativeLibrary( sharedLibraries, artifact.getType() ) && artifact.getScope() == null )
100 {
101
102 log.debug( "Including attached artifact: " + artifact + ". Artifact scope is not set." );
103 filteredArtifacts.add( artifact );
104 }
105 else
106 {
107 if ( isNativeLibrary( sharedLibraries, artifact.getType() ) && (
108 Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_RUNTIME
109 .equals( artifact.getScope() ) ) )
110 {
111 log.debug( "Including attached artifact: " + artifact + ". Artifact scope is Compile or Runtime." );
112 filteredArtifacts.add( artifact );
113 }
114 else
115 {
116 final String type = artifact.getType();
117
118
119
120
121 if ( AndroidExtension.APKLIB.equals( type ) || AndroidExtension.AAR.equals( type ) )
122 {
123 filteredArtifacts.add( artifact );
124 }
125 else
126 {
127
128 }
129 }
130 }
131 }
132
133 Set<Artifact> transitiveArtifacts = processTransitiveDependencies( project.getDependencies(), sharedLibraries );
134
135 filteredArtifacts.addAll( transitiveArtifacts );
136
137 return filteredArtifacts;
138 }
139
140 private boolean isNativeLibrary( boolean sharedLibraries, String artifactType )
141 {
142 return ( sharedLibraries
143 ? Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( artifactType )
144 : Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( artifactType )
145 );
146 }
147
148 private Set<Artifact> processTransitiveDependencies( List<Dependency> dependencies, boolean sharedLibraries )
149 throws MojoExecutionException
150 {
151 final Set<Artifact> transitiveArtifacts = new LinkedHashSet<Artifact>();
152 for ( Dependency dependency : dependencies )
153 {
154 if ( !Artifact.SCOPE_PROVIDED.equals( dependency.getScope() ) && !dependency.isOptional() )
155 {
156 final Set<Artifact> transArtifactsFor = processTransitiveDependencies( dependency, sharedLibraries );
157 log.debug( "Found transitive dependencies for : " + dependency + " transDeps : " + transArtifactsFor );
158 transitiveArtifacts.addAll( transArtifactsFor );
159 }
160 }
161
162 return transitiveArtifacts;
163
164 }
165
166 private Set<Artifact> processTransitiveDependencies( Dependency dependency, boolean sharedLibraries )
167 throws MojoExecutionException
168 {
169 log.debug( "Processing transitive dependencies for : " + dependency );
170
171 try
172 {
173 final Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
174
175 final List<String> exclusionPatterns = new ArrayList<String>();
176 if ( dependency.getExclusions() != null && !dependency.getExclusions().isEmpty() )
177 {
178 for ( final Exclusion exclusion : dependency.getExclusions() )
179 {
180 exclusionPatterns.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() );
181 }
182 }
183 final ArtifactFilter optionalFilter = new ArtifactFilter()
184 {
185 @Override
186 public boolean include( Artifact artifact )
187 {
188 return !artifact.isOptional();
189 }
190 };
191
192 final AndArtifactFilter filter = new AndArtifactFilter();
193 filter.add( new ExcludesArtifactFilter( exclusionPatterns ) );
194 filter.add( new OrArtifactFilter( Arrays.<ArtifactFilter>asList( new ScopeArtifactFilter( "compile" ),
195 new ScopeArtifactFilter( "runtime" ),
196 new ScopeArtifactFilter( "test" ) ) ) );
197 filter.add( optionalFilter );
198
199 final DependencyNode node = dependencyGraphBuilder.buildDependencyGraph( project, filter );
200 final CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
201 node.accept( collectingVisitor );
202
203 final List<DependencyNode> dependencies = collectingVisitor.getNodes();
204 for ( final DependencyNode dep : dependencies )
205 {
206 final boolean isNativeLibrary = isNativeLibrary( sharedLibraries, dep.getArtifact().getType() );
207 log.debug( "Processing library : " + dep.getArtifact() + " isNative=" + isNativeLibrary );
208 if ( isNativeLibrary )
209 {
210 artifacts.add( dep.getArtifact() );
211 }
212 }
213
214 return artifacts;
215 }
216 catch ( Exception e )
217 {
218 throw new MojoExecutionException( "Error while processing transitive dependencies", e );
219 }
220 }
221
222 public static void validateNDKVersion( File ndkHomeDir ) throws MojoExecutionException
223 {
224 final File ndkSourcePropertiesFile = new File( ndkHomeDir, "source.properties" );
225
226 if ( ndkSourcePropertiesFile.exists() )
227 {
228
229 return;
230 }
231
232 final File ndkVersionFile = new File( ndkHomeDir, "RELEASE.TXT" );
233
234 if ( !ndkVersionFile.exists() )
235 {
236 throw new MojoExecutionException(
237 "Could not locate RELEASE.TXT in the Android NDK base directory '" + ndkHomeDir.getAbsolutePath()
238 + "'. Please verify your setup! " + AndroidNdk.PROPER_NDK_HOME_DIRECTORY_MESSAGE );
239 }
240
241 try
242 {
243 String versionStr = FileUtils.readFileToString( ndkVersionFile );
244 validateNDKVersion( NDK_REQUIRED_VERSION, versionStr );
245 }
246 catch ( Exception e )
247 {
248 throw new MojoExecutionException( "Error while extracting NDK version from '"
249 + ndkVersionFile.getAbsolutePath() + "'. Please verify your setup! "
250 + AndroidNdk.PROPER_NDK_HOME_DIRECTORY_MESSAGE );
251 }
252 }
253
254 public static void validateNDKVersion( int desiredVersion, String versionStr ) throws MojoExecutionException
255 {
256
257 int version = 0;
258
259 if ( versionStr != null )
260 {
261 versionStr = versionStr.trim();
262 Pattern pattern = Pattern.compile( "[r]([0-9]{1,3})([a-z]{0,1}).*" );
263 Matcher m = pattern.matcher( versionStr );
264 if ( m.matches() )
265 {
266 final String group = m.group( 1 );
267 version = Integer.parseInt( group );
268 }
269 }
270
271 if ( version < desiredVersion )
272 {
273 throw new MojoExecutionException( "You are running an old NDK (version " + versionStr + "), please update "
274 + "to at least r'" + desiredVersion + "' or later" );
275 }
276 }
277
278 public static String[] getAppAbi( File applicationMakefile )
279 {
280 Scanner scanner = null;
281 try
282 {
283 if ( applicationMakefile != null && applicationMakefile.exists() )
284 {
285 scanner = new Scanner( applicationMakefile );
286 try
287 {
288 while ( scanner.hasNextLine() )
289 {
290 String line = scanner.nextLine().trim();
291 if ( line.startsWith( "APP_ABI" ) )
292 {
293 return line.substring( line.indexOf( ":=" ) + 2 ).trim().split( " " );
294 }
295 }
296 }
297 finally
298 {
299 scanner.close();
300 }
301 }
302 }
303 catch ( FileNotFoundException e )
304 {
305
306 }
307 return null;
308 }
309
310
311
312
313
314
315
316
317
318
319
320 public static String extractArchitectureFromArtifact( Artifact artifact, final String defaultArchitecture )
321 {
322 String classifier = artifact.getClassifier();
323 if ( classifier != null )
324 {
325
326
327
328
329
330
331 for ( int i = AndroidNdk.NDK_ARCHITECTURES.length - 1; i >= 0; i-- )
332 {
333 String ndkArchitecture = AndroidNdk.NDK_ARCHITECTURES[ i ];
334 if ( classifier.startsWith( ndkArchitecture ) )
335 {
336 return ndkArchitecture;
337 }
338 }
339
340 }
341
342 return defaultArchitecture;
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 public static String[] getNdkArchitectures( final String ndkArchitectures, final String applicationMakefile, final File basedir ) throws MojoExecutionException
363 {
364 String[] resolvedArchitectures = null;
365
366 if ( ndkArchitectures != null )
367 {
368 resolvedArchitectures = ndkArchitectures.split( " " );
369 }
370
371 if ( resolvedArchitectures == null )
372 {
373
374 String applicationMakefileToUse = applicationMakefile;
375 if ( applicationMakefileToUse == null )
376 {
377 applicationMakefileToUse = "jni/Application.mk";
378 }
379
380
381 File appMK = new File ( basedir, applicationMakefileToUse );
382 if ( appMK.exists () )
383 {
384 String[] foundNdkArchitectures = getAppAbi ( appMK );
385 if ( foundNdkArchitectures != null )
386 {
387 resolvedArchitectures = foundNdkArchitectures;
388 }
389 }
390 }
391
392
393 if ( resolvedArchitectures == null )
394 {
395 resolvedArchitectures = new String[] { "armeabi" };
396 }
397
398 List<String> processedResolvedArchitectures = new ArrayList<> ( );
399
400 for ( int i = 0; i < resolvedArchitectures.length; i++ )
401 {
402 final String resolvedArchitecture = resolvedArchitectures[ i ].trim ();
403 if ( resolvedArchitecture.length () > 0 )
404 {
405 processedResolvedArchitectures.add( resolvedArchitecture );
406 }
407 }
408
409
410 return processedResolvedArchitectures.toArray ( new String[ processedResolvedArchitectures.size () ] );
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424 public static boolean artifactHasHardwareArchitecture( Artifact artifact, String ndkArchitecture,
425 String defaultArchitecture )
426 {
427 return Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( artifact.getType() )
428 && ndkArchitecture.equals( extractArchitectureFromArtifact( artifact, defaultArchitecture ) );
429 }
430
431
432
433
434
435
436 public static boolean isNativeArtifactProject( MavenProject mavenProject )
437 {
438 final String packaging = mavenProject.getPackaging();
439 return Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( packaging ) || Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( packaging );
440 }
441
442 public static File[] listNativeFiles( Artifact artifact, File unpackDirectory, final boolean staticLibrary, final String architecture )
443 {
444 final List<File> acceptedFiles = new ArrayList<File> ( );
445 File libsFolder = new File( unpackDirectory, architecture );
446 if ( libsFolder.exists () )
447 {
448
449 return libsFolder.listFiles( new FilenameFilter ()
450 {
451 public boolean accept( final File dir, final String name )
452 {
453 return name.startsWith( "lib" ) && name.endsWith( ( staticLibrary ? ".a" : ".so" ) );
454 }
455 } );
456 }
457 return new File[0];
458 }
459
460
461 }