initial commit of experimental Phoenix code

git-svn-id: https://svn.wxwidgets.org/svn/wx/sandbox/trunk/Phoenix@66111 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2010-11-11 00:08:20 +00:00
parent 3122bf4f1f
commit ab37739a9b
38 changed files with 18652 additions and 0 deletions

1
README.txt Normal file
View File

@@ -0,0 +1 @@
See http://wiki.wxpython.org/ProjectPhoenix for details about this project.

0
buildtools/__init__.py Normal file
View File

503
buildtools/config.py Normal file
View File

@@ -0,0 +1,503 @@
#----------------------------------------------------------------------
# Name: buildtools.config
# Purpose: Code to set and validate platform options and etc. for
# the wxPython build. Moved to their own module and
# class to help setup.py to be simpler.
#
# Author: Robin Dunn
#
# Created: 3-Nov-2010
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#----------------------------------------------------------------------
import sys
import os
import glob
import fnmatch
import tempfile
from distutils.file_util import copy_file
from distutils.dir_util import mkpath
from distutils.dep_util import newer
from distutils.spawn import spawn
runSilently = False
#----------------------------------------------------------------------
class Configuration(object):
USE_SIP = True
SIP = '/projects/sip/sip/sipgen/sip'
SIPINC = 'sip/siplib' # Use our local copy of sip.h
SIPGEN = 'sip/gen' # Where the generated .zip files go
SIPFILES = 'sip' # where to find other sip files for %Include or %Import
SIPOUT = 'sip/cpp' # where to put the generated C++ code
SIPOPTS = ' '.join(['-e', # turn on exceptions support
'-k', # turn on keyword args support
'-o', # turn on auto-docstrings
#'-g', # always release and reaquire the GIL
#'-r', # turn on function call tracing
'-I', 'src'
])
WX_CONFIG = None
# Usually you shouldn't need to touch this, but you can set it to
# pass an alternate version of wx-config or alternate flags,
# eg. as required by the .deb in-tree build. By default a
# wx-config command will be assembled based on version, port,
# etc. and it will be looked for on the default $PATH.
WXPORT = 'gtk2'
# On Linux/Unix there are several ports of wxWidgets available.
# Setting this value lets you select which will be used for the
# wxPython build. Possibilites are 'gtk', 'gtk2' and 'x11'.
# Currently only gtk and gtk2 works.
BUILD_BASE = "build"
# Directory to use for temporary build files.
MONOLITHIC = 0
# The core wxWidgets lib can be built as either a single
# monolithic DLL or as a collection of DLLs. This flag controls
# which set of libs will be used on Windows. (For other platforms
# it is automatic via using wx-config.)
WXDLLVER = None
# Version part of wxWidgets LIB/DLL names
COMPILER = 'msvc'
# Used to select which compiler will be used on Windows. This not
# only affects distutils, but also some of the default flags and
# other assumptions in this script. Current supported values are
# 'msvc' and 'mingw32'
ARCH = ''
# If this is set, add an -arch XXX flag to cflags. Only tested (and
# presumably, needed) for OS X.
NO_SCRIPTS = False
# Don't install the tools/script files
PKGDIR = 'wx'
# The name of the top-level package
# ---------------------------------------------------------------
# Basic initialization and configuration code
def __init__(self):
self.CLEANUP = list()
# load the version numbers into this instance's namespace
execfile(opj(os.path.split(__file__)[0], 'version.py'), self.__dict__)
# If we're doing a dated build then alter the VERSION strings
if os.path.exists('DAILY_BUILD'):
self.VER_FLAGS += '.b' + open('DAILY_BUILD').read().strip()
self.VERSION = "%s.%s.%s.%s%s" % (self.VER_MAJOR,
self.VER_MINOR,
self.VER_RELEASE,
self.VER_SUBREL,
self.VER_FLAGS)
self.WXDLLVER = '%d%d' % (self.VER_MAJOR, self.VER_MINOR)
# change the PORT default for wxMac
if sys.platform[:6] == "darwin":
self.WXPORT = 'osx_carbon'
# and do the same for wxMSW, just for consistency
if os.name == 'nt':
self.WXPORT = 'msw'
self.parseCmdLine()
if self.WXPORT != 'msw':
# make sure we only use the compiler value on MSW builds
self.COMPILER=None
self.WXPLAT2 = None
if os.environ.has_key('WXWIN'):
self.WXDIR = os.environ['WXWIN']
else:
if os.path.exists('../wxWidgets'):
self.WXDIR = '../wxWidgets' # assumes in parallel SVN tree
else:
self.WXDIR = '..' # assumes wxPython is subdir
msg("WARNING: WXWIN not set in environment. Assuming '%s'" % self.WXDIR)
self.includes = ['sip/siplib'] # to get our version of sip.h
#---------------------------------------
# MSW specific settings
if os.name == 'nt' and self.COMPILER == 'msvc':
# Set compile flags and such for MSVC. These values are derived
# from the wxWidgets makefiles for MSVC, other compilers settings
# will probably vary...
self.WXPLAT = '__WXMSW__'
if os.environ.get('CPU', None) == 'AMD64':
self.VCDLL = 'vc_amd64_dll'
else:
self.VCDLL = 'vc_dll'
self.includes = ['include', 'src',
opj(WXDIR, 'lib', VCDLL, 'msw' + self.libFlag()),
opj(WXDIR, 'include'),
opj(WXDIR, 'contrib', 'include'),
]
self.defines = [ ('WIN32', None),
('_WINDOWS', None),
(WXPLAT, None),
('WXUSINGDLL', '1'),
('ISOLATION_AWARE_ENABLED', None),
('NDEBUG',), # using a 1-tuple makes it do an undef
]
self.libs = []
self.libdirs = [ opj(self.WXDIR, 'lib', self.VCDLL) ]
if self.MONOLITHIC:
self.libs += makeLibName('')
else:
self.libs += [ 'wxbase' + WXDLLVER + libFlag(),
'wxbase' + WXDLLVER + libFlag() + '_net',
'wxbase' + WXDLLVER + libFlag() + '_xml',
self.makeLibName('core')[0],
self.makeLibName('adv')[0],
self.makeLibName('html')[0],
]
self.libs += ['kernel32', 'user32', 'gdi32', 'comdlg32',
'winspool', 'winmm', 'shell32', 'oldnames', 'comctl32',
'odbc32', 'ole32', 'oleaut32', 'uuid', 'rpcrt4',
'advapi32', 'wsock32']
self.cflags = [ '/Gy',
'/EHsc',
# '/GX-' # workaround for internal compiler error in MSVC on some machines
]
self.lflags = None
# Other MSVC flags...
# Uncomment these to have debug info for all kinds of builds
#self.cflags += ['/Od', '/Z7']
#self.lflags = ['/DEBUG', ]
#---------------------------------------
# Posix (wxGTK, wxMac or mingw32) settings
elif os.name == 'posix' or COMPILER == 'mingw32':
self.Verify_WX_CONFIG()
self.includes = ['include', 'src']
self.defines = [ ('NDEBUG',), # using a 1-tuple makes it do an undef
]
self.libdirs = []
self.libs = []
self.cflags = self.getWxConfigValue('--cxxflags')
self.cflags = self.cflags.split()
if self.debug:
self.cflags.append('-ggdb')
self.cflags.append('-O0')
else:
self.cflags.append('-O3')
lflags = self.getWxConfigValue('--libs')
self.MONOLITHIC = (lflags.find("_xrc") == -1)
self.lflags = lflags.split()
self.WXBASENAME = self.getWxConfigValue('--basename')
self.WXRELEASE = self.getWxConfigValue('--release')
self.WXPREFIX = self.getWxConfigValue('--prefix')
# wxMac settings
if sys.platform[:6] == "darwin":
self.WXPLAT = '__WXMAC__'
if self.WXPORT == 'osx_carbon':
# Flags and such for a Darwin (Max OS X) build of Python
self.WXPLAT2 = '__WXOSX_CARBON__'
else:
self.WXPLAT2 = '__WXOSX_COCOA__'
self.libs = ['stdc++']
if not self.ARCH == "":
self.cflags.append("-arch")
self.cflags.append(self.ARCH)
self.lflags.append("-arch")
self.lflags.append(self.ARCH)
if not os.environ.get('CC') or not os.environ.get('CXX'):
os.environ["CXX"] = self.getWxConfigValue('--cxx')
os.environ["CC"] = self.getWxConfigValue('--cc')
# wxGTK settings
else:
# Set flags for other Unix type platforms
if self.WXPORT == 'gtk':
msg("WARNING: The GTK 1.x port is not supported")
self.WXPLAT = '__WXGTK__'
portcfg = os.popen('gtk-config --cflags', 'r').read()[:-1]
self.BUILD_BASE = self.BUILD_BASE + '-' + self.WXPORT
elif self.WXPORT == 'gtk2':
self.WXPLAT = '__WXGTK__'
portcfg = os.popen('pkg-config gtk+-2.0 --cflags', 'r').read()[:-1]
elif self.WXPORT == 'x11':
msg("WARNING: The wxX11 port is no supported")
self.WXPLAT = '__WXX11__'
portcfg = ''
self.BUILD_BASE = self.BUILD_BASE + '-' + self.WXPORT
elif self.WXPORT == 'msw':
self.WXPLAT = '__WXMSW__'
portcfg = ''
else:
raise SystemExit, "Unknown WXPORT value: " + self.WXPORT
self.cflags += portcfg.split()
# Some distros (e.g. Mandrake) put libGLU in /usr/X11R6/lib, but
# wx-config doesn't output that for some reason. For now, just
# add it unconditionally but we should really check if the lib is
# really found there or wx-config should be fixed.
if self.WXPORT != 'msw':
self.libdirs.append("/usr/X11R6/lib")
# Move the various -I, -D, etc. flags we got from the config scripts
# into the distutils lists.
self.cflags = self.adjustCFLAGS(self.cflags, self.defines, self.includes)
self.lflags = self.adjustLFLAGS(self.lflags, self.libdirs, self.libs)
if self.debug and self.WXPORT == 'msw' and self.COMPILER != 'mingw32':
self.defines.append( ('_DEBUG', None) )
# ---------------------------------------------------------------
# Helper functions
def parseCmdLine(self):
self.debug = '--debug' in sys.argv or '-g' in sys.argv
# the values of the items in the class namespace that start
# with an upper case letter can be overridden on the command
# line
for key, default in Configuration.__dict__.items():
if key[0] < 'A' or key[0] > 'Z':
continue
for idx, arg in enumerate(sys.argv):
if arg and arg.startswith(key + '='):
value = arg.split('=', 1)[1]
if isinstance(default, int):
value = int(value)
setattr(self, key, value)
sys.argv[idx] = None
# remove the cmd line args that we recognized
sys.argv = [arg for arg in sys.argv if arg is not None]
def Verify_WX_CONFIG(self):
"""
Called for the builds that need wx-config. If WX_CONFIG is
not set then determines the flags needed based on build
options and searches for wx-config on the PATH.
"""
# if WX_CONFIG hasn't been set to an explicit value then construct one.
if self.WX_CONFIG is None:
self.WX_CONFIG='wx-config'
port = self.WXPORT
if port == "x11":
port = "x11univ"
flags = ' --toolkit=%s' % port
flags += ' --unicode=yes'
flags += ' --version=%s.%s' % (self.VER_MAJOR, self.VER_MINOR)
searchpath = os.environ["PATH"]
for p in searchpath.split(':'):
fp = os.path.join(p, 'wx-config')
if os.path.exists(fp) and os.access(fp, os.X_OK):
# success
msg("Found wx-config: " + fp)
msg(" Using flags: " + flags)
self.WX_CONFIG = fp + flags
if runSilently:
self.WX_CONFIG += " 2>/dev/null "
break
else:
msg("ERROR: WX_CONFIG not specified and wx-config not found on the $PATH")
# TODO: execute WX_CONFIG --list and verify a matching config is found
def getWxConfigValue(self, flag):
cmd = "%s %s" % (self.WX_CONFIG, flag)
value = os.popen(cmd, 'r').read()[:-1]
return value
def build_locale_dir(self, destdir, verbose=1):
"""Build a locale dir under the wxPython package for MSW"""
moFiles = glob.glob(opj(self.WXDIR, 'locale', '*.mo'))
for src in moFiles:
lang = os.path.splitext(os.path.basename(src))[0]
dest = opj(destdir, lang, 'LC_MESSAGES')
mkpath(dest, verbose=verbose)
copy_file(src, opj(dest, 'wxstd.mo'), update=1, verbose=verbose)
self.CLEANUP.append(opj(dest, 'wxstd.mo'))
self.CLEANUP.append(dest)
def build_locale_list(self, srcdir):
# get a list of all files under the srcdir, to be used for install_data
def walk_helper(lst, dirname, files):
for f in files:
filename = opj(dirname, f)
if not os.path.isdir(filename):
lst.append( (dirname, [filename]) )
file_list = []
os.path.walk(srcdir, walk_helper, file_list)
return file_list
def find_data_files(self, srcdir, *wildcards, **kw):
# get a list of all files under the srcdir matching wildcards,
# returned in a format to be used for install_data
def walk_helper(arg, dirname, files):
if '.svn' in dirname:
return
names = []
lst, wildcards = arg
for wc in wildcards:
wc_name = opj(dirname, wc)
for f in files:
filename = opj(dirname, f)
if fnmatch.fnmatch(filename, wc_name) and not os.path.isdir(filename):
names.append(filename)
if names:
lst.append( (dirname, names ) )
file_list = []
recursive = kw.get('recursive', True)
if recursive:
os.path.walk(srcdir, walk_helper, (file_list, wildcards))
else:
walk_helper((file_list, wildcards),
srcdir,
[os.path.basename(f) for f in glob.glob(opj(srcdir, '*'))])
return file_list
def makeLibName(self, name):
if os.name == 'posix' or self.COMPILER == 'mingw32':
libname = '%s_%s-%s' % (self.WXBASENAME, name, self.WXRELEASE)
elif name:
libname = 'wxmsw%s%s_%s' % (self.WXDLLVER, self.libFlag(), name)
else:
libname = 'wxmsw%s%s' % (self.WXDLLVER, self.libFlag())
return [libname]
def libFlag(self):
if not self.debug:
rv = ''
else:
rv = 'd'
if True: ##UNICODE:
rv = 'u' + rv
return rv
def findLib(self, name, libdirs):
name = self.makeLibName(name)[0]
if os.name == 'posix' or self.COMPILER == 'mingw32':
lflags = self.getWxConfigValue('--libs')
lflags = lflags.split()
# if wx-config --libs output does not start with -L, wx is
# installed with a standard prefix and wx-config does not
# output these libdirs because they are already searched by
# default by the compiler and linker.
if lflags[0][:2] != '-L':
dirs = libdirs + ['/usr/lib', '/usr/local/lib']
else:
dirs = libdirs
name = 'lib'+name
else:
dirs = libdirs[:]
for d in dirs:
p = os.path.join(d, name)
if glob.glob(p+'*') != []:
return True
return False
def adjustCFLAGS(self, cflags, defines, includes):
"""
Extract the raw -I, -D, and -U flags from cflags and put them into
defines and includes as needed.
"""
newCFLAGS = []
for flag in cflags:
if flag[:2] == '-I':
includes.append(flag[2:])
elif flag[:2] == '-D':
flag = flag[2:]
if flag.find('=') == -1:
defines.append( (flag, None) )
else:
defines.append( tuple(flag.split('=')) )
elif flag[:2] == '-U':
defines.append( (flag[2:], ) )
else:
newCFLAGS.append(flag)
return newCFLAGS
def adjustLFLAGS(self, lflags, libdirs, libs):
"""
Extract the -L and -l flags from lflags and put them in libdirs and
libs as needed
"""
newLFLAGS = []
for flag in lflags:
if flag[:2] == '-L':
libdirs.append(flag[2:])
elif flag[:2] == '-l':
libs.append(flag[2:])
else:
newLFLAGS.append(flag)
return newLFLAGS
# We'll use a factory function so we can use the Configuration class as a singleton
_config = None
def Config(*args, **kw):
global _config
if _config is None:
_config = Configuration(*args, **kw)
return _config
#----------------------------------------------------------------------
# other helpers
def msg(text):
if not runSilently:
print text
def opj(*args):
path = os.path.join(*args)
return os.path.normpath(path)

View File

@@ -0,0 +1,375 @@
#----------------------------------------------------------------------
# Name: buildtools.distutils_hacks
# Purpose: Various hacks that have been needed to override features
# or work-around problems in Python's distutils.
#
# Author: Robin Dunn
#
# Created: 3-Nov-2010
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#----------------------------------------------------------------------
import sys
import os
import distutils.command.install
import distutils.command.install_data
import distutils.command.install_headers
import distutils.command.clean
from distutils.dep_util import newer
from config import Config
#----------------------------------------------------------------------
# New command classes
class wx_smart_install_data(distutils.command.install_data.install_data):
"""need to change self.install_dir to the actual library dir"""
def run(self):
install_cmd = self.get_finalized_command('install')
self.install_dir = getattr(install_cmd, 'install_lib')
return distutils.command.install_data.install_data.run(self)
class wx_extra_clean(distutils.command.clean.clean):
"""
Also cleans stuff that this setup.py copies itself. If the
--all flag was used also searches for .pyc, .pyd, .so files
"""
def run(self):
from distutils import log
from distutils.filelist import FileList
distutils.command.clean.clean.run(self)
cfg = Config()
if self.all:
fl = FileList()
fl.include_pattern("*.pyc", 0)
fl.include_pattern("*.pyd", 0)
fl.include_pattern("*.so", 0)
cfg.CLEANUP += fl.files
for f in cfg.CLEANUP:
if os.path.isdir(f):
try:
if not self.dry_run and os.path.exists(f):
os.rmdir(f)
log.info("removing '%s'", f)
except IOError:
log.warning("unable to remove '%s'", f)
else:
try:
if not self.dry_run and os.path.exists(f):
os.remove(f)
log.info("removing '%s'", f)
except IOError:
log.warning("unable to remove '%s'", f)
# The Ubuntu Python adds a --install-layout option to distutils that
# is used in our package build. If we detect that the current
# distutils does not have it then make sure that it is removed from
# the command-line options, otherwise the build will fail.
for item in distutils.command.install.install.user_options:
if item[0] == 'install-layout=':
break
else:
for arg in sys.argv:
if arg.startswith('--install-layout'):
sys.argv.remove(arg)
break
class wx_install(distutils.command.install.install):
"""
Turns off install_path_file
"""
def initialize_options(self):
distutils.command.install.install.initialize_options(self)
self.install_path_file = 0
class wx_install_headers(distutils.command.install_headers.install_headers):
"""
Install the header files to the WXPREFIX, with an extra dir per
filename too
"""
def initialize_options(self):
self.root = None
distutils.command.install_headers.install_headers.initialize_options(self)
def finalize_options(self):
self.set_undefined_options('install', ('root', 'root'))
distutils.command.install_headers.install_headers.finalize_options(self)
def run(self):
if os.name == 'nt':
return
headers = self.distribution.headers
if not headers:
return
cfg = Config()
root = self.root
#print "WXPREFIX is %s, root is %s" % (WXPREFIX, root)
# hack for universal builds, which append i386/ppc
# to the root
if root is None or cfg.WXPREFIX.startswith(os.path.dirname(root)):
root = ''
for header, location in headers:
install_dir = os.path.normpath(root +
cfg.WXPREFIX +
'/include/wx-%d.%d/wx' % (cfg.VER_MAJOR, cfg.VER_MINOR) +
location)
self.mkpath(install_dir)
(out, _) = self.copy_file(header, install_dir)
self.outfiles.append(out)
#----------------------------------------------------------------------
# These functions and class are copied from distutils in Python 2.5
# and then grafted back into the distutils modules so we can change
# how the -arch and -isysroot compiler args are handled. Basically if
# -arch is specified in our compiler args then we need to strip all of
# the -arch and -isysroot args provided by Python.
import distutils.unixccompiler
import distutils.sysconfig
from distutils.errors import DistutilsExecError, CompileError
def _darwin_compiler_fixup(compiler_so, cc_args):
"""
This function will strip '-isysroot PATH' and '-arch ARCH' from the
compile flags if the user has specified one them in extra_compile_flags.
This is needed because '-arch ARCH' adds another architecture to the
build, without a way to remove an architecture. Furthermore GCC will
barf if multiple '-isysroot' arguments are present.
"""
stripArch = stripSysroot = 0
compiler_so = list(compiler_so)
kernel_version = os.uname()[2] # 8.4.3
major_version = int(kernel_version.split('.')[0])
if major_version < 8:
# OSX before 10.4.0, these don't support -arch and -isysroot at
# all.
stripArch = stripSysroot = True
else:
stripArch = '-arch' in cc_args
stripSysroot = '-isysroot' in cc_args or stripArch # <== This line changed
if stripArch:
while 1:
try:
index = compiler_so.index('-arch')
# Strip this argument and the next one:
del compiler_so[index:index+2]
except ValueError:
break
if stripSysroot:
try:
index = compiler_so.index('-isysroot')
# Strip this argument and the next one:
del compiler_so[index:index+2]
except ValueError:
pass
# Check if the SDK that is used during compilation actually exists,
# the universal build requires the usage of a universal SDK and not all
# users have that installed by default.
sysroot = None
if '-isysroot' in cc_args:
idx = cc_args.index('-isysroot')
sysroot = cc_args[idx+1]
elif '-isysroot' in compiler_so:
idx = compiler_so.index('-isysroot')
sysroot = compiler_so[idx+1]
if sysroot and not os.path.isdir(sysroot):
log.warn("Compiling with an SDK that doesn't seem to exist: %s",
sysroot)
log.warn("Please check your Xcode installation")
return compiler_so
def _darwin_compiler_fixup_24(compiler_so, cc_args):
compiler_so = _darwin_compiler_fixup(compiler_so, cc_args)
return compiler_so, cc_args
class MyUnixCCompiler(distutils.unixccompiler.UnixCCompiler):
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
compiler_so = self.compiler_so
if sys.platform == 'darwin':
compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs)
try:
self.spawn(compiler_so + cc_args + [src, '-o', obj] +
extra_postargs)
except DistutilsExecError, msg:
raise CompileError, msg
_orig_parse_makefile = distutils.sysconfig.parse_makefile
def _parse_makefile(filename, g=None):
rv = _orig_parse_makefile(filename, g)
# If a different deployment target is specified in the
# environment then make sure it is put in the global
# config dict.
if os.getenv('MACOSX_DEPLOYMENT_TARGET'):
val = os.getenv('MACOSX_DEPLOYMENT_TARGET')
rv['MACOSX_DEPLOYMENT_TARGET'] = val
rv['CONFIGURE_MACOSX_DEPLOYMENT_TARGET'] = val
return rv
distutils.unixccompiler.UnixCCompiler = MyUnixCCompiler
distutils.unixccompiler._darwin_compiler_fixup = _darwin_compiler_fixup
distutils.unixccompiler._darwin_compiler = _darwin_compiler_fixup_24
distutils.sysconfig.parse_makefile = _parse_makefile
#----------------------------------------------------------------------
# Another hack-job for the CygwinCCompiler class, this time replacing
# the _compile function with one that will pass the -I flags to windres.
import distutils.cygwinccompiler
from distutils.errors import DistutilsExecError, CompileError
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
if ext == '.rc' or ext == '.res':
# gcc needs '.res' and '.rc' compiled to object files !!!
try:
#self.spawn(["windres", "-i", src, "-o", obj])
self.spawn(["windres", "-i", src, "-o", obj] +
[arg for arg in cc_args if arg.startswith("-I")] )
except DistutilsExecError, msg:
raise CompileError, msg
else: # for other files use the C-compiler
try:
self.spawn(self.compiler_so + cc_args + [src, '-o', obj] +
extra_postargs)
except DistutilsExecError, msg:
raise CompileError, msg
distutils.cygwinccompiler.CygwinCCompiler._compile = _compile
#----------------------------------------------------------------------
# Yet another distutils hack, this time for the msvc9compiler. There
# is a bug in at least version distributed with Python 2.6 where it
# adds '/pdb:None' to the linker command-line, but that just results
# in a 'None' file being created instead of putting the debug info
# into the .pyd files as expected. So we'll strip out that option via
# a monkey-patch of the msvc9compiler.MSVCCompiler.initialize method.
if os.name == 'nt' and sys.version_info >= (2,6):
import distutils.msvc9compiler
_orig_initialize = distutils.msvc9compiler.MSVCCompiler.initialize
def _initialize(self, *args, **kw):
rv = _orig_initialize(self, *args, **kw)
try:
self.ldflags_shared_debug.remove('/pdb:None')
except ValueError:
pass
return rv
distutils.msvc9compiler.MSVCCompiler.initialize = _initialize
#----------------------------------------------------------------------
from sipdistutils import build_ext
class etgsip_build_ext(build_ext):
"""
Override some parts of the SIP build command class so we can better
control how SIP is used
"""
def _find_sip(self):
cfg = Config()
return cfg.SIP
def _sip_inc_dir(self):
cfg = Config()
return cfg.SIPINC
def _sip_sipfiles_dir(self):
cfg = Config()
return cfg.SIPFILES
def _sip_output_dir(self):
cfg = Config()
return cfg.SIPOUT
def etg2sip(self, etgfile):
cfg = Config()
sipfile = os.path.splitext(os.path.basename(etgfile))[0] + '.sip'
sipfile = os.path.join(cfg.SIPGEN, sipfile)
return sipfile
def build_extension(self, extension):
"""
Modify the dependency list, adding the sip files generated
from the etg files.
"""
sources = extension.sources
if sources is not None and isinstance(sources, (list, tuple)):
etg_sources = [s for s in sources if s.startswith('etg/')]
for e in etg_sources:
extension.depends.append(self.etg2sip(e))
# let the base class do the rest
return build_ext.build_extension(self, extension)
def swig_sources (self, sources, extension):
"""
Run our ETG scripts to generate their .sip files, and adjust
the sources list before passing on to the base class, which
will then be responsible for running SIP and building the
generated C++ files.
"""
if not self.extensions:
return
cfg = Config()
etg_sources = [s for s in sources if s.startswith('etg/')]
other_sources = [s for s in sources if not s.startswith('etg/')]
for etg in etg_sources:
sipfile = self.etg2sip(etg)
if newer(etg, sipfile):
cmd = [sys.executable, etg, '--sip']
#if cfg.verbose:
# cmd.append('--verbose')
self.spawn(cmd)
if '%Module ' in file(sipfile).read():
other_sources.append(sipfile)
# now call the base class version of this method
return build_ext.swig_sources(self, other_sources, extension)

155
buildtools/sipdistutils.py Normal file
View File

@@ -0,0 +1,155 @@
# Subclasses disutils.command.build_ext,
# replacing it with a SIP version that compiles .sip -> .cpp
# before calling the original build_ext command.
# Written by Giovanni Bajo <rasky at develer dot com>
# Based on Pyrex.Distutils, written by Graham Fawcett and Darrel Gallion.
# NOTE: This has been tweaked slightly to allow the folder used for the SIP
# output to be overridden in a derived class. It is otherwise the same as the
# module provided by sip. --Robin
import distutils.command.build_ext
from distutils.dep_util import newer, newer_group
import os
import sys
from hashlib import sha1
build_ext_base = distutils.command.build_ext.build_ext
def replace_suffix(path, new_suffix):
return os.path.splitext(path)[0] + new_suffix
class build_ext (build_ext_base):
description = "Compile SIP descriptions, then build C/C++ extensions (compile/link to build directory)"
user_options = build_ext_base.user_options[:]
user_options = [opt for opt in user_options if not opt[0].startswith("swig")]
user_options += [
('sip-opts=', None,
"list of sip command line options"),
]
def initialize_options (self):
build_ext_base.initialize_options(self)
self.sip_opts = None
def finalize_options (self):
build_ext_base.finalize_options(self)
if self.sip_opts is None:
self.sip_opts = []
else:
self.sip_opts = self.sip_opts.split(' ')
def _get_sip_output_list(self, sbf):
"""
Parse the sbf file specified to extract the name of the generated source
files. Make them absolute assuming they reside in the temp directory.
"""
for L in file(sbf):
key, value = L.split("=", 1)
if key.strip() == "sources":
out = []
for o in value.split():
out.append(os.path.join(self._sip_output_dir(), o))
return out
raise RuntimeError("cannot parse SIP-generated '%s'" % sbf)
def _find_sip(self):
import sipconfig
cfg = sipconfig.Configuration()
if os.name == "nt":
if not os.path.splitext(os.path.basename(cfg.sip_bin))[1]:
return cfg.sip_bin + ".exe"
return cfg.sip_bin
def _sip_inc_dir(self):
import sipconfig
cfg = sipconfig.Configuration()
return cfg.sip_inc_dir
def _sip_sipfiles_dir(self):
import sipconfig
cfg = sipconfig.Configuration()
return cfg.default_sip_dir
def _sip_calc_signature(self):
sip_bin = self._find_sip()
return sha1(open(sip_bin, "rb").read()).hexdigest()
def _sip_signature_file(self):
return os.path.join(self._sip_output_dir(), "sip.signature")
def _sip_output_dir(self):
return self.build_temp
def build_extension (self, ext):
oldforce = self.force
if not self.force:
sip_sources = [source for source in ext.sources if source.endswith('.sip')]
if sip_sources:
sigfile = self._sip_signature_file()
if not os.path.isfile(sigfile):
self.force = True
else:
old_sig = open(sigfile).read()
new_sig = self._sip_calc_signature()
if old_sig != new_sig:
self.force = True
build_ext_base.build_extension(self, ext)
self.force = oldforce
def swig_sources (self, sources, extension=None):
if not self.extensions:
return
# Add the SIP include directory to the include path
if extension is not None:
extension.include_dirs.append(self._sip_inc_dir())
depends = extension.depends
else:
# pre-2.4 compatibility
self.include_dirs.append(self._sip_inc_dir())
depends = [] # ?
# Filter dependencies list: we are interested only in .sip files,
# since the main .sip files can only depend on additional .sip
# files. For instance, if a .h changes, there is no need to
# run sip again.
depends = [f for f in depends if os.path.splitext(f)[1] == ".sip"]
# Create the temporary directory if it does not exist already
if not os.path.isdir(self._sip_output_dir()):
os.makedirs(self._sip_output_dir())
# Collect the names of the source (.sip) files
sip_sources = []
sip_sources = [source for source in sources if source.endswith('.sip')]
other_sources = [source for source in sources if not source.endswith('.sip')]
generated_sources = []
sip_bin = self._find_sip()
for sip in sip_sources:
# Use the sbf file as dependency check
sipbasename = os.path.basename(sip)
sbf = os.path.join(self._sip_output_dir(), replace_suffix(sipbasename, ".sbf"))
if newer_group([sip]+depends, sbf) or self.force:
self._sip_compile(sip_bin, sip, sbf)
open(self._sip_signature_file(), "w").write(self._sip_calc_signature())
out = self._get_sip_output_list(sbf)
generated_sources.extend(out)
return generated_sources + other_sources
def _sip_compile(self, sip_bin, source, sbf):
self.spawn([sip_bin] + self.sip_opts +
["-c", self._sip_output_dir(),
"-b", sbf,
"-I", self._sip_sipfiles_dir(),
source])

8
buildtools/version.py Normal file
View File

@@ -0,0 +1,8 @@
# wxPython version numbers used in build
VER_MAJOR = 2 # The first three must match wxWidgets
VER_MINOR = 9
VER_RELEASE = 2
VER_SUBREL = 0 # wxPython release num for x.y.z release of wxWidgets
VER_FLAGS = "" # release flags, such as prerelease or RC num, etc.

3
etg/README.txt Normal file
View File

@@ -0,0 +1,3 @@
This directory contains Extractror-Tweaker-Generator (ETG) scripts which are
used to drive the process of converting the wxWidgets Doxygen XML files into
the files that will be fed to the bindings generator tool (SIP).

82
etg/_core.py Normal file
View File

@@ -0,0 +1,82 @@
#---------------------------------------------------------------------------
# Name: _core.py
# Author: Robin Dunn
#
# Created: 8-Nove-2010
# RCS-ID: $Id:$
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#---------------------------------------------------------------------------
PACKAGE = "wx"
MODULE = "_core"
NAME = "_core" # Base name of the file to generate to for this script
DOCSTRING = ""
# The classes and/or the basename of the Doxygen XML files to be processed by
# this script.
ITEMS = [
'defs_8h.xml'
]
#---------------------------------------------------------------------------
# Parse the XML file(s) building a collection of Extractor objects
import etgtools
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
etgtools.parseDoxyXML(module, ITEMS)
#---------------------------------------------------------------------------
# Tweak the parsed meta objects in the module object as needed for customizing
# the generated code and docstrings.
import etgtools.tweaker_tools
etgtools.tweaker_tools.ignoreAssignmentOperators(module)
etgtools.tweaker_tools.removeWxPrefixes(module)
# These items are in their own etg scripts for easier maintainability,
# but their class and function definitions are intended to be part of
# this module, not their own module. This also makes it easier to
# promote one of these to module status later if desired, simply
# remove it fron this list of Includes, and change the MODULE value in
# the promoted script to be the same as its NAME.
module.addInclude(['string',
'object',
'gdicmn',
'geometry',
])
# tweaks for defs.h
module.find('wxInt16').type = 'short'
module.find('wxInt64').type = 'long long'
module.find('wxUint64').type = 'unsigned long long'
module.find('wxIntPtr').type = 'long' #'ssize_t'
module.find('wxUIntPtr').type = 'unsigned long' #'size_t'
module.find('wxDELETE').ignore()
module.find('wxDELETEA').ignore()
module.find('wxSwap').ignore()
module.find('wxVaCopy').ignore()
# add some typedefs for wxChar, wxUChar
td = module.find('wxUIntPtr')
module.insertItemAfter(td, etgtools.TypedefDef(type='wchar_t', name='wxUChar'))
module.insertItemAfter(td, etgtools.TypedefDef(type='wchar_t', name='wxChar'))
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Run the generators
# Create the code generator and make the wrapper code
wg = etgtools.getWrapperGenerator()
wg.generate(module)
# Create a documentation generator and let it do its thing
dg = etgtools.getDocsGenerator()
dg.generate(module)
#---------------------------------------------------------------------------

228
etg/gdicmn.py Normal file
View File

