com.google.devtools.build.android.ManifestMergerAction.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.android.ManifestMergerAction.java

Source

// Copyright 2016 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 java.util.logging.Level.SEVERE;

import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
import com.google.devtools.build.android.Converters.ExistingPathStringDictionaryConverter;
import com.google.devtools.build.android.Converters.MergeTypeConverter;
import com.google.devtools.build.android.Converters.PathConverter;
import com.google.devtools.build.android.Converters.StringDictionaryConverter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParser;

import com.android.manifmerger.ManifestMerger2.MergeType;
import com.android.utils.StdLogger;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

/**
 * An action to perform manifest merging using the Gradle manifest merger.
 *
 * <pre>
 * Example Usage:
 *   java/com/google/build/android/ManifestMergerAction
 *       --manifest path to primary manifest
 *       --mergeeManifests colon separated list of manifests to merge
 *       --mergeType APPLICATION|LIBRARY
 *       --manifestValues key value pairs of manifest overrides
 *       --customPackage package to write for library manifest
 *       --manifestOutput path to write output manifest
 * </pre>
 */
public class ManifestMergerAction {
    /** Flag specifications for this action. */
    public static final class Options extends OptionsBase {
        @Option(name = "manifest", defaultValue = "null", converter = ExistingPathConverter.class, category = "input", help = "Path of primary manifest.")
        public Path manifest;

        @Option(name = "mergeeManifests", defaultValue = "", converter = ExistingPathStringDictionaryConverter.class, category = "input", help = "A dictionary of manifests, and originating target, to be merged into manifest.")
        public Map<Path, String> mergeeManifests;

        @Option(name = "mergeType", defaultValue = "APPLICATION", converter = MergeTypeConverter.class, category = "config", help = "The type of merging to perform.")
        public MergeType mergeType;

        @Option(name = "manifestValues", defaultValue = "", converter = StringDictionaryConverter.class, category = "config", help = "A dictionary string of values to be overridden in the manifest. Any instance of "
                + "${name} in the manifest will be replaced with the value corresponding to name in "
                + "this dictionary. applicationId, versionCode, versionName, minSdkVersion, "
                + "targetSdkVersion and maxSdkVersion have a dual behavior of also overriding the "
                + "corresponding attributes of the manifest and uses-sdk tags. packageName will be "
                + "ignored and will be set from either applicationId or the package in manifest. The "
                + "expected format of this string is: key:value[,key:value]*. The keys and values "
                + "may contain colons and commas as long as they are escaped with a backslash.")
        public Map<String, String> manifestValues;

        @Option(name = "customPackage", defaultValue = "null", category = "config", help = "Custom java package to insert in the package attribute of the manifest tag.")
        public String customPackage;

        @Option(name = "manifestOutput", defaultValue = "null", converter = PathConverter.class, category = "output", help = "Path for the merged manifest.")
        public Path manifestOutput;

        @Option(name = "log", defaultValue = "null", category = "output", converter = PathConverter.class, help = "Path to where the merger log should be written.")
        public Path log;
    }

    private static final String[] PERMISSION_TAGS = new String[] { "uses-permission", "uses-permission-sdk-23" };
    private static final StdLogger stdLogger = new StdLogger(StdLogger.Level.WARNING);
    private static final Logger logger = Logger.getLogger(ManifestMergerAction.class.getName());

    private static Options options;

    private static Path removePermissions(Path manifest, Path outputDir)
            throws IOException, ParserConfigurationException, TransformerConfigurationException,
            TransformerException, TransformerFactoryConfigurationError, SAXException {
        DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document doc = docBuilder.parse(manifest.toFile());
        for (String tag : PERMISSION_TAGS) {
            NodeList permissions = doc.getElementsByTagName(tag);
            if (permissions != null) {
                for (int i = permissions.getLength() - 1; i >= 0; i--) {
                    Node permission = permissions.item(i);
                    permission.getParentNode().removeChild(permission);
                }
            }
        }
        // Write resulting manifest to the output directory, maintaining full path to prevent collisions
        Path output = outputDir.resolve(manifest.toString().replaceFirst("^/", ""));
        Files.createDirectories(output.getParent());
        TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc),
                new StreamResult(output.toFile()));
        return output;
    }

    public static void main(String[] args) throws Exception {
        OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class);
        optionsParser.parseAndExitUponError(args);
        options = optionsParser.getOptions(Options.class);

        final AndroidResourceProcessor resourceProcessor = new AndroidResourceProcessor(stdLogger);

        try {
            Path mergedManifest;
            if (options.mergeType == MergeType.APPLICATION) {
                // Remove uses-permission tags from mergees before the merge.
                Path tmp = Files.createTempDirectory("manifest_merge_tmp");
                tmp.toFile().deleteOnExit();
                ImmutableMap.Builder<Path, String> mergeeManifests = ImmutableMap.builder();
                for (Entry<Path, String> mergeeManifest : options.mergeeManifests.entrySet()) {
                    mergeeManifests.put(removePermissions(mergeeManifest.getKey(), tmp), mergeeManifest.getValue());
                }

                // Ignore custom package at the binary level.
                mergedManifest = resourceProcessor.mergeManifest(options.manifest, mergeeManifests.build(),
                        options.mergeType, options.manifestValues, options.manifestOutput, options.log);
            } else {
                // Only need to stamp custom package into the library level.
                mergedManifest = resourceProcessor.writeManifestPackage(options.manifest, options.customPackage,
                        options.manifestOutput);
            }

            if (!mergedManifest.equals(options.manifestOutput)) {
                Files.copy(options.manifest, options.manifestOutput, StandardCopyOption.REPLACE_EXISTING);
            }

            // Set to the epoch for caching purposes.
            Files.setLastModifiedTime(options.manifestOutput, FileTime.fromMillis(0L));
        } catch (IOException e) {
            logger.log(SEVERE, "Error during merging manifests", e);
            throw e;
        } finally {
            resourceProcessor.shutdown();
        }
    }
}