cebfc94abbd8235804600db2fe5ce380e18bb23c
[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 == 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, depth=0):
149     ''' return flat string of all function used in lib
150     '''
151     res = []
152     indent = " " * depth
153     for k, v in lib.items():
154         if isinstance(v, dict):
155             res += get_all_func_names_from_lib(v, depth + 1)
156         elif isinstance(v, list):
157             for elem in v:
158                 e = elem.split('(')
159                 if len(e) < 2:
160                     continue  # not a function
161                 fname_part = e[0].strip().split(' ')
162                 fname = fname_part[-1]
163                 if fname:
164                     res += [fname]
165                 else:
166                     raise NameError('gen_lib : weird function: ' + str(e))
167
168     return res
169
170
171 def generate_lib_from_c_declarations(cpp_objects, c_declarations):
172     ''' returns a lib from given cpp_object names
173
174     a lib is a dict grouping functions by family (onset,pitch...)
175         each eement is itself a dict of functions grouped by puposes as : 
176         struct, new, del, do, get, set and other
177     '''
178     lib = {}
179
180     for o in cpp_objects:
181         shortname = o
182         if o[:6] == 'aubio_':
183             shortname = o[6:-2]  # without aubio_ prefix and _t suffix
184
185         lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []}
186         lib[shortname]['longname'] = o
187         lib[shortname]['shortname'] = shortname
188
189         fullshortname = o[:-2]  # name without _t suffix
190
191         for fn in c_declarations:
192             func_name = fn.split('(')[0].strip().split(' ')[-1]
193             if func_name.startswith(fullshortname + '_') or func_name.endswith(fullshortname):
194                 # print "found", shortname, "in", fn
195                 if 'typedef struct ' in fn:
196                     lib[shortname]['struct'].append(fn)
197                 elif '_do' in fn:
198                     lib[shortname]['do'].append(fn)
199                 elif 'new_' in fn:
200                     lib[shortname]['new'].append(fn)
201                 elif 'del_' in fn:
202                     lib[shortname]['del'].append(fn)
203                 elif '_get_' in fn:
204                     lib[shortname]['get'].append(fn)
205                 elif '_set_' in fn:
206                     lib[shortname]['set'].append(fn)
207                 else:
208                     # print "no idea what to do about", fn
209                     lib[shortname]['other'].append(fn)
210     return lib
211
212
213 def print_c_declarations_results(lib, c_declarations):
214     for fn in c_declarations:
215         found = 0
216         for o in lib:
217             for family in lib[o]:
218                 if fn in lib[o][family]:
219                     found = 1
220         if found == 0:
221             print("missing", fn)
222
223     for o in lib:
224         for family in lib[o]:
225             if type(lib[o][family]) == str:
226                 print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
227             elif len(lib[o][family]) == 1:
228                 print("{:15s} {:10s} {:s}".format(o, family, lib[o][family][0]))
229             else:
230                 print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
231
232
233 def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):
234     if not os.path.isdir(output_path):
235         os.mkdir(output_path)
236     elif not overwrite:
237         return sorted(glob.glob(os.path.join(output_path, '*.c')))
238
239     c_declarations = get_c_declarations(header, usedouble=usedouble)
240     cpp_objects = get_cpp_objects_from_c_declarations(c_declarations)
241
242     lib = generate_lib_from_c_declarations(cpp_objects, c_declarations)
243     # print_c_declarations_results(lib, c_declarations)
244
245     sources_list = []
246     try:
247         from .gen_code import MappedObject
248     except (SystemError, ValueError):
249         from gen_code import MappedObject
250     for o in lib:
251         out = source_header
252         mapped = MappedObject(lib[o], usedouble=usedouble)
253         out += mapped.gen_code()
254         output_file = os.path.join(output_path, 'gen-%s.c' % o)
255         with open(output_file, 'w') as f:
256             f.write(out)
257             print("wrote %s" % output_file)
258             sources_list.append(output_file)
259
260     out = source_header
261     out += "#include \"aubio-generated.h\""
262     check_types = "\n     ||  ".join(["PyType_Ready(&Py_%sType) < 0" % o for o in lib])
263     out += """
264
265 int generated_types_ready (void)
266 {{
267   return ({pycheck_types});
268 }}
269 """.format(pycheck_types=check_types)
270
271     add_types = "".join(["""
272   Py_INCREF (&Py_{name}Type);
273   PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name=o) for o in lib])
274     out += """
275
276 void add_generated_objects ( PyObject *m )
277 {{
278 {add_types}
279 }}
280 """.format(add_types=add_types)
281
282     output_file = os.path.join(output_path, 'aubio-generated.c')
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     objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib])
289     out = """// generated list of objects created with gen_external.py
290
291 #include <Python.h>
292 """
293     if usedouble:
294         out += """
295 #ifndef HAVE_AUBIO_DOUBLE
296 #define HAVE_AUBIO_DOUBLE 1
297 #endif
298 """
299     out += """
300 {objlist}
301 int generated_objects ( void );
302 void add_generated_objects( PyObject *m );
303 """.format(objlist=objlist)
304
305     output_file = os.path.join(output_path, 'aubio-generated.h')
306     with open(output_file, 'w') as f:
307         f.write(out)
308         print("wrote %s" % output_file)
309         # no need to add header to list of sources
310
311     return sorted(sources_list)
312
313 if __name__ == '__main__':
314     if len(sys.argv) > 1:
315         header = sys.argv[1]
316     if len(sys.argv) > 2:
317         output_path = sys.argv[2]
318     generate_external(header, output_path)