blob: a8cb36126c2aebc92f10eeb4e16da7f0238f83fb [file] [log] [blame]
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Determines all Chrome OS packages that are marked as `hot`.
Dumps results as a list of package names to a JSON file. Hotness is
determined by statically analyzing an ebuild.
Primarily intended for use by the Chrome OS toolchain team.
"""
import json
import logging
from pathlib import Path
from typing import Iterable, List, Tuple
from chromite.lib import commandline
from chromite.lib import portage_util
def contains_ebuild_hot_marker(file_path: Path):
"""Returns whether a file seems to be marked as hot."""
ebuild_contents = file_path.read_text(encoding="utf-8")
return "cros_optimize_package_for_speed" in ebuild_contents
def locate_all_package_ebuilds(
overlays: Iterable[Path],
) -> Iterable[Tuple[Path, str, List[Path]]]:
"""Determines package -> ebuild mappings for all packages.
Yields a series of (package_path, package_name, [path_to_ebuilds]). This may
yield the same package name multiple times if it's available in multiple
overlays.
"""
for overlay in overlays:
# Note that portage_util.GetOverlayEBuilds can't be used here, since that
# specifically only searches for cros_workon candidates. We care about
# everything we can possibly build.
for package_dir in overlay.glob("*/*"):
ebuilds = list(package_dir.glob("*.ebuild"))
if ebuilds:
package_name = f"{package_dir.parent.name}/{package_dir.name}"
yield package_dir, package_name, ebuilds
def locate_all_hot_env_packages(overlays: Iterable[Path]) -> Iterable[str]:
"""Yields packages marked hot by files in chromeos/config/env."""
for overlay in overlays:
env_dir = overlay / "chromeos" / "config" / "env"
for f in env_dir.glob("*/*"):
if f.is_file() and contains_ebuild_hot_marker(f):
yield f"{f.parent.name}/{f.name}"
def main(argv: List[str]):
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument("--output", required=True, type=Path)
opts = parser.parse_args(argv)
ebuilds_found = 0
packages_found = 0
merged_packages = 0
overlays = portage_util.FindOverlays(overlay_type="both")
logging.debug("Found overlays %s", overlays)
overlays = [Path(x) for x in overlays]
mappings = {x: True for x in locate_all_hot_env_packages(overlays)}
logging.debug("Found hot packages %r from env search", sorted(mappings))
for package_dir, package, ebuilds in locate_all_package_ebuilds(overlays):
packages_found += 1
ebuilds_found += len(ebuilds)
logging.debug(
"Found package %r in %r with ebuilds %r",
package,
package_dir,
ebuilds,
)
is_marked_hot = any(contains_ebuild_hot_marker(x) for x in ebuilds)
if is_marked_hot:
logging.debug("Package is marked as hot")
else:
logging.debug("Package is not marked as hot")
if package in mappings:
logging.warning(
"Multiple entries found for package %r; merging", package
)
merged_packages += 1
mappings[package] = is_marked_hot or mappings[package]
else:
mappings[package] = is_marked_hot
hot_packages = sorted(
package for package, is_hot in mappings.items() if is_hot
)
logging.info("%d ebuilds found", ebuilds_found)
logging.info("%d packages found", packages_found)
logging.info("%d packages merged", merged_packages)
logging.info("%d hot packages found, total", len(hot_packages))
opts.output.write_text(json.dumps(hot_packages), encoding="utf-8")