inherit terminal

DEVSHELL = "${SHELL}"

python do_devshell () {
    if d.getVarFlag("do_devshell", "manualfakeroot"):
       d.prependVar("DEVSHELL", "pseudo ")
       fakeenv = d.getVar("FAKEROOTENV", True).split()
       for f in fakeenv:
            k = f.split("=")
            d.setVar(k[0], k[1])           
            d.appendVar("OE_TERMINAL_EXPORTS", " " + k[0])
       d.delVarFlag("do_devshell", "fakeroot")

    oe_terminal(d.getVar('DEVSHELL', True), 'OpenEmbedded Developer Shell', d)
}

addtask devshell after do_patch

do_devshell[dirs] = "${S}"
do_devshell[nostamp] = "1"

# devshell and fakeroot/pseudo need careful handling since only the final
# command should run under fakeroot emulation, any X connection should
# be done as the normal user. We therfore carefully construct the envionment
# manually
python () {
    if d.getVarFlag("do_devshell", "fakeroot"):
       # We need to signal our code that we want fakeroot however we
       # can't manipulate the environment and variables here yet (see YOCTO #4795)
       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"