From 0dcebb3d80fd0892480f77ee4a6aa7d5d6c61fe8 Mon Sep 17 00:00:00 2001 From: William Ferguson Date: Mon, 22 Jan 2018 20:06:30 +1000 Subject: [PATCH 1/5] Creating a concrete release so I can use it in WT. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 448a51f32..7bbb27170 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.simpligility.maven.plugins android-maven-plugin - 4.5.1-SNAPSHOT + 4.5.1-PR-769 maven-plugin Android Maven Plugin - android-maven-plugin From 5310e352deda850065b814b4c4729302a58f955f Mon Sep 17 00:00:00 2001 From: William Ferguson Date: Mon, 24 Sep 2018 05:10:06 +1000 Subject: [PATCH 2/5] Adding D8Mojo to use d8 for dexing to allow for desugaring of Java8 syntax. TODO should only default to d8 if Android28 or greater. --- pom.xml | 2 +- .../maven/plugins/android/AndroidSdk.java | 14 +- .../plugins/android/configuration/D8.java | 75 +++ .../plugins/android/configuration/Dex.java | 8 + .../android/phase08preparepackage/D8Mojo.java | 570 ++++++++++++++++++ .../phase08preparepackage/DexMechanism.java | 10 + .../phase08preparepackage/DexMojo.java | 15 +- .../resources/META-INF/plexus/components.xml | 5 +- 8 files changed, 694 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java create mode 100644 src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java create mode 100644 src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java diff --git a/pom.xml b/pom.xml index 7bbb27170..3508de4cc 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.simpligility.maven.plugins android-maven-plugin - 4.5.1-PR-769 + 4.5.1-PR-769-B maven-plugin Android Maven Plugin - android-maven-plugin diff --git a/src/main/java/com/simpligility/maven/plugins/android/AndroidSdk.java b/src/main/java/com/simpligility/maven/plugins/android/AndroidSdk.java index dd7ef5852..a8c2f3378 100644 --- a/src/main/java/com/simpligility/maven/plugins/android/AndroidSdk.java +++ b/src/main/java/com/simpligility/maven/plugins/android/AndroidSdk.java @@ -189,8 +189,18 @@ public String getDxJarPath() { return getPathForBuildTool( BuildToolInfo.PathId.DX_JAR ); } - - /** + + /** + * @return the path to the dx.jar + */ + public String getD8JarPath() + { + final File pathToDexJar = new File( getPathForBuildTool( BuildToolInfo.PathId.DX_JAR ) ); + final File pathToD8Jar = new File( pathToDexJar.getParent(), "d8.jar" ); + return pathToD8Jar.getAbsolutePath(); + } + + /** * Get the path for proguard.jar * @return */ diff --git a/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java b/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java new file mode 100644 index 000000000..b02f342bb --- /dev/null +++ b/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java @@ -0,0 +1,75 @@ +package com.simpligility.maven.plugins.android.configuration; + +import com.simpligility.maven.plugins.android.phase08preparepackage.DexMechanism; + +/** + * Configuration for the D8 execution. This class is only the definition of the parameters that are + * shadowed in + * {@link com.simpligility.maven.plugins.android.phase08preparepackage.D8Mojo} and used there. + * + * @author William Ferguson - william.ferguson@xandar.com.aui + */ +public class D8 +{ + /** + * Mirror of {@link com.simpligility.maven.plugins.android.phase08preparepackage.D8Mojo#dexJvmArguments} + */ + private String[] jvmArguments; + /** + * Mirror of {@link com.simpligility.maven.plugins.android.phase08preparepackage.D8Mojo#dexIntermediate} + */ + private Boolean intermediate; + /** + * Mirror of {@link com.simpligility.maven.plugins.android.phase08preparepackage.D8Mojo#dexMainDexList} + */ + private String mainDexList; + + private String dexArguments; + + /** + * Mirror of {@link com.simpligility.maven.plugins.android.phase08preparepackage.D8Mojo#dexRelease} + */ + private Boolean release; + + /** + * Mirror of {@link com.simpligility.maven.plugins.android.phase08preparepackage.D8Mojo#dexMinApi} + */ + private Integer minApi; + + private DexMechanism dexMechanism = DexMechanism.D8; + + public String[] getJvmArguments() + { + return jvmArguments; + } + + public Boolean isIntermediate() + { + return intermediate; + } + + public String getMainDexList() + { + return mainDexList; + } + + public String getDexArguments() + { + return dexArguments; + } + + public DexMechanism getDexMechanism() + { + return dexMechanism; + } + + public Boolean isRelease() + { + return release; + } + + public Integer getMinApi() + { + return minApi; + } +} diff --git a/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java b/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java index 5cc24be4d..e3d4a3e2a 100644 --- a/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java +++ b/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java @@ -1,5 +1,7 @@ package com.simpligility.maven.plugins.android.configuration; +import com.simpligility.maven.plugins.android.phase08preparepackage.DexMechanism; + /** * Configuration for the dex test execution. This class is only the definition of the parameters that are * shadowed in @@ -60,6 +62,7 @@ public class Dex private String dexArguments; + private DexMechanism dexMechanism = DexMechanism.D8; public String[] getJvmArguments() { @@ -125,4 +128,9 @@ public String getDexArguments() { return dexArguments; } + + public DexMechanism getDexMechanism() + { + return dexMechanism; + } } diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java new file mode 100644 index 000000000..7e6d4bb4b --- /dev/null +++ b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java @@ -0,0 +1,570 @@ +/* + * Copyright (C) 2009 Jayway AB + * Copyright (C) 2007-2008 JVending Masa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.simpligility.maven.plugins.android.phase08preparepackage; + +import com.simpligility.maven.plugins.android.AbstractAndroidMojo; +import com.simpligility.maven.plugins.android.CommandExecutor; +import com.simpligility.maven.plugins.android.ExecutionException; +import com.simpligility.maven.plugins.android.IncludeExcludeSet; +import com.simpligility.maven.plugins.android.configuration.D8; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.model.Resource; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.codehaus.plexus.archiver.ArchiverException; +import org.codehaus.plexus.archiver.jar.JarArchiver; +import org.codehaus.plexus.archiver.util.DefaultFileSet; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.simpligility.maven.plugins.android.InclusionExclusionResolver.filterArtifacts; + +/** + * Converts compiled Java classes (including those containing Java 8 syntax) to the Android dex format. + * It is a replacement for the {@link DexMojo}. + * + * You should only run one or the other. + * By default D8 will run and Dex will not. But this is determined by the + * + * @author william.ferguson@xandar.com.au + */ +@Mojo( + name = "d8", + defaultPhase = LifecyclePhase.PREPARE_PACKAGE, + requiresDependencyResolution = ResolutionScope.COMPILE +) +public class D8Mojo extends AbstractAndroidMojo +{ + private static final String JAR = "jar"; + + /** + * Configuration for the D8 command execution. It can be configured in the plugin configuration like so + * + *
+     * <dex>
+     *   <dexMechanism>d8|dex</dexMechanism>
+     *   <jvmArguments>
+     *     <jvmArgument>-Xms256m</jvmArgument>
+     *     <jvmArgument>-Xmx512m</jvmArgument>
+     *   </jvmArguments>
+     *   <intermediate>true|false</intermediate>
+     *   <mainDexList>path to class list file</mainDexList>
+     *   <release>path to class list file</release>
+     *   <minApi>path to class list file</minApi>
+     *   <arguments>
+     *     <argument>--someOtherArgA</argument>
+     *     <argument>--someOtherArgB</argument>
+     *   </arguments>
+     * </dex>
+     * 
+ * + * or via properties dex* or command line parameters android.dex.* + */ + @Parameter + private D8 dex; + + /** + * Extra JVM Arguments. Using these you can e.g. increase memory for the jvm running the build. + */ + @Parameter( property = "android.dex.jvmArguments", defaultValue = "-Xmx1024M" ) + private String[] dexJvmArguments; + + /** + * Decides whether to pass the --intermediate flag to d8. + */ + @Parameter( property = "android.dex.intermediate", defaultValue = "false" ) + private boolean dexIntermediate; + + /** + * Full path to class list to multi dex + */ + @Parameter( property = "android.dex.maindexlist" ) + private String dexMainDexList; + + /** + * Whether to pass the --release flag to d8. + */ + @Parameter( property = "android.dex.release", defaultValue = "false" ) + private boolean dexRelease; + + /** + * The minApi (if any) to pass to d8. + */ + @Parameter( property = "android.dex.release" ) + private Integer dexMinApi; + + /** + * Additional command line parameters passed to d8. + */ + @Parameter( property = "android.dex.dexarguments" ) + private String dexArguments; + + /** + * The name of the obfuscated JAR. + */ + @Parameter( property = "android.proguard.obfuscatedJar" ) + private File obfuscatedJar; + + /** + * Skips transitive dependencies. May be useful if the target classes directory is populated with the + * {@code maven-dependency-plugin} and already contains all dependency classes. + */ + @Parameter( property = "skipDependencies", defaultValue = "false" ) + private boolean skipDependencies; + + /** + * Allows to include or exclude artifacts by type. The {@code include} parameter has higher priority than the + * {@code exclude} parameter. These two parameters can be overridden by the {@code artifactSet} parameter. Empty + * strings are ignored. Example: + *
+     *     <artifactTypeSet>
+     *         <includes>
+     *             <include>aar</include>
+     *         <includes>
+     *         <excludes>
+     *             <exclude>jar</exclude>
+     *         <excludes>
+     *     </artifactTypeSet>
+     * 
+ */ + @Parameter( property = "artifactTypeSet" ) + private IncludeExcludeSet artifactTypeSet; + + /** + * Allows to include or exclude artifacts by {@code groupId}, {@code artifactId}, and {@code versionId}. The + * {@code include} parameter has higher priority than the {@code exclude} parameter. These two parameters can + * override the {@code artifactTypeSet} and {@code skipDependencies} parameters. Artifact {@code groupId}, + * {@code artifactId}, and {@code versionId} are specified by a string with the respective values separated using + * a colon character {@code :}. {@code artifactId} and {@code versionId} can be optional covering an artifact + * range. Empty strings are ignored. Example: + *
+     *     <artifactTypeSet>
+     *         <includes>
+     *             <include>foo-group:foo-artifact:1.0-SNAPSHOT</include>
+     *             <include>bar-group:bar-artifact:1.0-SNAPSHOT</include>
+     *             <include>baz-group:*</include>
+     *         <includes>
+     *         <excludes>
+     *             <exclude>qux-group:qux-artifact:*</exclude>
+     *         <excludes>
+     *     </artifactTypeSet>
+     * 
+ */ + @Parameter( property = "artifactSet" ) + private IncludeExcludeSet artifactSet; + + private String[] parsedJvmArguments; + private boolean parsedIntermediate; + private String parsedMainDexList; + private String parsedDexArguments; + private DexMechanism parsedDexMechanism; + private boolean parsedRelease; + private Integer parsedMinApi; + + /** + * @throws MojoExecutionException + * @throws MojoFailureException + */ + @Override + public void execute() throws MojoExecutionException, MojoFailureException + { + parseConfiguration(); + + getLog().debug( "DexMechanism set to " + parsedDexMechanism ); + if ( parsedDexMechanism != DexMechanism.D8 ) + { + getLog().info( "Not executing D8Mojo because DexMechanism set to " + parsedDexMechanism ); + return; + } + + CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); + executor.setLogger( getLog() ); + + if ( generateApk ) + { + runD8( executor ); + } + + if ( attachJar ) + { + File jarFile = new File( targetDirectory + File.separator + + finalName + ".jar" ); + projectHelper.attachArtifact( project, "jar", project.getArtifact().getClassifier(), jarFile ); + } + + if ( attachSources ) + { + // Also attach an .apksources, containing sources from this project. + final File apksources = createApkSourcesFile(); + projectHelper.attachArtifact( project, "apksources", apksources ); + } + } + + private List getDependencies() + { + final List libraries = new ArrayList<>(); + for ( Artifact artifact : filterArtifacts( getTransitiveDependencyArtifacts(), skipDependencies, + artifactTypeSet.getIncludes(), artifactTypeSet.getExcludes(), artifactSet.getIncludes(), + artifactSet.getExcludes() ) ) + { + if ( "jar".equals( artifact.getType() ) ) + { + libraries.add( artifact.getFile() ); + } + } + + return libraries; + } + + /** + * @return Set of input files for dex. This is a combination of directories and jar files. + */ + private Set< File > getDexInputFiles() + { + final Set< File > inputs = new HashSet< File >(); + + if ( obfuscatedJar != null && obfuscatedJar.exists() ) + { + // proguard has been run, use this jar + getLog().debug( "Adding dex input (obfuscatedJar) : " + obfuscatedJar ); + inputs.add( obfuscatedJar ); + } + else + { + getLog().debug( "Using non-obfuscated input" ); + final File classesJar = new File( targetDirectory, finalName + ".jar" ); + inputs.add( classesJar ); + getLog().debug( "Adding dex input from : " + classesJar ); + + for ( Artifact artifact : filterArtifacts( getTransitiveDependencyArtifacts(), skipDependencies, + artifactTypeSet.getIncludes(), artifactTypeSet.getExcludes(), artifactSet.getIncludes(), + artifactSet.getExcludes() ) ) + { + if ( artifact.getType().equals( JAR ) ) + { + getLog().debug( "Adding dex input : " + artifact.getFile() ); + inputs.add( artifact.getFile().getAbsoluteFile() ); + } + } + } + + return inputs; + } + + private void parseConfiguration() + { + // config in pom found + if ( dex != null ) + { + // the if statements make sure that properties/command line + // parameter overrides configuration + // and that the dafaults apply in all cases; + if ( dex.getJvmArguments() == null ) + { + parsedJvmArguments = dexJvmArguments; + } + else + { + parsedJvmArguments = dex.getJvmArguments(); + } + if ( dex.isIntermediate() == null ) + { + parsedIntermediate = dexIntermediate; + } + else + { + parsedIntermediate = dex.isIntermediate(); + } + if ( dex.getMainDexList() == null ) + { + parsedMainDexList = dexMainDexList; + } + else + { + parsedMainDexList = dex.getMainDexList(); + } + if ( dex.getDexArguments() == null ) + { + parsedDexArguments = dexArguments; + } + else + { + parsedDexArguments = dex.getDexArguments(); + } + parsedDexMechanism = dex.getDexMechanism(); + if ( dex.isRelease() == null ) + { + parsedRelease = dexRelease; + } + else + { + parsedRelease = dex.isRelease(); + } + if ( dex.getMinApi() == null ) + { + parsedMinApi = dexMinApi; + } + else + { + parsedMinApi = dex.getMinApi(); + } + } + else + { + parsedJvmArguments = dexJvmArguments; + parsedIntermediate = dexIntermediate; + parsedMainDexList = dexMainDexList; + parsedDexArguments = dexArguments; + parsedDexMechanism = DexMechanism.D8; + parsedRelease = dexRelease; + parsedMinApi = dexMinApi; + } + } + + private List< String > dexDefaultCommands() throws MojoExecutionException + { + List< String > commands = jarDefaultCommands(); + commands.add( getAndroidSdk().getD8JarPath() ); + return commands; + } + + private List jarDefaultCommands() + { + List< String > commands = javaDefaultCommands(); + commands.add( "-jar" ); + return commands; + } + + private List javaDefaultCommands() + { + List< String > commands = new ArrayList< String > (); + if ( parsedJvmArguments != null ) + { + for ( String jvmArgument : parsedJvmArguments ) + { + // preserve backward compatibility allowing argument with or + // without dash (e.g. Xmx512m as well as + // -Xmx512m should work) (see + // http://code.google.com/p/maven-android-plugin/issues/detail?id=153) + if ( !jvmArgument.startsWith( "-" ) ) + { + jvmArgument = "-" + jvmArgument; + } + getLog().debug( "Adding jvm argument " + jvmArgument ); + commands.add( jvmArgument ); + } + } + return commands; + } + + private void runD8( CommandExecutor executor ) + throws MojoExecutionException + { + final List< String > commands = dexDefaultCommands(); + final Set< File > inputFiles = getDexInputFiles(); + if ( parsedIntermediate ) + { + commands.add( "--intermediate" ); + } + if ( parsedMainDexList != null ) + { + commands.add( "--main-dex-list " + parsedMainDexList ); + } + if ( parsedDexArguments != null ) + { + commands.add( parsedDexArguments ); + } + + if ( parsedRelease ) + { + commands.add( "--release" ); + } + + if ( parsedMinApi != null ) + { + commands.add( "--min api" ); + commands.add( parsedMinApi.toString() ); + } + + commands.add( "--output" ); + commands.add( targetDirectory.getAbsolutePath() ); + + final File androidJar = getAndroidSdk().getAndroidJar(); + commands.add( "--lib " ); + commands.add( androidJar.getAbsolutePath() ); + + // Add project classpath + final List dependencies = getDependencies(); + for ( final File file : dependencies ) + { + commands.add( "--classpath" ); + commands.add( file.getAbsolutePath() ); + } + + for ( File inputFile : inputFiles ) + { + commands.add( inputFile.getAbsolutePath() ); + } + + getLog().info( "Convert classes to Dex : " + targetDirectory ); + executeJava( commands, executor ); + } + + private String executeJava( final List commands, CommandExecutor executor ) throws MojoExecutionException + { + final String javaExecutable = getJavaExecutable().getAbsolutePath(); + getLog().debug( javaExecutable + " " + commands.toString() ); + try + { + executor.setCaptureStdOut( true ); + executor.executeCommand( javaExecutable, commands, project.getBasedir(), false ); + return executor.getStandardOut(); + } + catch ( ExecutionException e ) + { + throw new MojoExecutionException( "", e ); + } + } + + /** + * Figure out the full path to the current java executable. + * + * @return the full path to the current java executable. + */ + private static File getJavaExecutable() + { + final String javaHome = System.getProperty( "java.home" ); + final String slash = File.separator; + return new File( javaHome + slash + "bin" + slash + "java" ); + } + + /** + * @return + * @throws MojoExecutionException + */ + protected File createApkSourcesFile() throws MojoExecutionException + { + final File apksources = new File( targetDirectory, finalName + + ".apksources" ); + FileUtils.deleteQuietly( apksources ); + + try + { + JarArchiver jarArchiver = new JarArchiver(); + jarArchiver.setDestFile( apksources ); + + addDirectory( jarArchiver, assetsDirectory, "assets" ); + addDirectory( jarArchiver, resourceDirectory, "res" ); + addDirectory( jarArchiver, sourceDirectory, "src/main/java" ); + addJavaResources( jarArchiver, resources ); + + jarArchiver.createArchive(); + } + catch ( ArchiverException e ) + { + throw new MojoExecutionException( "ArchiverException while creating .apksource file.", e ); + } + catch ( IOException e ) + { + throw new MojoExecutionException( "IOException while creating .apksource file.", e ); + } + + return apksources; + } + + /** + * Makes sure the string ends with "/" + * + * @param prefix + * any string, or null. + * @return the prefix with a "/" at the end, never null. + */ + protected String endWithSlash( String prefix ) + { + prefix = StringUtils.defaultIfEmpty( prefix, "/" ); + if ( !prefix.endsWith( "/" ) ) + { + prefix = prefix + "/"; + } + return prefix; + } + + /** + * Adds a directory to a {@link JarArchiver} with a directory prefix. + * + * @param jarArchiver + * @param directory + * The directory to add. + * @param prefix + * An optional prefix for where in the Jar file the directory's contents should go. + */ + protected void addDirectory( JarArchiver jarArchiver, File directory, String prefix ) + { + if ( directory != null && directory.exists() ) + { + final DefaultFileSet fileSet = new DefaultFileSet(); + fileSet.setPrefix( endWithSlash( prefix ) ); + fileSet.setDirectory( directory ); + jarArchiver.addFileSet( fileSet ); + } + } + + /** + * @param jarArchiver + * @param javaResources + */ + protected void addJavaResources( JarArchiver jarArchiver, List< Resource > javaResources ) + { + for ( Resource javaResource : javaResources ) + { + addJavaResource( jarArchiver, javaResource ); + } + } + + /** + * Adds a Java Resources directory (typically "src/main/resources") to a {@link JarArchiver}. + * + * @param jarArchiver + * @param javaResource + * The Java resource to add. + */ + protected void addJavaResource( JarArchiver jarArchiver, Resource javaResource ) + { + if ( javaResource != null ) + { + final File javaResourceDirectory = new File( javaResource.getDirectory() ); + if ( javaResourceDirectory.exists() ) + { + final DefaultFileSet javaResourceFileSet = new DefaultFileSet(); + javaResourceFileSet.setDirectory( javaResourceDirectory ); + javaResourceFileSet.setPrefix( endWithSlash( "src/main/resources" ) ); + jarArchiver.addFileSet( javaResourceFileSet ); + } + } + } +} diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java new file mode 100644 index 000000000..7dbcbd4d2 --- /dev/null +++ b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java @@ -0,0 +1,10 @@ +package com.simpligility.maven.plugins.android.phase08preparepackage; + +/** + * Which mechanism to use to Dex the classes. + */ +public enum DexMechanism +{ + Dex, + D8 +} diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java index 3d6e34648..1b0af3f88 100644 --- a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java +++ b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java @@ -68,6 +68,7 @@ public class DexMojo extends AbstractAndroidMojo * *
      * <dex>
+     *   <dexMechanism>d8|dex</dexMechanism>
      *   <jvmArguments>
      *     <jvmArgument>-Xms256m</jvmArgument>
      *     <jvmArgument>-Xmx512m</jvmArgument>
@@ -243,6 +244,7 @@ public class DexMojo extends AbstractAndroidMojo
     private boolean parsedMinimalMainDex;
     private boolean parsedGenerateMainDexList;
     private String parsedDexArguments;
+    private DexMechanism parsedDexMechanism;
 
     /**
      * @throws MojoExecutionException
@@ -251,15 +253,24 @@ public class DexMojo extends AbstractAndroidMojo
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException
     {
+        parseConfiguration();
+
+        getLog().debug( "DexMechanism set to " + parsedDexMechanism );
+        if ( parsedDexMechanism != DexMechanism.Dex )
+        {
+            getLog().info( "Not executing DexMojo because DexMechanism set to " + parsedDexMechanism );
+            return;
+        }
+
         if ( getJack().isEnabled() )
         {
             //Dexxing is handled by Jack
             return;
         }
+
         CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor();
         executor.setLogger( getLog() );
 
-        parseConfiguration();
         File outputFile;
         if ( parsedMultiDex )
         {
@@ -473,6 +484,7 @@ private void parseConfiguration()
             {
                 parsedDexArguments = dex.getDexArguments();
             }
+            parsedDexMechanism = dex.getDexMechanism();
 
         }
         else
@@ -490,6 +502,7 @@ private void parseConfiguration()
             parsedMinimalMainDex = dexMinimalMainDex;
             parsedGenerateMainDexList = dexGenerateMainDexList;
             parsedDexArguments = dexArguments;
+            parsedDexMechanism = DexMechanism.D8;
         }
     }
 
diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml
index 8b2ddb001..a6ff57c3a 100644
--- a/src/main/resources/META-INF/plexus/components.xml
+++ b/src/main/resources/META-INF/plexus/components.xml
@@ -35,9 +35,12 @@
           org.apache.maven.plugins:maven-resources-plugin:testResources
           org.apache.maven.plugins:maven-compiler-plugin:testCompile
           org.apache.maven.plugins:maven-surefire-plugin:test
-          com.simpligility.maven.plugins:android-maven-plugin:dex
+          
+            com.simpligility.maven.plugins:android-maven-plugin:dex
+          
           
             org.apache.maven.plugins:maven-jar-plugin:jar,
+            com.simpligility.maven.plugins:android-maven-plugin:d8,
             com.simpligility.maven.plugins:android-maven-plugin:apk
           
           org.apache.maven.plugins:maven-install-plugin:install

From 6eb20e0307d29d57aff79921dea5442f8697752f Mon Sep 17 00:00:00 2001
From: William Ferguson 
Date: Sun, 14 Oct 2018 12:03:42 +1000
Subject: [PATCH 3/5] Switching back to SNAPSHOT

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 3508de4cc..448a51f32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
 
   com.simpligility.maven.plugins
   android-maven-plugin
-  4.5.1-PR-769-B
+  4.5.1-SNAPSHOT
   maven-plugin
 
   Android Maven Plugin - android-maven-plugin

From 25a4dee080cb0816021ceb00b3c6b7b7330bf2db Mon Sep 17 00:00:00 2001
From: William Ferguson 
Date: Sun, 14 Oct 2018 12:09:33 +1000
Subject: [PATCH 4/5] Making Dex the default mechanism. To enable D8 you must
 set D8.

---
 .../simpligility/maven/plugins/android/configuration/D8.java    | 2 +-
 .../simpligility/maven/plugins/android/configuration/Dex.java   | 2 +-
 .../maven/plugins/android/phase08preparepackage/D8Mojo.java     | 2 +-
 .../plugins/android/phase08preparepackage/DexMechanism.java     | 2 +-
 .../maven/plugins/android/phase08preparepackage/DexMojo.java    | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java b/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java
index b02f342bb..edb588164 100644
--- a/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java
+++ b/src/main/java/com/simpligility/maven/plugins/android/configuration/D8.java
@@ -36,7 +36,7 @@ public class D8
      */
     private Integer minApi;
 
-    private DexMechanism dexMechanism = DexMechanism.D8;
+    private DexMechanism dexMechanism = DexMechanism.Dex;
 
     public String[] getJvmArguments()
     {
diff --git a/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java b/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java
index e3d4a3e2a..9824a1bf0 100644
--- a/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java
+++ b/src/main/java/com/simpligility/maven/plugins/android/configuration/Dex.java
@@ -62,7 +62,7 @@ public class Dex
 
     private String dexArguments;
 
-    private DexMechanism dexMechanism = DexMechanism.D8;
+    private DexMechanism dexMechanism = DexMechanism.Dex;
 
     public String[] getJvmArguments()
     {
diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java
index 7e6d4bb4b..d7ed4d6d3 100644
--- a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java
+++ b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/D8Mojo.java
@@ -340,7 +340,7 @@ private void parseConfiguration()
             parsedIntermediate = dexIntermediate;
             parsedMainDexList = dexMainDexList;
             parsedDexArguments = dexArguments;
-            parsedDexMechanism = DexMechanism.D8;
+            parsedDexMechanism = DexMechanism.Dex;
             parsedRelease = dexRelease;
             parsedMinApi = dexMinApi;
         }
diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java
index 7dbcbd4d2..cb64cae7a 100644
--- a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java
+++ b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMechanism.java
@@ -5,6 +5,6 @@
  */
 public enum DexMechanism
 {
-    Dex,
+    Dex, // Default
     D8
 }
diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java
index 1b0af3f88..dd8681ab2 100644
--- a/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java
+++ b/src/main/java/com/simpligility/maven/plugins/android/phase08preparepackage/DexMojo.java
@@ -502,7 +502,7 @@ private void parseConfiguration()
             parsedMinimalMainDex = dexMinimalMainDex;
             parsedGenerateMainDexList = dexGenerateMainDexList;
             parsedDexArguments = dexArguments;
-            parsedDexMechanism = DexMechanism.D8;
+            parsedDexMechanism = DexMechanism.Dex;
         }
     }
 

From 906609fe468671f058350a0749fab5ff442cdc08 Mon Sep 17 00:00:00 2001
From: William Ferguson 
Date: Wed, 17 Oct 2018 18:48:36 +1000
Subject: [PATCH 5/5] Modifying the ApkMojo so that all Java resources of the
 project are added to the APK, even those in META-INF

---
 .../android/phase09package/ApkMojo.java       | 60 ++++++++++++++++---
 1 file changed, 52 insertions(+), 8 deletions(-)

diff --git a/src/main/java/com/simpligility/maven/plugins/android/phase09package/ApkMojo.java b/src/main/java/com/simpligility/maven/plugins/android/phase09package/ApkMojo.java
index b09224a88..b396aee07 100644
--- a/src/main/java/com/simpligility/maven/plugins/android/phase09package/ApkMojo.java
+++ b/src/main/java/com/simpligility/maven/plugins/android/phase09package/ApkMojo.java
@@ -20,13 +20,13 @@
 import com.android.sdklib.build.ApkCreationException;
 import com.android.sdklib.build.DuplicateFileException;
 import com.android.sdklib.build.SealedApkException;
-import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
 import com.google.common.io.Files;
+import com.simpligility.maven.plugins.android.AbstractAndroidMojo;
 import com.simpligility.maven.plugins.android.AndroidNdk;
 import com.simpligility.maven.plugins.android.AndroidSigner;
-import com.simpligility.maven.plugins.android.IncludeExcludeSet;
 import com.simpligility.maven.plugins.android.CommandExecutor;
 import com.simpligility.maven.plugins.android.ExecutionException;
+import com.simpligility.maven.plugins.android.IncludeExcludeSet;
 import com.simpligility.maven.plugins.android.common.AaptCommandBuilder;
 import com.simpligility.maven.plugins.android.common.AndroidExtension;
 import com.simpligility.maven.plugins.android.common.NativeHelper;
@@ -36,7 +36,6 @@
 import com.simpligility.maven.plugins.android.configuration.Apk;
 import com.simpligility.maven.plugins.android.configuration.MetaInf;
 import com.simpligility.maven.plugins.android.configuration.Sign;
-
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.filefilter.DirectoryFileFilter;
 import org.apache.commons.io.filefilter.FileFileFilter;
@@ -733,7 +732,8 @@ private void doAPKWithAPKBuilder( File outputFile, File dexFile, File zipArchive
             for ( File sourceFolder : sourceFolders )
             {
                 getLog().debug( "Adding source folder : " + sourceFolder );
-                apkBuilder.addSourceFolder( sourceFolder );
+                // Use ApkBuilder#addFile() to explicitly add resource files so that we can add META-INF/services.
+                addResourcesFromFolder( apkBuilder, sourceFolder );
             }
 
             for ( File jarFile : jarFiles )
@@ -799,9 +799,9 @@ public boolean accept( File dir, String name )
             }
             apkBuilder.sealApk();
         }
