Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Issue 97 #89

Merged
merged 10 commits into from

2 participants

@pa314159

Here is an workaround addressing the problem with missing resources in META-INF.

As already stated, the problem is not in android-maven-plugin itself but in com.android.sdklib.build.ApkBuilder which filters any path starting with META-INF .

This patch copies the META-INF based resources from dependencies into the APK as specified in the plugin configuration by ${android.metaIncludes}.

There are still issues with the obfuscated code because proguard is executed in an earlier stage and any resource supposed to be modified by proguard will remain unchanged

pom.xml
@@ -19,14 +19,14 @@
<modelVersion>4.0.0</modelVersion>
@mosabua Owner
mosabua added a note

Please update your pull request to not include these pom changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...way/maven/plugins/android/phase09package/ApkMojo.java
@@ -368,8 +482,9 @@ public boolean accept(File dir, String name) {
}
private File removeDuplicatesFromJar(File in, List<String> duplicates) {
- File target = new File(project.getBasedir(), "target");
- File tmp = new File(target, "unpacked-embedded-jars");
+// File target = new File(project.getBasedir(), "target");
+ String target = project.getBuild().getOutputDirectory();
+ File tmp = new File(target, "unpacked-wembedded-jars");
@mosabua Owner
mosabua added a note

is this a typo?

yes, it was a typo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...way/maven/plugins/android/phase09package/ApkMojo.java
((24 lines not shown))
+ /**
+ * <p>
+ * Pattern for additional META-INF resources to be packaged into the apk.
+ * </p>
+ * <p>
+ * The APK builder filters these resources and doesn't include them into the apk.
+ * </p>
+ * <p>
+ * This leads to bad behaviour of dependent libraries relying on these resources, for instance service discovery
+ * doesn't work.
+ * </p>
+ * <p>
+ * See also <a href="http://code.google.com/p/maven-android-plugin/issues/detail?id=97">Issue 97</a>
+ * </p>
+ *
+ * @parameter expression="${android.metaIncludes}" default-value=""
@mosabua Owner
mosabua added a note

This should be updated to follow the config parameter convention as documented in code conventions on the wiki with an Apk config class probably and an expression of android.apk.metaIncludes

I'm aware of that convention, but I followed the convention used by other configuration parameters used by this mojo, see for instance extractDuplicates.

Creating a Dex configuration should cover the rest of the Apk configurations as well - whichever is applicable

@mosabua Owner
mosabua added a note

There is a dex one already... I would have to look. In general though the old "convention" is only there because we have not gotten around to completely clean it up yet. So any new parameter should follow the new approach..

I meant an Apk configuration

I added it with the last commit and also included two "old" configurations plus a deprecation mechanism

@mosabua Owner
mosabua added a note

Thats great. I hope to test this out a bit more soon and merge it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...way/maven/plugins/android/phase09package/ApkMojo.java
@@ -255,8 +256,121 @@ void createApkFile(File outputFile, boolean signWithDebugKeyStore) throws MojoEx
doAPKWithCommand(outputFile, dexFile, zipArchive, sourceFolders, jarFiles,
nativeFolders, signWithDebugKeyStore);
}
+
+// ISSUE-97
@mosabua Owner
mosabua added a note

remove these comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...way/maven/plugins/android/phase09package/ApkMojo.java
((19 lines not shown))
}
+// ISSUE-97
+// vvv
@mosabua Owner
mosabua added a note

remove this comment please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@mosabua
Owner

So sorry it has taken so long to update. I just got the proguard pull request and the related config work on the proguard, run, pull and push mojos done. There is still lots outstanding ... could you refactor your approach here to use the new ConfigHandler that is now in master?

@mosabua
Owner

Are you going to refactor this as requested to be based off the latest master so we can pull this in?

@mosabua
Owner

Ping?

@pa314159

Pong!

Back to this, been busy last weeks and I'm glad to see my ConfigHelper has been improved

This patch is far behind the original master (3.2.x)... How should I proceed with?

@mosabua
Owner

Maybe pull down master to your machine. Create a new branch off master and apply a patch with the required changes to it. Once you got it tested create a new pull request. And we will just close this one without merging.

pa314159 added some commits
@pa314159 pa314159 Merge branch 'master' into issue-97 69e347f
@pa314159 pa314159 @ConfigPojo improvement
added ability to specify the parsed parameter prefix in the @ConfigPojo
annotation
87a2d05
@pa314159

Done, I managed to merge changes from the original master, so we can keep this pull request as is...

You may also have a look at the "modified" branch of this fork, which completely solves the issues with META-INF inclusions. Fill free to get anything you want from there.

@pa314159 pa314159 Updated Apk with the new ConfigHandler
Previous code used the ConfigHelper which was able to handle
configuration parameters deprecation.

Since the new ConfigHandler doesn't provide this mechanism - or I could
not find it - only the new "metaIncludes" parameter follows the POJO
configuration rules.

Other parameters are kept as they are and should be modified by the actual
maintainer of this class.
8c86102
@mosabua
Owner

Looks good. Pulling in.

@mosabua mosabua merged commit 5198b8c into simpligility:master
@mosabua
Owner

Btw. can you reveal your name and website or so for the changelog?

@mosabua
Owner

Oh and in terms of further enhancement please create a new pull request and we can discuss there..

@pa314159
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 8, 2012
  1. @pa314159

    Changed the artefact version and pointed the repository location to m…

    pa314159 authored
    …y own nexus... DO NOT merge this change!
  2. @pa314159

    Add resources specified by ${android.metaIncludes} to the final APK,

    pa314159 authored
    right after apkbuilder invocation
    
    TODO: must investigate proguard invocation as well
  3. @pa314159
Commits on Jan 17, 2012
  1. @pa314159
  2. @pa314159
Commits on Jan 18, 2012
  1. @pa314159

    - created APK configuration for metaIncludes, extractDuplicates and s…

    pa314159 authored
    …ourceDirectories
    
    - added deprecation mechanism for extractDuplicates and sourceDirectories
    - added ConfigHelper to parse mojo configurations + two simple tests
Commits on Jan 24, 2012
  1. @pa314159
Commits on May 23, 2012
  1. @pa314159
  2. @pa314159

    @ConfigPojo improvement

    pa314159 authored
    added ability to specify the parsed parameter prefix in the @ConfigPojo
    annotation
  3. @pa314159

    Updated Apk with the new ConfigHandler

    pa314159 authored
    Previous code used the ConfigHelper which was able to handle
    configuration parameters deprecation.
    
    Since the new ConfigHandler doesn't provide this mechanism - or I could
    not find it - only the new "metaIncludes" parameter follows the POJO
    configuration rules.
    
    Other parameters are kept as they are and should be modified by the actual
    maintainer of this class.
This page is out of date. Refresh to see the latest.
View
6 src/main/java/com/jayway/maven/plugins/android/config/ConfigHandler.java
@@ -22,8 +22,7 @@
private Object mojo;
private Object configPojoInstance;
private String configPojoName;
-
- private static final String PARSED_PARAMETER_PREFIX = "parsed";
+ private String configPojoPrefix;
public ConfigHandler(Object mojo) {
this.mojo = mojo;
@@ -152,7 +151,7 @@ private String toFirstLetterUppercase(String s) {
}
private String getFieldNameWithoutParsedPrefix(Field field) {
- return getFieldNameWithoutPrefix(field, PARSED_PARAMETER_PREFIX);
+ return getFieldNameWithoutPrefix(field, configPojoPrefix);
}
private void initConfigPojo() {
@@ -160,6 +159,7 @@ private void initConfigPojo() {
Field configPojo = findPropertiesByAnnotation(ConfigPojo.class).iterator().next();
configPojoName = configPojo.getName();
configPojoInstance = configPojo.get(mojo);
+ configPojoPrefix = configPojo.getAnnotation( ConfigPojo.class ).prefix();
}
catch (Exception e) {
// ignore, we can live without a config pojo
View
1  src/main/java/com/jayway/maven/plugins/android/config/ConfigPojo.java
@@ -17,4 +17,5 @@
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigPojo {
+ String prefix() default "parsed";
}
View
21 src/main/java/com/jayway/maven/plugins/android/configuration/Apk.java
@@ -0,0 +1,21 @@
+
+package com.jayway.maven.plugins.android.configuration;
+
+import java.io.File;
+
+import com.jayway.maven.plugins.android.phase09package.ApkMojo;
+
+/**
+ * Embedded configuration of {@link com.jayway.maven.plugins.android.phase09package.ApkMojo}.
+ *
+ * @author <a href="mailto:pa314159&#64;gmail.com">Pappy Răzvan STĂNESCU &lt;pa314159&#64;gmail.com&gt;</a>
+ */
+@SuppressWarnings( "unused" )
+public class Apk
+{
+
+ /**
+ * Mirror of {@link com.jayway.maven.plugins.android.phase09package.ApkMojo#apkMetaIncludes}.
+ */
+ private String[] metaIncludes;
+}
View
89 src/main/java/com/jayway/maven/plugins/android/configuration/ConfigHelper.java
@@ -0,0 +1,89 @@
+
+package com.jayway.maven.plugins.android.configuration;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+
+/**
+ * Helper for parsing the embedded configuration of a mojo.
+ *
+ * @author <a href="mailto:pa314159&#64;gmail.com">Pappy Răzvan STĂNESCU &lt;pa314159&#64;gmail.com&gt;</a>
+ */
+public final class ConfigHelper
+{
+
+ static public void copyValues( AbstractMojo mojo, String confFieldName )
+ throws MojoExecutionException
+ {
+ try {
+ final Class<? extends AbstractMojo> mojoClass = mojo.getClass();
+ final Field confField = mojoClass.getDeclaredField( confFieldName );
+
+ confField.setAccessible( true );
+
+ final Object conf = confField.get( mojo );
+
+ if( conf == null ) {
+ return;
+ }
+
+ for( final Field field : conf.getClass().getDeclaredFields() ) {
+ field.setAccessible( true );
+
+ final Object value = field.get( conf );
+
+ if( value == null ) {
+ continue;
+ }
+
+ final Class<?> cls = value.getClass();
+
+ if( (cls == String.class) && (((String) value).length() == 0) ) {
+ continue;
+ }
+ if( cls.isArray() && (Array.getLength( value ) == 0) ) {
+ continue;
+ }
+
+ {
+ String mojoFieldName = field.getName();
+
+ mojoFieldName = Character.toUpperCase( mojoFieldName.charAt( 0 ) ) + mojoFieldName.substring( 1 );
+ mojoFieldName = confFieldName + mojoFieldName;
+
+ try {
+ final Field mojoField = mojoClass.getDeclaredField( mojoFieldName );
+
+ mojoField.setAccessible( true );
+ mojoField.set( mojo, value );
+ }
+ catch( final NoSuchFieldException e ) {
+ ;
+ }
+ }
+
+ // handle deprecated parameters
+ {
+ try {
+ final Field mojoField = mojoClass.getDeclaredField( field.getName() );
+
+ mojoField.setAccessible( true );
+ mojoField.set( mojo, value );
+ }
+ catch( final NoSuchFieldException e ) {
+ ;
+ }
+ catch( final IllegalArgumentException e ) {
+ // probably not a deprecated parameter, see Proguard configuration;
+ }
+ }
+ }
+ }
+ catch( final Exception e ) {
+ throw new MojoExecutionException( e.getMessage(), e );
+ }
+ }
+}
View
146 src/main/java/com/jayway/maven/plugins/android/phase09package/ApkMojo.java
@@ -34,21 +34,26 @@
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-import com.jayway.maven.plugins.android.common.AetherHelper;
-import com.jayway.maven.plugins.android.common.NativeHelper;
-import com.jayway.maven.plugins.android.configuration.Sign;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.AbstractScanner;
+import org.codehaus.plexus.util.DirectoryScanner;
+import org.codehaus.plexus.util.SelectorUtils;
import com.jayway.maven.plugins.android.AbstractAndroidMojo;
import com.jayway.maven.plugins.android.AndroidSigner;
import com.jayway.maven.plugins.android.CommandExecutor;
import com.jayway.maven.plugins.android.ExecutionException;
-import org.codehaus.plexus.util.DirectoryScanner;
+import com.jayway.maven.plugins.android.common.NativeHelper;
+import com.jayway.maven.plugins.android.config.ConfigHandler;
+import com.jayway.maven.plugins.android.config.ConfigPojo;
+import com.jayway.maven.plugins.android.config.PullParameter;
+import com.jayway.maven.plugins.android.configuration.Apk;
+import com.jayway.maven.plugins.android.configuration.ConfigHelper;
+import com.jayway.maven.plugins.android.configuration.Sign;
/**
@@ -182,6 +187,47 @@
*/
protected ArtifactFactory artifactFactory;
+ /**
+ * Pattern for additional META-INF resources to be packaged into the apk.
+ * <p>
+ * The APK builder filters these resources and doesn't include them into the apk.
+ * This leads to bad behaviour of dependent libraries relying on these resources,
+ * for instance service discovery doesn't work.<br/>
+ * By specifying this pattern, the android plugin adds these resources to the final apk.
+ * </p>
+ * <p>The pattern is relative to META-INF, i.e. one must use
+ * <pre>
+ * <code>
+ * &lt;apkMetaIncludes&gt;
+ * &lt;metaInclude>services/**&lt;/metaInclude&gt;
+ * &lt;/apkMetaIncludes&gt;
+ * </code>
+ * </pre>
+ * ... instead of
+ * <pre>
+ * <code>
+ * &lt;apkMetaIncludes&gt;
+ * &lt;metaInclude>META-INF/services/**&lt;/metaInclude&gt;
+ * &lt;/apkMetaIncludes&gt;
+ * </code>
+ * </pre>
+ * <p>
+ * See also <a href="http://code.google.com/p/maven-android-plugin/issues/detail?id=97">Issue 97</a>
+ * </p>
+ *
+ * @parameter expression="${android.apk.metaIncludes}" default-value=""
+ */
+ @PullParameter( defaultValueGetterMethod = "getDefaultMetaIncludes" )
+ private String[] apkMetaIncludes;
+
+ /**
+ * Embedded configuration of this mojo.
+ *
+ * @parameter
+ */
+ @ConfigPojo( prefix="apk" )
+ private Apk apk;
+
private static final Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$", 2);
public void execute() throws MojoExecutionException, MojoFailureException {
@@ -191,6 +237,10 @@ public void execute() throws MojoExecutionException, MojoFailureException {
return;
}
+ ConfigHandler cfh = new ConfigHandler( this );
+
+ cfh.parseConfiguration();
+
generateIntermediateAp_();
// Initialize apk build configuration
@@ -255,8 +305,90 @@ void createApkFile(File outputFile, boolean signWithDebugKeyStore) throws MojoEx
doAPKWithCommand(outputFile, dexFile, zipArchive, sourceFolders, jarFiles,
nativeFolders, signWithDebugKeyStore);
}
+
+ if( this.apkMetaIncludes != null && this.apkMetaIncludes.length > 0 ) {
+ try {
+ addMetaInf( outputFile, jarFiles );
+ }
+ catch( IOException e ) {
+ throw new MojoExecutionException("Could not add META-INF resources.", e);
+ }
+ }
}
+ private void addMetaInf( File outputFile, ArrayList<File> jarFiles ) throws IOException
+ {
+ File tmp = File.createTempFile( outputFile.getName(), ".add", outputFile.getParentFile() );
+
+ FileOutputStream fos = new FileOutputStream( tmp );
+ ZipOutputStream zos = new ZipOutputStream( fos );
+ Set<String> entries = new HashSet<String>();
+
+ updateWithMetaInf( zos, outputFile, entries, false );
+
+ for( File f : jarFiles ) {
+ updateWithMetaInf( zos, f, entries, true );
+ }
+
+ zos.close();
+
+ outputFile.delete();
+
+ if( !tmp.renameTo( outputFile ) ) {
+ throw new IOException( String.format( "Cannot rename %s to %s", tmp, outputFile.getName() ) );
+ }
+ }
+
+ private void updateWithMetaInf( ZipOutputStream zos, File jarFile, Set<String> entries, boolean metaInfOnly )
+ throws ZipException, IOException
+ {
+ ZipFile zin = new ZipFile( jarFile );
+
+ for( Enumeration<? extends ZipEntry> en = zin.entries(); en.hasMoreElements(); ) {
+ ZipEntry ze = en.nextElement();
+
+ if( ze.isDirectory() )
+ continue;
+
+ String zn = ze.getName();
+
+ if( metaInfOnly ) {
+ if( !zn.startsWith( "META-INF/" ) ) {
+ continue;
+ }
+
+ if( this.extractDuplicates && !entries.add( zn ) ) {
+ continue;
+ }
+
+ if( !metaInfMatches( zn ) ) {
+ continue;
+ }
+ }
+
+ zos.putNextEntry( new ZipEntry( zn ) );
+
+ InputStream is = zin.getInputStream( ze );
+
+ copyStreamWithoutClosing( is, zos );
+
+ is.close();
+ zos.closeEntry();
+ }
+
+ zin.close();
+ }
+
+ private boolean metaInfMatches( String path )
+ {
+ for( String inc : this.apkMetaIncludes ) {
+ if( SelectorUtils.matchPath( "META-INF/" + inc, path ) )
+ return true;
+ }
+
+ return false;
+ }
+
private Map<String, List<File>> m_jars = new HashMap<String, List<File>>();
private void computeDuplicateFiles(File jar) throws ZipException, IOException {
@@ -368,7 +500,7 @@ public boolean accept(File dir, String name) {
}
private File removeDuplicatesFromJar(File in, List<String> duplicates) {
- File target = new File(project.getBasedir(), "target");
+ String target = project.getBuild().getOutputDirectory();
File tmp = new File(target, "unpacked-embedded-jars");
tmp.mkdirs();
File out = new File(tmp, in.getName());
@@ -837,4 +969,8 @@ protected AndroidSigner getAndroidSigner() {
return new AndroidSigner(sign.getDebug());
}
}
+
+ private String[] getDefaultMetaIncludes() {
+ return new String[0];
+ }
}
View
89 src/test/java/com/jayway/maven/plugins/android/phase09package/ApkMojoTest.java
@@ -0,0 +1,89 @@
+
+package com.jayway.maven.plugins.android.phase09package;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import com.jayway.maven.plugins.android.AbstractAndroidMojoTestCase;
+import com.jayway.maven.plugins.android.config.ConfigHandler;
+
+@RunWith( Parameterized.class )
+public class ApkMojoTest
+extends AbstractAndroidMojoTestCase<ApkMojo>
+{
+
+ @Parameters
+ static public List<Object[]> suite()
+ {
+ final List<Object[]> suite = new ArrayList<Object[]>();
+
+ suite.add( new Object[] { "apk-config-project1", new String[0] } );
+ suite.add( new Object[] { "apk-config-project2", new String[] { "persistence.xml" } } );
+ suite.add( new Object[] { "apk-config-project2", new String[] { "services/**", "persistence.xml" } } );
+
+ return suite;
+ }
+
+ private final String projectName;
+
+ private final String[] expected;
+
+ public ApkMojoTest( String projectName, String[] expected )
+ {
+ this.projectName = projectName;
+ this.expected = expected;
+ }
+
+ @Override
+ public String getPluginGoalName()
+ {
+ return "apk";
+ }
+
+ @Override
+ @Before
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ }
+
+ @Override
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ super.tearDown();
+ }
+
+ @Test
+ public void testConfigHelper()
+ throws Exception
+ {
+ final ApkMojo mojo = createMojo( this.projectName );
+
+ final ConfigHandler cfh = new ConfigHandler( mojo );
+
+ cfh.parseConfiguration();
+
+ final String[] includes = getFieldValue( mojo, "apkMetaIncludes" );
+
+ Assert.assertNotNull( includes );
+ Assert.assertArrayEquals( this.expected, includes );
+ }
+
+ protected <T> T getFieldValue( Object object, String fieldName )
+ throws IllegalAccessException
+ {
+ return (T) super.getVariableValueFromObject( object, fieldName );
+ }
+
+}
View
17 src/test/resources/apk-config-project1/plugin-config.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.jayway.maven.plugins.android.tests</groupId>
+ <artifactId>apk-config-project1</artifactId>
+ <version>15.4.3.1011</version>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>android-maven-plugin</artifactId>
+ <configuration>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
View
22 src/test/resources/apk-config-project2/plugin-config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.jayway.maven.plugins.android.tests</groupId>
+ <artifactId>apk-config-project2</artifactId>
+ <version>15.4.3.1011</version>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>android-maven-plugin</artifactId>
+ <configuration>
+ <apk>
+ <metaIncludes>
+ <include>persistence.xml</include>
+ </metaIncludes>
+ </apk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
View
23 src/test/resources/apk-config-project3/plugin-config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.jayway.maven.plugins.android.tests</groupId>
+ <artifactId>apk-config-project2</artifactId>
+ <version>15.4.3.1011</version>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>android-maven-plugin</artifactId>
+ <configuration>
+ <apk>
+ <metaIncludes>
+ <include>services/**</include>
+ <include>persistence.xml</include>
+ </metaIncludes>
+ </apk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
Something went wrong with that request. Please try again.