@@ -0,0 +1,228 @@
#---------------------------------------------------------------------------
# Name: gdicmn.py
# Author: Robin Dunn
#
# Created: 4-Nov-2010
# RCS-ID: $Id:$
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#---------------------------------------------------------------------------
PACKAGE = "wx"
MODULE = "_core"
NAME = "gdicmn"
DOCSTRING = ""
# The classes and/or the basename of the Doxygen XML files to be processed by
# this script.
ITEMS = [
'wxPoint',
'wxSize',
'wxRect',
'wxRealPoint',
]
#---------------------------------------------------------------------------
# Parse the XML file(s) building a collection of Extractor objects
import etgtools
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
etgtools.parseDoxyXML(module, ITEMS)
#---------------------------------------------------------------------------
# Tweak the parsed meta objects in the module object as needed for customizing
# the generated code and docstrings.
import etgtools.tweaker_tools
etgtools.tweaker_tools.ignoreAssignmentOperators(module)
etgtools.tweaker_tools.removeWxPrefixes(module)
module.addHeaderCode('#include <wx/wx.h>')
# ignore some of these enum values
e = module.find('wxBitmapType')
for i in e:
if i.name.endswith('_RESOURCE'):
i.ignore()
# TODO: We need an ETG way to indicate that an item is only available
# on certain platofmrs.
e = module.find('wxStockCursor')
e.find('wxCURSOR_BASED_ARROW_DOWN').ignore()
e.find('wxCURSOR_BASED_ARROW_UP').ignore()
e.find('wxCURSOR_CROSS_REVERSE').ignore()
e.find('wxCURSOR_DOUBLE_ARROW').ignore()
module.find('wxTheColourDatabase').ignore() # TODO
module.find('wxSetCursor').ignore() # TODO
module.find('wxClientDisplayRect.x').out = True
module.find('wxClientDisplayRect.y').out = True
module.find('wxClientDisplayRect.width').out = True
module.find('wxClientDisplayRect.height').out = True
module.find('wxDisplaySize.width').out = True
module.find('wxDisplaySize.height').out = True
module.find('wxDisplaySizeMM.width').out = True
module.find('wxDisplaySizeMM.height').out = True
#---------------------------------------
# wxPoint tweaks
c = module.find('wxPoint')
# Some operators are documented within the class that shouldn't be, so just
# ignore them all.
etgtools.tweaker_tools.ignoreAllOperators(c)
# Undo a few of those ignores for legitimate items that were
# documented correctly
for f in c.find('operator+=').all() + c.find('operator-=').all():
f.ignore(False)
# Add some stand-alone function declarations for the operators that really do
# exist.
wc = etgtools.WigCode("""\
bool operator==(const wxPoint& p1, const wxPoint& p2);
bool operator!=(const wxPoint& p1, const wxPoint& p2);
wxPoint operator+(const wxPoint& p, const wxSize& s);
wxPoint operator+(const wxPoint& p1, const wxPoint& p2);
wxPoint operator+(const wxSize& s, const wxPoint& p);
wxPoint operator-(const wxPoint& p);
wxPoint operator-(const wxPoint& p, const wxSize& s);
wxPoint operator-(const wxPoint& p1, const wxPoint& p2);
wxPoint operator-(const wxSize& s, const wxPoint& p);
wxPoint operator*(const wxPoint& s, int i);
wxPoint operator*(int i, const wxPoint& s);
wxPoint operator/(const wxPoint& s, int i);
""")
module.insertItemAfter(c, wc)
# wxPoint typemap
c.convertFromPyObject = """\
// is it just a typecheck?
if (!sipIsErr) {
if (sipCanConvertToType(sipPy, sipType_wxPoint, SIP_NO_CONVERTORS))
return 1;
if (PySequence_Check(sipPy) and PySequence_Size(sipPy) == 2) {
int rval = 1;
PyObject* o1 = PySequence_ITEM(sipPy, 0);
PyObject* o2 = PySequence_ITEM(sipPy, 1);
if (!PyNumber_Check(o1) || !PyNumber_Check(o2))
rval = 0;
Py_DECREF(o1);
Py_DECREF(o2);
return rval;
}
return 0;
}
// otherwise do the conversion
if (sipPy == Py_None) {
*sipCppPtr = new wxPoint(-1, -1);
return sipGetState(sipTransferObj);
}
if (PySequence_Check(sipPy)) {
PyObject* o1 = PySequence_ITEM(sipPy, 0);
PyObject* o2 = PySequence_ITEM(sipPy, 1);
*sipCppPtr = new wxPoint(PyInt_AsLong(o1), PyInt_AsLong(o2));
Py_DECREF(o1);
Py_DECREF(o2);
return sipGetState(sipTransferObj);
}
*sipCppPtr = reinterpret_cast<wxPoint*>(sipConvertToType(
sipPy, sipType_wxPoint, sipTransferObj, SIP_NO_CONVERTORS, 0, sipIsErr));
return 0;
"""
#---------------------------------------
# wxSize tweaks
c = module.find('wxSize')
c.addProperty("width GetWidth SetWidth")
c.addProperty("height GetHeight SetHeight")
# take care of the same issues as wxPoint
etgtools.tweaker_tools.ignoreAllOperators(c)
for f in c.find('operator+=').all() + \
c.find('operator-=').all() + \
c.find('operator*=').all() + \
c.find('operator/=').all():
f.ignore(False)
wc = etgtools.WigCode("""\
bool operator==(const wxSize& s1, const wxSize& s2);
bool operator!=(const wxSize& s1, const wxSize& s2);
wxSize operator*(const wxSize& s, int i);
wxSize operator*(int i, const wxSize& s);
wxSize operator+(const wxSize& s1, const wxSize& s2);
wxSize operator-(const wxSize& s1, const wxSize& s2);
wxSize operator/(const wxSize& s, int i);
""")
module.insertItemAfter(c, wc)
#---------------------------------------
# wxRect tweaks
c = module.find('wxRect')
# take care of the same issues as wxPoint
etgtools.tweaker_tools.ignoreAllOperators(c)
for f in c.find('operator+=').all() + \
c.find('operator*=').all():
f.ignore(False)
wc = etgtools.WigCode("""\
bool operator==(const wxRect& r1, const wxRect& r2);
bool operator!=(const wxRect& r1, const wxRect& r2);
wxRect operator+(const wxRect& r1, const wxRect& r2);
wxRect operator*(const wxRect& r1, const wxRect& r2);
""")
module.insertItemAfter(c, wc)
# These methods have some overloads that will end up with the same signature
# in Python, so we have to remove one.
module.find('wxRect.Deflate').findOverload(') const').ignore()
module.find('wxRect.Inflate').findOverload(') const').ignore()
module.find('wxRect.Union').findOverload(') const').ignore()
module.find('wxRect.Intersect').findOverload(') const').ignore()
#---------------------------------------
# wxRealPoint tweaks
c = module.find('wxRealPoint')
# take care of the same issues as wxPoint
etgtools.tweaker_tools.ignoreAllOperators(c)
for f in c.find('operator+=').all() + \
c.find('operator-=').all():
f.ignore(False)
wc = etgtools.WigCode("""\
bool operator==(const wxRealPoint& p1, const wxRealPoint& p2);
bool operator!=(const wxRealPoint& p1, const wxRealPoint& p2);
wxRealPoint operator*(const wxRealPoint& s, double i);
wxRealPoint operator*(double i, const wxRealPoint& s);
wxRealPoint operator+(const wxRealPoint& p1, const wxRealPoint& p2);
wxRealPoint operator-(const wxRealPoint& p1, const wxRealPoint& p2);
wxRealPoint operator/(const wxRealPoint& s, int i);
""")
module.insertItemAfter(c, wc)
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Run the generators
# Create the code generator and make the wrapper code
wg = etgtools.getWrapperGenerator()
wg.generate(module)
# Create a documentation generator and let it do its thing
dg = etgtools.getDocsGenerator()
dg.generate(module)
#---------------------------------------------------------------------------

94
etg/geometry.py Normal file
View File

@@ -0,0 +1,94 @@
#---------------------------------------------------------------------------
# Name: geometry.py
# Author: Robin Dunn
#
# Created: 4-Nov-2010
# RCS-ID: $Id:$
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#---------------------------------------------------------------------------
PACKAGE = "wx"
MODULE = "_core"
NAME = "geometry"
DOCSTRING = ""
# The classes and/or the basename of the Doxygen XML files to be processed by
# this script.
ITEMS = [
'wxPoint2DDouble',
'wxRect2DDouble',
]
#---------------------------------------------------------------------------
# Parse the XML file(s) building a collection of Extractor objects
import etgtools
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
etgtools.parseDoxyXML(module, ITEMS)
#---------------------------------------------------------------------------
# Tweak the parsed meta objects in the module object as needed for customizing
# the generated code and docstrings.
import etgtools.tweaker_tools
etgtools.tweaker_tools.ignoreAssignmentOperators(module)
etgtools.tweaker_tools.removeWxPrefixes(module)
module.addHeaderCode('#include <wx/wx.h>')
#---------------------------------------
# wxPoint2D and wxRect2D tweaks
c = module.find('wxPoint2DDouble')
c.pyName = 'Point2D'
c.find('wxPoint2DDouble').findOverload('wxPoint2DInt').ignore()
c.find('m_x').pyName = 'x'
c.find('m_y').pyName = 'y'
c.find('GetFloor.x').out = True
c.find('GetFloor.y').out = True
c.find('GetRounded.x').out = True
c.find('GetRounded.y').out = True
# these have link errors
c.find('SetPolarCoordinates').ignore()
c.find('operator/=').findOverload('wxDouble').ignore()
c.find('operator*=').findOverload('wxDouble').ignore()
# ignore these operator methods, since we are not wrapping the Int version
c.find('operator*=').findOverload('wxInt32').ignore()
c.find('operator/=').findOverload('wxInt32').ignore()
# ignore some of the global operators too
for item in module:
if isinstance(item, etgtools.FunctionDef) and item.type == 'wxPoint2DInt':
item.ignore()
if item.name in ['operator*', 'operator/'] and 'wxInt32' in item.argsString:
item.ignore()
c = module.find('wxRect2DDouble')
c.pyName = 'Rect2D'
c.find('m_x').pyName = 'x'
c.find('m_y').pyName = 'y'
c.find('m_width').pyName = 'width'
c.find('m_height').pyName = 'height'
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Run the generators
# Create the code generator and make the wrapper code
wg = etgtools.getWrapperGenerator()
wg.generate(module)
# Create a documentation generator and let it do its thing
dg = etgtools.getDocsGenerator()
dg.generate(module)
#---------------------------------------------------------------------------

62
etg/object.py Normal file
View File

@@ -0,0 +1,62 @@
#---------------------------------------------------------------------------
# Name: object.py
# Author: Robin Dunn
#
# Created: 9-Nov-2010
# RCS-ID: $Id:$
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#---------------------------------------------------------------------------
PACKAGE = "wx"
MODULE = "_core"
NAME = "object" # Base name of the file to generate to for this script
DOCSTRING = ""
# The classes and/or the basename of the Doxygen XML files to be processed by
# this script.
ITEMS = [
'wxRefCounter',
'wxObject',
'wxClassInfo',
]
#---------------------------------------------------------------------------
# Parse the XML file(s) building a collection of Extractor objects
import etgtools
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
etgtools.parseDoxyXML(module, ITEMS)
#---------------------------------------------------------------------------
# Tweak the parsed meta objects in the module object as needed for customizing
# the generated code and docstrings.
import etgtools.tweaker_tools
etgtools.tweaker_tools.ignoreAssignmentOperators(module)
etgtools.tweaker_tools.removeWxPrefixes(module)
module.find('wxObject.operator delete').ignore()
module.find('wxObject.operator new').ignore()
module.find('wxCreateDynamicObject').ignore()
module.find('wxClassInfo').abstract = True
module.find('wxClassInfo.wxClassInfo').ignore()
module.find('wxRefCounter.~wxRefCounter').ignore(False)
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Run the generators
# Create the code generator and make the wrapper code
wg = etgtools.getWrapperGenerator()
wg.generate(module)
# Create a documentation generator and let it do its thing
dg = etgtools.getDocsGenerator()
dg.generate(module)
#---------------------------------------------------------------------------

50
etg/template.py Normal file
View File

@@ -0,0 +1,50 @@
#---------------------------------------------------------------------------
# Name:
# Author: Robin Dunn
#
# Created:
# RCS-ID: $Id:$
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#---------------------------------------------------------------------------
PACKAGE = ""
MODULE = ""
NAME = "" # Base name of the file to generate to for this script
DOCSTRING = ""
# The classes and/or the basename of the Doxygen XML files to be processed by
# this script.
ITEMS = [ ]
#---------------------------------------------------------------------------
# Parse the XML file(s) building a collection of Extractor objects
import etgtools
module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING)
etgtools.parseDoxyXML(module, ITEMS)
#---------------------------------------------------------------------------
# Tweak the parsed meta objects in the module object as needed for customizing
# the generated code and docstrings.
import etgtools.tweaker_tools
etgtools.tweaker_tools.ignoreAssignmentOperators(module)
etgtools.tweaker_tools.removeWxPrefixes(module)
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Run the generators
# Create the code generator and make the wrapper code
wg = etgtools.getWrapperGenerator()
wg.generate(module)
# Create a documentation generator and let it do its thing
dg = etgtools.getDocsGenerator()
dg.generate(module)
#---------------------------------------------------------------------------

109
etgtools/__init__.py Normal file
View File

@@ -0,0 +1,109 @@
"""
Classes and tools for describing the public API of wxWidgets, parsing
them from the Doxygen XML, and producing wrapper code from them.
"""
import sys, os
from extractors import *
#---------------------------------------------------------------------------
phoenixRoot = os.path.abspath(os.path.split(__file__)[0]+'/..')
xmlsrcbase = 'docs/doxygen/out/xml'
WXWIN = os.environ.get('WXWIN')
if not WXWIN:
for rel in ['../wxWidgets', '..']:
path = os.path.join(phoenixRoot, rel, xmlsrcbase)
if path and os.path.exists(path):
WXWIN = os.path.abspath(os.path.join(phoenixRoot, rel))
break
if WXWIN:
XMLSRC = os.path.join(WXWIN, xmlsrcbase)
assert WXWIN and os.path.exists(XMLSRC), "Unable to locate Doxygen XML files"
#---------------------------------------------------------------------------
_filesparsed = set()
def parseDoxyXML(module, class_or_filename_list):
"""
Parse a list of Doxygen XML files and add the item(s) found there to the
ModuleDef object.
If a name in the list a wx class name then the Doxygen XML filename is
calculated from that name, otherwise it is treated as a filename in the
Doxygen XML output folder.
"""
def _classToDoxyName(name):
import string
filename = 'class'
for c in name:
if c in string.ascii_uppercase:
filename += '_' + c.lower()
else:
filename += c
return os.path.join(XMLSRC, filename) + '.xml'
def _includeToDoxyName(name):
name = os.path.basename(name)
name = name.replace('.h', '_8h')
return os.path.join(XMLSRC, name) + '.xml', name + '.xml'
for class_or_filename in class_or_filename_list:
pathname = _classToDoxyName(class_or_filename)
if not os.path.exists(pathname):
pathname = os.path.join(XMLSRC, class_or_filename)
if verbose():
print "Loading %s..." % pathname
_filesparsed.add(pathname)
root = et.parse(pathname).getroot()
for element in root:
# extract and add top-level elements from the XML document
item = module.addElement(element)
# Also automatically parse the XML for the include file to get related
# typedefs, functions, enums, etc.
if hasattr(item, 'includes'):
for inc in item.includes:
pathname, name = _includeToDoxyName(inc)
if os.path.exists(pathname) \
and pathname not in _filesparsed \
and name not in class_or_filename_list:
class_or_filename_list.append(name)
_filesparsed.clear()
#---------------------------------------------------------------------------
def getWrapperGenerator():
"""
A simple factory function to create a wrapper generator class of the desired type.
"""
if '--dump' in sys.argv:
import generators
gClass = generators.DumpWrapperGenerator
elif '--swig' in sys.argv:
import swig_generator
gClass = swig_generator.SwigWrapperGenerator
elif '--sip' in sys.argv:
import sip_generator
gClass = sip_generator.SipWrapperGenerator
else:
# The default is sip, at least for now...
import sip_generator
gClass = sip_generator.SipWrapperGenerator
return gClass()
#---------------------------------------------------------------------------
def getDocsGenerator():
import generators
g = generators.StubbedDocsGenerator()
return g
#---------------------------------------------------------------------------

845
etgtools/extractors.py Normal file
View File

@@ -0,0 +1,845 @@
# Functions and classes that can parse the Doxygen XML files and extract the
# wxWidgets API info which we need from them.
import sys
import os
import pprint
import xml.etree.ElementTree as et
#---------------------------------------------------------------------------
# TODOs
#
# * Need a way to idicate that items are not available on certain platforms
#
# * Something for TypeHeader code for classes
#
# * something for associating typemap or convert to/from code for classes
#
#---------------------------------------------------------------------------
# These classes simply hold various bits of information about the classes,
# methods, functions and other items in the C/C++ API being wrapped.
#
# NOTE: Currently very little is being done with the docstrings. They can
# either be reprocessed later by the document generator or we can do more
# tinkering with them here. It just depends on decisions not yet made...
#---------------------------------------------------------------------------
class BaseDef(object):
"""
The base class for all element types and provides the common attributes
and functions that they all share.
"""
nameTag = 'name'
def __init__(self, element=None):
self.name = '' # name of the item
self.pyName = '' # rename to this name
self.ignored = False # skip this item
self.briefDoc = '' # either a string or a single para Element
self.detailedDoc = [] # collection of para Elements
# The items list is used by some subclasses to collect items that are
# part of that item, like methods of a ClassDef, etc.
self.items = []
if element is not None:
self.extract(element)
def __iter__(self):
return iter(self.items)
def extract(self, element):
# Pull info from the ElementTree element that is pertinent to this
# class. Should be overridden in derived classes to get what each one
# needs in addition to the base.
self.name = element.find(self.nameTag).text
bd = element.find('briefdescription')
if len(bd):
self.briefDoc = bd[0] # Should be just one <para> element
self.detailedDoc = list(element.find('detaileddescription'))
def dump(self, indent=0, stream=None):
# debug helper, prints the extracted items
txt = ("name: %s\n"
"pyName: %s\n"
"ignored: %s\n"
"briefDoc: %s\n"
"detailedDoc: %s\n" ) % \
(self.name, self.pyName, self.ignored, self.briefDoc,
_pf(self.detailedDoc, indent))
_print(txt, indent, stream)
def ignore(self, val=True):
self.ignored = val
def find(self, name):
"""
Locate and return an item within this item that has a matching name.
The name string can use a dotted notation to continue the search
recursively. Raises ExtractorError if not found.
"""
try:
head, tail = name.split('.', 1)
except ValueError:
head, tail = name, None
for item in self._findItems():
if item.name == head or item.pyName == head:
if not tail:
return item
else:
return item.find(tail)
else: # got though all items with no match
raise ExtractorError("Unable to find item named '%s' within %s named '%s'" %
(head, self.__class__.__name__, self.name))
def insertItem(self, index, item):
self.items.insert(index, item)
def insertItemAfter(self, after, item):
try:
idx = self.items.index(after)
self.items.insert(idx+1, item)
except ValueError:
self.items.append(item)
def insertItemBefore(self, before, item):
try:
idx = self.items.index(before)
self.items.insert(idx, item)
except ValueError:
self.items.insert(0, item)
def allItems(self):
"""
Recursively create a sequence for traversing all items in the
collection. A generator would be nice but just prebuilding a list will
be good enough.
"""
items = [self]
for item in self.items:
items.extend(item.allItems())
if hasattr(item, 'overloads'):
for o in item.overloads:
items.extend(o.allItems())
return items
def _findItems(self):
# If there are more items to be searched than what is in self.items, a
# subclass can override this to give a different list.
return self.items
#---------------------------------------------------------------------------
class VariableDef(BaseDef):
"""
Represents a basic variable declaration.
"""
def __init__(self, element=None, **kw):
super(VariableDef, self).__init__()
self.type = None
self.definition = ''
self.argsString = ''
self.__dict__.update(**kw)
if element is not None:
self.extract(element)
def extract(self, element):
super(VariableDef, self).extract(element)
self.type = flattenNode(element.find('type'))
self.definition = element.find('definition').text
self.argsString = element.find('argsstring').text
def dump(self, indent=0, stream=None):
super(VariableDef, self).dump(indent, stream)
txt = ("type: %s\n"
"definition: %s\n"
"argsString: %s\n"
) % (self.type, self.definition, self.argsString)
_print(txt, indent, stream)
#---------------------------------------------------------------------------
# These need the same attributes as VariableDef, but we use separate classes
# so we can identify what kind of element it came from originally.
class GlobalVarDef(VariableDef):
pass
class TypedefDef(VariableDef):
pass
#---------------------------------------------------------------------------
class MemberVarDef(VariableDef):
"""
Represents a variable declaration in a class.
"""
def __init__(self, element=None, **kw):
super(MemberVarDef, self).__init__()
self.isStatic = False
self.protection = ''
self.__dict__.update(kw)
if element is not None:
self.extract(element)
def extract(self, element):
super(MemberVarDef, self).extract(element)
self.isStatic = element.get('static') == 'yes'
self.protection = element.get('prot')
assert self.protection in ['public', 'protected']
# TODO: Should protected items be ignored by default or should we
# leave that up to the tweaker code or the generators?
if self.protection == 'protected':
self.ignore()
def dump(self, indent=0, stream=None):
super(MemberVarDef, self).dump(indent, stream)
txt = ("isStatic: %s\n"
"protection: %s\n"
) % (self.isStatic, self.protection)
_print(txt, indent, stream)
#---------------------------------------------------------------------------
class FunctionDef(BaseDef):
"""
Information about a standalone function.
"""
def __init__(self, element=None, **kw):
super(FunctionDef, self).__init__()
self.type = None
self.definition = ''
self.argsString = ''
self.pyArgsString = ''
self.isOverloaded = False
self.overloads = []
self.deprecated = False # is the function deprecated
self.factory = False # a factory function that creates a new instance of the return value
self.pyReleaseGIL = False # release the Python GIL for this function call
self.noCopy = False # don't make a copy of the return value, just wrap the original
self.transfer = False # transfer ownership of return value to C++?
self.transferBack = False # transfer ownership of return value from C++ to Python?
self.transferThis = False # ownership of 'this' pointer transfered to this arg
self.__dict__.update(kw)
if element is not None:
self.extract(element)
def releaseGIL(self, release=True):
self.pyReleaseGIL = hold
def extract(self, element):
super(FunctionDef, self).extract(element)
self.type = flattenNode(element.find('type'))
self.definition = element.find('definition').text
self.argsString = element.find('argsstring').text
for node in element.findall('param'):
p = ParamDef(node)
self.items.append(p)
# TODO: Look at self.detailedDoc and pull out any matching
# parameter description items and assign that value as the
# briefDoc for this ParamDef object.
def dump(self, indent=0, stream=None):
super(FunctionDef, self).dump(indent, stream)
txt = ("type: %s\n"
"definition: %s\n"
"argsString: %s\n"
"pyArgsString: %s\n"
"isOverloaded: %s\n"
"deprecated: %s\n"
"factory: %s\n"
"releaseGIL: %s\n"
"noCopy: %s\n"
"transfer: %s\n"
"transferBack: %s\n"
"transferThis: %s\n"
) % (self.type, self.definition, self.argsString, self.pyArgsString,
self.isOverloaded, self.deprecated, self.factory, self.pyReleaseGIL,
self.noCopy,
self.transfer, self.transferBack, self.transferThis)
_print(txt, indent, stream)
if self.items:
for p in self.items:
assert isinstance(p, ParamDef)
_print("ParamDef:", indent, stream)
p.dump(indent+4, stream)
_print("\n", indent, stream)
if self.overloads:
_print("Overloaded functions:", indent, stream)
for f in self.overloads:
_print("MethodDef:", indent+4, stream)
f.dump(indent+8, stream)
_print("\n", indent, stream)
def checkForOverload(self, methods):
for m in methods:
if isinstance(m, MethodDef) and m.name == self.name:
m.overloads.append(self)
m.isOverloaded = self.isOverloaded = True
return True
return False
def all(self):
return [self] + self.overloads
def findOverload(self, matchText):
"""
Search for an overloaded method that has matchText in its C++ argsString.
"""
for o in self.all():
if matchText in o.argsString:
return o
return None
def _findItems(self):
items = list(self.items)
for o in self.overloads:
items.extend(o.items)
return items
#---------------------------------------------------------------------------
class MethodDef(FunctionDef):
"""
Represents a class method, ctor or dtor declaration.
"""
def __init__(self, element=None, className=None, **kw):
super(MethodDef, self).__init__()
self.isVirtual = False
self.isStatic = False
self.isCtor = False
self.isDtor = False
self.protection = ''
self.defaultCtor = False # use this ctor as the default one
self.__dict__.update(kw)
if element is not None:
self.extract(element, className)
def extract(self, element, className):
super(MethodDef, self).extract(element)
self.isStatic = element.get('static') == 'yes'
self.isVirtual = element.get('virt') == 'virtual'
self.isCtor = self.name == className
self.isDtor = self.name == '~' + className
self.protection = element.get('prot')
assert self.protection in ['public', 'protected']
# TODO: Should protected items be ignored by default or should we
# leave that up to the tweaker code or the generators?
if self.protection == 'protected':
self.ignore()
def dump(self, indent=0, stream=None):
super(MethodDef, self).dump(indent, stream)
txt = ("isCtor: %s\n"
"isDtor: %s\n"
"isStatic: %s\n"
"isVirtual: %s\n"
"protection: %s\n"
"defaultCtor: %s\n"
) % (self.isCtor, self.isDtor, self.isStatic, self.isVirtual,
self.protection, self.defaultCtor)
_print(txt, indent, stream)
#---------------------------------------------------------------------------
class ParamDef(BaseDef):
"""
A parameter of a function or method.
"""
def __init__(self, element=None, **kw):
super(ParamDef, self).__init__()
self.type = '' # data type
self.default = '' # default value
self.out = False # is it an output arg?
self.inOut = False # is it both input and output?
self.array = False # the param is to be treated as an array
self.arraySize = False # the param is the size of the array
self.transfer = False # transfer ownership of arg to C++?
self.transferBack = False # transfer ownership of arg from C++ to Python?
self.transferThis = False # ownership of 'this' pointer transfered to this arg
self.__dict__.update(kw)
if element is not None:
self.extract(element)
def extract(self, element):
self.name = element.find('declname').text
self.type = flattenNode(element.find('type'))
if element.find('defval') is not None:
self.default = flattenNode(element.find('defval'))
def dump(self, indent=0, stream=None):
#super(ParamDef, self).dump(indent, stream)
txt = ("name: %s\n"
"type: %s\n"
"default: %s\n"
"out: %s\n"
"inOut: %s\n"
"array: %s\n"
"arraySize: %s\n"
"transfer: %s\n"
"transferBack: %s\n"
"transferThis: %s\n"
) % (self.name, self.type, self.default, self.out, self.inOut,
self.array, self.arraySize,
self.transfer, self.transferBack, self.transferThis)
_print(txt, indent, stream)
#---------------------------------------------------------------------------
class ClassDef(BaseDef):
"""
The information about a class that is needed to generate wrappers for it.
"""
nameTag = 'compoundname'
def __init__(self, element=None, **kw):
super(ClassDef, self).__init__()
self.bases = [] # base class names
self.includes = [] # .h file for this class
self.abstract = False # is it an abstract base class?
self.deprecated = False # mark all methods as deprecated
self.external = False # class is in another module
self.noDefCtor = False # do not generate a default constructor
self.singlton = False # class is a singleton so don't call the dtor until the interpreter exits
self.convertFromPyObject = None
self.convertToPyObject = None
self.__dict__.update(kw)
if element is not None:
self.extract(element)
def extract(self, element):
super(ClassDef, self).extract(element)
for node in element.findall('basecompoundref'):
self.bases.append(node.text)
for node in element.findall('includes'):
self.includes.append(node.text)
# TODO: Is it possible for there to be memberdef's w/o a sectiondef?
for node in element.findall('sectiondef/memberdef'):
# skip any private items
if node.get('prot') == 'private':
continue
kind = node.get('kind')
if kind == 'function':
m = MethodDef(node, self.name)
if not m.checkForOverload(self.items):
self.items.append(m)
elif kind == 'variable':
v = MemberVarDef(node)
self.items.append(v)
elif kind == 'enum':
e = EnumDef(node)
self.items.append(e)
else:
raise ExtractorError('Unknown memberdef kind: %s' % kind)
# TODO: do we need support for nested classes?
def dump(self, indent=0, stream=None):
super(ClassDef, self).dump(indent, stream)
txt = ("bases: %s\n"
"includes: %s\n"
"abstract: %s\n"
"deprecated: %s\n"
"external: %s\n"
"noDefCtor: %s\n"
"singleton: %s\n"
) % (_pf(self.bases, indent), _pf(self.includes, indent),
self.abstract, self.deprecated, self.external, self.noDefCtor, self.singlton)
_print(txt, indent, stream)
for item in self.items:
_print("%s:" % item.__class__.__name__, indent, stream)
item.dump(indent+4, stream)
_print("\n", indent, stream)
def addProperty(self, *args, **kw):
"""
Add a property to a class, with a name, getter function and optionally
a setter method.
"""
# As a convenience allow the name, getter and (optionally) the setter
# to be passed as a single string. Otherwise the args will be passed
# as-is to PropertyDef
if len(args) == 1:
name = getter = setter = ''
split = args[0].split()
assert len(split) in [2 ,3]
if len(split) == 2:
name, getter = split
else:
name, getter, setter = split
p = PropertyDef(name, getter, setter, **kw)
else:
p = PropertyDef(*args, **kw)
self.items.append(p)
return p
def addCppMethod(self, type, name, argsString, body, doc=None, **kw):
"""
Add a new C++ method to a class.
"""
md = CppMethodDef(type, name, argsString, body, doc, **kw)
self.items.append(md)
return md
def addCppCtor(self, argsString, body, doc=None, **kw):
"""
Add a C++ method that is a constructor.
"""
md = CppMethodDef('', self.name, argsString, body, doc=doc, isCtor=True, **kw)
self.items.append(md)
return md
def addPyMethod(self, ):
# TODO: Add the ability to inject a method implememnted in Python into
# a wrapped class
raise NotImplementedError
#---------------------------------------------------------------------------
class EnumDef(BaseDef):
"""
A named or anonymous enumeration.
"""
def __init__(self, element=None, inClass=False, **kw):
super(EnumDef, self).__init__()
prot = element.get('prot')
if prot is not None:
self.protection = prot
assert self.protection in ['public', 'protected']
# TODO: Should protected items be ignored by default or should we
# leave that up to the tweaker code or the generators?
if self.protection == 'protected':
self.ignore()
self.__dict__.update(kw)
if element is not None:
self.extract(element)
def extract(self, element):
super(EnumDef, self).extract(element)
for node in element.findall('enumvalue'):
value = EnumValueDef(node)
self.items.append(value)
def dump(self, indent=0, stream=None):
super(EnumDef, self).dump(indent, stream)
for v in self.items:
_print("EnumValueDef:", indent, stream)
v.dump(indent+4, stream)
_print("\n", indent, stream)
class EnumValueDef(BaseDef):
"""
An item in an enumeration.
"""
def __init__(self, element=None, **kw):
super(EnumValueDef, self).__init__()
if element is not None:
self.extract(element)
self.__dict__.update(kw)
#---------------------------------------------------------------------------
class PropertyDef(BaseDef):
"""
Use the C++ methods of a class to make a Python property.
NOTE: This one is not automatically extracted, but can be added to
classes in the tweaker stage
"""
def __init__(self, name, getter, setter=None, doc=None, **kw):
super(PropertyDef, self).__init__()
self.name = name
self.getter = getter
self.setter = setter
self.briefDoc = doc
self.protection = 'public'
self.__dict__.update(kw)
def dump(self, indent=0, stream=None):
# debug helper, prints the extracted items
txt = ("name: %s\n"
"getter: %s\n"
"setter: %s\n"
"briefDoc: %s\n") % \
(self.name, self.getter, self.setter, self.briefDoc)
_print(txt, indent, stream)
#---------------------------------------------------------------------------
class CppMethodDef(MethodDef):
"""
This class provides information that can be used to add the code for a new
method to a wrapper class that does not actually exist in the real C++
class, or it can be used to provide an alternate implementation for a
method that does exist. The backend generator support for this feature
would be things like %extend in SWIG or %MethodCode in SIP.
NOTE: This one is not automatically extracted, but can be added to
classes in the tweaker stage
"""
def __init__(self, type, name, argsString, body, doc=None, isCtor=False, **kw):
super(CppMethodDef, self).__init__()
self.type = type
self.name = name
self.argsString = argsString
self.body = body
self.briefDoc = doc
self.isCtor = isCtor
self.protection = 'public'
self.__dict__.update(kw)
def dump(self, indent=0, stream=None):
# debug helper, prints the extracted items
txt = ("type: %s\n"
"name: %s\n"
"argsString: %s\n"
"briefDoc: %s\n"
"body: %s\n") % \
(self.type, self.name, self.argsString, self.briefDoc, self.body)
_print(txt, indent, stream)
#---------------------------------------------------------------------------
class WigCode(BaseDef):
"""
This class allows code defined by the triggers to be injected into the
generated Wrapper Interface Generator file.
"""
def __init__(self, code, **kw):
super(WigCode, self).__init__()
self.code = code
self.protection = 'public'
self.__dict__.update(kw)
#---------------------------------------------------------------------------
class ModuleDef(BaseDef):
"""
This class holds all the items that will be in the generated module
"""
def __init__(self, package, module, name, briefDoc='', detailedDoc=None):
super(ModuleDef, self).__init__()
self.package = package
self.module = module
self.name = name
self.briefDoc = briefDoc
if detailedDoc:
self.detailedDoc = detailedDoc
self.headerCode = []
self.cppCode = []
self.initializerCode = []
self.preInitializerCode = []
self.postInitializerCode = []
self.includes = []
self.imports = []
def addHeaderCode(self, code):
if isinstance(code, list):
self.headerCode.extend(code)
else:
self.headerCode.append(code)
def addCppCode(self, code):
if isinstance(code, list):
self.cppCode.extend(code)
else:
self.cppCode.append(code)
def addInitializerCode(self, code):
if isinstance(code, list):
self.initializerCode.extend(code)
else:
self.initializerCode.append(code)
def addPreInitializerCode(self, code):
if isinstance(code, list):
self.preInitializerCode.extend(code)
else:
self.preInitializerCode.append(code)
def addPostInitializerCode(self, code):
if isinstance(code, list):
self.postInitializerCode.extend(code)
else:
self.postInitializerCode.append(code)
def addInclude(self, name):
if isinstance(name, list):
self.includes.extend(name)
else:
self.includes.append(name)
def addImport(self, name):
if isinstance(name, list):
self.imports.extend(name)
else:
self.imports.append(name)
def addElement(self, element):
item = None
kind = element.get('kind')
if kind == 'class':
extractingMsg(kind, element, ClassDef.nameTag)
item = ClassDef(element)
self.items.append(item)
elif kind == 'function':
extractingMsg(kind, element)
item = FunctionDef(element)
if not item.checkForOverload(self.items):
self.items.append(item)
elif kind == 'enum':
extractingMsg(kind, element)
item = EnumDef(element)
self.items.append(item)
elif kind == 'variable':
extractingMsg(kind, element)
item = GlobalVarDef(element)
self.items.append(item)
elif kind == 'typedef':
extractingMsg(kind, element)
item = TypedefDef(element)
self.items.append(item)
elif kind == 'define':
skippingMsg(kind, element)
elif kind == 'file':
for node in element.findall('sectiondef/memberdef'):
self.addElement(node)
else:
raise ExtractorError('Unknown module item kind: %s' % kind)
return item
def dump(self, indent=0, stream=None):
super(ModuleDef, self).dump(indent, stream)
txt = ("includes: %s\n"
"imports: %s\n"
"headerCode: %s\n"
"cppCode: %s\n"
"pyCode: %s\n"
"initializerCode: %s\n"
) % (_pf(self.includes, indent),
_pf(self.imports, indent),
_pf(self.headerCode, indent),
_pf(self.cppCode, indent),
_pf(self.pyCode, indent),
_pf(self.initializerCode, indent))
_print(txt, indent, stream)
_print('\n', indent, stream)
for item in self.items:
_print(item.__class__.__name__+":", indent, stream)
item.dump(indent+4, stream)
_print('\n', indent, stream)
def addCppFunction(self, type, name, argsString, body, doc=None, **kw):
"""
Add a new C++ function into the module that is written by hand, not
wrapped.
"""
md = CppMethodDef(type, name, argsString, body, doc, **kw)
self.items.append(md)
return md
def addPyCode(self, code):
# TODO: Add the ability to have some python code mingled with the
# extension code. One way to do this is change the name of the %Module
# (if there are any pyCode items) to be _module, and then generate a
# module.py with this Python content, and have it do "from _module import *"
# The user code would still "import module" but would end up getting access
# to the combined items.
raise NotImplementedError
#---------------------------------------------------------------------------
# Some helper functions and such
#---------------------------------------------------------------------------
def flattenNode(node):
"""
Extract just the text from a node and its children, tossing out any child
node tags and attributes.
"""
text = node.text or ""
for n in node:
text += flattenNode(n)
if node.tail:
text += node.tail.rstrip()
return text.rstrip()
class ExtractorError(RuntimeError):
pass
def _print(value, indent, stream):
if stream is None:
stream = sys.stdout
indent = ' ' * indent
for line in str(value).splitlines():
stream.write("%s%s\n" % (indent, line))
def _pf(item, indent):
if indent == 0:
indent = 4
txt = pprint.pformat(item, indent)
if '\n' in txt:
txt = '\n' + txt
return txt
def verbose():
return '--verbose' in sys.argv
def extractingMsg(kind, element, nameTag='name'):
if verbose():
print 'Extracting %s: %s' % (kind, element.find(nameTag).text)
def skippingMsg(kind, element):
if verbose():
print 'Skipping %s: %s' % (kind, element.find('name').text)
#---------------------------------------------------------------------------

