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