source: python/lib/gen_external.py @ d9a5466

feature/crepe_org
Last change on this file since d9a5466 was 0a8f63f, checked in by Paul Brossier <piem@piem.org>, 6 years ago

[py] fix missing pre-processor output on win-amd64

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