Skip to content

Commit

Permalink
Fix usage of extras when installing via Wheels; resolves #882
Browse files Browse the repository at this point in the history
When resolving requirements, we now pass the list of extras we're using along
to Marker.evaluate, since we want to include the extra's requirements in our
list of required packages. This is sort of papering over the underlying issue;
namely, that the dependency map for dist-info distributions looks like:

  { None      : ['common_dep'],
    'my_extra': ['extra_dep; extra = "my_extra"'] }

If we eliminated 'extra = "my_extra"' when creating this map, the problem
would go away because the WorkingSet would no longer treat `extra_dep` as a
purely optional dependency. However, this would require copying and
manipulating Requirement objects, which is somewhat more complicated than the
current solution.
  • Loading branch information
Jim Porter committed Dec 29, 2016
1 parent a5b81d8 commit 8c1f489
Showing 1 changed file with 17 additions and 5 deletions.
22 changes: 17 additions & 5 deletions pkg_resources/__init__.py
Expand Up @@ -786,7 +786,7 @@ def add(self, dist, entry=None, insert=True, replace=False):
self._added_new(dist)

def resolve(self, requirements, env=None, installer=None,
replace_conflicting=False):
replace_conflicting=False, extras=None):
"""List all distributions needed to (recursively) meet `requirements`
`requirements` must be a sequence of ``Requirement`` objects. `env`,
Expand All @@ -802,6 +802,12 @@ def resolve(self, requirements, env=None, installer=None,
the wrong version. Otherwise, if an `installer` is supplied it will be
invoked to obtain the correct version of the requirement and activate
it.
`extras` is a list of the extras to be used with these requirements.
This is important because extra requirements may look like `my_req;
extra = "my_extra"`, which would otherwise be interpreted as a purely
optional requirement. Instead, we want to be able to assert that these
requirements are truly required.
"""

# set up the stack
Expand All @@ -825,7 +831,7 @@ def resolve(self, requirements, env=None, installer=None,
# Ignore cyclic or redundant dependencies
continue

if not req_extras.markers_pass(req):
if not req_extras.markers_pass(req, extras):
continue

dist = best.get(req.key)
Expand Down Expand Up @@ -1004,7 +1010,7 @@ class _ReqExtras(dict):
Map each requirement to the extras that demanded it.
"""

def markers_pass(self, req):
def markers_pass(self, req, extras=None):
"""
Evaluate markers for req against each extra that
demanded it.
Expand All @@ -1014,7 +1020,7 @@ def markers_pass(self, req):
"""
extra_evals = (
req.marker.evaluate({'extra': extra})
for extra in self.get(req, ()) + (None,)
for extra in self.get(req, ()) + (extras or (None,))
)
return not req.marker or any(extra_evals)

Expand Down Expand Up @@ -2299,8 +2305,14 @@ def resolve(self):
def require(self, env=None, installer=None):
if self.extras and not self.dist:
raise UnknownExtra("Can't require() without a distribution", self)

# Get the requirements for this entry point with all its extras and
# then resolve them. We have to pass `extras` along when resolving so
# that the working set knows what extras we want. Otherwise, for
# dist-info distributions, the working set will assume that the
# requirements for that extra are purely optional and skip over them.
reqs = self.dist.requires(self.extras)
items = working_set.resolve(reqs, env, installer)
items = working_set.resolve(reqs, env, installer, extras=self.extras)
list(map(working_set.add, items))

pattern = re.compile(
Expand Down

0 comments on commit 8c1f489

Please sign in to comment.