34
etgtools/generators.py Normal file
View File

@@ -0,0 +1,34 @@
#---------------------------------------------------------------------------
# Just some base classes and stubs for the various generators
class WrapperGeneratorBase(object):
def __init__(self):
pass
def generate(self, module, destFile=None):
raise NotImplementedError
class DumpWrapperGenerator(WrapperGeneratorBase):
"""This one is just for debugging"""
def generate(self, module, destFile=None):
print "\n----------------------------------------------------------"
print "Dump of Module Objects"
print "----------------------------------------------------------"
module.dump()
class DocsGeneratorBase(object):
def __init__(self):
pass
def generate(self, module):
raise NotImplementedError
class StubbedDocsGenerator(DocsGeneratorBase):
def generate(self, module):
pass
#---------------------------------------------------------------------------

371
etgtools/sip_generator.py Normal file
View File

@@ -0,0 +1,371 @@
import sys, os
import extractors
import generators
from cStringIO import StringIO
divider = '//' + '-'*75 + '\n'
phoenixRoot = os.path.abspath(os.path.split(__file__)[0]+'/..')
#---------------------------------------------------------------------------
class SipWrapperGenerator(generators.WrapperGeneratorBase):
def generate(self, module, destFile=None):
stream = StringIO()
# generate SIP code from the module and its objects
self.generateModule(module, stream)
# Write the contents of the stream to the destination file
if not destFile:
destFile = os.path.join(phoenixRoot, 'sip/gen', module.name + '.sip')
file(destFile, 'wt').write(stream.getvalue())
#-----------------------------------------------------------------------
def generateModule(self, module, stream):
assert isinstance(module, extractors.ModuleDef)
# write the file header
stream.write(divider + """\
// This file is generated by wxPython's SIP generator. Do not edit by hand.
//
// Copyright: (c) 2010 by Total Control Software
// Licence: wxWindows license
""")
if module.name == module.module:
stream.write("""
%%Module %s.%s
%%Copying
Copyright: (c) 2010 by Total Control Software
Licence: wxWindows license
%%End
%%RealArgNames
""" % (module.package, module.name))
else:
stream.write("//\n// This file is included from %s.sip\n//\n" % module.module)
stream.write(divider)
# %Imports and %Includes
for i in module.imports:
stream.write("%%Import %s.sip\n" % i)
stream.write("\n")
for i in module.includes:
stream.write("%%Include %s.sip\n" % i)
# C++ code to be written out to the generated module
if module.headerCode:
stream.write("\n%ModuleHeaderCode\n")
for c in module.headerCode:
stream.write('%s\n' % c)
stream.write("%End\n\n")
if module.cppCode:
stream.write("%ModuleCode\n")
for c in module.cppCode:
stream.write('%s\n' % c)
stream.write("%End\n")
stream.write('\n%s\n' % divider)
# Now generate each of the items in the module
self.generateModuleItems(module, stream)
# Add code for the module initialization sections.
if module.preInitializerCode:
stream.write('\n%s\n\n%%PreInitialisationCode\n' % divider)
for i in module.preInitializerCode:
stream.write('%s\n' % i)
stream.write('%End\n')
if module.initializerCode:
stream.write('\n%s\n\n%%InitialisationCode\n' % divider)
for i in module.initializerCode:
stream.write('%s\n' % i)
stream.write('%End\n')
if module.postInitializerCode:
stream.write('\n%s\n\n%%PostInitialisationCode\n' % divider)
for i in module.postInitializerCode:
stream.write('%s\n' % i)
stream.write('%End\n')
stream.write('\n%s\n' % divider)
def generateModuleItems(self, module, stream):
methodMap = {
extractors.ClassDef : self.generateClass,
extractors.FunctionDef : self.generateFunction,
extractors.EnumDef : self.generateEnum,
extractors.GlobalVarDef : self.generateGlobalVar,
extractors.TypedefDef : self.generateTypedef,
extractors.WigCode : self.generateWigCode,
}
for item in module:
if item.ignored:
continue
function = methodMap[item.__class__]
function(item, stream)
#-----------------------------------------------------------------------
def generateFunction(self, function, stream):
assert isinstance(function, extractors.FunctionDef)
if not function.ignored:
stream.write('%s %s(' % (function.type, function.name))
if function.items:
stream.write('\n')
self.generateParameters(function.items, stream, ' '*4)
stream.write(')%s;\n' % self.annotate(function))
for f in function.overloads:
self.generateFunction(f, stream)
stream.write('\n')
def generateParameters(self, parameters, stream, indent):
for idx, param in enumerate(parameters):
if param.ignored:
continue
stream.write(indent)
stream.write('%s %s' % (param.type, param.name))
if param.default:
stream.write(' = %s' % param.default)
stream.write(self.annotate(param))
if not idx == len(parameters)-1:
stream.write(',')
stream.write('\n')
#-----------------------------------------------------------------------
def generateEnum(self, enum, stream, indent=''):
assert isinstance(enum, extractors.EnumDef)
if enum.ignored:
return
name = enum.name
if name.startswith('@'):
name = ''
stream.write('%senum %s%s\n{\n' % (indent, name, self.annotate(enum)))
values = []
for v in enum.items:
if v.ignored:
continue
values.append("%s %s%s" % (indent, v.name, self.annotate(v)))
stream.write(',\n'.join(values))
stream.write('%s\n};\n\n' % (indent, ))
#-----------------------------------------------------------------------
def generateGlobalVar(self, globalVar, stream):
assert isinstance(globalVar, extractors.GlobalVarDef)
if globalVar.ignored:
return
stream.write('%s %s' % (globalVar.type, globalVar.name))
stream.write('%s;\n\n' % self.annotate(globalVar))
#-----------------------------------------------------------------------
def generateTypedef(self, typedef, stream):
assert isinstance(typedef, extractors.TypedefDef)
if typedef.ignored:
return
stream.write('typedef %s %s' % (typedef.type, typedef.name))
stream.write('%s;\n\n' % self.annotate(typedef))
#-----------------------------------------------------------------------
def generateWigCode(self, wig, stream, indent=''):
assert isinstance(wig, extractors.WigCode)
lines = [indent+line for line in wig.code.split('\n')]
stream.write('\n'.join(lines))
stream.write('\n\n')
#-----------------------------------------------------------------------
def generateClass(self, klass, stream, indent=''):
assert isinstance(klass, extractors.ClassDef)
if klass.ignored:
return
# write the class header
stream.write('%sclass %s' % (indent, klass.name))
if klass.bases:
stream.write(' : ')
stream.write(', '.join(klass.bases))
stream.write(self.annotate(klass))
stream.write('\n%s{\n' % indent)
if klass.includes:
stream.write('%s%%TypeHeaderCode\n' % indent)
for inc in klass.includes:
stream.write('%s #include <%s>\n' % (indent, inc))
stream.write('%s%%End\n' % indent)
stream.write('\n%spublic:\n' % indent)
# Split the items into public and protected groups
ctors = [i for i in klass if
isinstance(i, extractors.MethodDef) and
i.protection == 'public' and (i.isCtor or i.isDtor)]
public = [i for i in klass if i.protection == 'public' and i not in ctors]
protected = [i for i in klass if i.protection == 'protected']
dispatch = {
extractors.MemberVarDef : self.generateMemberVar,
extractors.PropertyDef : self.generateProperty,
extractors.MethodDef : self.generateMethod,
extractors.EnumDef : self.generateEnum,
extractors.CppMethodDef : self.generateCppMethod,
extractors.WigCode : self.generateWigCode,
# TODO: nested classes too?
}
for item in ctors:
f = dispatch[item.__class__]
f(item, stream, indent + ' '*4)
for item in public:
f = dispatch[item.__class__]
f(item, stream, indent + ' '*4)
if protected and [i for i in protected if not i.ignored]:
stream.write('\nprotected:\n')
for item in protected:
f = dispatch[item.__class__]
f(item, stream, indent + ' '*4)
if klass.convertFromPyObject:
stream.write('%s%%ConvertToTypeCode\n' % indent)
lines = [indent+l for l in klass.convertFromPyObject.split('\n')]
stream.write('\n'.join(lines))
stream.write('%s%%End\n' % indent)
if klass.convertToPyObject:
stream.write('%s%%ConvertFromTypeCode\n' % indent)
lines = [indent+l for l in klass.convertToPyObject.split('\n')]
stream.write('\n'.join(lines))
stream.write('%s%%End\n' % indent)
stream.write('%s}; // end of class %s\n\n\n' % (indent, klass.name))
def generateMemberVar(self, memberVar, stream, indent):
assert isinstance(memberVar, extractors.MemberVarDef)
if memberVar.ignored:
return
stream.write('%s%s %s' % (indent, memberVar.type, memberVar.name))
stream.write('%s;\n' % self.annotate(memberVar))
def generateProperty(self, prop, stream, indent):
assert isinstance(prop, extractors.PropertyDef)
if prop.ignored:
return
stream.write('%s%%Property(name=%s, get=%s' % (indent, prop.name, prop.getter))
if prop.setter:
stream.write(', set=%s' % prop.setter)
stream.write(')')
if prop.briefDoc:
stream.write(' // %s' % prop.briefDoc)
stream.write('\n')
def generateMethod(self, method, stream, indent):
assert isinstance(method, extractors.MethodDef)
if not method.ignored:
if method.isVirtual:
stream.write("%svirtual\n" % indent)
if method.isStatic:
stream.write("%sstatic\n" % indent)
if method.isCtor or method.isDtor:
stream.write('%s%s(' % (indent, method.name))
else:
stream.write('%s%s %s(' % (indent, method.type, method.name))
if method.items:
stream.write('\n')
self.generateParameters(method.items, stream, indent+' '*4)
stream.write(indent)
stream.write(')%s;\n\n' % self.annotate(method))
if method.overloads:
for m in method.overloads:
self.generateMethod(m, stream, indent)
def generateCppMethod(self, method, stream, indent):
assert isinstance(method, extractors.CppMethodDef)
if method.ignored:
return
if method.isCtor:
stream.write('%s%s%s%s;\n' %
(indent, method.name, method.argsString, self.annotate(method)))
else:
stream.write('%s%s %s%s%s;\n' %
(indent, method.type, method.name, method.argsString,
self.annotate(method)))
stream.write('%s%%MethodCode\n' % indent)
lines = [indent+l for l in method.body.split('\n')]
stream.write('\n'.join(lines))
if len(lines) == 1:
stream.write('\n%s' % indent)
stream.write('%End\n\n')
#-----------------------------------------------------------------------
def annotate(self, item):
annotations = []
if item.pyName:
annotations.append('PyName=%s' % item.pyName)
if isinstance(item, extractors.ParamDef):
if item.out:
annotations.append('Out')
if item.inOut:
annotations.extend(['In', 'Out'])
if item.array:
annotations.append('Array')
if item.arraySize:
annotations.append('ArraySize')
if isinstance(item, (extractors.ParamDef, extractors.FunctionDef)):
if item.transfer:
annotations.append('Transfer')
if item.transferBack:
annotations.append('TransferBack')
if item.transferThis:
annotations.append('TranserThis')
if isinstance(item, extractors.FunctionDef):
if item.deprecated:
annotations.append('Deprecated')
if item.factory:
annotations.append('Factory')
if item.pyReleaseGIL: # else HoldGIL??
annotations.append('ReleaseGIL')
if item.noCopy:
annotations.append('NoCopy')
if isinstance(item, extractors.MethodDef):
if item.defaultCtor:
annotations.append('Default')
if isinstance(item, extractors.ClassDef):
if item.abstract:
annotations.append('Abstract')
if item.deprecated:
annotations.append('Deprecated')
if item.external:
annotations.append('External')
if item.noDefCtor:
annotations.append('NoDefaultCtors')
if item.singlton:
annotations.append('DelayDtor')
if annotations:
return ' /%s/' % ', '.join(annotations)
else:
return ''
#---------------------------------------------------------------------------

View File

View File

@@ -0,0 +1 @@
# Move along, there's nothing to see here...

42
etgtools/tweaker_tools.py Normal file
View File

@@ -0,0 +1,42 @@
import extractors
def removeWxPrefixes(node):
"""
Rename items with a 'wx' prefix to not have the prefix.
"""
for item in node.allItems():
if not item.pyName \
and item.name.startswith('wx') \
and not item.name.startswith('wxEVT_') \
and not isinstance(item, (extractors.TypedefDef,
extractors.MethodDef )): # TODO: Any others?
item.pyName = item.name[2:]
def ignoreAssignmentOperators(node):
"""
Set the ignored flag for all class methods that are assignment operators
"""
for item in node.allItems():
if isinstance(item, extractors.MethodDef) and item.name == 'operator=':
item.ignore()
def ignoreAllOperators(node):
"""
Set the ignored flag for all class methods that are any kind of operator
"""
for item in node.allItems():
if isinstance(item, extractors.MethodDef) and item.name.startswith('operator'):
item.ignore()
def createPyArgsStrings(node):
"""
TODO: Create a pythonized version of the argsString in function and method
items that can be used as part of the docstring.
"""
pass

176
setup.py Normal file
View File

