1 package com.jayway.maven.plugins.android.phase05compile;
2
3 import com.jayway.maven.plugins.android.common.AetherHelper;
4 import com.jayway.maven.plugins.android.common.AndroidExtension;
5 import com.jayway.maven.plugins.android.common.JarHelper;
6 import com.jayway.maven.plugins.android.common.NativeHelper;
7
8 import org.apache.commons.io.FileUtils;
9 import org.apache.commons.io.FilenameUtils;
10 import org.apache.commons.io.filefilter.TrueFileFilter;
11 import org.apache.maven.artifact.Artifact;
12 import org.apache.maven.artifact.DefaultArtifact;
13 import org.apache.maven.plugin.MojoExecutionException;
14 import org.apache.maven.plugin.logging.Log;
15 import org.sonatype.aether.RepositorySystem;
16 import org.sonatype.aether.RepositorySystemSession;
17 import org.sonatype.aether.repository.RemoteRepository;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.List;
24 import java.util.Set;
25 import java.util.jar.JarEntry;
26 import java.util.jar.JarFile;
27
28
29
30
31
32
33 public class MakefileHelper
34 {
35 public static final String MAKEFILE_CAPTURE_FILE = "ANDROID_MAVEN_PLUGIN_LOCAL_C_INCLUDES_FILE";
36
37 public static final boolean IS_WINDOWS = System.getProperty( "os.name" ).toLowerCase().indexOf( "windows" ) >= 0;
38 public static final String WINDOWS_DRIVE_ROOT_REGEX = "[a-zA-Z]:\\\\";
39
40
41
42
43
44 public static class MakefileHolder
45 {
46 String makeFile;
47 List<File> includeDirectories;
48
49 public MakefileHolder( List<File> includeDirectories, String makeFile )
50 {
51 this.includeDirectories = includeDirectories;
52 this.makeFile = makeFile;
53 }
54
55 public List<File> getIncludeDirectories()
56 {
57 return includeDirectories;
58 }
59
60 public String getMakeFile()
61 {
62 return makeFile;
63 }
64 }
65
66 private Log log;
67 private final RepositorySystem repoSystem;
68 private final RepositorySystemSession repoSession;
69 private final List<RemoteRepository> projectRepos;
70 private final File unpackedApkLibsDirectory;
71
72
73
74
75
76
77
78
79
80 public MakefileHelper( Log log,
81 RepositorySystem repoSystem, RepositorySystemSession repoSession,
82 List<RemoteRepository> projectRepos,
83 File unpackedApkLibsDirectory )
84 {
85 this.log = log;
86 this.repoSystem = repoSystem;
87 this.repoSession = repoSession;
88 this.projectRepos = projectRepos;
89 this.unpackedApkLibsDirectory = unpackedApkLibsDirectory;
90 }
91
92
93
94
95
96
97
98
99 public static void cleanupAfterBuild( MakefileHolder makefileHolder )
100 {
101
102 if ( makefileHolder.getIncludeDirectories() != null )
103 {
104 for ( File file : makefileHolder.getIncludeDirectories() )
105 {
106 try
107 {
108 FileUtils.deleteDirectory( file );
109 }
110 catch ( IOException e )
111 {
112 e.printStackTrace();
113 }
114 }
115 }
116
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130
131 public MakefileHolder createMakefileFromArtifacts( File outputDir, Set<Artifact> artifacts,
132 String ndkArchitecture,
133 boolean useHeaderArchives )
134 throws IOException, MojoExecutionException
135 {
136
137 final StringBuilder makeFile = new StringBuilder( "# Generated by Android Maven Plugin\n" );
138 final List<File> includeDirectories = new ArrayList<File>();
139
140
141
142 makeFile.append( "$(shell echo \"LOCAL_C_INCLUDES=$(LOCAL_C_INCLUDES)\" > $(" + MAKEFILE_CAPTURE_FILE + "))" );
143 makeFile.append( '\n' );
144 makeFile.append( "$(shell echo \"LOCAL_PATH=$(LOCAL_PATH)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
145 makeFile.append( '\n' );
146 makeFile.append( "$(shell echo \"LOCAL_MODULE_FILENAME=$(LOCAL_MODULE_FILENAME)\" >> $("
147 + MAKEFILE_CAPTURE_FILE + "))" );
148 makeFile.append( '\n' );
149 makeFile.append( "$(shell echo \"LOCAL_MODULE=$(LOCAL_MODULE)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
150 makeFile.append( '\n' );
151 makeFile.append( "$(shell echo \"LOCAL_CFLAGS=$(LOCAL_CFLAGS)\" >> $(" + MAKEFILE_CAPTURE_FILE + "))" );
152 makeFile.append( '\n' );
153
154 if ( ! artifacts.isEmpty() )
155 {
156 for ( Artifact artifact : artifacts )
157 {
158 boolean apklibStatic = false;
159
160 makeFile.append( "#\n" );
161 makeFile.append( "# Group ID: " );
162 makeFile.append( artifact.getGroupId() );
163 makeFile.append( '\n' );
164 makeFile.append( "# Artifact ID: " );
165 makeFile.append( artifact.getArtifactId() );
166 makeFile.append( '\n' );
167 makeFile.append( "# Artifact Type: " );
168 makeFile.append( artifact.getType() );
169 makeFile.append( '\n' );
170 makeFile.append( "# Version: " );
171 makeFile.append( artifact.getVersion() );
172 makeFile.append( '\n' );
173 makeFile.append( "include $(CLEAR_VARS)" );
174 makeFile.append( '\n' );
175 makeFile.append( "LOCAL_MODULE := " );
176 makeFile.append( artifact.getArtifactId() );
177 makeFile.append( '\n' );
178 apklibStatic = addLibraryDetails( makeFile, outputDir, artifact, ndkArchitecture );
179 if ( useHeaderArchives )
180 {
181 try
182 {
183 Artifact harArtifact = new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(),
184 artifact.getVersion(), artifact.getScope(), "har", artifact.getClassifier(),
185 artifact.getArtifactHandler() );
186 final Artifact resolvedHarArtifact = AetherHelper
187 .resolveArtifact( harArtifact, repoSystem, repoSession, projectRepos );
188
189 File includeDir = new File( System.getProperty( "java.io.tmpdir" ),
190 "android_maven_plugin_native_includes" + System.currentTimeMillis() + "_"
191 + resolvedHarArtifact.getArtifactId() );
192 includeDir.deleteOnExit();
193 includeDirectories.add( includeDir );
194
195 JarHelper.unjar( new JarFile( resolvedHarArtifact.getFile() ), includeDir,
196 new JarHelper.UnjarListener()
197 {
198 @Override
199 public boolean include( JarEntry jarEntry )
200 {
201 return ! jarEntry.getName().startsWith( "META-INF" );
202 }
203 } );
204
205 makeFile.append( "LOCAL_EXPORT_C_INCLUDES := " );
206 final String str = includeDir.getAbsolutePath();
207 makeFile.append( str );
208 makeFile.append( '\n' );
209
210 if ( log.isDebugEnabled() )
211 {
212 Collection<File> includes = FileUtils.listFiles( includeDir,
213 TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE );
214 log.debug( "Listing LOCAL_EXPORT_C_INCLUDES for " + artifact.getId() + ": " + includes );
215 }
216 }
217 catch ( Exception e )
218 {
219 throw new MojoExecutionException(
220 "Error while resolving header archive file for: " + artifact.getArtifactId(), e );
221 }
222 }
223 if ( "a".equals( artifact.getType() ) || apklibStatic )
224 {
225 makeFile.append( "include $(PREBUILT_STATIC_LIBRARY)\n" );
226 }
227 else
228 {
229 makeFile.append( "include $(PREBUILT_SHARED_LIBRARY)\n" );
230 }
231 }
232 }
233
234 return new MakefileHolder( includeDirectories, makeFile.toString() );
235 }
236
237 private boolean addLibraryDetails( StringBuilder makeFile, File outputDir,
238 Artifact artifact, String ndkArchitecture ) throws IOException
239 {
240 boolean apklibStatic = false;
241 if ( AndroidExtension.APKLIB.equals( artifact.getType() ) )
242 {
243 String classifier = artifact.getClassifier();
244 String architecture = ( classifier != null ) ? classifier : ndkArchitecture;
245
246
247
248
249 File[] staticLibs = NativeHelper.listNativeFiles( artifact, unpackedApkLibsDirectory,
250 architecture, true );
251 if ( staticLibs != null && staticLibs.length > 0 )
252 {
253 int libIdx = findApklibNativeLibrary( staticLibs, artifact.getArtifactId() );
254 apklibStatic = true;
255 addLibraryDetails( makeFile, outputDir, staticLibs[libIdx] );
256 }
257 else
258 {
259 File[] sharedLibs = NativeHelper.listNativeFiles( artifact, unpackedApkLibsDirectory,
260 architecture, false );
261 if ( sharedLibs == null )
262 {
263 throw new IOException( "Failed to find any library file in APKLIB" );
264 }
265 int libIdx = findApklibNativeLibrary( sharedLibs, artifact.getArtifactId() );
266 addLibraryDetails( makeFile, outputDir, sharedLibs[libIdx] );
267 }
268 }
269 else
270 {
271 addLibraryDetails( makeFile, outputDir, artifact.getFile() );
272 }
273
274 return apklibStatic;
275 }
276
277 private void addLibraryDetails( StringBuilder makeFile, File outputDir, File libFile )
278 throws IOException
279 {
280 String localPath = resolveRelativePath( outputDir, libFile );
281 localPath = localPath.substring( 0, localPath.indexOf( libFile.getName() ) - 1 );
282
283 makeFile.append( "LOCAL_PATH := " );
284 makeFile.append( localPath );
285 makeFile.append( '\n' );
286 makeFile.append( "LOCAL_SRC_FILES := " );
287 makeFile.append( libFile.getName() );
288 makeFile.append( '\n' );
289 makeFile.append( "LOCAL_MODULE_FILENAME := " );
290 makeFile.append( FilenameUtils.removeExtension( libFile.getName() ) );
291 makeFile.append( '\n' );
292 }
293
294
295
296
297
298
299 private int findApklibNativeLibrary( File[] libs, String artifactName ) throws IOException
300 {
301 int libIdx = -1;
302
303 if ( libs.length == 1 )
304
305 {
306 libIdx = 0;
307 }
308 else
309 {
310 log.info( "Found multiple library files, looking for name match with artifact" );
311 StringBuilder sb = new StringBuilder();
312 for ( int i = 0; i < libs.length; i++ )
313 {
314 if ( sb.length() != 0 )
315 {
316 sb.append( ", " );
317 }
318 sb.append( libs[i].getName() );
319 if ( libs[i].getName().startsWith( "lib" + artifactName ) )
320 {
321 if ( libIdx != -1 )
322 {
323
324 throw new IOException( "Found multiple libraries matching artifact name " + artifactName
325 + ". Please use unique artifact/library names." );
326
327 }
328 libIdx = i;
329 }
330 }
331 if ( libIdx < 0 )
332 {
333 throw new IOException( "Unable to determine main library from " + sb.toString()
334 + " APKLIB should contain only 1 library or a library matching the artifact name" );
335 }
336 }
337 return libIdx;
338 }
339
340
341
342
343
344
345
346
347 protected static String resolveRelativePath( File outputDirectory, File file ) throws IOException
348 {
349 String resolvedPath = file.getCanonicalPath();
350
351 String strOutputDirectoryPath = outputDirectory.getCanonicalPath();
352 String strFilePath = file.getCanonicalPath();
353
354
355 if ( strFilePath.startsWith( strOutputDirectoryPath ) )
356 {
357
358 resolvedPath = strFilePath.substring( strOutputDirectoryPath.length() + 1 );
359 }
360 else
361 {
362
363 List<String> outputDirectoryPathParts = splitPath( outputDirectory.getCanonicalFile() );
364 List<String> filePathParts = splitPath( file.getCanonicalFile() );
365 int commonDepth = 0;
366 int maxCommonDepth = Math.min( outputDirectoryPathParts.size(), filePathParts.size() );
367 for ( int i = 0;
368 ( i < maxCommonDepth )
369 && outputDirectoryPathParts.get( i ).equals( filePathParts.get( i ) );
370 i++ )
371 {
372 commonDepth++;
373 }
374
375 if ( commonDepth > 0 )
376 {
377 final StringBuilder stringBuilder = new StringBuilder();
378 stringBuilder.append( ".." );
379 for ( int i = 0; i < outputDirectoryPathParts.size() - commonDepth - 1; i++ )
380 {
381 stringBuilder.append( File.separator );
382 stringBuilder.append( ".." );
383 }
384 for ( int i = commonDepth; i < filePathParts.size(); i++ )
385 {
386 stringBuilder.append( File.separator );
387 stringBuilder.append( filePathParts.get( i ) );
388 }
389 resolvedPath = stringBuilder.toString();
390 }
391 else
392 {
393 if ( IS_WINDOWS )
394 {
395
396
397 throw new IOException( "Unable to resolve relative path across windows drives" );
398 }
399
400
401 final StringBuilder stringBuilder = new StringBuilder();
402
403 File depthCheck = outputDirectory.getParentFile();
404 while ( depthCheck != null )
405 {
406 if ( stringBuilder.length() > 0 )
407 {
408 stringBuilder.append( File.separator );
409 }
410 stringBuilder.append( ".." );
411 depthCheck = depthCheck.getParentFile();
412 }
413
414 resolvedPath = stringBuilder.toString() + strFilePath;
415 }
416 }
417
418
419 return resolvedPath;
420 }
421
422
423
424
425
426
427 protected static List<String> splitPath( File f )
428 {
429 List<String> result;
430 File parent = f.getParentFile();
431 if ( parent == null )
432 {
433 result = new ArrayList<String>();
434 if ( f.getName().length() > 0 )
435 {
436
437
438 result.add( f.getName() );
439 }
440 else if ( IS_WINDOWS )
441 {
442 String strF = f.toString();
443 if ( strF.matches( WINDOWS_DRIVE_ROOT_REGEX ) )
444 {
445
446
447 result.add( strF.substring( 0, strF.length() - 1 ).toUpperCase() );
448 }
449 }
450 }
451 else
452 {
453 result = splitPath( parent );
454 result.add( f.getName() );
455 }
456 return result;
457 }
458
459
460
461
462
463
464
465
466
467 public String createLibraryList( Set<Artifact> resolvedLibraryList,
468 String ndkArchitecture,
469 boolean staticLibrary )
470 {
471 StringBuilder sb = new StringBuilder();
472
473 for ( Artifact a : resolvedLibraryList )
474 {
475 if ( staticLibrary && "a".equals( a.getType() ) )
476 {
477 sb.append( a.getArtifactId() );
478 }
479 if ( ! staticLibrary && "so".equals( a.getType() ) )
480 {
481 sb.append( a.getArtifactId() );
482 }
483 if ( AndroidExtension.APKLIB.equals( a.getType() ) )
484 {
485 File[] libFiles = NativeHelper.listNativeFiles( a, unpackedApkLibsDirectory,
486 ndkArchitecture, staticLibrary );
487 if ( libFiles != null && libFiles.length > 0 )
488 {
489 sb.append( a.getArtifactId() );
490 }
491
492 }
493 sb.append( " " );
494 }
495
496 return sb.toString();
497 }
498 }