Implement cleaner error handling

This commit is contained in:
Ken Van Hoeylandt 2026-02-08 22:57:10 +01:00
parent 2e7e5d36c0
commit 27cbec1151
7 changed files with 56 additions and 46 deletions

View File

@ -24,5 +24,6 @@ if __name__ == "__main__":
is_verbose = "--verbose" in sys.argv is_verbose = "--verbose" in sys.argv
devicetree_yaml_config = args[0] devicetree_yaml_config = args[0]
output_path = args[1] output_path = args[1]
main(devicetree_yaml_config, output_path, is_verbose) result = main(devicetree_yaml_config, output_path, is_verbose)
sys.exit(result)

View File

@ -1,4 +1,5 @@
import os import os
from .exception import DevicetreeException
def find_bindings(directory_path: str) -> list[str]: def find_bindings(directory_path: str) -> list[str]:
yaml_files = [] yaml_files = []
@ -14,6 +15,6 @@ def find_all_bindings(directory_paths: list[str]) -> list[str]:
for directory_path in directory_paths: for directory_path in directory_paths:
new_paths = find_bindings(directory_path) new_paths = find_bindings(directory_path)
if len(new_paths) == 0: if len(new_paths) == 0:
raise Exception(f"No bindings found in {directory_path}") raise DevicetreeException(f"No bindings found in {directory_path}")
yaml_files += new_paths yaml_files += new_paths
return yaml_files return yaml_files

View File

@ -0,0 +1,3 @@
class DevicetreeException(Exception):
def __init__(self, message):
super().__init__(message)

View File

