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