Skip to content

Commit eab4db8

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

File tree

11 files changed

+149
-103
lines changed

11 files changed

+149
-103
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: 94 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@
2727
LOG_DIVIDER = '----------------------------------------'
2828

2929

30+
class SubProcessResult(object):
31+
def __init__(self,
32+
cmd, # Iterable[str]
33+
returncode, # type: int
34+
had_error, # type: bool
35+
output=None # type: Optional[Iterable[str]]
36+
):
37+
# type (...) -> SubProcessResult
38+
"""
39+
Initialize a SubProcessResult
40+
"""
41+
self.cmd = cmd
42+
self.returncode = returncode
43+
self.output = output
44+
self.had_error = had_error
45+
46+
3047
def make_command(*args):
3148
# type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs
3249
"""
@@ -113,7 +130,7 @@ def make_subprocess_output_error(
113130
return msg
114131

115132

116-
def call_subprocess(
133+
def call_subprocess_for_install(
117134
cmd, # type: Union[List[str], CommandArgs]
118135
show_stdout=False, # type: bool
119136
cwd=None, # type: Optional[str]
@@ -136,10 +153,6 @@ def call_subprocess(
136153
prior to calling subprocess.Popen().
137154
log_failed_cmd: if false, failed commands are not logged, only raised.
138155
"""
139-
if extra_ok_returncodes is None:
140-
extra_ok_returncodes = []
141-
if unset_environ is None:
142-
unset_environ = []
143156
# Most places in pip use show_stdout=False. What this means is--
144157
#
145158
# - We connect the child's output (combined stderr and stdout) to a
@@ -155,55 +168,101 @@ def call_subprocess(
155168
#
156169
# If show_stdout=True, then the above is still done, but with DEBUG
157170
# 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-
171+
used_level = logging.INFO if show_stdout else logging.DEBUG
168172
# Whether the subprocess will be visible in the console.
169173
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-
175174
if command_desc is None:
176175
command_desc = format_command_args(cmd)
176+
proc = _call_subprocess(
177+
cmd=cmd, cwd=cwd, spinner=spinner, command_desc=command_desc,
178+
extra_environ=extra_environ, unset_environ=unset_environ,
179+
log_failed_cmd=log_failed_cmd, log_level=used_level,
180+
extra_ok_returncodes=extra_ok_returncodes)
181+
if proc.had_error:
182+
if on_returncode == "raise":
183+
if not showing_subprocess and log_failed_cmd:
184+
# Then the subprocess streams haven't been logged to the
185+
# console yet.
186+
msg = make_subprocess_output_error(
187+
cmd_args=cmd,
188+
cwd=cwd,
189+
lines=proc.output,
190+
exit_status=proc.returncode,
191+
)
192+
subprocess_logger.error(msg)
193+
exc_msg = (
194+
"Command errored out with exit status {}: {} "
195+
"Check the logs for full command output."
196+
).format(proc.returncode, command_desc)
197+
raise InstallationError(exc_msg)
198+
elif on_returncode == "warn":
199+
subprocess_logger.warning(
200+
"Command '{}' had error code {} in {}".format(
201+
command_desc, proc.returncode, cwd))
202+
elif on_returncode == "ignore":
203+
pass
204+
else:
205+
raise ValueError(
206+
"Invalid value: on_returncode={!r}".format(on_returncode))
207+
return "".join(proc.output)
208+
177209

178-
log_subprocess("Running command %s", command_desc)
210+
def _call_subprocess(
211+
cmd, # type: Union[List[str], CommandArgs]
212+
spinner=None, # type: Optional[SpinnerInterface]
213+
cwd=None, # type: Optional[str]
214+
log_level=logging.DEBUG, # type: int
215+
command_desc=None, # type: Optional[str]
216+
extra_environ=None, # type: Optional[Mapping[str, Any]]
217+
unset_environ=None, # type: Optional[Iterable[str]]
218+
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
219+
log_failed_cmd=True # type: Optional[bool]
220+
):
221+
"""
222+
# type: (...) -> SubProcessResult
223+
This function calls the subprocess and returns a SubProcessResult
224+
object representing the stdout lines, returncode, and command that
225+
was executed. If subprocess logging is enabled, then display it
226+
on stdout with the INFO logging level
227+
"""
228+
if unset_environ is None:
229+
unset_environ = []
230+
if extra_ok_returncodes is None:
231+
extra_ok_returncodes = []
232+
if log_level == logging.DEBUG:
233+
log_subprocess = subprocess_logger.debug
234+
else:
235+
log_subprocess = subprocess_logger.info
236+
showing_subprocess = subprocess_logger.getEffectiveLevel() <= log_level
237+
use_spinner = not showing_subprocess and spinner is not None
238+
log_subprocess("Running command {}".format(command_desc))
179239
env = os.environ.copy()
180240
if extra_environ:
181241
env.update(extra_environ)
182242
for name in unset_environ:
183243
env.pop(name, None)
244+
245+
all_output = []
184246
try:
247+
# Convert HiddenText objects to the underlying str.
185248
proc = subprocess.Popen(
186-
# Convert HiddenText objects to the underlying str.
187249
reveal_command_args(cmd),
188250
stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
189-
stdout=subprocess.PIPE, cwd=cwd, env=env,
251+
stdout=subprocess.PIPE, cwd=cwd, env=env
190252
)
191-
proc.stdin.close()
192253
except Exception as exc:
193254
if log_failed_cmd:
194255
subprocess_logger.critical(
195-
"Error %s while executing command %s", exc, command_desc,
256+
"Error %s while executing command %s", exc, command_desc
196257
)
197258
raise
198-
all_output = []
199259
while True:
200260
# The "line" value is a unicode string in Python 2.
201261
line = console_to_str(proc.stdout.readline())
202262
if not line:
203263
break
204264
line = line.rstrip()
205-
all_output.append(line + '\n')
206-
265+
all_output.append("{}\n".format(line))
207266
# Show the line immediately.
208267
log_subprocess(line)
209268
# Update the spinner.
@@ -214,42 +273,16 @@ def call_subprocess(
214273
finally:
215274
if proc.stdout:
216275
proc.stdout.close()
217-
proc_had_error = (
218-
proc.returncode and proc.returncode not in extra_ok_returncodes
219-
)
276+
had_error = proc.returncode and proc.returncode not in extra_ok_returncodes
220277
if use_spinner:
221-
if proc_had_error:
278+
if had_error:
222279
spinner.finish("error")
223280
else:
224281
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)
282+
return SubProcessResult(
283+
cmd=cmd, returncode=proc.returncode,
284+
had_error=had_error, output=all_output
285+
)
253286

254287

255288
def runner_with_spinner_message(message):
@@ -267,7 +300,7 @@ def runner(
267300
):
268301
# type: (...) -> None
269302
with open_spinner(message) as spinner:
270-
call_subprocess(
303+
call_subprocess_for_install(
271304
cmd,
272305
cwd=cwd,
273306
extra_environ=extra_environ,

src/pip/_internal/vcs/subversion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ 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
274-
# the user can be prompted for a password, if required.
273+
# call_subprocess_for_install(), pip must pass --force-interactive
274+
# to ensure 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
277277
# SVN 1.7, pip should continue to support SVN 1.7. Therefore, pip

src/pip/_internal/vcs/versioncontrol.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
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,
26+
make_command,
27+
)
2528
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2629
from pip._internal.utils.urls import get_url_scheme
2730

@@ -279,7 +282,8 @@ class VersionControl(object):
279282
repo_name = ''
280283
# List of supported schemes for this Version Control
281284
schemes = () # type: Tuple[str, ...]
282-
# Iterable of environment variable names to pass to call_subprocess().
285+
# Iterable of environment variable names to pass to
286+
# call_subprocess_for_install().
283287
unset_environ = () # type: Tuple[str, ...]
284288
default_arg_rev = None # type: Optional[str]
285289

@@ -665,19 +669,22 @@ def run_command(
665669
# type: (...) -> Text
666670
"""
667671
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
672+
This is simply a wrapper around call_subprocess_for_install
673+
that adds the VCS command name, and checks that the VCS
674+
is available
670675
"""
671676
cmd = make_command(cls.name, *cmd)
672677
try:
673-
return call_subprocess(cmd, show_stdout, cwd,
674-
on_returncode=on_returncode,
675-
extra_ok_returncodes=extra_ok_returncodes,
676-
command_desc=command_desc,
677-
extra_environ=extra_environ,
678-
unset_environ=cls.unset_environ,
679-
spinner=spinner,
680-
log_failed_cmd=log_failed_cmd)
678+
return call_subprocess_for_install(
679+
cmd, show_stdout, cwd,
680+
on_returncode=on_returncode,
681+
extra_ok_returncodes=extra_ok_returncodes,
682+
command_desc=command_desc,
683+
extra_environ=extra_environ,
684+
unset_environ=cls.unset_environ,
685+
spinner=spinner,
686+
log_failed_cmd=log_failed_cmd
687+
)
681688
except OSError as e:
682689
# errno.ENOENT = no such file or directory
683690
# In other words, the VCS executable isn't available

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)