diff options
Diffstat (limited to 'ext/pybind11/tools/mkdoc.py')
-rwxr-xr-x[-rw-r--r--] | ext/pybind11/tools/mkdoc.py | 127 |
1 files changed, 101 insertions, 26 deletions
diff --git a/ext/pybind11/tools/mkdoc.py b/ext/pybind11/tools/mkdoc.py index 1fd8cceed..44164af3d 100644..100755 --- a/ext/pybind11/tools/mkdoc.py +++ b/ext/pybind11/tools/mkdoc.py @@ -14,6 +14,7 @@ import textwrap from clang import cindex from clang.cindex import CursorKind from collections import OrderedDict +from glob import glob from threading import Thread, Semaphore from multiprocessing import cpu_count @@ -40,6 +41,10 @@ PRINT_LIST = [ CursorKind.FIELD_DECL ] +PREFIX_BLACKLIST = [ + CursorKind.TRANSLATION_UNIT +] + CPP_OPERATORS = { '<=': 'le', '>=': 'ge', '==': 'eq', '!=': 'ne', '[]': 'array', '+=': 'iadd', '-=': 'isub', '*=': 'imul', '/=': 'idiv', '%=': @@ -56,10 +61,13 @@ CPP_OPERATORS = OrderedDict( job_count = cpu_count() job_semaphore = Semaphore(job_count) -output = [] + +class NoFilenamesError(ValueError): + pass + def d(s): - return s.decode('utf8') + return s if isinstance(s, str) else s.decode('utf8') def sanitize_name(name): @@ -182,18 +190,18 @@ def process_comment(comment): return result.rstrip().lstrip('\n') -def extract(filename, node, prefix): +def extract(filename, node, prefix, output): if not (node.location.file is None or os.path.samefile(d(node.location.file.name), filename)): return 0 if node.kind in RECURSE_LIST: sub_prefix = prefix - if node.kind != CursorKind.TRANSLATION_UNIT: + if node.kind not in PREFIX_BLACKLIST: if len(sub_prefix) > 0: sub_prefix += '_' sub_prefix += d(node.spelling) for i in node.get_children(): - extract(filename, i, sub_prefix) + extract(filename, i, sub_prefix, output) if node.kind in PRINT_LIST: comment = d(node.raw_comment) if node.raw_comment is not None else '' comment = process_comment(comment) @@ -202,15 +210,15 @@ def extract(filename, node, prefix): sub_prefix += '_' if len(node.spelling) > 0: name = sanitize_name(sub_prefix + d(node.spelling)) - global output output.append((name, filename, comment)) class ExtractionThread(Thread): - def __init__(self, filename, parameters): + def __init__(self, filename, parameters, output): Thread.__init__(self) self.filename = filename self.parameters = parameters + self.output = output job_semaphore.acquire() def run(self): @@ -219,13 +227,18 @@ class ExtractionThread(Thread): index = cindex.Index( cindex.conf.lib.clang_createIndex(False, True)) tu = index.parse(self.filename, self.parameters) - extract(self.filename, tu.cursor, '') + extract(self.filename, tu.cursor, '', self.output) finally: job_semaphore.release() -if __name__ == '__main__': - parameters = ['-x', 'c++', '-std=c++11'] + +def read_args(args): + parameters = [] filenames = [] + if "-x" not in args: + parameters.extend(['-x', 'c++']) + if not any(it.startswith("-std=") for it in args): + parameters.append('-std=c++11') if platform.system() == 'Darwin': dev_path = '/Applications/Xcode.app/Contents/Developer/' @@ -240,17 +253,48 @@ if __name__ == '__main__': sysroot_dir = os.path.join(sdk_dir, next(os.walk(sdk_dir))[1][0]) parameters.append('-isysroot') parameters.append(sysroot_dir) - - for item in sys.argv[1:]: + elif platform.system() == 'Linux': + # clang doesn't find its own base includes by default on Linux, + # but different distros install them in different paths. + # Try to autodetect, preferring the highest numbered version. + def clang_folder_version(d): + return [int(ver) for ver in re.findall(r'(?<!lib)(?<!\d)\d+', d)] + clang_include_dir = max(( + path + for libdir in ['lib64', 'lib', 'lib32'] + for path in glob('/usr/%s/clang/*/include' % libdir) + if os.path.isdir(path) + ), default=None, key=clang_folder_version) + if clang_include_dir: + parameters.extend(['-isystem', clang_include_dir]) + + for item in args: if item.startswith('-'): parameters.append(item) else: filenames.append(item) if len(filenames) == 0: - print('Syntax: %s [.. a list of header files ..]' % sys.argv[0]) - exit(-1) + raise NoFilenamesError("args parameter did not contain any filenames") + + return parameters, filenames + + +def extract_all(args): + parameters, filenames = read_args(args) + output = [] + for filename in filenames: + thr = ExtractionThread(filename, parameters, output) + thr.start() + + print('Waiting for jobs to finish ..', file=sys.stderr) + for i in range(job_count): + job_semaphore.acquire() + return output + + +def write_header(comments, out_file=sys.stdout): print('''/* This file contains docstrings for the Python bindings. Do not edit! These were automatically extracted by mkdoc.py @@ -274,20 +318,12 @@ if __name__ == '__main__': #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" #endif -''') +''', file=out_file) - output.clear() - for filename in filenames: - thr = ExtractionThread(filename, parameters) - thr.start() - - print('Waiting for jobs to finish ..', file=sys.stderr) - for i in range(job_count): - job_semaphore.acquire() name_ctr = 1 name_prev = None - for name, _, comment in list(sorted(output, key=lambda x: (x[0], x[1]))): + for name, _, comment in list(sorted(comments, key=lambda x: (x[0], x[1]))): if name == name_prev: name_ctr += 1 name = name + "_%i" % name_ctr @@ -295,10 +331,49 @@ if __name__ == '__main__': name_prev = name name_ctr = 1 print('\nstatic const char *%s =%sR"doc(%s)doc";' % - (name, '\n' if '\n' in comment else ' ', comment)) + (name, '\n' if '\n' in comment else ' ', comment), file=out_file) print(''' #if defined(__GNUG__) #pragma GCC diagnostic pop #endif -''') +''', file=out_file) + + +def mkdoc(args): + args = list(args) + out_path = None + for idx, arg in enumerate(args): + if arg.startswith("-o"): + args.remove(arg) + try: + out_path = arg[2:] or args.pop(idx) + except IndexError: + print("-o flag requires an argument") + exit(-1) + break + + comments = extract_all(args) + + if out_path: + try: + with open(out_path, 'w') as out_file: + write_header(comments, out_file) + except: + # In the event of an error, don't leave a partially-written + # output file. + try: + os.unlink(out_path) + except: + pass + raise + else: + write_header(comments) + + +if __name__ == '__main__': + try: + mkdoc(sys.argv[1:]) + except NoFilenamesError: + print('Syntax: %s [.. a list of header files ..]' % sys.argv[0]) + exit(-1) |