blob: 385f1c08a83529d25ec4830a0f903f6282e32dc0 [file] [log] [blame]
"""Utilities to access an Android tree manifest data."""
import re
import xml.dom.minidom
class ProjectNotFoundError(Exception):
"""When a project is not found.
Attributes:
project: The project name.
"""
def __init__(self, project):
self.project = project
super(ProjectNotFoundError, self).__init__(
"Project not found: %s" % (project,)
)
class AndroidManifest:
"""A simple wrapper around an Android tree manifest.
Attributes:
projects: The mapping of AOSP project names to the checked-out project
path.
"""
#: Match the AOSP project name in a Fairphone manifest (special prefixes)
_AOSP_NAME_IN_FP_MANIFEST = re.compile(
r"^((fp2|fp2-dev)/)?(?P<name>(kernel|platform)/.*)$"
)
def __init__(self, manifest_file):
"""Create an instance from an Android tree XML file."""
self._root = self._load_manifest(manifest_file)
self.projects = self._extract_projects(self._root)
@classmethod
def _load_manifest(cls, manifest_file):
"""Load a XML manifest.
Arguments:
manifest_file: The path to the XML manifest file.
Returns:
The first XML node describing the manifest content.
Raises:
ValueError: If the manifest is a malformed XML file or if the
manifest node is missing.
"""
try:
root = xml.dom.minidom.parse(manifest_file)
except (OSError, xml.parsers.expat.ExpatError) as exception:
raise ValueError(
"Error parsing manifest %s: %s" % (manifest_file, exception)
)
if not root or not root.childNodes:
raise ValueError("No root node in %s" % (manifest_file,))
for node in root.childNodes:
if node.nodeName == "manifest":
return node
raise ValueError("No <manifest> node in %s" % (manifest_file,))
@classmethod
def _extract_projects(cls, root):
"""Extract the projects from an Android tree XML manifest.
The Fairphone manifests have changed some project names by adding the
prefix "fp2/" to them: "fp2/kernel/*", "fp2/platform/*", "fp2/vendor/*".
Vendor projects are, by definition, not going to be advertised in the
original Android manifest. Kernel and platform projects are in the AOSP
manifests and can thus be targeted by security bulletins. The Fairphone
prefix is removed to make the projects names match those from the AOSP
manifest.
The Fairphone Sibon manifests are using "fp2-dev/" in place of "fp2/".
They are also handled and removed here.
Another exception is the manifest project itself. The Fairphone
manifests do not include a self reference like the AOSP manifests do.
This is worked around by pointing to the checked-out manifests project.
Arguments:
root: The XML root node describing the manifest.
Returns:
The mapping of project names to real project paths (the check-outs).
Raises:
ValueError: If the manifest is a malformed XML file, if the
manifest node is missing, or if a project name appears more than
once.
"""
# The Fairphone manifests do not include self-references, add them here
projects = {
"manifest": ".repo/manifests",
"platform/manifest": ".repo/manifests",
}
for node in root.childNodes:
if node.nodeName == "project":
name = cls._get_project_name(node)
if name in projects:
raise ValueError("Duplicate project %s in manifest" % name)
path = node.getAttribute("path") or name
projects[name] = path
return projects
@classmethod
def _get_project_name(cls, node):
"""Extract the AOSP project name from an XML node.
Arguments:
node: The project XML node.
Returns:
The project name cleared of Fairphone-specific prefixes.
"""
name = node.getAttribute("name")
# Fix Fairphone-specific names
match = cls._AOSP_NAME_IN_FP_MANIFEST.match(name)
if match:
name = match.group("name")
return name