Skip to content

Commit 8f171cd

Browse files
xavfernandezdstufft
authored andcommitted
Fix environment markers evaluation - issue #3829 (#4051)
1 parent dd7df7f commit 8f171cd

File tree

5 files changed

+75
-18
lines changed

5 files changed

+75
-18
lines changed

pip/req/req_install.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,
100100
self._wheel_cache = wheel_cache
101101
self.link = self.original_link = link
102102
self.as_egg = as_egg
103-
self.markers = markers
103+
if markers is not None:
104+
self.markers = markers
105+
else:
106+
self.markers = req and req.marker
104107
self._egg_info_path = None
105108
# This holds the pkg_resources.Distribution object if this requirement
106109
# is already available:
@@ -175,6 +178,8 @@ def from_line(
175178
markers = markers.strip()
176179
if not markers:
177180
markers = None
181+
else:
182+
markers = Marker(markers)
178183
else:
179184
markers = None
180185
name = name.strip()
@@ -819,9 +824,15 @@ def _clean_zip_name(self, name, prefix):
819824
name = name.replace(os.path.sep, '/')
820825
return name
821826

822-
def match_markers(self):
827+
def match_markers(self, extras_requested=None):
828+
if not extras_requested:
829+
# Provide an extra to safely evaluate the markers
830+
# without matching any extra
831+
extras_requested = ('',)
823832
if self.markers is not None:
824-
return Marker(self.markers).evaluate()
833+
return any(
834+
self.markers.evaluate({'extra': extra})
835+
for extra in extras_requested)
825836
else:
826837
return True
827838

pip/req/req_set.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,8 @@ def __repr__(self):
212212
return ('<%s object; %d requirement(s): %s>'
213213
% (self.__class__.__name__, len(reqs), reqs_str))
214214

215-
def add_requirement(self, install_req, parent_req_name=None):
215+
def add_requirement(self, install_req, parent_req_name=None,
216+
extras_requested=None):
216217
"""Add install_req as a requirement to install.
217218
218219
:param parent_req_name: The name of the requirement that needed this
@@ -221,13 +222,15 @@ def add_requirement(self, install_req, parent_req_name=None):
221222
links that point outside the Requirements set. parent_req must
222223
already be added. Note that None implies that this is a user
223224
supplied requirement, vs an inferred one.
225+
:param extras_requested: an iterable of extras used to evaluate the
226+
environement markers.
224227
:return: Additional requirements to scan. That is either [] if
225228
the requirement is not applicable, or [install_req] if the
226229
requirement is applicable and has just been added.
227230
"""
228231
name = install_req.name
229-
if not install_req.match_markers():
230-
logger.warning("Ignoring %s: markers %r don't match your "
232+
if not install_req.match_markers(extras_requested):
233+
logger.warning("Ignoring %s: markers '%s' don't match your "
231234
"environment", install_req.name,
232235
install_req.markers)
233236
return []
@@ -669,15 +672,16 @@ def _prepare_file(self,
669672
raise
670673
more_reqs = []
671674

672-
def add_req(subreq):
675+
def add_req(subreq, extras_requested):
673676
sub_install_req = InstallRequirement(
674677
str(subreq),
675678
req_to_install,
676679
isolated=self.isolated,
677680
wheel_cache=self._wheel_cache,
678681
)
679682
more_reqs.extend(self.add_requirement(
680-
sub_install_req, req_to_install.name))
683+
sub_install_req, req_to_install.name,
684+
extras_requested=extras_requested))
681685

682686
# We add req_to_install before its dependencies, so that we
683687
# can refer to it when adding dependencies.
@@ -704,7 +708,7 @@ def add_req(subreq):
704708
set(dist.extras) & set(req_to_install.extras)
705709
)
706710
for subreq in dist.requires(available_requested):
707-
add_req(subreq)
711+
add_req(subreq, extras_requested=available_requested)
708712

709713
# cleanup tmp src
710714
self.reqs_to_cleanup.append(req_to_install)

