source: python/lib/gen_external.py @ dbdb48a

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

python/lib/gen_external.py: pass '-x c' to emcc only

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