xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/process.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#
2*4882a593Smuzhiyun# Copyright BitBake Contributors
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun
7*4882a593Smuzhiyunimport logging
8*4882a593Smuzhiyunimport signal
9*4882a593Smuzhiyunimport subprocess
10*4882a593Smuzhiyunimport errno
11*4882a593Smuzhiyunimport select
12*4882a593Smuzhiyunimport bb
13*4882a593Smuzhiyun
14*4882a593Smuzhiyunlogger = logging.getLogger('BitBake.Process')
15*4882a593Smuzhiyun
16*4882a593Smuzhiyundef subprocess_setup():
17*4882a593Smuzhiyun    # Python installs a SIGPIPE handler by default. This is usually not what
18*4882a593Smuzhiyun    # non-Python subprocesses expect.
19*4882a593Smuzhiyun    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
20*4882a593Smuzhiyun
21*4882a593Smuzhiyunclass CmdError(RuntimeError):
22*4882a593Smuzhiyun    def __init__(self, command, msg=None):
23*4882a593Smuzhiyun        self.command = command
24*4882a593Smuzhiyun        self.msg = msg
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun    def __str__(self):
27*4882a593Smuzhiyun        if not isinstance(self.command, str):
28*4882a593Smuzhiyun            cmd = subprocess.list2cmdline(self.command)
29*4882a593Smuzhiyun        else:
30*4882a593Smuzhiyun            cmd = self.command
31*4882a593Smuzhiyun
32*4882a593Smuzhiyun        msg = "Execution of '%s' failed" % cmd
33*4882a593Smuzhiyun        if self.msg:
34*4882a593Smuzhiyun            msg += ': %s' % self.msg
35*4882a593Smuzhiyun        return msg
36*4882a593Smuzhiyun
37*4882a593Smuzhiyunclass NotFoundError(CmdError):
38*4882a593Smuzhiyun    def __str__(self):
39*4882a593Smuzhiyun        return CmdError.__str__(self) + ": command not found"
40*4882a593Smuzhiyun
41*4882a593Smuzhiyunclass ExecutionError(CmdError):
42*4882a593Smuzhiyun    def __init__(self, command, exitcode, stdout = None, stderr = None):
43*4882a593Smuzhiyun        CmdError.__init__(self, command)
44*4882a593Smuzhiyun        self.exitcode = exitcode
45*4882a593Smuzhiyun        self.stdout = stdout
46*4882a593Smuzhiyun        self.stderr = stderr
47*4882a593Smuzhiyun        self.extra_message = None
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun    def __str__(self):
50*4882a593Smuzhiyun        message = ""
51*4882a593Smuzhiyun        if self.stderr:
52*4882a593Smuzhiyun            message += self.stderr
53*4882a593Smuzhiyun        if self.stdout:
54*4882a593Smuzhiyun            message += self.stdout
55*4882a593Smuzhiyun        if message:
56*4882a593Smuzhiyun            message = ":\n" + message
57*4882a593Smuzhiyun        return (CmdError.__str__(self) +
58*4882a593Smuzhiyun                " with exit code %s" % self.exitcode + message + (self.extra_message or ""))
59*4882a593Smuzhiyun
60*4882a593Smuzhiyunclass Popen(subprocess.Popen):
61*4882a593Smuzhiyun    defaults = {
62*4882a593Smuzhiyun        "close_fds": True,
63*4882a593Smuzhiyun        "preexec_fn": subprocess_setup,
64*4882a593Smuzhiyun        "stdout": subprocess.PIPE,
65*4882a593Smuzhiyun        "stderr": subprocess.PIPE,
66*4882a593Smuzhiyun        "stdin": subprocess.PIPE,
67*4882a593Smuzhiyun        "shell": False,
68*4882a593Smuzhiyun    }
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun    def __init__(self, *args, **kwargs):
71*4882a593Smuzhiyun        options = dict(self.defaults)
72*4882a593Smuzhiyun        options.update(kwargs)
73*4882a593Smuzhiyun        subprocess.Popen.__init__(self, *args, **options)
74*4882a593Smuzhiyun
75*4882a593Smuzhiyundef _logged_communicate(pipe, log, input, extrafiles):
76*4882a593Smuzhiyun    if pipe.stdin:
77*4882a593Smuzhiyun        if input is not None:
78*4882a593Smuzhiyun            pipe.stdin.write(input)
79*4882a593Smuzhiyun        pipe.stdin.close()
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun    outdata, errdata = [], []
82*4882a593Smuzhiyun    rin = []
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun    if pipe.stdout is not None:
85*4882a593Smuzhiyun        bb.utils.nonblockingfd(pipe.stdout.fileno())
86*4882a593Smuzhiyun        rin.append(pipe.stdout)
87*4882a593Smuzhiyun    if pipe.stderr is not None:
88*4882a593Smuzhiyun        bb.utils.nonblockingfd(pipe.stderr.fileno())
89*4882a593Smuzhiyun        rin.append(pipe.stderr)
90*4882a593Smuzhiyun    for fobj, _ in extrafiles:
91*4882a593Smuzhiyun        bb.utils.nonblockingfd(fobj.fileno())
92*4882a593Smuzhiyun        rin.append(fobj)
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun    def readextras(selected):
95*4882a593Smuzhiyun        for fobj, func in extrafiles:
96*4882a593Smuzhiyun            if fobj in selected:
97*4882a593Smuzhiyun                try:
98*4882a593Smuzhiyun                    data = fobj.read()
99*4882a593Smuzhiyun                except IOError as err:
100*4882a593Smuzhiyun                    if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK:
101*4882a593Smuzhiyun                        data = None
102*4882a593Smuzhiyun                if data is not None:
103*4882a593Smuzhiyun                    func(data)
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun    def read_all_pipes(log, rin, outdata, errdata):
106*4882a593Smuzhiyun        rlist = rin
107*4882a593Smuzhiyun        stdoutbuf = b""
108*4882a593Smuzhiyun        stderrbuf = b""
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun        try:
111*4882a593Smuzhiyun            r,w,e = select.select (rlist, [], [], 1)
112*4882a593Smuzhiyun        except OSError as e:
113*4882a593Smuzhiyun            if e.errno != errno.EINTR:
114*4882a593Smuzhiyun                raise
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun        readextras(r)
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun        if pipe.stdout in r:
119*4882a593Smuzhiyun            data = stdoutbuf + pipe.stdout.read()
120*4882a593Smuzhiyun            if data is not None and len(data) > 0:
121*4882a593Smuzhiyun                try:
122*4882a593Smuzhiyun                    data = data.decode("utf-8")
123*4882a593Smuzhiyun                    outdata.append(data)
124*4882a593Smuzhiyun                    log.write(data)
125*4882a593Smuzhiyun                    log.flush()
126*4882a593Smuzhiyun                    stdoutbuf = b""
127*4882a593Smuzhiyun                except UnicodeDecodeError:
128*4882a593Smuzhiyun                    stdoutbuf = data
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun        if pipe.stderr in r:
131*4882a593Smuzhiyun            data = stderrbuf + pipe.stderr.read()
132*4882a593Smuzhiyun            if data is not None and len(data) > 0:
133*4882a593Smuzhiyun                try:
134*4882a593Smuzhiyun                    data = data.decode("utf-8")
135*4882a593Smuzhiyun                    errdata.append(data)
136*4882a593Smuzhiyun                    log.write(data)
137*4882a593Smuzhiyun                    log.flush()
138*4882a593Smuzhiyun                    stderrbuf = b""
139*4882a593Smuzhiyun                except UnicodeDecodeError:
140*4882a593Smuzhiyun                    stderrbuf = data
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun    try:
143*4882a593Smuzhiyun        # Read all pipes while the process is open
144*4882a593Smuzhiyun        while pipe.poll() is None:
145*4882a593Smuzhiyun            read_all_pipes(log, rin, outdata, errdata)
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun        # Process closed, drain all pipes...
148*4882a593Smuzhiyun        read_all_pipes(log, rin, outdata, errdata)
149*4882a593Smuzhiyun    finally:
150*4882a593Smuzhiyun        log.flush()
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun    if pipe.stdout is not None:
153*4882a593Smuzhiyun        pipe.stdout.close()
154*4882a593Smuzhiyun    if pipe.stderr is not None:
155*4882a593Smuzhiyun        pipe.stderr.close()
156*4882a593Smuzhiyun    return ''.join(outdata), ''.join(errdata)
157*4882a593Smuzhiyun
158*4882a593Smuzhiyundef run(cmd, input=None, log=None, extrafiles=None, **options):
159*4882a593Smuzhiyun    """Convenience function to run a command and return its output, raising an
160*4882a593Smuzhiyun    exception when the command fails"""
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun    if not extrafiles:
163*4882a593Smuzhiyun        extrafiles = []
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun    if isinstance(cmd, str) and not "shell" in options:
166*4882a593Smuzhiyun        options["shell"] = True
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun    try:
169*4882a593Smuzhiyun        pipe = Popen(cmd, **options)
170*4882a593Smuzhiyun    except OSError as exc:
171*4882a593Smuzhiyun        if exc.errno == 2:
172*4882a593Smuzhiyun            raise NotFoundError(cmd)
173*4882a593Smuzhiyun        else:
174*4882a593Smuzhiyun            raise CmdError(cmd, exc)
175*4882a593Smuzhiyun
176*4882a593Smuzhiyun    if log:
177*4882a593Smuzhiyun        stdout, stderr = _logged_communicate(pipe, log, input, extrafiles)
178*4882a593Smuzhiyun    else:
179*4882a593Smuzhiyun        stdout, stderr = pipe.communicate(input)
180*4882a593Smuzhiyun        if not stdout is None:
181*4882a593Smuzhiyun            stdout = stdout.decode("utf-8")
182*4882a593Smuzhiyun        if not stderr is None:
183*4882a593Smuzhiyun            stderr = stderr.decode("utf-8")
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun    if pipe.returncode != 0:
186*4882a593Smuzhiyun        if log:
187*4882a593Smuzhiyun            # Don't duplicate the output in the exception if logging it
188*4882a593Smuzhiyun            raise ExecutionError(cmd, pipe.returncode, None, None)
189*4882a593Smuzhiyun        raise ExecutionError(cmd, pipe.returncode, stdout, stderr)
190*4882a593Smuzhiyun    return stdout, stderr
191