@@ -0,0 +1,176 @@
#----------------------------------------------------------------------
# Name: setup.py
# Purpose: Distutils build script for wxPython
#
# Author: Robin Dunn
#
# Created: 3-Nov-2010
# Copyright: (c) 2010 by Total Control Software
# Licence: wxWindows license
#----------------------------------------------------------------------
import sys, os
from distutils.core import setup, Extension
from distutils.file_util import copy_file
from distutils.dir_util import mkpath
from distutils.dep_util import newer
from distutils.spawn import spawn
from buildtools.config import Config, msg, opj
import buildtools.distutils_hacks as hacks
#----------------------------------------------------------------------
NAME = "wxPython (phoenix)"
DESCRIPTION = "Cross platform GUI toolkit for Python"
AUTHOR = "Robin Dunn"
AUTHOR_EMAIL = "Robin Dunn <robin@alldunn.com>"
URL = "http://wxPython.org/"
DOWNLOAD_URL = "http://wxPython.org/download.php"
LICENSE = "wxWidgets Library License (LGPL derivative)"
PLATFORMS = "WIN32,WIN64,OSX,POSIX"
KEYWORDS = "GUI,wx,wxWindows,wxWidgets,cross-platform,awesome"
LONG_DESCRIPTION = """\
wxPython is a GUI toolkit for Python that is a wrapper around the
wxWidgets C++ GUI library. wxPython provides a large variety of
window types and controls, all implemented with a native look and
feel, by using the native widgets where possible.
"""
CLASSIFIERS = """\
Development Status :: 6 - Mature
Environment :: MacOS X :: Carbon
Environment :: Win32 (MS Windows)
Environment :: Win64 (MS Windows)
Environment :: X11 Applications :: GTK
Intended Audience :: Developers
License :: OSI Approved
Operating System :: MacOS :: MacOS X
Operating System :: Microsoft :: Windows :: Windows 2000/XP/Vista/7
Operating System :: POSIX
Programming Language :: Python
Topic :: Software Development :: User Interfaces
"""
#----------------------------------------------------------------------
# Create a buildtools.config.Configuration object
cfg = Config()
# Ensure that the directory containing this script is on the python
# path for spawned commands so the builder and phoenix packages can be
# found.
thisdir = os.path.abspath(os.path.split(__file__)[0])
os.environ['PYTHONPATH'] = thisdir + os.pathsep + os.environ.get('PYTHONPATH', '')
WX_PKGLIST = [ cfg.PKGDIR ]
SCRIPTS = None
DATA_FILES = []
HEADERS = None
BUILD_OPTIONS = { 'build_base' : cfg.BUILD_BASE }
if cfg.WXPORT == 'msw':
BUILD_OPTIONS[ 'compiler' ] = cfg.COMPILER
copy_file('src/__init__.py', cfg.PKGDIR, update=1, verbose=0)
cfg.CLEANUP.append(opj(cfg.PKGDIR, '__init__.py'))
# update the license files
mkpath('license')
for file in ['preamble.txt', 'licence.txt', 'licendoc.txt', 'lgpl.txt']:
copy_file(opj(cfg.WXDIR, 'docs', file), opj('license',file), update=1, verbose=0)
cfg.CLEANUP.append(opj('license',file))
cfg.CLEANUP.append('license')
if sys.platform in ['win32', 'darwin']:
cfg.build_locale_dir(opj(cfg.PKGDIR, 'locale'))
DATA_FILES += cfg.build_locale_list(opj(cfg.PKGDIR, 'locale'))
if os.name == 'nt':
rc_file = ['src/wxc.rc']
else:
rc_file = []
#----------------------------------------------------------------------
extensions = [
Extension('siplib', ['sip/siplib/apiversions.c',
'sip/siplib/bool.cpp',
'sip/siplib/descriptors.c',
'sip/siplib/objmap.c',
'sip/siplib/qtlib.c',
'sip/siplib/siplib.c',
'sip/siplib/threads.c',
'sip/siplib/voidptr.c',
],
include_dirs = cfg.includes,
define_macros = cfg.defines,
library_dirs = cfg.libdirs,
libraries = cfg.libs,
extra_compile_args = cfg.cflags,
extra_link_args = cfg.lflags,
),
Extension('_core', ['etg/_core.py',
'etg/object.py',
'etg/gdicmn.py',
'etg/geometry.py',
],
include_dirs = cfg.includes,
define_macros = cfg.defines,
library_dirs = cfg.libdirs,
libraries = cfg.libs,
extra_compile_args = cfg.cflags,
extra_link_args = cfg.lflags,
),
]
#----------------------------------------------------------------------
if __name__ == '__main__':
setup(name = NAME, #'wxPython',
version = cfg.VERSION,
description = DESCRIPTION,
long_description = LONG_DESCRIPTION,
author = AUTHOR,
author_email = AUTHOR_EMAIL,
url = URL,
download_url = DOWNLOAD_URL,
license = LICENSE,
platforms = PLATFORMS,
classifiers = [c for c in CLASSIFIERS.split("\n") if c],
keywords = KEYWORDS,
packages = WX_PKGLIST,
#extra_path = EXTRA_PATH,
ext_package = cfg.PKGDIR,
ext_modules = extensions,
options = { 'build' : BUILD_OPTIONS,
'build_ext' : {'sip_opts' : cfg.SIPOPTS },
},
scripts = SCRIPTS,
data_files = DATA_FILES,
headers = HEADERS,
# Override some of the default distutils command classes with my own
cmdclass = { 'install' : hacks.wx_install,
'install_data' : hacks.wx_smart_install_data,
'install_headers' : hacks.wx_install_headers,
'clean' : hacks.wx_extra_clean,
'build_ext' : hacks.etgsip_build_ext,
},
)

3
sip/cpp/README.txt Normal file
View File

@@ -0,0 +1,3 @@
This folder is where the ouput of SIP is placed. These will be the C++ source
and header files, as well as a file for each module that specifies what files
were generated for that module (which is used by the build scripts.)

5
sip/gen/README.txt Normal file
View File

@@ -0,0 +1,5 @@
This directory is where wxPython's ETG scripts will deposit the
wrapper generator files which describe the wxWidgets APIs that
wxPython wraps. These files will be fed to SIP to procude the C++ code
for the extension modules.

48
sip/siplib/LICENSE Normal file
View File

@@ -0,0 +1,48 @@
RIVERBANK COMPUTING LIMITED LICENSE AGREEMENT FOR SIP
1. This LICENSE AGREEMENT is between Riverbank Computing Limited ("Riverbank"),
and the Individual or Organization ("Licensee") accessing and otherwise using
SIP software in source or binary form and its associated documentation. SIP
comprises a software tool for generating Python bindings for software C and C++
libraries, and a Python extension module used at runtime by those generated
bindings.
2. Subject to the terms and conditions of this License Agreement, Riverbank
hereby grants Licensee a nonexclusive, royalty-free, world-wide license to
reproduce, analyze, test, perform and/or display publicly, prepare derivative
works, distribute, and otherwise use SIP alone or in any derivative version,
provided, however, that Riverbank's License Agreement and Riverbank's notice of
copyright, e.g., "Copyright (c) 2010 Riverbank Computing Limited; All Rights
Reserved" are retained in SIP alone or in any derivative version prepared by
Licensee.
3. In the event Licensee prepares a derivative work that is based on or
incorporates SIP or any part thereof, and wants to make the derivative work
available to others as provided herein, then Licensee hereby agrees to include
in any such work a brief summary of the changes made to SIP.
4. Licensee may not use SIP to generate Python bindings for any C or C++
library for which bindings are already provided by Riverbank.
5. Riverbank is making SIP available to Licensee on an "AS IS" basis.
RIVERBANK MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY
OF EXAMPLE, BUT NOT LIMITATION, RIVERBANK MAKES NO AND DISCLAIMS ANY
REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
PURPOSE OR THAT THE USE OF SIP WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
6. RIVERBANK SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF SIP FOR ANY
INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING,
DISTRIBUTING, OR OTHERWISE USING SIP, OR ANY DERIVATIVE THEREOF, EVEN IF
ADVISED OF THE POSSIBILITY THEREOF.
7. This License Agreement will automatically terminate upon a material breach
of its terms and conditions.
8. Nothing in this License Agreement shall be deemed to create any relationship
of agency, partnership, or joint venture between Riverbank and Licensee. This
License Agreement does not grant permission to use Riverbank trademarks or
trade name in a trademark sense to endorse or promote products or services of
Licensee, or any third party.
9. By copying, installing or otherwise using SIP, Licensee agrees to be bound
by the terms and conditions of this License Agreement.

11
sip/siplib/README.txt Normal file
View File

@@ -0,0 +1,11 @@
This folder contains a copy of the SIP runtime library code. It is
here so we can make it part of the wxPython build instead of needing
to have a dependency upon SIP already being installed on user
machines.
3rd party extension modules that need to use or interact with wxPython
types or other items will need to ensure that they #include the sip.h
located in this folder so they will know the proper module name to
import to find this version of the runtime library. This feature was
added in SIP 4.12.

295
sip/siplib/apiversions.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* The implementation of the supprt for setting API versions.
*
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <Python.h>
#include <string.h>
#include "sip.h"
#include "sipint.h"
/*
* The structure that defines the version number of an API.
*/
typedef struct _apiVersionDef {
/* The name of the API. */
const char *api_name;
/*
* The version number of the API. This will either be set explicitly via
* a call to sip.setapi() or implicitly by an imported module.
*/
int version_nr;
/* The next in the list of APIs. */
struct _apiVersionDef *next;
} apiVersionDef;
/*
* The list of API versions.
*/
static apiVersionDef *api_versions = NULL;
/*
* Forward declarations.
*/
static int add_api(const char *api, int version_nr);
static apiVersionDef *find_api(const char *api);
/*
* See if a range of versions of a particular API is enabled.
*/
int sip_api_is_api_enabled(const char *name, int from, int to)
{
const apiVersionDef *avd;
if ((avd = find_api(name)) == NULL)
return FALSE;
if (from > 0 && avd->version_nr < from)
return FALSE;
if (to > 0 && avd->version_nr >= to)
return FALSE;
return TRUE;
}
/*
* Initialise the the API for a module and return a negative value on error.
*/
int sipInitAPI(sipExportedModuleDef *em, PyObject *mod_dict)
{
int *apis, i;
sipVersionedFunctionDef *vf;
sipTypeDef **tdp;
/* See if the module defines any APIs. */
if ((apis = em->em_versions) != NULL)
{
while (apis[0] >= 0)
{
/*
* See if it is an API definition rather than a range
* definition.
*/
if (apis[2] < 0)
{
const char *api_name;
const apiVersionDef *avd;
api_name = sipNameFromPool(em, apis[0]);
/* Use the default version if not already set explicitly. */
if ((avd = find_api(api_name)) == NULL)
if (add_api(api_name, apis[1]) < 0)
return -1;
}
apis += 3;
}
}
/* Add any versioned global functions to the module dictionary. */
if ((vf = em->em_versioned_functions) != NULL)
{
while (vf->vf_name >= 0)
{
if (sipIsRangeEnabled(em, vf->vf_api_range))
{
const char *func_name = sipNameFromPool(em, vf->vf_name);
PyMethodDef *pmd;
PyObject *py_func;
if ((pmd = sip_api_malloc(sizeof (PyMethodDef))) == NULL)
return -1;
pmd->ml_name = SIP_MLNAME_CAST(func_name);
pmd->ml_meth = vf->vf_function;
pmd->ml_flags = vf->vf_flags;
pmd->ml_doc = vf->vf_docstring;
if ((py_func = PyCFunction_New(pmd, NULL)) == NULL)
return -1;
if (PyDict_SetItemString(mod_dict, func_name, py_func) < 0)
{
Py_DECREF(py_func);
return -1;
}
Py_DECREF(py_func);
}
++vf;
}
}
/* Update the types table according to any version information. */
for (tdp = em->em_types, i = 0; i < em->em_nrtypes; ++i, ++tdp)
{
sipTypeDef *td;
if ((td = *tdp) != NULL && td->td_version >= 0)
{
do
{
if (sipIsRangeEnabled(em, td->td_version))
{
/* Update the type with the enabled version. */
*tdp = td;
break;
}
}
while ((td = td->td_next_version) != NULL);
/*
* If there is no enabled version then stub the disabled version
* so that we don't lose the name from the (sorted) types table.
*/
if (td == NULL)
sipTypeSetStub(*tdp);
}
}
return 0;
}
/*
* Get the version number for an API.
*/
PyObject *sipGetAPI(PyObject *self, PyObject *args)
{
const char *api;
const apiVersionDef *avd;
if (!PyArg_ParseTuple(args, "s:getapi", &api))
return NULL;
if ((avd = find_api(api)) == NULL)
{
PyErr_Format(PyExc_ValueError, "unknown API '%s'", api);
return NULL;
}
#if PY_MAJOR_VERSION >= 3
return PyLong_FromLong(avd->version_nr);
#else
return PyInt_FromLong(avd->version_nr);
#endif
}
/*
* Set the version number for an API.
*/
PyObject *sipSetAPI(PyObject *self, PyObject *args)
{
const char *api;
int version_nr;
const apiVersionDef *avd;
if (!PyArg_ParseTuple(args, "si:setapi", &api, &version_nr))
return NULL;
if (version_nr < 1)
{
PyErr_Format(PyExc_ValueError,
"API version numbers must be greater or equal to 1, not %d",
version_nr);
return NULL;
}
if ((avd = find_api(api)) == NULL)
{
char *api_copy;
/* Make a deep copy of the name. */
if ((api_copy = sip_api_malloc(strlen(api) + 1)) == NULL)
return NULL;
strcpy(api_copy, api);
if (add_api(api_copy, version_nr) < 0)
return NULL;
}
else if (avd->version_nr != version_nr)
{
PyErr_Format(PyExc_ValueError,
"API '%s' has already been set to version %d", api,
avd->version_nr);
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
/*
* Add a new API to the global list returning a negative value on error.
*/
static int add_api(const char *api, int version_nr)
{
apiVersionDef *avd;
if ((avd = sip_api_malloc(sizeof (apiVersionDef))) == NULL)
return -1;
avd->api_name = api;
avd->version_nr = version_nr;
avd->next = api_versions;
api_versions = avd;
return 0;
}
/*
* Return the definition for the given API, or NULL if there was none.
*/
static apiVersionDef *find_api(const char *api)
{
apiVersionDef *avd;
for (avd = api_versions; avd != NULL; avd = avd->next)
if (strcmp(avd->api_name, api) == 0)
break;
return avd;
}
/*
* Return TRUE if a range defined by a range index is enabled.
*/
int sipIsRangeEnabled(sipExportedModuleDef *em, int range_index)
{
int *range = &em->em_versions[range_index * 3];
const char *api_name = sipNameFromPool(em, range[0]);
return sip_api_is_api_enabled(api_name, range[1], range[2]);
}

22
sip/siplib/bool.cpp Normal file
View File

@@ -0,0 +1,22 @@
// This contains all the C++ code that is needed by the sip module.
//
// Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
//
// This file is part of SIP.
//
// This copy of SIP is licensed for use under the terms of the SIP License
// Agreement. See the file LICENSE for more details.
//
// This copy of SIP may also used under the terms of the GNU General Public
// License v2 or v3 as published by the Free Software Foundation which can be
// found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
//
// SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// Set a C++ bool for the main C implementation of the module.
extern "C" void sipSetBool(void *ptr, int val)
{
*reinterpret_cast<bool *>(ptr) = val;
}

305
sip/siplib/descriptors.c Normal file
View File

@@ -0,0 +1,305 @@
/*
* The implementation of the different descriptors.
*
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <Python.h>
#include "sip.h"
#include "sipint.h"
/*****************************************************************************
* A method descriptor. We don't use the similar Python descriptor because it
* doesn't support a method having static and non-static overloads.
*****************************************************************************/
/* Forward declarations of slots. */
static PyObject *sipMethodDescr_descr_get(PyObject *self, PyObject *obj,
PyObject *type);
static PyObject *sipMethodDescr_repr(PyObject *self);
/*
* The object data structure.
*/
typedef struct _sipMethodDescr {
PyObject_HEAD
/* The method definition. */
PyMethodDef *pmd;
} sipMethodDescr;
/*
* The type data structure.
*/
PyTypeObject sipMethodDescr_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"sip.methoddescriptor", /* tp_name */
sizeof (sipMethodDescr), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
sipMethodDescr_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
sipMethodDescr_descr_get, /* tp_descr_get */
};
/*
* Return a new method descriptor for the given method.
*/
PyObject *sipMethodDescr_New(PyMethodDef *pmd)
{
PyObject *descr = PyType_GenericAlloc(&sipMethodDescr_Type, 0);
if (descr != NULL)
((sipMethodDescr *)descr)->pmd = pmd;
return descr;
}
/*
* The descriptor's descriptor get slot.
*/
static PyObject *sipMethodDescr_descr_get(PyObject *self, PyObject *obj,
PyObject *type)
{
sipMethodDescr *md = (sipMethodDescr *)self;
if (obj == Py_None)
obj = NULL;
return PyCFunction_New(md->pmd, obj);
}
/*
* The descriptor's repr slot. This is for the benefit of cProfile which seems
* to determine attribute names differently to the rest of Python.
*/
static PyObject *sipMethodDescr_repr(PyObject *self)
{
sipMethodDescr *md = (sipMethodDescr *)self;
return
#if PY_MAJOR_VERSION >= 3
PyUnicode_FromFormat
#else
PyString_FromFormat
#endif
("<built-in method %s>", md->pmd->ml_name);
}
/*****************************************************************************
* A variable descriptor. We don't use the similar Python descriptor because
* it doesn't support static variables.
*****************************************************************************/
/* Forward declarations of slots. */
static PyObject *sipVariableDescr_descr_get(PyObject *self, PyObject *obj,
PyObject *type);
static int sipVariableDescr_descr_set(PyObject *self, PyObject *obj,
PyObject *value);
/*
* The object data structure.
*/
typedef struct _sipVariableDescr {
PyObject_HEAD
/* The getter/setter definition. */
sipVariableDef *vd;
/* The generated type definition. */
const sipTypeDef *td;
/* The generated container definition. */
const sipContainerDef *cod;
} sipVariableDescr;
/*
* The type data structure.
*/
PyTypeObject sipVariableDescr_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"sip.variabledescriptor", /* tp_name */
sizeof (sipVariableDescr), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
sipVariableDescr_descr_get, /* tp_descr_get */
sipVariableDescr_descr_set, /* tp_descr_set */
};
/* Forward declarations. */
static int get_instance_address(sipVariableDescr *vd, PyObject *obj,
void **addrp);
/*
* Return a new method descriptor for the given getter/setter.
*/
PyObject *sipVariableDescr_New(sipVariableDef *vd, const sipTypeDef *td,
const sipContainerDef *cod)
{
PyObject *descr = PyType_GenericAlloc(&sipVariableDescr_Type, 0);
if (descr != NULL)
{
((sipVariableDescr *)descr)->vd = vd;
((sipVariableDescr *)descr)->td = td;
((sipVariableDescr *)descr)->cod = cod;
}
return descr;
}
/*
* The descriptor's descriptor get slot.
*/
static PyObject *sipVariableDescr_descr_get(PyObject *self, PyObject *obj,
PyObject *type)
{
sipVariableDescr *vd = (sipVariableDescr *)self;
void *addr;
if (get_instance_address(vd, obj, &addr) < 0)
return NULL;
return ((sipVariableGetterFunc)vd->vd->vd_getter)(addr, type);
}
/*
* The descriptor's descriptor set slot.
*/
static int sipVariableDescr_descr_set(PyObject *self, PyObject *obj,
PyObject *value)
{
sipVariableDescr *vd = (sipVariableDescr *)self;
void *addr;
/* Check that the value isn't const. */
if (vd->vd->vd_setter == NULL)
{
PyErr_Format(PyExc_AttributeError,
"'%s' object attribute '%s' is read-only",
sipPyNameOfContainer(vd->cod, vd->td), vd->vd->vd_name);
return -1;
}
if (get_instance_address(vd, obj, &addr) < 0)
return -1;
return ((sipVariableSetterFunc)vd->vd->vd_setter)(addr, value, obj);
}
/*
* Return the C/C++ address of any instance.
*/
static int get_instance_address(sipVariableDescr *vd, PyObject *obj,
void **addrp)
{
void *addr;
if (vd->vd->vd_type == ClassVariable)
{
addr = NULL;
}
else
{
/* Check that access was via an instance. */
if (obj == NULL || obj == Py_None)
{
PyErr_Format(PyExc_AttributeError,
"'%s' object attribute '%s' is an instance attribute",
sipPyNameOfContainer(vd->cod, vd->td), vd->vd->vd_name);
return -1;
}
/* Get the C++ instance. */
if ((addr = sip_api_get_cpp_ptr((sipSimpleWrapper *)obj, vd->td)) == NULL)
return -1;
}
*addrp = addr;
return 0;
}

