source: python/lib/gen_external.py @ 61e9d99

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

[py] simplify import in gen_external.py

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