8000 bpo-44554: refactor pdb targets (and internal tweaks) by jaraco · Pull Request #26992 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-44554: refactor pdb targets (and internal tweaks) #26992

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

Merged
merged 14 commits into from
Jul 19, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

8000
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Refactor module/script handling to share an interface (check method).
  • Loading branch information
jaraco committed Jul 2, 2021
commit 59b5906908987c4b6c110521412b2ea8817e339b
81 changes: 51 additions & 30 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1534,11 +1534,16 @@ def lookupmodule(self, filename):
return fullname
return None

def _runmodule(self, module_name):
def _run(self, target):
if isinstance(target, ModuleTarget):
return self._runmodule(target)
elif isinstance(target, ScriptTarget):
return self._runscript(target)

def _runmodule(self, target: 'ModuleTarget'):
self._wait_for_mainpyfile = True
self._user_requested_quit = False
import runpy
mod_name, mod_spec, code = runpy._get_module_details(module_name)
mod_name, mod_spec, code = target.module_details
self.mainpyfile = self.canonic(code.co_filename)
import __main__
__main__.__dict__.clear()
Expand All @@ -1552,7 +1557,7 @@ def _runmodule(self, module_name):
})
self.run(code)

def _runscript(self, filename):
def _runscript(self, filename: 'ScriptTarget'):
# The script has to run in __main__ namespace (or imports from
# __main__ will break).
#
Expand Down Expand Up @@ -1665,6 +1670,34 @@ def help():
To let the script run up to a given line X in the debugged file, use
"-c 'until X'"."""


class ScriptTarget(str):
def __new__(cls, val):
res = super().__new__(cls, os.path.realpath(val))
res.orig = val
return res

def check(self):
if not os.path.exists(self):
print('Error:', self.orig, 'does not exist')
sys.exit(1)

# Replace pdb's dir with script's dir in front of module search path.
sys.path[0] = os.path.dirname(self)


class ModuleTarget(str):
def check(self):
pass

@property
def module_details(self):
if not hasattr(self, '_details'):
import runpy
self._details = runpy._get_module_details(self)
return self._details


def main():
import getopt

Expand All @@ -1674,28 +1707,19 @@ def main():
print(_usage)
sys.exit(2)

commands = []
run_as_module = False
for opt, optarg in opts:
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously, the opts would be iterated over just once. In this new code, they are iterated over three times. Any performance degradation is negligible and outweighed by the value of disentangling the concerns.

if opt in ['-h', '--help']:
print(_usage)
sys.exit()
elif opt in ['-c', '--command']:
commands.append(optarg)
elif opt in ['-m']:
run_as_module = True

mainpyfile = args[0] # Get script filename
if not run_as_module and not os.path.exists(mainpyfile):
print('Error:', mainpyfile, 'does not exist')
sys.exit(1)
if any(opt in ['-h', '--help'] for opt, optarg in opts):
print(_usage)
sys.exit()

sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list
commands = [optarg for opt, optarg in opts if opt in ['-c', '--command']]

if not run_as_module:
mainpyfile = os.path.realpath(mainpyfile)
Copy link
Member Author

Choose a reason for hiding this comment

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

This line changes the meaning of mainpyfile mid-function. It's because of this behavior that ScriptTarget contains an 'orig' property (to keep both representations), even though the "real path" form is the preferred one except during error checking.

# Replace pdb's dir with script's dir in front of module search path.
sys.path[0] = os.path.dirname(mainpyfile)
module_indicated = any(opt in ['-m'] for opt, optarg in opts)
cls = ModuleTarget if module_indicated else ScriptTarget
target = cls(args[0])

target.check()

sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list
Copy link
Member Author

Choose a reason for hiding this comment

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

In the previous implementation, assignment of sys.argv[:] would happen before the sys.path change. In the new code, the sys.path change happens during the ScriptTarget.check. I contend these two operations are independent and can happen in any order.


# Note on saving/restoring sys.argv: it's a good idea when sys.argv was
# modified by the script being debugged. It's a bad idea when it was
Expand All @@ -1705,15 +1729,12 @@ def main():
pdb.rcLines.extend(commands)
while True:
try:
if run_as_module:
pdb._runmodule(mainpyfile)
else:
pdb._runscript(mainpyfile)
pdb._run(target)
if pdb._user_requested_quit:
break
print("The program finished and will be restarted")
except Restart:
print("Restarting", mainpyfile, "with arguments:")
print("Restarting", target, "with arguments:")
print("\t" + " ".join(sys.argv[1:]))
except SystemExit:
# In most cases SystemExit does not warrant a post-mortem session.
Expand All @@ -1728,7 +1749,7 @@ def main():
print("Running 'cont' or 'step' will restart the program")
t = sys.exc_info()[2]
pdb.interaction(None, t)
print("Post mortem debugger finished. The " + mainpyfile +
print("Post mortem debugger finished. The " + target +
" will be restarted")


Expand Down
0