-        catch ( ApkCreationException e )
+        catch ( ApkCreationException | SealedApkException | IOException e )
         {
-            throw new MojoExecutionException( e.getMessage() );
+            throw new MojoExecutionException( e.getMessage(), e );
         }
         catch ( DuplicateFileException e )
         {
@@ -809,9 +809,53 @@ public boolean accept( File dir, String name )
                     e.getArchivePath(), e.getFile1(), e.getFile2() );
             throw new MojoExecutionException( msg, e );
         }
-        catch ( SealedApkException e )
+    }
+
+    /**
+     * Collect all Files from Folder (recursively) that are not class files.
+     */
+    private void collectFiles( File folder, final List collectedFiles )
+    {
+        folder.listFiles( new FileFilter()
+        {
+            @Override
+            public boolean accept( File file )
+            {
+                if ( file.isDirectory() )
+                {
+                    collectFiles( file, collectedFiles );
+                }
+                else if ( file.isFile() )
+                {
+                    if ( !file.getName().endsWith( ".class" ) )
+                    {
+                        collectedFiles.add( file );
+                    }
+                }
+                return false;
+            }
+        } );
+
+    }
+    /**
+     * Adds all non-class files from folder, so that we can add META-INF/services resources.
+     */
+    private void addResourcesFromFolder( ApkBuilder builder,  File folder )
+            throws SealedApkException, DuplicateFileException, ApkCreationException, IOException
+    {
+        final int folderPathLength = folder.getCanonicalPath().length();
+
+        final List resourceFiles = new ArrayList<>(  );
+        collectFiles( folder, resourceFiles );
+
+        for ( final File resourceFile : resourceFiles )
         {
-            throw new MojoExecutionException( e.getMessage() );
+            final String resourceName = resourceFile
+                    .getCanonicalPath()
+                    .substring( folderPathLength + 1 )
+                    .replaceAll( "\\\\", "/" );
+            getLog().info( "Adding resource " + resourceFile + " : " + resourceName );
+            builder.addFile( resourceFile,  resourceName );
         }
     }