8
8
9
9
from pex .dist_metadata import Requirement
10
10
from pex .exceptions import production_assert
11
+ from pex .interpreter_constraints import iter_compatible_versions
11
12
from pex .orderedset import OrderedSet
13
+ from pex .pep_440 import Version
12
14
from pex .pep_503 import ProjectName
13
- from pex .resolve .locked_resolve import LockedRequirement , LockedResolve
15
+ from pex .resolve .locked_resolve import LockedRequirement , LockedResolve , TargetSystem
14
16
from pex .sorted_tuple import SortedTuple
15
17
from pex .third_party .packaging .markers import Marker , Variable
16
- from pex .typing import TYPE_CHECKING , cast
18
+ from pex .typing import TYPE_CHECKING , Generic , cast
17
19
18
20
if TYPE_CHECKING :
19
- from typing import Callable , DefaultDict , Dict , Iterable , Iterator , List , Optional , Tuple , Union
21
+ from typing import (
22
+ Any ,
23
+ Callable ,
24
+ DefaultDict ,
25
+ Dict ,
26
+ FrozenSet ,
27
+ Iterable ,
28
+ Iterator ,
29
+ List ,
30
+ Optional ,
31
+ Tuple ,
32
+ Type ,
33
+ TypeVar ,
34
+ Union ,
35
+ )
20
36
21
37
import attr # vendor:skip
22
38
23
- EvalExtra = Callable [[ProjectName ], bool ]
39
+ EvalMarker = Callable [["MarkerEnv" ], bool ]
24
40
else :
25
41
from pex .third_party import attr
26
42
27
43
44
+ @attr .s (frozen = True )
45
+ class MarkerEnv (object ):
46
+ @classmethod
47
+ def create (
48
+ cls ,
49
+ extras , # type: Iterable[str]
50
+ requires_python , # type: Iterable[str]
51
+ target_systems , # type: Iterable[TargetSystem.Value]
52
+ ):
53
+ # type: (...) -> MarkerEnv
54
+
55
+ python_full_versions = (
56
+ list (iter_compatible_versions (requires_python )) if requires_python else []
57
+ )
58
+ python_versions = OrderedSet (
59
+ python_full_version [:2 ] for python_full_version in python_full_versions
60
+ )
61
+
62
+ os_names = []
63
+ platform_systems = []
64
+ sys_platforms = []
65
+ for target_system in target_systems :
66
+ if target_system is TargetSystem .LINUX :
67
+ os_names .append ("posix" )
68
+ platform_systems .append ("Linux" )
69
+ sys_platforms .append ("linux" )
70
+ sys_platforms .append ("linux2" )
71
+ elif target_system is TargetSystem .MAC :
72
+ os_names .append ("posix" )
73
+ platform_systems .append ("Darwin" )
74
+ sys_platforms .append ("darwin" )
75
+ elif target_system is TargetSystem .WINDOWS :
76
+ os_names .append ("nt" )
77
+ platform_systems .append ("Windows" )
78
+ sys_platforms .append ("win32" )
79
+
80
+ return cls (
81
+ extras = frozenset (ProjectName (extra ) for extra in (extras or ["" ])),
82
+ os_names = frozenset (os_names ),
83
+ platform_systems = frozenset (platform_systems ),
84
+ sys_platforms = frozenset (sys_platforms ),
85
+ python_versions = frozenset (
86
+ Version ("." .join (map (str , python_version ))) for python_version in python_versions
87
+ ),
88
+ python_full_versions = frozenset (
89
+ Version ("." .join (map (str , python_full_version )))
90
+ for python_full_version in python_full_versions
91
+ ),
92
+ )
93
+
94
+ extras = attr .ib () # type: FrozenSet[ProjectName]
95
+ os_names = attr .ib () # type: FrozenSet[str]
96
+ platform_systems = attr .ib () # type: FrozenSet[str]
97
+ sys_platforms = attr .ib () # type: FrozenSet[str]
98
+ python_versions = attr .ib () # type: FrozenSet[Version]
99
+ python_full_versions = attr .ib () # type: FrozenSet[Version]
100
+
101
+
28
102
_OPERATORS = {
29
103
"in" : lambda lhs , rhs : lhs in rhs ,
30
104
"not in" : lambda lhs , rhs : lhs not in rhs ,
39
113
40
114
class _Op (object ):
41
115
def __init__ (self , lhs ):
42
- self .lhs = lhs # type: EvalExtra
43
- self .rhs = None # type: Optional[EvalExtra ]
116
+ self .lhs = lhs # type: EvalMarker
117
+ self .rhs = None # type: Optional[EvalMarker ]
44
118
45
119
46
120
class _And (_Op ):
47
- def __call__ (self , extra ):
48
- # type: (ProjectName ) -> bool
121
+ def __call__ (self , marker_env ):
122
+ # type: (MarkerEnv ) -> bool
49
123
production_assert (self .rhs is not None )
50
- return self .lhs (extra ) and cast ("EvalExtra " , self .rhs )(extra )
124
+ return self .lhs (marker_env ) and cast ("EvalMarker " , self .rhs )(marker_env )
51
125
52
126
53
127
class _Or (_Op ):
54
- def __call__ (self , extra ):
55
- # type: (ProjectName ) -> bool
128
+ def __call__ (self , marker_env ):
129
+ # type: (MarkerEnv ) -> bool
56
130
production_assert (self .rhs is not None )
57
- return self .lhs (extra ) or cast ("EvalExtra" , self .rhs )(extra )
131
+ return self .lhs (marker_env ) or cast ("EvalMarker" , self .rhs )(marker_env )
132
+
58
133
134
+ def _get_values_func (marker ):
135
+ # type: (str) -> Optional[Tuple[Callable[[MarkerEnv], FrozenSet], Type]]
59
136
60
- def _parse_extra_item (
61
- stack , # type: List[EvalExtra]
137
+ if marker == "extra" :
138
+ return lambda marker_env : marker_env .extras , ProjectName
139
+ elif marker == "os_name" :
140
+ return lambda marker_env : marker_env .os_names , str
141
+ elif marker == "platform_system" :
142
+ return lambda marker_env : marker_env .platform_systems , str
143
+ elif marker == "sys_platform" :
144
+ return lambda marker_env : marker_env .sys_platforms , str
145
+ elif marker == "python_version" :
146
+ return lambda marker_env : marker_env .python_versions , Version
147
+ elif marker == "python_full_version" :
148
+ return lambda marker_env : marker_env .python_full_versions , Version
149
+ return None
150
+
151
+
152
+ if TYPE_CHECKING :
153
+ _T = TypeVar ("_T" )
154
+
155
+
156
+ class EvalMarkerFunc (Generic ["_T" ]):
157
+ @classmethod
158
+ def create (
159
+ cls ,
160
+ lhs , # type: Any
161
+ op , # type: Any
162
+ rhs , # type: Any
163
+ ):
164
+ # type: (...) -> Callable[[MarkerEnv], bool]
165
+
166
+ if isinstance (lhs , Variable ):
167
+ value = _get_values_func (str (lhs ))
168
+ if value :
169
+ get_values , operand_type = value
170
+ return cls (
171
+ get_values = get_values ,
172
+ op = _OPERATORS [str (op )],
173
+ operand_type = operand_type ,
174
+ rhs = operand_type (rhs ),
175
+ )
176
+
177
+ if isinstance (rhs , Variable ):
178
+ value = _get_values_func (str (rhs ))
179
+ if value :
180
+ get_values , operand_type = value
181
+ return cls (
182
+ get_values = get_values ,
183
+ op = _OPERATORS [str (op )],
184
+ operand_type = operand_type ,
185
+ lhs = operand_type (lhs ),
186
+ )
187
+
188
+ return lambda _ : True
189
+
190
+ def __init__ (
191
+ self ,
192
+ get_values , # type: Callable[[MarkerEnv], Iterable[_T]]
193
+ op , # type: Callable[[_T, _T], bool]
194
+ operand_type , # type: Callable[[Any], _T]
195
+ lhs = None , # type: Optional[_T]
196
+ rhs = None , # type: Optional[_T]
197
+ ):
198
+ # type: (...) -> None
199
+
200
+ self ._get_values = get_values
201
+ if lhs is not None :
202
+ self ._func = lambda value : op (cast ("_T" , lhs ), operand_type (value ))
203
+ elif rhs is not None :
204
+ self ._func = lambda value : op (operand_type (value ), cast ("_T" , rhs ))
205
+ else :
206
+ raise ValueError (
207
+ "Must be called with exactly one of lhs or rhs but not both. "
208
+ "Given lhs={lhs} and rhs={rhs}" .format (lhs = lhs , rhs = rhs )
209
+ )
210
+
211
+ def __call__ (self , marker_env ):
212
+ # type: (MarkerEnv) -> bool
213
+
214
+ values = self ._get_values (marker_env )
215
+ return any (map (self ._func , values )) if values else True
216
+
217
+
218
+ def _parse_marker_item (
219
+ stack , # type: List[EvalMarker]
62
220
item , # type: Union[str, List, Tuple]
63
221
marker , # type: Marker
64
222
):
@@ -70,16 +228,10 @@ def _parse_extra_item(
70
228
stack .append (_Or (stack .pop ()))
71
229
elif isinstance (item , list ):
72
230
for element in item :
73
- _parse_extra_item (stack , element , marker )
231
+ _parse_marker_item (stack , element , marker )
74
232
elif isinstance (item , tuple ):
75
233
lhs , op , rhs = item
76
- if isinstance (lhs , Variable ) and "extra" == str (lhs ):
77
- check = lambda extra : _OPERATORS [str (op )](extra , ProjectName (str (rhs )))
78
- elif isinstance (rhs , Variable ) and "extra" == str (rhs ):
79
- check = lambda extra : _OPERATORS [str (op )](extra , ProjectName (str (lhs )))
80
- else :
81
- # Any other condition could potentially be true.
82
- check = lambda _ : True
234
+ check = EvalMarkerFunc .create (lhs , op , rhs )
83
235
if stack :
84
236
production_assert (isinstance (stack [- 1 ], _Op ))
85
237
cast (_Op , stack [- 1 ]).rhs = check
@@ -89,47 +241,53 @@ def _parse_extra_item(
89
241
raise ValueError ("Marker is invalid: {marker}" .format (marker = marker ))
90
242
91
243
92
- def _parse_extra_check (marker ):
93
- # type: (Marker) -> EvalExtra
94
- checks = [] # type: List[EvalExtra ]
244
+ def _parse_marker_check (marker ):
245
+ # type: (Marker) -> EvalMarker
246
+ checks = [] # type: List[EvalMarker ]
95
247
for item in marker ._markers :
96
- _parse_extra_item (checks , item , marker )
248
+ _parse_marker_item (checks , item , marker )
97
249
production_assert (len (checks ) == 1 )
98
250
return checks [0 ]
99
251
100
252
101
- _EXTRA_CHECKS = {} # type: Dict[str, EvalExtra ]
253
+ _MARKER_CHECKS = {} # type: Dict[str, EvalMarker ]
102
254
103
255
104
- def _parse_marker_for_extra_check (marker ):
105
- # type: (Marker) -> EvalExtra
256
+ def _parse_marker (marker ):
257
+ # type: (Marker) -> EvalMarker
106
258
maker_str = str (marker )
107
- eval_extra = _EXTRA_CHECKS .get (maker_str )
108
- if not eval_extra :
109
- eval_extra = _parse_extra_check (marker )
110
- _EXTRA_CHECKS [maker_str ] = eval_extra
111
- return eval_extra
259
+ eval_marker = _MARKER_CHECKS .get (maker_str )
260
+ if not eval_marker :
261
+ eval_marker = _parse_marker_check (marker )
262
+ _MARKER_CHECKS [maker_str ] = eval_marker
263
+ return eval_marker
112
264
113
265
114
266
def filter_dependencies (
115
267
requirement , # type: Requirement
116
268
locked_requirement , # type: LockedRequirement
269
+ requires_python = (), # type: Iterable[str]
270
+ target_systems = (), # type: Iterable[TargetSystem.Value]
117
271
):
118
272
# type: (...) -> Iterator[Requirement]
119
273
120
- extras = requirement .extras or ["" ]
274
+ marker_env = MarkerEnv .create (
275
+ extras = requirement .extras , requires_python = requires_python , target_systems = target_systems
276
+ )
121
277
for dep in locked_requirement .requires_dists :
122
278
if not dep .marker :
123
279
yield dep
124
280
else :
125
- eval_extra = _parse_marker_for_extra_check (dep .marker )
126
- if any ( eval_extra ( ProjectName ( extra )) for extra in extras ):
281
+ eval_marker = _parse_marker (dep .marker )
282
+ if eval_marker ( marker_env ):
127
283
yield dep
128
284
129
285
130
286
def remove_unused_requires_dist (
131
287
resolve_requirements , # type: Iterable[Requirement]
132
288
locked_resolve , # type: LockedResolve
289
+ requires_python = (), # type: Iterable[str]
290
+ target_systems = (), # type: Iterable[TargetSystem.Value]
133
291
):
134
292
# type: (...) -> LockedResolve
135
293
@@ -151,7 +309,9 @@ def remove_unused_requires_dist(
151
309
if not locked_req :
152
310
continue
153
311
154
- for dep in filter_dependencies (requirement , locked_req ):
312
+ for dep in filter_dependencies (
313
+ requirement , locked_req , requires_python = requires_python , target_systems = target_systems
314
+ ):
155
315
if dep .project_name in locked_req_by_project_name :
156
316
requires_dist_by_locked_req [locked_req ].add (dep )
157
317
requirements .append (dep )
0 commit comments