summaryrefslogtreecommitdiff
path: root/build/generator/swig/header_wrappers.py
blob: 89b2802a59765787fdc78d11543d851e38bfd022 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#!/usr/bin/env python
#
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#

#
# header_wrappers.py: Generates SWIG proxy wrappers around Subversion
#                     header files
#

import os, re, sys, glob, shutil, tempfile
if __name__ == "__main__":
  parent_dir = os.path.dirname(os.path.abspath(os.path.dirname(sys.argv[0])))
  sys.path[0:0] = [ parent_dir, os.path.dirname(parent_dir) ]
from gen_base import unique, native_path, build_path_basename, build_path_join
import generator.swig

class Generator(generator.swig.Generator):
  """Generate SWIG proxy wrappers around Subversion header files"""

  def __init__(self, conf, swig_path):
    """Initialize Generator object"""
    generator.swig.Generator.__init__(self, conf, swig_path)

    # Build list of header files
    self.header_files = list(map(native_path, self.includes))
    self.header_basenames = list(map(os.path.basename, self.header_files))

  # Ignore svn_repos_parse_fns_t because SWIG can't parse it
  _ignores = ["svn_repos_parse_fns_t",
              "svn_auth_gnome_keyring_unlock_prompt_func_t",
              ]

  def write_makefile_rules(self, makefile):
    """Write makefile rules for generating SWIG wrappers for Subversion
    header files."""
    wrapper_fnames = []
    python_script = '$(abs_srcdir)/build/generator/swig/header_wrappers.py'
    makefile.write('GEN_SWIG_WRAPPER = cd $(top_srcdir) && $(PYTHON)' +
                   ' %s build.conf $(SWIG)\n\n'  % python_script)
    for fname in self.includes:
      wrapper_fname = build_path_join(self.proxy_dir,
        self.proxy_filename(build_path_basename(fname)))
      wrapper_fnames.append(wrapper_fname)
      makefile.write(
        '%s: %s %s\n' % (wrapper_fname, fname, python_script) +
        '\t$(GEN_SWIG_WRAPPER) %s\n\n' % fname
      )
    makefile.write('SWIG_WRAPPERS = %s\n\n' % ' '.join(wrapper_fnames))
    for short_name in self.short.values():
      # swig-pl needs the '.swig_checked' target here; swig-rb and swig-py
      # already reach it via a different dependency chain:
      #
      #    In build-outputs.mk, swig-py and swig-rb targets depend on *.la
      #    targets, which depend on *.lo targets, which depend on *.c targets,
      #    which depend on .swig_checked target.
      makefile.write('autogen-swig-%s: .swig_checked $(SWIG_WRAPPERS)\n' % short_name)
    makefile.write('\n\n')

  def proxy_filename(self, include_filename):
    """Convert a .h filename into a _h.swg filename"""
    return include_filename.replace(".h","_h.swg")

  def _write_nodefault_calls(self, structs):
    """Write proxy definitions to a SWIG interface file"""
    self.ofile.write("\n/* No default constructors for opaque structs */\n")
    self.ofile.write('#ifdef SWIGPYTHON\n');
    for structName, structDefinition in structs:
      if not structDefinition:
        self.ofile.write('%%nodefault %s;\n' % structName)
    self.ofile.write('#endif\n');

  def _write_includes(self, includes, base_fname):
    """Write includes to a SWIG interface file"""

    self.ofile.write('\n/* Includes */\n')
    self.ofile.write('%%{\n#include "%s"\n%%}\n' % base_fname)
    if base_fname not in self._ignores:
      self.ofile.write('%%include %s\n' % base_fname)


  def _write_callback(self, type, return_type, module, function, params,
                      callee):
    """Write out an individual callback"""

    # Get rid of any extra spaces or newlines
    return_type = ' '.join(return_type.split())
    params = ' '.join(params.split())

    # Calculate parameters
    if params == "void":
      param_names = ""
      params = "%s _obj" % type
    else:
      param_names = ", ".join(self._re_param_names.findall(params))
      params = "%s _obj, %s" % (type, params)

    invoke_callback = "%s(%s)" % (callee, param_names)
    if return_type != "void":
      invoke_callback = "return %s" % (invoke_callback)

    # Write out the declaration
    self.ofile.write(
      "static %s %s_invoke_%s(\n" % (return_type, module, function) +
      "  %s) {\n" % params +
      "  %s;\n" % invoke_callback +
      "}\n\n")


  def _write_callback_typemaps(self, callbacks):
    """Apply the CALLABLE_CALLBACK typemap to all callbacks"""

    self.ofile.write('\n/* Callback typemaps */\n')
    types = [];
    for match in callbacks:
      if match[0] and match[1]:
        # Callbacks declared as a typedef
        return_type, module, function, params = match
        type = "%s_%s_t" % (module, function)
        types.append(type)

    if types:
      self.ofile.write(
        "#ifdef SWIGPYTHON\n"
        "%%apply CALLABLE_CALLBACK {\n"
        "  %s\n"
        "};\n"
        "%%apply CALLABLE_CALLBACK * {\n"
        "  %s *\n"
        "};\n"
        "#endif\n" % ( ",\n  ".join(types), " *,\n  ".join(types) )
      );


  def _write_baton_typemaps(self, batons):
    """Apply the PY_AS_VOID typemap to all batons"""

    self.ofile.write('\n/* Baton typemaps */\n')

    if batons:
      self.ofile.write(
        "#ifdef SWIGPYTHON\n"
        "%%apply void *PY_AS_VOID {\n"
        "  void *%s\n"
        "};\n"
        "#endif\n" % ( ",\n  void *".join(batons) )
      )


  def _write_callbacks(self, callbacks):
    """Write invoker functions for callbacks"""
    self.ofile.write('\n/* Callbacks */\n')
    self.ofile.write("\n%inline %{\n")

    struct = None
    for match in callbacks:
      if match[0] and not match[1]:
        # Struct definitions
        struct = match[0]
      elif not match[0] and struct not in self._ignores:
        # Struct member callbacks
        return_type, name, params = match[1:]
        type = "%s *" % struct

        self._write_callback(type, return_type, struct[:-2], name, params,
                             "(_obj->%s)" % name)
      elif match[0] and match[1]:
        # Callbacks declared as a typedef
        return_type, module, function, params = match
        type = "%s_%s_t" % (module, function)

        if type not in self._ignores:
          self._write_callback(type, return_type, module, function, params,
                               "_obj")

    self.ofile.write("%}\n")

    self.ofile.write("\n#ifdef SWIGPYTHON\n")
    for match in callbacks:

      if match[0] and not match[1]:
        # Struct definitions
        struct = match[0]
      elif not match[0] and struct not in self._ignores:
        # Using funcptr_member_proxy, add proxy methods to anonymous
        # struct member callbacks, so that they can be invoked directly.
        return_type, name, params = match[1:]
        self.ofile.write('%%funcptr_member_proxy(%s, %s, %s_invoke_%s);\n'
          % (struct, name, struct[:-2], name))
      elif match[0] and match[1]:
        # Using funcptr_proxy, create wrapper objects for each typedef'd
        # callback, so that they can be invoked directly. The
        # CALLABLE_CALLBACK typemap (used in _write_callback_typemaps)
        # ensures that these wrapper objects are actually used.
        return_type, module, function, params = match
        self.ofile.write('%%funcptr_proxy(%s_%s_t, %s_invoke_%s);\n'
          % (module, function, module, function))
    self.ofile.write("\n#endif\n")

  def _write_proxy_definitions(self, structs):
    """Write proxy definitions to a SWIG interface file"""
    self.ofile.write('\n/* Structure definitions */\n')
    self.ofile.write('#ifdef SWIGPYTHON\n');
    for structName, structDefinition in structs:
      if structDefinition:
        self.ofile.write('%%proxy(%s);\n' % structName)
      else:
        self.ofile.write('%%opaque_proxy(%s);\n' % structName)
    self.ofile.write('#endif\n');

  """Regular expression for parsing includes from a C header file"""
  _re_includes = re.compile(r'#\s*include\s*[<"]([^<">;\s]+)')

  """Regular expression for parsing structs from a C header file"""
  _re_structs = re.compile(r'\btypedef\s+(?:struct|union)\s+'
                           r'(svn_[a-z_0-9]+)\b\s*(\{?)')

  """Regular expression for parsing callbacks declared inside structs
     from a C header file"""
  _re_struct_callbacks = re.compile(r'\btypedef\s+(?:struct|union)\s+'
                                    r'(svn_[a-z_0-9]+)\b|'
                                    r'\n[ \t]+((?!typedef)[a-z_0-9\s*]+)'
                                    r'\(\*(\w+)\)'
                                    r'\s*\(([^)]+)\);')


  """Regular expression for parsing callbacks declared as a typedef
     from a C header file"""
  _re_typed_callbacks = re.compile(r'typedef\s+([a-z_0-9\s*]+)'
                                   r'\(\*(svn_[a-z]+)_([a-z_0-9]+)_t\)\s*'
                                   r'\(([^)]+)\);');

  """Regular expression for parsing batons"""
  _re_batons = re.compile(r'void\s*\*\s*(\w*baton\w*)');

  """Regular expression for parsing parameter names from a parameter list"""
  _re_param_names = re.compile(r'\b(\w+)\s*\)*\s*(?:,|$)')

  """Regular expression for parsing comments"""
  _re_comments = re.compile(r'/\*.*?\*/')

  def _write_swig_interface_file(self, base_fname, batons, includes, structs,
                                 callbacks):
    """Convert a header file into a SWIG header file"""

    # Calculate output filename from base filename
    output_fname = os.path.join(self.proxy_dir,
      self.proxy_filename(base_fname))

    # Open a temporary output file
    if sys.version_info[0] >= 3:
      self.ofile = tempfile.TemporaryFile(dir=self.proxy_dir,
                                          mode="w+",
                                          encoding="utf8")
    else:
      self.ofile = tempfile.TemporaryFile(dir=self.proxy_dir)

    self.ofile.write('/* Proxy classes for %s\n' % base_fname)
    self.ofile.write(' * DO NOT EDIT -- AUTOMATICALLY GENERATED\n')
    self.ofile.write(' * BY build/generator/swig/header_wrappers.py */\n')

    # Write list of structs for which we shouldn't define constructors
    # by default
    self._write_nodefault_calls(structs)

    # Write typemaps for the callbacks
    self._write_callback_typemaps(callbacks)

    # Write typemaps for the batons
    self._write_baton_typemaps(batons)

    # Write includes into the SWIG interface file
    self._write_includes(includes, base_fname)

    # Write proxy definitions into the SWIG interface file
    self._write_proxy_definitions(structs)

    # Write callback definitions into the SWIG interface file
    self._write_callbacks(callbacks)

    # Copy the temporary file over to the result file.
    # Ideally we'd simply rename the temporary file to output_fname,
    # but NamedTemporaryFile() only supports its 'delete' parameter
    # in python 2.6 and above, and renaming the file while it's opened
    # exclusively is probably not a good idea.
    outputfile = open(output_fname, 'w')
    self.ofile.seek(0)
    shutil.copyfileobj(self.ofile, outputfile)

    # Close our temporary file.
    # It will also be deleted automatically.
    self.ofile.close()

    # Close our output file, too.
    outputfile.close()

  def process_header_file(self, fname):
    """Generate a wrapper around a header file"""

    # Read the contents of the header file
    contents = open(fname).read()

    # Remove comments
    contents = self._re_comments.sub("", contents)

    # Get list of includes
    includes = unique(self._re_includes.findall(contents))

    # Get list of structs
    structs = unique(self._re_structs.findall(contents))

    # Get list of batons
    batons = unique(self._re_batons.findall(contents))

    # Get list of callbacks
    callbacks = (self._re_struct_callbacks.findall(contents) +
                 self._re_typed_callbacks.findall(contents))

    # Get the location of the output file
    base_fname = os.path.basename(fname)

    # Write the SWIG interface file
    self._write_swig_interface_file(base_fname, batons, includes, structs,
                                    callbacks)

  def write(self):
    """Generate wrappers for all header files"""

    for fname in self.header_files:
      self.process_header_file(fname)

if __name__ == "__main__":
  if len(sys.argv) < 3:
    print("""Usage: %s build.conf swig [ subversion/include/header_file.h ]
Generates SWIG proxy wrappers around Subversion header files. If no header
files are specified, generate wrappers for subversion/include/*.h. """ % \
    os.path.basename(sys.argv[0]))
  else:
    gen = Generator(sys.argv[1], sys.argv[2])
    if len(sys.argv) > 3:
      for fname in sys.argv[3:]:
        gen.process_header_file(fname)
    else:
      gen.write()