Commit 8c1f489f authored by Jim Porter's avatar Jim Porter

Fix usage of extras when installing via Wheels; resolves #882

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.
parent a5b81d8e
......@@ -786,7 +786,7 @@ class WorkingSet(object):
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`,
......@@ -802,6 +802,12 @@ class WorkingSet(object):
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
......@@ -825,7 +831,7 @@ class WorkingSet(object):
# 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)
......@@ -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.
......@@ -1014,7 +1020,7 @@ class _ReqExtras(dict):
"""
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)
......@@ -2299,8 +2305,14 @@ class EntryPoint(object):
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(
......
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