summaryrefslogtreecommitdiff
path: root/ext/pybind11/tools/mkdoc.py
diff options
context:
space:
mode:
Diffstat (limited to 'ext/pybind11/tools/mkdoc.py')
-rwxr-xr-x[-rw-r--r--]ext/pybind11/tools/mkdoc.py127
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)