Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions scripts/dts/README.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This directory used to contain the edtlib.py and dtlib.py libraries

Check warning on line 1 in scripts/dts/README.txt

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Copyright missing

scripts/dts/README.txt:1 File has no SPDX-FileCopyrightText header, consider adding one.

Check warning on line 1 in scripts/dts/README.txt

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

License missing

scripts/dts/README.txt:1 File has no SPDX-License-Identifier header, consider adding one.
and tests, alongside the gen_defines.py script that uses them for
converting DTS to the C macros used by Zephyr.

Expand Down Expand Up @@ -30,3 +30,9 @@
Eventually, as APIs stabilize, the python-devicetree code in this
repository will disappear, and a standalone repository will be the
'main' one.

Additional utilities in this directory include helper scripts for
exploring the binding metadata. For example, ``list_bus_nodes.py``
summarizes bindings that define a bus or appear on one, including the
binding's ``compatible`` string, making it easier to review the
supported bus topologies.
176 changes: 176 additions & 0 deletions scripts/dts/list_bus_nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/usr/bin/env python3

Check warning on line 1 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Copyright missing

scripts/dts/list_bus_nodes.py:1 File has no SPDX-FileCopyrightText header, consider adding one.

Check warning on line 1 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

License missing

scripts/dts/list_bus_nodes.py:1 File has no SPDX-License-Identifier header, consider adding one.
"""Utility for listing bindings that define or use buses."""

from __future__ import annotations

import argparse
import csv
import sys
from pathlib import Path
from typing import Iterable, List, Sequence, Tuple

Check failure on line 10 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP035) see https://docs.astral.sh/ruff/rules/deprecated-import

scripts/dts/list_bus_nodes.py:10 `typing.Tuple` is deprecated, use `tuple` instead

Check failure on line 10 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP035) see https://docs.astral.sh/ruff/rules/deprecated-import

scripts/dts/list_bus_nodes.py:10 `typing.List` is deprecated, use `list` instead

Check failure on line 10 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP035) see https://docs.astral.sh/ruff/rules/deprecated-import

scripts/dts/list_bus_nodes.py:10 Import from `collections.abc` instead: `Iterable`, `Sequence`

Check failure on line 10 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (I001) see https://docs.astral.sh/ruff/rules/unsorted-imports

scripts/dts/list_bus_nodes.py:4 Import block is un-sorted or un-formatted


SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent.parent
PYTHON_DEVICETREE = SCRIPT_DIR / "python-devicetree" / "src"

# Ensure the in-tree python-devicetree sources are preferred.
sys.path.insert(0, str(PYTHON_DEVICETREE))

try:
from devicetree.edtlib import Binding, EDTError # type: ignore # noqa: E402
except ModuleNotFoundError as exc: # pragma: no cover - environment guard
if exc.name == "yaml":
raise SystemExit(
"PyYAML is required to load bindings. Install it with 'pip install PyYAML'."
) from exc
raise


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(

Check failure on line 31 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

E9901

scripts/dts/list_bus_nodes.py:31 Argument parser with abbreviations is disallowed (argument-parser-with-abbreviations)

Check failure on line 31 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

E9901

scripts/dts/list_bus_nodes.py:31 Argument parser with abbreviations is disallowed (argument-parser-with-abbreviations)
description=(
"List bindings that declare a bus or appear on a bus, including "
"their child-binding definitions."
)
)
default_root = REPO_ROOT / "dts" / "bindings"
parser.add_argument(
"--bindings-root",
type=Path,
default=default_root,
help=(
"Root directory to search for binding YAML files (default: %(default)s)."
),
)
parser.add_argument(
"--csv",
action="store_true",
help="Emit the table as comma-separated values instead of padded text.",
)
return parser.parse_args()


def find_binding_files(root: Path) -> List[Path]:

Check failure on line 54 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP006) see https://docs.astral.sh/ruff/rules/non-pep585-annotation

scripts/dts/list_bus_nodes.py:54 Use `list` instead of `List` for type annotation
if not root.is_dir():
raise SystemExit(f"Bindings root '{root}' does not exist or is not a directory")

files = {
path.resolve()
for suffix in ("*.yaml", "*.yml")
for path in root.rglob(suffix)
if path.is_file()
}
if not files:
raise SystemExit(f"No binding YAML files found under '{root}'")
return sorted(files)


def load_bindings(binding_files: Sequence[str]) -> List[Binding]:

Check failure on line 69 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP006) see https://docs.astral.sh/ruff/rules/non-pep585-annotation

scripts/dts/list_bus_nodes.py:69 Use `list` instead of `List` for type annotation
"""Return bindings, skipping ones that fail to load."""

fname2path = {Path(path).name: path for path in binding_files}

bindings: List[Binding] = []

Check failure on line 74 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP006) see https://docs.astral.sh/ruff/rules/non-pep585-annotation

scripts/dts/list_bus_nodes.py:74 Use `list` instead of `List` for type annotation
errors: List[Tuple[str, EDTError]] = []

Check failure on line 75 in scripts/dts/list_bus_nodes.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (UP006) see https://docs.astral.sh/ruff/rules/non-pep585-annotation

scripts/dts/list_bus_nodes.py:75 Use `list` instead of `List` for type annotation

for path in binding_files:
try:
bindings.append(Binding(path, fname2path))
except EDTError as err:
errors.append((path, err))

for path, err in errors:
print(
f"warning: failed to load binding {path}: {err}",
file=sys.stderr,
)

return bindings


def relative_label(path: Path, root: Path, depth: int) -> str:
try:
rel = path.resolve().relative_to(root.resolve())
except ValueError:
rel = path.name

if depth == 0:
return str(rel)
if depth == 1:
suffix = "child-binding"
else:
suffix = f"child-binding depth {depth}"
return f"{rel} ({suffix})"


def collect_binding_rows(
binding: Binding, root: Path, depth: int = 0
) -> List[Tuple[str, str, str, str]]:
rows: List[Tuple[str, str, str, str]] = []
binding_path = Path(binding.path) if binding.path else None

if binding_path is not None and (binding.buses or binding.on_bus):
label = relative_label(binding_path, root, depth)
buses = ", ".join(binding.buses) if binding.buses else ""
on_bus = binding.on_bus or ""
compatible = binding.compatible or ""
rows.append((label, compatible, buses, on_bus))

if binding.child_binding is not None:
rows.extend(collect_binding_rows(binding.child_binding, root, depth + 1))

return rows


def sort_rows(rows: Iterable[Tuple[str, str, str, str]]) -> List[Tuple[str, str, str, str]]:
return sorted(rows, key=lambda row: row[0])


def emit_csv(rows: Sequence[Tuple[str, str, str, str]]) -> None:
writer = csv.writer(sys.stdout)
writer.writerow(["Binding", "Compatible", "Buses", "On Bus"])
for row in rows:
writer.writerow(row)


def emit_table(rows: Sequence[Tuple[str, str, str, str]]) -> None:
headers = ("Binding", "Compatible", "Buses", "On Bus")
if not rows:
print("No bus-related bindings found.")
return

widths = [len(header) for header in headers]
for row in rows:
for idx, value in enumerate(row):
widths[idx] = max(widths[idx], len(value))

header_line = " ".join(header.ljust(widths[idx]) for idx, header in enumerate(headers))
separator = " ".join("-" * widths[idx] for idx in range(len(headers)))
print(header_line)
print(separator)
for row in rows:
print(" ".join(value.ljust(widths[idx]) for idx, value in enumerate(row)))


def main() -> None:
args = parse_args()
bindings_root = args.bindings_root.resolve()
binding_files = [str(path) for path in find_binding_files(bindings_root)]

bindings = load_bindings(binding_files)

rows: List[Tuple[str, str, str, str]] = []
for binding in bindings:
rows.extend(collect_binding_rows(binding, bindings_root))

rows = sort_rows(rows)

if args.csv:
emit_csv(rows)
else:
emit_table(rows)


if __name__ == "__main__":
main()
Loading