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 | 9 | { |
46 | 9 | this.project = project; |
47 | 9 | this.dependencyGraphBuilder = dependencyGraphBuilder; |
48 | 9 | this.log = log; |
49 | 9 | } |
50 | |
|
51 | |
public static boolean hasStaticNativeLibraryArtifact ( Set<Artifact> resolveNativeLibraryArtifacts ) |
52 | |
{ |
53 | 0 | for ( Artifact resolveNativeLibraryArtifact : resolveNativeLibraryArtifacts ) |
54 | |
{ |
55 | 0 | if ( Const.ArtifactType.NATIVE_IMPLEMENTATION_ARCHIVE.equals( resolveNativeLibraryArtifact.getType() ) ) |
56 | |
{ |
57 | 0 | return true; |
58 | |
} |
59 | 0 | } |
60 | 0 | return false; |
61 | |
} |
62 | |
|
63 | |
public static boolean hasSharedNativeLibraryArtifact ( Set<Artifact> resolveNativeLibraryArtifacts ) |
64 | |
{ |
65 | 0 | for ( Artifact resolveNativeLibraryArtifact : resolveNativeLibraryArtifacts ) |
66 | |
{ |
67 | 0 | if ( Const.ArtifactType.NATIVE_SYMBOL_OBJECT.equals( resolveNativeLibraryArtifact.getType() ) ) |
68 | |
{ |
69 | 0 | return true; |
70 | |
} |
71 | 0 | } |
72 | 0 | return false; |
73 | |
} |
74 | |
|
75 | |
public Set<Artifact> getNativeDependenciesArtifacts( boolean sharedLibraries ) |
76 | |
throws MojoExecutionException |
77 | |
{ |
78 | 0 | log.debug( "Finding native dependencies. shared=" + sharedLibraries ); |
79 | 0 | final Set<Artifact> filteredArtifacts = new LinkedHashSet<Artifact>(); |
80 | 0 | final Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>(); |
81 | |
|
82 | |
|
83 | |
|
84 | |
|
85 | 0 | allArtifacts.addAll( project.getDependencyArtifacts() ); |
86 | |
|
87 | |
|
88 | 0 | allArtifacts.addAll( project.getAttachedArtifacts() ); |
89 | |
|
90 | |
|
91 | |
|
92 | 0 | allArtifacts.addAll( project.getArtifacts() ); |
93 | |
|
94 | 0 | for ( Artifact artifact : allArtifacts ) |
95 | |
{ |
96 | 0 | log.debug( "Checking artifact : " + artifact ); |
97 | |
|
98 | |
|
99 | 0 | if ( isNativeLibrary( sharedLibraries, artifact.getType() ) && artifact.getScope() == null ) |
100 | |
{ |
101 | |
|
102 | 0 | log.debug( "Including attached artifact: " + artifact + ". Artifact scope is not set." ); |
103 | 0 | filteredArtifacts.add( artifact ); |
104 | |
} |
105 | |
else |
106 | |
{ |
107 | 0 | if ( isNativeLibrary( sharedLibraries, artifact.getType() ) && ( |
108 | |
Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_RUNTIME |
109 | |
.equals( artifact.getScope() ) ) ) |
110 | |
{ |
111 | 0 | log.debug( "Including attached artifact: " + artifact + ". Artifact scope is Compile or Runtime." ); |
112 | 0 | filteredArtifacts.add( artifact ); |
113 | |
} |
114 | |
else |
115 | |
{ |
116 | 0 | final String type = artifact.getType(); |
117 | |
|
118 | |
|
119 | |
|
120 | |
|
121 | 0 | if ( AndroidExtension.APKLIB.equals( type ) || AndroidExtension.AAR.equals( type ) ) |
122 | |
{ |
123 | 0 | filteredArtifacts.add( artifact ); |
124 | |
} |
125 | |
else |
126 | |
{ |
127 | |
|
128 | |
} |
129 | |
} |
130 | |
} |
131 | 0 | } |
132 | |
|
133 | 0 | Set<Artifact> transitiveArtifacts = processTransitiveDependencies( project.getDependencies(), sharedLibraries ); |
134 | |
|
135 | 0 | filteredArtifacts.addAll( transitiveArtifacts ); |
136 | |
|
137 | 0 | return filteredArtifacts; |
138 | |
} |
139 | |
|
140 | |
private boolean isNativeLibrary( boolean sharedLibraries, String artifactType ) |
141 | |
{ |
142 | 0 | 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 | 0 | final Set<Artifact> transitiveArtifacts = new LinkedHashSet<Artifact>(); |
152 | 0 | for ( Dependency dependency : dependencies ) |
153 | |
{ |
154 | 0 | if ( !Artifact.SCOPE_PROVIDED.equals( dependency.getScope() ) && !dependency.isOptional() ) |
155 | |
{ |
156 | 0 | final Set<Artifact> transArtifactsFor = processTransitiveDependencies( dependency, sharedLibraries ); |
157 | 0 | log.debug( "Found transitive dependencies for : " + dependency + " transDeps : " + transArtifactsFor ); |
158 | 0 | transitiveArtifacts.addAll( transArtifactsFor ); |
159 | |
} |
160 | 0 | } |
161 | |
|
162 | 0 | return transitiveArtifacts; |
163 | |
|
164 | |
} |
165 | |
|
166 | |
private Set<Artifact> processTransitiveDependencies( Dependency dependency, boolean sharedLibraries ) |
167 | |
throws MojoExecutionException |
168 | |
{ |
169 | 0 | log.debug( "Processing transitive dependencies for : " + dependency ); |
170 | |
|
171 | |
try |
172 | |
{ |
173 | 0 | final Set<Artifact> artifacts = new LinkedHashSet<Artifact>(); |
174 | |
|
175 | 0 | final List<String> exclusionPatterns = new ArrayList<String>(); |
176 | 0 | if ( dependency.getExclusions() != null && !dependency.getExclusions().isEmpty() ) |
177 | |
{ |
178 | 0 | for ( final Exclusion exclusion : dependency.getExclusions() ) |
179 | |
{ |
180 | 0 | exclusionPatterns.add( exclusion.getGroupId() + ":" + exclusion.getArtifactId() ); |
181 | 0 | } |
182 | |
} |
183 | 0 | final ArtifactFilter optionalFilter = new ArtifactFilter() |
184 | 0 | { |
185 | |
@Override |
186 | |
public boolean include( Artifact artifact ) |
187 | |
{ |
188 | 0 | return !artifact.isOptional(); |
189 | |
} |
190 | |
}; |
191 | |
|
192 | 0 | final AndArtifactFilter filter = new AndArtifactFilter(); |
193 | 0 | filter.add( new ExcludesArtifactFilter( exclusionPatterns ) ); |
194 | 0 | filter.add( new OrArtifactFilter( Arrays.<ArtifactFilter>asList( new ScopeArtifactFilter( "compile" ), |
195 | |
new ScopeArtifactFilter( "runtime" ), |
196 | |
new ScopeArtifactFilter( "test" ) ) ) ); |
197 | 0 | filter.add( optionalFilter ); |
198 | |
|
199 | 0 | final DependencyNode node = dependencyGraphBuilder.buildDependencyGraph( project, filter ); |
200 | 0 | final CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor(); |
201 | 0 | node.accept( collectingVisitor ); |
202 | |
|
203 | 0 | final List<DependencyNode> dependencies = collectingVisitor.getNodes(); |
204 | 0 | for ( final DependencyNode dep : dependencies ) |
205 | |
{ |
206 | 0 | final boolean isNativeLibrary = isNativeLibrary( sharedLibraries, dep.getArtifact().getType() ); |
207 | 0 | log.debug( "Processing library : " + dep.getArtifact() + " isNative=" + isNativeLibrary ); |
208 | 0 | if ( isNativeLibrary ) |
209 | |
{ |
210 | 0 | artifacts.add( dep.getArtifact() ); |
211 | |
} |
212 | 0 | } |
213 | |
|
214 | 0 | return artifacts; |
215 | |
} |
216 | 0 | catch ( Exception e ) |
217 | |
{ |
218 | 0 | throw new MojoExecutionException( "Error while processing transitive dependencies", e ); |
219 | |
} |
220 | |
} |
221 | |
|
222 | |
public static void validateNDKVersion( File ndkHomeDir ) throws MojoExecutionException |
223 | |
{ |
224 | 0 | final File ndkSourcePropertiesFile = new File( ndkHomeDir, "source.properties" ); |
225 | |
|
226 | 0 | if ( ndkSourcePropertiesFile.exists() ) |
227 | |
{ |
228 | |
|
229 | 0 | return; |
230 | |
} |
231 | |
|
232 | 0 | final File ndkVersionFile = new File( ndkHomeDir, "RELEASE.TXT" ); |
233 | |
|
234 | 0 | if ( !ndkVersionFile.exists() ) |
235 | |
{ |
236 | 0 | 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 | 0 | String versionStr = FileUtils.readFileToString( ndkVersionFile ); |
244 | 0 | validateNDKVersion( NDK_REQUIRED_VERSION, versionStr ); |
245 | |
} |
246 | 0 | catch ( Exception e ) |
247 | |
{ |
248 | 0 | throw new MojoExecutionException( "Error while extracting NDK version from '" |
249 | |
+ ndkVersionFile.getAbsolutePath() + "'. Please verify your setup! " |
250 | |
+ AndroidNdk.PROPER_NDK_HOME_DIRECTORY_MESSAGE ); |
251 | 0 | } |
252 | 0 | } |
253 | |
|
254 | |
public static void validateNDKVersion( int desiredVersion, String versionStr ) throws MojoExecutionException |
255 | |
{ |
256 | |
|
257 | 14 | int version = 0; |
258 | |
|
259 | 14 | if ( versionStr != null ) |
260 | |
{ |
261 | 14 | versionStr = versionStr.trim(); |
262 | 14 | Pattern pattern = Pattern.compile( "[r]([0-9]{1,3})([a-z]{0,1}).*" ); |
263 | 14 | Matcher m = pattern.matcher( versionStr ); |
264 | 14 | if ( m.matches() ) |
265 | |
{ |
266 | 14 | final String group = m.group( 1 ); |
267 | 14 | version = Integer.parseInt( group ); |
268 | |
} |
269 | |
} |
270 | |
|
271 | 14 | if ( version < desiredVersion ) |
272 | |
{ |
273 | 6 | throw new MojoExecutionException( "You are running an old NDK (version " + versionStr + "), please update " |
274 | |
+ "to at least r'" + desiredVersion + "' or later" ); |
275 | |
} |
276 | 8 | } |
277 | |
|
278 | |
public static String[] getAppAbi( File applicationMakefile ) |
279 | |
{ |
280 | 0 | Scanner scanner = null; |
281 | |
try |
282 | |
{ |
283 | 0 | if ( applicationMakefile != null && applicationMakefile.exists() ) |
284 | |
{ |
285 | 0 | scanner = new Scanner( applicationMakefile ); |
286 | |
try |
287 | |
{ |
288 | 0 | while ( scanner.hasNextLine() ) |
289 | |
{ |
290 | 0 | String line = scanner.nextLine().trim(); |
291 | 0 | if ( line.startsWith( "APP_ABI" ) ) |
292 | |
{ |
293 | 0 | return line.substring( line.indexOf( ":=" ) + 2 ).trim().split( " " ); |
294 | |
} |
295 | 0 | } |
296 | |
} |
297 | |
finally |
298 | |
{ |
299 | 0 | scanner.close(); |
300 | 0 | } |
301 | |
} |
302 | |
} |
303 | 0 | catch ( FileNotFoundException e ) |
304 | |
{ |
305 | |
|
306 | 0 | } |
307 | 0 | 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 | 30 | String classifier = artifact.getClassifier(); |
323 | 30 | if ( classifier != null ) |
324 | |
{ |
325 | |
|
326 | |
|
327 | |
|
328 | |
|
329 | |
|
330 | |
|
331 | 128 | for ( int i = AndroidNdk.NDK_ARCHITECTURES.length - 1; i >= 0; i-- ) |
332 | |
{ |
333 | 126 | String ndkArchitecture = AndroidNdk.NDK_ARCHITECTURES[ i ]; |
334 | 126 | if ( classifier.startsWith( ndkArchitecture ) ) |
335 | |
{ |
336 | 28 | return ndkArchitecture; |
337 | |
} |
338 | |
} |
339 | |
|
340 | |
} |
341 | |
|
342 | 2 | 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 | 0 | String[] resolvedArchitectures = null; |
365 | |
|
366 | 0 | if ( ndkArchitectures != null ) |
367 | |
{ |
368 | 0 | resolvedArchitectures = ndkArchitectures.split( " " ); |
369 | |
} |
370 | |
|
371 | 0 | if ( resolvedArchitectures == null ) |
372 | |
{ |
373 | |
|
374 | 0 | String applicationMakefileToUse = applicationMakefile; |
375 | 0 | if ( applicationMakefileToUse == null ) |
376 | |
{ |
377 | 0 | applicationMakefileToUse = "jni/Application.mk"; |
378 | |
} |
379 | |
|
380 | |
|
381 | 0 | File appMK = new File ( basedir, applicationMakefileToUse ); |
382 | 0 | if ( appMK.exists () ) |
383 | |
{ |
384 | 0 | String[] foundNdkArchitectures = getAppAbi ( appMK ); |
385 | 0 | if ( foundNdkArchitectures != null ) |
386 | |
{ |
387 | 0 | resolvedArchitectures = foundNdkArchitectures; |
388 | |
} |
389 | |
} |
390 | |
} |
391 | |
|
392 | |
|
393 | 0 | if ( resolvedArchitectures == null ) |
394 | |
{ |
395 | 0 | resolvedArchitectures = new String[] { "armeabi" }; |
396 | |
} |
397 | |
|
398 | 0 | List<String> processedResolvedArchitectures = new ArrayList<> ( ); |
399 | |
|
400 | 0 | for ( int i = 0; i < resolvedArchitectures.length; i++ ) |
401 | |
{ |
402 | 0 | final String resolvedArchitecture = resolvedArchitectures[ i ].trim (); |
403 | 0 | if ( resolvedArchitecture.length () > 0 ) |
404 | |
{ |
405 | 0 | processedResolvedArchitectures.add( resolvedArchitecture ); |
406 | |
} |
407 | |
} |
408 | |
|
409 | |
|
410 | 0 | 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 | 16 | 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 | 0 | final String packaging = mavenProject.getPackaging(); |
439 | 0 | 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 | 0 | final List<File> acceptedFiles = new ArrayList<File> ( ); |
445 | 0 | File libsFolder = new File( unpackDirectory, architecture ); |
446 | 0 | if ( libsFolder.exists () ) |
447 | |
{ |
448 | |
|
449 | 0 | return libsFolder.listFiles( new FilenameFilter () |
450 | 0 | { |
451 | |
public boolean accept( final File dir, final String name ) |
452 | |
{ |
453 | 0 | return name.startsWith( "lib" ) && name.endsWith( ( staticLibrary ? ".a" : ".so" ) ); |
454 | |
} |
455 | |
} ); |
456 | |
} |
457 | 0 | return new File[0]; |
458 | |
} |
459 | |
|
460 | |
|
461 | |
} |