diff options
author | Kevin Schlosser <kdschlosser@users.noreply.github.com> | 2024-06-20 14:02:25 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-20 22:02:25 +0200 |
commit | ec80fe49fa3a3e239109949dd5ef2f84326f0fc5 (patch) | |
tree | 9330860a27b9de617935c990c9557da26c961792 /scripts/gen_json/gen_json.py | |
parent | 25e993a1372a9e98187c37331a7d0705475ae431 (diff) | |
download | lvgl-ec80fe49fa3a3e239109949dd5ef2f84326f0fc5.tar.gz lvgl-ec80fe49fa3a3e239109949dd5ef2f84326f0fc5.zip |
feat: add API JSON generator (#5677)
Co-authored-by: Liam <30486941+liamHowatt@users.noreply.github.com>
Diffstat (limited to 'scripts/gen_json/gen_json.py')
-rw-r--r-- | scripts/gen_json/gen_json.py | 378 |
1 files changed, 378 insertions, 0 deletions
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) |