|  | # Lint as: python2, python3 | 
|  | # pylint: disable=g-direct-third-party-import | 
|  | # Copyright 2017 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. | 
|  | """AndroidManifest checks for android_instrumentation_test. | 
|  |  | 
|  | Ensures that the targetPackage of the instrumentation APK references | 
|  | the correct target package name. | 
|  | """ | 
|  |  | 
|  | import os | 
|  | import sys | 
|  | import xml.etree.ElementTree as ET | 
|  |  | 
|  | # Do not edit this line. Copybara replaces it with PY2 migration helper. | 
|  | from absl import app | 
|  | from absl import flags | 
|  |  | 
|  | flags.DEFINE_string("instrumentation_manifest", None, | 
|  | "AndroidManifest.xml of the instrumentation APK") | 
|  | flags.DEFINE_string("target_manifest", None, | 
|  | "AndroidManifest.xml of the target APK") | 
|  | flags.DEFINE_string("output", None, "Output of the check") | 
|  |  | 
|  | FLAGS = flags.FLAGS | 
|  |  | 
|  |  | 
|  | class ManifestError(Exception): | 
|  | """Raised when there is a problem with an AndroidManifest.xml.""" | 
|  |  | 
|  |  | 
|  | # There might be more than one <instrumentation> tag to use different | 
|  | # test runners, so we need to extract the targetPackage attribute values | 
|  | # from all of them and check that they are the same. | 
|  | def _ExtractTargetPackageToInstrument(xml_content, path): | 
|  | """Extract the targetPackage value from the <instrumentation> tag.""" | 
|  |  | 
|  | # https://developer.android.com/guide/topics/manifest/manifest-element.html | 
|  | # xmlns:android is the required namespace in an Android manifest. | 
|  | tree = ET.ElementTree(ET.fromstring(xml_content)) | 
|  | package_key = "{http://schemas.android.com/apk/res/android}targetPackage" | 
|  | instrumentation_elems = tree.iterfind( | 
|  | ".//instrumentation[@{0}]".format(package_key)) | 
|  |  | 
|  | package_names = set(e.attrib[package_key] for e in instrumentation_elems) | 
|  |  | 
|  | if not package_names: | 
|  | raise ManifestError("No <instrumentation> tag containing " | 
|  | "the targetPackage attribute is found in the " | 
|  | "manifest at %s" % path) | 
|  |  | 
|  | if len(package_names) > 1: | 
|  | raise ManifestError( | 
|  | "The <instrumentation> tags in the manifest at %s do not " | 
|  | "reference the same target package: %s" % (path, list(package_names))) | 
|  |  | 
|  | return package_names.pop() | 
|  |  | 
|  |  | 
|  | def _ExtractTargetPackageName(xml_content, path): | 
|  | """Extract the package name value from the root <manifest> tag.""" | 
|  | tree = ET.ElementTree(ET.fromstring(xml_content)) | 
|  | root = tree.getroot() | 
|  | if "package" in root.attrib: | 
|  | return root.attrib["package"] | 
|  | else: | 
|  | raise ManifestError("The <manifest> tag in the manifest at %s needs to " | 
|  | "specify the package name using the 'package' " | 
|  | "attribute." % path) | 
|  |  | 
|  |  | 
|  | def _ValidateManifestPackageNames(instr_manifest_content, instr_manifest_path, | 
|  | target_manifest_content, | 
|  | target_manifest_path): | 
|  | """Diff the package names and throw a ManifestError if not identical.""" | 
|  | target_package_to_instrument = _ExtractTargetPackageToInstrument( | 
|  | instr_manifest_content, instr_manifest_path) | 
|  | target_package_name = _ExtractTargetPackageName(target_manifest_content, | 
|  | target_manifest_path) | 
|  |  | 
|  | if target_package_to_instrument != target_package_name: | 
|  | raise ManifestError( | 
|  | "The targetPackage specified in the instrumentation manifest at " | 
|  | "{instr_manifest_path} ({target_package_to_instrument}) does not match " | 
|  | "the package name of the target manifest at {target_manifest_path} " | 
|  | "({target_package_name})".format( | 
|  | instr_manifest_path=instr_manifest_path, | 
|  | target_package_to_instrument=target_package_to_instrument, | 
|  | target_manifest_path=target_manifest_path, | 
|  | target_package_name=target_package_name)) | 
|  |  | 
|  | return target_package_to_instrument, target_package_name | 
|  |  | 
|  |  | 
|  | def main(unused_argv): | 
|  | instr_manifest_path = FLAGS.instrumentation_manifest | 
|  | target_manifest_path = FLAGS.target_manifest | 
|  | output_path = FLAGS.output | 
|  | dirname = os.path.dirname(output_path) | 
|  | if not os.path.exists(dirname): | 
|  | os.makedirs(dirname) | 
|  |  | 
|  | with open(instr_manifest_path, "rb") as f: | 
|  | instr_manifest = f.read() | 
|  |  | 
|  | with open(target_manifest_path, "rb") as f: | 
|  | target_manifest = f.read() | 
|  |  | 
|  | try: | 
|  | package_to_instrument, package_name = _ValidateManifestPackageNames( | 
|  | instr_manifest, instr_manifest_path, target_manifest, | 
|  | target_manifest_path) | 
|  | except ManifestError as e: | 
|  | sys.exit(str(e)) | 
|  |  | 
|  | with open(output_path, "w") as f: | 
|  | f.write("target_package={0}\n".format(package_to_instrument)) | 
|  | f.write("package_name={0}\n".format(package_name)) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | app.run(main) |