devshell: Add interactive python shell

Being able to interact with the python context in the Bitbake task execution
environment has long been desireable. This patch introduces such a
mechanism. Executing "bitbake X -c devpyshell" will open a terminal connected
to a python interactive interpretor in the task context so for example you can
run commands like "d.getVar('WORKDIR')"

This version now includes readline support for command history and various other
bug fixes such as exiting cleanly compared to previous versions.

(From OE-Core rev: 36734f34fe6e4b91e293234687e63c02f5b3117e)

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Richard Purdie 2014-05-27 16:09:16 +01:00
parent 3bf24188b6
commit cd7b437d4b
2 changed files with 213 additions and 0 deletions

View File

@ -31,3 +31,124 @@ python () {
d.setVarFlag("do_devshell", "manualfakeroot", "1")
d.delVarFlag("do_devshell", "fakeroot")
}
def devpyshell(d):
import code
import select
import signal
import termios
m, s = os.openpty()
sname = os.ttyname(s)
def noechoicanon(fd):
old = termios.tcgetattr(fd)
old[3] = old[3] &~ termios.ECHO &~ termios.ICANON
# &~ termios.ISIG
termios.tcsetattr(fd, termios.TCSADRAIN, old)
# No echo or buffering over the pty
noechoicanon(s)
pid = os.fork()
if pid:
os.close(m)
oe_terminal("oepydevshell-internal.py %s %d" % (sname, pid), 'OpenEmbedded Developer PyShell', d)
os._exit(0)
else:
os.close(s)
os.dup2(m, sys.stdin.fileno())
os.dup2(m, sys.stdout.fileno())
os.dup2(m, sys.stderr.fileno())
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
bb.utils.nonblockingfd(sys.stdout)
bb.utils.nonblockingfd(sys.stderr)
bb.utils.nonblockingfd(sys.stdin)
_context = {
"os": os,
"bb": bb,
"time": time,
"d": d,
}
ps1 = "pydevshell> "
ps2 = "... "
buf = []
more = False
i = code.InteractiveInterpreter(locals=_context)
print("OE PyShell (PN = %s)\n" % d.getVar("PN", True))
def prompt(more):
if more:
prompt = ps2
else:
prompt = ps1
sys.stdout.write(prompt)
# Restore Ctrl+C since bitbake masks this
def signal_handler(signal, frame):
raise KeyboardInterrupt
signal.signal(signal.SIGINT, signal_handler)
child = None
prompt(more)
while True:
try:
try:
(r, _, _) = select.select([sys.stdin], [], [], 1)
if not r:
continue
line = sys.stdin.readline().strip()
if not line:
prompt(more)
continue
except EOFError as e:
sys.stdout.write("\n")
except (OSError, IOError) as e:
if e.errno == 11:
continue
if e.errno == 5:
return
raise
else:
if not child:
child = int(line)
continue
buf.append(line)
source = "\n".join(buf)
more = i.runsource(source, "<pyshell>")
if not more:
buf = []
prompt(more)
except KeyboardInterrupt:
i.write("\nKeyboardInterrupt\n")
buf = []
more = False
prompt(more)
except SystemExit:
# Easiest way to ensure everything exits
os.kill(child, signal.SIGTERM)
break
python do_devpyshell() {
import signal
try:
devpyshell(d)
except SystemExit:
# Stop the SIGTERM above causing an error exit code
return
finally:
return
}
addtask devpyshell after do_patch
do_devpyshell[nostamp] = "1"

View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
import os
import sys
import time
import select
import fcntl
import termios
import readline
import signal
def nonblockingfd(fd):
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
def echonocbreak(fd):
old = termios.tcgetattr(fd)
old[3] = old[3] | termios.ECHO | termios.ICANON
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def cbreaknoecho(fd):
old = termios.tcgetattr(fd)
old[3] = old[3] &~ termios.ECHO &~ termios.ICANON
termios.tcsetattr(fd, termios.TCSADRAIN, old)
if len(sys.argv) != 3:
print("Incorrect parameters")
sys.exit(1)
pty = open(sys.argv[1], "w+b", 0)
parent = int(sys.argv[2])
# Don't buffer output by line endings
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
nonblockingfd(pty)
nonblockingfd(sys.stdin)
histfile = os.path.expanduser("~/.oedevpyshell-history")
readline.parse_and_bind("tab: complete")
try:
readline.read_history_file(histfile)
except IOError:
pass
try:
i = ""
o = ""
# Need cbreak/noecho whilst in select so we trigger on any keypress
cbreaknoecho(sys.stdin.fileno())
# Send our PID to the other end so they can kill us.
pty.write(str(os.getpid()) + "\n")
while True:
try:
writers = []
if i:
writers.append(sys.stdout)
(ready, _, _) = select.select([pty, sys.stdin], writers , [], 0)
try:
if pty in ready:
i = i + pty.read()
if i:
# Write a page at a time to avoid overflowing output
# d.keys() is a good way to do that
sys.stdout.write(i[:4096])
i = i[4096:]
if sys.stdin in ready:
echonocbreak(sys.stdin.fileno())
o = raw_input()
cbreaknoecho(sys.stdin.fileno())
pty.write(o + "\n")
except (IOError, OSError) as e:
if e.errno == 11:
continue
if e.errno == 5:
sys.exit(0)
raise
except EOFError:
sys.exit(0)
except KeyboardInterrupt:
os.kill(parent, signal.SIGINT)
except SystemExit:
pass
except Exception as e:
import traceback
print("Exception in oepydehshell-internal: " + str(e))
traceback.print_exc()
time.sleep(5)
finally:
readline.write_history_file(histfile)