307
sip/siplib/objmap.c Normal file
View File

@@ -0,0 +1,307 @@
/*
* This module implements a hash table class for mapping C/C++ addresses to the
* corresponding wrapped Python object.
*
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <string.h>
#include "sip.h"
#include "sipint.h"
#define hash_1(k,s) (((unsigned long)(k)) % (s))
#define hash_2(k,s) ((s) - 2 - (hash_1((k),(s)) % ((s) - 2)))
/* Prime numbers to use as hash table sizes. */
static unsigned long hash_primes[] = {
521, 1031, 2053, 4099,
8209, 16411, 32771, 65537, 131101, 262147,
524309, 1048583, 2097169, 4194319, 8388617, 16777259,
33554467, 67108879, 134217757, 268435459, 536870923, 1073741827,
2147483659U,0
};
static sipHashEntry *newHashTable(unsigned long);
static sipHashEntry *findHashEntry(sipObjectMap *,void *);
static void reorganiseMap(sipObjectMap *om);
static void *getUnguardedPointer(sipSimpleWrapper *w);
/*
* Initialise an object map.
*/
void sipOMInit(sipObjectMap *om)
{
om -> primeIdx = 0;
om -> unused = om -> size = hash_primes[om -> primeIdx];
om -> stale = 0;
om -> hash_array = newHashTable(om -> size);
}
/*
* Finalise an object map.
*/
void sipOMFinalise(sipObjectMap *om)
{
sip_api_free(om -> hash_array);
}
/*
* Allocate and initialise a new hash table.
*/
static sipHashEntry *newHashTable(unsigned long size)
{
size_t nbytes;
sipHashEntry *hashtab;
nbytes = sizeof (sipHashEntry) * size;
if ((hashtab = (sipHashEntry *)sip_api_malloc(nbytes)) != NULL)
memset(hashtab,0,nbytes);
return hashtab;
}
/*
* Return a pointer to the hash entry that is used, or should be used, for the
* given C/C++ address.
*/
static sipHashEntry *findHashEntry(sipObjectMap *om,void *key)
{
unsigned long hash, inc;
void *hek;
hash = hash_1(key,om -> size);
inc = hash_2(key,om -> size);
while ((hek = om -> hash_array[hash].key) != NULL && hek != key)
hash = (hash + inc) % om -> size;
return &om -> hash_array[hash];
}
/*
* Return the wrapped Python object of a specific type for a C/C++ address or
* NULL if it wasn't found.
*/
sipSimpleWrapper *sipOMFindObject(sipObjectMap *om, void *key,
const sipTypeDef *td)
{
sipHashEntry *he = findHashEntry(om, key);
sipSimpleWrapper *sw;
PyTypeObject *py_type = sipTypeAsPyTypeObject(td);
/* Go through each wrapped object at this address. */
for (sw = he->first; sw != NULL; sw = sw->next)
{
/*
* If the reference count is 0 then it is in the process of being
* deleted, so ignore it. It's not completely clear how this can
* happen (but it can) because it implies that the garbage collection
* code is being re-entered (and there are guards in place to prevent
* this).
*/
if (Py_REFCNT(sw) == 0)
continue;
/* Ignore it if the C/C++ address is no longer valid. */
if (sip_api_get_address(sw) == NULL)
continue;
/*
* If this wrapped object is of the given type, or a sub-type of it,
* then we assume it is the same C++ object.
*/
if (PyObject_TypeCheck(sw, py_type))
return sw;
}
return NULL;
}
/*
* Add a C/C++ address and the corresponding wrapped Python object to the map.
*/
void sipOMAddObject(sipObjectMap *om, sipSimpleWrapper *val)
{
sipHashEntry *he = findHashEntry(om, getUnguardedPointer(val));
/*
* If the bucket is in use then we appear to have several objects at the
* same address.
*/
if (he->first != NULL)
{
/*
* This can happen for three reasons. A variable of one class can be
* declared at the start of another class. Therefore there are two
* objects, of different classes, with the same address. The second
* reason is that the old C/C++ object has been deleted by C/C++ but we
* didn't get to find out for some reason, and a new C/C++ instance has
* been created at the same address. The third reason is if we are in
* the process of deleting a Python object but the C++ object gets
* wrapped again because the C++ dtor called a method that has been
* re-implemented in Python. The absence of the SIP_SHARE_MAP flag
* tells us that a new C++ instance has just been created and so we
* know the second reason is the correct one so we mark the old
* pointers as invalid and reuse the entry. Otherwise we just add this
* one to the existing list of objects at this address.
*/
if (!(val->flags & SIP_SHARE_MAP))
{
sipSimpleWrapper *sw = he->first;
he->first = NULL;
while (sw != NULL)
{
sipSimpleWrapper *next = sw->next;
/* We are removing it from the map here. */
sipSetNotInMap(sw);
sip_api_common_dtor(sw);
sw = next;
}
}
val->next = he->first;
he->first = val;
return;
}
/* See if the bucket was unused or stale. */
if (he->key == NULL)
{
he->key = getUnguardedPointer(val);
om->unused--;
}
else
om->stale--;
/* Add the rest of the new value. */
he->first = val;
val->next = NULL;
reorganiseMap(om);
}
/*
* Reorganise a map if it is running short of space.
*/
static void reorganiseMap(sipObjectMap *om)
{
unsigned long old_size, i;
sipHashEntry *ohe, *old_tab;
/* Don't bother if it still has more than 12% available. */
if (om -> unused > om -> size >> 3)
return;
/*
* If reorganising (ie. making the stale buckets unused) using the same
* sized table would make 25% available then do that. Otherwise use a
* bigger table (if possible).
*/
if (om -> unused + om -> stale < om -> size >> 2 && hash_primes[om -> primeIdx + 1] != 0)
om -> primeIdx++;
old_size = om -> size;
old_tab = om -> hash_array;
om -> unused = om -> size = hash_primes[om -> primeIdx];
om -> stale = 0;
om -> hash_array = newHashTable(om -> size);
/* Transfer the entries from the old table to the new one. */
ohe = old_tab;
for (i = 0; i < old_size; ++i)
{
if (ohe -> key != NULL && ohe -> first != NULL)
{
*findHashEntry(om,ohe -> key) = *ohe;
om -> unused--;
}
++ohe;
}
sip_api_free(old_tab);
}
/*
* Remove a C/C++ object from the table. Return 0 if it was removed
* successfully.
*/
int sipOMRemoveObject(sipObjectMap *om, sipSimpleWrapper *val)
{
sipHashEntry *he;
sipSimpleWrapper **swp;
void *addr;
/* Handle the trivial case. */
if (sipNotInMap(val))
return 0;
if ((addr = getUnguardedPointer(val)) == NULL)
return 0;
he = findHashEntry(om, addr);
for (swp = &he->first; *swp != NULL; swp = &(*swp)->next)
if (*swp == val)
{
*swp = val->next;
/*
* If the bucket is now empty then count it as stale. Note that we
* do not NULL the key and count it as unused because that might
* throw out the search for another entry that wanted to go here,
* found it already occupied, and was put somewhere else. In other
* words, searches must be repeatable until we reorganise the
* table.
*/
if (he->first == NULL)
om->stale++;
return 0;
}
return -1;
}
/*
* Return the unguarded pointer to a C/C++ instance, ie. the pointer was valid
* but may longer be.
*/
static void *getUnguardedPointer(sipSimpleWrapper *w)
{
return (w->access_func != NULL) ? w->access_func(w, UnguardedPointer) : w->data;
}

659
sip/siplib/qtlib.c Normal file
View File

@@ -0,0 +1,659 @@
/*
* The SIP library code that implements the interface to the optional module
* supplied Qt support.
*
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <Python.h>
#include <assert.h>
#include <string.h>
#include "sip.h"
#include "sipint.h"
/* This is how Qt "types" signals and slots. */
#define isQtSlot(s) (*(s) == '1')
#define isQtSignal(s) (*(s) == '2')
static PyObject *getWeakRef(PyObject *obj);
static char *sipStrdup(const char *);
static void *createUniversalSlot(sipWrapper *txSelf, const char *sig,
PyObject *rxObj, const char *slot, const char **member, int flags);
static void *findSignal(void *txrx, const char **sig);
static void *newSignal(void *txrx, const char **sig);
/*
* Find an existing signal.
*/
static void *findSignal(void *txrx, const char **sig)
{
if (sipQtSupport->qt_find_universal_signal != NULL)
txrx = sipQtSupport->qt_find_universal_signal(txrx, sig);
return txrx;
}
/*
* Return a usable signal, creating a new universal signal if needed.
*/
static void *newSignal(void *txrx, const char **sig)
{
void *new_txrx = findSignal(txrx, sig);
if (new_txrx == NULL && sipQtSupport->qt_create_universal_signal != NULL)
new_txrx = sipQtSupport->qt_create_universal_signal(txrx, sig);
return new_txrx;
}
/*
* Create a universal slot. Returns a pointer to it or 0 if there was an
* error.
*/
static void *createUniversalSlot(sipWrapper *txSelf, const char *sig,
PyObject *rxObj, const char *slot, const char **member, int flags)
{
void *us = sipQtSupport->qt_create_universal_slot(txSelf, sig, rxObj, slot,
member, flags);
if (us && txSelf)
sipSetPossibleProxy((sipSimpleWrapper *)txSelf);
return us;
}
/*
* Invoke a single slot (Qt or Python) and return the result.
*/
PyObject *sip_api_invoke_slot(const sipSlot *slot, PyObject *sigargs)
{
PyObject *sa, *oxtype, *oxvalue, *oxtb, *sfunc, *sref;
/* Keep some compilers quiet. */
oxtype = oxvalue = oxtb = NULL;
/* Fan out Qt signals. (Only PyQt3 will do this.) */
if (slot->name != NULL && slot->name[0] != '\0')
{
assert(sipQtSupport->qt_emit_signal);
if (sipQtSupport->qt_emit_signal(slot->pyobj, slot->name, sigargs) < 0)
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
/* Get the object to call, resolving any weak references. */
if (slot->weakSlot == Py_True)
{
/*
* The slot is guaranteed to be Ok because it has an extra reference or
* is None.
*/
sref = slot->pyobj;
Py_INCREF(sref);
}
else if (slot -> weakSlot == NULL)
sref = NULL;
else if ((sref = PyWeakref_GetObject(slot -> weakSlot)) == NULL)
return NULL;
else
Py_INCREF(sref);
if (sref == Py_None)
{
/*
* If the real object has gone then we pretend everything is Ok. This
* mimics the Qt behaviour of not caring if a receiving object has been
* deleted.
*/
Py_DECREF(sref);
Py_INCREF(Py_None);
return Py_None;
}
if (slot -> pyobj == NULL)
{
PyObject *self = (sref != NULL ? sref : slot->meth.mself);
/*
* If the receiver wraps a C++ object then ignore the call if it no
* longer exists.
*/
if (PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type) &&
sip_api_get_address((sipSimpleWrapper *)self) == NULL)
{
Py_XDECREF(sref);
Py_INCREF(Py_None);
return Py_None;
}
#if PY_MAJOR_VERSION >= 3
sfunc = PyMethod_New(slot->meth.mfunc, self);
#else
sfunc = PyMethod_New(slot->meth.mfunc, self, slot->meth.mclass);
#endif
if (sfunc == NULL)
{
Py_XDECREF(sref);
return NULL;
}
}
else if (slot -> name != NULL)
{
char *mname = slot -> name + 1;
PyObject *self = (sref != NULL ? sref : slot->pyobj);
if ((sfunc = PyObject_GetAttrString(self, mname)) == NULL || !PyCFunction_Check(sfunc))
{
/*
* Note that in earlier versions of SIP this error would be
* detected when the slot was connected.
*/
PyErr_Format(PyExc_NameError,"Invalid slot %s",mname);
Py_XDECREF(sfunc);
Py_XDECREF(sref);
return NULL;
}
}
else
{
sfunc = slot->pyobj;
Py_INCREF(sfunc);
}
/*
* We make repeated attempts to call a slot. If we work out that it failed
* because of an immediate type error we try again with one less argument.
* We keep going until we run out of arguments to drop. This emulates the
* Qt ability of the slot to accept fewer arguments than a signal provides.
*/
sa = sigargs;
Py_INCREF(sa);
for (;;)
{
PyObject *nsa, *xtype, *xvalue, *xtb, *resobj;
if ((resobj = PyEval_CallObject(sfunc, sa)) != NULL)
{
Py_DECREF(sfunc);
Py_XDECREF(sref);
/* Remove any previous exception. */
if (sa != sigargs)
{
Py_XDECREF(oxtype);
Py_XDECREF(oxvalue);
Py_XDECREF(oxtb);
PyErr_Clear();
}
Py_DECREF(sa);
return resobj;
}
/* Get the exception. */
PyErr_Fetch(&xtype,&xvalue,&xtb);
/*
* See if it is unacceptable. An acceptable failure is a type error
* with no traceback - so long as we can still reduce the number of
* arguments and try again.
*/
if (!PyErr_GivenExceptionMatches(xtype,PyExc_TypeError) ||
xtb != NULL ||
PyTuple_GET_SIZE(sa) == 0)
{
/*
* If there is a traceback then we must have called the slot and
* the exception was later on - so report the exception as is.
*/
if (xtb != NULL)
{
if (sa != sigargs)
{
Py_XDECREF(oxtype);
Py_XDECREF(oxvalue);
Py_XDECREF(oxtb);
}
PyErr_Restore(xtype,xvalue,xtb);
}
else if (sa == sigargs)
PyErr_Restore(xtype,xvalue,xtb);
else
{
/*
* Discard the latest exception and restore the original one.
*/
Py_XDECREF(xtype);
Py_XDECREF(xvalue);
Py_XDECREF(xtb);
PyErr_Restore(oxtype,oxvalue,oxtb);
}
break;
}
/* If this is the first attempt, save the exception. */
if (sa == sigargs)
{
oxtype = xtype;
oxvalue = xvalue;
oxtb = xtb;
}
else
{
Py_XDECREF(xtype);
Py_XDECREF(xvalue);
Py_XDECREF(xtb);
}
/* Create the new argument tuple. */
if ((nsa = PyTuple_GetSlice(sa,0,PyTuple_GET_SIZE(sa) - 1)) == NULL)
{
/* Tidy up. */
Py_XDECREF(oxtype);
Py_XDECREF(oxvalue);
Py_XDECREF(oxtb);
break;
}
Py_DECREF(sa);
sa = nsa;
}
Py_DECREF(sfunc);
Py_XDECREF(sref);
Py_DECREF(sa);
return NULL;
}
/*
* Compare two slots to see if they are the same.
*/
int sip_api_same_slot(const sipSlot *sp, PyObject *rxObj, const char *slot)
{
/* See if they are signals or Qt slots, ie. they have a name. */
if (slot != NULL)
{
if (sp->name == NULL || sp->name[0] == '\0')
return 0;
return (sipQtSupport->qt_same_name(sp->name, slot) && sp->pyobj == rxObj);
}
/* See if they are pure Python methods. */
if (PyMethod_Check(rxObj))
{
if (sp->pyobj != NULL)
return 0;
return (sp->meth.mfunc == PyMethod_GET_FUNCTION(rxObj)
&& sp->meth.mself == PyMethod_GET_SELF(rxObj)
#if PY_MAJOR_VERSION < 3
&& sp->meth.mclass == PyMethod_GET_CLASS(rxObj)
#endif
);
}
/* See if they are wrapped C++ methods. */
if (PyCFunction_Check(rxObj))
{
if (sp->name == NULL || sp->name[0] != '\0')
return 0;
return (sp->pyobj == PyCFunction_GET_SELF(rxObj) &&
strcmp(&sp->name[1], ((PyCFunctionObject *)rxObj)->m_ml->ml_name) == 0);
}
/* The objects must be the same. */
return (sp->pyobj == rxObj);
}
/*
* Convert a valid Python signal or slot to an existing universal slot.
*/
void *sipGetRx(sipSimpleWrapper *txSelf, const char *sigargs, PyObject *rxObj,
const char *slot, const char **memberp)
{
if (slot != NULL)
if (isQtSlot(slot) || isQtSignal(slot))
{
void *rx;
*memberp = slot;
if ((rx = sip_api_get_cpp_ptr((sipSimpleWrapper *)rxObj, sipQObjectType)) == NULL)
return NULL;
if (isQtSignal(slot))
rx = findSignal(rx, memberp);
return rx;
}
/*
* The slot was either a Python callable or PyQt3 Python signal so there
* should be a universal slot.
*/
return sipQtSupport->qt_find_slot(sip_api_get_address(txSelf), sigargs, rxObj, slot, memberp);
}
/*
* Convert a Python receiver (either a Python signal or slot or a Qt signal or
* slot) to a Qt receiver. It is only ever called when the signal is a Qt
* signal. Return NULL is there was an error.
*/
void *sip_api_convert_rx(sipWrapper *txSelf, const char *sigargs,
PyObject *rxObj, const char *slot, const char **memberp, int flags)
{
if (slot == NULL)
return createUniversalSlot(txSelf, sigargs, rxObj, NULL, memberp, flags);
if (isQtSlot(slot) || isQtSignal(slot))
{
void *rx;
*memberp = slot;
if ((rx = sip_api_get_cpp_ptr((sipSimpleWrapper *)rxObj, sipQObjectType)) == NULL)
return NULL;
if (isQtSignal(slot))
rx = newSignal(rx, memberp);
return rx;
}
/* The slot is a Python signal so we need a universal slot to catch it. */
return createUniversalSlot(txSelf, sigargs, rxObj, slot, memberp, 0);
}
/*
* Connect a Qt signal or a Python signal to a Qt slot, a Qt signal, a Python
* slot or a Python signal. This is all possible combinations.
*/
PyObject *sip_api_connect_rx(PyObject *txObj, const char *sig, PyObject *rxObj,
const char *slot, int type)
{
/* Handle Qt signals. */
if (isQtSignal(sig))
{
void *tx, *rx;
const char *member, *real_sig;
int res;
if ((tx = sip_api_get_cpp_ptr((sipSimpleWrapper *)txObj, sipQObjectType)) == NULL)
return NULL;
real_sig = sig;
if ((tx = newSignal(tx, &real_sig)) == NULL)
return NULL;
if ((rx = sip_api_convert_rx((sipWrapper *)txObj, sig, rxObj, slot, &member, 0)) == NULL)
return NULL;
res = sipQtSupport->qt_connect(tx, real_sig, rx, member, type);
return PyBool_FromLong(res);
}
/* Handle Python signals. Only PyQt3 will get this far. */
assert(sipQtSupport->qt_connect_py_signal);
if (sipQtSupport->qt_connect_py_signal(txObj, sig, rxObj, slot) < 0)
return NULL;
Py_INCREF(Py_True);
return Py_True;
}
/*
* Disconnect a signal to a signal or a Qt slot.
*/
PyObject *sip_api_disconnect_rx(PyObject *txObj,const char *sig,
PyObject *rxObj,const char *slot)
{
/* Handle Qt signals. */
if (isQtSignal(sig))
{
sipSimpleWrapper *txSelf = (sipSimpleWrapper *)txObj;
void *tx, *rx;
const char *member;
int res;
if ((tx = sip_api_get_cpp_ptr(txSelf, sipQObjectType)) == NULL)
return NULL;
if ((rx = sipGetRx(txSelf, sig, rxObj, slot, &member)) == NULL)
{
Py_INCREF(Py_False);
return Py_False;
}
/* Handle Python signals. */
tx = findSignal(tx, &sig);
res = sipQtSupport->qt_disconnect(tx, sig, rx, member);
/*
* Delete it if it is a universal slot as this will be it's only
* connection. If the slot is actually a universal signal then it
* should leave it in place.
*/
sipQtSupport->qt_destroy_universal_slot(rx);
return PyBool_FromLong(res);
}
/* Handle Python signals. Only PyQt3 will get this far. */
assert(sipQtSupport->qt_disconnect_py_signal);
sipQtSupport->qt_disconnect_py_signal(txObj, sig, rxObj, slot);
Py_INCREF(Py_True);
return Py_True;
}
/*
* Free the resources of a slot.
*/
void sip_api_free_sipslot(sipSlot *slot)
{
if (slot->name != NULL)
{
sip_api_free(slot->name);
}
else if (slot->weakSlot == Py_True)
{
Py_DECREF(slot->pyobj);
}
/* Remove any weak reference. */
Py_XDECREF(slot->weakSlot);
}
/*
* Implement strdup() using sip_api_malloc().
*/
static char *sipStrdup(const char *s)
{
char *d;
if ((d = (char *)sip_api_malloc(strlen(s) + 1)) != NULL)
strcpy(d,s);
return d;
}
/*
* Initialise a slot, returning 0 if there was no error. If the signal was a
* Qt signal, then the slot may be a Python signal or a Python slot. If the
* signal was a Python signal, then the slot may be anything.
*/
int sip_api_save_slot(sipSlot *sp, PyObject *rxObj, const char *slot)
{
sp -> weakSlot = NULL;
if (slot == NULL)
{
sp -> name = NULL;
if (PyMethod_Check(rxObj))
{
/*
* Python creates methods on the fly. We could increment the
* reference count to keep it alive, but that would keep "self"
* alive as well and would probably be a circular reference.
* Instead we remember the component parts and hope they are still
* valid when we re-create the method when we need it.
*/
sipSaveMethod(&sp -> meth,rxObj);
/* Notice if the class instance disappears. */
sp -> weakSlot = getWeakRef(sp -> meth.mself);
/* This acts a flag to say that the slot is a method. */
sp -> pyobj = NULL;
}
else
{
PyObject *self;
/*
* We know that it is another type of callable, ie. a
* function/builtin.
*/
if (PyCFunction_Check(rxObj) &&
(self = PyCFunction_GET_SELF(rxObj)) != NULL &&
PyObject_TypeCheck(self, (PyTypeObject *)&sipSimpleWrapper_Type))
{
/*
* It is a wrapped C++ class method. We can't keep a copy
* because they are generated on the fly and we can't take a
* reference as that may keep the instance (ie. self) alive.
* We therefore treat it as if the user had specified the slot
* at "obj, SLOT('meth()')" rather than "obj.meth" (see below).
*/
const char *meth;
/* Get the method name. */
meth = ((PyCFunctionObject *)rxObj) -> m_ml -> ml_name;
if ((sp -> name = (char *)sip_api_malloc(strlen(meth) + 2)) == NULL)
return -1;
/*
* Copy the name and set the marker that it needs converting to
* a built-in method.
*/
sp -> name[0] = '\0';
strcpy(&sp -> name[1],meth);
sp -> pyobj = self;
sp -> weakSlot = getWeakRef(self);
}
else
{
/*
* Give the slot an extra reference to keep it alive and
* remember we have done so by treating weakSlot specially.
*/
Py_INCREF(rxObj);
sp->pyobj = rxObj;
Py_INCREF(Py_True);
sp->weakSlot = Py_True;
}
}
}
else if ((sp -> name = sipStrdup(slot)) == NULL)
return -1;
else if (isQtSlot(slot))
{
/*
* The user has decided to connect a Python signal to a Qt slot and
* specified the slot as "obj, SLOT('meth()')" rather than "obj.meth".
*/
char *tail;
/* Remove any arguments. */
if ((tail = strchr(sp -> name,'(')) != NULL)
*tail = '\0';
/*
* A bit of a hack to indicate that this needs converting to a built-in
* method.
*/
sp -> name[0] = '\0';
/* Notice if the class instance disappears. */
sp -> weakSlot = getWeakRef(rxObj);
sp -> pyobj = rxObj;
}
else
/* It's a Qt signal. */
sp -> pyobj = rxObj;
return 0;
}
/*
* Return a weak reference to the given object.
*/
static PyObject *getWeakRef(PyObject *obj)
{
PyObject *wr;
if ((wr = PyWeakref_NewRef(obj,NULL)) == NULL)
PyErr_Clear();
return wr;
}

