source: python/lib/gen_external.py

Last change on this file was 08d07ce, checked in by Paul Brossier <piem@piem.org>, 5 years ago

Merge branch 'master' into feature/timestretch

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