summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorEd Tanous <edtanous@google.com>2022-03-15 00:56:47 +0300
committerEd Tanous <edtanous@google.com>2022-12-08 23:08:01 +0300
commit0ec8b83db7e8629c721c0e632de702bf1018f58f (patch)
tree207fa734b40162e4953f4c8c229cbcfb4c02900d /scripts
parent7e8890c5c2b8d69c104f2db4e82fb06c40374c08 (diff)
downloadbmcweb-0ec8b83db7e8629c721c0e632de702bf1018f58f.tar.xz
Generate Redfish enums from schemas
OpenBMC tends to have a significant problem in doing the appropriate lookups from the schema files, and many bugs have been injected by users picking a bad enum, or mistyping the casing of an enum value. At the same time, nlohmann::json has recently added first class support for enums, https://json.nlohmann.me/features/enum_conversion/ This commit attempts to build a set of redfish includes file with all the available Redfish enums in an easy to use enum class. This makes it very clear which enums are supported by the schemas we produce, and adds very little to no extra boilerplate on the human-written code we produced previously. Note, in the generated enum class, because of our use of the clang-tidy check for macros, the clang-tidy check needs an exception for these macros that don't technically follow the coding standard. This seems like a reasonable compromise, and in this case, given that nlohmann doesn't support a non-macro version of this. One question that arises is what this does to the binary size.... Under the current compiler optimizations, and with the current best practices, it leads to an overall increase in binary size of ~1200 bytes for the enum machinery, then approximately 200 bytes for every call site we switch over. We should decide if this nominal increase is reasonable. Tested: Redfish protocol validator runs with same number of failures as previously. Redfish Service Validator passes (one unrelated qemu-specific exception) Signed-off-by: Ed Tanous <edtanous@google.com> Change-Id: I7c7ee4db0823f7c57ecaa59620b280b53a46e2c1
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/generate_schema_enums.py163
-rwxr-xr-xscripts/update_schemas.py3
2 files changed, 166 insertions, 0 deletions
diff --git a/scripts/generate_schema_enums.py b/scripts/generate_schema_enums.py
new file mode 100755
index 0000000000..13fbefe40f
--- /dev/null
+++ b/scripts/generate_schema_enums.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python3
+import os
+import xml.etree.ElementTree as ET
+from collections import defaultdict
+import shutil
+import re
+
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+REDFISH_SCHEMA_DIR = os.path.realpath(
+ os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1", "schema")
+)
+
+OUTFOLDER = os.path.realpath(os.path.join(
+ SCRIPT_DIR,
+ "..",
+ "redfish-core",
+ "include",
+ "generated",
+ "enums"
+ )
+)
+
+# Odata string types
+EDMX = "{http://docs.oasis-open.org/odata/ns/edmx}"
+EDM = "{http://docs.oasis-open.org/odata/ns/edm}"
+
+
+class Enum:
+ def __init__(self, name, values, namespace, from_file):
+ self.name = name
+ self.values = values
+ self.namespace = namespace
+ self.from_file = from_file
+
+
+def parse_schema(element, filename):
+ EntityTypes = []
+ namespace = element.attrib["Namespace"]
+ for schema_element in element:
+ name = schema_element.attrib.get("Name", None)
+ if name is None:
+ continue
+ if schema_element.tag == EDM + "EnumType":
+ enums = []
+ for member in schema_element.findall(EDM + "Member"):
+ enums.append(member.attrib["Name"])
+ EntityTypes.append(Enum(name, enums, namespace, filename))
+ return EntityTypes
+
+
+def parse_file(filename):
+ tree = ET.parse(filename)
+ root = tree.getroot()
+ results = []
+ data_services = root.findall(EDMX + "DataServices")
+ for ds in data_services:
+ for element in ds:
+ if element.tag == EDM + "Schema":
+ results.extend(parse_schema(element, filename))
+
+ return results
+
+
+def camel_to_snake(name):
+ # snake casing PCIeDevice and PCIeFunction results in mediocre results
+ # given that the standard didn't camel case those in a way that the
+ # algorithm expects, so change the casing explicitly to generate sane
+ # snake case results.
+ name = name.replace("PCIe", "Pcie")
+ name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
+ return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
+
+
+def write_enum_list(redfish_defs_file, enum_list, snake_case_namespace):
+ redfish_defs_file.write(
+ "#pragma once\n"
+ "#include <nlohmann/json.hpp>\n\n"
+ "namespace {}\n"
+ "{{\n"
+ "// clang-format off\n\n".format(snake_case_namespace)
+ )
+
+ for element in enum_list:
+ redfish_defs_file.write("enum class {}{{\n".format(element.name))
+ values = element.values
+ if "Invalid" not in values:
+ values.insert(0, "Invalid")
+
+ for value in values:
+ redfish_defs_file.write(" {},\n".format(value))
+
+ redfish_defs_file.write("};\n\n")
+
+ for element in enum_list:
+ values = element.values
+ if "Invalid" not in values:
+ values.insert(0, "Invalid")
+ # nlohmann::json aparently uses c style arrays in their enum
+ # implementation, and clang-tidy isn't smart enough to figure out that
+ # the C arrays are in their code not bmcwebs, so we have to explicitly
+ # ignore the error.
+ redfish_defs_file.write(
+ "NLOHMANN_JSON_SERIALIZE_ENUM({}, {{\n".format(
+ element.name
+ )
+ )
+ for value in values:
+ redfish_defs_file.write(
+ ' {{{}::{}, "{}"}},\n'.format(element.name, value, value)
+ )
+
+ redfish_defs_file.write("});\n\n")
+
+ print(element.name)
+
+ redfish_defs_file.write("}\n" "// clang-format on\n")
+
+
+def generate_enums(flat_list):
+ # clear out the old results if they exist
+ if os.path.exists(OUTFOLDER):
+ shutil.rmtree(OUTFOLDER)
+ os.makedirs(OUTFOLDER)
+
+ enum_by_namespace = defaultdict(list)
+
+ for element in flat_list:
+ if isinstance(element, Enum):
+ namespace_split = element.namespace.split(".")[0]
+ enum_by_namespace[namespace_split].append(element)
+
+ for namespace, enum_list in enum_by_namespace.items():
+ snake_case_namespace = camel_to_snake(namespace)
+ outfile = os.path.join(
+ OUTFOLDER, "{}.hpp".format(snake_case_namespace)
+ )
+
+ with open(outfile, "w") as redfish_defs:
+ write_enum_list(redfish_defs, enum_list, snake_case_namespace)
+
+
+def main():
+ print("Reading from {}".format(REDFISH_SCHEMA_DIR))
+ dir_list = os.listdir(REDFISH_SCHEMA_DIR)
+
+ filepaths = [
+ os.path.join(REDFISH_SCHEMA_DIR, filename) for filename in dir_list
+ ]
+
+ enum_list = []
+
+ for filepath in filepaths:
+ out = parse_file(filepath)
+ enum_list.extend(out)
+
+ print("Parsing done")
+
+ generate_enums(enum_list)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/update_schemas.py b/scripts/update_schemas.py
index 459d0457bd..f6eca42c78 100755
--- a/scripts/update_schemas.py
+++ b/scripts/update_schemas.py
@@ -8,6 +8,7 @@ from io import BytesIO
import requests
from packaging.version import parse
+import generate_schema_enums
VERSION = "DSP8010_2022.2"
@@ -390,3 +391,5 @@ with open(os.path.join(cpp_path, "schemas.hpp"), "w") as hpp_file:
hpp_file.write(" };\n}\n")
zip_ref.close()
+
+generate_schema_enums.main()