Skip to content

pip debug: Add versions of vendored libraries to output #7887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/7794.trivial
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Print vendored libraries version in pip debug.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def get_version(rel_path):
exclude=["contrib", "docs", "tests*", "tasks"],
),
package_data={
"pip._vendor": ["vendor.txt"],
"pip._vendor.certifi": ["*.pem"],
"pip._vendor.requests": ["*.pem"],
"pip._vendor.distlib._backport": ["sysconfig.cfg"],
Expand Down
96 changes: 95 additions & 1 deletion src/pip/_internal/commands/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import os
import sys

import pip._vendor
from pip._vendor import pkg_resources
from pip._vendor.certifi import where

from pip import __file__ as pip_location
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.cmdoptions import make_target_python
Expand All @@ -19,7 +22,8 @@
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

if MYPY_CHECK_RUNNING:
from typing import Any, List, Optional
from types import ModuleType
from typing import Any, List, Optional, Dict
from optparse import Values

logger = logging.getLogger(__name__)
Expand All @@ -43,6 +47,93 @@ def show_sys_implementation():
show_value('name', implementation_name)


def create_vendor_txt_map():
# type: () -> Dict[str, str]
vendor_txt_path = os.path.join(
os.path.dirname(pip_location),
'_vendor',
'vendor.txt'
)

with open(vendor_txt_path) as f:
# Purge non version specifying lines.
# Also, remove any space prefix or suffixes (including comments).
lines = [line.strip().split(' ', 1)[0]
for line in f.readlines() if '==' in line]

# Transform into "module" -> version dict.
return dict(line.split('==', 1) for line in lines) # type: ignore


def get_module_from_module_name(module_name):
# type: (str) -> ModuleType

# Module name can be uppercase in vendor.txt for some reason...
module_name = module_name.lower()
# PATCH: setuptools is actually only pkg_resources.
if module_name == 'setuptools':
module_name = 'pkg_resources'

__import__(
'pip._vendor.{}'.format(module_name),
globals(),
locals(),
level=0
)
return getattr(pip._vendor, module_name)


def get_vendor_version_from_module(module_name):
# type: (str) -> str

module = get_module_from_module_name(module_name)
version = getattr(module, '__version__', None)

if not version:
# Try to find version in debundled module info
pkg_set = pkg_resources.WorkingSet(
[os.path.dirname(getattr(module, '__file__'))]
)
package = pkg_set.find(pkg_resources.Requirement.parse(module_name))
version = getattr(package, 'version', None)

return version


def show_actual_vendor_versions(vendor_txt_versions):
# type: (Dict[str, str]) -> None
# Logs the actual version and print extra info
# if there is a conflict or if the actual version could not be imported.

for module_name, expected_version in vendor_txt_versions.items():
extra_message = ''
actual_version = get_vendor_version_from_module(module_name)
if not actual_version:
extra_message = ' (Unable to locate actual module version, using'\
' vendor.txt specified version)'
actual_version = expected_version
elif actual_version != expected_version:
extra_message = ' (CONFLICT: vendor.txt suggests version should'\
' be {})'.format(expected_version)

logger.info(
'{name}=={actual}{extra}'.format(
name=module_name,
actual=actual_version,
extra=extra_message
)
)


def show_vendor_versions():
# type: () -> None
logger.info('vendored library versions:')

vendor_txt_versions = create_vendor_txt_map()
with indent_log():
show_actual_vendor_versions(vendor_txt_versions)


def show_tags(options):
# type: (Values) -> None
tag_limit = 10
Expand Down Expand Up @@ -136,6 +227,9 @@ def run(self, options, args):
show_value("REQUESTS_CA_BUNDLE", os.environ.get('REQUESTS_CA_BUNDLE'))
show_value("CURL_CA_BUNDLE", os.environ.get('CURL_CA_BUNDLE'))
show_value("pip._vendor.certifi.where()", where())
show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED)

show_vendor_versions()

show_tags(options)

Expand Down
15 changes: 15 additions & 0 deletions tests/functional/test_debug.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from pip._internal.commands.debug import create_vendor_txt_map
from pip._internal.utils import compatibility_tags


Expand All @@ -14,6 +15,8 @@
'REQUESTS_CA_BUNDLE: ',
'CURL_CA_BUNDLE: ',
'pip._vendor.certifi.where(): ',
'pip._vendor.DEBUNDLED: ',
'vendored library versions:',

])
def test_debug(script, expected_text):
Expand All @@ -27,6 +30,18 @@ def test_debug(script, expected_text):
assert expected_text in stdout


def test_debug__library_versions(script):
"""
Check the library versions normal output.
"""
args = ['debug']
result = script.pip(*args, allow_stderr_warning=True)
stdout = result.stdout
vendored_versions = create_vendor_txt_map()
for name, value in vendored_versions.items():
assert '{}=={}'.format(name, value) in stdout


@pytest.mark.parametrize(
'args',
[
Expand Down