blob: e8fbff34b2f69d20baea4d6e4d4e2d62d3e7baa8 [file] [log] [blame] [edit]
# Copyright 2024 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Regenerate the build metadata cache.
This script runs egencache on all overlays, regenerating the metadata/md5-cache
directory.
Updated overlays are printed as relative paths to the source root on stdout,
one per line.
"""
import contextlib
import os
from pathlib import Path
import sys
from typing import List, Optional, TextIO
from chromite.lib import commandline
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import git
from chromite.lib import osutils
from chromite.lib import parallel
from chromite.lib import portage_util
from chromite.utils import key_value_store
def run_egencache(
repo_name: str,
repos_conf: Path,
) -> cros_build_lib.CompletedProcess:
"""Execute egencache for repo_name inside the chroot.
Args:
repo_name: Name of the repo for the overlay.
repos_conf: repos.conf file.
Returns:
A cros_build_lib.CompletedProcess object.
"""
return cros_build_lib.run(
[
"egencache",
"--repos-conf",
repos_conf,
"--update",
"--repo",
repo_name,
"--jobs",
str(os.cpu_count()),
]
)
def generate_repos_conf(output: TextIO) -> None:
"""Make a repos.conf file with all overlays for egencache.
Generate the repositories configuration containing every overlay in the same
format as repos.conf so egencache can produce an md5-cache for every overlay
The repositories configuration can be accepted as a string in egencache.
Args:
output: A file-like object opened for writing. The config will be
written here.
"""
overlays = portage_util.FindOverlays(constants.BOTH_OVERLAYS)
for overlay_path in overlays:
overlay_name = portage_util.GetOverlayName(overlay_path)
output.write(f"[{overlay_name}]\nlocation = {overlay_path}\n")
def regen_overlay_cache(
overlay: Path,
repos_conf: Path,
) -> Optional[Path]:
"""Regenerate the cache of the specified overlay.
Args:
overlay: The tree to regenerate the cache for.
repos_conf: repos.conf file.
Returns:
The overlay when there are were changes made changes, or None when there
were no updates. This is meant to be a simple, parallelism-friendly
means of identifying which overlays have been changed.
"""
repo_name = portage_util.GetOverlayName(overlay)
if not repo_name:
return None
layout = key_value_store.LoadFile(
overlay / "metadata" / "layout.conf",
ignore_missing=True,
)
if layout.get("cache-format") != "md5-dict":
return None
# Regen for the whole repo.
run_egencache(repo_name, repos_conf=repos_conf)
# If there was nothing new generated, then let's just bail.
result = git.RunGit(overlay, ["status", "-s", "metadata/"])
if not result.stdout:
return None
return overlay
def get_parser() -> commandline.ArgumentParser:
"""Build the argument parser."""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument(
"--overlay-type",
choices=[
constants.PUBLIC_OVERLAYS,
constants.PRIVATE_OVERLAYS,
constants.BOTH_OVERLAYS,
],
default=constants.BOTH_OVERLAYS,
help="Overlay type to update.",
)
return parser
def parse_arguments(argv: Optional[List[str]]) -> commandline.ArgumentNamespace:
"""Parse and validate arguments."""
parser = get_parser()
opts = parser.parse_args(argv)
opts.Freeze()
return opts
def main(argv: Optional[List[str]] = None) -> Optional[int]:
"""Main."""
commandline.RunInsideChroot()
opts = parse_arguments(argv)
with osutils.TempDir() as tempdir:
repos_conf = Path(tempdir) / "repos.conf"
with repos_conf.open("w", encoding="utf-8") as f:
generate_repos_conf(f)
task_inputs = [
(Path(x), repos_conf)
for x in portage_util.FindOverlays(opts.overlay_type)
]
# chromite.lib.parallel is hardwired to mix stderr into stdout. Send it
# back to the right place.
with contextlib.redirect_stdout(sys.stderr):
results = parallel.RunTasksInProcessPool(
regen_overlay_cache, task_inputs
)
for result in results:
if result:
print(result.relative_to(constants.SOURCE_ROOT))