blob: ba3b6763108cc0fa36ed89bcf6ec0167580ee30b [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Require every overlay have good metadata in the root directory.
If no files are specified, all overlays will be checked.
"""
import dataclasses
import logging
from pathlib import Path
import site
import sys
from typing import List, Optional
DIR = Path(__file__).resolve().parent
# pylint: disable=wrong-import-position
site.addsitedir(DIR.parent.parent)
from chromite.lib import commandline
assert sys.version_info >= (3, 8), "This module requires Python 3.8+"
# The files we check.
METADATA = ("DIR_METADATA", "OWNERS", "README.md")
# Overlays that haven't added metadata yet. Do not add any more entries.
MISSING_OVERLAYS_DIR_METADATA = {
"baseboard-asurada",
"baseboard-brya",
"baseboard-cherry",
"baseboard-coral",
"baseboard-corsola",
"baseboard-dedede",
"baseboard-drallion",
"baseboard-fizz",
"baseboard-grunt",
"baseboard-hatch",
"baseboard-kalista",
"baseboard-krabbylake",
"baseboard-kukui",
"baseboard-nami",
"baseboard-oak",
"baseboard-octopus",
"baseboard-poppy",
"baseboard-puff",
"baseboard-rammus",
"baseboard-reef",
"baseboard-rex",
"baseboard-sarien",
"baseboard-trogdor",
"baseboard-volteer",
"chipset-adl",
"chipset-adln",
"chipset-apl",
"chipset-cezanne",
"chipset-cml",
"chipset-glk",
"chipset-jsl",
"chipset-kbl",
"chipset-mendocino",
"chipset-mt8173",
"chipset-mt8183",
"chipset-mt8186",
"chipset-mt8192",
"chipset-mt8195",
"chipset-mtl",
"chipset-picasso",
"chipset-qc7180",
"chipset-stnyridge",
"chipset-tgl",
"chipset-whl",
"overlay-amd64-generic",
"overlay-amd64-generic-embedded",
"overlay-amd64-host",
"overlay-amd64-starnix",
"overlay-arm64-generic",
"overlay-arm-generic",
"overlay-asurada",
"overlay-asurada64",
"overlay-aurora",
"overlay-brask",
"overlay-cherry",
"overlay-cherry64",
"overlay-corsola",
"overlay-fizz",
"overlay-fizz-labstation",
"overlay-jacuzzi64",
"overlay-kalista",
"overlay-nami",
"overlay-nautilus",
"overlay-octopus",
"overlay-pyro",
"overlay-rammus",
"overlay-rex",
"overlay-sand",
"overlay-simple-fake-board",
"overlay-skyrim",
"overlay-snappy",
"overlay-soraka",
"overlay-soraka-libcamera",
"overlay-strongbad",
"overlay-strongbad-kernelnext",
"overlay-tael",
"overlay-tatl",
"overlay-trogdor-kernelnext",
"project-labstation",
"project-termina",
}
MISSING_OVERLAYS_README_MD = {
"baseboard-asurada",
"baseboard-brya",
"baseboard-coral",
"baseboard-dedede",
"baseboard-drallion",
"baseboard-fizz",
"baseboard-grunt",
"baseboard-hatch",
"baseboard-kalista",
"baseboard-krabbylake",
"baseboard-kukui",
"baseboard-nami",
"baseboard-oak",
"baseboard-octopus",
"baseboard-poppy",
"baseboard-puff",
"baseboard-rammus",
"baseboard-reef",
"baseboard-rex",
"baseboard-sarien",
"baseboard-trogdor",
"baseboard-volteer",
"chipset-adl",
"chipset-adln",
"chipset-apl",
"chipset-cezanne",
"chipset-cml",
"chipset-glk",
"chipset-jsl",
"chipset-kbl",
"chipset-mendocino",
"chipset-mt8173",
"chipset-mt8183",
"chipset-mt8192",
"chipset-mtl",
"chipset-picasso",
"chipset-qc7180",
"chipset-stnyridge",
"chipset-tgl",
"chipset-whl",
"overlay-amd64-generic",
"overlay-amd64-generic-embedded",
"overlay-amd64-host",
"overlay-arm64-generic",
"overlay-arm-generic",
"overlay-asurada",
"overlay-asurada64",
"overlay-atlas",
"overlay-aurora",
"overlay-brask",
"overlay-brask-labstation",
"overlay-brya",
"overlay-cherry64",
"overlay-coral",
"overlay-dedede",
"overlay-drallion",
"overlay-elm",
"overlay-eve",
"overlay-fizz",
"overlay-fizz-labstation",
"overlay-fizz-moblab",
"overlay-grunt",
"overlay-guybrush",
"overlay-hana",
"overlay-hatch",
"overlay-jacuzzi",
"overlay-jacuzzi64",
"overlay-kalista",
"overlay-kukui",
"overlay-nami",
"overlay-nautilus",
"overlay-nissa",
"overlay-nocturne",
"overlay-octopus",
"overlay-puff",
"overlay-pyro",
"overlay-rammus",
"overlay-reef",
"overlay-rex",
"overlay-sand",
"overlay-sarien",
"overlay-skyrim",
"overlay-snappy",
"overlay-soraka",
"overlay-soraka-libcamera",
"overlay-strongbad",
"overlay-strongbad-kernelnext",
"overlay-trogdor",
"overlay-trogdor-kernelnext",
"overlay-volteer",
"overlay-zork",
"project-labstation",
"project-mobbase",
"project-moblab",
"project-wilco",
}
MISSING_OVERLAYS_OWNERS = {
"baseboard-coral",
"baseboard-fizz",
"baseboard-hatch",
"baseboard-kalista",
"baseboard-krabbylake",
"baseboard-nami",
"baseboard-poppy",
"baseboard-rammus",
"baseboard-reef",
"baseboard-sarien",
"baseboard-volteer",
"chipset-apl",
"chipset-glk",
"chipset-kbl",
"chipset-picasso",
"chipset-stnyridge",
"chipset-tgl",
"chipset-whl",
"overlay-amd64-generic",
"overlay-amd64-generic-embedded",
"overlay-arm-generic",
"overlay-atlas",
"overlay-brask",
"overlay-cherry64",
"overlay-coral",
"overlay-eve",
"overlay-hatch",
"overlay-kalista",
"overlay-nami",
"overlay-nautilus",
"overlay-nocturne",
"overlay-pyro",
"overlay-rammus",
"overlay-reef",
"overlay-sand",
"overlay-sarien",
"overlay-snappy",
"overlay-soraka",
"overlay-soraka-libcamera",
"overlay-volteer",
"project-wilco",
}
MISSING_OVERLAYS = {
"DIR_METADATA": MISSING_OVERLAYS_DIR_METADATA,
"OWNERS": MISSING_OVERLAYS_OWNERS,
"README.md": MISSING_OVERLAYS_README_MD,
}
@dataclasses.dataclass
class CheckResults:
"""Results from a checker."""
# Whether the check passed.
passed: bool
# In case of failures, what went wrong.
message: Optional[str] = None
DIR_METADATA_DOCS = """
Please see: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/dir_metadata.md
"""
def check_content_dir_metadata(data: str) -> CheckResults:
"""Make sure DIR_METADATA content is reasonable."""
# This is very rough, but the dirmd tool requires files on disk.
# https://crbug.com/1343601
has_b = "buganizer {" in data
has_b_pub = "buganizer_public {" in data
has_mono = "monorail {" in data
if has_b and "component_id:" not in data:
return CheckResults(
False, "missing buganizer component_id" + DIR_METADATA_DOCS
)
if has_b_pub and "component_id:" not in data:
return CheckResults(
False, "missing public buganizer component_id" + DIR_METADATA_DOCS
)
if has_mono and not ("component:" in data or "project:" in data):
return CheckResults(
False,
"missing monorail component and/or project" + DIR_METADATA_DOCS,
)
return CheckResults(
any((has_b, has_b_pub, has_mono)),
"missing bug tracker information" + DIR_METADATA_DOCS,
)
def check_content_owners(data: str) -> CheckResults:
"""Make sure owners content is reasonable."""
contents = []
for line in data.splitlines():
# Strip off comments.
line = line.split("#", 1)[0].split()
if not line:
continue
contents.append(line)
if not contents:
return CheckResults(False, "empty file")
return CheckResults(True)
def check_content_readme_md(data: str):
"""Make sure README.md content is reasonable."""
if not data.strip():
return CheckResults(False, "empty file")
return CheckResults(True)
# Map from metadata name to the check function.
CHECKERS = {
"DIR_METADATA": check_content_dir_metadata,
"OWNERS": check_content_owners,
"README.md": check_content_readme_md,
}
def check_content(name: str, data: str) -> CheckResults:
"""Make sure metadata content is reasonable."""
if name in CHECKERS:
return CHECKERS[name](data)
return CheckResults(True)
def check_metadata(path: Path, name: str) -> bool:
"""Check metadata in the overlay."""
ret = True
missing_overlays = MISSING_OVERLAYS[name]
# If the file exists, we're all set!
metadata_path = path / name
if metadata_path.is_file():
# Make sure it has actual content.
results = check_content(name, metadata_path.read_text())
if not results.passed:
logging.error(
"%s: %s: %s",
path,
name,
results.message,
)
ret = False
elif path.name in missing_overlays:
logging.error(
"%s: remove from MISSING_OVERLAYS_%s",
path.name,
name.upper(),
)
ret = False
elif ret:
ret = path.name in missing_overlays
return ret
def check_overlay(path: Path) -> bool:
"""Check metadata in overlay |path|."""
ret = True
for metadata_name in METADATA:
logging.debug("%s: checking %s", path, metadata_name)
if not check_metadata(path, metadata_name):
logging.error(
"%s: missing %s",
path.name,
metadata_name,
)
ret = False
return ret
def main(argv: List[str]) -> int:
"""Main function."""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument("overlays", nargs="*", type=Path)
opts = parser.parse_args(argv)
if not opts.overlays:
opts.overlays = DIR.glob("*-*/")
ret = 0
# Check for overlays that have been removed.
for name in METADATA:
missing_overlays = MISSING_OVERLAYS[name]
for overlay in missing_overlays:
if not (DIR / overlay).exists():
logging.error(
"%s: remove from MISSING_OVERLAYS_%s", overlay, name.upper()
)
ret = 1
for path in opts.overlays:
if path.is_dir():
if not check_overlay(path):
ret = 1
return ret
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))