blob: 89b2802a59765787fdc78d11543d851e38bfd022 [file] [log] [blame]
#!/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()