1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7inherit terminal
8
9DEVSHELL = "${SHELL}"
10
11python do_devshell () {
12    if d.getVarFlag("do_devshell", "manualfakeroot"):
13       d.prependVar("DEVSHELL", "pseudo ")
14       fakeenv = d.getVar("FAKEROOTENV").split()
15       for f in fakeenv:
16            k = f.split("=")
17            d.setVar(k[0], k[1])
18            d.appendVar("OE_TERMINAL_EXPORTS", " " + k[0])
19       d.delVarFlag("do_devshell", "fakeroot")
20
21    oe_terminal(d.getVar('DEVSHELL'), 'OpenEmbedded Developer Shell', d)
22}
23
24addtask devshell after do_patch do_prepare_recipe_sysroot
25
26# The directory that the terminal starts in
27DEVSHELL_STARTDIR ?= "${S}"
28do_devshell[dirs] = "${DEVSHELL_STARTDIR}"
29do_devshell[nostamp] = "1"
30do_devshell[network] = "1"
31
32# devshell and fakeroot/pseudo need careful handling since only the final
33# command should run under fakeroot emulation, any X connection should
34# be done as the normal user. We therfore carefully construct the envionment
35# manually
36python () {
37    if d.getVarFlag("do_devshell", "fakeroot"):
38       # We need to signal our code that we want fakeroot however we
39       # can't manipulate the environment and variables here yet (see YOCTO #4795)
40       d.setVarFlag("do_devshell", "manualfakeroot", "1")
41       d.delVarFlag("do_devshell", "fakeroot")
42}
43
44def pydevshell(d):
45
46    import code
47    import select
48    import signal
49    import termios
50
51    m, s = os.openpty()
52    sname = os.ttyname(s)
53
54    def noechoicanon(fd):
55        old = termios.tcgetattr(fd)
56        old[3] = old[3] &~ termios.ECHO &~ termios.ICANON
57        # &~ termios.ISIG
58        termios.tcsetattr(fd, termios.TCSADRAIN, old)
59
60    # No echo or buffering over the pty
61    noechoicanon(s)
62
63    pid = os.fork()
64    if pid:
65        os.close(m)
66        oe_terminal("oepydevshell-internal.py %s %d" % (sname, pid), 'OpenEmbedded Developer PyShell', d)
67        os._exit(0)
68    else:
69        os.close(s)
70
71        os.dup2(m, sys.stdin.fileno())
72        os.dup2(m, sys.stdout.fileno())
73        os.dup2(m, sys.stderr.fileno())
74
75        bb.utils.nonblockingfd(sys.stdout)
76        bb.utils.nonblockingfd(sys.stderr)
77        bb.utils.nonblockingfd(sys.stdin)
78
79        _context = {
80            "os": os,
81            "bb": bb,
82            "time": time,
83            "d": d,
84        }
85
86        ps1 = "pydevshell> "
87        ps2 = "... "
88        buf = []
89        more = False
90
91        i = code.InteractiveInterpreter(locals=_context)
92        print("OE PyShell (PN = %s)\n" % d.getVar("PN"))
93
94        def prompt(more):
95            if more:
96                prompt = ps2
97            else:
98                prompt = ps1
99            sys.stdout.write(prompt)
100            sys.stdout.flush()
101
102        # Restore Ctrl+C since bitbake masks this
103        def signal_handler(signal, frame):
104            raise KeyboardInterrupt
105        signal.signal(signal.SIGINT, signal_handler)
106
107        child = None
108
109        prompt(more)
110        while True:
111            try:
112                try:
113                    (r, _, _) = select.select([sys.stdin], [], [], 1)
114                    if not r:
115                        continue
116                    line = sys.stdin.readline().strip()
117                    if not line:
118                        prompt(more)
119                        continue
120                except EOFError as e:
121                    sys.stdout.write("\n")
122                    sys.stdout.flush()
123                except (OSError, IOError) as e:
124                    if e.errno == 11:
125                        continue
126                    if e.errno == 5:
127                        return
128                    raise
129                else:
130                    if not child:
131                        child = int(line)
132                        continue
133                    buf.append(line)
134                    source = "\n".join(buf)
135                    more = i.runsource(source, "<pyshell>")
136                    if not more:
137                        buf = []
138                    sys.stderr.flush()
139                    prompt(more)
140            except KeyboardInterrupt:
141                i.write("\nKeyboardInterrupt\n")
142                buf = []
143                more = False
144                prompt(more)
145            except SystemExit:
146                # Easiest way to ensure everything exits
147                os.kill(child, signal.SIGTERM)
148                break
149
150python do_pydevshell() {
151    import signal
152
153    try:
154        pydevshell(d)
155    except SystemExit:
156        # Stop the SIGTERM above causing an error exit code
157        return
158    finally:
159        return
160}
161addtask pydevshell after do_patch
162
163do_pydevshell[nostamp] = "1"
164do_pydevshell[network] = "1"
165