blob: bcee43138c7bd7c9d2ab250bd52bd9089523c47b [file] [log] [blame]
Raul Tambrea04028c2019-05-13 17:23:36 +00001# coding=utf-8
maruel@chromium.org4f6852c2012-04-20 20:39:20 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org4860f052011-03-25 20:34:38 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Collection of subprocess wrapper functions.
6
7In theory you shouldn't need anything else in subprocess, or this module failed.
8"""
9
John Budorick9875e182018-12-05 22:57:31 +000010import codecs
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000011import errno
maruel@chromium.org4860f052011-03-25 20:34:38 +000012import logging
13import os
14import subprocess
15import sys
maruel@chromium.org4860f052011-03-25 20:34:38 +000016import threading
17
18# Constants forwarded from subprocess.
19PIPE = subprocess.PIPE
20STDOUT = subprocess.STDOUT
Gavin Mak512f3cb2023-09-05 18:02:24 +000021DEVNULL = subprocess.DEVNULL
maruel@chromium.org4860f052011-03-25 20:34:38 +000022
maruel@chromium.org4860f052011-03-25 20:34:38 +000023
24class CalledProcessError(subprocess.CalledProcessError):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000025 """Augment the standard exception with more data."""
26 def __init__(self, returncode, cmd, cwd, stdout, stderr):
27 super(CalledProcessError, self).__init__(returncode, cmd, output=stdout)
28 self.stdout = self.output # for backward compatibility.
29 self.stderr = stderr
30 self.cwd = cwd
maruel@chromium.org4860f052011-03-25 20:34:38 +000031
Mike Frysinger124bb8e2023-09-06 05:48:55 +000032 def __str__(self):
33 out = 'Command %r returned non-zero exit status %s' % (' '.join(
34 self.cmd), self.returncode)
35 if self.cwd:
36 out += ' in ' + self.cwd
37 if self.stdout:
38 out += '\n' + self.stdout.decode('utf-8', 'ignore')
39 if self.stderr:
40 out += '\n' + self.stderr.decode('utf-8', 'ignore')
41 return out
maruel@chromium.org4860f052011-03-25 20:34:38 +000042
43
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000044class CygwinRebaseError(CalledProcessError):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000045 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000046
47
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000048## Utility functions
49
50
51def kill_pid(pid):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000052 """Kills a process by its process id."""
53 try:
54 # Unable to import 'module'
55 # pylint: disable=no-member,F0401
56 import signal
57 return os.kill(pid, signal.SIGTERM)
58 except ImportError:
59 pass
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000060
61
maruel@chromium.org4860f052011-03-25 20:34:38 +000062def get_english_env(env):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000063 """Forces LANG and/or LANGUAGE to be English.
maruel@chromium.org4860f052011-03-25 20:34:38 +000064
65 Forces encoding to utf-8 for subprocesses.
66
67 Returns None if it is unnecessary.
68 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +000069 if sys.platform == 'win32':
70 return None
71 env = env or os.environ
maruel@chromium.org4860f052011-03-25 20:34:38 +000072
Mike Frysinger124bb8e2023-09-06 05:48:55 +000073 # Test if it is necessary at all.
74 is_english = lambda name: env.get(name, 'en').startswith('en')
maruel@chromium.org4860f052011-03-25 20:34:38 +000075
Mike Frysinger124bb8e2023-09-06 05:48:55 +000076 if is_english('LANG') and is_english('LANGUAGE'):
77 return None
maruel@chromium.org4860f052011-03-25 20:34:38 +000078
Mike Frysinger124bb8e2023-09-06 05:48:55 +000079 # Requires modifications.
80 env = env.copy()
81
82 def fix_lang(name):
83 if not is_english(name):
84 env[name] = 'en_US.UTF-8'
85
86 fix_lang('LANG')
87 fix_lang('LANGUAGE')
88 return env
maruel@chromium.org4860f052011-03-25 20:34:38 +000089
90
maruel@google.comef77f9e2011-11-24 15:24:02 +000091class Popen(subprocess.Popen):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000092 """Wraps subprocess.Popen() with various workarounds.
maruel@chromium.org4860f052011-03-25 20:34:38 +000093
maruel@chromium.org421982f2011-04-01 17:38:06 +000094 - Forces English output since it's easier to parse the stdout if it is always
95 in English.
96 - Sets shell=True on windows by default. You can override this by forcing
97 shell parameter to a value.
Edward Lesmescf06cad2020-12-14 22:03:23 +000098 - Adds support for DEVNULL to not buffer when not needed.
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +000099 - Adds self.start property.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000100
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000101 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
102 exceptions generated by cygwin when it fails trying to emulate fork().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000103 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000104 # subprocess.Popen.__init__() is not threadsafe; there is a race between
105 # creating the exec-error pipe for the child and setting it to CLOEXEC
106 # during which another thread can fork and cause the pipe to be inherited by
107 # its descendents, which will cause the current Popen to hang until all
108 # those descendents exit. Protect this with a lock so that only one
109 # fork/exec can happen at a time.
110 popen_lock = threading.Lock()
torne@chromium.org434e7902015-09-15 09:57:01 +0000111
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000112 def __init__(self, args, **kwargs):
113 env = get_english_env(kwargs.get('env'))
114 if env:
115 kwargs['env'] = env
116 if kwargs.get('env') is not None:
117 # Subprocess expects environment variables to be strings in Python
118 # 3.
119 def ensure_str(value):
120 if isinstance(value, bytes):
121 return value.decode()
122 return value
Raul Tambree9730d72020-01-15 19:28:48 +0000123
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000124 kwargs['env'] = {
125 ensure_str(k): ensure_str(v)
126 for k, v in kwargs['env'].items()
127 }
128 if kwargs.get('shell') is None:
129 # *Sigh*: Windows needs shell=True, or else it won't search %PATH%
130 # for the executable, but shell=True makes subprocess on Linux fail
131 # when it's called with a list because it only tries to execute the
132 # first item in the list.
133 kwargs['shell'] = bool(sys.platform == 'win32')
maruel@chromium.org4860f052011-03-25 20:34:38 +0000134
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000135 if isinstance(args, (str, bytes)):
136 tmp_str = args
137 elif isinstance(args, (list, tuple)):
138 tmp_str = ' '.join(args)
139 else:
140 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
141 if kwargs.get('cwd', None):
142 tmp_str += '; cwd=%s' % kwargs['cwd']
143 logging.debug(tmp_str)
maruel@chromium.org421982f2011-04-01 17:38:06 +0000144
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000145 try:
146 with self.popen_lock:
147 super(Popen, self).__init__(args, **kwargs)
148 except OSError as e:
149 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
150 # Convert fork() emulation failure into a CygwinRebaseError().
151 raise CygwinRebaseError(
152 e.errno, args, kwargs.get('cwd'), None, 'Visit '
153 'http://code.google.com/p/chromium/wiki/'
154 'CygwinDllRemappingFailure '
155 'to learn how to fix this error; you need to rebase your '
156 'cygwin dlls')
157 # Popen() can throw OSError when cwd or args[0] doesn't exist.
158 raise OSError(
159 'Execution failed with error: %s.\n'
160 'Check that %s or %s exist and have execution permission.' %
161 (str(e), kwargs.get('cwd'), args[0]))
maruel@chromium.org4860f052011-03-25 20:34:38 +0000162
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000163
Edward Lemur1556fbc2019-08-09 15:24:48 +0000164def communicate(args, **kwargs):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000165 """Wraps subprocess.Popen().communicate().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000166
maruel@chromium.org421982f2011-04-01 17:38:06 +0000167 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000168
szager@chromium.orge0558e62013-05-02 02:48:51 +0000169 - If the subprocess runs for |nag_timer| seconds without producing terminal
170 output, print a warning to stderr.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000171 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000172 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000173 stdin = None
174 # When stdin is passed as an argument, use it as the actual input data and
175 # set the Popen() parameter accordingly.
176 if 'stdin' in kwargs and isinstance(kwargs['stdin'], (str, bytes)):
177 stdin = kwargs['stdin']
178 kwargs['stdin'] = PIPE
maruel@chromium.org4860f052011-03-25 20:34:38 +0000179
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000180 proc = Popen(args, **kwargs)
181 return proc.communicate(stdin), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000182
183
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000184def call(args, **kwargs):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000185 """Emulates subprocess.call().
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000186
Edward Lesmescf06cad2020-12-14 22:03:23 +0000187 Automatically convert stdout=PIPE or stderr=PIPE to DEVNULL.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000188 In no case they can be returned since no code path raises
189 subprocess2.CalledProcessError.
Josip Sokcevic252ff1f2020-06-01 23:08:00 +0000190
191 Returns exit code.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000192 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000193 if kwargs.get('stdout') == PIPE:
194 kwargs['stdout'] = DEVNULL
195 if kwargs.get('stderr') == PIPE:
196 kwargs['stderr'] = DEVNULL
197 return communicate(args, **kwargs)[1]
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000198
199
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000200def check_call_out(args, **kwargs):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000201 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000202
maruel@chromium.org421982f2011-04-01 17:38:06 +0000203 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000204 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000205 out, returncode = communicate(args, **kwargs)
206 if returncode:
207 raise CalledProcessError(returncode, args, kwargs.get('cwd'), out[0],
208 out[1])
209 return out
maruel@chromium.org4860f052011-03-25 20:34:38 +0000210
211
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000212def check_call(args, **kwargs):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000213 """Emulate subprocess.check_call()."""
214 check_call_out(args, **kwargs)
215 return 0
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000216
217
maruel@chromium.org4860f052011-03-25 20:34:38 +0000218def capture(args, **kwargs):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000219 """Captures stdout of a process call and returns it.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000220
maruel@chromium.org421982f2011-04-01 17:38:06 +0000221 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000222
maruel@chromium.org421982f2011-04-01 17:38:06 +0000223 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000224 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000225 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000226 kwargs.setdefault('stdin', DEVNULL)
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000227
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000228 # Like check_output, deny the caller from using stdout arg.
229 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000230
231
232def check_output(args, **kwargs):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000233 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000234
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000235 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000236
maruel@chromium.org421982f2011-04-01 17:38:06 +0000237 - Throws if return code is not 0.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000238 - Blocks stdin by default if not specified since no output will be visible.
239 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000240 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000241 kwargs.setdefault('stdin', DEVNULL)
242 if 'stdout' in kwargs:
243 raise ValueError('stdout argument not allowed, it would be overridden.')
244 return check_call_out(args, stdout=PIPE, **kwargs)[0]