com.jayway.maven.plugins.android.standalonemojos.ManifestUpdateMojo Maven / Gradle / Ivy
Show all versions of android-maven-plugin Show documentation
package com.jayway.maven.plugins.android.standalonemojos;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import com.jayway.maven.plugins.android.configuration.Manifest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import com.jayway.maven.plugins.android.AbstractAndroidMojo;
import com.jayway.maven.plugins.android.common.AndroidExtension;
/**
* Updates various version attributes present in the AndroidManifest.xml
file.
*
* You can configure this mojo to update the following manifest attributes:
*
*
* android:versionName
on the manifest
element.
* android:versionCode
on the manifest
element.
* android:sharedUserId
on the manifest
element.
* android:debuggable
on the application
element.
*
*
* Note: This process will reformat the AndroidManifest.xml
per JAXP {@link Transformer} defaults if updates are made to the manifest.
*
* You can configure attributes in the plugin configuration like so
*
* <plugin>
* <groupId>com.jayway.maven.plugins.android.generation2</groupId>
* <artifactId>android-maven-plugin</artifactId>
* <executions>
* <execution>
* <id>update-manifest</id>
* <goals>
* <goal>manifest-update</goal>
* </goals>
* <configuration>
* <manifest>
* <versionName></versionName>
* <versionCode>123</versionCode>
* <versionCodeAutoIncrement>true|false</versionCodeAutoIncrement>
* <versionCodeUpdateFromVersion>true|false</versionCodeUpdateFromVersion>
* <sharedUserId>anId</sharedUserId>
* <debuggable>true|false</debuggable>
* </manifest>
* </configuration>
* </execution>
* </executions>
* </plugin>
*
* or use properties set in the pom or settings file or supplied as parameter. All parameters follow a android
* .manifest.* naming convention.
*
*
* @author [email protected]
* @author [email protected]
* @author Manfred Moser
* @goal manifest-update
* @requiresProject true
* @phase process-resources
*
*/
public class ManifestUpdateMojo extends AbstractAndroidMojo {
private static final String ATTR_VERSION_NAME = "android:versionName";
private static final String ATTR_VERSION_CODE = "android:versionCode";
private static final String ATTR_SHARED_USER_ID = "android:sharedUserId";
private static final String ATTR_DEBUGGABLE = "android:debuggable";
private static final String ELEM_APPLICATION = "application";
/**
* The container for all the manifest update related configuration.
*
* @parameter
*/
private Manifest manifest;
/**
* Update the android:versionName
with the specified parameter. If left empty it
* will use the version number of the project.
*
* @parameter expression="${android.manifest.versionName}" default-value="${project.version}"
*/
protected String versionName;
/**
* Update the android:versionCode
attribute with the specified parameter.
*
* @parameter expression="${android.manifest.versionCode}"
*/
protected Integer versionCode;
/**
* Auto increment the android:versionCode
attribute with each build.
*
* @parameter expression="${android.manifest.versionCodeAutoincrement}" default-value="false"
*/
private boolean versionCodeAutoIncrement = false;
/**
* Update the android:versionCode
attribute automatically from the project version
* e.g 3.0.1 will become version code 301. As described in this blog post
* http://www.simpligility.com/2010/11/release-version-management-for-your-android-application/
* but done without using resource filtering.
*
* @parameter expression="${android.manifest.versionCodeUpdateFromVersion} default-value="false"
*/
protected Boolean versionCodeUpdateFromVersion = false;
/**
* Update the android:sharedUserId
attribute with the specified parameter.
*
* @parameter expression="${android.manifest.sharedUserId}"
*/
protected String sharedUserId;
/**
* Update the android:debuggable
attribute with the specified parameter.
*
* @parameter expression="${android.manifest.debuggable}"
*/
protected Boolean debuggable;
private String parsedVersionName;
private Integer parsedVersionCode;
private boolean parsedVersionCodeAutoIncrement;
private Boolean parsedVersionCodeUpdateFromVersion;
private String parsedSharedUserId;
private Boolean parsedDebuggable;
public void execute() throws MojoExecutionException, MojoFailureException {
if (!AndroidExtension.isAndroidPackaging(project.getPackaging())) {
return; // skip, not an android project.
}
if (androidManifestFile == null) {
return; // skip, no androidmanifest.xml defined (rare case)
}
parseConfiguration();
getLog().info("Attempting to update manifest " + androidManifestFile);
getLog().debug(" versionName=" + parsedVersionName);
getLog().debug(" versionCode=" + parsedVersionCode);
getLog().debug(" versionCodeAutoIncrement=" + parsedVersionCodeAutoIncrement);
getLog().debug(" versionCodeUpdateFromVersion=" + parsedVersionCodeUpdateFromVersion);
getLog().debug(" sharedUserId=" + parsedSharedUserId);
getLog().debug(" debuggable=" + parsedDebuggable);
if (!androidManifestFile.exists()) {
return; // skip, no AndroidManifest.xml file found.
}
try {
updateManifest(androidManifestFile);
} catch (IOException e) {
throw new MojoFailureException("XML I/O error: " + androidManifestFile, e);
} catch (ParserConfigurationException e) {
throw new MojoFailureException("Unable to prepare XML parser", e);
} catch (SAXException e) {
throw new MojoFailureException("Unable to parse XML: " + androidManifestFile, e);
} catch (TransformerException e) {
throw new MojoFailureException("Unable write XML: " + androidManifestFile, e);
}
}
private void parseConfiguration() {
// manifest element found in plugin config in pom
if (manifest != null) {
parsedVersionName = manifest.getVersionName();
parsedVersionCode = manifest.getVersionCode();
parsedVersionCodeAutoIncrement = manifest.isVersionCodeAutoIncrement();
parsedVersionCodeUpdateFromVersion = manifest.getVersionCodeUpdateFromVersion();
parsedSharedUserId = manifest.getSharedUserId();
parsedDebuggable = manifest.getDebuggable();
} else {
parsedVersionName = versionName;
parsedVersionCode = versionCode;
parsedVersionCodeAutoIncrement = versionCodeAutoIncrement;
parsedVersionCodeUpdateFromVersion = versionCodeUpdateFromVersion;
parsedSharedUserId = sharedUserId;
parsedDebuggable = debuggable;
}
}
/**
* Read manifest using JAXP
*/
private Document readManifest(File manifestFile) throws IOException, ParserConfigurationException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(manifestFile);
return doc;
}
/**
* Write manifest using JAXP transformer
*/
private void writeManifest(File manifestFile, Document doc) throws IOException, TransformerException {
TransformerFactory xfactory = TransformerFactory.newInstance();
Transformer xformer = xfactory.newTransformer();
xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
Source source = new DOMSource(doc);
FileWriter writer = null;
try {
writer = new FileWriter(manifestFile, false);
String xmldecl = String.format("%n", doc.getXmlVersion(),
doc.getXmlEncoding());
writer.write(xmldecl);
Result result = new StreamResult(writer);
xformer.transform(source, result);
} finally {
IOUtils.closeQuietly(writer);
}
}
public void updateManifest(File manifestFile) throws IOException, ParserConfigurationException, SAXException,
TransformerException, MojoFailureException {
Document doc = readManifest(manifestFile);
Element manifestElement = doc.getDocumentElement();
boolean dirty = false;
if (StringUtils.isEmpty(parsedVersionName)) { // default to ${project.version}
parsedVersionName = project.getVersion();
}
Attr versionNameAttrib = manifestElement.getAttributeNode(ATTR_VERSION_NAME);
if (versionNameAttrib == null || !StringUtils.equals(parsedVersionName, versionNameAttrib.getValue())) {
getLog().info("Setting " + ATTR_VERSION_NAME +" to " + parsedVersionName);
manifestElement.setAttribute(ATTR_VERSION_NAME, parsedVersionName);
dirty = true;
}
if ((parsedVersionCodeAutoIncrement && parsedVersionCode != null) ||
(parsedVersionCodeUpdateFromVersion && parsedVersionCode != null) ||
(parsedVersionCodeAutoIncrement && parsedVersionCodeUpdateFromVersion)) {
throw new MojoFailureException("versionCodeAutoIncrement, versionCodeUpdateFromVersion and versionCode " +
"are mutual exclusive. They cannot be specified at the same time. " +
"Please specify either versionCodeAutoIncrement, versionCodeUpdateFromVersion or versionCode!");
}
if (parsedVersionCodeAutoIncrement) {
Attr versionCode = manifestElement.getAttributeNode(ATTR_VERSION_CODE);
int currentVersionCode = 0;
if (versionCode != null) {
currentVersionCode = NumberUtils.toInt(versionCode.getValue(), 0);
}
currentVersionCode++;
manifestElement.setAttribute(ATTR_VERSION_CODE, String.valueOf(currentVersionCode));
dirty = true;
}
if (parsedVersionCodeUpdateFromVersion) {
String verString = project.getVersion();
getLog().debug("Generating versionCode for " + verString);
ArtifactVersion artifactVersion = new DefaultArtifactVersion(verString);
String verCode = Integer.toString(artifactVersion.getMajorVersion()) +
Integer.toString(artifactVersion.getMinorVersion()) +
Integer.toString(artifactVersion.getIncrementalVersion());
getLog().info("Setting " + ATTR_VERSION_CODE + " to " + verCode);
manifestElement.setAttribute(ATTR_VERSION_CODE, verCode);
dirty = true;
}
if (parsedVersionCode != null) {
Attr versionCodeAttr = manifestElement.getAttributeNode(ATTR_VERSION_CODE);
int currentVersionCode = 0;
if (versionCodeAttr != null) {
currentVersionCode = NumberUtils.toInt(versionCodeAttr.getValue(), 0);
}
if (currentVersionCode != parsedVersionCode) {
getLog().info("Setting " + ATTR_VERSION_CODE + " to " + parsedVersionCode);
manifestElement.setAttribute(ATTR_VERSION_CODE, String.valueOf(parsedVersionCode));
dirty = true;
}
}
if (!StringUtils.isEmpty(parsedSharedUserId)) {
Attr sharedUserIdAttrib = manifestElement.getAttributeNode(ATTR_SHARED_USER_ID);
if (sharedUserIdAttrib == null || !StringUtils.equals(parsedSharedUserId, sharedUserIdAttrib.getValue())) {
getLog().info("Setting " + ATTR_SHARED_USER_ID +" to " + parsedSharedUserId);
manifestElement.setAttribute(ATTR_SHARED_USER_ID, parsedSharedUserId);
dirty = true;
}
}
if (parsedDebuggable != null) {
NodeList appElems = manifestElement.getElementsByTagName(ELEM_APPLICATION);
// Update all application nodes. Not sure whether there will ever be more than one.
for (int i = 0; i < appElems.getLength(); ++i) {
Node node = appElems.item(i);
getLog().info("Testing if node " + node.getNodeName() + " is application");
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element)node;
Attr debuggableAttrib = element.getAttributeNode(ATTR_DEBUGGABLE);
if (debuggableAttrib == null || debuggable != BooleanUtils.toBoolean(debuggableAttrib.getValue())) {
getLog().info("Setting " + ATTR_DEBUGGABLE + " to " + parsedDebuggable);
element.setAttribute(ATTR_DEBUGGABLE, String.valueOf(parsedDebuggable));
dirty = true;
}
}
}
}
if (dirty) {
if (!manifestFile.delete()) {
getLog().warn("Could not remove old " + manifestFile);
}
getLog().info("Made changes to manifest file, updating " + manifestFile);
writeManifest(manifestFile, doc);
} else {
getLog().info("No changes found to write to manifest file");
}
}
}