tests/functional/test_install.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,3 +1140,24 @@ def test_install_compatible_python_requires(script):
11401140
script.pip('install', 'setuptools>24.2') # This should not be needed
11411141
res = script.pip('install', pkga_path, expect_error=True)
11421142
assert "Successfully installed pkga-0.1" in res.stdout, res
1143+
1144+
1145+
def test_install_environment_markers(script, data):
1146+
# make a dummy project
1147+
pkga_path = script.scratch_path / 'pkga'
1148+
pkga_path.mkdir()
1149+
pkga_path.join("setup.py").write(textwrap.dedent("""
1150+
from setuptools import setup
1151+
setup(name='pkga',
1152+
version='0.1',
1153+
install_requires=[
1154+
'missing_pkg; python_version=="1.0"',
1155+
],
1156+
)
1157+
"""))
1158+
1159+
res = script.pip('install', '--no-index', pkga_path, expect_stderr=True)
1160+
# missing_pkg should be ignored
1161+
assert ("Ignoring missing-pkg: markers 'python_version == \"1.0\"' don't "
1162+
"match your environment") in res.stderr, str(res)
1163+
assert "Successfully installed pkga-0.1" in res.stdout, str(res)

tests/functional/test_install_reqs.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,8 @@ def test_install_unsupported_wheel_link_with_marker(script, data):
498498
expect_stderr=True,
499499
)
500500

501-
s = "Ignoring asdf: markers %r don't match your environment" %\
502-
u'sys_platform == "xyz"'
503-
assert s in result.stderr
501+
assert ("Ignoring asdf: markers 'sys_platform == \"xyz\"' don't match "
502+
"your environment") in result.stderr
504503
assert len(result.files_created) == 0
505504

506505

tests/unit/test_req.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pip.req.req_install import parse_editable
1717
from pip.utils import read_text_file
1818
from pip._vendor import pkg_resources
19+
from pip._vendor.packaging.markers import Marker
1920
from pip._vendor.packaging.requirements import Requirement
2021
from tests.lib import assert_raises_regexp, requirements_file
2122

@@ -415,22 +416,22 @@ def test_markers(self):
415416
req = InstallRequirement.from_line(line)
416417
assert req.req.name == 'mock3'
417418
assert str(req.req.specifier) == ''
418-
assert req.markers == 'python_version >= "3"'
419+
assert str(req.markers) == 'python_version >= "3"'
419420

420421
def test_markers_semicolon(self):
421422
# check that the markers can contain a semicolon
422423
req = InstallRequirement.from_line('semicolon; os_name == "a; b"')
423424
assert req.req.name == 'semicolon'
424425
assert str(req.req.specifier) == ''
425-
assert req.markers == 'os_name == "a; b"'
426+
assert str(req.markers) == 'os_name == "a; b"'
426427

427428
def test_markers_url(self):
428429
# test "URL; markers" syntax
429430
url = 'http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz'
430431
line = '%s; python_version >= "3"' % url
431432
req = InstallRequirement.from_line(line)
432433
assert req.link.url == url, req.url
433-
assert req.markers == 'python_version >= "3"'
434+
assert str(req.markers) == 'python_version >= "3"'
434435

435436
# without space, markers are part of the URL
436437
url = 'http://foo.com/?p=bar.git;a=snapshot;h=v0.1;sf=tgz'
@@ -439,15 +440,15 @@ def test_markers_url(self):
439440
assert req.link.url == line, req.url
440441
assert req.markers is None
441442

442-
def test_markers_match(self):
443+
def test_markers_match_from_line(self):
443444
# match
444445
for markers in (
445446
'python_version >= "1.0"',
446447
'sys_platform == %r' % sys.platform,
447448
):
448449
line = 'name; ' + markers
449450
req = InstallRequirement.from_line(line)
450-
assert req.markers == markers
451+
assert str(req.markers) == str(Marker(markers))
451452
assert req.match_markers()
452453

453454
# don't match
@@ -457,7 +458,28 @@ def test_markers_match(self):
457458
):
458459
line = 'name; ' + markers
459460
req = InstallRequirement.from_line(line)
460-
assert req.markers == markers
461+
assert str(req.markers) == str(Marker(markers))
462+
assert not req.match_markers()
463+
464+
def test_markers_match(self):
465+
# match
466+
for markers in (
467+
'python_version >= "1.0"',
468+
'sys_platform == %r' % sys.platform,
469+
):
470+
line = 'name; ' + markers
471+
req = InstallRequirement(line, comes_from='')
472+
assert str(req.markers) == str(Marker(markers))
473+
assert req.match_markers()
474+
475+
# don't match
476+
for markers in (
477+
'python_version >= "5.0"',
478+
'sys_platform != %r' % sys.platform,
479+
):
480+
line = 'name; ' + markers
481+
req = InstallRequirement(line, comes_from='')
482+
assert str(req.markers) == str(Marker(markers))
461483
assert not req.match_markers()
462484

463485
def test_extras_for_line_path_requirement(self):

0 commit comments

Comments
 (0)