1668
sip/siplib/sip.h Normal file

File diff suppressed because it is too large Load Diff

148
sip/siplib/sipint.h Normal file
View File

@@ -0,0 +1,148 @@
/*
* This file defines the SIP library internal interfaces.
*
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef _SIPINT_H
#define _SIPINT_H
#ifdef __cplusplus
extern "C" {
#endif
#undef TRUE
#define TRUE 1
#undef FALSE
#define FALSE 0
/*
* This defines a single entry in an object map's hash table.
*/
typedef struct
{
void *key; /* The C/C++ address. */
sipSimpleWrapper *first; /* The first object at this address. */
} sipHashEntry;
/*
* This defines the interface to a hash table class for mapping C/C++ addresses
* to the corresponding wrapped Python object.
*/
typedef struct
{
int primeIdx; /* Index into table sizes. */
unsigned long size; /* Size of hash table. */
unsigned long unused; /* Nr. unused in hash table. */
unsigned long stale; /* Nr. stale in hash table. */
sipHashEntry *hash_array; /* Current hash table. */
} sipObjectMap;
/*
* Support for the descriptors.
*/
extern PyTypeObject sipMethodDescr_Type;
PyObject *sipMethodDescr_New(PyMethodDef *pmd);
extern PyTypeObject sipVariableDescr_Type;
PyObject *sipVariableDescr_New(sipVariableDef *vd, const sipTypeDef *td,
const sipContainerDef *cod);
/*
* Support for API versions.
*/
PyObject *sipGetAPI(PyObject *self, PyObject *args);
PyObject *sipSetAPI(PyObject *self, PyObject *args);
int sip_api_is_api_enabled(const char *name, int from, int to);
int sipIsRangeEnabled(sipExportedModuleDef *em, int range_index);
int sipInitAPI(sipExportedModuleDef *em, PyObject *mod_dict);
/*
* Support for void pointers.
*/
extern PyTypeObject sipVoidPtr_Type;
void *sip_api_convert_to_void_ptr(PyObject *obj);
PyObject *sip_api_convert_from_void_ptr(void *val);
PyObject *sip_api_convert_from_const_void_ptr(const void *val);
PyObject *sip_api_convert_from_void_ptr_and_size(void *val, SIP_SSIZE_T size);
PyObject *sip_api_convert_from_const_void_ptr_and_size(const void *val,
SIP_SSIZE_T size);
extern sipQtAPI *sipQtSupport; /* The Qt support API. */
extern sipWrapperType sipSimpleWrapper_Type; /* The simple wrapper type. */
extern sipTypeDef *sipQObjectType; /* The QObject type. */
void *sipGetRx(sipSimpleWrapper *txSelf, const char *sigargs, PyObject *rxObj,
const char *slot, const char **memberp);
PyObject *sip_api_connect_rx(PyObject *txObj, const char *sig, PyObject *rxObj,
const char *slot, int type);
PyObject *sip_api_disconnect_rx(PyObject *txObj, const char *sig,
PyObject *rxObj,const char *slot);
/*
* These are part of the SIP API but are also used within the SIP module.
*/
void *sip_api_malloc(size_t nbytes);
void sip_api_free(void *mem);
void *sip_api_get_address(sipSimpleWrapper *w);
void *sip_api_get_cpp_ptr(sipSimpleWrapper *w, const sipTypeDef *td);
PyObject *sip_api_convert_from_type(void *cppPtr, const sipTypeDef *td,
PyObject *transferObj);
void sip_api_common_dtor(sipSimpleWrapper *sipSelf);
void sip_api_start_thread(void);
void sip_api_end_thread(void);
void sip_api_free_sipslot(sipSlot *slot);
int sip_api_same_slot(const sipSlot *sp, PyObject *rxObj, const char *slot);
PyObject *sip_api_invoke_slot(const sipSlot *slot, PyObject *sigargs);
void *sip_api_convert_rx(sipWrapper *txSelf, const char *sigargs,
PyObject *rxObj, const char *slot, const char **memberp, int flags);
int sip_api_save_slot(sipSlot *sp, PyObject *rxObj, const char *slot);
/*
* These are not part of the SIP API but are used within the SIP module.
*/
void sipSaveMethod(sipPyMethod *pm,PyObject *meth);
void *sipGetPending(sipWrapper **op, int *fp);
PyObject *sipWrapSimpleInstance(void *cppPtr, const sipTypeDef *td,
sipWrapper *owner, int initflags);
void *sipConvertRxEx(sipWrapper *txSelf, const char *sigargs,
PyObject *rxObj, const char *slot, const char **memberp, int flags);
void sipOMInit(sipObjectMap *om);
void sipOMFinalise(sipObjectMap *om);
sipSimpleWrapper *sipOMFindObject(sipObjectMap *om, void *key,
const sipTypeDef *td);
void sipOMAddObject(sipObjectMap *om, sipSimpleWrapper *val);
int sipOMRemoveObject(sipObjectMap *om, sipSimpleWrapper *val);
void sipSetBool(void *ptr,int val);
#ifdef __cplusplus
}
#endif
#endif

10758
sip/siplib/siplib.c Normal file

File diff suppressed because it is too large Load Diff

226
sip/siplib/threads.c Normal file
View File

@@ -0,0 +1,226 @@
/*
* Thread support for the SIP library. This module provides the hooks for
* C++ classes that provide a thread interface to interact properly with the
* Python threading infrastructure.
*
* Copyright (c) 2010 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "sip.h"
#include "sipint.h"
/*
* The data associated with pending request to wrap an object.
*/
typedef struct _pendingDef {
void *cpp; /* The C/C++ object ot be wrapped. */
sipWrapper *owner; /* The owner of the object. */
int flags; /* The flags. */
} pendingDef;
#ifdef WITH_THREAD
#include <pythread.h>
/*
* The per thread data we need to maintain.
*/
typedef struct _threadDef {
long thr_ident; /* The thread identifier. */
pendingDef pending; /* An object waiting to be wrapped. */
struct _threadDef *next; /* Next in the list. */
} threadDef;
static threadDef *threads = NULL; /* Linked list of threads. */
static threadDef *currentThreadDef(void);
#endif
static pendingDef pending; /* An object waiting to be wrapped. */
/*
* Get the address of any C/C++ object waiting to be wrapped.
*/
void *sipGetPending(sipWrapper **op, int *fp)
{
pendingDef *pp;
#ifdef WITH_THREAD
threadDef *thread;
if ((thread = currentThreadDef()) != NULL)
pp = &thread->pending;
else
pp = &pending;
#else
pp = &pending;
#endif
if (pp->cpp != NULL)
{
if (op != NULL)
*op = pp->owner;
if (fp != NULL)
*fp = pp->flags;
}
return pp->cpp;
}
/*
* Convert a new C/C++ pointer to a Python instance.
*/
PyObject *sipWrapSimpleInstance(void *cppPtr, const sipTypeDef *td,
sipWrapper *owner, int flags)
{
static PyObject *nullargs = NULL;
pendingDef old_pending;
PyObject *self;
#ifdef WITH_THREAD
threadDef *thread;
#endif
if (nullargs == NULL && (nullargs = PyTuple_New(0)) == NULL)
return NULL;
if (cppPtr == NULL)
{
Py_INCREF(Py_None);
return Py_None;
}
/*
* Object creation can trigger the Python garbage collector which in turn
* can execute arbitrary Python code which can then call this function
* recursively. Therefore we save any existing pending object before
* setting the new one.
*/
#ifdef WITH_THREAD
if ((thread = currentThreadDef()) != NULL)
{
old_pending = thread->pending;
thread->pending.cpp = cppPtr;
thread->pending.owner = owner;
thread->pending.flags = flags;
}
else
{
old_pending = pending;
pending.cpp = cppPtr;
pending.owner = owner;
pending.flags = flags;
}
#else
old_pending = pending;
pending.cpp = cppPtr;
pending.owner = owner;
pending.flags = flags;
#endif
self = PyObject_Call((PyObject *)sipTypeAsPyTypeObject(td), nullargs, NULL);
#ifdef WITH_THREAD
if (thread != NULL)
thread->pending = old_pending;
else
pending = old_pending;
#else
pending = old_pending;
#endif
return self;
}
/*
* This is called from a newly created thread to initialise some thread local
* storage.
*/
void sip_api_start_thread(void)
{
#ifdef WITH_THREAD
threadDef *thread;
/* Save the thread ID. First, find an empty slot in the list. */
for (thread = threads; thread != NULL; thread = thread->next)
if (thread->thr_ident == 0)
break;
if (thread == NULL)
{
thread = sip_api_malloc(sizeof (threadDef));
thread->next = threads;
threads = thread;
}
if (thread != NULL)
{
thread->thr_ident = PyThread_get_thread_ident();
thread->pending.cpp = NULL;
}
#endif
}
/*
* Handle the termination of a thread. The thread state should already have
* been handled by the last call to PyGILState_Release().
*/
void sip_api_end_thread(void)
{
#ifdef WITH_THREAD
threadDef *thread;
/* We have the GIL at this point. */
if ((thread = currentThreadDef()) != NULL)
thread->thr_ident = 0;
#endif
}
#ifdef WITH_THREAD
/*
* Return the thread data for the current thread or NULL if it wasn't
* recognised.
*/
static threadDef *currentThreadDef(void)
{
threadDef *thread;
long ident = PyThread_get_thread_ident();
for (thread = threads; thread != NULL; thread = thread->next)
if (thread->thr_ident == ident)
break;
return thread;
}
#endif

1017
sip/siplib/voidptr.c Normal file

File diff suppressed because it is too large Load Diff

3
src/README.txt Normal file
View File

@@ -0,0 +1,3 @@
This directory holds source code that is not generated by any of the tools,
but is edited by hand instead. It may include C/C++, SIP or Python code as
needed.

3
src/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from _core import *

31
src/string.sip Normal file
View File

@@ -0,0 +1,31 @@
/////////////////////////////////////////////////////////////////////////////
// Name: string.sip
// Purpose: Implements a %MappedType for wxString
//
// Author: Robin Dunn
//
// Created: 9-Nov-2010
// RCS-ID: $Id:$
// Copyright: (c) 2010 by Total Control Software
// Licence: wxWindows license
/////////////////////////////////////////////////////////////////////////////
// We don't want the Python user to ever need to deal with wxString, so it
// will be mapped to and from Python UNicode objects using the code snippets
// below.
%MappedType wxString
{
// Code to test a PyObject for compatibility and to convert from a
// PyObject to a wxString
%ConvertToTypeCode
// TODO
%End
// Code to convert a wxString to a PyObject
%ConvertFromTypeCode
// TODO
%End
};

4
src/wxc.rc Normal file
View File

@@ -0,0 +1,4 @@
#include "wx/msw/wx.rc"