Skip to content

Commit b273e62

Browse files
committed
Refactor call_subprocess into call_subprocess_for_install
1 parent 4052379 commit b273e62

File tree

11 files changed

+129
-93
lines changed

11 files changed

+129
-93
lines changed

news/7711.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor `call_subprocess` by renaming it into `call_subprocess_for_install` and making it a wrapper of the former `call_subprocess` function.

src/pip/_internal/build_env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from pip import __file__ as pip_location
1919
from pip._internal.cli.spinners import open_spinner
20-
from pip._internal.utils.subprocess import call_subprocess
20+
from pip._internal.utils.subprocess import call_subprocess_for_install
2121
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
2222
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2323

@@ -196,7 +196,7 @@ def install_requirements(
196196
args.append('--')
197197
args.extend(requirements)
198198
with open_spinner(message) as spinner:
199-
call_subprocess(args, spinner=spinner)
199+
call_subprocess_for_install(args, spinner=spinner)
200200

201201

202202
class NoOpBuildEnvironment(BuildEnvironment):

src/pip/_internal/operations/build/metadata_legacy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pip._internal.exceptions import InstallationError
88
from pip._internal.utils.misc import ensure_dir
99
from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args
10-
from pip._internal.utils.subprocess import call_subprocess
10+
from pip._internal.utils.subprocess import call_subprocess_for_install
1111
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1212
from pip._internal.vcs import vcs
1313

@@ -112,7 +112,7 @@ def generate_metadata(
112112
)
113113

114114
with build_env:
115-
call_subprocess(
115+
call_subprocess_for_install(
116116
args,
117117
cwd=source_dir,
118118
command_desc='python setup.py egg_info',

src/pip/_internal/operations/build/wheel_legacy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
)
88
from pip._internal.utils.subprocess import (
99
LOG_DIVIDER,
10-
call_subprocess,
10+
call_subprocess_for_install,
1111
format_command_args,
1212
)
1313
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -94,7 +94,7 @@ def build_wheel_legacy(
9494
logger.debug('Destination directory: %s', tempd)
9595

9696
try:
97-
output = call_subprocess(
97+
output = call_subprocess_for_install(
9898
wheel_args,
9999
cwd=source_dir,
100100
spinner=spinner,

src/pip/_internal/operations/install/editable_legacy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from pip._internal.utils.logging import indent_log
66
from pip._internal.utils.setuptools_build import make_setuptools_develop_args
7-
from pip._internal.utils.subprocess import call_subprocess
7+
from pip._internal.utils.subprocess import call_subprocess_for_install
88
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
99

1010
if MYPY_CHECK_RUNNING:
@@ -46,7 +46,7 @@ def install_editable(
4646

4747
with indent_log():
4848
with build_env:
49-
call_subprocess(
49+
call_subprocess_for_install(
5050
args,
5151
cwd=unpacked_source_directory,
5252
)

src/pip/_internal/utils/subprocess.py

Lines changed: 89 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@
2727
LOG_DIVIDER = '----------------------------------------'
2828

2929

30+
class SubProcessResult(object):
31+
def __init__(self, cmd, returncode, had_error, output=None):
32+
# type (...) -> SubProcessResult
33+
"""
34+
Initialize a SubProcessResult
35+
"""
36+
self.cmd = cmd
37+
self.returncode = returncode
38+
self.output = output
39+
self.had_error = had_error
40+
41+
3042
def make_command(*args):
3143
# type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs
3244
"""
@@ -113,7 +125,7 @@ def make_subprocess_output_error(
113125
return msg
114126

115127

116-
def call_subprocess(
128+
def call_subprocess_for_install(
117129
cmd, # type: Union[List[str], CommandArgs]
118130
show_stdout=False, # type: bool
119131
cwd=None, # type: Optional[str]
@@ -136,10 +148,6 @@ def call_subprocess(
136148
prior to calling subprocess.Popen().
137149
log_failed_cmd: if false, failed commands are not logged, only raised.
138150
"""
139-
if extra_ok_returncodes is None:
140-
extra_ok_returncodes = []
141-
if unset_environ is None:
142-
unset_environ = []
143151
# Most places in pip use show_stdout=False. What this means is--
144152
#
145153
# - We connect the child's output (combined stderr and stdout) to a
@@ -155,55 +163,101 @@ def call_subprocess(
155163
#
156164
# If show_stdout=True, then the above is still done, but with DEBUG
157165
# replaced by INFO.
158-
if show_stdout:
159-
# Then log the subprocess output at INFO level.
160-
log_subprocess = subprocess_logger.info
161-
used_level = logging.INFO
162-
else:
163-
# Then log the subprocess output using DEBUG. This also ensures
164-
# it will be logged to the log file (aka user_log), if enabled.
165-
log_subprocess = subprocess_logger.debug
166-
used_level = logging.DEBUG
167-
166+
used_level = logging.INFO if show_stdout else logging.DEBUG
168167
# Whether the subprocess will be visible in the console.
169168
showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level
170-
171-
# Only use the spinner if we're not showing the subprocess output
172-
# and we have a spinner.
173-
use_spinner = not showing_subprocess and spinner is not None
174-
175169
if command_desc is None:
176170
command_desc = format_command_args(cmd)
171+
proc = _call_subprocess(
172+
cmd=cmd, cwd=cwd, spinner=spinner, command_desc=command_desc,
173+
extra_environ=extra_environ, unset_environ=unset_environ,
174+
log_failed_cmd=log_failed_cmd, log_level=used_level,
175+
extra_ok_returncodes=extra_ok_returncodes)
176+
if proc.had_error:
177+
if on_returncode == "raise":
178+
if not showing_subprocess and log_failed_cmd:
179+
# Then the subprocess streams haven't been logged to the
180+
# console yet.
181+
msg = make_subprocess_output_error(
182+
cmd_args=cmd,
183+
cwd=cwd,
184+
lines=proc.output,
185+
exit_status=proc.returncode,
186+
)
187+
subprocess_logger.error(msg)
188+
exc_msg = (
189+
"Command errored out with exit status {}: {} "
190+
"Check the logs for full command output."
191+
).format(proc.returncode, command_desc)
192+
raise InstallationError(exc_msg)
193+
elif on_returncode == "warn":
194+
subprocess_logger.warning(
195+
"Command '{}' had error code {} in {}".format(
196+
command_desc, proc.returncode, cwd))
197+
elif on_returncode == "ignore":
198+
pass
199+
else:
200+
raise ValueError(
201+
"Invalid value: on_returncode={!r}".format(on_returncode))
202+
return "".join(proc.output)
177203

178-
log_subprocess("Running command %s", command_desc)
204+
205+
def _call_subprocess(
206+
cmd, # type: Union[List[str], CommandArgs]
207+
spinner=None, # type: Optional[SpinnerInterface]
208+
cwd=None, # type: Optional[str]
209+
log_level=logging.DEBUG, # type: int
210+
command_desc=None, # type: Optional[str]
211+
extra_environ=None, # type: Optional[Mapping[str, Any]]
212+
unset_environ=None, # type: Optional[Iterable[str]]
213+
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
214+
log_failed_cmd=True # type: Optional[bool]
215+
):
216+
"""
217+
type: (...) -> SubProcessResult
218+
This function calls the subprocess and returns a SubProcessResult
219+
object representing the stdout lines, returncode, and command that
220+
was executed. If subprocess logging is enabled, then display it
221+
on stdout with the INFO logging level
222+
"""
223+
if unset_environ is None:
224+
unset_environ = []
225+
if extra_ok_returncodes is None:
226+
extra_ok_returncodes = []
227+
if log_level == logging.DEBUG:
228+
log_subprocess = subprocess_logger.debug
229+
else:
230+
log_subprocess = subprocess_logger.info
231+
showing_subprocess = subprocess_logger.getEffectiveLevel() <= log_level
232+
use_spinner = not showing_subprocess and spinner is not None
233+
log_subprocess("Running command {}".format(command_desc))
179234
env = os.environ.copy()
180235
if extra_environ:
181236
env.update(extra_environ)
182237
for name in unset_environ:
183238
env.pop(name, None)
239+
240+
all_output = []
184241
try:
242+
# Convert HiddenText objects to the underlying str.
185243
proc = subprocess.Popen(
186-
# Convert HiddenText objects to the underlying str.
187244
reveal_command_args(cmd),
188245
stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
189-
stdout=subprocess.PIPE, cwd=cwd, env=env,
246+
stdout=subprocess.PIPE, cwd=cwd, env=env
190247
)
191-
proc.stdin.close()
192248
except Exception as exc:
193249
if log_failed_cmd:
194250
subprocess_logger.critical(
195-
"Error %s while executing command %s", exc, command_desc,
251+
"Error %s while executing command %s", exc, command_desc
196252
)
197253
raise
198-
all_output = []
199254
while True:
200255
# The "line" value is a unicode string in Python 2.
201256
line = console_to_str(proc.stdout.readline())
202257
if not line:
203258
break
204259
line = line.rstrip()
205-
all_output.append(line + '\n')
206-
260+
all_output.append("{}\n".format(line))
207261
# Show the line immediately.
208262
log_subprocess(line)
209263
# Update the spinner.
@@ -214,42 +268,16 @@ def call_subprocess(
214268
finally:
215269
if proc.stdout:
216270
proc.stdout.close()
217-
proc_had_error = (
218-
proc.returncode and proc.returncode not in extra_ok_returncodes
219-
)
271+
had_error = proc.returncode and proc.returncode not in extra_ok_returncodes
220272
if use_spinner:
221-
if proc_had_error:
273+
if had_error:
222274
spinner.finish("error")
223275
else:
224276
spinner.finish("done")
225-
if proc_had_error:
226-
if on_returncode == 'raise':
227-
if not showing_subprocess and log_failed_cmd:
228-
# Then the subprocess streams haven't been logged to the
229-
# console yet.
230-
msg = make_subprocess_output_error(
231-
cmd_args=cmd,
232-
cwd=cwd,
233-
lines=all_output,
234-
exit_status=proc.returncode,
235-
)
236-
subprocess_logger.error(msg)
237-
exc_msg = (
238-
'Command errored out with exit status {}: {} '
239-
'Check the logs for full command output.'
240-
).format(proc.returncode, command_desc)
241-
raise InstallationError(exc_msg)
242-
elif on_returncode == 'warn':
243-
subprocess_logger.warning(
244-
'Command "{}" had error code {} in {}'.format(
245-
command_desc, proc.returncode, cwd)
246-
)
247-
elif on_returncode == 'ignore':
248-
pass
249-
else:
250-
raise ValueError('Invalid value: on_returncode={!r}'.format(
251-
on_returncode))
252-
return ''.join(all_output)
277+
return SubProcessResult(
278+
cmd=cmd, returncode=proc.returncode,
279+
had_error=had_error, output=all_output
280+
)
253281

254282

255283
def runner_with_spinner_message(message):
@@ -267,7 +295,7 @@ def runner(
267295
):
268296
# type: (...) -> None
269297
with open_spinner(message) as spinner:
270-
call_subprocess(
298+
call_subprocess_for_install(
271299
cmd,
272300
cwd=cwd,
273301
extra_environ=extra_environ,

src/pip/_internal/vcs/subversion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def get_remote_call_options(self):
270270
svn_version = self.get_vcs_version()
271271
# By default, Subversion >= 1.8 runs in non-interactive mode if
272272
# stdin is not a TTY. Since that is how pip invokes SVN, in
273-
# call_subprocess(), pip must pass --force-interactive to ensure
273+
# call_subprocess_for_install(), pip must pass --force-interactive to ensure
274274
# the user can be prompted for a password, if required.
275275
# SVN added the --force-interactive option in SVN 1.8. Since
276276
# e.g. RHEL/CentOS 7, which is supported until 2024, ships with

src/pip/_internal/vcs/versioncontrol.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
hide_value,
2222
rmtree,
2323
)
24-
from pip._internal.utils.subprocess import call_subprocess, make_command
24+
from pip._internal.utils.subprocess import (
25+
call_subprocess_for_install, make_command
26+
)
2527
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2628
from pip._internal.utils.urls import get_url_scheme
2729

@@ -279,7 +281,8 @@ class VersionControl(object):
279281
repo_name = ''
280282
# List of supported schemes for this Version Control
281283
schemes = () # type: Tuple[str, ...]
282-
# Iterable of environment variable names to pass to call_subprocess().
284+
# Iterable of environment variable names to pass to
285+
# call_subprocess_for_install().
283286
unset_environ = () # type: Tuple[str, ...]
284287
default_arg_rev = None # type: Optional[str]
285288

@@ -665,12 +668,13 @@ def run_command(
665668
# type: (...) -> Text
666669
"""
667670
Run a VCS subcommand
668-
This is simply a wrapper around call_subprocess that adds the VCS
669-
command name, and checks that the VCS is available
671+
This is simply a wrapper around call_subprocess_for_install
672+
that adds the VCS command name, and checks that the VCS
673+
is available
670674
"""
671675
cmd = make_command(cls.name, *cmd)
672676
try:
673-
return call_subprocess(cmd, show_stdout, cwd,
677+
return call_subprocess_for_install(cmd, show_stdout, cwd,
674678
on_returncode=on_returncode,
675679
extra_ok_returncodes=extra_ok_returncodes,
676680
command_desc=command_desc,

src/pip/_internal/wheel_builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from pip._internal.utils.logging import indent_log
1616
from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed
1717
from pip._internal.utils.setuptools_build import make_setuptools_clean_args
18-
from pip._internal.utils.subprocess import call_subprocess
18+
from pip._internal.utils.subprocess import call_subprocess_for_install
1919
from pip._internal.utils.temp_dir import TempDirectory
2020
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2121
from pip._internal.utils.urls import path_to_url
@@ -246,7 +246,7 @@ def _clean_one_legacy(req, global_options):
246246

247247
logger.info('Running setup.py clean for %s', req.name)
248248
try:
249-
call_subprocess(clean_args, cwd=req.source_dir)
249+
call_subprocess_for_install(clean_args, cwd=req.source_dir)
250250
return True
251251
except Exception:
252252
logger.error('Failed cleaning build dir for %s', req.name)

0 commit comments

Comments
 (0)