scripts: ci: check_compliance: use git-top to check modules

BoardYml, DeviceTreeBindings, Kconfig, and KconfigBasic checks
can be run with a decentralized west manifest.

Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
This commit is contained in:
Chris Friedt 2025-01-15 12:26:12 -08:00 committed by Benjamin Cabé
commit b032304bbe

View file

@ -80,6 +80,42 @@ def get_files(filter=None, paths=None):
files.remove(file) files.remove(file)
return files return files
def get_module_setting_root(root, settings_file):
"""
Parse the Zephyr module generated settings file given by 'settings_file'
and return all root settings defined by 'root'.
"""
# Invoke the script directly using the Python executable since this is
# not a module nor a pip-installed Python utility
root_paths = []
if os.path.exists(settings_file):
with open(settings_file, 'r') as fp_setting_file:
content = fp_setting_file.read()
lines = content.strip().split('\n')
for line in lines:
root = root.upper()
if line.startswith(f'"{root}_ROOT":'):
_, root_path = line.split(":", 1)
root_paths.append(Path(root_path.strip('"')))
return root_paths
def get_vendor_prefixes(path, errfn = print) -> set[str]:
vendor_prefixes = set()
with open(path) as fp:
for line in fp.readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
try:
vendor, _ = line.split("\t", 2)
vendor_prefixes.add(vendor)
except ValueError:
errfn(f"Invalid line in {path}:\"{line}\".")
errfn("Did you forget the tab character?")
return vendor_prefixes
class FmtdFailure(Failure): class FmtdFailure(Failure):
def __init__( def __init__(
self, severity, title, file, line=None, col=None, desc="", end_line=None, end_col=None self, severity, title, file, line=None, col=None, desc="", end_line=None, end_col=None
@ -120,19 +156,21 @@ class ComplianceTest:
Link to documentation related to what's being tested Link to documentation related to what's being tested
path_hint: path_hint:
The path the test runs itself in. This is just informative and used in The path the test runs itself in. By default it uses the magic string
the message that gets printed when running the test. "<git-top>" which refers to the top-level repository directory.
There are two magic strings that can be used instead of a path: This avoids running 'git' to find the top-level directory before main()
- The magic string "<zephyr-base>" can be used to refer to the runs (class variable assignments run when the 'class ...' statement
environment variable ZEPHYR_BASE or, when missing, the calculated base of runs). That avoids swallowing errors, because main() reports them to
the zephyr tree GitHub.
- The magic string "<git-top>" refers to the top-level repository
directory. This avoids running 'git' to find the top-level directory Subclasses may override the default with a specific path or one of the
before main() runs (class variable assignments run when the 'class ...' magic strings below:
statement runs). That avoids swallowing errors, because main() reports - "<zephyr-base>" can be used to refer to the environment variable
them to GitHub ZEPHYR_BASE or, when missing, the calculated base of the zephyr tree.
""" """
path_hint = "<git-top>"
def __init__(self): def __init__(self):
self.case = TestCase(type(self).name, "Guidelines") self.case = TestCase(type(self).name, "Guidelines")
# This is necessary because Failure can be subclassed, but since it is # This is necessary because Failure can be subclassed, but since it is
@ -204,7 +242,6 @@ class CheckPatch(ComplianceTest):
""" """
name = "Checkpatch" name = "Checkpatch"
doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#coding-style for more details." doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#coding-style for more details."
path_hint = "<git-top>"
def run(self): def run(self):
checkpatch = ZEPHYR_BASE / 'scripts' / 'checkpatch.pl' checkpatch = ZEPHYR_BASE / 'scripts' / 'checkpatch.pl'
@ -263,7 +300,6 @@ class BoardYmlCheck(ComplianceTest):
""" """
name = "BoardYml" name = "BoardYml"
doc = "Check the board.yml file format" doc = "Check the board.yml file format"
path_hint = "<zephyr-base>"
def check_board_file(self, file, vendor_prefixes): def check_board_file(self, file, vendor_prefixes):
"""Validate a single board file.""" """Validate a single board file."""
@ -278,20 +314,19 @@ class BoardYmlCheck(ComplianceTest):
desc=desc) desc=desc)
def run(self): def run(self):
vendor_prefixes = ["others"] path = resolve_path_hint(self.path_hint)
with open(os.path.join(ZEPHYR_BASE, "dts", "bindings", "vendor-prefixes.txt")) as fp:
for line in fp.readlines(): vendor_prefixes = {"others"}
line = line.strip() # add vendor prefixes from the main zephyr repo
if not line or line.startswith("#"): vendor_prefixes |= get_vendor_prefixes(ZEPHYR_BASE / "dts" / "bindings" / "vendor-prefixes.txt", self.error)
continue
try: # add vendor prefixes from the current repo
vendor, _ = line.split("\t", 2) dts_roots = get_module_setting_root('dts', path / "zephyr" / "module.yml")
vendor_prefixes.append(vendor) for dts_root in dts_roots:
except ValueError: vendor_prefix_file = dts_root / "dts" / "bindings" / "vendor-prefixes.txt"
self.error(f"Invalid line in vendor-prefixes.txt:\"{line}\".") if vendor_prefix_file.exists():
self.error("Did you forget the tab character?") vendor_prefixes |= get_vendor_prefixes(vendor_prefix_file, self.error)
path = Path(ZEPHYR_BASE)
for file in path.glob("**/board.yml"): for file in path.glob("**/board.yml"):
self.check_board_file(file, vendor_prefixes) self.check_board_file(file, vendor_prefixes)
@ -302,7 +337,6 @@ class ClangFormatCheck(ComplianceTest):
""" """
name = "ClangFormat" name = "ClangFormat"
doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#clang-format for more details." doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#clang-format for more details."
path_hint = "<git-top>"
def run(self): def run(self):
exe = f"clang-format-diff.{'exe' if platform.system() == 'Windows' else 'py'}" exe = f"clang-format-diff.{'exe' if platform.system() == 'Windows' else 'py'}"
@ -344,7 +378,6 @@ class DevicetreeBindingsCheck(ComplianceTest):
""" """
name = "DevicetreeBindings" name = "DevicetreeBindings"
doc = "See https://docs.zephyrproject.org/latest/build/dts/bindings.html for more details." doc = "See https://docs.zephyrproject.org/latest/build/dts/bindings.html for more details."
path_hint = "<zephyr-base>"
def run(self, full=True): def run(self, full=True):
dts_bindings = self.parse_dt_bindings() dts_bindings = self.parse_dt_bindings()
@ -381,7 +414,6 @@ class KconfigCheck(ComplianceTest):
""" """
name = "Kconfig" name = "Kconfig"
doc = "See https://docs.zephyrproject.org/latest/build/kconfig/tips.html for more details." doc = "See https://docs.zephyrproject.org/latest/build/kconfig/tips.html for more details."
path_hint = "<zephyr-base>"
# Top-level Kconfig file. The path can be relative to srctree (ZEPHYR_BASE). # Top-level Kconfig file. The path can be relative to srctree (ZEPHYR_BASE).
FILENAME = "Kconfig" FILENAME = "Kconfig"
@ -437,27 +469,6 @@ class KconfigCheck(ComplianceTest):
)) ))
fp_module_file.write(content) fp_module_file.write(content)
def get_module_setting_root(self, root, settings_file):
"""
Parse the Zephyr module generated settings file given by 'settings_file'
and return all root settings defined by 'root'.
"""
# Invoke the script directly using the Python executable since this is
# not a module nor a pip-installed Python utility
root_paths = []
if os.path.exists(settings_file):
with open(settings_file, 'r') as fp_setting_file:
content = fp_setting_file.read()
lines = content.strip().split('\n')
for line in lines:
root = root.upper()
if line.startswith(f'"{root}_ROOT":'):
_, root_path = line.split(":", 1)
root_paths.append(Path(root_path.strip('"')))
return root_paths
def get_kconfig_dts(self, kconfig_dts_file, settings_file): def get_kconfig_dts(self, kconfig_dts_file, settings_file):
""" """
Generate the Kconfig.dts using dts/bindings as the source. Generate the Kconfig.dts using dts/bindings as the source.
@ -1146,6 +1157,7 @@ class KconfigBasicNoModulesCheck(KconfigBasicCheck):
defined only in a module. defined only in a module.
""" """
name = "KconfigBasicNoModules" name = "KconfigBasicNoModules"
path_hint = "<zephyr-base>"
def get_modules(self, modules_file, sysbuild_modules_file, settings_file): def get_modules(self, modules_file, sysbuild_modules_file, settings_file):
with open(modules_file, 'w') as fp_module_file: with open(modules_file, 'w') as fp_module_file:
@ -1212,6 +1224,7 @@ class SysbuildKconfigBasicNoModulesCheck(SysbuildKconfigCheck, KconfigBasicNoMod
but defined only in a module. but defined only in a module.
""" """
name = "SysbuildKconfigBasicNoModules" name = "SysbuildKconfigBasicNoModules"
path_hint = "<zephyr-base>"
class Nits(ComplianceTest): class Nits(ComplianceTest):
@ -1221,7 +1234,6 @@ class Nits(ComplianceTest):
""" """
name = "Nits" name = "Nits"
doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#coding-style for more details." doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#coding-style for more details."
path_hint = "<git-top>"
def run(self): def run(self):
# Loop through added/modified files # Loop through added/modified files
@ -1315,7 +1327,6 @@ class GitDiffCheck(ComplianceTest):
""" """
name = "GitDiffCheck" name = "GitDiffCheck"
doc = "Git conflict markers and whitespace errors are not allowed in added changes" doc = "Git conflict markers and whitespace errors are not allowed in added changes"
path_hint = "<git-top>"
def run(self): def run(self):
offending_lines = [] offending_lines = []
@ -1343,7 +1354,6 @@ class GitLint(ComplianceTest):
""" """
name = "Gitlint" name = "Gitlint"
doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#commit-guidelines for more details" doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#commit-guidelines for more details"
path_hint = "<git-top>"
def run(self): def run(self):
# By default gitlint looks for .gitlint configuration only in # By default gitlint looks for .gitlint configuration only in
@ -1366,7 +1376,6 @@ class PyLint(ComplianceTest):
""" """
name = "Pylint" name = "Pylint"
doc = "See https://www.pylint.org/ for more details" doc = "See https://www.pylint.org/ for more details"
path_hint = "<git-top>"
def run(self): def run(self):
# Path to pylint configuration file # Path to pylint configuration file
@ -1441,9 +1450,6 @@ class Identity(ComplianceTest):
""" """
name = "Identity" name = "Identity"
doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#commit-guidelines for more details" doc = "See https://docs.zephyrproject.org/latest/contribute/guidelines.html#commit-guidelines for more details"
# git rev-list and git log don't depend on the current (sub)directory
# unless explicited
path_hint = "<git-top>"
def run(self): def run(self):
for shaidx in get_shas(COMMIT_RANGE): for shaidx in get_shas(COMMIT_RANGE):
@ -1484,7 +1490,6 @@ class BinaryFiles(ComplianceTest):
""" """
name = "BinaryFiles" name = "BinaryFiles"
doc = "No binary files allowed." doc = "No binary files allowed."
path_hint = "<git-top>"
def run(self): def run(self):
BINARY_ALLOW_PATHS = ("doc/", "boards/", "samples/") BINARY_ALLOW_PATHS = ("doc/", "boards/", "samples/")
@ -1507,7 +1512,6 @@ class ImageSize(ComplianceTest):
""" """
name = "ImageSize" name = "ImageSize"
doc = "Check the size of image files." doc = "Check the size of image files."
path_hint = "<git-top>"
def run(self): def run(self):
SIZE_LIMIT = 250 << 10 SIZE_LIMIT = 250 << 10
@ -1537,7 +1541,6 @@ class MaintainersFormat(ComplianceTest):
""" """
name = "MaintainersFormat" name = "MaintainersFormat"
doc = "Check that MAINTAINERS file parses correctly." doc = "Check that MAINTAINERS file parses correctly."
path_hint = "<git-top>"
def run(self): def run(self):
MAINTAINERS_FILES = ["MAINTAINERS.yml", "MAINTAINERS.yaml"] MAINTAINERS_FILES = ["MAINTAINERS.yml", "MAINTAINERS.yaml"]
@ -1557,7 +1560,6 @@ class ModulesMaintainers(ComplianceTest):
""" """
name = "ModulesMaintainers" name = "ModulesMaintainers"
doc = "Check that all modules have a MAINTAINERS entry." doc = "Check that all modules have a MAINTAINERS entry."
path_hint = "<git-top>"
def run(self): def run(self):
MAINTAINERS_FILES = ["MAINTAINERS.yml", "MAINTAINERS.yaml"] MAINTAINERS_FILES = ["MAINTAINERS.yml", "MAINTAINERS.yaml"]
@ -1592,7 +1594,6 @@ class YAMLLint(ComplianceTest):
""" """
name = "YAMLLint" name = "YAMLLint"
doc = "Check YAML files with YAMLLint." doc = "Check YAML files with YAMLLint."
path_hint = "<git-top>"
def run(self): def run(self):
config_file = ZEPHYR_BASE / ".yamllint" config_file = ZEPHYR_BASE / ".yamllint"
@ -1623,7 +1624,6 @@ class SphinxLint(ComplianceTest):
name = "SphinxLint" name = "SphinxLint"
doc = "Check Sphinx/reStructuredText files with sphinx-lint." doc = "Check Sphinx/reStructuredText files with sphinx-lint."
path_hint = "<git-top>"
# Checkers added/removed to sphinx-lint's default set # Checkers added/removed to sphinx-lint's default set
DISABLE_CHECKERS = ["horizontal-tab", "missing-space-before-default-role"] DISABLE_CHECKERS = ["horizontal-tab", "missing-space-before-default-role"]
@ -1665,7 +1665,6 @@ class KeepSorted(ComplianceTest):
""" """
name = "KeepSorted" name = "KeepSorted"
doc = "Check for blocks of code or config that should be kept sorted." doc = "Check for blocks of code or config that should be kept sorted."
path_hint = "<git-top>"
MARKER = "zephyr-keep-sorted" MARKER = "zephyr-keep-sorted"
@ -1761,7 +1760,6 @@ class Ruff(ComplianceTest):
""" """
name = "Ruff" name = "Ruff"
doc = "Check python files with ruff." doc = "Check python files with ruff."
path_hint = "<git-top>"
def run(self): def run(self):
for file in get_files(filter="d"): for file in get_files(filter="d"):
@ -1809,7 +1807,6 @@ class TextEncoding(ComplianceTest):
""" """
name = "TextEncoding" name = "TextEncoding"
doc = "Check the encoding of text files." doc = "Check the encoding of text files."
path_hint = "<git-top>"
ALLOWED_CHARSETS = ["us-ascii", "utf-8"] ALLOWED_CHARSETS = ["us-ascii", "utf-8"]