Commit 6e43dc83 authored by Robert Bradshaw's avatar Robert Bradshaw
parents 951baff4 01fb1ac9
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# Cython - Command Line Parsing # Cython - Command Line Parsing
# #
import os
import sys import sys
import Options import Options
...@@ -27,6 +28,7 @@ Options: ...@@ -27,6 +28,7 @@ Options:
Level indicates aggressiveness, default 0 releases nothing. Level indicates aggressiveness, default 0 releases nothing.
-w, --working <directory> Sets the working directory for Cython (the directory modules -w, --working <directory> Sets the working directory for Cython (the directory modules
are searched from) are searched from)
--gdb Output debug information for cygdb
-D, --no-docstrings Remove docstrings. -D, --no-docstrings Remove docstrings.
-a, --annotate Produce a colorized HTML version of the source. -a, --annotate Produce a colorized HTML version of the source.
...@@ -114,6 +116,9 @@ def parse_command_line(args): ...@@ -114,6 +116,9 @@ def parse_command_line(args):
Options.convert_range = True Options.convert_range = True
elif option == "--line-directives": elif option == "--line-directives":
options.emit_linenums = True options.emit_linenums = True
elif option == "--gdb":
options.gdb_debug = True
options.output_dir = os.curdir
elif option == '-2': elif option == '-2':
options.language_level = 2 options.language_level = 2
elif option == '-3': elif option == '-3':
......
...@@ -916,6 +916,14 @@ class CCodeWriter(object): ...@@ -916,6 +916,14 @@ class CCodeWriter(object):
return self.buffer.getvalue() return self.buffer.getvalue()
def write(self, s): def write(self, s):
# also put invalid markers (lineno 0), to indicate that those lines
# have no Cython source code correspondence
if self.marker is None:
cython_lineno = self.last_marker_line
else:
cython_lineno = self.marker[0]
self.buffer.markers.extend([cython_lineno] * s.count('\n'))
self.buffer.write(s) self.buffer.write(s)
def insertion_point(self): def insertion_point(self):
...@@ -1000,6 +1008,7 @@ class CCodeWriter(object): ...@@ -1000,6 +1008,7 @@ class CCodeWriter(object):
self.emit_marker() self.emit_marker()
if self.emit_linenums and self.last_marker_line != 0: if self.emit_linenums and self.last_marker_line != 0:
self.write('\n#line %s "%s"\n' % (self.last_marker_line, self.source_desc)) self.write('\n#line %s "%s"\n' % (self.last_marker_line, self.source_desc))
if code: if code:
if safe: if safe:
self.put_safe(code) self.put_safe(code)
......
...@@ -97,6 +97,10 @@ class CompilerCrash(CompileError): ...@@ -97,6 +97,10 @@ class CompilerCrash(CompileError):
message += u'%s: %s' % (cause.__class__.__name__, cause) message += u'%s: %s' % (cause.__class__.__name__, cause)
CompileError.__init__(self, pos, message) CompileError.__init__(self, pos, message)
class NoElementTreeInstalledException(PyrexError):
"""raised when the user enabled options.gdb_debug but no ElementTree
implementation was found
"""
listing_file = None listing_file = None
num_errors = 0 num_errors = 0
......
...@@ -13,7 +13,9 @@ except NameError: ...@@ -13,7 +13,9 @@ except NameError:
# Python 2.3 # Python 2.3
from sets import Set as set from sets import Set as set
import itertools
from time import time from time import time
import Code import Code
import Errors import Errors
import Parsing import Parsing
...@@ -85,6 +87,8 @@ class Context(object): ...@@ -85,6 +87,8 @@ class Context(object):
self.set_language_level(language_level) self.set_language_level(language_level)
self.gdb_debug_outputwriter = None
def set_language_level(self, level): def set_language_level(self, level):
self.language_level = level self.language_level = level
if level >= 3: if level >= 3:
...@@ -178,13 +182,22 @@ class Context(object): ...@@ -178,13 +182,22 @@ class Context(object):
from Cython.TestUtils import TreeAssertVisitor from Cython.TestUtils import TreeAssertVisitor
test_support.append(TreeAssertVisitor()) test_support.append(TreeAssertVisitor())
return ([ if options.gdb_debug:
create_parse(self), from Cython.Debugger import DebugWriter
] + self.create_pipeline(pxd=False, py=py) + test_support + [ from ParseTreeTransforms import DebugTransform
inject_pxd_code, self.gdb_debug_outputwriter = DebugWriter.CythonDebugWriter(
abort_on_errors, options.output_dir)
generate_pyx_code, debug_transform = [DebugTransform(self, options, result)]
]) else:
debug_transform = []
return list(itertools.chain(
[create_parse(self)],
self.create_pipeline(pxd=False, py=py),
test_support,
[inject_pxd_code, abort_on_errors],
debug_transform,
[generate_pyx_code]))
def create_pxd_pipeline(self, scope, module_name): def create_pxd_pipeline(self, scope, module_name):
def parse_pxd(source_desc): def parse_pxd(source_desc):
...@@ -807,4 +820,5 @@ default_options = dict( ...@@ -807,4 +820,5 @@ default_options = dict(
evaluate_tree_assertions = False, evaluate_tree_assertions = False,
emit_linenums = False, emit_linenums = False,
language_level = 2, language_level = 2,
gdb_debug = False,
) )
...@@ -297,12 +297,34 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -297,12 +297,34 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
f = open_new_file(result.c_file) f = open_new_file(result.c_file)
rootwriter.copyto(f) rootwriter.copyto(f)
if options.gdb_debug:
self._serialize_lineno_map(env, rootwriter)
f.close() f.close()
result.c_file_generated = 1 result.c_file_generated = 1
if Options.annotate or options.annotate: if Options.annotate or options.annotate:
self.annotate(rootwriter) self.annotate(rootwriter)
rootwriter.save_annotation(result.main_source_file, result.c_file) rootwriter.save_annotation(result.main_source_file, result.c_file)
def _serialize_lineno_map(self, env, ccodewriter):
tb = env.context.gdb_debug_outputwriter
markers = ccodewriter.buffer.allmarkers()
d = {}
for c_lineno, cython_lineno in enumerate(markers):
if cython_lineno > 0:
d.setdefault(cython_lineno, []).append(c_lineno + 1)
tb.start('LineNumberMapping')
for cython_lineno, c_linenos in sorted(d.iteritems()):
attrs = {
'c_linenos': ' '.join(map(str, c_linenos)),
'cython_lineno': str(cython_lineno),
}
tb.start('LineNumber', attrs)
tb.end('LineNumber')
tb.end('LineNumberMapping')
tb.serialize()
def find_referenced_modules(self, env, module_list, modules_seen): def find_referenced_modules(self, env, module_list, modules_seen):
if env not in modules_seen: if env not in modules_seen:
modules_seen[env] = 1 modules_seen[env] = 1
......
...@@ -20,6 +20,12 @@ from Cython.Compiler.TreeFragment import TreeFragment, TemplateTransform ...@@ -20,6 +20,12 @@ from Cython.Compiler.TreeFragment import TreeFragment, TemplateTransform
from Cython.Compiler.StringEncoding import EncodedString from Cython.Compiler.StringEncoding import EncodedString
from Cython.Compiler.Errors import error, warning, CompileError, InternalError from Cython.Compiler.Errors import error, warning, CompileError, InternalError
try:
set
except NameError:
from sets import Set as set
import copy import copy
...@@ -1574,3 +1580,136 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -1574,3 +1580,136 @@ class TransformBuiltinMethods(EnvTransform):
self.visitchildren(node) self.visitchildren(node)
return node return node
class DebugTransform(CythonTransform):
"""
Create debug information and all functions' visibility to extern in order
to enable debugging.
"""
def __init__(self, context, options, result):
super(DebugTransform, self).__init__(context)
self.visited = set()
# our treebuilder and debug output writer
# (see Cython.Debugger.debug_output.CythonDebugWriter)
self.tb = self.context.gdb_debug_outputwriter
#self.c_output_file = options.output_file
self.c_output_file = result.c_file
# tells visit_NameNode whether it should register step-into functions
self.register_stepinto = False
def visit_ModuleNode(self, node):
self.tb.module_name = node.full_module_name
attrs = dict(
module_name=node.full_module_name,
filename=node.pos[0].filename,
c_filename=self.c_output_file)
self.tb.start('Module', attrs)
# serialize functions
self.tb.start('Functions')
self.visitchildren(node)
self.tb.end('Functions')
# 2.3 compatibility. Serialize global variables
self.tb.start('Globals')
entries = {}
for k, v in node.scope.entries.iteritems():
if (v.qualified_name not in self.visited and not
v.name.startswith('__pyx_') and not
v.type.is_cfunction and not
v.type.is_extension_type):
entries[k]= v
self.serialize_local_variables(entries)
self.tb.end('Globals')
# self.tb.end('Module') # end Module after the line number mapping in
# Cython.Compiler.ModuleNode.ModuleNode._serialize_lineno_map
return node
def visit_FuncDefNode(self, node):
self.visited.add(node.local_scope.qualified_name)
# node.entry.visibility = 'extern'
if node.py_func is None:
pf_cname = ''
else:
pf_cname = node.py_func.entry.func_cname
attrs = dict(
name=node.entry.name,
cname=node.entry.func_cname,
pf_cname=pf_cname,
qualified_name=node.local_scope.qualified_name,
lineno=str(node.pos[1]))
self.tb.start('Function', attrs=attrs)
self.tb.start('Locals')
self.serialize_local_variables(node.local_scope.entries)
self.tb.end('Locals')
self.tb.start('Arguments')
for arg in node.local_scope.arg_entries:
self.tb.start(arg.name)
self.tb.end(arg.name)
self.tb.end('Arguments')
self.tb.start('StepIntoFunctions')
self.register_stepinto = True
self.visitchildren(node)
self.register_stepinto = False
self.tb.end('StepIntoFunctions')
self.tb.end('Function')
return node
def visit_NameNode(self, node):
if (self.register_stepinto and
node.type.is_cfunction and
getattr(node, 'is_called', False) and
node.entry.func_cname is not None):
# don't check node.entry.in_cinclude, as 'cdef extern: ...'
# declared functions are not 'in_cinclude'.
# This means we will list called 'cdef' functions as
# "step into functions", but this is not an issue as they will be
# recognized as Cython functions anyway.
attrs = dict(name=node.entry.func_cname)
self.tb.start('StepIntoFunction', attrs=attrs)
self.tb.end('StepIntoFunction')
self.visitchildren(node)
return node
def serialize_local_variables(self, entries):
for entry in entries.values():
if entry.type.is_pyobject:
vartype = 'PythonObject'
else:
vartype = 'CObject'
cname = entry.cname
# if entry.type.is_extension_type:
# cname = entry.type.typeptr_cname
if not entry.pos:
# this happens for variables that are not in the user's code,
# e.g. for the global __builtins__, __doc__, etc. We can just
# set the lineno to 0 for those.
lineno = '0'
else:
lineno = str(entry.pos[1])
attrs = dict(
name=entry.name,
cname=cname,
qualified_name=entry.qualified_name,
type=vartype,
lineno=lineno)
self.tb.start('LocalVar', attrs)
self.tb.end('LocalVar')
import os
from Cython.Compiler import CmdLine
from Cython.TestUtils import TransformTest from Cython.TestUtils import TransformTest
from Cython.Compiler.ParseTreeTransforms import * from Cython.Compiler.ParseTreeTransforms import *
from Cython.Compiler.Nodes import * from Cython.Compiler.Nodes import *
class TestNormalizeTree(TransformTest): class TestNormalizeTree(TransformTest):
def test_parserbehaviour_is_what_we_coded_for(self): def test_parserbehaviour_is_what_we_coded_for(self):
t = self.fragment(u"if x: y").root t = self.fragment(u"if x: y").root
...@@ -140,6 +144,80 @@ class TestWithTransform(object): # (TransformTest): # Disabled! ...@@ -140,6 +144,80 @@ class TestWithTransform(object): # (TransformTest): # Disabled!
""", t) """, t)
if sys.version_info[:2] > (2, 4):
from Cython.Debugger import DebugWriter
from Cython.Debugger.Tests.TestLibCython import DebuggerTestCase
else:
# skip test, don't let it inherit unittest.TestCase
DebuggerTestCase = object
class TestDebugTransform(DebuggerTestCase):
def elem_hasattrs(self, elem, attrs):
# we shall supporteth python 2.3 !
return all([attr in elem.attrib for attr in attrs])
def test_debug_info(self):
try:
assert os.path.exists(self.debug_dest)
t = DebugWriter.etree.parse(self.debug_dest)
# the xpath of the standard ElementTree is primitive, don't use
# anything fancy
L = list(t.find('/Module/Globals'))
# assertTrue is retarded, use the normal assert statement
assert L
xml_globals = dict(
[(e.attrib['name'], e.attrib['type']) for e in L])
self.assertEqual(len(L), len(xml_globals))
L = list(t.find('/Module/Functions'))
assert L
xml_funcs = dict([(e.attrib['qualified_name'], e) for e in L])
self.assertEqual(len(L), len(xml_funcs))
# test globals
self.assertEqual('CObject', xml_globals.get('c_var'))
self.assertEqual('PythonObject', xml_globals.get('python_var'))
# test functions
funcnames = 'codefile.spam', 'codefile.ham', 'codefile.eggs'
required_xml_attrs = 'name', 'cname', 'qualified_name'
assert all([f in xml_funcs for f in funcnames])
spam, ham, eggs = [xml_funcs[funcname] for funcname in funcnames]
self.assertEqual(spam.attrib['name'], 'spam')
self.assertNotEqual('spam', spam.attrib['cname'])
assert self.elem_hasattrs(spam, required_xml_attrs)
# test locals of functions
spam_locals = list(spam.find('Locals'))
assert spam_locals
spam_locals.sort(key=lambda e: e.attrib['name'])
names = [e.attrib['name'] for e in spam_locals]
self.assertEqual(list('abcd'), names)
assert self.elem_hasattrs(spam_locals[0], required_xml_attrs)
# test arguments of functions
spam_arguments = list(spam.find('Arguments'))
assert spam_arguments
self.assertEqual(1, len(list(spam_arguments)))
# test step-into functions
step_into = spam.find('StepIntoFunctions')
spam_stepinto = [x.attrib['name'] for x in step_into]
assert spam_stepinto
self.assertEqual(2, len(spam_stepinto))
assert 'puts' in spam_stepinto
assert 'some_c_function' in spam_stepinto
except:
print open(self.debug_dest).read()
raise
if __name__ == "__main__": if __name__ == "__main__":
import unittest import unittest
unittest.main() unittest.main()
#!/usr/bin/env python
"""
The Cython debugger
The current directory should contain a directory named 'cython_debug', or a
path to the cython project directory should be given (the parent directory of
cython_debug).
Additional gdb args can be provided only if a path to the project directory is
given.
"""
import os
import sys
import glob
import tempfile
import textwrap
import subprocess
usage = "Usage: cygdb [PATH [GDB_ARGUMENTS]]"
def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
if not no_import:
pattern = os.path.join(path_to_debug_info,
'cython_debug',
'cython_debug_info_*')
debug_files = glob.glob(pattern)
if not debug_files:
sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
usage, os.path.abspath(path_to_debug_info)))
fd, tempfilename = tempfile.mkstemp()
f = os.fdopen(fd, 'w')
f.write(prefix_code)
f.write('set breakpoint pending on\n')
f.write("set print pretty on\n")
f.write('python from Cython.Debugger import libcython, libpython\n')
if no_import:
# don't do this, this overrides file command in .gdbinit
# f.write("file %s\n" % sys.executable)
pass
else:
path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
interpreter = open(path).read()
f.write("file %s\n" % interpreter)
f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
f.write(textwrap.dedent('''\
python
import sys
try:
gdb.lookup_type('PyModuleObject')
except RuntimeError:
sys.stderr.write(
'Python was not compiled with debug symbols (or it was '
'stripped). Some functionality may not work (properly).\\n')
end
'''))
f.close()
return tempfilename
def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
"""
Start the Cython debugger. This tells gdb to import the Cython and Python
extensions (libcython.py and libpython.py) and it enables gdb's pending
breakpoints.
path_to_debug_info is the path to the Cython build directory
gdb_argv is the list of options to gdb
no_import tells cygdb whether it should import debug information
"""
if path_to_debug_info is None:
if len(sys.argv) > 1:
path_to_debug_info = sys.argv[1]
else:
path_to_debug_info = os.curdir
if gdb_argv is None:
gdb_argv = sys.argv[2:]
if path_to_debug_info == '--':
no_import = True
tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
p = subprocess.Popen(['gdb', '-command', tempfilename] + gdb_argv)
while True:
try:
p.wait()
except KeyboardInterrupt:
pass
else:
break
os.remove(tempfilename)
from __future__ import with_statement
import os
import sys
import errno
try:
from lxml import etree
have_lxml = True
except ImportError:
have_lxml = False
try:
# Python 2.5
from xml.etree import cElementTree as etree
except ImportError:
try:
# Python 2.5
from xml.etree import ElementTree as etree
except ImportError:
try:
# normal cElementTree install
import cElementTree as etree
except ImportError:
try:
# normal ElementTree install
import elementtree.ElementTree as etree
except ImportError:
etree = None
from Cython.Compiler import Errors
class CythonDebugWriter(object):
"""
Class to output debugging information for cygdb
It writes debug information to cython_debug/cython_debug_info_<modulename>
in the build directory.
"""
def __init__(self, output_dir):
if etree is None:
raise Errors.NoElementTreeInstalledException()
self.output_dir = os.path.join(output_dir, 'cython_debug')
self.tb = etree.TreeBuilder()
# set by Cython.Compiler.ParseTreeTransforms.DebugTransform
self.module_name = None
self.start('cython_debug', attrs=dict(version='1.0'))
def start(self, name, attrs=None):
self.tb.start(name, attrs or {})
def end(self, name):
self.tb.end(name)
def serialize(self):
self.tb.end('Module')
self.tb.end('cython_debug')
xml_root_element = self.tb.close()
try:
os.makedirs(self.output_dir)
except OSError, e:
if e.errno != errno.EEXIST:
raise
et = etree.ElementTree(xml_root_element)
kw = {}
if have_lxml:
kw['pretty_print'] = True
fn = "cython_debug_info_" + self.module_name
et.write(os.path.join(self.output_dir, fn), encoding="UTF-8", **kw)
interpreter_path = os.path.join(self.output_dir, 'interpreter')
with open(interpreter_path, 'w') as f:
f.write(sys.executable)
from __future__ import with_statement
import os
import re
import sys
import uuid
import shutil
import warnings
import textwrap
import unittest
import tempfile
import subprocess
import distutils.core
from distutils import sysconfig
from distutils import ccompiler
import runtests
import Cython.Distutils.extension
from Cython.Debugger import Cygdb as cygdb
root = os.path.dirname(os.path.abspath(__file__))
codefile = os.path.join(root, 'codefile')
cfuncs_file = os.path.join(root, 'cfuncs.c')
with open(codefile) as f:
source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f))
class DebuggerTestCase(unittest.TestCase):
def setUp(self):
"""
Run gdb and have cygdb import the debug information from the code
defined in TestParseTreeTransforms's setUp method
"""
self.tempdir = tempfile.mkdtemp()
self.destfile = os.path.join(self.tempdir, 'codefile.pyx')
self.debug_dest = os.path.join(self.tempdir,
'cython_debug',
'cython_debug_info_codefile')
self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs')
self.cwd = os.getcwd()
os.chdir(self.tempdir)
shutil.copy(codefile, self.destfile)
shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c')
compiler = ccompiler.new_compiler()
compiler.compile(['cfuncs.c'], debug=True)
opts = dict(
test_directory=self.tempdir,
module='codefile',
)
cython_compile_testcase = runtests.CythonCompileTestCase(
workdir=self.tempdir,
# we clean up everything (not only compiled files)
cleanup_workdir=False,
**opts
)
cython_compile_testcase.run_cython(
targetdir=self.tempdir,
incdir=None,
annotate=False,
extra_compile_options={
'gdb_debug':True,
'output_dir':self.tempdir,
},
**opts
)
cython_compile_testcase.run_distutils(
incdir=None,
workdir=self.tempdir,
extra_extension_args={'extra_objects':['cfuncs.o']},
**opts
)
# ext = Cython.Distutils.extension.Extension(
# 'codefile',
# ['codefile.pyx'],
# pyrex_gdb=True,
# extra_objects=['cfuncs.o'])
#
# distutils.core.setup(
# script_args=['build_ext', '--inplace'],
# ext_modules=[ext],
# cmdclass=dict(build_ext=Cython.Distutils.build_ext)
# )
def tearDown(self):
os.chdir(self.cwd)
shutil.rmtree(self.tempdir)
class GdbDebuggerTestCase(DebuggerTestCase):
def setUp(self):
super(GdbDebuggerTestCase, self).setUp()
prefix_code = textwrap.dedent('''\
python
import os
import sys
import traceback
def excepthook(type, value, tb):
traceback.print_exception(type, value, tb)
os._exit(1)
sys.excepthook = excepthook
# Have tracebacks end up on sys.stderr (gdb replaces sys.stderr
# with an object that calls gdb.write())
sys.stderr = sys.__stderr__
end
''')
code = textwrap.dedent('''\
python
from Cython.Debugger.Tests import test_libcython_in_gdb
test_libcython_in_gdb.main()
end
''')
self.gdb_command_file = cygdb.make_command_file(self.tempdir,
prefix_code)
open(self.gdb_command_file, 'a').write(code)
args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args',
sys.executable, '-c', 'import codefile']
paths = []
path = os.environ.get('PYTHONPATH')
if path:
paths.append(path)
paths.append(os.path.dirname(os.path.dirname(
os.path.abspath(Cython.__file__))))
env = dict(os.environ, PYTHONPATH=os.pathsep.join(paths))
try:
p = subprocess.Popen(['gdb', '-v'], stdout=subprocess.PIPE)
have_gdb = True
except OSError:
# gdb was not installed
have_gdb = False
else:
gdb_version = p.stdout.read()
p.wait()
p.stdout.close()
if have_gdb:
# Based on Lib/test/test_gdb.py
regex = "^GNU gdb [^\d]*(\d+)\.(\d+)"
gdb_version_number = re.search(regex, gdb_version).groups()
if not have_gdb or map(int, gdb_version_number) < [7, 2]:
self.p = None
warnings.warn('Skipping gdb tests, need gdb >= 7.2')
else:
self.p = subprocess.Popen(
args,
stdout=open(os.devnull, 'w'),
stderr=subprocess.PIPE,
env=env)
def tearDown(self):
super(GdbDebuggerTestCase, self).tearDown()
if self.p:
self.p.stderr.close()
self.p.wait()
os.remove(self.gdb_command_file)
class TestAll(GdbDebuggerTestCase):
def test_all(self):
if self.p is None:
return
out, err = self.p.communicate()
border = '*' * 30
start = '%s v INSIDE GDB v %s' % (border, border)
end = '%s ^ INSIDE GDB ^ %s' % (border, border)
errmsg = '\n%s\n%s%s' % (start, err, end)
self.assertEquals(0, self.p.wait(), errmsg)
sys.stderr.write(err)
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
void
some_c_function(void)
{
int a, b, c;
a = 1;
b = 2;
}
\ No newline at end of file
cdef extern from "stdio.h":
int puts(char *s)
cdef extern:
void some_c_function()
import os
cdef int c_var = 12
python_var = 13
def spam(a=0):
cdef:
int b, c
b = c = d = 0
b = 1
c = 2
int(10)
puts("spam")
os.path.join("foo", "bar")
some_c_function()
cdef ham():
pass
cpdef eggs():
pass
cdef class SomeClass(object):
def spam(self):
pass
spam()
print "bye!"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -17,11 +17,55 @@ from distutils.dep_util import newer, newer_group ...@@ -17,11 +17,55 @@ from distutils.dep_util import newer, newer_group
from distutils import log from distutils import log
from distutils.dir_util import mkpath from distutils.dir_util import mkpath
from distutils.command import build_ext as _build_ext from distutils.command import build_ext as _build_ext
from distutils import sysconfig
extension_name_re = _build_ext.extension_name_re extension_name_re = _build_ext.extension_name_re
show_compilers = _build_ext.show_compilers show_compilers = _build_ext.show_compilers
class Optimization(object):
def __init__(self):
self.flags = (
'OPT',
'CFLAGS',
'CPPFLAGS',
'EXTRA_CFLAGS',
'BASECFLAGS',
'PY_CFLAGS',
)
self.state = sysconfig.get_config_vars(*self.flags)
self.config_vars = sysconfig.get_config_vars()
def disable_optimization(self):
"disable optimization for the C or C++ compiler"
badoptions = ('-O1', '-O2', '-O3')
for flag, option in zip(self.flags, self.state):
if option is not None:
L = [opt for opt in option.split() if opt not in badoptions]
self.config_vars[flag] = ' '.join(L)
def restore_state(self):
"restore the original state"
for flag, option in zip(self.flags, self.state):
if option is not None:
self.config_vars[flag] = option
optimization = Optimization()
try:
any
except NameError:
def any(it):
for x in it:
if x:
return True
return False
class build_ext(_build_ext.build_ext): class build_ext(_build_ext.build_ext):
description = "build C/C++ and Cython extensions (compile/link to build directory)" description = "build C/C++ and Cython extensions (compile/link to build directory)"
...@@ -47,10 +91,13 @@ class build_ext(_build_ext.build_ext): ...@@ -47,10 +91,13 @@ class build_ext(_build_ext.build_ext):
"generate .pxi file for public declarations"), "generate .pxi file for public declarations"),
('pyrex-directives=', None, ('pyrex-directives=', None,
"compiler directive overrides"), "compiler directive overrides"),
('pyrex-gdb', None,
"generate debug information for cygdb"),
]) ])
boolean_options.extend([ boolean_options.extend([
'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives', 'pyrex-c-in-temp' 'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives',
'pyrex-c-in-temp', 'pyrex-gdb',
]) ])
def initialize_options(self): def initialize_options(self):
...@@ -62,6 +109,7 @@ class build_ext(_build_ext.build_ext): ...@@ -62,6 +109,7 @@ class build_ext(_build_ext.build_ext):
self.pyrex_directives = None self.pyrex_directives = None
self.pyrex_c_in_temp = 0 self.pyrex_c_in_temp = 0
self.pyrex_gen_pxi = 0 self.pyrex_gen_pxi = 0
self.pyrex_gdb = False
def finalize_options (self): def finalize_options (self):
_build_ext.build_ext.finalize_options(self) _build_ext.build_ext.finalize_options(self)
...@@ -74,9 +122,21 @@ class build_ext(_build_ext.build_ext): ...@@ -74,9 +122,21 @@ class build_ext(_build_ext.build_ext):
self.pyrex_directives = {} self.pyrex_directives = {}
# finalize_options () # finalize_options ()
def run(self):
# We have one shot at this before build_ext initializes the compiler.
# If --pyrex-gdb is in effect as a command line option or as option
# of any Extension module, disable optimization for the C or C++
# compiler.
if (self.pyrex_gdb or any([getattr(ext, 'pyrex_gdb', False)
for ext in self.extensions])):
optimization.disable_optimization()
_build_ext.build_ext.run(self)
def build_extensions(self): def build_extensions(self):
# First, sanity-check the 'extensions' list # First, sanity-check the 'extensions' list
self.check_extensions_list(self.extensions) self.check_extensions_list(self.extensions)
for ext in self.extensions: for ext in self.extensions:
ext.sources = self.cython_sources(ext.sources, ext) ext.sources = self.cython_sources(ext.sources, ext)
self.build_extension(ext) self.build_extension(ext)
...@@ -128,7 +188,7 @@ class build_ext(_build_ext.build_ext): ...@@ -128,7 +188,7 @@ class build_ext(_build_ext.build_ext):
cplus = self.pyrex_cplus or getattr(extension, 'pyrex_cplus', 0) or \ cplus = self.pyrex_cplus or getattr(extension, 'pyrex_cplus', 0) or \
(extension.language and extension.language.lower() == 'c++') (extension.language and extension.language.lower() == 'c++')
pyrex_gen_pxi = self.pyrex_gen_pxi or getattr(extension, 'pyrex_gen_pxi', 0) pyrex_gen_pxi = self.pyrex_gen_pxi or getattr(extension, 'pyrex_gen_pxi', 0)
pyrex_gdb = self.pyrex_gdb or getattr(extension, 'pyrex_gdb', False)
# Set up the include_path for the Cython compiler: # Set up the include_path for the Cython compiler:
# 1. Start with the command line option. # 1. Start with the command line option.
# 2. Add in any (unique) paths from the extension # 2. Add in any (unique) paths from the extension
...@@ -201,6 +261,10 @@ class build_ext(_build_ext.build_ext): ...@@ -201,6 +261,10 @@ class build_ext(_build_ext.build_ext):
if rebuild: if rebuild:
log.info("cythoning %s to %s", source, target) log.info("cythoning %s to %s", source, target)
self.mkpath(os.path.dirname(target)) self.mkpath(os.path.dirname(target))
if self.inplace:
output_dir = os.curdir
else:
output_dir = self.build_lib
options = CompilationOptions(pyrex_default_options, options = CompilationOptions(pyrex_default_options,
use_listing_file = create_listing, use_listing_file = create_listing,
include_path = includes, include_path = includes,
...@@ -208,7 +272,9 @@ class build_ext(_build_ext.build_ext): ...@@ -208,7 +272,9 @@ class build_ext(_build_ext.build_ext):
output_file = target, output_file = target,
cplus = cplus, cplus = cplus,
emit_linenums = line_directives, emit_linenums = line_directives,
generate_pxi = pyrex_gen_pxi) generate_pxi = pyrex_gen_pxi,
output_dir = output_dir,
gdb_debug = pyrex_gdb)
result = cython_compile(source, options=options, result = cython_compile(source, options=options,
full_module_name=module_name) full_module_name=module_name)
else: else:
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python
import sys
from Cython.Debugger import Cygdb as cygdb
if __name__ == '__main__':
cygdb.main()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment