feat: add API JSON generator (#5677)
Co-authored-by: Liam <30486941+liamHowatt@users.noreply.github.com>
This commit is contained in:
172
scripts/gen_json/create_fake_lib_c.py
Normal file
172
scripts/gen_json/create_fake_lib_c.py
Normal file
File diff suppressed because one or more lines are too long
378
scripts/gen_json/gen_json.py
Normal file
378
scripts/gen_json/gen_json.py
Normal file
@@ -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)
|
||||
66
scripts/gen_json/get_sdl2.py
Normal file
66
scripts/gen_json/get_sdl2.py
Normal file
@@ -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
|
||||
1694
scripts/gen_json/pycparser_monkeypatch.py
Normal file
1694
scripts/gen_json/pycparser_monkeypatch.py
Normal file
File diff suppressed because it is too large
Load Diff
16
scripts/gen_json/requirements.txt
Normal file
16
scripts/gen_json/requirements.txt
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user