acrn-hypervisor/misc/config_tools/configurator/pyodide/loadBoard.py
Junjie Mao f78c8f4660 config_tools: support dynamic enum names in XML schema
The dynamic enum mechanism today only allows specifying the enum values
using XPATH. While this is sufficient from functionality point of view, it
may not provide the best experience as users have to understand the raw
data used internally. The typical way to present more informational labels
of enum values to users is enum names which cannot be supported by the
current XML schema to JSONSchema converter.

This patch allows the XML schema to specify dynamic enum names by adding an
`acrn:option-names` attribute to an element. The attribute is interpreted
as an XPATH which evaluates to a sequence of the same length of
`acrn-options`. The element at index i in that sequence is considered the
enum name of the enum value at index i of the results of `acrn:options`.

This mechanism is first applied to the `pcpu_id` element to indicate
whether a physical CPU is P-core or E-core.

Tracked-On: #8050
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
2022-08-29 17:48:12 +08:00

160 lines
5.4 KiB
Python

#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
import json
import logging
import re
import os
from copy import deepcopy
import elementpath
import lxml.etree as etree
from bs4 import BeautifulSoup
from . import convert_result, nuc11_board, scenario_json_schema, nuc11_board_path
def get_dynamic_scenario(board):
"""
:type board: str
:param board: board xml text
"""
board_xml = etree.fromstring(board)
def get_enum(source, options, option_names, obj_type):
elements = [str(x) for x in elementpath.select(source, options) if x]
element_names = [str(x) for x in elementpath.select(source, option_names) if x]
elements = list(set(zip(elements, element_names)))
if not elements:
elements = [('', '')]
# TODO: Add more converters if needed
enum_type_convert = {'integer': lambda x: int(x) if x else 0}
if obj_type in enum_type_convert.keys():
elements = [(enum_type_convert[obj_type](x[0]), x[1]) for x in elements]
return elements
def dynamic_enum(**enum_setting):
# value from env
function, source = [
{"get_enum": get_enum, "board_xml": board_xml}[enum_setting[key]]
for key in ['function', 'source']
]
# value from given
selector, name_selector, sorted_func, obj_type = [enum_setting[key] for key in ['selector', 'name-selector', 'sorted', 'type']]
# get enum data
enum = function(source, selector, name_selector, obj_type)
if sorted_func:
fn = eval(sorted_func)
enum = sorted(enum, key=lambda x: fn(x[0]))
return zip(*enum)
def dynamic_enum_apply(obj):
# get json schema enum obj
if 'enum' in obj and isinstance(obj['enum'], dict):
enum_setting = obj['enum']
# check enum obj type
if enum_setting['type'] == 'dynamicEnum':
enum_setting['type'] = obj.get('type', '')
# replace json schema obj enum field data
enum, enum_names = dynamic_enum(**enum_setting)
obj['enum'] = enum
obj['enumNames'] = enum_names
return obj
data = json.loads(scenario_json_schema, object_hook=dynamic_enum_apply)
form_schemas = {}
tab_types = ['HV', 'PreLaunchedVM', 'ServiceVM', 'PostLaunchedVM']
form_types = ['BasicConfigType', 'AdvancedConfigType']
for tab_type in tab_types:
form_schemas[tab_type] = {}
for form_type in form_types:
form_schema = deepcopy(data)
current_form_type_schema_obj = form_schema['definitions'][f'{tab_type}{form_type}']
for key in ['type', 'required', 'properties']:
if key == 'required' and key not in current_form_type_schema_obj:
form_schema[key] = []
continue
form_schema[key] = current_form_type_schema_obj[key]
form_schemas[tab_type][form_type] = form_schema
return form_schemas
def get_cat_info(soup):
threads = soup.select('core thread')
threads = {thread.attrs['id']: thread.select_one('cpu_id').text for thread in threads}
caches = soup.select('caches cache')
cat_info = []
for cache in caches:
cache_level = int(cache.attrs['level'])
# ignore cache_level 1 and single core cache region
if cache_level == 1 or len(processors := cache.select('processors processor')) <= 1:
continue
# ignore no CAT capability cache region
if cache.select_one('#CAT') is None:
continue
capacity_mask_length = cache.select_one('capability capacity_mask_length')
capacity_mask_length = int(capacity_mask_length.text)
processors = [int(threads[processor.text]) for processor in processors]
processors.sort()
cache_info = {
'id': cache.attrs['id'],
'level': cache_level,
'type': cache.attrs['type'],
'cache_size': int(cache.select_one('cache_size').text),
'capacity_mask_length': capacity_mask_length,
'processors': processors,
}
cat_info.append(cache_info)
cat_info.sort(key=lambda x: int(x['id'], 16))
cat_info.sort(key=lambda x: x['level'], reverse=True)
return cat_info
def get_board_info(board, path):
soup = BeautifulSoup(board, 'xml')
# Workaround: The pyodide thinks it runs under os.name == posix.
# So os.path.basename will not work on windows.
# Here we replace all '\' with '/' to make it work (most of the time).
try:
path = path.replace('\\', '/')
fname = os.path.basename(path)
basename, _ = os.path.splitext(fname)
board_name = basename + '.xml' if basename.endswith('.board') \
else basename + '.board.xml'
except Exception as e:
logging.warning(e)
board_name = 'default'
result = {
'name': board_name,
'content': board,
'CAT_INFO': get_cat_info(soup),
'BIOS_INFO': soup.select_one('BIOS_INFO').text,
'BASE_BOARD_INFO': soup.select_one('BASE_BOARD_INFO').text
}
return result
def load_board(board, path):
result = {
'scenarioJSONSchema': get_dynamic_scenario(board),
'boardInfo': get_board_info(board, path)
}
return convert_result(result)
def test():
load_board(nuc11_board, nuc11_board_path)
main = load_board
if __name__ == '__main__':
test()