Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 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,8 @@
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, making it
easier to review the supported bus topologies.
150 changes: 150 additions & 0 deletions scripts/dts/list_bus_nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/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, bindings_from_paths # 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(
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 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]]:

Check failure on line 84 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 (E501) see https://docs.astral.sh/ruff/rules/line-too-long

scripts/dts/list_bus_nodes.py:84 Line too long (101 > 100)

Check failure on line 84 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:84 Use `tuple` instead of `Tuple` for type annotation

Check failure on line 84 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:84 Use `list` instead of `List` for type annotation
rows: List[Tuple[str, str, str]] = []

Check failure on line 85 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:85 Use `tuple` instead of `Tuple` for type annotation

Check failure on line 85 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:85 Use `list` instead of `List` for type annotation
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 ""
rows.append((label, 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]]) -> List[Tuple[str, str, str]]:
return sorted(rows, key=lambda row: row[0])


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


def emit_table(rows: Sequence[Tuple[str, str, str]]) -> None:
headers = ("Binding", "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 = bindings_from_paths(binding_files)

Comment on lines 159 to 162

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip bindings without compatibles before loading

The script gathers every YAML file under the bindings root and feeds them to bindings_from_paths(). Many files in dts/bindings are include fragments such as base.yaml that intentionally omit a compatible key; Binding() raises EDTError for those by default. With the current call the tool aborts before producing any output as soon as it encounters such a file, so the default invocation (list_bus_nodes.py with no arguments) cannot succeed even when PyYAML is installed. Consider filtering the list to only bindings with compatible or pass ignore_errors=True so include-only YAML does not kill the script.

Useful? React with 👍 / 👎.

rows: List[Tuple[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