From fc16fe1a0c7653feb6c331958386e03c9f2b8de3 Mon Sep 17 00:00:00 2001 From: Hendrik Langer Date: Thu, 18 Jul 2019 13:13:10 +0200 Subject: [PATCH] clang-tidy test --- .temp-clang-tidy.cpp | 0 compile_commands.json | 7 ++ script/build_compile_commands.py | 94 +++++++++++++++++ script/clang-tidy | 170 +++++++++++++++++++++++++++++++ script/helpers.py | 139 +++++++++++++++++++++++++ script/helpers.pyc | Bin 0 -> 6050 bytes src/main.h | 0 7 files changed, 410 insertions(+) create mode 100644 .temp-clang-tidy.cpp create mode 100644 compile_commands.json create mode 100755 script/build_compile_commands.py create mode 100755 script/clang-tidy create mode 100644 script/helpers.py create mode 100644 script/helpers.pyc create mode 100644 src/main.h diff --git a/.temp-clang-tidy.cpp b/.temp-clang-tidy.cpp new file mode 100644 index 0000000..e69de29 diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..ca84cf4 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,7 @@ +[ + { + "directory": "/home/hendrik/Projects/esp32-weatherstation", + "command": "/home/hendrik/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-g++ -I/home/hendrik/Projects/esp32-weatherstation/include -I/home/hendrik/Projects/esp32-weatherstation/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/config -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/app_trace -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/app_update -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/asio -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/bootloader_support -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/bt -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/coap -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/console -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/driver -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp-tls -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp32 -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp_adc_cal -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp_event -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp_http_client -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp_http_server -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp_https_ota -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp_ringbuf -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/ethernet -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/expat -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/fatfs -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/freemodbus -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/freertos -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/heap -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/idf_test -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/jsmn -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/json -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/libsodium -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/log -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/lwip -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/mbedtls -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/mdns -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/micro-ecc -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/mqtt -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/newlib -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/nghttp -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/nvs_flash -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/openssl -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/protobuf-c -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/protocomm -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/pthread -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/sdmmc -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/smartconfig_ack -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/soc -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/spi_flash -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/spiffs -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/tcp_transport -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/tcpip_adapter -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/ulp -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/vfs -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/wear_levelling -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/wifi_provisioning -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/wpa_supplicant -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/xtensa-debug-module -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp32-camera -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/esp-face -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/tools/sdk/include/fb_gfx -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/cores/esp32 -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/variants/d32_pro -I/home/hendrik/Projects/esp32-weatherstation/lib/python2.7 -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/AzureIoT/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/FS/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/SD/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/Update/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src -I/home/hendrik/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src -I/home/hendrik/.platformio/packages/toolchain-xtensa32/xtensa-esp32-elf/include -I/home/hendrik/.platformio/packages/toolchain-xtensa32/xtensa-esp32-elf/include/c++/5.2.0 -I/home/hendrik/.platformio/packages/toolchain-xtensa32/xtensa-esp32-elf/include/c++/5.2.0/xtensa-esp32-elf -I/home/hendrik/.platformio/packages/toolchain-xtensa32/lib/gcc/xtensa-esp32-elf/5.2.0/include -I/home/hendrik/.platformio/packages/toolchain-xtensa32/lib/gcc/xtensa-esp32-elf/5.2.0/include-fixed -I/home/hendrik/.platformio/packages/tool-unity -DPLATFORMIO=40000 -DARDUINO_LOLIN_D32_PRO -DBOARD_HAS_PSRAM -DESP32 -DESP_PLATFORM -DF_CPU=240000000L -DHAVE_CONFIG_H '-DMBEDTLS_CONFIG_FILE=\"mbedtls/esp_config.h\"' -DARDUINO=10805 -DARDUINO_ARCH_ESP32 '-DARDUINO_VARIANT=\"d32_pro\"' '-DARDUINO_BOARD=\"WEMOS' -std=gnu++11 -Wall -Wno-delete-non-virtual-dtor -Wno-unused-variable -Wunreachable-code -o /home/hendrik/Projects/esp32-weatherstation/.temp-clang-tidy.cpp.o -c /home/hendrik/Projects/esp32-weatherstation/.temp-clang-tidy.cpp", + "file": "/home/hendrik/Projects/esp32-weatherstation/.temp-clang-tidy.cpp" + } +] \ No newline at end of file diff --git a/script/build_compile_commands.py b/script/build_compile_commands.py new file mode 100755 index 0000000..233c7ab --- /dev/null +++ b/script/build_compile_commands.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +import codecs +import json +import os.path +import re +import subprocess +import sys + +root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) +basepath = os.path.join(root_path, 'esphome') +temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp') + + +def shlex_quote(s): + if not s: + return u"''" + if re.search(r'[^\w@%+=:,./-]', s) is None: + return s + + return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" + +def build_all_include(): + # Build a cpp file that includes all header files in this repo. + # Otherwise header-only integrations would not be tested by clang-tidy + headers = [] + for path in walk_files(basepath): + filetypes = ('.h',) + ext = os.path.splitext(path)[1] + if ext in filetypes: + path = os.path.relpath(path, root_path) + include_p = path.replace(os.path.sep, '/') + headers.append('#include "{}"'.format(include_p)) + headers.sort() + headers.append('') + content = '\n'.join(headers) + with codecs.open(temp_header_file, 'w', encoding='utf-8') as f: + f.write(content) + +def build_compile_commands(): + gcc_flags_json = os.path.join(root_path, '.gcc-flags.json') + if not os.path.isfile(gcc_flags_json): + print("Could not find {} file which is required for clang-tidy.") + print('Please run "pio init --ide atom" in the root esphome folder to generate that file.') + sys.exit(1) + with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f: + gcc_flags = json.load(f) + exec_path = gcc_flags['execPath'] + include_paths = gcc_flags['gccIncludePaths'].split(',') + includes = ['-I{}'.format(p) for p in include_paths] + cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') + defines = [flag for flag in cpp_flags if flag.startswith('-D')] + command = [exec_path] + command.extend(includes) + command.extend(defines) + command.append('-std=gnu++11') + command.append('-Wall') + command.append('-Wno-delete-non-virtual-dtor') + command.append('-Wno-unused-variable') + command.append('-Wunreachable-code') + + source_files = [] + for path in walk_files(basepath): + filetypes = ('.cpp',) + ext = os.path.splitext(path)[1] + if ext in filetypes: + source_files.append(os.path.abspath(path)) + source_files.append(temp_header_file) + source_files.sort() + compile_commands = [{ + 'directory': root_path, + 'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])), + 'file': p + } for p in source_files] + compile_commands_json = os.path.join(root_path, 'compile_commands.json') + if os.path.isfile(compile_commands_json): + with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f: + try: + if json.load(f) == compile_commands: + return + except: + pass + with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f: + json.dump(compile_commands, f, indent=2) + + +def walk_files(path): + for root, _, files in os.walk(path): + for name in files: + yield os.path.join(root, name) + +if __name__ == "__main__": + build_compile_commands() + build_all_include() diff --git a/script/clang-tidy b/script/clang-tidy new file mode 100755 index 0000000..463071f --- /dev/null +++ b/script/clang-tidy @@ -0,0 +1,170 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import multiprocessing +import os +import re + +import pexpect +import shutil +import subprocess +import sys +import tempfile + +import argparse +import click +import threading + +sys.path.append(os.path.dirname(__file__)) +from helpers import basepath, shlex_quote, get_output, build_compile_commands, \ + build_all_include, temp_header_file, git_ls_files, filter_changed + +is_py2 = sys.version[0] == '2' + +if is_py2: + import Queue as queue +else: + import queue as queue + + +def run_tidy(args, tmpdir, queue, lock, failed_files): + print("tidy") + while True: + path = queue.get() + invocation = ['clang-tidy-7', '-header-filter=^{}/.*'.format(re.escape(basepath))] + if tmpdir is not None: + invocation.append('-export-fixes') + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + invocation.append(name) + invocation.append('-p=.') + if args.quiet: + invocation.append('-quiet') + for arg in ['-Wfor-loop-analysis', '-Wshadow-field', '-Wshadow-field-in-constructor']: + invocation.append('-extra-arg={}'.format(arg)) + invocation.append(os.path.abspath(path)) + invocation_s = ' '.join(shlex_quote(x) for x in invocation) + + # Use pexpect for a pseudy-TTY with colored output + output, rc = pexpect.run(invocation_s, withexitstatus=True, encoding='utf-8', + timeout=15*60) + with lock: + if rc != 0: + print() + print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) + print(invocation_s) + print(output) + print() + failed_files.append(path) + queue.task_done() + + +def progress_bar_show(value): + if value is None: + return '' + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-j', '--jobs', type=int, + default=multiprocessing.cpu_count(), + help='number of tidy instances to be run in parallel.') + parser.add_argument('files', nargs='*', default=[], + help='files to be processed (regex on path)') + parser.add_argument('--fix', action='store_true', help='apply fix-its') + parser.add_argument('-q', '--quiet', action='store_false', + help='Run clang-tidy in quiet mode') + parser.add_argument('-c', '--changed', action='store_true', + help='Only run on changed files') + parser.add_argument('--all-headers', action='store_true', + help='Create a dummy file that checks all headers') + args = parser.parse_args() + + try: + get_output('clang-tidy-7', '-version') + except: + print(""" + Oops. It looks like clang-tidy is not installed. + + Please check you can run "clang-tidy-7 -version" in your terminal and install + clang-tidy (v7) if necessary. + + Note you can also upload your code as a pull request on GitHub and see the CI check + output to apply clang-tidy. + """) + return 1 + + build_all_include() + build_compile_commands() + + print("test") + + files = [] + for path in git_ls_files(): + filetypes = ('.cpp',) + ext = os.path.splitext(path)[1] + if ext in filetypes: + path = os.path.relpath(path, os.getcwd()) + files.append(path) + # Match against re + file_name_re = re.compile('|'.join(args.files)) + files = [p for p in files if file_name_re.search(p)] + + if args.changed: + files = filter_changed(files) + + files.sort() + + if args.all_headers: + files.insert(0, temp_header_file) + + tmpdir = None + if args.fix: + tmpdir = tempfile.mkdtemp() + + failed_files = [] + return_code = 0 + try: + task_queue = queue.Queue(args.jobs) + lock = threading.Lock() + for _ in range(args.jobs): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, task_queue, lock, failed_files)) + t.daemon = True + t.start() + + # Fill the queue with files. + with click.progressbar(files, width=30, file=sys.stderr, + item_show_func=progress_bar_show) as bar: + for name in bar: + task_queue.put(name) + + # Wait for all threads to be done. + task_queue.join() + return_code = len(failed_files) + + except KeyboardInterrupt: + print() + print('Ctrl-C detected, goodbye.') + if tmpdir: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + print("finished") + if args.fix and failed_files: + print('Applying fixes ...') + try: + subprocess.call(['clang-apply-replacements-7', tmpdir]) + except: + print('Error applying fixes.\n', file=sys.stderr) + raise + + sys.exit(return_code) + + +if __name__ == '__main__': + main() + diff --git a/script/helpers.py b/script/helpers.py new file mode 100644 index 0000000..1b38b56 --- /dev/null +++ b/script/helpers.py @@ -0,0 +1,139 @@ +import codecs +import json +import os.path +import re +import subprocess +import sys + +root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) +basepath = os.path.join(root_path, 'src') +temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp') + + +def shlex_quote(s): + if not s: + return u"''" + if re.search(r'[^\w@%+=:,./-]', s) is None: + return s + + return u"'" + s.replace(u"'", u"'\"'\"'") + u"'" + + +def build_all_include(): + # Build a cpp file that includes all header files in this repo. + # Otherwise header-only integrations would not be tested by clang-tidy + headers = [] + for path in walk_files(basepath): + filetypes = ('.h',) + ext = os.path.splitext(path)[1] + if ext in filetypes: + path = os.path.relpath(path, root_path) + include_p = path.replace(os.path.sep, '/') + headers.append('#include "{}"'.format(include_p)) + headers.sort() + headers.append('') + content = '\n'.join(headers) + with codecs.open(temp_header_file, 'w', encoding='utf-8') as f: + f.write(content) + + +def build_compile_commands(): + gcc_flags_json = os.path.join(root_path, '.gcc-flags.json') + if not os.path.isfile(gcc_flags_json): + print("Could not find {} file which is required for clang-tidy.") + print('Please run "pio init --ide atom" in the root esphome folder to generate that file.') + sys.exit(1) + with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f: + gcc_flags = json.load(f) + exec_path = gcc_flags['execPath'] + include_paths = gcc_flags['gccIncludePaths'].split(',') + includes = ['-isystem{}'.format(p) for p in include_paths] + cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ') + defines = [flag for flag in cpp_flags if flag.startswith('-D')] + command = [exec_path] + command.extend(includes) + command.extend(defines) + command.append('-std=gnu++11') + command.append('-Wall') + command.append('-Wno-delete-non-virtual-dtor') + command.append('-Wno-unused-variable') + command.append('-Wunreachable-code') + + source_files = [] + for path in walk_files(basepath): + filetypes = ('.cpp',) + ext = os.path.splitext(path)[1] + if ext in filetypes: + source_files.append(os.path.abspath(path)) + source_files.append(temp_header_file) + source_files.sort() + compile_commands = [{ + 'directory': root_path, + 'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])), + 'file': p + } for p in source_files] + compile_commands_json = os.path.join(root_path, 'compile_commands.json') + if os.path.isfile(compile_commands_json): + with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f: + try: + if json.load(f) == compile_commands: + return + except: + pass + with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f: + json.dump(compile_commands, f, indent=2) + + +def walk_files(path): + for root, _, files in os.walk(path): + for name in files: + yield os.path.join(root, name) + + +def get_output(*args): + proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = proc.communicate() + return output.decode('utf-8') + + +def splitlines_no_ends(string): + return [s.strip() for s in string.splitlines()] + + +def changed_files(): + for remote in ('upstream', 'origin'): + command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD'] + try: + merge_base = splitlines_no_ends(get_output(*command))[0] + break + except: + pass + else: + raise ValueError("Git not configured") + command = ['git', 'diff', merge_base, '--name-only'] + changed = splitlines_no_ends(get_output(*command)) + changed = [os.path.relpath(f, os.getcwd()) for f in changed] + changed.sort() + return changed + + +def filter_changed(files): + changed = changed_files() + files = [f for f in files if f in changed] + print("Changed files:") + if not files: + print(" No changed files!") + for c in files: + print(" {}".format(c)) + return files + + +def git_ls_files(): + command = ['git', 'ls-files', '-s'] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + output, err = proc.communicate() + lines = [x.split() for x in output.decode('utf-8').splitlines()] + return { + s[3].strip(): int(s[0]) for s in lines + } + diff --git a/script/helpers.pyc b/script/helpers.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2411e0fc68ae691ce42765bae558ad5ebba81e4d GIT binary patch literal 6050 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

;a*LKA)Vy!#2kZFT z&g-M?xSG3i_^f#`bjQcF%ApA*1zXHUOzKF4-J>hjFyjh%3X6OBXk`PG7{WopH@*S& zR^at&BqWv*4d;sOBLij!r$Zrac)H7sKX!31J?|%po(JK$UT&VSGaQgm8ty!>HC!7V zrF|inRQb$@n-lL*yYkUsJ9viq%`}qK_~Pzd1a5-QSV3-)&n4$f%934H`AA@6IMC+n z1O?Cm96B^~8_~L^&i7dmL zhkwQ#vd^j#H3W=2f?qS)X7U9S#yr87Ox{J(;HioA2E1kk)IsoDCci);UO`y;N|8k_ z1lzSz<4hN)IsPO#j}Im=)>4Dfw2Q0X;XZvU+6%jEBG`@fPT=;<9@0i jazkbf$sE2Ljs8n-Bd}a9SF6><>PEGK|B34L>g@jkF#X`k literal 0 HcmV?d00001 diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..e69de29