Skip to content

Instantly share code, notes, and snippets.

@jcfr
Last active February 9, 2022 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcfr/816febb58a9afdfe6e12675b95770fc9 to your computer and use it in GitHub Desktop.
Save jcfr/816febb58a9afdfe6e12675b95770fc9 to your computer and use it in GitHub Desktop.
Execuable to find all Qt modules referenced by a set of C++ source files
"""
This executable allows to find all Qt modules referenced by a set of C++ source files.
A Qt module (e.g QtNetwork) aggregates together a collection of Qt classes and is
associated with a set of public and private headers.
For example, to select multiple files and identify associated Qt modules:
QT_INCLUDE_DIR=/path/to/Qt5.15.2/5.15.2/gcc_64/include
python FindQtModules.py $QT_INCLUDE_DIR $(find /path/to/Awesome/Lib/ -type f)
Limitations:
* Preprocessor directives are not evaluated.
* Tested with Qt 5.x
This code is licensed under the terms of the Apache License, Version 2.0.
"""
import argparse
import os
import re
def configure_parser(parser):
parser.add_argument(
"qt_include_dir", type=str, metavar="QT_INCLUDE_DIR",
help="Path to the Qt include directory (e.g /path/to/Qt5.15.2/5.15.2/gcc_64/include)"
)
parser.add_argument(
"source_files", type=str, nargs="+", metavar="SOURCE_FILE",
help="Path of the source file to inspect"
)
def collect_qt_headers(qt_include_dir):
"""Return a map of Qt header to Qt module name.
The Qt modules are identified as the direct sub-directory of ``qt_include_dir``.
"""
qt_modules = os.listdir(qt_include_dir)
if "QtCore" not in qt_modules:
raise RuntimeException("Invalid QT_INCLUDE_DIR: Missing QtCore sub-directory")
qt_header_without_extension_pattern = re.compile(r'^[qQ]\w+(?!\.h)$')
qt_headers_to_modules = dict()
for qt_module in qt_modules:
for dirname, dirnames, filenames in os.walk(os.path.join(qt_include_dir, qt_module)):
for filename in filenames:
if filename.endswith(".h") or qt_header_without_extension_pattern.match(filename):
qt_headers_to_modules[filename] = qt_module
return qt_headers_to_modules
def is_binary_file(filepath):
"""Return True if ``filepath`` exists and is a binary file.
Adapted from https://stackoverflow.com/questions/898669/how-can-i-detect-if-a-file-is-binary-non-text-in-python/7392391#7392391
"""
textchars = bytearray({7,8,9,10,12,13,27} | set(range(0x20, 0x100)) - {0x7f})
is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))
with open(filepath, 'rb') as fp:
return is_binary_string(fp.read(1024))
def inspect_source_file(source_file, qt_headers_to_modules):
"""Return a map of Qt module to header referenced by ``source_file``.
"""
referenced_qt_modules = dict()
include_pattern = re.compile(r'\#\s*include\s*[\<\"]([\w\.]+)[\>\"]')
try:
with open(source_file) as contents:
for line in contents:
match_object = include_pattern.search(line)
if match_object is not None:
header_filename = match_object.group(1)
if header_filename in qt_headers_to_modules:
qt_module = qt_headers_to_modules[header_filename]
referenced_qt_modules[qt_module] = header_filename
except UnicodeDecodeError as exc:
print(f"Ignoring {source_file}: {exc}")
return referenced_qt_modules
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
configure_parser(parser)
args = parser.parse_args(argv[1:] if argv else None)
referenced_qt_modules = dict()
qt_headers_to_modules = collect_qt_headers(args.qt_include_dir)
for source_file in args.source_files:
if is_binary_file(source_file):
continue
referenced_qt_modules = {**referenced_qt_modules, **inspect_source_file(source_file, qt_headers_to_modules)}
for qt_module in referenced_qt_modules:
print(qt_module)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment