| // Copyright 2018 The Bazel Authors. All rights reserved. | |
| // | |
| // 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.google.devtools.build.android; | |
| import static com.google.common.base.Predicates.not; | |
| import com.android.SdkConstants; | |
| import com.google.common.base.MoreObjects; | |
| import com.google.common.base.Strings; | |
| import com.google.common.collect.ImmutableList; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.StandardOpenOption; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| import java.util.logging.Logger; | |
| import javax.xml.namespace.QName; | |
| import javax.xml.stream.XMLEventReader; | |
| import javax.xml.stream.XMLStreamException; | |
| import javax.xml.stream.events.Attribute; | |
| import javax.xml.stream.events.StartElement; | |
| import javax.xml.stream.events.XMLEvent; | |
| /** Represents an AndroidManifest in the context of resource link and compilation. */ | |
| public class AndroidManifest { | |
| private static final Logger logger = Logger.getLogger(AndroidManifest.class.getName()); | |
| private static final QName PACKAGE_NAME = QName.valueOf("package"); | |
| private static final QName MANIFEST = QName.valueOf("manifest"); | |
| private static final QName USES_SDK = QName.valueOf("uses-sdk"); | |
| private static final QName MIN_SDK = | |
| new QName(SdkConstants.ANDROID_URI, "minSdkVersion", "android"); | |
| private final String packageName; | |
| private final String minSdk; | |
| private AndroidManifest(String packageName, String minSdk) { | |
| this.packageName = packageName; | |
| this.minSdk = minSdk; | |
| } | |
| /** Parses the manifest from the path. */ | |
| public static AndroidManifest parseFrom(Path manifest) { | |
| try (InputStream input = Files.newInputStream(manifest)) { | |
| final XMLEventReader xmlEventReader = | |
| XmlResourceValues.getXmlInputFactory().createXMLEventReader(input); | |
| String packageName = ""; | |
| String minSdk = ""; | |
| while (xmlEventReader.hasNext()) { | |
| final XMLEvent xmlEvent = xmlEventReader.nextEvent(); | |
| if (xmlEvent.isStartElement()) { | |
| final StartElement element = xmlEvent.asStartElement(); | |
| if (MANIFEST.equals(element.getName())) { | |
| packageName = getAttributeValue(xmlEvent, PACKAGE_NAME).orElse(packageName); | |
| } | |
| if (USES_SDK.equals(element.getName())) { | |
| minSdk = getAttributeValue(xmlEvent, MIN_SDK).orElse(minSdk); | |
| } | |
| } | |
| } | |
| if (minSdk.isEmpty()) { | |
| logger.warning( | |
| String.format( | |
| "\n\u001B[31mCONFIGURATION:\u001B[0m" + " %s has no minSdkVersion. Using 1.", | |
| manifest)); | |
| } | |
| return new AndroidManifest(packageName, minSdk.isEmpty() ? "1" : minSdk); | |
| } catch (IOException | XMLStreamException e) { | |
| throw new ManifestProcessingException(e); | |
| } | |
| } | |
| private static Optional<String> getAttributeValue(XMLEvent xmlEvent, QName attributeName) { | |
| return Optional.ofNullable(xmlEvent.asStartElement().getAttributeByName(attributeName)) | |
| .map(Attribute::getValue) | |
| .filter(not(String::isEmpty)); | |
| } | |
| public static AndroidManifest asEmpty() { | |
| return new AndroidManifest("", "1"); | |
| } | |
| public static AndroidManifest of(String packageName, String minSdk) { | |
| return new AndroidManifest(packageName, minSdk); | |
| } | |
| @Override | |
| public boolean equals(Object o) { | |
| if (this == o) { | |
| return true; | |
| } | |
| if (!(o instanceof AndroidManifest)) { | |
| return false; | |
| } | |
| AndroidManifest that = (AndroidManifest) o; | |
| return Objects.equals(packageName, that.packageName) && Objects.equals(minSdk, that.minSdk); | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this) | |
| .add("packageName", packageName) | |
| .add("minSdk", minSdk) | |
| .toString(); | |
| } | |
| @Override | |
| public int hashCode() { | |
| return Objects.hash(packageName, minSdk); | |
| } | |
| /** Creates a dummy manifest for processing without any manifest template variables. */ | |
| public Path writeDummyManifestForAapt(Path workingDirectory, String packageForR) { | |
| try { | |
| return Files.write( | |
| Files.createDirectories(workingDirectory).resolve("AndroidManifest.xml"), | |
| ImmutableList.of( | |
| "<?xml version='1.0' encoding='utf-8'?>", | |
| "<manifest xmlns:android='http://schemas.android.com/apk/res/android'", | |
| String.format( | |
| "package='%s'>", Strings.isNullOrEmpty(packageForR) ? packageName : packageForR), | |
| "<application/>", | |
| Strings.isNullOrEmpty(minSdk) | |
| ? "" | |
| : String.format("<uses-sdk android:minSdkVersion='%s'/>", minSdk), | |
| "</manifest>"), | |
| StandardOpenOption.CREATE_NEW); | |
| } catch (IOException e) { | |
| throw new AndroidManifestProcessor.ManifestProcessingException(e); | |
| } | |
| } | |
| } |