Openembedded Core Discussions
 help / color / mirror / Atom feed
* [PATCH] devshell: Add interactive python shell
@ 2014-05-22 16:28 Richard Purdie
  0 siblings, 0 replies; 2+ messages in thread
From: Richard Purdie @ 2014-05-22 16:28 UTC (permalink / raw)
  To: openembedded-core

Being able to interact with the python context in the Bitbake task execution
environment has long been desirable. 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 fixes various issues with the previous RFC version from a
while ago and generally seems to work effectively.

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>

diff --git a/meta/classes/devshell.bbclass b/meta/classes/devshell.bbclass
index 92edb9e..a8e707d 100644
--- a/meta/classes/devshell.bbclass
+++ b/meta/classes/devshell.bbclass
@@ -31,3 +31,79 @@ python () {
        d.setVarFlag("do_devshell", "manualfakeroot", "1")
        d.delVarFlag("do_devshell", "fakeroot")
 } 
+
+python do_devpyshell() {
+    m, s = os.openpty()
+    sname = os.ttyname(s)
+    os.system('stty cs8 -icanon min 1 -isig -echo -F %s > /dev/null 2> /dev/null' % sname)
+    pid = os.fork()
+    if pid:
+        oe_terminal("oepydevshell-internal.py %s" % sname, 'OpenEmbedded Developer PyShell', d)
+        os._exit(0)
+    else:
+        os.dup2(m, sys.stdin.fileno())
+        os.dup2(m, sys.stdout.fileno())
+        os.dup2(m, sys.stderr.fileno())
+        os.close(s)
+
+        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 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,
+        }
+
+        import code, select
+
+        ps1 = "pydevshell> "
+        ps2 = "... "
+        buf = []
+        more = False
+        try:
+            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)
+
+            prompt(more)
+            while True:
+                try:
+                    try:
+                        (r, _, _) = select.select([sys.stdin], [], [], 1)
+                        if not r:
+                            continue
+                        line = sys.stdin.readline().strip()
+                    except EOFError as e:
+                        sys.stdout.write("\n")
+                    except (OSError, IOError) as e:
+                        if e.errno == 11:
+                            continue
+                        raise
+                    else:
+                        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
+        except Exception as e:
+            bb.fatal(str(e))
+}
+addtask devpyshell after do_patch
+
+do_devpyshell[nostamp] = "1"
diff --git a/scripts/oepydevshell-internal.py b/scripts/oepydevshell-internal.py
new file mode 100755
index 0000000..11bb828
--- /dev/null
+++ b/scripts/oepydevshell-internal.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import time
+import select
+import fcntl
+
+def nonblockingfd(fd):
+    fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
+if len(sys.argv) != 2:
+    print("Incorrect parameters")
+    sys.exit(1)
+
+try:
+    pty = open(sys.argv[1], "w+b", 0)
+    nonblockingfd(pty)
+    nonblockingfd(sys.stdin)
+    # Don't buffer output by line endings
+    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+
+    i = ""
+    o = ""
+
+    while True:
+        writers = []
+        if i:
+            writers.append(sys.stdout)
+        if o:
+            writers.append(pty)
+        (ready, _, _) = select.select([pty, sys.stdin], writers , [], 1)
+        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:
+                o = o + sys.stdin.read()
+            if o:
+                pty.write(o)
+                o = ""
+        except (IOError, OSError) as e:
+            if e.errno == 11:
+                continue
+            raise
+except Exception as e:
+    print("Exception in oepydehshell-internal: " + str(e))
+    time.sleep(5)
+
+




^ permalink raw reply related	[flat|nested] 2+ messages in thread
* [PATCH] devshell: Add interactive python shell
@ 2014-05-27 15:09 Richard Purdie
  0 siblings, 0 replies; 2+ messages in thread
From: Richard Purdie @ 2014-05-27 15:09 UTC (permalink / raw)
  To: openembedded-core

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.

Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>

diff --git a/meta/classes/devshell.bbclass b/meta/classes/devshell.bbclass
index 92edb9e..41164a3 100644
--- a/meta/classes/devshell.bbclass
+++ b/meta/classes/devshell.bbclass
@@ -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"
diff --git a/scripts/oepydevshell-internal.py b/scripts/oepydevshell-internal.py
new file mode 100755
index 0000000..f7b2e4e
--- /dev/null
+++ b/scripts/oepydevshell-internal.py
@@ -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)




^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2014-05-27 15:09 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-05-22 16:28 [PATCH] devshell: Add interactive python shell Richard Purdie
  -- strict thread matches above, loose matches on Subject: below --
2014-05-27 15:09 Richard Purdie

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox