| #!/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() |