def call_subprocess(
    cmd,  # type: List[str]
    show_stdout=True,  # type: bool
    cwd=None,  # type: Optional[str]
    on_returncode='raise',  # type: str
    extra_ok_returncodes=None,  # type: Optional[Iterable[int]]
    command_desc=None,  # type: Optional[str]
    extra_environ=None,  # type: Optional[Mapping[str, Any]]
    unset_environ=None,  # type: Optional[Iterable[str]]
    spinner=None  # type: Optional[SpinnerInterface]
):
    # type: (...) -> Optional[Text]
    """
    Args:
      extra_ok_returncodes: an iterable of integer return codes that are
        acceptable, in addition to 0. Defaults to None, which means [].
      unset_environ: an iterable of environment variable names to unset
        prior to calling subprocess.Popen().
    """
    if extra_ok_returncodes is None:
        extra_ok_returncodes = []
    if unset_environ is None:
        unset_environ = []
    # This function's handling of subprocess output is confusing and I
    # previously broke it terribly, so as penance I will write a long comment
    # explaining things.
    #
    # The obvious thing that affects output is the show_stdout=
    # kwarg. show_stdout=True means, let the subprocess write directly to our
    # stdout. Even though it is nominally the default, it is almost never used
    # inside pip (and should not be used in new code without a very good
    # reason); as of 2016-02-22 it is only used in a few places inside the VCS
    # wrapper code. Ideally we should get rid of it entirely, because it
    # creates a lot of complexity here for a rarely used feature.
    #
    # Most places in pip set show_stdout=False. What this means is:
    # - We connect the child stdout to a pipe, which we read.
    # - By default, we hide the output but show a spinner -- unless the
    #   subprocess exits with an error, in which case we show the output.
    # - If the --verbose option was passed (= loglevel is DEBUG), then we show
    #   the output unconditionally. (But in this case we don't want to show
    #   the output a second time if it turns out that there was an error.)
    #
    # stderr is always merged with stdout (even if show_stdout=True).
    if show_stdout:
        stdout = None
    else:
        stdout = subprocess.PIPE
    if command_desc is None:
        cmd_parts = []
        for part in cmd:
            if ' ' in part or '\n' in part or '"' in part or "'" in part:
                part = '"%s"' % part.replace('"', '\\"')
            cmd_parts.append(part)
        command_desc = ' '.join(cmd_parts)
    logger.debug("Running command %s", command_desc)
    env = os.environ.copy()
    if extra_environ:
        env.update(extra_environ)
    for name in unset_environ:
        env.pop(name, None)
    try:
        proc = subprocess.Popen(
            cmd, stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
            stdout=stdout, cwd=cwd, env=env,
        )
        proc.stdin.close()
    except Exception as exc:
        logger.critical(
            "Error %s while executing command %s", exc, command_desc,
        )
        raise
    all_output = []
    if stdout is not None:
        while True:
            line = console_to_str(proc.stdout.readline())
            if not line:
                break
            line = line.rstrip()
            all_output.append(line + '\n')
            if logger.getEffectiveLevel() <= std_logging.DEBUG:
                # Show the line immediately
                logger.debug(line)
            else:
                # Update the spinner
                if spinner is not None:
                    spinner.spin()
    try:
        proc.wait()
    finally:
        if proc.stdout:
            proc.stdout.close()
    if spinner is not None:
        if proc.returncode:
            spinner.finish("error")
        else:
            spinner.finish("done")
    if proc.returncode and proc.returncode not in extra_ok_returncodes:
        if on_returncode == 'raise':
            if (logger.getEffectiveLevel() > std_logging.DEBUG and
                    not show_stdout):
                logger.info(
                    'Complete output from command %s:', command_desc,
                )
                logger.info(
                    ''.join(all_output) +
                    '\n----------------------------------------'
                )
            raise InstallationError(
                'Command "%s" failed with error code %s in %s'
                % (command_desc, proc.returncode, cwd))
        elif on_returncode == 'warn':
            logger.warning(
                'Command "%s" had error code %s in %s',
                command_desc, proc.returncode, cwd,
            )
        elif on_returncode == 'ignore':
            pass
        else:
            raise ValueError('Invalid value: on_returncode=%s' %
                             repr(on_returncode))
    if not show_stdout:
        return ''.join(all_output)
    return None