/* * ### * Android Maven Plugin - android-maven-plugin * * Copyright (C) 1999 - 2012 Photon Infotech Inc. * * 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. * ### */ /* * Copyright (C) 2009 Jayway AB * * 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.photon.maven.plugins.android.phase01generatesources; import static com.photon.maven.plugins.android.common.AndroidExtension.APK; import static com.photon.maven.plugins.android.common.AndroidExtension.APKLIB; import static com.photon.maven.plugins.android.common.AndroidExtension.APKSOURCES; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.codehaus.plexus.util.AbstractScanner; import com.photon.maven.plugins.android.AbstractAndroidMojo; import com.photon.maven.plugins.android.CommandExecutor; import com.photon.maven.plugins.android.ExecutionException; import com.photon.maven.plugins.android.common.AetherHelper; /** * Generates <code>R.java</code> based on resources specified by the <code>resources</code> configuration parameter.<br/> * Generates java files based on aidl files.<br/> * <br/> * * @goal generate-sources * @phase generate-sources * @requiresProject true * @requiresDependencyResolution compile */ public class GenerateSourcesMojo extends AbstractAndroidMojo { /** * Override default generated folder containing R.java * * @parameter expression="${android.genDirectory}" default-value="${project.build.directory}/generated-sources/r" * */ protected File genDirectory; /** * Override default generated folder containing aidl classes * * @parameter expression="${android.genDirectoryAidl}" default-value="${project.build.directory}/generated-sources/aidl" * */ protected File genDirectoryAidl; 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; } try { extractSourceDependencies(); extractApkLibDependencies(); final String[] relativeAidlFileNames1 = findRelativeAidlFileNames(sourceDirectory); final String[] relativeAidlFileNames2 = findRelativeAidlFileNames(extractedDependenciesJavaSources); final Map <String, String[]> relativeApklibAidlFileNames = new HashMap<String, String[]>(); String[] apklibAidlFiles; for (Artifact artifact: getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { apklibAidlFiles = findRelativeAidlFileNames(new File(getLibraryUnpackDirectory(artifact)+"/src")); relativeApklibAidlFileNames.put(artifact.getArtifactId(), apklibAidlFiles); } } generateR(); generateApklibR(); // 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 Map<File, String[]> files = new HashMap<File, String[]>(); files.put(sourceDirectory, relativeAidlFileNames1); files.put(extractedDependenciesJavaSources, relativeAidlFileNames2); for (Artifact artifact: getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { files.put(new File(getLibraryUnpackDirectory(artifact)+"/src"), relativeApklibAidlFileNames.get(artifact.getArtifactId())); } } generateAidlFiles(files); } catch (MojoExecutionException e) { getLog().error("Error when generating sources.", e); throw e; } } protected void extractSourceDependencies() throws MojoExecutionException { for (Artifact artifact :getRelevantDependencyArtifacts()) { String type = artifact.getType(); if (type.equals(APKSOURCES)) { getLog().debug("Detected apksources dependency " + artifact + " with file " + artifact.getFile() + ". Will resolve and extract..."); Artifact resolvedArtifact = AetherHelper.resolveArtifact(artifact,repoSystem,repoSession,projectRepos); File apksourcesFile = resolvedArtifact.getFile(); // When the artifact is not installed in local repository, but rather part of the current reactor, // resolve from within the reactor. (i.e. ../someothermodule/target/*) if (!apksourcesFile.exists()) { apksourcesFile = resolveArtifactToFile(artifact); } //When using maven under eclipse the artifact will by default point to a directory, which isn't correct. //To work around this we'll first try to get the archive from the local repo, and only if it isn't found there we'll do a normal resolve. if (apksourcesFile.isDirectory()) { apksourcesFile = resolveArtifactToFile(artifact); } getLog().debug("Extracting " + apksourcesFile + "..."); extractApksources(apksourcesFile); } } projectHelper.addResource(project, extractedDependenciesJavaResources.getAbsolutePath(), null, null); project.addCompileSourceRoot(extractedDependenciesJavaSources.getAbsolutePath()); } 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 extractApkLibDependencies() throws MojoExecutionException { for (Artifact artifact :getAllRelevantDependencyArtifacts()) { String type = artifact.getType(); if (type.equals(APKLIB)) { getLog().debug("Extracting apklib " + artifact.getArtifactId() + "..."); extractApklib(artifact); } } } private void extractApklib(Artifact apklibArtifact) throws MojoExecutionException { final Artifact resolvedArtifact = AetherHelper.resolveArtifact(apklibArtifact,repoSystem,repoSession,projectRepos); File apkLibFile = resolvedArtifact.getFile(); // When the artifact is not installed in local repository, but rather part of the current reactor, // resolve from within the reactor. (i.e. ../someothermodule/target/*) if (!apkLibFile.exists()) { apkLibFile = resolveArtifactToFile(apklibArtifact); } //When using maven under eclipse the artifact will by default point to a directory, which isn't correct. //To work around this we'll first try to get the archive from the local repo, and only if it isn't found there we'll do a normal resolve. if (apkLibFile.isDirectory()) { apkLibFile = resolveArtifactToFile(apklibArtifact); } if (apkLibFile.isDirectory()) { getLog().warn("The apklib artifact points to '" + apkLibFile + "' which is a directory; skipping unpacking it."); return; } final UnArchiver unArchiver = new ZipUnArchiver(apkLibFile) { @Override protected Logger getLogger() { return new ConsoleLogger(Logger.LEVEL_DEBUG, "dependencies-unarchiver"); } }; File apklibDirectory = new File(getLibraryUnpackDirectory(apklibArtifact)); apklibDirectory.mkdirs(); unArchiver.setDestDirectory(apklibDirectory); try { unArchiver.extract(); } catch (ArchiverException e) { throw new MojoExecutionException("ArchiverException while extracting " + apklibDirectory.getAbsolutePath() + ". Message: " + e.getLocalizedMessage(), e); } projectHelper.addResource(project, apklibDirectory.getAbsolutePath() + File.separator + "src", null, Arrays.asList("**/*.java", "**/*.aidl")); project.addCompileSourceRoot(apklibDirectory.getAbsolutePath() + File.separator + "src"); } private void generateR() throws MojoExecutionException { getLog().debug("Generating R file for "+project.getPackaging()); genDirectory.mkdirs(); File[] overlayDirectories; if (resourceOverlayDirectories == null || resourceOverlayDirectories.length == 0) { overlayDirectories = new File[]{resourceOverlayDirectory}; } else { overlayDirectories = resourceOverlayDirectories; } if (extractedDependenciesRes.exists()) { try { getLog().info("Copying dependency resource files to combined resource directory."); if (!combinedRes.exists() && !combinedRes.mkdirs()) { throw new MojoExecutionException("Could not create directory for combined resources at " + combinedRes.getAbsolutePath()); } org.apache.commons.io.FileUtils.copyDirectory(extractedDependenciesRes, combinedRes); } catch (IOException e) { throw new MojoExecutionException("", e); } } if (resourceDirectory.exists() && combinedRes.exists()) { try { getLog().info("Copying local resource files to combined resource directory."); org.apache.commons.io.FileUtils.copyDirectory(resourceDirectory, combinedRes, new FileFilter() { /** * Excludes files matching one of the common file to exclude. * The default excludes pattern are the ones from * {org.codehaus.plexus.util.AbstractScanner#DEFAULTEXCLUDES} * @see java.io.FileFilter#accept(java.io.File) */ public boolean accept(File file) { for (String pattern : AbstractScanner.DEFAULTEXCLUDES) { if (AbstractScanner.match(pattern, file.getAbsolutePath())) { getLog().debug("Excluding " + file.getName() + " from resource copy : matching " + pattern); return false; } } return true; } }); } catch (IOException e) { throw new MojoExecutionException("", e); } } CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger(this.getLog()); List<String> commands = new ArrayList<String>(); commands.add("package"); commands.add("-m"); commands.add("-J"); commands.add(genDirectory.getAbsolutePath()); commands.add("-M"); commands.add(androidManifestFile.getAbsolutePath()); if (StringUtils.isNotBlank(customPackage)) { commands.add("--custom-package"); commands.add(customPackage); } for (File resOverlayDir : overlayDirectories) { if (resOverlayDir != null && resOverlayDir.exists()) { commands.add("-S"); commands.add(resOverlayDir.getAbsolutePath()); } } if (combinedRes.exists()) { commands.add("-S"); commands.add(combinedRes.getAbsolutePath()); } else { if (resourceDirectory.exists()) { commands.add("-S"); commands.add(resourceDirectory.getAbsolutePath()); } } for (Artifact artifact: getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { commands.add("-S"); commands.add(getLibraryUnpackDirectory(artifact)+ File.separator +"res"); } } commands.add("--auto-add-overlay"); if (assetsDirectory.exists()) { commands.add("-A"); commands.add(assetsDirectory.getAbsolutePath()); } if (extractedDependenciesAssets.exists()) { commands.add("-A"); commands.add(extractedDependenciesAssets.getAbsolutePath()); } commands.add("-I"); commands.add(getAndroidSdk().getAndroidJar().getAbsolutePath()); if (StringUtils.isNotBlank(configurations)) { commands.add("-c"); commands.add(configurations); } getLog().info(getAndroidSdk().getPathForTool("aapt") + " " + commands.toString()); try { executor.executeCommand(getAndroidSdk().getPathForTool("aapt"), commands, project.getBasedir(), false); } catch (ExecutionException e) { throw new MojoExecutionException("", e); } project.addCompileSourceRoot(genDirectory.getAbsolutePath()); } private void generateApklibR() throws MojoExecutionException { getLog().debug("Generating R file for projects dependent on apklibs"); genDirectory.mkdirs(); for (Artifact artifact : getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { generateRForApkLibDependency(artifact); } } project.addCompileSourceRoot(genDirectory.getAbsolutePath()); } private void generateRForApkLibDependency(Artifact apklibArtifact) throws MojoExecutionException { //final String unpackDir = getLibraryUnpackDirectory(apklibArtifact)+"/"+"source"; final String unpackDir = getLibraryUnpackDirectory(apklibArtifact); getLog().debug("Generating R file for apklibrary: " + apklibArtifact.getGroupId()); CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger(this.getLog()); List<String> commands = new ArrayList<String>(); commands.add("package"); commands.add("-m"); commands.add("-J"); commands.add(genDirectory.getAbsolutePath()); commands.add("--custom-package"); commands.add(extractPackageNameFromAndroidManifest(new File(unpackDir + File.separator + "AndroidManifest.xml"))); commands.add("-M"); commands.add(androidManifestFile.getAbsolutePath()); if (resourceDirectory.exists()){ commands.add("-S"); commands.add(resourceDirectory.getAbsolutePath()); } for (Artifact artifact: getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { final String apkLibResDir = getLibraryUnpackDirectory(artifact) + File.separator + "res"; if (new File(apkLibResDir).exists()){ commands.add("-S"); commands.add(apkLibResDir); } } } commands.add("--auto-add-overlay"); if (assetsDirectory.exists()) { commands.add("-A"); commands.add(assetsDirectory.getAbsolutePath()); } for (Artifact artifact : getAllRelevantDependencyArtifacts()) { if (artifact.getType().equals(APKLIB)) { final String apkLibAssetsDir = getLibraryUnpackDirectory(artifact) + File.separator + "assets"; if (new File(apkLibAssetsDir).exists()){ commands.add("-A"); commands.add(apkLibAssetsDir); } } } commands.add("-I"); commands.add(getAndroidSdk().getAndroidJar().getAbsolutePath()); if (StringUtils.isNotBlank(configurations)) { commands.add("-c"); commands.add(configurations); } getLog().info(getAndroidSdk().getPathForTool("aapt") + " " + commands.toString()); try { executor.executeCommand(getAndroidSdk().getPathForTool("aapt"), commands, project.getBasedir(), false); } catch (ExecutionException e) { throw new MojoExecutionException("", 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<File /*sourceDirectory*/, String[] /*relativeAidlFileNames*/> files) throws MojoExecutionException { List<String> protoCommands = new ArrayList<String>(); protoCommands.add("-p" + getAndroidSdk().getPathForFrameworkAidl()); genDirectoryAidl.mkdirs(); project.addCompileSourceRoot(genDirectoryAidl.getPath()); Set<File> 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<String> commands = new ArrayList<String>(protoCommands); commands.add(aidlFileInSourceDirectory.getAbsolutePath()); commands.add(new File(targetDirectory, shortJavaFileName).getAbsolutePath()); try { CommandExecutor executor = CommandExecutor.Factory.createDefaultCommmandExecutor(); executor.setLogger(this.getLog()); executor.executeCommand(getAndroidSdk().getPathForTool("aidl"), commands, project.getBasedir(), false); } catch (ExecutionException e) { throw new MojoExecutionException("", e); } } } } private String[] findRelativeAidlFileNames(File sourceDirectory) { String[] relativeAidlFileNames = findFilesInDirectory(sourceDirectory, "**/*.aidl"); 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<String> androidArtifacts = new HashSet<String>() {{ addAll(Arrays.asList(APK, APKLIB, APKSOURCES)); }}; return androidArtifacts.contains(project.getArtifact().getType()); } }