scripts: dts: Improve syntax and code for including binding files
Have include: foo.dts include: [foo.dts, bar.dts] instead of inherits: !include foo.dts inherits: !include [foo.dts, bar.dts] This is a nicer and shorter and less cryptic syntax, and will make it possible to get rid of the custom PyYAML constructor for '!include' later. 'inherits: !include ...' is still supported for backwards compatibility for now. Later on, I'm planning to mass-replace it, add a deprecation warning if it's used, and document 'include:'. Then the '!include' implementation can be removed a bit later. '!include' has caused issues in the past (see the comment above the add_constructor() call), gets iffy with multiple EDT instances, and makes the code harder to follow. I'm guessing '!include' might've been intended to be useful outside of 'inherits:' originally, but that's the only place where it's used. It's undocumented that it's possible to put it elsewhere. To implement the backwards compatibility, the code just transforms inherits: !include foo.dts into inherits: - foo.dts and treats 'inherits:' similarly to 'include:'. Previously, !include inserted the contents of the included file instead. Some more sanity checks for 'include:'/'inherits:' are included as well. Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
parent
fcd665a26c
commit
d834b69bd9
9 changed files with 103 additions and 61 deletions
|
@ -5,15 +5,14 @@ description: >
|
|||
Can go over multiple lines.
|
||||
|
||||
# Many bindings have common fields. These are stored in files given in
|
||||
# 'inherits', which are merged into the binding (with a recursive dictionary
|
||||
# 'include', which are merged into the binding (with a recursive dictionary
|
||||
# merge).
|
||||
#
|
||||
# If a field appears both in the binding and in a file it inherits, then the
|
||||
# If a field appears both in the binding and in a file it includes, then the
|
||||
# value in the binding takes precedence. This can be used e.g. to change a
|
||||
# 'required: false' from an inherited file to a 'required: true' (see the
|
||||
# 'properties' description below).
|
||||
inherits:
|
||||
!include other.yaml # or [other1.yaml, other2.yaml]
|
||||
include: other.yaml # or [other1.yaml, other2.yaml]
|
||||
|
||||
# If the node describes a bus, then the bus type should be given, like below
|
||||
child:
|
||||
|
|
|
@ -157,13 +157,9 @@ class EDT:
|
|||
# Only bindings for 'compatible' strings that appear in the device tree
|
||||
# are loaded.
|
||||
|
||||
# Add '!include foo.yaml' handling.
|
||||
#
|
||||
# Do yaml.Loader.add_constructor() instead of yaml.add_constructor() to be
|
||||
# Add legacy '!include foo.yaml' handling. Do
|
||||
# yaml.Loader.add_constructor() instead of yaml.add_constructor() to be
|
||||
# compatible with both version 3.13 and version 5.1 of PyYAML.
|
||||
#
|
||||
# TODO: Is there some 3.13/5.1-compatible way to only do this once, even
|
||||
# if multiple EDT objects are created?
|
||||
yaml.Loader.add_constructor("!include", self._binding_include)
|
||||
|
||||
dt_compats = _dt_compats(self._dt)
|
||||
|
@ -203,48 +199,90 @@ class EDT:
|
|||
if compat not in dt_compats:
|
||||
continue
|
||||
|
||||
# It's a match. Merge in the !included bindings, do sanity checks,
|
||||
# It's a match. Merge in the included bindings, do sanity checks,
|
||||
# and register the binding.
|
||||
|
||||
binding = _merge_included_bindings(binding, binding_path)
|
||||
binding = self._merge_included_bindings(binding, binding_path)
|
||||
_check_binding(binding, binding_path)
|
||||
self._compat2binding[compat, _binding_bus(binding)] = \
|
||||
(binding, binding_path)
|
||||
|
||||
def _binding_include(self, loader, node):
|
||||
# Implements !include. Returns a list with the YAML structures for the
|
||||
# included files (a single-element list if the !include is for a single
|
||||
# file).
|
||||
def _merge_included_bindings(self, binding, binding_path):
|
||||
# Merges any bindings listed in the 'include:' section of the binding
|
||||
# into the top level of 'binding'. Also supports the legacy
|
||||
# 'inherits: !include ...' syntax for including bindings.
|
||||
#
|
||||
# Properties in 'binding' take precedence over properties from included
|
||||
# bindings.
|
||||
|
||||
if isinstance(node, yaml.ScalarNode):
|
||||
# !include foo.yaml
|
||||
return [self._binding_include_file(loader.construct_scalar(node))]
|
||||
fnames = []
|
||||
|
||||
if isinstance(node, yaml.SequenceNode):
|
||||
# !include [foo.yaml, bar.yaml]
|
||||
return [self._binding_include_file(filename)
|
||||
for filename in loader.construct_sequence(node)]
|
||||
if "include" in binding:
|
||||
include = binding.pop("include")
|
||||
if isinstance(include, str):
|
||||
fnames.append(include)
|
||||
elif isinstance(include, list):
|
||||
if not all(isinstance(elm, str) for elm in include):
|
||||
_err("all elements in 'include:' in {} should be strings"
|
||||
.format(binding_path))
|
||||
fnames += include
|
||||
else:
|
||||
_err("'include:' in {} should be a string or a list of strings"
|
||||
.format(binding_path))
|
||||
|
||||
_binding_inc_error("unrecognised node type in !include statement")
|
||||
# Legacy syntax
|
||||
if "inherits" in binding:
|
||||
_warn("the 'inherits:' syntax in {} is deprecated and will be "
|
||||
"removed - please use 'include: foo.yaml' or "
|
||||
"'include: [foo.yaml, bar.yaml]' instead"
|
||||
.format(binding_path))
|
||||
|
||||
def _binding_include_file(self, filename):
|
||||
# _binding_include() helper for loading an !include'd file. !include
|
||||
# takes just the basename of the file, so we need to make sure there
|
||||
# aren't multiple candidates.
|
||||
inherits = binding.pop("inherits")
|
||||
if not isinstance(inherits, list) or \
|
||||
not all(isinstance(elm, str) for elm in inherits):
|
||||
_err("malformed 'inherits:' in " + binding_path)
|
||||
fnames += inherits
|
||||
|
||||
for fname in fnames:
|
||||
included = self._file_yaml(fname)
|
||||
_merge_props(
|
||||
binding, self._merge_included_bindings(included, binding_path),
|
||||
None, binding_path)
|
||||
|
||||
return binding
|
||||
|
||||
def _file_yaml(self, filename):
|
||||
# _merge_included_bindings() helper for loading an included file.
|
||||
# 'include:' lists just the basenames of the files, so we check that
|
||||
# there aren't multiple candidates.
|
||||
|
||||
paths = [path for path in self._binding_paths
|
||||
if os.path.basename(path) == filename]
|
||||
|
||||
if not paths:
|
||||
_binding_inc_error("'{}' not found".format(filename))
|
||||
_err("'{}' not found".format(filename))
|
||||
|
||||
if len(paths) > 1:
|
||||
_binding_inc_error("multiple candidates for '{}' in !include: {}"
|
||||
.format(filename, ", ".join(paths)))
|
||||
_err("multiple candidates for included file '{}': {}"
|
||||
.format(filename, ", ".join(paths)))
|
||||
|
||||
with open(paths[0], encoding="utf-8") as f:
|
||||
return yaml.load(f, Loader=yaml.Loader)
|
||||
|
||||
def _binding_include(self, loader, node):
|
||||
# Implements !include, for backwards compatibility.
|
||||
# '!include [foo, bar]' just becomes [foo, bar].
|
||||
|
||||
if isinstance(node, yaml.ScalarNode):
|
||||
# !include foo.yaml
|
||||
return [loader.construct_scalar(node)]
|
||||
|
||||
if isinstance(node, yaml.SequenceNode):
|
||||
# !include [foo.yaml, bar.yaml]
|
||||
return loader.construct_sequence(node)
|
||||
|
||||
_binding_inc_error("unrecognised node type in !include statement")
|
||||
|
||||
def _init_devices(self):
|
||||
# Creates a list of devices (Device objects) from the DT nodes, in
|
||||
# self.devices
|
||||
|
@ -1174,26 +1212,9 @@ def _binding_inc_error(msg):
|
|||
raise yaml.constructor.ConstructorError(None, None, "error: " + msg)
|
||||
|
||||
|
||||
def _merge_included_bindings(binding, binding_path):
|
||||
# Merges any bindings in the 'inherits:' section of 'binding' into the top
|
||||
# level of 'binding'. !includes have already been processed at this point,
|
||||
# and leave the data for the included binding(s) in 'inherits:'.
|
||||
#
|
||||
# Properties in 'binding' take precedence over properties from included
|
||||
# bindings.
|
||||
|
||||
if "inherits" in binding:
|
||||
for inherited in binding.pop("inherits"):
|
||||
_merge_props(
|
||||
binding, _merge_included_bindings(inherited, binding_path),
|
||||
None, binding_path)
|
||||
|
||||
return binding
|
||||
|
||||
|
||||
def _merge_props(to_dict, from_dict, parent, binding_path):
|
||||
# Recursively merges 'from_dict' into 'to_dict', to implement !include. If
|
||||
# a key exists in both 'from_dict' and 'to_dict', then the value in
|
||||
# Recursively merges 'from_dict' into 'to_dict', to implement 'include:'.
|
||||
# If a key exists in both 'from_dict' and 'to_dict', then the value in
|
||||
# 'to_dict' takes precedence.
|
||||
#
|
||||
# 'parent' is the name of the parent key containing 'to_dict' and
|
||||
|
@ -1207,7 +1228,7 @@ def _merge_props(to_dict, from_dict, parent, binding_path):
|
|||
elif prop not in to_dict:
|
||||
to_dict[prop] = from_dict[prop]
|
||||
elif _bad_overwrite(to_dict, from_dict, prop):
|
||||
_err("{} (in '{}'): '{}' from !included file overwritten "
|
||||
_err("{} (in '{}'): '{}' from included file overwritten "
|
||||
"('{}' replaced with '{}')".format(
|
||||
binding_path, parent, prop, from_dict[prop],
|
||||
to_dict[prop]))
|
||||
|
|
|
@ -211,13 +211,26 @@ def merge_included_bindings(fname, node):
|
|||
# section of the binding. 'fname' is the path to the top-level binding
|
||||
# file, and 'node' the current top-level YAML node being processed.
|
||||
|
||||
res = node
|
||||
|
||||
if "include" in node:
|
||||
fnames = node.pop("include")
|
||||
if isinstance(fnames, str):
|
||||
fnames = [fnames]
|
||||
|
||||
for fname in fnames:
|
||||
binding = load_binding_file(fname)
|
||||
inherited = merge_included_bindings(fname, binding)
|
||||
merge_properties(None, fname, inherited, res)
|
||||
res = inherited
|
||||
|
||||
if 'inherits' in node:
|
||||
for inherited in node.pop('inherits'):
|
||||
inherited = merge_included_bindings(fname, inherited)
|
||||
merge_properties(None, fname, inherited, node)
|
||||
node = inherited
|
||||
merge_properties(None, fname, inherited, res)
|
||||
res = inherited
|
||||
|
||||
return node
|
||||
return res
|
||||
|
||||
|
||||
def define_str(name, value, value_tabs, is_deprecated=False):
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
include: [grandchild-1.yaml, grandchild-2.yaml]
|
||||
|
||||
# Legacy include
|
||||
inherits:
|
||||
!include grandchild.yaml
|
||||
!include grandchild-3.yaml
|
||||
|
||||
properties:
|
||||
bar:
|
||||
|
|
6
scripts/dts/test-bindings/grandchild-2.yaml
Normal file
6
scripts/dts/test-bindings/grandchild-2.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
properties:
|
||||
baz:
|
||||
required: true
|
||||
type: int
|
|
@ -3,8 +3,7 @@
|
|||
title: Parent binding
|
||||
description: Parent binding
|
||||
|
||||
inherits:
|
||||
!include child.yaml
|
||||
include: child.yaml
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
|
@ -12,6 +11,6 @@ properties:
|
|||
type: string-array
|
||||
|
||||
foo:
|
||||
# Changed from not being required in grandchild.yaml
|
||||
# Changed from not being required in grandchild-1.yaml
|
||||
required: true
|
||||
# Type set in grandchild
|
||||
|
|
|
@ -274,7 +274,7 @@
|
|||
};
|
||||
|
||||
//
|
||||
// !include in bindings
|
||||
// For testing 'include:' and the legacy 'inherits: !include ...'
|
||||
//
|
||||
|
||||
binding-include {
|
||||
|
@ -282,6 +282,7 @@
|
|||
foo = <0>;
|
||||
bar = <1>;
|
||||
baz = <2>;
|
||||
qaz = <3>;
|
||||
};
|
||||
|
||||
//
|
||||
|
|
|
@ -96,14 +96,14 @@ def run():
|
|||
"[<Register, addr: 0x30000000200000001, size: 0x1>]")
|
||||
|
||||
#
|
||||
# Test !include in bindings
|
||||
# Test 'include:' and the legacy 'inherits: !include ...'
|
||||
#
|
||||
|
||||
verify_streq(edt.get_dev("/binding-include").description,
|
||||
"Parent binding")
|
||||
|
||||
verify_streq(edt.get_dev("/binding-include").props,
|
||||
"{'compatible': <Property, name: compatible, type: string-array, value: ['binding-include-test']>, 'foo': <Property, name: foo, type: int, value: 0>, 'bar': <Property, name: bar, type: int, value: 1>, 'baz': <Property, name: baz, type: int, value: 2>}")
|
||||
"{'compatible': <Property, name: compatible, type: string-array, value: ['binding-include-test']>, 'foo': <Property, name: foo, type: int, value: 0>, 'bar': <Property, name: bar, type: int, value: 1>, 'baz': <Property, name: baz, type: int, value: 2>, 'qaz': <Property, name: qaz, type: int, value: 3>}")
|
||||
|
||||
#
|
||||
# Test 'sub-node:' in binding
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue