source: python/lib/gen_external.py @ 8ced1e8

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

python/lib/gen_external.py: verbose compiler warnings

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