1 package com.simpligility.maven.plugins.android.common;
2
3 import com.google.common.io.PatternFilenameFilter;
4 import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
5 import com.simpligility.maven.plugins.android.AndroidNdk;
6
7 import org.apache.maven.artifact.Artifact;
8 import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
9 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
10 import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
11 import org.apache.maven.artifact.resolver.filter.OrArtifactFilter;
12 import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
13 import org.apache.maven.model.Dependency;
14 import org.apache.maven.model.Exclusion;
15 import org.apache.maven.plugin.MojoExecutionException;
16 import org.apache.maven.plugin.logging.Log;
17 import org.apache.maven.project.MavenProject;
18 import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
19 import org.apache.maven.shared.dependency.graph.DependencyNode;
20 import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
21
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Scanner;
29 import java.util.Set;
30
31 import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
32 import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
33
34
35
36
37 public class NativeHelper
38 {
39
40 public static final int NDK_REQUIRED_VERSION = 7;
41
42 private MavenProject project;
43 private DependencyGraphBuilder dependencyGraphBuilder;
44 private Log log;
45
46 public NativeHelper( MavenProject project, DependencyGraphBuilder dependencyGraphBuilder, Log log )
47 {
48 this.project = project;
49 this.dependencyGraphBuilder = dependencyGraphBuilder;
50 this.log = log;
51 }
52
53 public Set<Artifact> getNativeDependenciesArtifacts(
54 AbstractAndroidMojo mojo, File unpackDirectory, boolean sharedLibraries )
55 throws MojoExecutionException
56 {
57 log.debug( "Finding native dependencies. UnpackFolder=" + unpackDirectory + " shared=" + sharedLibraries );
58 final Set<Artifact> filteredArtifacts = new LinkedHashSet<Artifact>();
59 final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>();
60
61
62
63
64 allArtifacts.addAll( project.getDependencyArtifacts() );
65
66
67
68 allArtifacts.addAll( project.getArtifacts() );
69
70 for ( Artifact artifact : allArtifacts )
71 {
72 final boolean isNativeLibrary = isNativeLibrary( sharedLibraries, artifact.getType() );
73
74 log.debug( "Checking artifact : " + artifact );
75
76
77 if ( isNativeLibrary && artifact.getScope() == null )
78 {
79
80 log.debug( "Including attached artifact: " + artifact + ". Artifact scope is not set." );
81 filteredArtifacts.add( artifact );
82 }
83 else
84 {
85 if ( isNativeLibrary && (
86 Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_RUNTIME
87 .equals( artifact.getScope() ) ) )
88 {
89 log.debug( "Including attached artifact: " + artifact + ". Artifact scope is Compile or Runtime." );
90 filteredArtifacts.add( artifact );
91 }
92 else
93 {
94 final String type = artifact.getType();
95
96 if ( APKLIB.equals( type ) || AAR.equals( type ) )
97 {
98
99 final File libsFolder;
100 if ( mojo != null )
101 {
102 libsFolder = mojo.getUnpackedLibNativesFolder( artifact );
103 }
104 else
105 {
106
107 libsFolder = new File(
108 AbstractAndroidMojo.getLibraryUnpackDirectory( unpackDirectory, artifact ),
109 AAR.equals( type ) ? "jni" : "libs" );
110 }
111
112 if ( !libsFolder.exists() )
113 {
114 log.debug( "Skipping " + libsFolder.getAbsolutePath() + " for native artifacts" );
115 continue;
116 }
117
118 if ( !libsFolder.isDirectory() )
119 {
120 continue;
121 }
122
123 log.debug( "Checking " + libsFolder.getAbsolutePath() + " for native artifacts" );
124
125
126
127 if ( libsFolder.list( new PatternFilenameFilter( "^.*(?<!(?i)\\.jar)$" ) ).length > 0 )
128 {
129 log.debug( "Including attached artifact: " + artifact + ". Artifact is "
130 + artifact.getType() );
131 filteredArtifacts.add( artifact );
132 }
133 }
134 else if ( ! "jar".equals( type ) )
135 {
136 log.debug( "Not checking " + type + " for native artifacts" );
137 }
138 }
139 }
140 }
141
142 Set<Artifact> transitiveArtifacts = processTransitiveDependencies( project.getDependencies(), sharedLibraries );
143
144 filteredArtifacts.addAll( transitiveArtifacts );
145
146 return filteredArtifacts;
147 }
148
149 private boolean isNativeLibrary( boolean sharedLibraries, String artifactType )
150 {
151 return ( sharedLibraries
152 ? Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( artifactType )
153 : Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( artifactType )
154 );
155 }
156
157 private Set<Artifact> processTransitiveDependencies( List<Dependency> dependencies, boolean sharedLibraries )
158 throws MojoExecutionException
159 {
160 final Set<Artifact> transitiveArtifacts = new LinkedHashSet<Artifact>();
161 for ( Dependency dependency : dependencies )
162 {
163 if ( ! Artifact.SCOPE_PROVIDED.equals( dependency.getScope() ) && ! dependency.isOptional() )
164 {
165 final Set<Artifact> transArtifactsFor = processTransitiveDependencies( dependency, sharedLibraries );
166 log.debug( "Found transitive dependencies for : " + dependency + " transDeps : " + transArtifactsFor );
167 transitiveArtifacts.addAll( transArtifactsFor );
168 }
169 }
170
171 return transitiveArtifacts;
172
173 }
174
175 private Set<Artifact> processTransitiveDependencies( Dependency dependency, boolean sharedLibraries )
176 throws MojoExecutionException
177 {
178 try
179 {
180 final Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
181
182 final List<String> exclusionPatterns = new ArrayList<String>();
183 if ( dependency.getExclusions() != null && ! dependency.getExclusions().isEmpty() )
184 {
185 for ( final Exclusion exclusion : dependency.getExclusions() )
186 {
187 exclusionPatterns.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() );
188 }
189 }
190 final ArtifactFilter optionalFilter = new ArtifactFilter()
191 {
192 @Override
193 public boolean include( Artifact artifact )
194 {
195 return !artifact.isOptional();
196 }
197 };
198
199 final AndArtifactFilter filter = new AndArtifactFilter();
200 filter.add( new ExcludesArtifactFilter( exclusionPatterns ) );
201 filter.add( new OrArtifactFilter( Arrays.<ArtifactFilter>asList( new ScopeArtifactFilter( "compile" ),
202 new ScopeArtifactFilter( "runtime" ),
203 new ScopeArtifactFilter( "test" ) ) ) );
204 filter.add( optionalFilter );
205
206 final DependencyNode node = dependencyGraphBuilder.buildDependencyGraph( project, filter );
207 final CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
208 node.accept( collectingVisitor );
209
210 final List<DependencyNode> dependencies = collectingVisitor.getNodes();
211 for ( final DependencyNode dep : dependencies )
212 {
213 final boolean isNativeLibrary = isNativeLibrary( sharedLibraries, dep.getArtifact().getType() );
214 if ( isNativeLibrary )
215 {
216 artifacts.add( dep.getArtifact() );
217 }
218 }
219
220 return artifacts;
221 }
222 catch ( Exception e )
223 {
224 throw new MojoExecutionException( "Error while processing transitive dependencies", e );
225 }
226 }
227
228
229 public static String[] getAppAbi( File applicationMakefile )
230 {
231 Scanner scanner = null;
232 try
233 {
234 if ( applicationMakefile != null && applicationMakefile.exists() )
235 {
236 scanner = new Scanner( applicationMakefile );
237 while ( scanner.hasNextLine( ) )
238 {
239 String line = scanner.nextLine().trim();
240 if ( line.startsWith( "APP_ABI" ) )
241 {
242 return line.substring( line.indexOf( ":=" ) + 2 ).trim().split( " " );
243 }
244 }
245 }
246 }
247 catch ( FileNotFoundException e )
248 {
249
250 }
251 finally
252 {
253 if ( scanner != null )
254 {
255 scanner.close();
256 }
257 }
258 return null;
259 }
260
261
262
263
264
265
266
267
268
269
270
271 public static String extractArchitectureFromArtifact( Artifact artifact, final String defaultArchitecture )
272 {
273 String classifier = artifact.getClassifier();
274 if ( classifier != null )
275 {
276
277
278
279
280
281
282 for ( int i = AndroidNdk.NDK_ARCHITECTURES.length - 1; i >= 0; i-- )
283 {
284 String ndkArchitecture = AndroidNdk.NDK_ARCHITECTURES[i];
285 if ( classifier.startsWith( ndkArchitecture ) )
286 {
287 return ndkArchitecture;
288 }
289 }
290
291 }
292
293 return defaultArchitecture;
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315 public static String[] getNdkArchitectures( final String ndkArchitectures, final String applicationMakefile,
316 final File basedir )
317 throws MojoExecutionException
318 {
319
320 if ( ndkArchitectures != null )
321 {
322 return ndkArchitectures.split( " " );
323 }
324
325
326 String applicationMakefileToUse = applicationMakefile;
327 if ( applicationMakefileToUse == null )
328 {
329 applicationMakefileToUse = "jni/Application.mk";
330 }
331
332
333 File appMK = new File( basedir, applicationMakefileToUse );
334 if ( appMK.exists() )
335 {
336 String[] foundNdkArchitectures = getAppAbi( appMK );
337 if ( foundNdkArchitectures != null )
338 {
339 return foundNdkArchitectures;
340 }
341 }
342
343
344 return new String[] { "armeabi" };
345 }
346
347
348
349
350
351
352
353
354
355
356
357 public static boolean artifactHasHardwareArchitecture( Artifact artifact, String ndkArchitecture,
358 String defaultArchitecture )
359 {
360 return Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( artifact.getType() )
361 && ndkArchitecture.equals( extractArchitectureFromArtifact( artifact, defaultArchitecture ) );
362 }
363
364 }