Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Fix debug filename path for remote debugging #389

Merged
merged 12 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 42 additions & 45 deletions spyder_kernels/customize/spydercustomize.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@
import time
import warnings

from IPython import __version__ as ipy_version
from IPython.core.getipython import get_ipython

from spyder_kernels.comms.frontendcomm import CommError, frontend_request
from spyder_kernels.comms.frontendcomm import frontend_request
from spyder_kernels.customize.namespace_manager import NamespaceManager
from spyder_kernels.customize.spyderpdb import SpyderPdb, get_new_debugger
from spyder_kernels.customize.umr import UserModuleReloader
from spyder_kernels.py3compat import (
TimeoutError, PY2, _print, encode, compat_exec, FileNotFoundError)
from spyder_kernels.customize.utils import (
capture_last_Expr, normalise_filename)
PY2, _print, encode, compat_exec, FileNotFoundError)
from spyder_kernels.customize.utils import capture_last_Expr, canonic

if not PY2:
from IPython.core.inputtransformer2 import (
Expand Down Expand Up @@ -505,18 +503,23 @@ def exec_code(code, filename, ns_globals, ns_locals=None, post_mortem=False,
__tracebackhide__ = "__pdb_exit__"


def get_file_code(filename, save_all=True):
def get_file_code(filename, save_all=True, raise_exception=False):
"""Retrive the content of a file."""
impact27 marked this conversation as resolved.
Show resolved Hide resolved
# Get code from spyder
try:
file_code = frontend_request(blocking=True).get_file_code(
return frontend_request(blocking=True).get_file_code(
filename, save_all=save_all)
except (CommError, TimeoutError, RuntimeError, FileNotFoundError):
file_code = None
if file_code is None:
with open(filename, 'r') as f:
return f.read()
return file_code
except Exception:
# Maybe this is a local file
try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
pass
if raise_exception:
raise
# Else return None
return None


def runfile(filename=None, args=None, wdir=None, namespace=None,
Expand All @@ -536,19 +539,14 @@ def runfile(filename=None, args=None, wdir=None, namespace=None,

def _exec_file(filename=None, args=None, wdir=None, namespace=None,
post_mortem=False, current_namespace=False, stack_depth=0,
exec_fun=None):
exec_fun=None, canonic_filename=None):
# Tell IPython to hide this frame (>7.16)
__tracebackhide__ = True
ipython_shell = get_ipython()
if filename is None:
filename = get_current_file_name()
if filename is None:
return
else:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed because remote filename and canonic filename are separated

# get_debugger replaces \\ by / so we must undo that here
# Otherwise code caching doesn't work
if os.name == 'nt':
filename = filename.replace('/', '\\')

try:
filename = filename.decode('utf-8')
Expand All @@ -562,22 +560,23 @@ def _exec_file(filename=None, args=None, wdir=None, namespace=None,
__umr__.run()
if args is not None and not isinstance(args, basestring):
raise TypeError("expected a character buffer object")

try:
file_code = get_file_code(filename)
file_code = get_file_code(filename, raise_exception=True)
except Exception:
# Show an error and return None
_print(
"This command failed to be executed because an error occurred"
" while trying to get the file code from Spyder's"
" editor. The error was:\n\n")
get_ipython().showtraceback(exception_only=True)
return
if file_code is None:
_print("Could not get code from editor.\n")
return

# Normalise the filename
filename = os.path.abspath(filename)
filename = os.path.normcase(filename)
# Here the remote filename has been used. It must now be valid locally.
if canonic_filename is not None:
filename = canonic_filename
else:
filename = canonic(filename)

with NamespaceManager(filename, namespace, current_namespace,
file_code=file_code, stack_depth=stack_depth + 1
Expand Down Expand Up @@ -655,7 +654,7 @@ def debugfile(filename=None, args=None, wdir=None, post_mortem=False,
if shell.is_debugging():
# Recursive
code = (
"runfile({}".format(repr(normalise_filename(filename))) +
Copy link
Contributor Author

@impact27 impact27 Jun 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed normalise_filename because filename is now the remote one, unchanged

"runfile({}".format(repr(filename)) +
", args=%r, wdir=%r, current_namespace=%r)" % (
args, wdir, current_namespace)
)
Expand All @@ -666,11 +665,13 @@ def debugfile(filename=None, args=None, wdir=None, post_mortem=False,
else:
debugger = get_new_debugger(filename, True)
_exec_file(
filename=debugger.canonic(filename),
args=args, wdir=wdir,
filename=filename,
canonic_filename=debugger.canonic(filename),
args=args,
wdir=wdir,
current_namespace=current_namespace,
exec_fun=debugger.run,
stack_depth=1
stack_depth=1,
)


Expand All @@ -696,7 +697,7 @@ def runcell(cellname, filename=None, post_mortem=False):


def _exec_cell(cellname, filename=None, post_mortem=False, stack_depth=0,
exec_fun=None):
exec_fun=None, canonic_filename=None):
"""
Execute a code cell with a given exec function.
"""
Expand All @@ -706,11 +707,6 @@ def _exec_cell(cellname, filename=None, post_mortem=False, stack_depth=0,
filename = get_current_file_name()
if filename is None:
return
else:
# get_debugger replaces \\ by / so we must undo that here
# Otherwise code caching doesn't work
if os.name == 'nt':
filename = filename.replace('/', '\\')
try:
filename = filename.decode('utf-8')
except (UnicodeError, TypeError, AttributeError):
Expand All @@ -736,14 +732,14 @@ def _exec_cell(cellname, filename=None, post_mortem=False, stack_depth=0,
# Trigger `post_execute` to exit the additional pre-execution.
# See Spyder PR #7310.
ipython_shell.events.trigger('post_execute')
try:
file_code = get_file_code(filename, save_all=False)
except Exception:
file_code = None
file_code = get_file_code(filename, save_all=False)

# Normalise the filename
filename = os.path.abspath(filename)
filename = os.path.normcase(filename)
# Here the remote filename has been used. It must now be valid locally.
if canonic_filename is not None:
filename = canonic_filename
else:
# Normalise the filename
filename = canonic(filename)

with NamespaceManager(filename, current_namespace=True,
file_code=file_code, stack_depth=stack_depth + 1
Expand All @@ -770,7 +766,7 @@ def debugcell(cellname, filename=None, post_mortem=False):
# Recursive
code = (
"runcell({}, ".format(repr(cellname)) +
"{})".format(repr(normalise_filename(filename)))
"{})".format(repr(filename))
)
shell.pdb_session.enter_recursive_debugger(
code, filename, False,
Expand All @@ -779,7 +775,8 @@ def debugcell(cellname, filename=None, post_mortem=False):
debugger = get_new_debugger(filename, False)
_exec_cell(
cellname=cellname,
filename=debugger.canonic(filename),
filename=filename,
canonic_filename=debugger.canonic(filename),
exec_fun=debugger.run,
stack_depth=1
)
Expand Down
21 changes: 13 additions & 8 deletions spyder_kernels/customize/spyderpdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import ast
import bdb
import logging
import os
import sys
import traceback
from collections import namedtuple
Expand Down Expand Up @@ -100,6 +99,9 @@ def __init__(self, completekey='tab', stdin=None, stdout=None,
# has no effect in previous versions.
self.report_skipped = False

# Keep track of remote filename
self.remote_filename = None

# --- Methods overriden for code execution
def print_exclamation_warning(self):
"""Print pdb warning for exclamation mark."""
Expand Down Expand Up @@ -774,6 +776,8 @@ def publish_pdb_state(self):
fname = unicode(fname, "utf-8")
except TypeError:
pass
if fname == self.mainpyfile and self.remote_filename is not None:
fname = self.remote_filename
lineno = frame.f_lineno

if self._previous_step == (fname, lineno):
Expand Down Expand Up @@ -833,9 +837,7 @@ def enter_recursive_debugger(self, code, filename,
debugger.use_rawinput = self.use_rawinput
debugger.prompt = "(%s) " % self.prompt.strip()

filename = debugger.canonic(filename)
debugger._wait_for_mainpyfile = True
debugger.mainpyfile = filename
debugger.set_remote_filename(filename)
debugger.continue_if_has_breakpoints = continue_if_has_breakpoints
debugger._user_requested_quit = False

Expand All @@ -852,14 +854,17 @@ def enter_recursive_debugger(self, code, filename,
# but the parent debugger (self) is not aware of this.
self._previous_step = None

def set_remote_filename(self, filename):
"""set remote filename to signal spyder on mainpyfile."""
impact27 marked this conversation as resolved.
Show resolved Hide resolved
self.remote_filename = filename
self.mainpyfile = self.canonic(filename)
self._wait_for_mainpyfile = True


def get_new_debugger(filename, continue_if_has_breakpoints):
"""Get a new debugger."""
debugger = SpyderPdb()

filename = debugger.canonic(filename)
debugger._wait_for_mainpyfile = True
debugger.mainpyfile = filename
debugger.set_remote_filename(filename)
debugger.continue_if_has_breakpoints = continue_if_has_breakpoints
debugger._user_requested_quit = False
return debugger
18 changes: 12 additions & 6 deletions spyder_kernels/customize/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,15 @@ def capture_last_Expr(code_ast, out_varname):
return code_ast, capture_last_expression


def normalise_filename(filename):
"""Normalise path for window."""
# Recursive
if os.name == 'nt':
return filename.replace('\\', '/')
return filename
def canonic(filename):
"""
Return canonical form of filename.

This is a copy of bdb.canonic, so that the debugger will process
filenames in the same way
"""
if filename == "<" + filename[1:-1] + ">":
return filename
canonic = os.path.abspath(filename)
canonic = os.path.normcase(canonic)
return canonic