diff --git a/.github/workflows/gen_json.yml b/.github/workflows/gen_json.yml new file mode 100644 index 000000000..32c1cd385 --- /dev/null +++ b/.github/workflows/gen_json.yml @@ -0,0 +1,28 @@ +name: Test API JSON generator +on: + push: + pull_request: + +jobs: + test_api_json: + if: github.repository == 'lvgl/lvgl' + runs-on: ubuntu-22.04 + name: Test API JSON + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install Doxygen and Latex dependencies + run: | + sudo apt-get update + sudo apt-get install doxygen texlive-xetex texlive-binaries texlive-lang-english latexmk fonts-freefont-otf + + - name: Install requirements + run: pip install -r scripts/gen_json/requirements.txt + + - name: Run test + run: python3 tests/gen_json/test_gen_json.py diff --git a/docs/build.py b/docs/build.py index c919662cc..ed45b32f6 100755 --- a/docs/build.py +++ b/docs/build.py @@ -16,6 +16,7 @@ import shutil import tempfile import config_builder import add_translation +from docbuilder_utils import spawn # due to the modifications that take place to the documentation files # when the documentaation builds it is better to copy the source files to a @@ -70,10 +71,11 @@ def cmd(s): print("") print(s) print("-------------------------------------") - r = os.system(s) - if r != 0: + + result = os.system(s) + if result != 0: print("Exit build due to previous error") - exit(-1) + sys.exit(result) # Get the current branch name @@ -140,7 +142,7 @@ print("Add translation") add_translation.exec(temp_directory) print("Running doxygen") -cmd('cd "{0}" && doxygen Doxyfile'.format(temp_directory)) +cmd('cd "{temp_directory}" && doxygen Doxyfile'.format(temp_directory=temp_directory)) print('Reading Doxygen output') diff --git a/docs/config_builder.py b/docs/config_builder.py index b53f4fe61..fde52275e 100644 --- a/docs/config_builder.py +++ b/docs/config_builder.py @@ -10,20 +10,34 @@ src_config = os.path.abspath(os.path.join( )) -def run(): +def run(c_path=None): + global dst_config + + if c_path is not None: + dst_config = c_path + with open(src_config, 'r') as f: data = f.read() data = data.split('\n') for i, line in enumerate(data): - if 'LV_USE' in line or 'LV_FONT' in line: + if 'LV_USE_PROFILER' in line: + continue + + if 'LV_USE' in line or 'LV_FONT' in line and '#define' in line: line = [item for item in line.split(' ') if item] + for j, item in enumerate(line): if item == '0': line[j] = '1' + line = ' '.join(line) data[i] = line + elif line.startswith('#if 0'): + line = line.replace('#if 0', '#if 1') + data[i] = line + data = '\n'.join(data) with open(dst_config, 'w') as f: diff --git a/docs/doc_builder.py b/docs/doc_builder.py index ec9cd46b7..c22ba52fb 100644 --- a/docs/doc_builder.py +++ b/docs/doc_builder.py @@ -1,9 +1,79 @@ import os +import sys from xml.etree import ElementTree as ET base_path = '' xml_path = '' +EMIT_WARNINGS = True +DOXYGEN_OUTPUT = True + +MISSING_FUNC = 'MissingFunctionDoc' +MISSING_FUNC_ARG = 'MissingFunctionArgDoc' +MISSING_FUNC_RETURN = 'MissingFunctionReturnDoc' +MISSING_FUNC_ARG_MISMATCH = 'FunctionArgMissing' +MISSING_STRUCT = 'MissingStructureDoc' +MISSING_STRUCT_FIELD = 'MissingStructureFieldDoc' +MISSING_UNION = 'MissingUnionDoc' +MISSING_UNION_FIELD = 'MissingUnionFieldDoc' +MISSING_ENUM = 'MissingEnumDoc' +MISSING_ENUM_ITEM = 'MissingEnumItemDoc' +MISSING_TYPEDEF = 'MissingTypedefDoc' +MISSING_VARIABLE = 'MissingVariableDoc' +MISSING_MACRO = 'MissingMacroDoc' + + +def warn(warning_type, *args): + if EMIT_WARNINGS: + args = ' '.join(str(arg) for arg in args) + + if warning_type is None: + output = f'\033[31;1m {args}\033[0m\n' + else: + output = f'\033[31;1m{warning_type}: {args}\033[0m\n' + + sys.stdout.write(output) + sys.stdout.flush() + + +def build_docstring(element): + docstring = None + if element.tag == 'parameterlist': + return None + + if element.text: + docstring = element.text.strip() + + for item in element: + ds = build_docstring(item) + if ds: + if docstring: + docstring += ' ' + ds + else: + docstring = ds.strip() + + if element.tag == 'para': + if docstring: + docstring = '\n\n' + docstring + + if element.tag == 'ref': + docstring = f':ref:`{docstring}`' + + if element.tail: + if docstring: + docstring += ' ' + element.tail.strip() + else: + docstring = element.tail.strip() + + return docstring + + +def read_as_xml(d): + try: + return ET.fromstring(d) + except: # NOQA + return None + def load_xml(fle): fle = os.path.join(xml_path, fle + '.xml') @@ -39,7 +109,24 @@ namespaces = {} files = {} +# things to remove from description +# + + +class STRUCT_FIELD(object): + + def __init__(self, name, type, description, file_name, line_no): + self.name = name + self.type = type + self.description = description + self.file_name = file_name + self.line_no = line_no + + class STRUCT(object): + _missing = MISSING_STRUCT + _missing_field = MISSING_STRUCT_FIELD + template = '''\ .. doxygenstruct:: {name} :project: lvgl @@ -52,36 +139,83 @@ class STRUCT(object): def __init__(self, parent, refid, name, **_): if name in structures: self.__dict__.update(structures[name].__dict__) - return + else: + structures[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.types = set() + self._deps = None + self.header_file = '' + self.description = None + self.fields = [] + self.file_name = None + self.line_no = None - structures[name] = self - self.parent = parent - self.refid = refid - self.name = name - self.types = set() - self._deps = None - self.header_file = '' + if parent and refid: + root = load_xml(refid) - root = load_xml(refid) - - for compounddef in root: - if compounddef.attrib['id'] != self.refid: - continue - - for child in compounddef: - if child.tag == 'includes': - self.header_file = os.path.splitext(child.text)[0] - - if child.tag != 'sectiondef': + for compounddef in root: + if compounddef.attrib['id'] != self.refid: continue - for memberdef in child: - t = get_type(memberdef) - - if t is None: + for child in compounddef: + if child.tag == 'includes': + self.header_file = os.path.splitext(child.text)[0] continue - self.types.add(t) + elif child.tag == 'location': + self.file_name = child.attrib['file'] + self.line_no = child.attrib['line'] + + elif child.tag == 'detaileddescription': + self.description = build_docstring(child) + + elif child.tag == 'sectiondef': + for memberdef in child: + t = get_type(memberdef) + description = None + name = '' + file_name = None + line_no = None + + for element in memberdef: + if element.tag == 'location': + file_name = element.attrib['file'] + line_no = element.attrib['line'] + + elif element.tag == 'name': + name = element.text + + elif element.tag == 'detaileddescription': + description = build_docstring(element) + + field = STRUCT_FIELD(name, t, description, file_name, line_no) + self.fields.append(field) + + if t is None: + continue + + self.types.add(t) + + if not self.description: + warn(self._missing, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + + for field in self.fields: + if not field.description: + warn(self._missing_field, self.name) + warn(None, 'FIELD:', field.name) + warn(None, 'FILE:', field.file_name) + warn(None, 'LINE:', field.line_no) + warn(None) + + def get_field(self, name): + for field in self.fields: + if field.name == name: + return field @property def deps(self): @@ -117,6 +251,9 @@ class STRUCT(object): class UNION(STRUCT): + _missing = MISSING_UNION + _missing_field = MISSING_UNION_FIELD + template = '''\ .. doxygenunion:: {name} :project: lvgl @@ -148,12 +285,48 @@ class VARIABLE(object): def __init__(self, parent, refid, name, **_): if name in variables: self.__dict__.update(variables[name].__dict__) - return + else: + variables[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.description = None + self.type = '' + self.file_name = None + self.line_no = None - variables[name] = self - self.parent = parent - self.refid = refid - self.name = name + if parent is not None: + root = load_xml(parent.refid) + + for compounddef in root: + if compounddef.attrib['id'] != parent.refid: + continue + + for child in compounddef: + if ( + child.tag == 'sectiondef' and + child.attrib['kind'] == 'var' + ): + for memberdef in child: + if memberdef.attrib['id'] == refid: + break + else: + continue + + self.type = get_type(memberdef) + + for element in memberdef: + if element.tag == 'location': + self.file_name = element.attrib['file'] + self.line_no = element.attrib['line'] + elif element.tag == 'detaileddescription': + self.description = build_docstring(element) + + if not self.description: + warn(MISSING_VARIABLE, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) def __str__(self): return self.template.format(name=self.name) @@ -172,17 +345,92 @@ class NAMESPACE(object): def __init__(self, parent, refid, name, **_): if name in namespaces: self.__dict__.update(namespaces[name].__dict__) - return + else: + namespaces[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.description = None + self.line_no = None + self.file_name = None + self.enums = [] + self.funcs = [] + self.vars = [] + self.typedefs = [] + self.structs = [] + self.unions = [] + self.classes = [] - namespaces[name] = self - self.parent = parent - self.refid = refid - self.name = name + # root = load_xml(refid) + # + # for compounddef in root: + # if compounddef.attrib['id'] != refid: + # continue + # + # for sectiondef in compounddef: + # if sectiondef.tag != 'sectiondef': + # continue + # + # enum + # typedef + # func + # struct + # union + # + # + # cls = globals()[sectiondef.attrib['kind'].upper()] + # if cls == ENUM: + # if sectiondef[0].text: + # sectiondef.attrib['name'] = sectiondef[0].text.strip() + # enums_.append(cls(self, **sectiondef.attrib)) + # else: + # sectiondef.attrib['name'] = None + # enums_.append(cls(self, **sectiondef.attrib)) + # + # elif cls == ENUMVALUE: + # if enums_[-1].is_member(sectiondef): + # enums_[-1].add_member(sectiondef) + # + # else: + # sectiondef.attrib['name'] = sectiondef[0].text.strip() + # cls(self, **sectiondef.attrib) def __str__(self): return self.template.format(name=self.name) +class FUNC_ARG(object): + + def __init__(self, name, type): + self.name = name + self.type = type + self.description = None + + +groups = {} + + +class GROUP(object): + template = '''\ +.. doxygengroup:: {name} + :project: lvgl +''' + + def __init__(self, parent, refid, name, **_): + if name in groups: + self.__dict__.update(functions[name].__dict__) + else: + functions[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.description = None + + def __str__(self): + return self.template.format(name=self.name) + + + class FUNCTION(object): template = '''\ .. doxygenfunction:: {name} @@ -192,15 +440,20 @@ class FUNCTION(object): def __init__(self, parent, refid, name, **_): if name in functions: self.__dict__.update(functions[name].__dict__) - return - - functions[name] = self - self.parent = parent - self.refid = refid - self.name = name - self.types = set() - self.restype = None - self._deps = None + else: + functions[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.types = set() + self.restype = None + self.args = [] + self._deps = None + self.description = None + self.res_description = None + self.file_name = None + self.line_no = None + self.void_return = False if parent is not None: root = load_xml(parent.refid) @@ -212,10 +465,14 @@ class FUNCTION(object): for child in compounddef: if child.tag != 'sectiondef': continue + if child.attrib['kind'] != 'func': continue for memberdef in child: + if 'id' not in memberdef.attrib: + continue + if memberdef.attrib['id'] == refid: break else: @@ -232,11 +489,88 @@ class FUNCTION(object): self.restype = get_type(memberdef) for child in memberdef: + if child.tag == 'type': + if child.text and child.text.strip() == 'void': + self.void_return = True + if child.tag == 'param': t = get_type(child) if t is not None: self.types.add(t) + for element in child: + if element.tag == 'declname': + arg = FUNC_ARG(element.text, t) + self.args.append(arg) + + for child in memberdef: + if child.tag == 'location': + self.file_name = child.attrib['file'] + self.line_no = child.attrib['line'] + + elif child.tag == 'detaileddescription': + self.description = build_docstring(child) + for element in child: + if element.tag != 'para': + continue + + for desc_element in element: + if desc_element.tag == 'simplesect' and desc_element.attrib['kind'] == 'return': + self.res_description = build_docstring(desc_element) + + if desc_element.tag != 'parameterlist': + continue + + for parameter_item in desc_element: + parameternamelist = parameter_item[0] + if parameternamelist.tag != 'parameternamelist': + continue + + parameter_name = parameternamelist[0].text + + try: + parameterdescription = parameter_item[1] + if parameterdescription.tag == 'parameterdescription': + parameter_description = build_docstring(parameterdescription) + else: + parameter_description = None + except IndexError: + parameter_description = None + + if parameter_name is not None: + for arg in self.args: + if arg.name != parameter_name: + continue + + arg.description = parameter_description + break + else: + warn(MISSING_FUNC_ARG_MISMATCH, self.name) + warn(None, 'ARG:', parameter_name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + + if not self.description: + warn(MISSING_FUNC, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + else: + for arg in self.args: + if not arg.description: + warn(MISSING_FUNC_ARG, self.name) + warn(None, 'ARG:', arg.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + + if not self.res_description and not self.void_return: + warn(MISSING_FUNC_RETURN, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + if self.restype in self.types: self.restype = None @@ -277,6 +611,7 @@ class FUNCTION(object): class FILE(object): + def __init__(self, _, refid, name, node, **__): if name in files: self.__dict__.update(files[name].__dict__) @@ -296,8 +631,13 @@ class FILE(object): cls = globals()[member.attrib['kind'].upper()] if cls == ENUM: - member.attrib['name'] = member[0].text.strip() - enums_.append(cls(self, **member.attrib)) + if member[0].text: + member.attrib['name'] = member[0].text.strip() + enums_.append(cls(self, **member.attrib)) + else: + member.attrib['name'] = None + enums_.append(cls(self, **member.attrib)) + elif cls == ENUMVALUE: if enums_[-1].is_member(member): enums_[-1].add_member(member) @@ -316,14 +656,102 @@ class ENUM(object): def __init__(self, parent, refid, name, **_): if name in enums: self.__dict__.update(enums[name].__dict__) - return + else: - enums[name] = self + enums[name] = self - self.parent = parent - self.refid = refid - self.name = name - self.members = [] + self.parent = parent + self.refid = refid + self.name = name + self.members = [] + self.description = None + self.file_name = None + self.line_no = None + + if parent is not None: + root = load_xml(parent.refid) + + for compounddef in root: + if compounddef.attrib['id'] != parent.refid: + continue + + for child in compounddef: + if child.tag != 'sectiondef': + continue + + if child.attrib['kind'] != 'enum': + continue + + for memberdef in child: + if 'id' not in memberdef.attrib: + continue + + if memberdef.attrib['id'] == refid: + break + else: + continue + + break + else: + continue + + break + else: + return + # raise RuntimeError(f'not able to locate enum {name} ({refid})') + + for element in memberdef: + if element.tag == 'location': + self.file_name = element.attrib['file'] + self.line_no = element.attrib['line'] + + if element.tag == 'detaileddescription': + self.description = build_docstring(element) + elif element.tag == 'enumvalue': + item_name = None + item_description = None + item_file_name = None + item_line_no = None + + for s_element in element: + if s_element.tag == 'name': + item_name = s_element.text + elif s_element.tag == 'detaileddescription': + item_description = build_docstring(s_element) + + elif s_element.tag == 'location': + item_file_name = child.attrib['file'] + item_line_no = child.attrib['line'] + + if item_name is not None: + for ev in self.members: + if ev.name != item_name: + continue + break + else: + ev = ENUMVALUE( + self, + element.attrib['id'], + item_name + ) + + self.members.append(ev) + + ev.description = item_description + + if not self.description: + warn(MISSING_ENUM, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + + for member in self.members: + if not member.description: + warn(MISSING_ENUM_ITEM, self.name) + warn(None, 'MEMBER:', member.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) def is_member(self, member): return ( @@ -332,11 +760,16 @@ class ENUM(object): ) def add_member(self, member): + name = member[0].text.strip() + for ev in self.members: + if ev.name == name: + return + self.members.append( ENUMVALUE( self, member.attrib['refid'], - member[0].text.strip() + name ) ) @@ -350,6 +783,29 @@ class ENUM(object): defines = {} +def build_define(element): + define = None + + if element.text: + define = element.text.strip() + + for item in element: + ds = build_define(item) + if ds: + if define: + define += ' ' + ds + else: + define = ds.strip() + + if element.tail: + if define: + define += ' ' + element.tail.strip() + else: + define = element.tail.strip() + + return define + + class DEFINE(object): template = '''\ .. doxygendefine:: {name} @@ -359,51 +815,17 @@ class DEFINE(object): def __init__(self, parent, refid, name, **_): if name in defines: self.__dict__.update(defines[name].__dict__) - return + else: + defines[name] = self - defines[name] = self - - self.parent = parent - self.refid = refid - self.name = name - - def __str__(self): - return self.template.format(name=self.name) - - -class ENUMVALUE(object): - template = '''\ -.. doxygenenumvalue:: {name} - :project: lvgl -''' - - def __init__(self, parent, refid, name, **_): - self.parent = parent - self.refid = refid - self.name = name - - def __str__(self): - return self.template.format(name=self.name) - - -class TYPEDEF(object): - template = '''\ -.. doxygentypedef:: {name} - :project: lvgl -''' - - def __init__(self, parent, refid, name, **_): - if name in typedefs: - self.__dict__.update(typedefs[name].__dict__) - return - - typedefs[name] = self - - self.parent = parent - self.refid = refid - self.name = name - self.type = None - self._deps = None + self.parent = parent + self.refid = refid + self.name = name + self.description = None + self.file_name = None + self.line_no = None + self.params = None + self.initializer = None if parent is not None: root = load_xml(parent.refid) @@ -415,7 +837,8 @@ class TYPEDEF(object): for child in compounddef: if child.tag != 'sectiondef': continue - if child.attrib['kind'] != 'typedef': + + if child.attrib['kind'] != 'define': continue for memberdef in child: @@ -432,6 +855,123 @@ class TYPEDEF(object): else: return + for element in memberdef: + if element.tag == 'location': + self.file_name = element.attrib['file'] + self.line_no = element.attrib['line'] + + elif element.tag == 'detaileddescription': + self.description = build_docstring(element) + + elif element.tag == 'param': + for child in element: + if child.tag == 'defname': + if self.params is None: + self.params = [] + + if child.text: + self.params.append(child.text) + + elif element.tag == 'initializer': + initializer = build_define(element) + if initializer is None: + self.initializer = '' + else: + self.initializer = initializer + + if not self.description: + warn(MISSING_MACRO, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + + def __str__(self): + return self.template.format(name=self.name) + + +class ENUMVALUE(object): + template = '''\ +.. doxygenenumvalue:: {name} + :project: lvgl +''' + + def __init__(self, parent, refid, name, **_): + self.parent = parent + self.refid = refid + self.name = name + self.description = None + self.file_name = None + self.line_no = None + + def __str__(self): + return self.template.format(name=self.name) + + +class TYPEDEF(object): + template = '''\ +.. doxygentypedef:: {name} + :project: lvgl +''' + + def __init__(self, parent, refid, name, **_): + if name in typedefs: + self.__dict__.update(typedefs[name].__dict__) + else: + typedefs[name] = self + + self.parent = parent + self.refid = refid + self.name = name + self.type = None + self._deps = None + self.description = None + self.file_name = None + self.line_no = None + + if parent is not None: + root = load_xml(parent.refid) + + for compounddef in root: + if compounddef.attrib['id'] != parent.refid: + continue + + for child in compounddef: + if child.tag != 'sectiondef': + continue + if child.attrib['kind'] != 'typedef': + continue + + for memberdef in child: + if 'id' not in memberdef.attrib: + continue + + if memberdef.attrib['id'] == refid: + break + else: + continue + + break + else: + continue + + break + else: + return + + for element in memberdef: + if element.tag == 'location': + self.file_name = element.attrib['file'] + self.line_no = element.attrib['line'] + + if element.tag == 'detaileddescription': + self.description = build_docstring(element) + + if not self.description: + warn(MISSING_TYPEDEF, self.name) + warn(None, 'FILE:', self.file_name) + warn(None, 'LINE:', self.line_no) + warn(None) + self.type = get_type(memberdef) @property @@ -622,7 +1162,7 @@ def get_includes(name1, name2, obj, includes): if not is_name_match(name1, name2): return - if obj.parent is not None: + if obj.parent is not None and hasattr(obj.parent, 'header_file'): header_file = obj.parent.header_file elif hasattr(obj, 'header_file'): header_file = obj.header_file @@ -638,12 +1178,112 @@ def get_includes(name1, name2, obj, includes): includes.add((header_file, html_files[header_file])) +class XMLSearch(object): + + def __init__(self, temp_directory): + global xml_path + import subprocess + import re + import sys + + bp = os.path.abspath(os.path.dirname(__file__)) + + lvgl_path = os.path.join(temp_directory, 'lvgl') + src_path = os.path.join(lvgl_path, 'src') + + doxy_path = os.path.join(bp, 'Doxyfile') + + with open(doxy_path, 'rb') as f: + data = f.read().decode('utf-8') + + data = data.replace( + '#*#*LV_CONF_PATH*#*#', + os.path.join(temp_directory, 'lv_conf.h') + ) + data = data.replace('*#*#SRC#*#*', '"{0}"'.format(src_path)) + + with open(os.path.join(temp_directory, 'Doxyfile'), 'wb') as f: + f.write(data.encode('utf-8')) + + status, br = subprocess.getstatusoutput("git branch") + _, gitcommit = subprocess.getstatusoutput("git rev-parse HEAD") + br = re.sub('\* ', '', br) + + urlpath = re.sub('release/', '', br) + + os.environ['LVGL_URLPATH'] = urlpath + os.environ['LVGL_GITCOMMIT'] = gitcommit + + p = subprocess.Popen( + f'cd "{temp_directory}" && doxygen Doxyfile', + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True + ) + + out, err = p.communicate() + if p.returncode: + if out: + sys.stdout.write(out) + sys.stdout.flush() + if err: + sys.stderr.write(err) + sys.stdout.flush() + + sys.exit(p.returncode) + + xml_path = os.path.join(temp_directory, 'xml') + + self.index = load_xml('index') + + for compound in self.index: + compound.attrib['name'] = compound[0].text.strip() + if compound.attrib['kind'] in ('example', 'page', 'dir'): + continue + + globals()[compound.attrib['kind'].upper()]( + None, + node=compound, + **compound.attrib + ) + + def get_macros(self): + return list(defines.values()) + + def get_enum_item(self, e_name): + for enum, obj in enums.items(): + for enum_item in obj.members: + if enum_item.name == e_name: + return enum_item + + def get_enum(self, e_name): + return enums.get(e_name, None) + + def get_function(self, f_name): + return functions.get(f_name, None) + + def get_variable(self, v_name): + return variables.get(v_name, None) + + def get_union(self, u_name): + return unions.get(u_name, None) + + def get_structure(self, s_name): + return structures.get(s_name, None) + + def get_typedef(self, t_name): + return typedefs.get(t_name, None) + + def get_macro(self, m_name): + return defines.get(m_name, None) + + def run(project_path, temp_directory, *doc_paths): global base_path global xml_path global lvgl_src_path global api_path - + base_path = temp_directory xml_path = os.path.join(base_path, 'xml') api_path = os.path.join(base_path, 'API') @@ -651,7 +1291,7 @@ def run(project_path, temp_directory, *doc_paths): if not os.path.exists(api_path): os.makedirs(api_path) - + iter_src('API', '') index = load_xml('index') diff --git a/docs/integration/bindings/api_json.rst b/docs/integration/bindings/api_json.rst new file mode 100644 index 000000000..f0983da93 --- /dev/null +++ b/docs/integration/bindings/api_json.rst @@ -0,0 +1,504 @@ +Output API as JSON data +======================= + +We have written a script that will read the header files in LVGL and outputs a +more friendly JSON format for the API. This is done so that bindings that generate +code automatically will have an easy way to collect the needed information without +having to reinvent the wheel. The JSON data format has already made libraries for +reading the format for just about every programming language out there. + +The script in order to run does have some requirements. + + - Python >= 3.10 + - Pycparser >= 2.21: Python Library for reading the preprocessor ouotput from the C compiler + - PyMSVC >= 0.4.0: Python library is using MSVC Compiler + - C compiler, gcc for Linux, clang for OSX and MSVC for Windows + - Doxygen: used to read the docstrings from the header files. + +There are several options when running the script. They are as follows + + - `--output-path`: output directory for JSON file. If one is not supplied + then it will be output stdout + - `--lvgl-config`: path to lv_conf.h (including file name), if this is not + set then a config file will be generated that has most common things turned on + - `--develop`: leaves the temporary folder in place. + + +to use the script + +.. code:: shell + + python /scripts/gen_json/gen_json.py --output-path=json/output/directory --lvgl-config=path/to/lv_conf.h + + +or if you want to run a subprocess from inside of a generation script and read the output from stdout + +.. code:: shell + + python /scripts/gen_json/gen_json.py --lvgl-config=path/to/lv_conf.h + + + + +The JSON data is broken apart into a couple of main categories. + + - enums + - functions + - function_pointers + - structures + - unions + - variables + - typedefs + - forward_decls + - macros + +Those categories are the element names undert the root of the JSON data. +The value for each categry is an array of JSON elements. There is a bit of +nesting with the elements in the arrays and I have created "json_types" that +will allow you to identify exactly what you are dealing with. + +The different "json_types" are as follows: + + - ``"array"``: The array type is used to identify arrays. + + Available JSON fields: + - ``"dim"``: number of items in the array + - ``"quals"``: array of qualifiers, IE "const" + - ``"type"``: This may or may not be available. + - ``"name"``: the name of the data type + + + - ``"field"``: This type is used to describe fields in structures and unions. + It is used in the ``"fields"`` array of the ``"struct"`` and ``"union"`` JSON types. + + Available JSON fields: + - ``"name"``: The name of the field. + - ``"type"``: This contains the type information for the field. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"bitsize"``: The number of bits the field has or ``null`` + if there is no bit size defined + - ``"docstring"``: you should know what this is. + + + - ``"arg"``: Used to describe an argument/parameter in a function or a function pointer. + + Available JSON fields: + - ``"name"``: The name of the argument/parameter. + - ``"type"``: This contains the type information for the field. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"docstring"``: you should know what this is. + - ``"quals"``: array of qualifiers, IE "const" + + + - ``"forward_decl"``: Describes a forward declaration.There are structures in + LVGL that are considered to be private and that is what these desccribe. + + Available JSON fields: + - ``"name"``: The name of the formard declaration. + - ``"type"``: This contains the type information for the field. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"docstring"``: you should know what this is. + - ``"quals"``: array of qualifiers, IE "const" + + + - ``"function_pointer"``: Describes a function pointer. These are used when + registering callback functions in LVGL. + + Available JSON fields: + - ``"name"``: The name of the function pointer. + - ``"type"``: This contains the return type information for the function pointer. + - ``"docstring"``: you should know what this is. + - ``"args"``: array of ``"arg"`` objects. This describes the fuction arguments/parameters. + - ``"quals"``: array of qualifiers, IE "const" + + + - ``"variable"``: Describes a global variable. + + Available JSON fields: + - ``"name"``: The name of the variable. + - ``"type"``: This contains the type information for the field. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"docstring"``: you should know what this is. + - ``"quals"``: array of qualifiers, IE "const" + - ``"storage"``: array of storage classifiers, IE "extern" + + + - ``"special_type"``: Currently only used to describe an ellipsis argument + for a function. + + Available JSON fields: + - ``"name"``: will always be "ellipsis". + + + - ``"primitive_type"``: This is a base type. There or no other types beneith this. + This tells you that the type is a basic or primitive C type. + IE: struct, union, int, unsigned int, etc... + + Available JSON fields: + - ``"name"``: The name of the primitive type. + + + - ``"enum"``: Describes a grouping of enumeration items/members. + + Available JSON fields: + - ``"name"``: The name of the enumeration group/type. + - ``"type"``: This contains the type information for the enumeration group. + This is always going to be an "int" type. Make sure you do not use this + type as the type for the members of this enumeration group. Check the + enumeration members type to get the correct type. + - ``"docstring"``: you should know what this is. + - ``"members"``: array of ``"enum_member"`` objects + + + - ``"enum_member"``: Describes an enumeration item/member. Only found under + the ``"members"`` field of an ``"enum"`` JSON type + + Available JSON fields: + - ``"name"``: The name of the enumeration. + - ``"type"``: This contains the type information for the enum member. + This gets a bit tricky because the type specified in here is not always + going to be an "int". It will usually point to an lvgl type and the type + of the lvgl type can be found in the ``"typedefs"`` section. + - ``"docstring"``: you should know what this is. + - ``"value"``: the enumeration member/item's value + + + - ``"lvgl_type"``: This is a base type. There or no other types beneith this. + This tells you that the type is an LVGL data type. + + Available JSON fields: + - ``"name"``: The name of the type. + - ``"quals"``: array of qualifiers, IE "const + + + - ``"struct"``: Describes a structure + + Available JSON fields: + - ``"name"``: The name of the structure. + - ``"type"``: This contains the primitive type information for the structure. + - ``"docstring"``: you should know what this is. + - ``"fields"``: array of ``"field"`` elements. + - ``"quals"``: array of qualifiers, IE "const" + + + - ``"union"``: Describes a union + + Available JSON fields: + - ``"name"``: The name of the union. + - ``"type"``: This contains the primitive type information for the union. + - ``"docstring"``: you should know what this is. + - ``"fields"``: array of ``"field"`` elements. + - ``"quals"``: array of qualifiers, IE "const" + + + - ``"macro"``: describes a macro. There is limited information that can be + collected about macros and in most cases a binding will need to have these + statically added to a binding. It is more for collecting the docstrings than + anything else. + + Available JSON fields: + - ``"name"``: The name of the macro. + - ``"docstring"``: you should know what this is. + + + - ``"ret_type"``: return type from a function. This is only going to be seen in the ``"type"`` + element of a ``"function"`` type. + + Available JSON fields: + - ``"type"``: This contains the type information for the field. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"docstring"``: you should know what this is. + + + - ``"function"``: Describes a function. + + Available JSON fields: + - ``"name"``: The name of the function. + - ``"type"``: This contains the type information for the return value. + - ``"docstring"``: you should know what this is. + - ``"args"``: array of ``"arg"`` json types. This describes the fuction arguments/parameters. + + + - ``"stdlib_type"``: This is a base type, meaning that there are no more + type levels beneith this. This tells us that the type is from the C stdlib. + + Available JSON fields: + - ``"name"``: The name of the type. + - ``"quals"``: array of qualifiers, IE "const + + + - ``"unknown_type"``: This should not be seen. If it is then there needs to be + an adjustment made to the script. Please open an issue and let us know if you see this type. + + Available JSON fields: + - ``"name"``: The name of the type. + - ``"quals"``: array of qualifiers, IE "const + + + - ``"pointer"``: This is a wrapper object to let you know that the type you + are dealing with is a pointer + + Available JSON fields: + - ``"type"``: This contains the type information for the pointer. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"quals"``: array of qualifiers, IE "const", may or may not be available. + + + - ``"typedef"``: type definitions. I will explain more on this below. + + Available JSON fields: + - ``"name"``: The name of the typedef. + - ``"type"``: This contains the type information for the field. Check the + ``"json_type"`` to know what type you are dealing with. + - ``"docstring"``: you should know what this is. + - ``"quals"``: array of qualifiers, IE "const" + + + +Here is an example of what the output will look like. + +.. code:: json + + { + "enums":[ + { + "name":"_lv_result_t", + "type":{ + "name":"int", + "json_type":"primitive_type" + }, + "json_type":"enum", + "docstring":"LVGL error codes. ", + "members":[ + { + "name":"LV_RESULT_INVALID", + "type":{ + "name":"_lv_result_t", + "json_type":"lvgl_type" + }, + "json_type":"enum_member", + "docstring":"", + "value":"0x0" + }, + { + "name":"LV_RESULT_OK", + "type":{ + "name":"_lv_result_t", + "json_type":"lvgl_type" + }, + "json_type":"enum_member", + "docstring":"", + "value":"0x1" + } + ] + } + ], + "functions":[ + { + "name":"lv_version_info", + "type":{ + "type":{ + "type":{ + "name":"char", + "json_type":"primitive_type", + "quals":[ + "const" + ] + }, + "json_type":"pointer", + "quals":[] + }, + "json_type":"ret_type", + "docstring":"" + }, + "json_type":"function", + "docstring":"", + "args":[ + { + "name":null, + "type":{ + "name":"void", + "json_type":"primitive_type", + "quals":[] + }, + "json_type":"arg", + "docstring":"", + "quals":[] + } + ] + } + ], + "function_pointers":[ + { + "name":"lv_tlsf_walker", + "type":{ + "type":{ + "name":"void", + "json_type":"primitive_type", + "quals":[] + }, + "json_type":"ret_type", + "docstring":"" + }, + "json_type":"function_pointer", + "docstring":"", + "args":[ + { + "name":"ptr", + "type":{ + "type":{ + "name":"void", + "json_type":"primitive_type", + "quals":[] + }, + "json_type":"pointer", + "quals":[] + }, + "json_type":"arg", + "docstring":"" + }, + { + "name":"size", + "type":{ + "name":"size_t", + "json_type":"stdlib_type", + "quals":[] + }, + "json_type":"arg", + "docstring":"" + }, + { + "name":"used", + "type":{ + "name":"int", + "json_type":"primitive_type", + "quals":[] + }, + "json_type":"arg", + "docstring":"" + }, + { + "name":"user", + "type":{ + "type":{ + "name":"void", + "json_type":"primitive_type", + "quals":[] + }, + "json_type":"pointer", + "quals":[] + }, + "json_type":"arg", + "docstring":"" + } + ], + "quals":[] + } + ], + "structures":[ + { + "name":"_lv_gradient_cache_t", + "type":{ + "name":"struct", + "json_type":"primitive_type" + }, + "json_type":"struct", + "docstring":null, + "fields":[ + { + "name":"color_map", + "type":{ + "type":{ + "name":"lv_color_t", + "json_type":"lvgl_type", + "quals":[] + }, + "json_type":"pointer", + "quals":[] + }, + "json_type":"field", + "bitsize":null, + "docstring":"" + }, + { + "name":"opa_map", + "type":{ + "type":{ + "name":"lv_opa_t", + "json_type":"lvgl_type", + "quals":[] + }, + "json_type":"pointer", + "quals":[] + }, + "json_type":"field", + "bitsize":null, + "docstring":"" + }, + { + "name":"size", + "type":{ + "name":"uint32_t", + "json_type":"stdlib_type", + "quals":[] + }, + "json_type":"field", + "bitsize":null, + "docstring":"" + } + ] + } + ], + "unions":[], + "variables":[ + { + "name":"lv_global", + "type":{ + "name":"lv_global_t", + "json_type":"lvgl_type", + "quals":[] + }, + "json_type":"variable", + "docstring":"", + "quals":[], + "storage":[ + "extern" + ] + } + ], + "typedefs":[ + { + "name":"lv_pool_t", + "type":{ + "type":{ + "name":"void", + "json_type":"primitive_type", + "quals":[] + }, + "json_type":"pointer" + }, + "json_type":"typedef", + "docstring":"", + "quals":[] + } + ], + "forward_decls":[ + { + "name":"lv_fragment_managed_states_t", + "type":{ + "name":"struct", + "json_type":"primitive_type" + }, + "json_type":"forward_decl", + "docstring":"", + "quals":[] + } + ], + "macros":[ + { + "name":"ZERO_MEM_SENTINEL", + "json_type":"macro", + "docstring":"" + } + ] + } diff --git a/docs/integration/bindings/index.rst b/docs/integration/bindings/index.rst index 762b9c458..9846efc34 100644 --- a/docs/integration/bindings/index.rst +++ b/docs/integration/bindings/index.rst @@ -10,3 +10,4 @@ Bindings cpp pikascript javascript + api_json diff --git a/scripts/gen_json/create_fake_lib_c.py b/scripts/gen_json/create_fake_lib_c.py new file mode 100644 index 000000000..d07dc6d7e --- /dev/null +++ b/scripts/gen_json/create_fake_lib_c.py @@ -0,0 +1,172 @@ +import os + +lib_c_files = ( + ('aio.h', ''), + ('alloca.h', ''), + ('ar.h', ''), + ('argz.h', ''), + ('assert.h', ''), + ('complex.h', ''), + ('cpio.h', ''), + ('ctype.h', ''), + ('dirent.h', ''), + ('dlfcn.h', ''), + ('emmintrin.h', ''), + ('endian.h', ''), + ('envz.h', ''), + ('errno.h', ''), + ('fastmath.h', ''), + ('fcntl.h', ''), + ('features.h', ''), + ('fenv.h', ''), + ('float.h', ''), + ('fmtmsg.h', ''), + ('fnmatch.h', ''), + ('ftw.h', ''), + ('getopt.h', ''), + ('glob.h', ''), + ('grp.h', ''), + ('iconv.h', ''), + ('ieeefp.h', ''), + ('immintrin.h', ''), + ('inttypes.h', ''), + ('iso646.h', ''), + ('langinfo.h', ''), + ('libgen.h', ''), + ('libintl.h', ''), + ('limits.h', ''), + ('locale.h', ''), + ('malloc.h', 'void *malloc(size_t size);\nvoid *realloc(void *memblock, size_t size);\nvoid free(void *memblock);'), + ('math.h', ''), + ('memory.h', 'void *memset(void *dest, int c, size_t count);\nvoid *memcpy(void *dest, const void *src, size_t count);\nint memcmp(const void *buffer1, const void *buffer2, size_t count);'), + ('monetary.h', ''), + ('mqueue.h', ''), + ('ndbm.h', ''), + ('netdb.h', ''), + ('newlib.h', ''), + ('nl_types.h', ''), + ('paths.h', ''), + ('poll.h', ''), + ('process.h', ''), + ('pthread.h', ''), + ('pwd.h', ''), + ('reent.h', ''), + ('regdef.h', ''), + ('regex.h', ''), + ('sched.h', ''), + ('search.h', ''), + ('semaphore.h', ''), + ('setjmp.h', ''), + ('signal.h', ''), + ('smmintrin.h', ''), + ('spawn.h', ''), + ('stdalign.h', ''), + ('stdarg.h', ''), + ('stdatomic.h', '/* C11 stdatomic.h defines */\n#define ATOMIC_BOOL_LOCK_FREE 0\n#define ATOMIC_CHAR_LOCK_FREE 0\n#define ATOMIC_CHAR16_T_LOCK_FREE 0\n#define ATOMIC_CHAR32_T_LOCK_FREE 0\n#define ATOMIC_WCHAR_T_LOCK_FREE 0\n#define ATOMIC_SHORT_LOCK_FREE 0\n#define ATOMIC_INT_LOCK_FREE 0\n#define ATOMIC_LONG_LOCK_FREE 0\n#define ATOMIC_LLONG_LOCK_FREE 0\n#define ATOMIC_POINTER_LOCK_FREE 0\n#define ATOMIC_VAR_INIT(value) (value)\n#define ATOMIC_FLAG_INIT { 0 }\n\n\n/* C11 stdatomic.h types */\ntypedef _Atomic(_Bool) atomic_bool;\ntypedef _Atomic(char) atomic_char;\ntypedef _Atomic(signed char) atomic_schar;\ntypedef _Atomic(unsigned char) atomic_uchar;\ntypedef _Atomic(short) atomic_short;\ntypedef _Atomic(unsigned short) atomic_ushort;\ntypedef _Atomic(int) atomic_int;\ntypedef _Atomic(unsigned int) atomic_uint;\ntypedef _Atomic(long) atomic_long;\ntypedef _Atomic(unsigned long) atomic_ulong;\ntypedef _Atomic(long long) atomic_llong;\ntypedef _Atomic(unsigned long long) atomic_ullong;\ntypedef _Atomic(uint_least16_t) atomic_char16_t;\ntypedef _Atomic(uint_least32_t) atomic_char32_t;\ntypedef _Atomic(wchar_t) atomic_wchar_t;\ntypedef _Atomic(int_least8_t) atomic_int_least8_t;\ntypedef _Atomic(uint_least8_t) atomic_uint_least8_t;\ntypedef _Atomic(int_least16_t) atomic_int_least16_t;\ntypedef _Atomic(uint_least16_t) atomic_uint_least16_t;\ntypedef _Atomic(int_least32_t) atomic_int_least32_t;\ntypedef _Atomic(uint_least32_t) atomic_uint_least32_t;\ntypedef _Atomic(int_least64_t) atomic_int_least64_t;\ntypedef _Atomic(uint_least64_t) atomic_uint_least64_t;\ntypedef _Atomic(int_fast8_t) atomic_int_fast8_t;\ntypedef _Atomic(uint_fast8_t) atomic_uint_fast8_t;\ntypedef _Atomic(int_fast16_t) atomic_int_fast16_t;\ntypedef _Atomic(uint_fast16_t) atomic_uint_fast16_t;\ntypedef _Atomic(int_fast32_t) atomic_int_fast32_t;\ntypedef _Atomic(uint_fast32_t) atomic_uint_fast32_t;\ntypedef _Atomic(int_fast64_t) atomic_int_fast64_t;\ntypedef _Atomic(uint_fast64_t) atomic_uint_fast64_t;\ntypedef _Atomic(intptr_t) atomic_intptr_t;\ntypedef _Atomic(uintptr_t) atomic_uintptr_t;\ntypedef _Atomic(size_t) atomic_size_t;\ntypedef _Atomic(ptrdiff_t) atomic_ptrdiff_t;\ntypedef _Atomic(intmax_t) atomic_intmax_t;\ntypedef _Atomic(uintmax_t) atomic_uintmax_t;\ntypedef struct atomic_flag { atomic_bool _Value; } atomic_flag;\n\n\ntypedef enum memory_order {\n memory_order_relaxed,\n memory_order_consume,\n memory_order_acquire,\n memory_order_release,\n memory_order_acq_rel,\n memory_order_seq_cst\n} memory_order;'), + ('stdbool.h', ''), + ('stddef.h', ''), + ('stdint.h', ''), + ('stdio.h', ''), + ('stdlib.h', ''), + ('stdnoreturn.h', ''), + ('string.h', 'size_t strlen(const char *str);\nchar *strncpy(char *strDest, const char *strSource, size_t count);\nchar *strcpy(char *strDestination, const char *strSource);'), + ('strings.h', ''), + ('stropts.h', ''), + ('syslog.h', ''), + ('tar.h', ''), + ('termios.h', ''), + ('tgmath.h', ''), + ('threads.h', ''), + ('time.h', ''), + ('trace.h', ''), + ('ulimit.h', ''), + ('unctrl.h', ''), + ('unistd.h', ''), + ('utime.h', ''), + ('utmp.h', ''), + ('utmpx.h', ''), + ('vadefs.h', 'typedef unsigned int uintptr_t;\ntypedef char* va_list;'), + ('vcruntime.h', ''), + ('wchar.h', ''), + ('wctype.h', ''), + ('wordexp.h', ''), + ('zlib.h', 'typedef int uInt;\ntypedef int uLong;\n#if !defined(__MACTYPES__)\ntypedef int Byte;\n#endif\n\ntypedef int Bytef;\ntypedef int charf;\ntypedef int intf;\ntypedef int uIntf;\ntypedef int uLongf;\n\ntypedef int voidpc;\ntypedef int voidpf;\ntypedef int voidp;\n\n#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC)\ntypedef int Z_U4;\n#endif\n\ntypedef int z_crc_t;\ntypedef int z_size_t;\n\ntypedef int alloc_func;\ntypedef int free_func;'), + ('_ansi.h', ''), + ('_fake_defines.h', '#define\tNULL\t0\n#define\tBUFSIZ\t\t1024\n#define\tFOPEN_MAX\t20\n#define\tFILENAME_MAX\t1024\n\n#ifndef SEEK_SET\n#define\tSEEK_SET\t0\t/* set file offset to offset */\n#endif\n#ifndef SEEK_CUR\n#define\tSEEK_CUR\t1\t/* set file offset to current plus offset */\n#endif\n#ifndef SEEK_END\n#define\tSEEK_END\t2\t/* set file offset to EOF plus offset */\n#endif\n\n#define __LITTLE_ENDIAN 1234\n#define LITTLE_ENDIAN __LITTLE_ENDIAN\n#define __BIG_ENDIAN 4321\n#define BIG_ENDIAN __BIG_ENDIAN\n#define __BYTE_ORDER __LITTLE_ENDIAN\n#define BYTE_ORDER __BYTE_ORDER\n\n#define EXIT_FAILURE 1\n#define EXIT_SUCCESS 0\n\n#define SCHAR_MIN -128\n#define SCHAR_MAX 127\n#define CHAR_MIN -128\n#define CHAR_MAX 127\n#define UCHAR_MAX 255\n#define SHRT_MIN -32768\n#define SHRT_MAX 32767\n#define USHRT_MAX 65535\n#define INT_MIN -2147483648\n#define INT_MAX 2147483647\n#define UINT_MAX 4294967295U\n#define LONG_MIN -9223372036854775808L\n#define LONG_MAX 9223372036854775807L\n#define ULONG_MAX 18446744073709551615UL\n#define RAND_MAX 32767\n\n/* C99 inttypes.h defines */\n#define PRId8 "d"\n#define PRIi8 "i"\n#define PRIo8 "o"\n#define PRIu8 "u"\n#define PRIx8 "x"\n#define PRIX8 "X"\n#define PRId16 "d"\n#define PRIi16 "i"\n#define PRIo16 "o"\n#define PRIu16 "u"\n#define PRIx16 "x"\n#define PRIX16 "X"\n#define PRId32 "d"\n#define PRIi32 "i"\n#define PRIo32 "o"\n#define PRIu32 "u"\n#define PRIx32 "x"\n#define PRIX32 "X"\n#define PRId64 "d"\n#define PRIi64 "i"\n#define PRIo64 "o"\n#define PRIu64 "u"\n#define PRIx64 "x"\n#define PRIX64 "X"\n#define PRIdLEAST8 "d"\n#define PRIiLEAST8 "i"\n#define PRIoLEAST8 "o"\n#define PRIuLEAST8 "u"\n#define PRIxLEAST8 "x"\n#define PRIXLEAST8 "X"\n#define PRIdLEAST16 "d"\n#define PRIiLEAST16 "i"\n#define PRIoLEAST16 "o"\n#define PRIuLEAST16 "u"\n#define PRIxLEAST16 "x"\n#define PRIXLEAST16 "X"\n#define PRIdLEAST32 "d"\n#define PRIiLEAST32 "i"\n#define PRIoLEAST32 "o"\n#define PRIuLEAST32 "u"\n#define PRIxLEAST32 "x"\n#define PRIXLEAST32 "X"\n#define PRIdLEAST64 "d"\n#define PRIiLEAST64 "i"\n#define PRIoLEAST64 "o"\n#define PRIuLEAST64 "u"\n#define PRIxLEAST64 "x"\n#define PRIXLEAST64 "X"\n#define PRIdFAST8 "d"\n#define PRIiFAST8 "i"\n#define PRIoFAST8 "o"\n#define PRIuFAST8 "u"\n#define PRIxFAST8 "x"\n#define PRIXFAST8 "X"\n#define PRIdFAST16 "d"\n#define PRIiFAST16 "i"\n#define PRIoFAST16 "o"\n#define PRIuFAST16 "u"\n#define PRIxFAST16 "x"\n#define PRIXFAST16 "X"\n#define PRIdFAST32 "d"\n#define PRIiFAST32 "i"\n#define PRIoFAST32 "o"\n#define PRIuFAST32 "u"\n#define PRIxFAST32 "x"\n#define PRIXFAST32 "X"\n#define PRIdFAST64 "d"\n#define PRIiFAST64 "i"\n#define PRIoFAST64 "o"\n#define PRIuFAST64 "u"\n#define PRIxFAST64 "x"\n#define PRIXFAST64 "X"\n#define PRIdPTR "d"\n#define PRIiPTR "i"\n#define PRIoPTR "o"\n#define PRIuPTR "u"\n#define PRIxPTR "x"\n#define PRIXPTR "X"\n#define PRIdMAX "d"\n#define PRIiMAX "i"\n#define PRIoMAX "o"\n#define PRIuMAX "u"\n#define PRIxMAX "x"\n#define PRIXMAX "X"\n#define SCNd8 "d"\n#define SCNi8 "i"\n#define SCNo8 "o"\n#define SCNu8 "u"\n#define SCNx8 "x"\n#define SCNd16 "d"\n#define SCNi16 "i"\n#define SCNo16 "o"\n#define SCNu16 "u"\n#define SCNx16 "x"\n#define SCNd32 "d"\n#define SCNi32 "i"\n#define SCNo32 "o"\n#define SCNu32 "u"\n#define SCNx32 "x"\n#define SCNd64 "d"\n#define SCNi64 "i"\n#define SCNo64 "o"\n#define SCNu64 "u"\n#define SCNx64 "x"\n#define SCNdLEAST8 "d"\n#define SCNiLEAST8 "i"\n#define SCNoLEAST8 "o"\n#define SCNuLEAST8 "u"\n#define SCNxLEAST8 "x"\n#define SCNdLEAST16 "d"\n#define SCNiLEAST16 "i"\n#define SCNoLEAST16 "o"\n#define SCNuLEAST16 "u"\n#define SCNxLEAST16 "x"\n#define SCNdLEAST32 "d"\n#define SCNiLEAST32 "i"\n#define SCNoLEAST32 "o"\n#define SCNuLEAST32 "u"\n#define SCNxLEAST32 "x"\n#define SCNdLEAST64 "d"\n#define SCNiLEAST64 "i"\n#define SCNoLEAST64 "o"\n#define SCNuLEAST64 "u"\n#define SCNxLEAST64 "x"\n#define SCNdFAST8 "d"\n#define SCNiFAST8 "i"\n#define SCNoFAST8 "o"\n#define SCNuFAST8 "u"\n#define SCNxFAST8 "x"\n#define SCNdFAST16 "d"\n#define SCNiFAST16 "i"\n#define SCNoFAST16 "o"\n#define SCNuFAST16 "u"\n#define SCNxFAST16 "x"\n#define SCNdFAST32 "d"\n#define SCNiFAST32 "i"\n#define SCNoFAST32 "o"\n#define SCNuFAST32 "u"\n#define SCNxFAST32 "x"\n#define SCNdFAST64 "d"\n#define SCNiFAST64 "i"\n#define SCNoFAST64 "o"\n#define SCNuFAST64 "u"\n#define SCNxFAST64 "x"\n#define SCNdPTR "d"\n#define SCNiPTR "i"\n#define SCNoPTR "o"\n#define SCNuPTR "u"\n#define SCNxPTR "x"\n#define SCNdMAX "d"\n#define SCNiMAX "i"\n#define SCNoMAX "o"\n#define SCNuMAX "u"\n#define SCNxMAX "x"\n\n/* C99 stdbool.h defines */\n#define __bool_true_false_are_defined 1\n#define false 0\n#define true 1\n\n/* va_arg macros and type*/\n#define va_start(_ap, _type) __builtin_va_start((_ap))\n#define va_arg(_ap, _type) __builtin_va_arg((_ap))\n#define va_end(_list)\n\n/* Vectors */\n#define __m128 int\n#define __m128_u int\n#define __m128d int\n#define __m128d_u int\n#define __m128i int\n#define __m128i_u int\n#define __m256 int\n#define __m256_u int\n#define __m256d int\n#define __m256d_u int\n#define __m256i int\n#define __m256i_u int\n#define __m512 int\n#define __m512_u int\n#define __m512d int\n#define __m512d_u int\n#define __m512i int\n#define __m512i_u int\n\n/* C11 stdnoreturn.h defines */\n#define __noreturn_is_defined 1\n#define noreturn _Noreturn\n\n/* C11 threads.h defines */\n#define thread_local _Thread_local\n\n/* C11 assert.h defines */\n#define static_assert _Static_assert\n\n#define kill_dependency(y) (y)\n\n/* C11 stdalign.h defines */\n#define alignas _Alignas\n#define alignof _Alignof\n#define __alignas_is_defined 1\n#define __alignof_is_defined 1'), + ('_fake_typedefs.h', 'typedef int size_t;\ntypedef int __builtin_va_list;\ntypedef int __gnuc_va_list;\ntypedef int va_list;\ntypedef int __int8_t;\ntypedef int __uint8_t;\ntypedef int __int16_t;\ntypedef int __uint16_t;\ntypedef int __int_least16_t;\ntypedef int __uint_least16_t;\ntypedef int __int32_t;\ntypedef int __uint32_t;\ntypedef int __int64_t;\ntypedef int __uint64_t;\ntypedef int __int_least32_t;\ntypedef int __uint_least32_t;\ntypedef int __s8;\ntypedef int __u8;\ntypedef int __s16;\ntypedef int __u16;\ntypedef int __s32;\ntypedef int __u32;\ntypedef int __s64;\ntypedef int __u64;\ntypedef int _LOCK_T;\ntypedef int _LOCK_RECURSIVE_T;\ntypedef int _off_t;\ntypedef int __dev_t;\ntypedef int __uid_t;\ntypedef int __gid_t;\ntypedef int _off64_t;\ntypedef int _fpos_t;\ntypedef int _ssize_t;\ntypedef int wint_t;\ntypedef int _mbstate_t;\ntypedef int _flock_t;\ntypedef int _iconv_t;\ntypedef int __ULong;\ntypedef int __FILE;\ntypedef int ptrdiff_t;\ntypedef int wchar_t;\ntypedef int char16_t;\ntypedef int char32_t;\ntypedef int __off_t;\ntypedef int __pid_t;\ntypedef int __loff_t;\ntypedef int u_char;\ntypedef int u_short;\ntypedef int u_int;\ntypedef int u_long;\ntypedef int ushort;\ntypedef int uint;\ntypedef int clock_t;\ntypedef int time_t;\ntypedef int daddr_t;\ntypedef int caddr_t;\ntypedef int ino_t;\ntypedef int off_t;\ntypedef int dev_t;\ntypedef int uid_t;\ntypedef int gid_t;\ntypedef int pid_t;\ntypedef int key_t;\ntypedef int ssize_t;\ntypedef int mode_t;\ntypedef int nlink_t;\ntypedef int fd_mask;\ntypedef int _types_fd_set;\ntypedef int clockid_t;\ntypedef int timer_t;\ntypedef int useconds_t;\ntypedef int suseconds_t;\ntypedef int FILE;\ntypedef int fpos_t;\ntypedef int cookie_read_function_t;\ntypedef int cookie_write_function_t;\ntypedef int cookie_seek_function_t;\ntypedef int cookie_close_function_t;\ntypedef int cookie_io_functions_t;\ntypedef int div_t;\ntypedef int ldiv_t;\ntypedef int lldiv_t;\ntypedef int sigset_t;\ntypedef int __sigset_t;\ntypedef int _sig_func_ptr;\ntypedef int sig_atomic_t;\ntypedef int __tzrule_type;\ntypedef int __tzinfo_type;\ntypedef int mbstate_t;\ntypedef int sem_t;\ntypedef int pthread_t;\ntypedef int pthread_attr_t;\ntypedef int pthread_mutex_t;\ntypedef int pthread_mutexattr_t;\ntypedef int pthread_cond_t;\ntypedef int pthread_condattr_t;\ntypedef int pthread_key_t;\ntypedef int pthread_once_t;\ntypedef int pthread_rwlock_t;\ntypedef int pthread_rwlockattr_t;\ntypedef int pthread_spinlock_t;\ntypedef int pthread_barrier_t;\ntypedef int pthread_barrierattr_t;\ntypedef int jmp_buf;\ntypedef int rlim_t;\ntypedef int sa_family_t;\ntypedef int sigjmp_buf;\ntypedef int stack_t;\ntypedef int siginfo_t;\ntypedef int z_stream;\n\n/* C99 exact-width integer types */\ntypedef int int8_t;\ntypedef int uint8_t;\ntypedef int int16_t;\ntypedef int uint16_t;\ntypedef int int32_t;\ntypedef int uint32_t;\ntypedef int int64_t;\ntypedef int uint64_t;\n\n/* C99 minimum-width integer types */\ntypedef int int_least8_t;\ntypedef int uint_least8_t;\ntypedef int int_least16_t;\ntypedef int uint_least16_t;\ntypedef int int_least32_t;\ntypedef int uint_least32_t;\ntypedef int int_least64_t;\ntypedef int uint_least64_t;\n\n/* C99 fastest minimum-width integer types */\ntypedef int int_fast8_t;\ntypedef int uint_fast8_t;\ntypedef int int_fast16_t;\ntypedef int uint_fast16_t;\ntypedef int int_fast32_t;\ntypedef int uint_fast32_t;\ntypedef int int_fast64_t;\ntypedef int uint_fast64_t;\n\n/* C99 integer types capable of holding object pointers */\ntypedef int intptr_t;\ntypedef int uintptr_t;\n\n/* C99 greatest-width integer types */\ntypedef int intmax_t;\ntypedef int uintmax_t;\n\n/* C99 stdbool.h bool type. _Bool is built-in in C99 */\ntypedef _Bool bool;\n\n/* Mir typedefs */\ntypedef void* MirEGLNativeWindowType;\ntypedef void* MirEGLNativeDisplayType;\ntypedef struct MirConnection MirConnection;\ntypedef struct MirSurface MirSurface;\ntypedef struct MirSurfaceSpec MirSurfaceSpec;\ntypedef struct MirScreencast MirScreencast;\ntypedef struct MirPromptSession MirPromptSession;\ntypedef struct MirBufferStream MirBufferStream;\ntypedef struct MirPersistentId MirPersistentId;\ntypedef struct MirBlob MirBlob;\ntypedef struct MirDisplayConfig MirDisplayConfig;\n\n/* xcb typedefs */\ntypedef struct xcb_connection_t xcb_connection_t;\ntypedef uint32_t xcb_window_t;\ntypedef uint32_t xcb_visualid_t;'), + ('_syslist.h', ''), + ('arpa/inet.h', ''), + ('asm-generic/int-ll64.h', ''), + ('linux/socket.h', ''), + ('linux/version.h', ''), + ('mir_toolkit/client_types.h', ''), + ('net/if.h', ''), + ('netinet/in.h', ''), + ('netinet/tcp.h', ''), + ('openssl/err.h', ''), + ('openssl/evp.h', ''), + ('openssl/hmac.h', ''), + ('openssl/ssl.h', ''), + ('openssl/x509v3.h', ''), + ('sys/ioctl.h', ''), + ('sys/ipc.h', ''), + ('sys/mman.h', ''), + ('sys/msg.h', ''), + ('sys/poll.h', ''), + ('sys/resource.h', ''), + ('sys/select.h', ''), + ('sys/sem.h', ''), + ('sys/shm.h', ''), + ('sys/socket.h', ''), + ('sys/stat.h', ''), + ('sys/statvfs.h', ''), + ('sys/sysctl.h', ''), + ('sys/time.h', ''), + ('sys/times.h', ''), + ('sys/types.h', ''), + ('sys/uio.h', ''), + ('sys/un.h', ''), + ('sys/utsname.h', ''), + ('sys/wait.h', ''), + ('X11/Intrinsic.h', '#include "_X11_fake_defines.h"\n#include "_X11_fake_typedefs.h"'), + ('X11/Xlib.h', '#include "_X11_fake_defines.h"\n#include "_X11_fake_typedefs.h"'), + ('X11/_X11_fake_defines.h', '#define Atom CARD32\n#define Bool int\n#define KeySym CARD32\n#define Pixmap CARD32\n#define Time CARD32\n#define _XFUNCPROTOBEGIN\n#define _XFUNCPROTOEND\n#define _Xconst const\n\n#define _X_RESTRICT_KYWD\n#define Cardinal unsigned int\n#define Boolean int'), + ('X11/_X11_fake_typedefs.h', 'typedef char* XPointer;\ntypedef unsigned char KeyCode;\ntypedef unsigned int CARD32;\ntypedef unsigned long VisualID;\ntypedef unsigned long XIMResetState;\ntypedef unsigned long XID;\ntypedef XID Window;\ntypedef XID Colormap;\ntypedef XID Cursor;\ntypedef XID Drawable;\ntypedef void* XtPointer;\ntypedef XtPointer XtRequestId;\ntypedef struct Display Display;\ntypedef struct Screen Screen;\ntypedef struct Status Status;\ntypedef struct Visual Visual;\ntypedef struct Widget *Widget;\ntypedef struct XColor XColor;\ntypedef struct XClassHint XClassHint;\ntypedef struct XEvent XEvent;\ntypedef struct XFontStruct XFontStruct;\ntypedef struct XGCValues XGCValues;\ntypedef struct XKeyEvent XKeyEvent;\ntypedef struct XKeyPressedEvent XKeyPressedEvent;\ntypedef struct XPoint XPoint;\ntypedef struct XRectangle XRectangle;\ntypedef struct XSelectionRequestEvent XSelectionRequestEvent;\ntypedef struct XWindowChanges XWindowChanges;\ntypedef struct _XGC _XCG;\ntypedef struct _XGC *GC;\ntypedef struct _XIC *XIC;\ntypedef struct _XIM *XIM;\ntypedef struct _XImage XImage;'), + ('xcb/xcb.h', ''), +) + + +contents = '''\ +#include "_fake_defines.h" +#include "_fake_typedefs.h" +''' + +define_template = ''' +#ifndef {define_name} +#define {define_name} +{file_data} +#endif +''' + + +def run(temp_dir): + fake_libc_path = os.path.join(temp_dir, 'fake_libc_include') + os.mkdir(fake_libc_path) + + for file, file_data in lib_c_files: + head, tail = os.path.split(file) + file = os.path.join(fake_libc_path, file) + file_name = os.path.split(file)[-1] + define_name = f'__{file_name.replace(".", "_").upper()}__' + + if None not in (head, tail): + pth = os.path.join(fake_libc_path, head) + if not os.path.exists(pth): + os.mkdir(pth) + + file_data = contents + file_data + + with open(file, 'w') as f: + f.write(define_template.format(define_name=define_name, file_data=file_data)) + + return fake_libc_path diff --git a/scripts/gen_json/gen_json.py b/scripts/gen_json/gen_json.py new file mode 100644 index 000000000..8a0222cc8 --- /dev/null +++ b/scripts/gen_json/gen_json.py @@ -0,0 +1,378 @@ +import os +import sys +import argparse +import shutil +import tempfile +import json +import subprocess +import threading + +base_path = os.path.abspath(os.path.dirname(__file__)) +sys.path.insert(0, base_path) + +project_path = os.path.abspath(os.path.join(base_path, '..', '..')) +docs_path = os.path.join(project_path, 'docs') +sys.path.insert(0, docs_path) + +import create_fake_lib_c # NOQA +import pycparser_monkeypatch # NOQA +import pycparser # NOQA + +DEVELOP = False + + +class STDOut: + def __init__(self): + self._stdout = sys.stdout + sys.__stdout__ = self + sys.stdout = self + + def write(self, data): + pass + + def __getattr__(self, item): + if item in self.__dict__: + return self.__dict__[item] + + return getattr(self._stdout, item) + + def reset(self): + sys.stdout = self._stdout + + +temp_directory = tempfile.mkdtemp(suffix='.lvgl_json') + + +def run(output_path, lvgl_config_path, output_to_stdout, target_header, filter_private, *compiler_args): + # stdout = STDOut() + + pycparser_monkeypatch.FILTER_PRIVATE = filter_private + + # The thread is to provide an indication that things are being processed. + # There are long periods where nothing gets output to the screen and this + # is to let the user know that it is still working. + if not output_to_stdout: + event = threading.Event() + + def _do(): + while not event.is_set(): + event.wait(1) + sys.stdout.write('.') + sys.stdout.flush() + + print() + + t = threading.Thread(target=_do) + t.daemon = True + t.start() + + lvgl_path = project_path + lvgl_src_path = os.path.join(lvgl_path, 'src') + temp_lvgl = os.path.join(temp_directory, 'lvgl') + target_header_base_name = ( + os.path.splitext(os.path.split(target_header)[-1])[0] + ) + + try: + os.mkdir(temp_lvgl) + shutil.copytree(lvgl_src_path, os.path.join(temp_lvgl, 'src')) + shutil.copyfile(os.path.join(lvgl_path, 'lvgl.h'), os.path.join(temp_lvgl, 'lvgl.h')) + + pp_file = os.path.join(temp_directory, target_header_base_name + '.pp') + + if lvgl_config_path is None: + lvgl_config_path = os.path.join(lvgl_path, 'lv_conf_template.h') + + with open(lvgl_config_path, 'rb') as f: + data = f.read().decode('utf-8').split('\n') + + for i, line in enumerate(data): + if line.startswith('#if 0'): + data[i] = '#if 1' + else: + for item in ( + 'LV_USE_LOG', + 'LV_USE_OBJ_ID', + 'LV_USE_OBJ_ID_BUILTIN', + 'LV_USE_FLOAT', + 'LV_USE_BIDI', + 'LV_USE_LODEPNG', + 'LV_USE_LIBPNG', + 'LV_USE_BMP', + 'LV_USE_TJPGD', + 'LV_USE_LIBJPEG_TURBO', + 'LV_USE_GIF', + 'LV_BIN_DECODER_RAM_LOAD', + 'LV_USE_RLE', + 'LV_USE_QRCODE', + 'LV_USE_BARCODE', + 'LV_USE_TINY_TTF', + 'LV_USE_GRIDNAV', + 'LV_USE_FRAGMENT', + 'LV_USE_IMGFONT', + 'LV_USE_SNAPSHOT', + 'LV_USE_FREETYPE' + ): + if line.startswith(f'#define {item} '): + data[i] = f'#define {item} 1' + break + + with open(os.path.join(temp_directory, 'lv_conf.h'), 'wb') as f: + f.write('\n'.join(data).encode('utf-8')) + else: + src = lvgl_config_path + dst = os.path.join(temp_directory, 'lv_conf.h') + shutil.copyfile(src, dst) + + include_dirs = [temp_directory, project_path] + + if sys.platform.startswith('win'): + import get_sdl2 + + try: + import pyMSVC # NOQA + except ImportError: + sys.stderr.write( + '\nThe pyMSVC library is missing, ' + 'please run "pip install pyMSVC" to install it.\n' + ) + sys.stderr.flush() + sys.exit(-500) + + env = pyMSVC.setup_environment() # NOQA + cpp_cmd = ['cl', '/std:c11', '/nologo', '/P'] + output_pp = f'/Fi"{pp_file}"' + sdl2_include, _ = get_sdl2.get_sdl2(temp_directory) + include_dirs.append(sdl2_include) + include_path_env_key = 'INCLUDE' + + elif sys.platform.startswith('darwin'): + include_path_env_key = 'C_INCLUDE_PATH' + cpp_cmd = [ + 'clang', '-std=c11', '-E', '-DINT32_MIN=0x80000000', + ] + output_pp = f' >> "{pp_file}"' + else: + include_path_env_key = 'C_INCLUDE_PATH' + cpp_cmd = [ + 'gcc', '-std=c11', '-E', '-Wno-incompatible-pointer-types', + ] + output_pp = f' >> "{pp_file}"' + + fake_libc_path = create_fake_lib_c.run(temp_directory) + + if include_path_env_key not in os.environ: + os.environ[include_path_env_key] = '' + + os.environ[include_path_env_key] = ( + f'{fake_libc_path}{os.pathsep}{os.environ[include_path_env_key]}' + ) + + if 'PATH' not in os.environ: + os.environ['PATH'] = '' + + os.environ['PATH'] = ( + f'{fake_libc_path}{os.pathsep}{os.environ["PATH"]}' + ) + + cpp_cmd.extend(compiler_args) + cpp_cmd.extend([ + '-DLV_LVGL_H_INCLUDE_SIMPLE', + '-DLV_CONF_INCLUDE_SIMPLE', + '-DLV_USE_DEV_VERSION' + ]) + + cpp_cmd.extend(['-DPYCPARSER', f'"-I{fake_libc_path}"']) + cpp_cmd.extend([f'"-I{item}"' for item in include_dirs]) + cpp_cmd.append(f'"{target_header}"') + + if sys.platform.startswith('win'): + cpp_cmd.insert(len(cpp_cmd) - 2, output_pp) + else: + cpp_cmd.append(output_pp) + + cpp_cmd = ' '.join(cpp_cmd) + + p = subprocess.Popen( + cpp_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=os.environ, + shell=True + ) + out, err = p.communicate() + exit_code = p.returncode + + if not os.path.exists(pp_file): + sys.stdout.write(out.decode('utf-8').strip() + '\n') + sys.stdout.write('EXIT CODE: ' + str(exit_code) + '\n') + sys.stderr.write(err.decode('utf-8').strip() + '\n') + sys.stdout.flush() + sys.stderr.flush() + + raise RuntimeError('Unknown Failure') + + with open(pp_file, 'r') as f: + pp_data = f.read() + + cparser = pycparser.CParser() + ast = cparser.parse(pp_data, target_header) + + ast.setup_docs(temp_directory) + + if not output_to_stdout and output_path is None: + # stdout.reset() + + if not DEVELOP: + shutil.rmtree(temp_directory) + + return ast + + elif output_to_stdout: + # stdout.reset() + print(json.dumps(ast.to_dict(), indent=4)) + else: + if not os.path.exists(output_path): + os.makedirs(output_path) + + output_path = os.path.join(output_path, target_header_base_name + '.json') + + with open(output_path, 'w') as f: + f.write(json.dumps(ast.to_dict(), indent=4)) + + # stdout.reset() + + if not output_to_stdout: + event.set() # NOQA + t.join() # NOQA + except Exception as err: + if not output_to_stdout: + event.set() # NOQA + t.join() # NOQA + + print() + try: + print(cpp_cmd) # NOQA + print() + except: # NOQA + pass + + for key, value in os.environ.items(): + print(key + ':', value) + + print() + import traceback + + traceback.print_exc() + print() + + exceptions = [ + ArithmeticError, + AssertionError, + AttributeError, + EOFError, + FloatingPointError, + GeneratorExit, + ImportError, + IndentationError, + IndexError, + KeyError, + KeyboardInterrupt, + LookupError, + MemoryError, + NameError, + NotImplementedError, + OverflowError, + ReferenceError, + RuntimeError, + StopIteration, + SyntaxError, + TabError, + SystemExit, + TypeError, + UnboundLocalError, + UnicodeError, + UnicodeEncodeError, + UnicodeDecodeError, + UnicodeTranslateError, + ValueError, + ZeroDivisionError, + SystemError + ] + + if isinstance(err, OSError): + error = err.errno + else: + if type(err) in exceptions: + error = ~exceptions.index(type(err)) + else: + error = -100 + else: + error = 0 + + if DEVELOP: + print('temporary file path:', temp_directory) + else: + shutil.rmtree(temp_directory) + + sys.exit(error) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser('-') + parser.add_argument( + '--output-path', + dest='output_path', + help=( + 'output directory for JSON file. If one is not ' + 'supplied then it will be output stdout' + ), + action='store', + default=None + ) + parser.add_argument( + '--lvgl-config', + dest='lv_conf', + help=( + 'path to lv_conf.h (including file name), if this is not set then ' + 'a config file will be generated that has everything turned on.' + ), + action='store', + default=None + ) + parser.add_argument( + '--develop', + dest='develop', + help='this option leaves the temporary folder in place.', + action='store_true', + ) + parser.add_argument( + "--target-header", + dest="target_header", + help=( + "path to a custom header file. When using this to supply a custom" + "header file you MUST insure that any LVGL includes are done so " + "they are relitive to the LVGL repository root folder.\n\n" + '#include "src/lvgl_private.h"\n\n' + "If you have includes to header files that are not LVGL then you " + "will need to add the include locations for those header files " + "when running this script. It is done using the same manner that " + "is used when calling a C compiler\n\n" + "You need to provide the absolute path to the header file when " + "using this feature." + ), + action="store", + default=os.path.join(temp_directory, "lvgl.h") + ) + parser.add_argument( + '--filter-private', + dest='filter_private', + help='Internal Use', + action='store_true', + ) + + args, extra_args = parser.parse_known_args() + + DEVELOP = args.develop + + run(args.output_path, args.lv_conf, args.output_path is None, args.target_header, args.filter_private, *extra_args) diff --git a/scripts/gen_json/get_sdl2.py b/scripts/gen_json/get_sdl2.py new file mode 100644 index 000000000..ea775af7c --- /dev/null +++ b/scripts/gen_json/get_sdl2.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +import zipfile +import io +import os + + +SDL2_URL = 'https://github.com/libsdl-org/SDL/releases/download/release-2.26.5/SDL2-devel-2.26.5-VC.zip' # NOQA + + +def get_path(name: str, p: str) -> str: + for file in os.listdir(p): + file = os.path.join(p, file) + + if file.endswith(name): + return file + + if os.path.isdir(file): + if res := get_path(name, file): + return res + + +def get_sdl2(path, url=SDL2_URL): + import requests # NOQA + + stream = io.BytesIO() + + with requests.get(url, stream=True) as r: + r.raise_for_status() + + content_length = int(r.headers['Content-Length']) + chunks = 0 + # print() + # sys.stdout.write('\r' + str(chunks) + '/' + str(content_length)) + # sys.stdout.flush() + + for chunk in r.iter_content(chunk_size=1024): + stream.write(chunk) + chunks += len(chunk) + # sys.stdout.write('\r' + str(chunks) + '/' + str(content_length)) + # sys.stdout.flush() + + # print() + stream.seek(0) + zf = zipfile.ZipFile(stream) + + for z_item in zf.infolist(): + for ext in ('.h', '.dll', '.lib'): + if not z_item.filename.endswith(ext): + continue + + zf.extract(z_item, path=path) + break + + include_path = get_path('include', path) + lib_path = get_path('lib\\x64', path) + dll_path = get_path('SDL2.dll', lib_path) + + sdl_include_path = os.path.split(include_path)[0] + if not os.path.exists(os.path.join(sdl_include_path, 'SDL2')): + os.rename(include_path, os.path.join(sdl_include_path, 'SDL2')) + + zf.close() + stream.close() + + return os.path.abspath(sdl_include_path), dll_path diff --git a/scripts/gen_json/pycparser_monkeypatch.py b/scripts/gen_json/pycparser_monkeypatch.py new file mode 100644 index 000000000..7742ce34c --- /dev/null +++ b/scripts/gen_json/pycparser_monkeypatch.py @@ -0,0 +1,1694 @@ +# -*- coding: utf-8 -*- + +import sys +import os + +try: + from pycparser import c_ast # NOQA +except ImportError: + sys.stderr.write( + '\nThe pycparser library is missing, ' + 'please run "pip install pycparser" to install it.\n' + ) + sys.stderr.flush() + sys.exit(-500) + +from pycparser.c_generator import CGenerator +from collections import OrderedDict + +import doc_builder # NOQA + +generator = CGenerator() + +doc_builder.EMIT_WARNINGS = False +# doc_builder.DOXYGEN_OUTPUT = False + + +BASIC_TYPES = [ + 'float', + 'double', + 'long', + 'ulong', + 'unsigned long', + 'long double', + 'signed long double', + 'unsigned long double', + 'long long', + 'signed long long', + 'unsigned long long', + 'int', + 'uint', + 'signed int', + 'unsigned int', + 'long int', + 'signed long int', + 'unsigned long int', + 'short' + 'ushort', + 'signed short', + 'unsigned short', + 'void', + 'char', + 'uchar', + 'signed char', + 'unsigned char', + 'bool' +] + +STDLIB_TYPES = [ + 'size_t', + 'uint8_t', + 'uint16_t', + 'uint32_t', + 'uint64_t', + 'int8_t', + 'int16_t', + 'int32_t', + 'int64_t', + 'va_list', + 'uintptr_t', + 'intptr_t', +] + +enums = {} +functions = {} +structures = {} +unions = {} +typedefs = {} +macros = {} + +FILTER_PRIVATE = False + + +def filter_node(n): + if hasattr(n, 'coord') and n.coord is not None: + if 'fake_libc_include' in n.coord.file: + return True + if FILTER_PRIVATE and '_private.h' not in n.coord.file: + return True + + return False + + +class ArrayDecl(c_ast.ArrayDecl): + + def process(self): + self.type.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def name(self): + return None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + self.type.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if self.dim is None: + dim = None + else: + dim = generator.visit(self.dim) + + if isinstance(self.type, TypeDecl): + res = self.type.to_dict() + res['json_type'] = 'array' + res['dim'] = dim + res['quals'].extend(self.dim_quals) + return res + + res = OrderedDict([ + ('type', self.type.to_dict()), + ('json_type', 'array'), + ('dim', dim), + ('quals', self.dim_quals) + ]) + + return res + + +class Constant(c_ast.Constant): + + def process(self): + pass + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + self.type.parent = self + + def to_dict(self): + if filter_node(self): + return None + + return self.value + + +collected_types = [] + +forward_decls = {} + + +class Decl(c_ast.Decl): + + def process(self): + self.type.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + self.type.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if self.name and self.name == '_silence_gcc_warning': + return None + + if not self.name: + try: + name = self.type.name + except AttributeError: + name = None + + if name: + if name == '_silence_gcc_warning': + return None + else: + name = self.name + + if isinstance(self.parent, (Struct, Union)): + if self.bitsize: + bitsize = self.bitsize.to_dict() + else: + bitsize = None + + res = OrderedDict([ + ('name', name), + ('type', self.type.to_dict()), + ('json_type', 'field'), + ('bitsize', bitsize) + ]) + elif isinstance(self.parent, FuncDecl): + res = OrderedDict([ + ('name', name), + ('type', self.type.to_dict()), + ('json_type', 'arg'), + ]) + elif isinstance(self.type, Enum): + res = self.type.to_dict() + res['name'] = name + + elif isinstance(self.type, (FuncDef, FuncDecl)): + res = self.type.to_dict() + res['name'] = name + + else: + if isinstance(self.type, (Struct, Union)): + res = self.type.to_dict() + + if 'quals' in res: + res['quals'].extend(self.quals) + else: + res['quals'] = self.quals + + if res['json_type'] == 'forward_decl': + if res['name'] and res['name'] not in forward_decls: + forward_decls[res['name']] = res + + return None + + return res + + if self.name: + name = self.name + else: + name = self.type.name + + doc_search = get_var_docs(name) # NOQA + + if doc_search is None: + docstring = '' + else: + docstring = doc_search.description + + if ( + isinstance(self.type, PtrDecl) and + isinstance(self.type.type, FuncDecl) + ): + type_dict = self.type.type.to_dict() + type_dict['json_type'] = 'function_pointer' + + if docstring: + type_dict['docstring'] = docstring + + if 'quals' in type_dict: + type_dict['quals'].extend(self.quals) + else: + type_dict['quals'] = self.quals + + return type_dict + + res = OrderedDict([ + ('name', name), + ('type', self.type.to_dict()), + ('json_type', 'variable'), + ('docstring', docstring), + ('quals', self.quals), + ('storage', self.storage) + ]) + + return res + + +class EllipsisParam(c_ast.EllipsisParam): + + def process(self): + pass + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + def to_dict(self): + if filter_node(self): + return None + + res = OrderedDict([ + ('name', '...'), + ('type', OrderedDict([ + ('name', 'ellipsis'), + ('json_type', 'special_type') + ])), + ('json_type', 'arg'), + ('docstring', None) + ]) + + return res + + @property + def name(self): + return '...' + + +member_namespace = {} + + +class Enum(c_ast.Enum): + + def process(self): + name = self.name + parent = self.parent + + while parent is not None and name is None: + try: + name = parent.name + except AttributeError: + pass + + parent = parent.parent + + if name and name not in collected_types: + collected_types.append(name) + + for item in (self.values or []): + item.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + if self.values: + self.values.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if self.name: + doc_search = get_enum_docs(self.name) # NOQA + + if doc_search is None: + docstring = '' + else: + docstring = doc_search.description + else: + docstring = '' + + members = [] + value_num = 0 + + for item in (self.values or []): + item_dict = item.to_dict() + try: + code = generator.visit(item.value) + + try: + value = eval("bytearray([b'" + code + "'])[0]") + except: # NOQA + index = code.find('L') + + while index >= 1: + if code[index - 1].isdigit(): + code = list(code) + code.pop(index) + code = ''.join(code) + + index = code.find('L', index + 1) + + value = eval(code, member_namespace) + + member_namespace[item_dict['name']] = value + + value_num = value + 1 + + code = f'0x{hex(value)[2:].upper()}' + value = code + except: # NOQA + value = f'0x{hex(value_num)[2:].upper()}' + member_namespace[item_dict['name']] = value_num + value_num += 1 + + item_dict['value'] = value + members.append(item_dict) + + hex_len = len(hex(value_num)[2:]) + for member in members: + member['value'] = ( + f'0x{hex(int(member["value"], 16))[2:].zfill(hex_len).upper()}' + ) + + res = OrderedDict([ + ('name', self.name), + ('type', OrderedDict([ + ('name', 'int'), + ('json_type', 'primitive_type') + ])), + ('json_type', 'enum'), + ('docstring', docstring), + ('members', members) + ]) + + return res + + +class Enumerator(c_ast.Enumerator): + + def process(self): + pass + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + def to_dict(self): + if filter_node(self): + return None + + parent_name = self.parent.name + parent = self.parent + + while parent is not None and parent_name is None: + try: + parent_name = parent.name + except AttributeError: + continue + + parent = parent.parent + + if parent_name and parent_name.startswith('_'): + if parent_name[1:] in collected_types: + type_ = OrderedDict([ + ('name', parent_name[1:]), + ('json_type', 'lvgl_type') + ]) + elif parent_name in collected_types: + type_ = OrderedDict([ + ('name', parent_name), + ('json_type', 'lvgl_type') + ]) + else: + type_ = OrderedDict([ + ('name', 'int'), + ('json_type', 'primitive_type') + ]) + + elif parent_name and parent_name in collected_types: + type_ = OrderedDict([ + ('name', parent_name), + ('json_type', 'lvgl_type') + ]) + else: + type_ = OrderedDict([ + ('name', 'int'), + ('json_type', 'primitive_type') + ]) + + doc_search = get_enum_item_docs(self.name) # NOQA + + if doc_search is None: + docstring = '' + else: + docstring = doc_search.description + + res = OrderedDict([ + ('name', self.name), + ('type', type_), + ('json_type', 'enum_member'), + ('docstring', docstring) + ]) + + return res + + +class EnumeratorList(c_ast.EnumeratorList): + + def process(self, indent): + pass + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + for item in (self.enumerators or []): + item.parent = value + + def to_dict(self): + if filter_node(self): + return None + + pass + + +def is_type(obj, type_): + if isinstance(obj, list): + return type_ == 'typedef' + + return obj['json_type'] == type_ + + +found_types = {} + +get_enum_item_docs = None +get_enum_docs = None +get_func_docs = None +get_var_docs = None +get_union_docs = None +get_struct_docs = None +get_typedef_docs = None +get_macro_docs = None +get_macros = None + + +_enums = {} +_functions = {} +_structures = {} +_unions = {} +_typedefs = {} +_variables = {} +_function_pointers = {} +_forward_decls = {} + + +class FileAST(c_ast.FileAST): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + def setup_docs(self, temp_directory): # NOQA + global get_enum_item_docs + global get_enum_docs + global get_func_docs + global get_var_docs + global get_union_docs + global get_struct_docs + global get_typedef_docs + global get_macro_docs + global get_macros + + docs = doc_builder.XMLSearch(temp_directory) + + get_enum_item_docs = docs.get_enum_item + get_enum_docs = docs.get_enum + get_func_docs = docs.get_function + get_var_docs = docs.get_variable + get_union_docs = docs.get_union + get_struct_docs = docs.get_structure + get_typedef_docs = docs.get_typedef + get_macro_docs = docs.get_macro + get_macros = docs.get_macros + + @property + def name(self): + return None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + def to_dict(self): + items = [] + + # This code block is to handle how pycparser handles forward + # declarations and combining the forward declarations with the actual + # types so any information that is contained in the type gets properly + # attached to the forward declaration + forward_struct_decls = {} + + for item in self.ext[:]: + if ( + isinstance(item, Decl) and + item.name is None and + isinstance( + item.type, + (Struct, Union) + ) and + item.type.name is not None + ): + if item.type.decls is None: + forward_struct_decls[item.type.name] = [item] + else: + if item.type.name in forward_struct_decls: + decs = forward_struct_decls[item.type.name] + if len(decs) == 2: + decl, td = decs + + if FILTER_PRIVATE: + if ( + '_private.h' not in decl.coord.file and + '_private.h' not in td.coord.file and + '_private.h' not in item.coord.file + ): + continue + + if decl.type.decls and '_private.h' in decl.coord.file: + decl.name = decl.type.name + self.ext.remove(item) + elif item.type.decls and '_private.h' in item.coord.file: + item.name = item.type.name + self.ext.remove(decl) + + self.ext.remove(td) + else: + td.type.type.decls = item.type.decls[:] + + self.ext.remove(decl) + self.ext.remove(item) + elif ( + isinstance(item, Typedef) and + isinstance(item.type, TypeDecl) and + item.name and + item.type.declname and + item.name == item.type.declname and + isinstance( + item.type.type, + (Struct, Union) + ) and + item.type.type.decls is None + ): + if item.type.type.name in forward_struct_decls: + forward_struct_decls[item.type.type.name].append(item) + ############################ + + for item in self.ext: + if filter_node(item): + continue + try: + item.parent = self + items.append(item) + except AttributeError: + pass + + enums = [] # NOQA + functions = [] # NOQA + structures = [] # NOQA + unions = [] # NOQA + typedefs = [] # NOQA + variables = [] + function_pointers = [] + forward_decl = [] + + no_enum_name_count = 1 + + for itm in items: + itm.process() + item = itm.to_dict() + + if item is None: + continue + + if is_type(item, 'typedef'): + typedefs.append(item) + _typedefs[itm.name] = item + elif is_type(item, 'function_pointer'): + function_pointers.append(item) + _function_pointers[item['name']] = item + elif is_type(item, 'function'): + functions.append(item) + _functions[item['name']] = item + elif is_type(item, 'struct'): + structures.append(item) + _structures[item['name']] = item + elif is_type(item, 'union'): + unions.append(item) + _unions[item['name']] = item + elif is_type(item, 'enum'): + enums.append(item) + + if item['name'] is None: + item['name'] = f'NO_NAME_{no_enum_name_count}' + no_enum_name_count += 1 + + _enums[item['name']] = item + elif is_type(item, 'variable'): + variables.append(item) + _variables[item['name']] = item + elif is_type(item, 'forward_decl'): + forward_decl.append(item) + _forward_decls[item['name']] = item + else: + print('UNKNOWN TYPE:') + print(item) + print(item.to_dict()) + + for tdef_name in _typedefs.keys(): + if '_' + tdef_name in _enums: + enum_dict = _enums['_' + tdef_name] + for member in enum_dict['members']: + member['type']['name'] = tdef_name + member['type']['json_type'] = 'lvgl_type' + else: + if tdef_name.endswith('_t'): + td_name = tdef_name[:-2].upper() + else: + td_name = tdef_name.upper() + + for en_name, enum_dict in _enums.items(): + if not en_name.startswith('NO_NAME_'): + continue + + member_names = [ + member['name'] + for member in enum_dict['members'] + if not member['name'].startswith('_') + ] + + if not member_names: + continue + + c_name = os.path.commonprefix(member_names) + c_name = "_".join(c_name.split("_")[:-1]) + if c_name != td_name: + continue + + for member in enum_dict['members']: + member['type']['name'] = tdef_name + member['type']['json_type'] = 'lvgl_type' + break + + for enm in enums: + if enm['name'].startswith('NO_NAME_'): + enm['name'] = None + + res = { + 'enums': enums, + 'functions': functions, + 'function_pointers': function_pointers, + 'structures': structures, + 'unions': unions, + 'variables': variables, + 'typedefs': [], + 'forward_decls': forward_decl, + 'macros': [] + } + + for typedef in typedefs: + if isinstance(typedef, list): + typedef, obj_dict = typedef + if obj_dict['json_type'] == 'struct': + res['structures'].append(obj_dict) + elif obj_dict['json_type'] == 'union': + res['unions'].append(obj_dict) + elif obj_dict['json_type'] == 'enum': + res['enums'].append(obj_dict) + + res['typedefs'].append(typedef) + + for macro in get_macros(): # NOQA + macro_type = OrderedDict([ + ('name', macro.name), + ('json_type', 'macro'), + ('docstring', macro.description), + ('params', macro.params), + ('initializer', macro.initializer) + ]) + + res['macros'].append(macro_type) + + return res + + +class FuncDecl(c_ast.FuncDecl): + + @property + def name(self): + type_ = self.type + while isinstance(type_, PtrDecl): + type_ = type_.type + + try: + name = type_.name + except AttributeError: + name = None + parent = self.parent + while parent is not None and name is None: + try: + name = parent.name + except AttributeError: + pass + + parent = parent.parent + + return name + + def process(self): + name = self.name + if name and name not in collected_types: + collected_types.append(name) + + for arg in (self.args or []): + arg.process() + + self.type.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + for arg in (self.args or []): + arg.parent = self + + self.type.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if self.name: + doc_search = get_func_docs(self.name) # NOQA + if doc_search is None: + docstring = '' + ret_docstring = '' + else: + docstring = doc_search.description + ret_docstring = doc_search.res_description + + if docstring is None: + docstring = '' + if ret_docstring is None: + ret_docstring = '' + + else: + doc_search = None + docstring = '' + ret_docstring = '' + + args = [] + + for arg in (self.args or []): + arg = arg.to_dict() + if arg['name'] and doc_search is not None: + for doc_arg in doc_search.args: + if doc_arg.name == arg['name']: + if doc_arg.description is None: + arg['docstring'] = '' + else: + arg['docstring'] = doc_arg.description + break + else: + arg['docstring'] = '' + else: + arg['docstring'] = '' + + args.append(arg) + + type_dict = OrderedDict([ + ('type', self.type.to_dict()), + ('json_type', 'ret_type'), + ('docstring', ret_docstring) + ]) + + res = OrderedDict([ + ('name', self.name), + ('type', type_dict), + ('json_type', 'function'), + ('docstring', docstring), + ('args', args) + ]) + + return res + + +class FuncDef(c_ast.FuncDef): + + @property + def name(self): + return None + + def process(self): + self.decl.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + self.decl.parent = value + + def to_dict(self): + if filter_node(self): + return None + + return self.decl.to_dict() + + +class IdentifierType(c_ast.IdentifierType): + + def process(self): + pass + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def name(self): + return ' '.join(self.names) + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + def to_dict(self): + if filter_node(self): + return None + + name = ' '.join(self.names) + + if name in BASIC_TYPES: + json_type = 'primitive_type' + elif name in STDLIB_TYPES: + json_type = 'stdlib_type' + elif name in collected_types: + json_type = 'lvgl_type' + elif name.startswith('_') and name[1:] in collected_types: + name = name[1:] + json_type = 'lvgl_type' + elif name.startswith('_lv_') or name.startswith('lv_'): + json_type = 'lvgl_type' + else: + json_type = 'unknown_type' + + res = OrderedDict([ + ('name', name), + ('json_type', json_type), + ]) + + return res + + +class ParamList(c_ast.ParamList): + + def process(self): + pass + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + for param in (self.params or []): + param.parent = value + + def to_dict(self): + if filter_node(self): + return None + + pass + + +class PtrDecl(c_ast.PtrDecl): + + @property + def name(self): + return None + + def process(self): + self.type.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + self.type.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if isinstance(self.type, FuncDecl): + type_dict = self.type.to_dict() + type_dict['json_type'] = 'function_pointer' + res = type_dict + else: + res = OrderedDict([ + ('type', self.type.to_dict()), + ('json_type', 'pointer') + ]) + + if 'quals' in res: + res['quals'].extend(self.quals) + else: + res['quals'] = self.quals + + return res + + +class Struct(c_ast.Struct): + + def process(self): + for decl in (self.decls or []): + decl.process() + + name = self.name + parent = self.parent + while parent is not None and name is None: + name = parent.name + parent = parent.parent + + if name and name not in collected_types: + collected_types.append(name) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + for decl in (self.decls or []): + decl.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if not self.decls: + name = self.name + if not name: + self.name = self.parent.name + + if name: + struct_doc = get_struct_docs(name) # NOQA + if struct_doc: + docstring = struct_doc.description + else: + docstring = '' + else: + docstring = '' + + res = OrderedDict([ + ('name', name), + ('type', OrderedDict([ + ('name', 'struct'), + ('json_type', 'primitive_type') + ])), + ('json_type', 'forward_decl'), + ('docstring', docstring), + ]) + + else: + if self.name: + struct_doc = get_struct_docs(self.name) # NOQA + elif self.parent.name: + struct_doc = get_struct_docs(self.parent.name) # NOQA + else: + struct_doc = None + + if struct_doc is not None: + docstring = struct_doc.description + else: + docstring = '' + + fields = [] + + for field in self.decls: + field = field.to_dict() + + if struct_doc is not None: + for field_doc in struct_doc.fields: + if field_doc.name == field['name']: + field_docstring = field_doc.description + break + else: + field_docstring = '' + else: + field_docstring = '' + + field['docstring'] = field_docstring + field['json_type'] = 'field' + fields.append(field) + + res = OrderedDict([ + ('name', self.name), + ('type', OrderedDict([ + ('name', 'struct'), + ('json_type', 'primitive_type') + ])), + ('json_type', 'struct'), + ('docstring', docstring), + ('fields', fields) + ]) + + return res + + +class TypeDecl(c_ast.TypeDecl): + + @property + def name(self): + return self.declname + + def process(self): + self.type.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + parent = value + + while parent is not None: + try: + if parent.declname == self.declname: + break + except AttributeError: + pass + + try: + if parent.name == self.declname: + break + except AttributeError: + pass + + parent = parent.parent + + if parent is None: + self._parent = value + + self.type.parent = self + else: + self.type.parent = parent + + def to_dict(self): + if filter_node(self): + return None + + if self.parent is None: + res = self.type.to_dict() + if self.declname is not None and not self.type.name: + res['name'] = self.declname + + elif isinstance(self.type, (Union, Struct)): + res = self.type.to_dict() + + if not self.type.name and self.declname: + res['name'] = self.declname + else: + res = OrderedDict([ + ('name', self.declname), + ('type', self.type.to_dict()), + ('json_type', str(type(self))), + ]) + + res['quals'] = self.quals + + return res + + +class Typedef(c_ast.Typedef): + + def process(self): + if self._parent is None: + self.type.parent = self + + self.type.process() + + if self.name and self.name not in collected_types: + collected_types.append(self.name) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + if filter_node(self): + return None + + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + self.type.parent = self + + @property + def is_struct(self): + return ( + isinstance(self.type, TypeDecl) and + isinstance(self.type.type, Struct) + ) + + @property + def is_union(self): + return ( + isinstance(self.type, TypeDecl) and + isinstance(self.type.type, Union) + ) + + def get_struct_union(self): + res = self.type.type.to_dict() + if not self.type.type.name and self.type.name: + res['name'] = self.type.name + elif not self.type.type.name: + res['name'] = self.name + + return res + + def to_dict(self): + doc_search = get_typedef_docs(self.name) # NOQA + + if doc_search is None: + docstring = '' + else: + docstring = doc_search.description + + if ( + isinstance(self.type, PtrDecl) and + isinstance(self.type.type, FuncDecl) + ): + type_dict = self.type.type.to_dict() + type_dict['json_type'] = 'function_pointer' + type_dict['name'] = self.name + if 'quals' in type_dict: + type_dict['quals'].extend(self.quals) + else: + type_dict['quals'] = self.quals + + if ( + 'docstring' not in type_dict or + not type_dict['docstring'] + ): + type_dict['docstring'] = docstring + + return type_dict + + if isinstance(self.type, TypeDecl): + type_dict = self.type.type.to_dict() + + if type_dict['name'] is None: + if self.name is not None: + type_dict['name'] = self.name + else: + raise RuntimeError(str(type_dict)) + + if 'quals' in type_dict: + type_dict['quals'].extend(self.quals) + else: + type_dict['quals'] = self.quals + + type_dict['quals'].extend(self.type.quals) + + if 'docstring' not in type_dict: + type_dict['docstring'] = '' + + if not type_dict['docstring']: + type_dict['docstring'] = docstring + + if type_dict['name'] in _structures: + _structures[type_dict['name']]['name'] = self.name + + if 'quals' in _structures[type_dict['name']]: + _structures[type_dict['name']]['quals'].extend( + type_dict['quals'] + ) + else: + _structures[type_dict['name']]['quals'] = type_dict['quals'] + + if ( + type_dict['docstring'] and + not _structures[type_dict['name']]['docstring'] + ): + _structures[type_dict['name']]['docstring'] = ( + type_dict['docstring'] + ) + + return None + + if type_dict['name'] in _unions: + _unions[type_dict['name']]['name'] = self.name + + if 'quals' in _unions[type_dict['name']]: + _unions[type_dict['name']]['quals'].extend( + type_dict['quals'] + ) + else: + _unions[type_dict['name']]['quals'] = type_dict['quals'] + + if ( + type_dict['docstring'] and + not _structures[type_dict['name']]['docstring'] + ): + _structures[type_dict['name']]['docstring'] = ( + type_dict['docstring'] + ) + + return None + + if type_dict['name'] in _enums: + if self.name is not None: + type_dict = self.type.to_dict() + + res = OrderedDict( + [ + ('name', self.name), + ('type', type_dict), + ('json_type', 'typedef'), + ('docstring', docstring), + ('quals', self.quals) + ] + ) + return res + + if 'quals' in _enums[type_dict['name']]: + _enums[type_dict['name']]['quals'].extend( + type_dict['quals'] + ) + else: + _enums[type_dict['name']]['quals'] = type_dict['quals'] + + if ( + type_dict['docstring'] and + not _enums[type_dict['name']]['docstring'] + ): + _enums[type_dict['name']]['docstring'] = ( + type_dict['docstring'] + ) + + return None + + if not type_dict['name']: + type_dict['name'] = self.name + return type_dict + + if type_dict['name'] and type_dict['name'][1:] == self.name: + type_dict['name'] = self.name + return type_dict + + if type_dict['name'] and type_dict['name'] == self.name: + return type_dict + + if isinstance(self.type.type, (Struct, Union)): + res = OrderedDict([ + ('name', self.name), + ('type', OrderedDict([ + ('name', self.type.type.name), + ('json_type', 'lvgl_type') + ])), + ('json_type', 'typedef'), + ('docstring', docstring), + ('quals', self.quals + self.type.quals) + ]) + + return [res, self.type.type.to_dict()] + + elif isinstance(self.type.type, Enum): + if self.type.type.name: + type_dict = self.type.type.to_dict() + + if not type_dict['docstring']: + type_dict['docstring'] = docstring + docstring = '' + + if not type_dict['name']: + if self.type.name: + type_dict['name'] = self.type.name + else: + type_dict['name'] = self.name + + res = OrderedDict([ + ('name', self.name), + ('type', OrderedDict([ + ('name', type_dict['name']), + ('json_type', 'lvgl_type') + ])), + ('json_type', 'typedef'), + ('docstring', docstring), + ]) + + return [res, type_dict] + + type_dict = self.type.to_dict() + + if 'quals' in type_dict: + type_dict['quals'].extend(self.quals) + else: + type_dict['quals'] = self.quals + + if ( + docstring and + 'docstring' in type_dict and + not type_dict['docstring'] + ): + type_dict['docstring'] = docstring + + if 'name' in type_dict and type_dict['name']: + if type_dict['name'] == self.name: + return type_dict + if type_dict['name'][1:] == self.name: + type_dict['name'] = self.name + return type_dict + + quals = type_dict['quals'] + del type_dict['quals'] + + res = OrderedDict([ + ('name', self.name), + ('type', type_dict), + ('json_type', 'typedef'), + ('docstring', docstring), + ('quals', quals) + ]) + + return res + + +class Typename(c_ast.Typename): + + def process(self): + self.type.process() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + self.type.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if not self.name and isinstance(self.type, IdentifierType): + res = self.type.to_dict() + res['quals'] = self.quals + + elif isinstance(self.parent, FuncDecl): + if self.name and self.parent.name: + func_docs = get_func_docs(self.parent.name) # NOQA + + if func_docs is not None: + for arg in func_docs.args: + if arg.name and arg.name == self.name: + docstring = arg.description + break + else: + docstring = '' + else: + docstring = '' + else: + docstring = '' + + res = OrderedDict([ + ('name', self.name), + ('type', self.type.to_dict()), + ('json_type', 'arg'), + ('docstring', docstring), + ('quals', self.quals) + ]) + else: + res = OrderedDict([ + ('name', self.name), + ('type', self.type.to_dict()), + ('json_type', str(type(self))), + ('quals', self.quals) + ]) + + return res + + +class Union(c_ast.Union): + + def process(self): + for field in (self.decls or []): + field.process() + + name = self.name + parent = self.parent + while parent is not None and name is None: + try: + name = parent.name + except AttributeError: + pass + + parent = parent.parent + + if name and name not in collected_types: + collected_types.append(name) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent = None + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, value): + self._parent = value + + for decl in (self.decls or []): + decl.parent = self + + def to_dict(self): + if filter_node(self): + return None + + if not self.decls: + name = self.name + if not name: + self.name = self.parent.name + + if name: + union_doc = get_union_docs(name) # NOQA + if union_doc: + docstring = union_doc.description + else: + docstring = '' + else: + docstring = '' + + res = OrderedDict([ + ('name', name), + ('type', OrderedDict([ + ('name', 'union'), + ('json_type', 'primitive_type') + ])), + ('json_type', 'forward_decl'), + ('docstring', docstring), + ]) + else: + if self.name: + union_doc = get_union_docs(self.name) # NOQA + elif self.parent.name: + union_doc = get_union_docs(self.parent.name) # NOQA + else: + union_doc = None + + if union_doc is not None: + docstring = union_doc.description + else: + docstring = '' + + fields = [] + + for field in self.decls: + field = field.to_dict() + + if union_doc is not None: + for field_doc in union_doc.fields: + if field_doc.name == field['name']: + field_docstring = field_doc.description + break + else: + field_docstring = '' + else: + field_docstring = '' + + field['docstring'] = field_docstring + field['json_type'] = 'field' + fields.append(field) + + res = OrderedDict([ + ('name', self.name), + ('type', OrderedDict([ + ('name', 'union'), + ('json_type', 'primitive_type') + ])), + ('json_type', 'union'), + ('docstring', docstring), + ('fields', fields) + ]) + + return res + + +for cls in ( + ArrayDecl, + Constant, + Decl, + EllipsisParam, + Enum, + Enumerator, + EnumeratorList, + EnumeratorList, + FileAST, + FuncDecl, + FuncDef, + IdentifierType, + ParamList, + PtrDecl, + Struct, + TypeDecl, + Typedef, + Typename, + Union +): + cls_name = cls.__name__ + setattr(getattr(sys.modules['pycparser.c_parser'], 'c_ast'), cls_name, cls) diff --git a/scripts/gen_json/requirements.txt b/scripts/gen_json/requirements.txt new file mode 100644 index 000000000..bdfeda197 --- /dev/null +++ b/scripts/gen_json/requirements.txt @@ -0,0 +1,16 @@ +pycparser>=2.22 +pyMSVC>=0.5.3; platform_system == "Windows" +Sphinx +breathe +imagesize +importlib-metadata +sphinx-rtd-theme +sphinx-sitemap +sphinxcontrib-applehelp +sphinxcontrib-devhelp +sphinxcontrib-htmlhelp +sphinxcontrib-jsmath +sphinxcontrib-qthelp +sphinxcontrib-serializinghtml +sphinx-rtd-dark-mode +typing-extensions \ No newline at end of file diff --git a/tests/gen_json/test_gen_json.py b/tests/gen_json/test_gen_json.py new file mode 100644 index 000000000..25406ada8 --- /dev/null +++ b/tests/gen_json/test_gen_json.py @@ -0,0 +1,41 @@ +# Basic test to see if the API jeson generator is able to run without any errors +# This test doesn't not check to make sure the output is correct. It is for the +# sole purpose of making sure it completes. + +import os +import sys + +BASE_PATH = os.path.abspath(os.path.dirname(__file__)) +SCRIPT_PATH = os.path.join( + BASE_PATH, '..', '..', 'scripts', + 'gen_json', 'gen_json.py' +) + +OUTPUT_FILE = os.path.join(BASE_PATH, 'lvgl.json') + +cmd = f'{sys.executable} "{SCRIPT_PATH}" --output-path "{BASE_PATH}"' +print('running test') +print(cmd) + +result = os.system(cmd) +if result != 0: + print() + sys.stderr.write('TEST FAILED!!\n\n') + sys.stderr.flush() + sys.exit(result) + + +if not os.path.exists(OUTPUT_FILE): + print() + sys.stderr.write(f'"{OUTPUT_FILE}" was not found.\n') + sys.stderr.write('TEST FAILED!!\n\n') + sys.stderr.flush() + sys.exit(-500) + +try: + os.remove(OUTPUT_FILE) +except: # NOQA + pass + +print() +print('TEST PASSED!')