@ -1,7 +1,7 @@
import os.path import os.path
from textwrap import dedent from textwrap import dedent
from source.models import * from source.models import *
from .exception import DevicetreeException
def write_include(file, include: IncludeC, verbose: bool): def write_include(file, include: IncludeC, verbose: bool):
if verbose: if verbose:
@ -26,9 +26,9 @@ def get_device_node_name_safe(device: Device):
def get_device_type_name(device: Device, bindings: list[Binding]): def get_device_type_name(device: Device, bindings: list[Binding]):
device_binding = find_device_binding(device, bindings) device_binding = find_device_binding(device, bindings)
if device_binding is None: if device_binding is None:
raise Exception(f"Binding not found for {device.node_name}") raise DevicetreeException(f"Binding not found for {device.node_name}")
if device_binding.compatible is None: if device_binding.compatible is None:
raise Exception(f"Couldn't find compatible binding for {device.node_name}") raise DevicetreeException(f"Couldn't find compatible binding for {device.node_name}")
compatible_safe = device_binding.compatible.split(",")[-1] compatible_safe = device_binding.compatible.split(",")[-1]
return compatible_safe.replace("-", "_") return compatible_safe.replace("-", "_")
@ -41,7 +41,7 @@ def find_device_property(device: Device, name: str) -> DeviceProperty:
def find_device_binding(device: Device, bindings: list[Binding]) -> Binding: def find_device_binding(device: Device, bindings: list[Binding]) -> Binding:
compatible_property = find_device_property(device, "compatible") compatible_property = find_device_property(device, "compatible")
if compatible_property is None: if compatible_property is None:
raise Exception(f"property 'compatible' not found in device {device.node_name}") raise DevicetreeException(f"property 'compatible' not found in device {device.node_name}")
for binding in bindings: for binding in bindings:
if binding.compatible == compatible_property.value: if binding.compatible == compatible_property.value:
return binding return binding
@ -57,7 +57,7 @@ def find_phandle(devices: list[Device], phandle: str):
for device in devices: for device in devices:
if device.node_name == phandle or device.node_alias == phandle: if device.node_name == phandle or device.node_alias == phandle:
return f"&{get_device_node_name_safe(device)}" return f"&{get_device_node_name_safe(device)}"
raise Exception(f"phandle '{phandle}' not found in device tree") raise DevicetreeException(f"phandle '{phandle}' not found in device tree")
def property_to_string(property: DeviceProperty, devices: list[Device]) -> str: def property_to_string(property: DeviceProperty, devices: list[Device]) -> str:
type = property.type type = property.type
@ -74,15 +74,15 @@ def property_to_string(property: DeviceProperty, devices: list[Device]) -> str:
elif type == "phandle": elif type == "phandle":
return find_phandle(devices, property.value) return find_phandle(devices, property.value)
else: else:
raise Exception(f"property_to_string() has an unsupported type: {type}") raise DevicetreeException(f"property_to_string() has an unsupported type: {type}")
def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], devices: list[Device]) -> list: def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], devices: list[Device]) -> list:
compatible_property = find_device_property(device, "compatible") compatible_property = find_device_property(device, "compatible")
if compatible_property is None: if compatible_property is None:
raise Exception(f"Cannot find 'compatible' property for {device.node_name}") raise DevicetreeException(f"Cannot find 'compatible' property for {device.node_name}")
device_binding = find_binding(compatible_property.value, bindings) device_binding = find_binding(compatible_property.value, bindings)
if device_binding is None: if device_binding is None:
raise Exception(f"Binding not found for {device.node_name} and compatible '{compatible_property.value}'") raise DevicetreeException(f"Binding not found for {device.node_name} and compatible '{compatible_property.value}'")
# Filter out system properties # Filter out system properties
binding_properties = [] binding_properties = []
binding_property_names = set() binding_property_names = set()
@ -96,7 +96,7 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de
if device_property.name in ["compatible"]: if device_property.name in ["compatible"]:
continue continue
if device_property.name not in binding_property_names: if device_property.name not in binding_property_names:
raise Exception(f"Device '{device.node_name}' has invalid property '{device_property.name}'") raise DevicetreeException(f"Device '{device.node_name}' has invalid property '{device_property.name}'")
# Allocate total expected configuration arguments # Allocate total expected configuration arguments
result = [0] * len(binding_properties) result = [0] * len(binding_properties)
@ -106,7 +106,7 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de
if binding_property.type == "bool": if binding_property.type == "bool":
result[index] = "false" result[index] = "false"
elif binding_property.required: elif binding_property.required:
raise Exception(f"device {device.node_name} doesn't have property '{binding_property.name}'") raise DevicetreeException(f"device {device.node_name} doesn't have property '{binding_property.name}'")
elif binding_property.default is not None: elif binding_property.default is not None:
temp_prop = DeviceProperty( temp_prop = DeviceProperty(
name=binding_property.name, name=binding_property.name,
@ -115,7 +115,7 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], de
) )
result[index] = property_to_string(temp_prop, devices) result[index] = property_to_string(temp_prop, devices)
else: else:
raise Exception(f"Device {device.node_name} doesn't have property '{binding_property.name}' and no default value is set") raise DevicetreeException(f"Device {device.node_name} doesn't have property '{binding_property.name}' and no default value is set")
else: else:
result[index] = property_to_string(device_property, devices) result[index] = property_to_string(device_property, devices)
return result return result
@ -142,7 +142,7 @@ def write_device_structs(file, device: Device, parent_device: Device, bindings:
type_name = get_device_type_name(device, bindings) type_name = get_device_type_name(device, bindings)
compatible_property = find_device_property(device, "compatible") compatible_property = find_device_property(device, "compatible")
if compatible_property is None: if compatible_property is None:
raise Exception(f"Cannot find 'compatible' property for {device.node_name}") raise DevicetreeException(f"Cannot find 'compatible' property for {device.node_name}")
node_name = get_device_node_name_safe(device) node_name = get_device_node_name_safe(device)
config_variable_name = f"{node_name}_config" config_variable_name = f"{node_name}_config"
if parent_device is not None: if parent_device is not None:
@ -169,7 +169,7 @@ def write_device_init(file, device: Device, bindings: list[Binding], verbose: bo
# Assemble some pre-requisites # Assemble some pre-requisites
compatible_property = find_device_property(device, "compatible") compatible_property = find_device_property(device, "compatible")
if compatible_property is None: if compatible_property is None:
raise Exception(f"Cannot find 'compatible' property for {device.node_name}") raise DevicetreeException(f"Cannot find 'compatible' property for {device.node_name}")
# Type & instance names # Type & instance names
node_name = get_device_node_name_safe(device) node_name = get_device_node_name_safe(device)
device_variable = node_name device_variable = node_name

View File

@ -9,38 +9,44 @@ from source.generator import *
from source.binding_files import find_all_bindings from source.binding_files import find_all_bindings
from source.binding_parser import parse_binding from source.binding_parser import parse_binding
from source.config import * from source.config import *
from source.exception import DevicetreeException
def main(config_path: str, output_path: str, verbose: bool): def main(config_path: str, output_path: str, verbose: bool) -> int:
print(f"Generating devicetree code\n config: {config_path}\n output: {output_path}") print(f"Generating devicetree code\n config: {config_path}\n output: {output_path}")
if not os.path.isdir(config_path): if not os.path.isdir(config_path):
raise Exception(f"Directory not found: {config_path}") raise DevicetreeException(f"Directory not found: {config_path}")
config = parse_config(config_path, os.getcwd()) config = parse_config(config_path, os.getcwd())
if verbose: if verbose:
pprint(config) pprint(config)
project_dir = os.path.dirname(os.path.realpath(__file__)) try:
grammar_path = os.path.join(project_dir, "grammar.lark") project_dir = os.path.dirname(os.path.realpath(__file__))
lark_data = read_file(grammar_path) grammar_path = os.path.join(project_dir, "grammar.lark")
dts_data = read_file(config.dts) lark_data = read_file(grammar_path)
lark = Lark(lark_data) dts_data = read_file(config.dts)
parsed = lark.parse(dts_data) lark = Lark(lark_data)
if verbose: parsed = lark.parse(dts_data)
print(parsed.pretty()) if verbose:
transformed = DtsTransformer().transform(parsed) print(parsed.pretty())
if verbose: transformed = DtsTransformer().transform(parsed)
pprint(transformed) if verbose:
binding_files = find_all_bindings(config.bindings) pprint(transformed)
if verbose: binding_files = find_all_bindings(config.bindings)
print(f"Bindings found:") if verbose:
print(f"Bindings found:")
for binding_file in binding_files:
print(f" {binding_file}")
if verbose:
print(f"Parsing bindings")
bindings = []
for binding_file in binding_files: for binding_file in binding_files:
print(f" {binding_file}") bindings.append(parse_binding(binding_file, config.bindings))
if verbose: if verbose:
print(f"Parsing bindings") for binding in bindings:
bindings = [] pprint(binding)
for binding_file in binding_files: generate(output_path, transformed, bindings, verbose)
bindings.append(parse_binding(binding_file, config.bindings)) return 0
if verbose: except DevicetreeException as caught:
for binding in bindings: print("\033[31mError: ", caught, "\033[0m")
pprint(binding) return 1
generate(output_path, transformed, bindings, verbose)

View File

@ -4,6 +4,7 @@ from lark import Transformer
from lark import Token from lark import Token
from source.models import * from source.models import *
from dataclasses import dataclass from dataclasses import dataclass
from .exception import DevicetreeException
def flatten_token_array(tokens: List[Token], name: str): def flatten_token_array(tokens: List[Token], name: str):
result_list = list() result_list = list()
@ -23,7 +24,7 @@ class DtsTransformer(Transformer):
def dts_version(self, tokens: List[Token]): def dts_version(self, tokens: List[Token]):
version = tokens[0].value version = tokens[0].value
if version != "dts-v1": if version != "dts-v1":
raise Exception(f"Unsupported DTS version: {version}") raise DevicetreeException(f"Unsupported DTS version: {version}")
return DtsVersion(version) return DtsVersion(version)
def device(self, tokens: list): def device(self, tokens: list):
node_name = None node_name = None
@ -46,12 +47,12 @@ class DtsTransformer(Transformer):
if (len(objects) == 1) or (objects[1] is None): if (len(objects) == 1) or (objects[1] is None):
return DeviceProperty(name, "boolean", True) return DeviceProperty(name, "boolean", True)
if type(objects[1]) is not PropertyValue: if type(objects[1]) is not PropertyValue:
raise Exception(f"Object was not converted to PropertyValue: {objects[1]}") raise DevicetreeException(f"Object was not converted to PropertyValue: {objects[1]}")
return DeviceProperty(name, objects[1].type, objects[1].value) return DeviceProperty(name, objects[1].type, objects[1].value)
def property_value(self, tokens: List): def property_value(self, tokens: List):
token = tokens[0] token = tokens[0]
if type(token) is Token: if type(token) is Token:
raise Exception(f"Failed to convert token to PropertyValue: {token}") raise DevicetreeException(f"Failed to convert token to PropertyValue: {token}")
return token return token
def PHANDLE(self, token: Token): def PHANDLE(self, token: Token):
return PropertyValue(type="phandle", value=token.value[1:]) return PropertyValue(type="phandle", value=token.value[1:])

View File

@ -12,8 +12,6 @@
## Higher Priority ## Higher Priority
- Make a root device type so it can be discovered more easily. - Make a root device type so it can be discovered more easily.
- DTS/yaml: Consider support for default values.
- DTS: throw custom exceptions and catch them to show cleaner error messages.
- When device.py selects a new device, it should automatically delete the build dirs (build/, cmake-*/) when it detects that the platform has changed. - When device.py selects a new device, it should automatically delete the build dirs (build/, cmake-*/) when it detects that the platform has changed.
- Add font design tokens such as "regular", "title" and "smaller". Perhaps via the LVGL kernel module. - Add font design tokens such as "regular", "title" and "smaller". Perhaps via the LVGL kernel module.
- Add kernel listening mechanism so that the root device init can be notified when a device becomes available: - Add kernel listening mechanism so that the root device init can be notified when a device becomes available: