All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.simpligility.maven.plugins.android.phase01generatesources.GenerateSourcesMojo Maven / Gradle / Ivy

The newest version!
/*
 * 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.phase01generatesources;

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.common.AaptCommandBuilder;
import com.simpligility.maven.plugins.android.common.AaptCommandBuilder.AaptPackageCommandBuilder;
import com.simpligility.maven.plugins.android.common.DependencyResolver;
import com.simpligility.maven.plugins.android.common.FileRetriever;
import com.simpligility.maven.plugins.android.configuration.BuildConfigConstant;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
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.apache.maven.repository.RepositorySystem;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import static com.simpligility.maven.plugins.android.common.AndroidExtension.AAR;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.APK;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKLIB;
import static com.simpligility.maven.plugins.android.common.AndroidExtension.APKSOURCES;

/**
 * Generates R.java based on resources specified by the resources configuration parameter.
 * Generates java files based on aidl files.
 *
 * @author [email protected]
 * @author Manfred Moser - [email protected]
 * @author William Ferguson - [email protected]
 * @author Malachi de AElfweald [email protected]
 */
@Mojo(
        name = "generate-sources",
        defaultPhase = LifecyclePhase.GENERATE_SOURCES,
        requiresDependencyResolution = ResolutionScope.COMPILE
)
public class GenerateSourcesMojo extends AbstractAndroidMojo
{

    /**
     * 

Whether to produce a warning if there is an aar dependency that has an apklib artifact in its dependency * tree. The combination of aar library including or depending on an apklib has been deprecated and may not be * supported by future plugin versions. Traversing the dependency graph is done for all project dependencies * present in build classpath.

* *

It is recommended to keep this set to true to catch possible issues as soon as possible.

*/ @Parameter( defaultValue = "true" ) protected boolean warnOnApklibDependencies; /** * Whether to fail the build if one of the dependencies and/or the project duplicate a layout file. *

* Such a scenario generally means that the build will fail with a compilation error due to missing resource files. * This is because any Ids contained in the duplicate layout files can never be generated by aapt, as appt * only picks up the first resource file it finds in its path. */ @Parameter( defaultValue = "true" ) private boolean failOnConflictingLayouts; /** * Whether to fail the build if one of the dependencies and/or the project have similar package in the * AndroidManifest. *

* Such scenario generally means that the build will fail with a compilation error due to * missing resources in the R file generated. */ @Parameter( defaultValue = "true" ) private boolean failOnDuplicatePackages; /** * Override default generated folder containing aidl classes */ @Parameter( property = "android.genDirectoryAidl", defaultValue = "${project.build.directory}/generated-sources/aidl" ) protected File genDirectoryAidl; /** * The directory containing the aidl files. */ @Parameter( property = "android.aidlSourceDirectory", defaultValue = "${project.basedir}/src/main/aidl" ) protected File aidlSourceDirectory; /** *

Parameter designed to generate custom BuildConfig constants */ @Parameter( property = "android.buildConfigConstants" ) protected BuildConfigConstant[] buildConfigConstants; /** */ @Component private RepositorySystem repositorySystem; /** * Pre AMP-4 AndroidManifest file. */ @Parameter( readonly = true, defaultValue = "${project.basedir}/AndroidManifest.xml" ) private File androidManifestFilePre4; /** * Pre AMP-4 android resources folder. */ @Parameter( readonly = true, defaultValue = "${project.basedir}/res" ) private File resourceDirectoryPre4; /** * Pre AMP-4 android assets directory. */ @Parameter( readonly = true, defaultValue = "${project.basedir}/assets" ) private File assetsDirectoryPre4; /** * Pre AMP-4 native libraries folder. */ @Parameter( readonly = true, defaultValue = "${project.basedir}/libs" ) private File nativeLibrariesDirectoryPre4; /** * If any non-standard files/folders exist and have NOT been explicitly configured then fail the build. */ @Parameter( defaultValue = "true" ) private boolean failOnNonStandardStructure; /** * Which dependency scopes should not be included when unpacking dependencies */ protected static final List EXCLUDED_DEPENDENCY_SCOPES_FOR_EXTRACTION = Arrays.asList( Artifact.SCOPE_SYSTEM, Artifact.SCOPE_IMPORT ); /** * Generates the sources. * * @throws MojoExecutionException if it fails. * @throws MojoFailureException if it fails. */ public void execute() throws MojoExecutionException, MojoFailureException { // If the current POM isn't an Android-related POM, then don't do // anything. This helps work with multi-module projects. if ( !isCurrentProjectAndroid() ) { return; } validateStandardLocations(); try { targetDirectory.mkdirs(); copyManifest(); // Check for a deprecated AAR > APKLIB artifact combination if ( warnOnApklibDependencies ) { checkForApklibDependencies(); } // TODO Do we really want to continue supporting APKSOURCES? How long has it been deprecated extractSourceDependencies(); // Extract the apklib and aar dependencies into unpacked-libs so that they can be referenced in the build. extractLibraryDependencies(); // Copy project assets to combinedAssets so that aapt has a single assets folder to load. copyFolder( assetsDirectory, combinedAssets ); final String[] relativeAidlFileNames1 = findRelativeAidlFileNames( aidlSourceDirectory ); final String[] relativeAidlFileNames2 = findRelativeAidlFileNames( extractedDependenciesJavaSources ); final Map relativeApklibAidlFileNames = new HashMap(); if ( !isInstrumentationTest() ) { // Only add transitive APKLIB deps if we are building an APK and not an instrumentation test apk. for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB ) ) { final File libSourceFolder = getUnpackedApkLibSourceFolder( artifact ); final String[] apklibAidlFiles = findRelativeAidlFileNames( libSourceFolder ); relativeApklibAidlFileNames.put( artifact.getId(), apklibAidlFiles ); } } checkPackagesForDuplicates(); checkForConflictingLayouts(); generateR(); generateBuildConfig(); // When compiling AIDL for this project, // make sure we compile AIDL for dependencies as well. // This is so project A, which depends on project B, can // use AIDL info from project B in its own AIDL final Map files = new HashMap(); files.put( aidlSourceDirectory, relativeAidlFileNames1 ); files.put( extractedDependenciesJavaSources, relativeAidlFileNames2 ); if ( !isInstrumentationTest() ) { // Only add transitive APKLIB deps if we are building an APK and not an instrumentation test apk. for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB ) ) { final File unpackedLibSourceFolder = getUnpackedApkLibSourceFolder( artifact ); files.put( unpackedLibSourceFolder, relativeApklibAidlFileNames.get( artifact.getId() ) ); } } generateAidlFiles( files ); } catch ( MojoExecutionException e ) { getLog().error( "Error when generating sources.", e ); throw e; } } /** * Performs some level of validation on the configured files and folders in light of the change * to AndroidStudio/Gradle style folder structures instead of the original Ant/Eclipse structures. *

* Essentially we will be noisy if we see an artifact that looks like before * Android Maven Plugin 4 and is not explicitly configured. */ private void validateStandardLocations() throws MojoExecutionException { boolean hasNonStandardStructure = false; if ( androidManifestFilePre4.exists() && !androidManifestFilePre4.equals( androidManifestFile ) ) { getLog().warn( "Non-standard location of AndroidManifest.xml file found, but not configured:\n " + androidManifestFilePre4 + "\nMove to the standard location src/main/AndroidManifest.xml\n" + "Or configure androidManifestFile. \n" ); hasNonStandardStructure = true; } if ( resourceDirectoryPre4.exists() && !resourceDirectoryPre4.equals( resourceDirectory ) ) { getLog().warn( "Non-standard location of Android res folder found, but not configured:\n " + resourceDirectoryPre4 + "\nMove to the standard location src/main/res/\n" + "Or configure resourceDirectory. \n" ); hasNonStandardStructure = true; } if ( assetsDirectoryPre4.exists() && !assetsDirectoryPre4.equals( assetsDirectory ) ) { getLog().warn( "Non-standard location assets folder found, but not configured:\n " + assetsDirectoryPre4 + "\nMove to the standard location src/main/assets/\n" + "Or configure assetsDirectory. \n" ); hasNonStandardStructure = true; } if ( nativeLibrariesDirectoryPre4.exists() && !nativeLibrariesDirectoryPre4.equals( nativeLibrariesDirectory ) ) { getLog().warn( "Non-standard location native libs folder found, but not configured:\n " + nativeLibrariesDirectoryPre4 + "\nMove to the standard location src/main/libs/\n" + "Or configure nativeLibrariesDirectory. \n" ); hasNonStandardStructure = true; } if ( hasNonStandardStructure && failOnNonStandardStructure ) { throw new MojoExecutionException( "\n\nFound files or folders in non-standard locations in the project!\n" + "....This might be a side-effect of a migration to Android Maven Plugin 4+.\n" + "....Please observe the warnings for specific files and folders above.\n" + "....Ideally you should restructure your project.\n" + "....Alternatively add explicit configuration overrides for files or folders.\n" + "....Finally you could set failOnNonStandardStructure to false, potentially " + "resulting in other failures.\n\n\n" ); } } /** * Copy the AndroidManifest.xml from androidManifestFile to destinationManifestFile * * @throws MojoExecutionException */ protected void copyManifest() throws MojoExecutionException { getLog().debug( "copyManifest: " + androidManifestFile + " -> " + destinationManifestFile ); if ( androidManifestFile == null ) { getLog().debug( "Manifest copying disabled. Using default manifest only" ); return; } try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse( androidManifestFile ); Source source = new DOMSource( doc ); TransformerFactory xfactory = TransformerFactory.newInstance(); Transformer xformer = xfactory.newTransformer(); xformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "yes" ); FileWriter writer = null; try { destinationManifestFile.getParentFile().mkdirs(); writer = new FileWriter( destinationManifestFile, false ); if ( doc.getXmlEncoding() != null && doc.getXmlVersion() != null ) { String xmldecl = String.format( "%n", doc.getXmlVersion(), doc.getXmlEncoding() ); writer.write( xmldecl ); } Result result = new StreamResult( writer ); xformer.transform( source, result ); getLog().info( "Manifest copied from " + androidManifestFile + " to " + destinationManifestFile ); } finally { IOUtils.closeQuietly( writer ); } } catch ( Exception e ) { getLog().error( "Error during copyManifest" ); throw new MojoExecutionException( "Error during copyManifest", e ); } } /** * Extract the source dependencies. * * @throws MojoExecutionException if it fails. */ protected void extractSourceDependencies() throws MojoExecutionException { for ( Artifact artifact : getDirectDependencyArtifacts() ) { String type = artifact.getType(); if ( type.equals( APKSOURCES ) ) { getLog().debug( "Detected apksources dependency " + artifact + " with file " + artifact.getFile() + ". Will resolve and extract..." ); final File apksourcesFile = resolveArtifactToFile( artifact ); getLog().debug( "Extracting " + apksourcesFile + "..." ); extractApksources( apksourcesFile ); } } if ( extractedDependenciesJavaResources.exists() ) { projectHelper.addResource( project, extractedDependenciesJavaResources.getAbsolutePath(), null, null ); project.addCompileSourceRoot( extractedDependenciesJavaSources.getAbsolutePath() ); } } /** * @deprecated Support APKSOURCES artifacts has been deprecated. Use APKLIB instead. */ @Deprecated private void extractApksources( File apksourcesFile ) throws MojoExecutionException { if ( apksourcesFile.isDirectory() ) { getLog().warn( "The apksources artifact points to '" + apksourcesFile + "' which is a directory; skipping unpacking it." ); return; } final UnArchiver unArchiver = new ZipUnArchiver( apksourcesFile ) { @Override protected Logger getLogger() { return new ConsoleLogger( Logger.LEVEL_DEBUG, "dependencies-unarchiver" ); } }; extractedDependenciesDirectory.mkdirs(); unArchiver.setDestDirectory( extractedDependenciesDirectory ); try { unArchiver.extract(); } catch ( ArchiverException e ) { throw new MojoExecutionException( "ArchiverException while extracting " + apksourcesFile.getAbsolutePath() + ". Message: " + e.getLocalizedMessage(), e ); } } private void extractLibraryDependencies() throws MojoExecutionException { final Collection artifacts = getTransitiveDependencyArtifacts( EXCLUDED_DEPENDENCY_SCOPES_FOR_EXTRACTION ); getLog().info( "Extracting libs" ); // The only library dependencies we want included in an instrumentation test are APK and AARs. // Any APKLIB classes have already been compiled into the APK. final boolean instrumentationTest = isInstrumentationTest(); for ( Artifact artifact : artifacts ) { final String type = artifact.getType(); if ( type.equals( APKLIB ) && !instrumentationTest ) { getLog().info( "Extracting apklib " + artifact.getArtifactId() + "..." ); extractApklib( artifact ); } else if ( type.equals( AAR ) ) { getLog().info( "Extracting aar " + artifact.getArtifactId() + "..." ); extractAarLib( artifact ); } else if ( type.equals( APK ) ) { getLog().info( "Extracting apk " + artifact.getArtifactId() + "..." ); extractApkClassesJar( artifact ); } else { getLog().debug( "Not extracting " + artifact.getArtifactId() + "..." ); } } } /** * Extracts ApkLib and adds the assets and apklib sources and resources to the build. */ private void extractApklib( Artifact apklibArtifact ) throws MojoExecutionException { getUnpackedLibHelper().extractApklib( apklibArtifact ); // Copy the assets to the the combinedAssets folder. // Add the apklib source and resource to the compile. // NB apklib sources are added to compileSourceRoot because we may need to compile against them. // This means the apklib classes will be compiled into target/classes and packaged with this build. copyFolder( getUnpackedLibAssetsFolder( apklibArtifact ), combinedAssets ); final File apklibSourceFolder = getUnpackedApkLibSourceFolder( apklibArtifact ); final List resourceExclusions = Arrays.asList( "**/*.java", "**/*.aidl" ); projectHelper.addResource( project, apklibSourceFolder.getAbsolutePath(), null, resourceExclusions ); project.addCompileSourceRoot( apklibSourceFolder.getAbsolutePath() ); } /** * Extracts AarLib and if this is an APK build then adds the assets and resources to the build. */ private void extractAarLib( Artifact aarArtifact ) throws MojoExecutionException { getUnpackedLibHelper().extractAarLib( aarArtifact ); // Copy the assets to the the combinedAssets folder, but only if an APK build. // Ie we only want to package assets that we own. // Assets should only live within their owners or the final APK. if ( isAPKBuild() ) { copyFolder( getUnpackedLibAssetsFolder( aarArtifact ), combinedAssets ); } // Aar lib resources should only be included if we are building an apk. // So we need to extract them into a folder that we then add to the resource classpath. if ( isAPKBuild() ) { // (If we are including SYSTEM_SCOPE artifacts when considering resources for APK packaging) // then adding the AAR resources to the project would have them added twice. getLog().debug( "Not adding AAR resources to resource classpath : " + aarArtifact ); } } /** * Copies a dependent APK jar over the top of the placeholder created for it in AarMavenLifeCycleParticipant. *

* This is necessary because we need the classes of the APK added to the compile classpath. * NB APK dependencies are uncommon as they should really only be used in a project that tests an apk. * * @param artifact APK dependency for this project whose classes will be copied over. */ private void extractApkClassesJar( Artifact artifact ) throws MojoExecutionException { final File apkClassesJar = getUnpackedLibHelper().getJarFileForApk( artifact ); final File unpackedClassesJar = getUnpackedLibHelper().getUnpackedClassesJar( artifact ); try { FileUtils.copyFile( apkClassesJar, unpackedClassesJar ); } catch ( IOException e ) { throw new MojoExecutionException( "Could not copy APK classes jar " + apkClassesJar + " to " + unpackedClassesJar, e ); } getLog().debug( "Copied APK classes jar into compile classpath : " + unpackedClassesJar ); } /** * Traverses the list of project dependencies looking for "AAR depends on APKLIB" artifact combination * that has been deprecated. For each occurrence of an AAR artifact with APKLIB direct or transitive dependency, * produces a warning message to inform the user. Future plugin versions may default to skipping or not handling * unsupported artifacts during build lifecycle. * * @throws MojoExecutionException */ private void checkForApklibDependencies() throws MojoExecutionException { final boolean isAarBuild = project.getPackaging().equals( AAR ); final DependencyResolver dependencyResolver = getDependencyResolver(); final Set allArtifacts = project.getArtifacts(); Set dependencyArtifacts = getArtifactResolverHelper().getFilteredArtifacts( allArtifacts ); boolean foundApklib = false; for ( Artifact artifact : dependencyArtifacts ) { final String type = artifact.getType(); if ( type.equals( APKLIB ) && isAarBuild ) { getLog().warn( "Detected APKLIB transitive dependency: " + artifact.getId() ); foundApklib = true; } else if ( type.equals( AAR ) ) { final Set dependencies = dependencyResolver .getLibraryDependenciesFor( session, repositorySystem, artifact ); for ( Artifact dependency : dependencies ) { if ( dependency.getType().equals( APKLIB ) ) { getLog().warn( "Detected " + artifact.getId() + " that depends on APKLIB: " + dependency.getId() ); foundApklib = true; } } } } if ( foundApklib ) { getLog().warn( "AAR libraries should not depend or include APKLIB artifacts.\n" + "APKLIBs have been deprecated and the combination of the two may yield unexpected results.\n" + "Check the problematic AAR libraries for newer versions that use AAR packaging." ); } } /** * Checks packages in AndroidManifest.xml file of project and all dependent libraries for duplicates. *

Generate warning if duplicates presents. *

(in case of packages similarity R.java and BuildConfig files will be overridden) * * @throws MojoExecutionException */ private void checkPackagesForDuplicates() throws MojoExecutionException { Set dependencyArtifacts = getTransitiveDependencyArtifacts( AAR, APKLIB ); if ( dependencyArtifacts.isEmpty() ) { //if no AAR or APKLIB dependencies presents than only one package presents return; } Map> packageCompareMap = getPackageCompareMap( dependencyArtifacts ); List duplicatesMessageList = new ArrayList(); for ( Map.Entry> entry : packageCompareMap.entrySet() ) { Set artifacts = entry.getValue(); if ( artifacts != null && artifacts.size() > 1 ) { StringBuilder messageBuilder = new StringBuilder(); for ( Artifact item : artifacts ) { messageBuilder .append( messageBuilder.length() > 0 ? ", " : " [" ) .append( item.getArtifactId() ); } messageBuilder .append( "] have similar package='" ) .append( entry.getKey() ) .append( "'" ); duplicatesMessageList.add( messageBuilder.toString() ); } } if ( !duplicatesMessageList.isEmpty() ) { List messageList = new ArrayList(); messageList.add( "" ); messageList.add( "Duplicate packages detected in AndroidManifest.xml files" ); messageList.add( "" ); messageList.add( "Such scenario generally means that the build will fail with a compilation error due to" + " missing resources in R file." ); messageList.add( "You should consider renaming some of the duplicate packages listed below" + " to avoid the conflict." ); messageList.add( "" ); messageList.add( "Conflicting artifacts:" ); messageList.addAll( duplicatesMessageList ); messageList.add( "" ); if ( failOnDuplicatePackages ) { StringBuilder builder = new StringBuilder(); for ( String line : messageList ) { builder.append( line ); builder.append( "\n" ); } builder.append( "\n" ); builder.append( "You can downgrade the failure to a warning " ); builder.append( "by setting the 'failOnDuplicatePackages' plugin property to false." ); throw new MojoExecutionException( builder.toString() ); } for ( String messageLine : messageList ) { getLog().warn( messageLine ); } } } /** * Looks for dependencies that have a layout file with the same name as either one of the project layout files * or one of the other dependencies. If such a duplicate occurs then it will fail the build or generate a warning * depending upon the value of the failOnConflictingLayouts build parameter. * * @throws MojoExecutionException */ private void checkForConflictingLayouts() throws MojoExecutionException { final ConflictingLayoutDetector detector = new ConflictingLayoutDetector(); // Add layout files for this project final FileRetriever retriever = new FileRetriever( "layout*/*.xml" ); detector.addLayoutFiles( getAndroidManifestPackageName(), retriever.getFileNames( resourceDirectory ) ); // Add layout files for all dependencies. for ( final Artifact dependency : getTransitiveDependencyArtifacts( AAR, APKLIB ) ) { final String packageName = extractPackageNameFromAndroidArtifact( dependency ); final String[] layoutFiles = retriever.getFileNames( getUnpackedLibResourceFolder( dependency ) ); detector.addLayoutFiles( packageName, layoutFiles ); } final Collection conflictingLayouts = detector.getConflictingLayouts(); getLog().debug( "checkConflictingLayouts - conflicts : " + conflictingLayouts ); if ( !conflictingLayouts.isEmpty() ) { final List sb = new ArrayList(); sb.add( "" ); sb.add( "" ); sb.add( "Duplicate layout files have been detected across more than one Android package." ); sb.add( "" ); sb.add( "Such a scenario generally means that the build will fail with a compilation error due to" ); sb.add( "missing resource files. You should consider renaming some of the layout files listed below" ); sb.add( "to avoid the conflict." ); sb.add( "" ); sb.add( "However, if you believe you know better, then you can downgrade the failure to a warning" ); sb.add( "by setting the failOnConflictingLayouts plugin property to false." ); sb.add( "But you really don't want to do that." ); sb.add( "" ); sb.add( "Conflicting Layouts:" ); for ( final ConflictingLayout layout : conflictingLayouts ) { sb.add( " " + layout.getLayoutFileName() + " packages=" + layout.getPackageNames().toString() ); } sb.add( "" ); if ( failOnConflictingLayouts ) { final StringBuilder builder = new StringBuilder(); for ( final String line : sb ) { builder.append( line ); builder.append( "\n" ); } throw new MojoExecutionException( builder.toString() ); } for ( final String line : sb ) { getLog().warn( line ); } } } /** * Provides map with all provided dependencies or project itself grouped by package name * * @param dependencyArtifacts artifacts that should be grouped by package name * @return map of with package names(String) and sets of artifacts (Set) * that have similar package names * @throws MojoExecutionException */ Map> getPackageCompareMap( Set dependencyArtifacts ) throws MojoExecutionException { if ( dependencyArtifacts == null ) { throw new IllegalArgumentException( "dependencies must be initialized" ); } Map> packageCompareMap = new HashMap>(); Set artifactSet = new HashSet(); artifactSet.add( project.getArtifact() ); packageCompareMap.put( getAndroidManifestPackageName(), artifactSet ); for ( Artifact artifact : dependencyArtifacts ) { String libPackage = extractPackageNameFromAndroidArtifact( artifact ); Set artifacts = packageCompareMap.get( libPackage ); if ( artifacts == null ) { artifacts = new HashSet(); packageCompareMap.put( libPackage, artifacts ); } artifacts.add( artifact ); } return packageCompareMap; } private void generateR() throws MojoExecutionException { getLog().info( "Generating R file for " + project.getArtifact() ); genDirectory.mkdirs(); final AaptPackageCommandBuilder commandBuilder = AaptCommandBuilder .packageResources( getLog() ) .makePackageDirectories() .setResourceConstantsFolder( genDirectory ) .forceOverwriteExistingFiles() .disablePngCrunching() .generateRIntoPackage( customPackage ) .setPathToAndroidManifest( destinationManifestFile ) .addResourceDirectoriesIfExists( getResourceOverlayDirectories() ) .addResourceDirectoryIfExists( resourceDirectory ) // Need to include any AAR or APKLIB dependencies when generating R because if any local // resources directly reference dependent resources then R generation will crash. .addResourceDirectoriesIfExists( getLibraryResourceFolders() ) .autoAddOverlay() .addRawAssetsDirectoryIfExists( combinedAssets ) .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() ) .addConfigurations( configurations ) .setVerbose( aaptVerbose ) // We need to generate R.txt for all projects as it needs to be consumed when generating R class. // It also needs to be consumed when packaging aar. .generateRTextFile( targetDirectory ) // If a proguard file is defined then output Proguard options to it. .setProguardOptionsOutputFile( proguardFile ) .makeResourcesNonConstant( AAR.equals( project.getArtifact().getType() ) ) .addExtraArguments( aaptExtraArgs ); getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() ); try { final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger( getLog() ); executor.setCaptureStdOut( true ); final List commands = commandBuilder.build(); executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "", e ); } final ClassLoader compileClassLoader = getCompileClassLoader(); final ResourceClassGenerator resGenerator = new ResourceClassGenerator( this, targetDirectory, genDirectory, compileClassLoader ); generateCorrectRJavaForApklibDependencies( resGenerator ); generateCorrectRJavaForAarDependencies( resGenerator ); getLog().info( "Adding R gen folder to compile classpath: " + genDirectory ); project.addCompileSourceRoot( genDirectory.getAbsolutePath() ); } /** * @return ClassLoader containing the compile paths. */ private ClassLoader getCompileClassLoader() { try { final List runtimeClasspathElements = project.getCompileClasspathElements(); final ClassLoaderFactory factory = new ClassLoaderFactory( runtimeClasspathElements ); return factory.create(); } catch ( DependencyResolutionRequiredException e ) { throw new IllegalStateException( "Mojo should have resolved dependencies", e ); } } /** * Generate correct R.java for apklibs dependencies of a current project * * @throws MojoExecutionException */ private void generateCorrectRJavaForApklibDependencies( ResourceClassGenerator resourceGenerator ) throws MojoExecutionException { getLog().debug( "" ); getLog().debug( "#generateCorrectRJavaFoApklibDeps" ); // Generate R.java for apklibs // Compatibility with Apklib which isn't present in AndroidBuilder getLog().debug( "Generating Rs for apklib deps of project " + project.getArtifact() ); final Set apklibDependencies = getTransitiveDependencyArtifacts( APKLIB ); for ( final Artifact artifact : apklibDependencies ) { getLog().debug( "Generating apklib R.java for " + artifact.getArtifactId() + "..." ); generateRForApkLibDependency( artifact ); } // Generate corrected R.java for APKLIB dependencies, but only if this is an APK build. if ( !apklibDependencies.isEmpty() && APK.equals( project.getArtifact().getType() ) ) { // Generate R.java for each APKLIB based on R.txt getLog().debug( "" ); getLog().debug( "Rewriting R files for APKLIB dependencies : " + apklibDependencies ); resourceGenerator.generateLibraryRs( apklibDependencies ); } } /** * Generate correct R.java for aar dependencies of a current project * * @throws MojoExecutionException if it could not generate the R java for one of the libraries. */ private void generateCorrectRJavaForAarDependencies( ResourceClassGenerator resourceGenerator ) throws MojoExecutionException { // Generate corrected R.java for AAR dependencies. final Set aarLibraries = getTransitiveDependencyArtifacts( AAR ); if ( !aarLibraries.isEmpty() ) { // Generate R.java for each AAR based on R.txt getLog().debug( "Generating R file for AAR dependencies" ); resourceGenerator.generateLibraryRs( aarLibraries ); } } private List getLibraryResourceFolders() { final List resourceFolders = new ArrayList(); for ( Artifact artifact : getTransitiveDependencyArtifacts( AAR, APKLIB ) ) { getLog().debug( "Considering dep artifact : " + artifact ); final File resourceFolder = getUnpackedLibResourceFolder( artifact ); if ( resourceFolder.exists() ) { getLog().debug( "Adding apklib or aar resource folder : " + resourceFolder ); resourceFolders.add( resourceFolder ); } } return resourceFolders; } /** * Executes aapt to generate the R class for the given apklib. * * @param apklibArtifact apklib for which to generate the R class. * @throws MojoExecutionException if it fails. */ private void generateRForApkLibDependency( Artifact apklibArtifact ) throws MojoExecutionException { final File unpackDir = getUnpackedLibFolder( apklibArtifact ); getLog().debug( "Generating incomplete R file for apklib: " + apklibArtifact.getGroupId() + ":" + apklibArtifact.getArtifactId() ); final File apklibManifest = new File( unpackDir, "AndroidManifest.xml" ); final File apklibResDir = new File( unpackDir, "res" ); List dependenciesResDirectories = new ArrayList(); final Set apklibDeps = getDependencyResolver() .getLibraryDependenciesFor( this.session, this.repositorySystem, apklibArtifact ); getLog().debug( "apklib=" + apklibArtifact + " dependencies=" + apklibDeps ); for ( Artifact dependency : apklibDeps ) { // Add in the resources that are dependencies of the apklib. final String extension = dependency.getType(); final File dependencyResDir = getUnpackedLibResourceFolder( dependency ); if ( ( extension.equals( APKLIB ) || extension.equals( AAR ) ) && dependencyResDir.exists() ) { dependenciesResDirectories.add( dependencyResDir ); } } // Create combinedAssets for this apklib dependency - can't have multiple -A args final File apklibCombAssets = new File( getUnpackedLibFolder( apklibArtifact ), "combined-assets" ); for ( Artifact dependency : apklibDeps ) { // Accumulate assets for dependencies of the apklib (if they exist). final String extension = dependency.getType(); final File dependencyAssetsDir = getUnpackedLibAssetsFolder( dependency ); if ( ( extension.equals( APKLIB ) || extension.equals( AAR ) ) ) { copyFolder( dependencyAssetsDir, apklibCombAssets ); } } // Overlay the apklib dependency assets (if they exist) final File apkLibAssetsDir = getUnpackedLibAssetsFolder( apklibArtifact ); copyFolder( apkLibAssetsDir, apklibCombAssets ); final CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger( getLog() ); final AaptCommandBuilder commandBuilder = AaptCommandBuilder .packageResources( getLog() ) .makeResourcesNonConstant() .makePackageDirectories() .setResourceConstantsFolder( genDirectory ) .generateRIntoPackage( extractPackageNameFromAndroidManifest( apklibManifest ) ) .setPathToAndroidManifest( apklibManifest ) .addResourceDirectoryIfExists( apklibResDir ) .addResourceDirectoriesIfExists( dependenciesResDirectories ) .autoAddOverlay() .addRawAssetsDirectoryIfExists( apklibCombAssets ) .addExistingPackageToBaseIncludeSet( getAndroidSdk().getAndroidJar() ) .addConfigurations( configurations ) .setVerbose( aaptVerbose ) .addExtraArguments( aaptExtraArgs ) // We need to generate R.txt for all projects as it needs to be consumed when generating R class. // It also needs to be consumed when packaging aar. .generateRTextFile( unpackDir ); getLog().debug( getAndroidSdk().getAaptPath() + " " + commandBuilder.toString() ); try { executor.setCaptureStdOut( true ); final List commands = commandBuilder.build(); executor.executeCommand( getAndroidSdk().getAaptPath(), commands, project.getBasedir(), false ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "", e ); } } private void generateBuildConfig() throws MojoExecutionException { getLog().debug( "Generating BuildConfig file" ); // Create the BuildConfig for our package. String packageName = extractPackageNameFromAndroidManifest( destinationManifestFile ); if ( StringUtils.isNotBlank( customPackage ) ) { packageName = customPackage; } generateBuildConfigForPackage( packageName ); // Skip BuildConfig generation for dependencies if this is an AAR project if ( project.getPackaging().equals( AAR ) ) { return; } // Generate the BuildConfig for any APKLIB and some AAR dependencies. // Need to generate for AAR, because some old AARs like ActionBarSherlock do not have BuildConfig (or R) for ( Artifact artifact : getTransitiveDependencyArtifacts( APKLIB, AAR ) ) { if ( skipBuildConfigGeneration( artifact ) ) { getLog().info( "Skip BuildConfig.java generation for " + artifact.getGroupId() + " " + artifact.getArtifactId() ); continue; } final String depPackageName = extractPackageNameFromAndroidArtifact( artifact ); generateBuildConfigForPackage( depPackageName ); } } private boolean skipBuildConfigGeneration( Artifact artifact ) throws MojoExecutionException { if ( artifact.getType().equals( AAR ) ) { String depPackageName = extractPackageNameFromAndroidArtifact( artifact ); if ( isBuildConfigPresent( artifact, depPackageName ) ) { return true; } Set transitiveDep = getArtifactResolverHelper() .getFilteredArtifacts( project.getArtifacts(), AAR ); for ( Artifact transitiveArtifact : transitiveDep ) { if ( isBuildConfigPresent( transitiveArtifact, depPackageName ) ) { return true; } } } return false; } /** * Check if given artifact includes a matching BuildConfig class * * @throws MojoExecutionException */ private boolean isBuildConfigPresent( Artifact artifact ) throws MojoExecutionException { String depPackageName = extractPackageNameFromAndroidArtifact( artifact ); return isBuildConfigPresent( artifact, depPackageName ); } /** * Check whether the artifact includes a BuildConfig located in a given package. * * @param artifact an AAR artifact to look for BuildConfig in * @param packageName BuildConfig package name * @throws MojoExecutionException */ private boolean isBuildConfigPresent( Artifact artifact, String packageName ) throws MojoExecutionException { try { JarFile jar = new JarFile( getUnpackedAarClassesJar( artifact ) ); JarEntry entry = jar.getJarEntry( packageName.replace( '.', '/' ) + "/BuildConfig.class" ); return ( entry != null ); } catch ( IOException e ) { getLog().error( "Error generating BuildConfig ", e ); throw new MojoExecutionException( "Error generating BuildConfig", e ); } } private void generateBuildConfigForPackage( String packageName ) throws MojoExecutionException { getLog().debug( "Creating BuildConfig for " + packageName ); File outputFolder = new File( genDirectory, packageName.replace( ".", File.separator ) ); outputFolder.mkdirs(); StringBuilder buildConfig = new StringBuilder(); buildConfig.append( "package " ).append( packageName ).append( ";\n\n" ); buildConfig.append( "public final class BuildConfig {\n" ); buildConfig.append( " public static final boolean DEBUG = " ).append( !release ).append( ";\n" ); for ( BuildConfigConstant constant : buildConfigConstants ) { String value = constant.getValue(); if ( "String".equals( constant.getType() ) ) { value = "\"" + value + "\""; } buildConfig.append( " public static final " ) .append( constant.getType() ) .append( " " ) .append( constant.getName() ) .append( " = " ) .append( value ) .append( ";\n" ); } buildConfig.append( "}\n" ); File outputFile = new File( outputFolder, "BuildConfig.java" ); try { FileUtils.writeStringToFile( outputFile, buildConfig.toString() ); } catch ( IOException e ) { getLog().error( "Error generating BuildConfig ", e ); throw new MojoExecutionException( "Error generating BuildConfig", e ); } } /** * Given a map of source directories to list of AIDL (relative) filenames within each, * runs the AIDL compiler for each, such that all source directories are available to * the AIDL compiler. * * @param files Map of source directory File instances to the relative paths to all AIDL files within * @throws MojoExecutionException If the AIDL compiler fails */ private void generateAidlFiles( Map files ) throws MojoExecutionException { List protoCommands = new ArrayList(); protoCommands.add( "-p" + getAndroidSdk().getPathForFrameworkAidl() ); genDirectoryAidl.mkdirs(); getLog().info( "Adding AIDL gen folder to compile classpath: " + genDirectoryAidl ); project.addCompileSourceRoot( genDirectoryAidl.getPath() ); Set sourceDirs = files.keySet(); for ( File sourceDir : sourceDirs ) { protoCommands.add( "-I" + sourceDir ); } for ( File sourceDir : sourceDirs ) { for ( String relativeAidlFileName : files.get( sourceDir ) ) { File targetDirectory = new File( genDirectoryAidl, new File( relativeAidlFileName ).getParent() ); targetDirectory.mkdirs(); final String shortAidlFileName = new File( relativeAidlFileName ).getName(); final String shortJavaFileName = shortAidlFileName.substring( 0, shortAidlFileName.lastIndexOf( "." ) ) + ".java"; final File aidlFileInSourceDirectory = new File( sourceDir, relativeAidlFileName ); List commands = new ArrayList( protoCommands ); commands.add( aidlFileInSourceDirectory.getAbsolutePath() ); commands.add( new File( targetDirectory, shortJavaFileName ).getAbsolutePath() ); try { CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger( this.getLog() ); executor.setCaptureStdOut( true ); executor.executeCommand( getAndroidSdk().getAidlPath(), commands, project.getBasedir(), false ); } catch ( ExecutionException e ) { throw new MojoExecutionException( "", e ); } } } } private String[] findRelativeAidlFileNames( File sourceDirectory ) { final FileRetriever retriever = new FileRetriever( "**/*.aidl" ); final String[] relativeAidlFileNames = retriever.getFileNames( sourceDirectory ); if ( relativeAidlFileNames.length == 0 ) { getLog().debug( "ANDROID-904-002: No aidl files found" ); } else { getLog().info( "ANDROID-904-002: Found aidl files: Count = " + relativeAidlFileNames.length ); } return relativeAidlFileNames; } /** * @return true if the pom type is APK, APKLIB, or APKSOURCES */ private boolean isCurrentProjectAndroid() { Set androidArtifacts = new HashSet() { { addAll( Arrays.asList( APK, APKLIB, APKSOURCES, AAR ) ); } }; return androidArtifacts.contains( project.getArtifact().getType() ); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy