44import os
55import sys
66import tempfile
7+ from functools import wraps
78from subprocess import run # nosec
89from typing import Deque , Iterable , Mapping , ValuesView
910
4041]
4142
4243
44+ def patch_match_markers () -> None :
45+ """
46+ Monkey patches pip._internal.req.InstallRequirement.match_markers to allow
47+ us to pass environment other than "extra".
48+ """
49+
50+ @wraps (InstallRequirement .match_markers )
51+ def match_markers (
52+ self : InstallRequirement ,
53+ extras_requested : Iterable [str ] | None = None ,
54+ environment : dict [str , str ] | None = None ,
55+ ) -> bool :
56+ if environment is None :
57+ environment = {}
58+
59+ assert "extra" not in environment
60+
61+ if not extras_requested :
62+ # Provide an extra to safely evaluate the markers
63+ # without matching any extra
64+ extras_requested = ("" ,)
65+ if self .markers is not None :
66+ return any (
67+ self .markers .evaluate ({"extra" : extra , ** environment })
68+ for extra in extras_requested
69+ )
70+ else :
71+ return True
72+
73+ InstallRequirement .match_markers = match_markers
74+
75+
76+ patch_match_markers ()
77+
78+
4379def dependency_tree (
4480 installed_keys : Mapping [str , Distribution ], root_key : str
4581) -> set [str ]:
@@ -93,15 +129,17 @@ def get_dists_to_ignore(installed: Iterable[Distribution]) -> list[str]:
93129
94130
95131def merge (
96- requirements : Iterable [InstallRequirement ], ignore_conflicts : bool
132+ requirements : Iterable [InstallRequirement ],
133+ ignore_conflicts : bool ,
134+ environment : dict [str , str ] | None = None ,
97135) -> ValuesView [InstallRequirement ]:
98136 by_key : dict [str , InstallRequirement ] = {}
99137
100138 for ireq in requirements :
101139 # Limitation: URL requirements are merged by precise string match, so
102140 # "file:///example.zip#egg=example", "file:///example.zip", and
103141 # "example==1.0" will not merge with each other
104- if ireq .match_markers ():
142+ if ireq .match_markers (environment = environment ):
105143 key = key_from_ireq (ireq )
106144
107145 if not ignore_conflicts :
@@ -158,6 +196,7 @@ def diff_key_from_req(req: Distribution) -> str:
158196def diff (
159197 compiled_requirements : Iterable [InstallRequirement ],
160198 installed_dists : Iterable [Distribution ],
199+ environment : dict [str , str ] | None = None ,
161200) -> tuple [set [InstallRequirement ], set [str ]]:
162201 """
163202 Calculate which packages should be installed or uninstalled, given a set
@@ -172,13 +211,15 @@ def diff(
172211 pkgs_to_ignore = get_dists_to_ignore (installed_dists )
173212 for dist in installed_dists :
174213 key = diff_key_from_req (dist )
175- if key not in requirements_lut or not requirements_lut [key ].match_markers ():
214+ if key not in requirements_lut or not requirements_lut [key ].match_markers (
215+ environment = environment
216+ ):
176217 to_uninstall .add (key )
177218 elif requirements_lut [key ].specifier .contains (dist .version ):
178219 satisfied .add (key )
179220
180221 for key , requirement in requirements_lut .items ():
181- if key not in satisfied and requirement .match_markers ():
222+ if key not in satisfied and requirement .match_markers (environment = environment ):
182223 to_install .add (requirement )
183224
184225 # Make sure to not uninstall any packages that should be ignored
0 commit comments