blob: 6498ccf6334e668b71509449a1a37a3aa52fffa2 [file] [log] [blame] [edit]
# Copyright 2022 The ChromiumOS Authors.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Routines which facilitate data collection for build operations.
Service module to parse or extract data about build behavior for structured
data storage. Complementary to chromite.lib.metrics, chromite.api.metrics, and
related library usage.
"""
import functools
import logging
import os
import re
from typing import Dict, Iterable, NamedTuple, Pattern, Tuple
from chromite.lib import portage_util
from chromite.lib.parser import package_info
class PackageVersion(NamedTuple):
"""Container class akin to chromite.observability.PackageVersion proto."""
major: int
minor: int
patch: int
extended: int
revision: int
full_version: str
class PackageName(NamedTuple):
"""Container class akin to chromite.observability.PackageName proto."""
atom: str
category: str
package_name: str
class PackageIdentifier(NamedTuple):
"""Container class akin to chromite.observability.PackageIdentifier proto."""
package_name: PackageName
package_version: PackageVersion
@functools.lru_cache(maxsize=None)
def _get_version_component_regex() -> Pattern:
# Parse version number, expecting up to 4 integer components arranged
# like: major[.minor[.patch[.extended]]]
extended = r'(?:\.(?P<extended>\d+))?'
patch = rf'(?:\.(?P<patch>\d+){extended})?'
minor = rf'(?:\.(?P<minor>\d+){patch})?'
complete = rf'^(?P<major>\d+){minor}'
return re.compile(complete)
# TODO(zland): refactor scripts/pkg_size (and this function) to use common
# library. This implementation does not want to recreate metrics records for the
# base image's rootfs, and we may not want to append all of the partition &
# image information to metrics, so a simplified approach to
# chromite/scripts/pkg_size is being used here.
def get_package_details_for_partition(
installation_path: os.PathLike,
pkgs: Iterable[Tuple[portage_util.InstalledPackage, Iterable[Tuple[str,
str]]]]
) -> Dict[PackageIdentifier, int]:
"""Retrieve package size and format name details for a given set of packages.
Args:
installation_path: The path to the partition's root that the package's
installed files are relative to.
pkgs: The packages of interest in the partition (typically, the entire
contents of the package db).
"""
details = {}
for installed_package, pkg_fileset in pkgs:
size = portage_util.CalculatePackageSize(pkg_fileset, installation_path)
pkg_identifier = parse_package_name(installed_package.package_info)
details[pkg_identifier] = size
return details
def parse_package_name(pkg_info: package_info.PackageInfo) -> PackageIdentifier:
"""Produce detailed NamedTuple for a package from a PackageInfo object."""
# Version number parsing, looking for 1-4 numerical components and ignoring
# any alphanumeric suffixes (e.g. _alpha1).
matcher = _get_version_component_regex()
matches = matcher.match(pkg_info.version)
major = int(matches.group('major') or 0)
minor = int(matches.group('minor') or 0)
patch = int(matches.group('patch') or 0)
extended = int(matches.group('extended') or 0)
version = PackageVersion(
major=major,
minor=minor,
patch=patch,
extended=extended,
revision=pkg_info.revision,
full_version=pkg_info.vr)
name = PackageName(
atom=pkg_info.atom,
category=pkg_info.category,
package_name=pkg_info.package)
return PackageIdentifier(package_name=name, package_version=version)