source: python/lib/gen_external.py @ d214124

feature/autosinkfeature/constantqfeature/pitchshiftfeature/pydocstringsfeature/timestretch
Last change on this file since d214124 was d214124, checked in by Paul Brossier <piem@piem.org>, 18 months ago

python/lib/gen_external.py: split long functions

  • Property mode set to 100644
File size: 10.6 KB
Line 
1import distutils.ccompiler
2import sys
3import os
4import subprocess
5import glob
6
7header = os.path.join('src', 'aubio.h')
8output_path = os.path.join('python', 'gen')
9
10source_header = """// this file is generated! do not modify
11#include "aubio-types.h"
12"""
13
14default_skip_objects = [
15    # already in ext/
16    'fft',
17    'pvoc',
18    'filter',
19    'filterbank',
20    # AUBIO_UNSTABLE
21    'hist',
22    'parameter',
23    'scale',
24    'beattracking',
25    'resampler',
26    'peakpicker',
27    'pitchfcomb',
28    'pitchmcomb',
29    'pitchschmitt',
30    'pitchspecacf',
31    'pitchyin',
32    'pitchyinfft',
33    'pitchyinfast',
34    'sink',
35    'sink_apple_audio',
36    'sink_sndfile',
37    'sink_wavwrite',
38    #'mfcc',
39    'source',
40    'source_apple_audio',
41    'source_sndfile',
42    'source_avcodec',
43    'source_wavread',
44    #'sampler',
45    'audio_unit',
46    'spectral_whitening',
47]
48
49
50def get_preprocessor():
51    # findout which compiler to use
52    from distutils.sysconfig import customize_compiler
53    compiler_name = distutils.ccompiler.get_default_compiler()
54    compiler = distutils.ccompiler.new_compiler(compiler=compiler_name)
55    try:
56        customize_compiler(compiler)
57    except AttributeError as e:
58        print("Warning: failed customizing compiler ({:s})".format(repr(e)))
59
60    if hasattr(compiler, 'initialize'):
61        try:
62            compiler.initialize()
63        except ValueError as e:
64            print("Warning: failed initializing compiler ({:s})".format(repr(e)))
65
66    cpp_cmd = None
67    if hasattr(compiler, 'preprocessor'):  # for unixccompiler
68        cpp_cmd = compiler.preprocessor
69    elif hasattr(compiler, 'compiler'):  # for ccompiler
70        cpp_cmd = compiler.compiler.split()
71        cpp_cmd += ['-E']
72    elif hasattr(compiler, 'cc'):  # for msvccompiler
73        cpp_cmd = compiler.cc.split()
74        cpp_cmd += ['-E']
75
76    if not cpp_cmd:
77        print("Warning: could not guess preprocessor, using env's CC")
78        cpp_cmd = os.environ.get('CC', 'cc').split()
79        cpp_cmd += ['-E']
80    if 'emcc' in cpp_cmd:
81        cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language
82    return cpp_cmd
83
84
85def get_c_declarations(header=header, usedouble=False):
86    ''' return a dense and preprocessed  string of all c declarations implied by aubio.h
87    '''
88    cpp_output = get_cpp_output(header=header, usedouble=usedouble)
89    return filter_cpp_output (cpp_output)
90
91
92def get_cpp_output(header=header, usedouble=False):
93    ''' find and run a C pre-processor on aubio.h '''
94    cpp_cmd = get_preprocessor()
95
96    macros = [('AUBIO_UNSTABLE', 1)]
97    if usedouble:
98        macros += [('HAVE_AUBIO_DOUBLE', 1)]
99
100    if not os.path.isfile(header):
101        raise Exception("could not find include file " + header)
102
103    includes = [os.path.dirname(header)]
104    cpp_cmd += distutils.ccompiler.gen_preprocess_options(macros, includes)
105    cpp_cmd += [header]
106
107    print("Running command: {:s}".format(" ".join(cpp_cmd)))
108    proc = subprocess.Popen(cpp_cmd,
109                            stderr=subprocess.PIPE,
110                            stdout=subprocess.PIPE)
111    assert proc, 'Proc was none'
112    cpp_output = proc.stdout.read()
113    err_output = proc.stderr.read()
114    if err_output:
115        print("Warning: preprocessor produced errors or warnings:\n%s" \
116                % err_output.decode('utf8'))
117    if not cpp_output:
118        raise_msg = "preprocessor output is empty! Running command " \
119                + "\"%s\" failed" % " ".join(cpp_cmd)
120        if err_output:
121            raise_msg += " with stderr: \"%s\"" % err_output.decode('utf8')
122        else:
123            raise_msg += " with no stdout or stderr"
124        raise Exception(raise_msg)
125    if not isinstance(cpp_output, list):
126        cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]
127
128    return cpp_output
129
130def filter_cpp_output(cpp_raw_output):
131    ''' prepare cpp-output for parsing '''
132    cpp_output = filter(lambda y: len(y) > 1, cpp_raw_output)
133    cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))
134
135    i = 1
136    while 1:
137        if i >= len(cpp_output):
138            break
139        if ('{' in cpp_output[i - 1]) and ('}' not in cpp_output[i - 1]) or (';' not in cpp_output[i - 1]):
140            cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
141            cpp_output.pop(i - 1)
142        elif ('}' in cpp_output[i]):
143            cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
144            cpp_output.pop(i - 1)
145        else:
146            i += 1
147
148    # clean pointer notations
149    tmp = []
150    for l in cpp_output:
151        tmp += [l.replace(' *', ' * ')]
152    cpp_output = tmp
153
154    return cpp_output
155
156
157def get_cpp_objects_from_c_declarations(c_declarations, skip_objects=None):
158    if skip_objects is None:
159        skip_objects = default_skip_objects
160    typedefs = filter(lambda y: y.startswith('typedef struct _aubio'), c_declarations)
161    cpp_objects = [a.split()[3][:-1] for a in typedefs]
162    cpp_objects_filtered = filter(lambda y: not y[6:-2] in skip_objects, cpp_objects)
163    return cpp_objects_filtered
164
165
166def get_all_func_names_from_lib(lib):
167    ''' return flat string of all function used in lib
168    '''
169    res = []
170    for _, v in lib.items():
171        if isinstance(v, dict):
172            res += get_all_func_names_from_lib(v)
173        elif isinstance(v, list):
174            for elem in v:
175                e = elem.split('(')
176                if len(e) < 2:
177                    continue  # not a function
178                fname_part = e[0].strip().split(' ')
179                fname = fname_part[-1]
180                if fname:
181                    res += [fname]
182                else:
183                    raise NameError('gen_lib : weird function: ' + str(e))
184
185    return res
186
187
188def generate_lib_from_c_declarations(cpp_objects, c_declarations):
189    ''' returns a lib from given cpp_object names
190
191    a lib is a dict grouping functions by family (onset,pitch...)
192        each eement is itself a dict of functions grouped by puposes as :
193        struct, new, del, do, get, set and other
194    '''
195    lib = {}
196
197    for o in cpp_objects:
198        shortname = o
199        if o[:6] == 'aubio_':
200            shortname = o[6:-2]  # without aubio_ prefix and _t suffix
201
202        lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'rdo': [], 'get': [], 'set': [], 'other': []}
203        lib[shortname]['longname'] = o
204        lib[shortname]['shortname'] = shortname
205
206        fullshortname = o[:-2]  # name without _t suffix
207
208        for fn in c_declarations:
209            func_name = fn.split('(')[0].strip().split(' ')[-1]
210            if func_name.startswith(fullshortname + '_') or func_name.endswith(fullshortname):
211                # print "found", shortname, "in", fn
212                if 'typedef struct ' in fn:
213                    lib[shortname]['struct'].append(fn)
214                elif '_do' in fn:
215                    lib[shortname]['do'].append(fn)
216                elif '_rdo' in fn:
217                    lib[shortname]['rdo'].append(fn)
218                elif 'new_' in fn:
219                    lib[shortname]['new'].append(fn)
220                elif 'del_' in fn:
221                    lib[shortname]['del'].append(fn)
222                elif '_get_' in fn:
223                    lib[shortname]['get'].append(fn)
224                elif '_set_' in fn:
225                    lib[shortname]['set'].append(fn)
226                else:
227                    # print "no idea what to do about", fn
228                    lib[shortname]['other'].append(fn)
229    return lib
230
231
232def print_c_declarations_results(lib, c_declarations):
233    for fn in c_declarations:
234        found = 0
235        for o in lib:
236            for family in lib[o]:
237                if fn in lib[o][family]:
238                    found = 1
239        if found == 0:
240            print("missing", fn)
241
242    for o in lib:
243        for family in lib[o]:
244            if type(lib[o][family]) == str:
245                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
246            elif len(lib[o][family]) == 1:
247                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family][0]))
248            else:
249                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
250
251
252def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):
253    if not os.path.isdir(output_path):
254        os.mkdir(output_path)
255    elif not overwrite:
256        return sorted(glob.glob(os.path.join(output_path, '*.c')))
257
258    c_declarations = get_c_declarations(header, usedouble=usedouble)
259    cpp_objects = get_cpp_objects_from_c_declarations(c_declarations)
260
261    lib = generate_lib_from_c_declarations(cpp_objects, c_declarations)
262    # print_c_declarations_results(lib, c_declarations)
263
264    sources_list = []
265    try:
266        from .gen_code import MappedObject
267    except (SystemError, ValueError):
268        from gen_code import MappedObject
269    for o in lib:
270        out = source_header
271        mapped = MappedObject(lib[o], usedouble=usedouble)
272        out += mapped.gen_code()
273        output_file = os.path.join(output_path, 'gen-%s.c' % o)
274        with open(output_file, 'w') as f:
275            f.write(out)
276            print("wrote %s" % output_file)
277            sources_list.append(output_file)
278
279    out = source_header
280    out += "#include \"aubio-generated.h\""
281    check_types = "\n     ||  ".join(["PyType_Ready(&Py_%sType) < 0" % o for o in lib])
282    out += """
283
284int generated_types_ready (void)
285{{
286  return ({pycheck_types});
287}}
288""".format(pycheck_types=check_types)
289
290    add_types = "".join(["""
291  Py_INCREF (&Py_{name}Type);
292  PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name=o) for o in lib])
293    out += """
294
295void add_generated_objects ( PyObject *m )
296{{
297{add_types}
298}}
299""".format(add_types=add_types)
300
301    output_file = os.path.join(output_path, 'aubio-generated.c')
302    with open(output_file, 'w') as f:
303        f.write(out)
304        print("wrote %s" % output_file)
305        sources_list.append(output_file)
306
307    objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib])
308    out = """// generated list of objects created with gen_external.py
309
310#include <Python.h>
311"""
312    if usedouble:
313        out += """
314#ifndef HAVE_AUBIO_DOUBLE
315#define HAVE_AUBIO_DOUBLE 1
316#endif
317"""
318    out += """
319{objlist}
320int generated_objects ( void );
321void add_generated_objects( PyObject *m );
322""".format(objlist=objlist)
323
324    output_file = os.path.join(output_path, 'aubio-generated.h')
325    with open(output_file, 'w') as f:
326        f.write(out)
327        print("wrote %s" % output_file)
328        # no need to add header to list of sources
329
330    return sorted(sources_list)
331
332if __name__ == '__main__':
333    if len(sys.argv) > 1:
334        header = sys.argv[1]
335    if len(sys.argv) > 2:
336        output_path = sys.argv[2]
337    generate_external(header, output_path)
Note: See TracBrowser for help on using the repository browser.