Skip to contentSkip to conversation

devshell: Add interactive python shell

PATCH New Richard Purdie 1 participant 1 message May 27, 2014

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 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.

meta/classes/devshell.bbclass +121 -0
index 92edb9e..41164a3 100644
@@ -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"
scripts/oepydevshell-internal.py new file +92 -0

92 lines not on this page · raw message