diff --git a/bin/iwyu.py b/bin/iwyu.py new file mode 100755 index 00000000..db1fea07 --- /dev/null +++ b/bin/iwyu.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# Copyright (c) 2022 Michael Richters + +# This is free and unencumbered software released into the public domain. + +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. + +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +# For more information, please refer to +# ------------------------------------------------------------------------------ + +"""This is a script for maintenance of the headers included in Kaleidoscope source +files. It is not currently possible to run this automatically on all +Kaleidoscope source files, because of the peculiarities therein. It uses +llvm/clang to determine which headers should be included in a given file, but +there's no avr-clang, so we're limited to using the virtual hardware device. + +It takes any number of source files as its input, examines them and updates the +list of header `#include` directives. + +It is safe to run on most Kaleidoscope source files, and a good idea to run it +on new ones (after staging them, so you can easily see what changes have been +made). +""" + +# Example invocation: +# $ git ls-files -m | grep '\.\(h\|cpp\)' | bin/iwyu.py + +import os +import shutil +import subprocess +import sys + +def main(): + """Organize includes in Kaleidoscpe source files.""" + + iwyu = shutil.which('include-what-you-use') + print(f'IWYU: {iwyu}') + iwyu_flags = [ + '-Xiwyu', '--no_fwd_decls', # No forward declarations + '-x', 'c++', + ] + + fix_includes = shutil.which('fix_includes.py') + print(f'fix_includes: {fix_includes}') + + clang = os.getenv('CLANG_COMPILER') + if clang is None: + clang = shutil.which('clang') + print(f'clang: {clang}') + + result = subprocess.run([clang, '-print-resource-dir'], + capture_output=True, check=True) + clang_resource_dir = result.stdout.decode('utf-8').rstrip() + system_include_dir = os.path.join(clang_resource_dir, 'include') + + kaleidoscope_dir = os.getenv('KALEIDOSCOPE_DIR') + if kaleidoscope_dir is None: + kaleidoscope_dir = os.getcwd() + kaleidoscope_src_dir = os.path.join(kaleidoscope_dir, 'src') + print(f'KALEIDOSCOPE_DIR: {kaleidoscope_dir}') + + virtual_hardware_dir = os.path.join( + kaleidoscope_dir, '.arduino', 'user', 'hardware', 'keyboardio', 'virtual' + ) + virtual_arduino_core_dir = os.path.join(virtual_hardware_dir, 'cores', 'arduino') + virtual_model01_dir = os.path.join(virtual_hardware_dir, 'variants', 'model01') + virtual_keyboardiohid_dir = os.path.join(virtual_hardware_dir, + 'libraries', 'KeyboardioHID', 'src') + + clang_flags = [ + '-c', + '-g', + '-Wall', + '-Wextra', + '-std=gnu++14', # Not `c++14`, because we're using clang, not gcc + '-ffunction-sections', + '-fdata-sections', + '-fno-threadsafe-statics', + '-MMD', + '-Woverloaded-virtual', + '-Wno-unused-parameter', + '-Wno-unused-variable', + '-Wno-ignored-qualifiers', + '-Wno-type-limits', + '-D' + 'KALEIDOSCOPE_VIRTUAL_BUILD=1', + '-D' + 'KEYBOARDIOHID_BUILD_WITHOUT_HID=1', + '-D' + 'USBCON=dummy', + '-D' + 'ARDUINO_ARCH_AVR=1', + '-D' + 'ARDUINO=10607', + '-D' + 'ARDUINO_AVR_MODEL01', + '-D' + 'ARDUINO_ARCH_VIRTUAL', + '-D' + 'USB_VID=0x1209', + '-D' + 'USB_PID=0x2301', + '-D' + 'USB_MANUFACTURER="Keyboardio"', + '-D' + 'USB_PRODUCT="Model 01"', + '-D' + 'KALEIDOSCOPE_HARDWARE_H="Kaleidoscope-Hardware-Keyboardio-Model01.h"', + '-D' + 'TWI_BUFFER_LENGTH=32', + '-I' + system_include_dir, + '-I' + kaleidoscope_src_dir, + '-I' + virtual_arduino_core_dir, + '-I' + virtual_model01_dir, + '-I' + virtual_keyboardiohid_dir, + ] + + plugins_dir = os.path.join(kaleidoscope_dir, 'plugins') + for basename in os.listdir(plugins_dir): + plugin_dir = os.path.join(plugins_dir, basename) + if not os.path.isdir(plugin_dir): + continue + clang_flags.append('-I' + os.path.join(plugin_dir, 'src')) + + for arg in [iwyu] + iwyu_flags: + print(arg) + for arg in clang_flags: + print(arg) + + for source_file in sys.argv[1:]: + iwyu_cmd = [iwyu] + iwyu_flags + clang_flags + [source_file] + print('------------------------------------------------------------') + print(f'File: {source_file}') + + # Sometimes, it's useful to force IWYU to make changes, or to have a + # more definitive marker of whether or not it failed due to compilation + # errors (which may differ between IWYU and normal compilation, + # unfortunately). If so, the follwing code can be uncommented. It adds + # a harmless `#include` at the end of the file, which will be removed if + # this script runs successfully. + + # with open(source_file, "rb+") as fd: + # fd.seek(-1, 2) + # char = fd.read(1) + # if char != b'\n': + # print('missing newline at end of file') + # fd.write(b'\n') + # if source_file != 'src/kaleidoscope/HIDTables.h': + # fd.write(b'#include "kaleidoscope/HIDTables.h"') + + iwyu_proc = subprocess.run(iwyu_cmd, capture_output=True, check=False) + + fix_includes_cmd = [ + fix_includes, + '--update_comments', + '--nosafe_headers', + # Don't change the order of headers in existing files, because some + # of them have been changed from what IWYU will do. For new files, + # use `--reorder` instead. + '--noreorder', + ] + subprocess.run(fix_includes_cmd, input=iwyu_proc.stderr, check=False) + + # Optionally, we can write the output of `include-what-you-use` to a + # file for debugging purposes: + # with open(source_file + '.iwyu', 'wb') as fd: + # fd.write(iwyu_proc.stderr) + +if __name__ == "__main__": + main()