Commit d0a7f1bf authored by Reinout van Rees's avatar Reinout van Rees

Recording where requirements come from to debug version conflicts

Before you'd get a simple output like:

    Installing django.
    While:
      Installing django.
    Error: The requirement ('Django>=1.7') is not allowed by your [versions] constraint (1.6.6)

... which would mean you'd have to grep in all your requirements'
sub-requirements which package actually requires the offending "django>=1.7"

With this change you'll get a much more helpful output right before the error:

    Installing django.
    version and requirements information containing django:
      [versions] constraint on django: 1.6.6
      Base installation request: 'sso', 'djangorecipe'
      Requirement of djangorecipe==1.10: Django
      Requirement of djangorecipe==1.10: zc.recipe.egg
      Requirement of djangorecipe==1.10: zc.buildout
      Requirement of sso: django-nose
      Requirement of sso: django-mama-cas
      Requirement of sso: django-debug-toolbar
      Requirement of sso: django-auth-ldap
      Requirement of sso: Django<1.7,>=1.4.2
      Requirement of lizard-auth-server: django-nose
      Requirement of lizard-auth-server: django-extensions
      Requirement of lizard-auth-server: Django<1.7,>=1.6
      Requirement of django-nose: Django>=1.2
      Requirement of django-nose: nose>=1.2.1
      Requirement of django-mama-cas: requests==1.1.0
      Requirement of django-debug-toolbar: sqlparse
      Requirement of django-debug-toolbar: Django>=1.7
      Requirement of django-auth-ldap: python-ldap>=2.0
      Requirement of django-auth-ldap: django>=1.1
      Requirement of translations: Django>=1.4
      Requirement of django-extensions: six>=1.2
    While:
      Installing django.
    Error: The requirement ('Django>=1.7') is not allowed by your [versions] constraint (1.6.6)

This makes it much easier to spot the cause (in this case
django-debug-toolbar).

There *are* some unrelated packages in here because I'm doing a textual
comparison. The advantage is that it is very robust. And extracting the right
package name from requirements without messing things up is harder to get
right and takes more code.
parent 99202210
...@@ -222,10 +222,28 @@ class Installer: ...@@ -222,10 +222,28 @@ class Installer:
self._newest = newest self._newest = newest
self._env = pkg_resources.Environment(path) self._env = pkg_resources.Environment(path)
self._index = _get_index(index, links, self._allow_hosts) self._index = _get_index(index, links, self._allow_hosts)
self._requirements_and_constraints = []
if versions is not None: if versions is not None:
self._versions = normalize_versions(versions) self._versions = normalize_versions(versions)
def _version_conflict_information(self, name):
"""Return textual requirements/constraint information for debug purposes
We do a very simple textual search, as that filters out most
extraneous information witout missing anything.
"""
output = [
"version and requirements information containing %s:" % name]
version_constraint = self._versions.get(name)
if version_constraint:
output.append(
"[versions] constraint on %s: %s" % (name, version_constraint))
output += [line for line in self._requirements_and_constraints
if name.lower() in line.lower()]
return '\n '.join(output)
def _satisfied(self, req, source=None): def _satisfied(self, req, source=None):
dists = [dist for dist in self._env[req.project_name] if dist in req] dists = [dist for dist in self._env[req.project_name] if dist in req]
if not dists: if not dists:
...@@ -611,12 +629,21 @@ class Installer: ...@@ -611,12 +629,21 @@ class Installer:
"""Return requirement with optional [versions] constraint added.""" """Return requirement with optional [versions] constraint added."""
constraint = self._versions.get(requirement.project_name.lower()) constraint = self._versions.get(requirement.project_name.lower())
if constraint: if constraint:
requirement = _constrained_requirement(constraint, requirement) try:
requirement = _constrained_requirement(constraint,
requirement)
except IncompatibleConstraintError:
logger.info(self._version_conflict_information(
requirement.project_name.lower()))
raise
return requirement return requirement
def install(self, specs, working_set=None): def install(self, specs, working_set=None):
logger.debug('Installing %s.', repr(specs)[1:-1]) logger.debug('Installing %s.', repr(specs)[1:-1])
self._requirements_and_constraints.append(
"Base installation request: %s" % repr(specs)[1:-1])
for_buildout_run = bool(working_set) for_buildout_run = bool(working_set)
path = self._path path = self._path
...@@ -689,11 +716,17 @@ class Installer: ...@@ -689,11 +716,17 @@ class Installer:
self._maybe_add_setuptools(ws, dist) self._maybe_add_setuptools(ws, dist)
if dist not in req: if dist not in req:
# Oops, the "best" so far conflicts with a dependency. # Oops, the "best" so far conflicts with a dependency.
logger.info(self._version_conflict_information(dist))
raise VersionConflict( raise VersionConflict(
pkg_resources.VersionConflict(dist, req), ws) pkg_resources.VersionConflict(dist, req), ws)
best[req.key] = dist best[req.key] = dist
requirements.extend(dist.requires(req.extras)[::-1]) extra_requirements = dist.requires(req.extras)[::-1]
for extra_requirement in extra_requirements:
self._requirements_and_constraints.append(
"Requirement of %s: %s" % (current_requirement, extra_requirement))
requirements.extend(extra_requirements)
processed[req] = True processed[req] = True
return ws return ws
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment