1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
""" Preprocess and parse the libpulse headers.
Function signatures are found when parsing:
- 'types' (callbacks).
- 'structs' (arrays of function pointers).
- 'functions'.
- as argument of a function when this is a function pointer itself,
for example in 'pa_mainloop_api_once'.
"""
import os
import sys
import textwrap
import shutil
import subprocess
import pprint
from pyclibrary.c_library import CParser
PULSEAUDIO_H = '/usr/include/pulse/pulseaudio.h'
EXT_DEVICE_RESTORE = '/usr/include/pulse/ext-device-restore.h'
class ParseError(Exception): pass
def dedent(txt):
"""A dedent that does not use the first line to compute the margin."""
lines = txt.splitlines(keepends=True)
# Find the first non empty line, skipping the first line.
idx = 1
for i, l in enumerate(lines[1:]):
if l != '\n':
idx = i + 1
break
return ''.join(lines[:idx]) + textwrap.dedent(''.join(lines[idx:]))
def preprocess(header, pathname):
with open(pathname, 'w') as f:
proc = subprocess.run(['gcc', '-E', '-P', header],
stdout=f, text=True)
print(f"'{pathname}' created")
def get_parser():
if shutil.which('gcc') is None:
print('*** Error: GNU gcc is required', file=sys.stderr)
sys.exit(1)
if not os.path.exists(PULSEAUDIO_H):
print(f'*** Error: {PULSEAUDIO_H} does not exist', file=sys.stderr)
sys.exit(1)
dirname = os.path.dirname(__file__)
pulse_cpp = os.path.join(dirname, 'libpulse.cpp')
ext_device_restore_cpp = os.path.join(dirname, 'ext_device_restore.cpp')
preprocess(PULSEAUDIO_H, pulse_cpp)
preprocess(EXT_DEVICE_RESTORE, ext_device_restore_cpp)
print('Please wait, this may take a while ...')
parser = CParser([pulse_cpp, ext_device_restore_cpp],
replace={r'__attribute__ *\(\([^)]+\)\)': ''})
return parser
def lib_generator(parser, type):
return ((name, item) for (name, item) in parser.defs[type].items() if
name.startswith('pa_') and not name.startswith('pa_x'))
def signature_index(type_instance):
# A Type instance is a function signature when one of its members is a
# tuple.
sig_idx = 0
for i, item in enumerate(type_instance):
if i == 0:
# The type name.
continue
if isinstance(item, tuple):
sig_idx = i
break
return sig_idx
def get_type(type_instance):
items = []
for item in type_instance:
# An array.
if (isinstance(item, list) and len(item) == 1 and
isinstance(item[0], int)):
count = int(item[0])
if count > 0:
item = f'* {count}'
else:
item = '*'
items.append(item)
return ' '.join(items)
def parse_types(parser):
types = {}
callbacks = {}
for name, type_instance in lib_generator(parser, 'types'):
if signature_index(type_instance):
# Signature of a function pointer.
callbacks[name] = (type_instance[0], list(get_type(s[1]) for s in
type_instance[1]))
else:
types[name] = type_instance[0]
return types, callbacks
def parse_enums(parser):
enums = {}
for name, enum in lib_generator(parser, 'enums'):
enums[name] = enum
return enums
def parse_array(struct):
array = {}
for member in struct.members:
name = member[0]
type_instance = member[1]
sig_idx = signature_index(type_instance)
if sig_idx:
# A structure member as a function pointer.
# The signature has a variable length as the return type is not a
# Type instance as usual but a variable number of str elements.
restype = ' '.join(type_instance[:sig_idx])
arg_types = list(get_type(s[1]) for s in type_instance[sig_idx])
array[name] = (restype, arg_types)
else:
# A structure member as a plain type.
array[name] = get_type(type_instance)
continue
return array
def parse_structs(parser):
structs ={}
arrays = {}
try:
for name, struct in lib_generator(parser, 'structs'):
# An array of function pointers.
if name in ('pa_mainloop_api', 'pa_spawn_api'):
arrays[name] = parse_array(struct)
continue
result = []
for member in struct.members:
result.append((member[0], get_type(member[1])))
structs[name] = tuple(result)
except Exception as e:
raise ParseError(f"Structure '{name}': {struct}") from e
return structs, arrays
def parse_functions(parser):
functions = {}
for name, func in lib_generator(parser, 'functions'):
assert signature_index(func)
try:
restype = " ".join(func[0])
arg_types = []
for arg in func[1]:
type_instance = arg[1]
if signature_index(type_instance):
# Signature of a function pointer.
arg_types.append((type_instance[0],
list(get_type(s[1]) for s in type_instance[1])))
else:
arg_types.append(get_type(type_instance))
functions[name] = (restype, arg_types)
except Exception as e:
raise ParseError(f"Function '{name}': {func}") from e
return functions
def main():
parser = get_parser()
types, callbacks = parse_types(parser)
enums = parse_enums(parser)
structs, arrays = parse_structs(parser)
signatures = parse_functions(parser)
# The files of the parsed sections are written to 'dirname'.
dirname = '.'
if len(sys.argv) > 1:
dirname = sys.argv[1]
if not os.path.isdir(dirname):
print(f"*** Error: '{dirname}' is not a directory",
file=sys.stderr)
sys.exit(1)
# Merge all signatures into a 'functions' dictionary.
functions = {}
functions['signatures'] = {}
functions['signatures'].update(signatures)
functions['callbacks'] = callbacks
for name, signature in arrays['pa_mainloop_api'].items():
if name != 'userdata':
functions['callbacks'][name] = signature
# Create the parsed sections files.
for name in ('types', 'enums', 'structs', 'functions'):
pulse_name = 'pulse_' + name
doc = f'''"""File {pulse_name}.
This file has been generated by libpulse_parser.py - DO NOT MODIFY.
"""
'''
pathname = os.path.join(dirname, pulse_name + '.py')
with open(pathname, 'w') as f:
print(dedent(doc), file=f)
print(f'{pulse_name} = ', file=f, end='\\\n')
pprint.pprint(eval(name), width=100, stream=f)
print(f"'{pathname}' created")
if __name__ == '__main__':
main()
|