Pyrex and Distuils: an enhanced build_ext command
Graham Fawcett
gmfawcett at operamail.com
Wed Jul 10 06:16:49 EDT 2002
I wrote:
> I suppose that one could add a distutils.command.pyrexc as a
> subcommand of "build_ext" to handle the .pyx compilation. That way the
> os.system() call in the build script could be avoided. I've never
> written a distutils command, but perhaps I'll give that a try.
Okay, I wrote the distutils command. Actually I just added a
subpackage to Pyrex that you can import into your setup script:
from distutils.core import setup
from distutils.extension import Extension
import Pyrex.Distutils
setup(name='foo', ext_modules=[Extension("foo", ["foo.pyx"])])
Note that the Extension() specifies a .pyx file, not a .c file. You
can specify .c files as well, but of course Pyrex will ignore them.
Pyrex.Distuils (my subpackage) is a build_ext replacement -- it
import-hacks itself into the distutils.command package, so that it
gets a first shot at any build_ext calls. Once it's finished, it
returns control to the "real" build_ext.
I tested on Win32 only, with mingw32 and msvc compilers, but it should
run fine on other platforms.
I've put a modified distribution of Pyrex 0.3.3 at
http://cfl-x.uwindsor.ca/graham/pyrex/ if anyone wants to try it
out. Here are the two key source files:
###########################################
# Pyrex.Distutils.__init__.py
# July 2002, Graham Fawcett
#
# this hack was inspired by the way Thomas Heller got py2exe
# to appear as a distutil command
#
# we replace distutils.command.build_ext with our own version
# and keep the old one under the module name _build_ext,
# so that *our* build_ext can make use of it.
import sys
import distutils.command.build_ext
sys.modules['_build_ext'] = distutils.command.build_ext
import build_ext
sys.modules['distutils.command.build_ext']=build_ext
###########################################
# Pyrex.Distutils.build_ext.py
"""
Subclasses disutils.command.build_ext,
replacing it with a Pyrex version that compiles pyx->c
before calling the original build_ext command.
"""
# July 2002, Graham Fawcett
# Pyrex is (c) Greg Ewing.
import Pyrex.Compiler.Main
from Pyrex.Compiler.Errors import PyrexError
from distutils.dep_util import newer
import os
import sys
# in Distutils.__init__, the original build_ext
# is inserted into sys.modules as _build_ext
import _build_ext
class build_ext (_build_ext.build_ext):
description = "compile Pyrex scripts, then build C/C++ extensions
(compile/link to build directory)"
def run (self):
if not self.extensions:
return
# collect the names of the source (.pyx) files
pyx_sources = []
for e in self.extensions:
pyx_sources.extend(e.sources)
pyx_sources = [source for source in pyx_sources if
source.endswith('.pyx')]
for pyx in pyx_sources:
# should I raise an exception if it doesn't exist?
if os.path.exists(pyx):
source = pyx
target = source.replace('.pyx', '.c')
if newer(source, target) or self.force:
self.pyrex_compile(source)
# compiling with mingw32 gets an "initializer not
a constant" error
#
http://www.python.org/cgi-bin/faqw.py?req=show&file=faq03.024.htp
# doesn't appear to happen with MSVC
# so if we are compiling with mingw32, massage the
Pyrex-generated
# C files to compile properly
if self.compiler=='mingw32':
self.fix_c_file(target)
# correct the extension sourcefile names in setup script
# so that .pyx files are refered to as .c files
# then we can call the "real" build_ext on them.
for e in self.extensions:
e.sources = [src.replace('.pyx', '.c') for src in
e.sources]
# run the original build_ext.
_build_ext.build_ext.run(self)
def pyrex_compile(self, source):
try:
Pyrex.Compiler.Main.compile(source, c_only=1,
use_listing_file=0)
except PyrexError, e:
print e
sys.exit(1)
def fix_c_file(self, filename):
print 'fixing ' + filename
HEAD_INIT = 'PyObject_HEAD_INIT(0)'
TYPESTART = 'statichere PyTypeObject __pyx_type_'
GETATTR = 'PyObject_GenericGetAttr, /*tp_getattro*/'
SETATTR = 'PyObject_GenericSetAttr, /*tp_setattro*/'
ALLOC = 'PyType_GenericAlloc, /*tp_alloc*/'
FREE = '_PyObject_Del, /*tp_free*/'
TYPEINIT ='''__pyx_type_%(typename)s.ob_type = &PyType_Type;
__pyx_type_%(typename)s.tp_getattro = PyObject_GenericGetAttr;
/*tp_getattro*/
__pyx_type_%(typename)s.tp_setattro = PyObject_GenericSetAttr;
/*tp_setattro*/
__pyx_type_%(typename)s.tp_alloc = PyType_GenericAlloc;
/*tp_alloc*/
__pyx_type_%(typename)s.tp_free = _PyObject_Del; /*tp_free*/
'''
f = open(filename, 'r')
lines = f.readlines()
f.close()
lines = [line.rstrip() for line in lines]
types = []
f = open(filename, 'w')
for line in lines:
if line.startswith(HEAD_INIT):
print >> f, 'PyObject_HEAD_INIT(NULL)'
elif line.startswith(TYPESTART):
typename = line[len(TYPESTART):].split(' ')[0]
types.append(typename)
print >> f, line
elif line.startswith(GETATTR):
print >> f, 'NULL,' + GETATTR.split(',')[1]
elif line.startswith(SETATTR):
print >> f, 'NULL,' + SETATTR.split(',')[1]
elif line.startswith(ALLOC):
print >> f, 'NULL,' + ALLOC.split(',')[1]
elif line.startswith(FREE):
print >> f, 'NULL,' + FREE.split(',')[1]
elif line.startswith('void init') and
line.endswith('(void) {'):
print >> f, line
for typename in types:
print >> f, TYPEINIT % locals()
else:
pass
print >> f, line
f.close()
###########################################
This was fun to do! I hope that it will be useful to others. Having
read through the Distutils source, I have a new and profound respect
for its authors.
Regards,
-- Graham
More information about the Python-list
mailing list