source: python/lib/gen_external.py @ 9fa0ed1

feature/autosinkfeature/constantqfeature/pitchshiftfeature/pydocstringsfeature/timestretch
Last change on this file since 9fa0ed1 was 9fa0ed1, checked in by Paul Brossier <piem@piem.org>, 3 years ago

Merge branch 'master' into yinfast

  • Property mode set to 100644
File size: 9.9 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    cpp_cmd += ['-x', 'c']  # force C language (emcc defaults to c++)
81    return cpp_cmd
82
83
84def get_c_declarations(header=header, usedouble=False):
85    ''' return a dense and preprocessed  string of all c declarations implied by aubio.h
86    '''
87    cpp_cmd = get_preprocessor()
88
89    macros = [('AUBIO_UNSTABLE', 1)]
90    if usedouble:
91        macros += [('HAVE_AUBIO_DOUBLE', 1)]
92
93    if not os.path.isfile(header):
94        raise Exception("could not find include file " + header)
95
96    includes = [os.path.dirname(header)]
97    cpp_cmd += distutils.ccompiler.gen_preprocess_options(macros, includes)
98    cpp_cmd += [header]
99
100    print("Running command: {:s}".format(" ".join(cpp_cmd)))
101    proc = subprocess.Popen(cpp_cmd,
102                            stderr=subprocess.PIPE,
103                            stdout=subprocess.PIPE)
104    assert proc, 'Proc was none'
105    cpp_output = proc.stdout.read()
106    err_output = proc.stderr.read()
107    if not cpp_output:
108        raise Exception("preprocessor output is empty:\n%s" % err_output)
109    elif err_output:
110        print("Warning: preprocessor produced warnings:\n%s" % err_output)
111    if not isinstance(cpp_output, list):
112        cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]
113
114    cpp_output = filter(lambda y: len(y) > 1, cpp_output)
115    cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))
116
117    i = 1
118    while 1:
119        if i >= len(cpp_output):
120            break
121        if ('{' in cpp_output[i - 1]) and (not '}' in cpp_output[i - 1]) or (not ';' in cpp_output[i - 1]):
122            cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
123            cpp_output.pop(i - 1)
124        elif ('}' in cpp_output[i]):
125            cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
126            cpp_output.pop(i - 1)
127        else:
128            i += 1
129
130    # clean pointer notations
131    tmp = []
132    for l in cpp_output:
133        tmp += [l.replace(' *', ' * ')]
134    cpp_output = tmp
135
136    return cpp_output
137
138
139def get_cpp_objects_from_c_declarations(c_declarations, skip_objects=None):
140    if skip_objects == None:
141        skip_objects = default_skip_objects
142    typedefs = filter(lambda y: y.startswith('typedef struct _aubio'), c_declarations)
143    cpp_objects = [a.split()[3][:-1] for a in typedefs]
144    cpp_objects_filtered = filter(lambda y: not y[6:-2] in skip_objects, cpp_objects)
145    return cpp_objects_filtered
146
147
148def get_all_func_names_from_lib(lib, depth=0):
149    ''' return flat string of all function used in lib
150    '''
151    res = []
152    indent = " " * depth
153    for k, v in lib.items():
154        if isinstance(v, dict):
155            res += get_all_func_names_from_lib(v, depth + 1)
156        elif isinstance(v, list):
157            for elem in v:
158                e = elem.split('(')
159                if len(e) < 2:
160                    continue  # not a function
161                fname_part = e[0].strip().split(' ')
162                fname = fname_part[-1]
163                if fname:
164                    res += [fname]
165                else:
166                    raise NameError('gen_lib : weird function: ' + str(e))
167
168    return res
169
170
171def generate_lib_from_c_declarations(cpp_objects, c_declarations):
172    ''' returns a lib from given cpp_object names
173
174    a lib is a dict grouping functions by family (onset,pitch...)
175        each eement is itself a dict of functions grouped by puposes as :
176        struct, new, del, do, get, set and other
177    '''
178    lib = {}
179
180    for o in cpp_objects:
181        shortname = o
182        if o[:6] == 'aubio_':
183            shortname = o[6:-2]  # without aubio_ prefix and _t suffix
184
185        lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []}
186        lib[shortname]['longname'] = o
187        lib[shortname]['shortname'] = shortname
188
189        fullshortname = o[:-2]  # name without _t suffix
190
191        for fn in c_declarations:
192            func_name = fn.split('(')[0].strip().split(' ')[-1]
193            if func_name.startswith(fullshortname + '_') or func_name.endswith(fullshortname):
194                # print "found", shortname, "in", fn
195                if 'typedef struct ' in fn:
196                    lib[shortname]['struct'].append(fn)
197                elif '_do' in fn:
198                    lib[shortname]['do'].append(fn)
199                elif 'new_' in fn:
200                    lib[shortname]['new'].append(fn)
201                elif 'del_' in fn:
202                    lib[shortname]['del'].append(fn)
203                elif '_get_' in fn:
204                    lib[shortname]['get'].append(fn)
205                elif '_set_' in fn:
206                    lib[shortname]['set'].append(fn)
207                else:
208                    # print "no idea what to do about", fn
209                    lib[shortname]['other'].append(fn)
210    return lib
211
212
213def print_c_declarations_results(lib, c_declarations):
214    for fn in c_declarations:
215        found = 0
216        for o in lib:
217            for family in lib[o]:
218                if fn in lib[o][family]:
219                    found = 1
220        if found == 0:
221            print("missing", fn)
222
223    for o in lib:
224        for family in lib[o]:
225            if type(lib[o][family]) == str:
226                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
227            elif len(lib[o][family]) == 1:
228                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family][0]))
229            else:
230                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
231
232
233def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):
234    if not os.path.isdir(output_path):
235        os.mkdir(output_path)
236    elif not overwrite:
237        return sorted(glob.glob(os.path.join(output_path, '*.c')))
238
239    c_declarations = get_c_declarations(header, usedouble=usedouble)
240    cpp_objects = get_cpp_objects_from_c_declarations(c_declarations)
241
242    lib = generate_lib_from_c_declarations(cpp_objects, c_declarations)
243    # print_c_declarations_results(lib, c_declarations)
244
245    sources_list = []
246    try:
247        from .gen_code import MappedObject
248    except (SystemError, ValueError):
249        from gen_code import MappedObject
250    for o in lib:
251        out = source_header
252        mapped = MappedObject(lib[o], usedouble=usedouble)
253        out += mapped.gen_code()
254        output_file = os.path.join(output_path, 'gen-%s.c' % o)
255        with open(output_file, 'w') as f:
256            f.write(out)
257            print("wrote %s" % output_file)
258            sources_list.append(output_file)
259
260    out = source_header
261    out += "#include \"aubio-generated.h\""
262    check_types = "\n     ||  ".join(["PyType_Ready(&Py_%sType) < 0" % o for o in lib])
263    out += """
264
265int generated_types_ready (void)
266{{
267  return ({pycheck_types});
268}}
269""".format(pycheck_types=check_types)
270
271    add_types = "".join(["""
272  Py_INCREF (&Py_{name}Type);
273  PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name=o) for o in lib])
274    out += """
275
276void add_generated_objects ( PyObject *m )
277{{
278{add_types}
279}}
280""".format(add_types=add_types)
281
282    output_file = os.path.join(output_path, 'aubio-generated.c')
283    with open(output_file, 'w') as f:
284        f.write(out)
285        print("wrote %s" % output_file)
286        sources_list.append(output_file)
287
288    objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib])
289    out = """// generated list of objects created with gen_external.py
290
291#include <Python.h>
292"""
293    if usedouble:
294        out += """
295#ifndef HAVE_AUBIO_DOUBLE
296#define HAVE_AUBIO_DOUBLE 1
297#endif
298"""
299    out += """
300{objlist}
301int generated_objects ( void );
302void add_generated_objects( PyObject *m );
303""".format(objlist=objlist)
304
305    output_file = os.path.join(output_path, 'aubio-generated.h')
306    with open(output_file, 'w') as f:
307        f.write(out)
308        print("wrote %s" % output_file)
309        # no need to add header to list of sources
310
311    return sorted(sources_list)
312
313if __name__ == '__main__':
314    if len(sys.argv) > 1:
315        header = sys.argv[1]
316    if len(sys.argv) > 2:
317        output_path = sys.argv[2]
318    generate_external(header, output_path)
Note: See TracBrowser for help on using the repository browser.