@@ -29,7 +29,7 @@ behavior.
29
29
load ("//python/pip_install:requirements_parser.bzl" , "parse" )
30
30
load (":normalize_name.bzl" , "normalize_name" )
31
31
load (":pypi_index_sources.bzl" , "get_simpleapi_sources" )
32
- load (":whl_target_platforms.bzl" , "whl_target_platforms" )
32
+ load (":whl_target_platforms.bzl" , "select_whls" , " whl_target_platforms" )
33
33
34
34
# This includes the vendored _translate_cpu and _translate_os from
35
35
# @platforms//host:extension.bzl at version 0.0.9 so that we don't
@@ -147,6 +147,9 @@ def parse_requirements(
147
147
requirements_lock = None ,
148
148
requirements_windows = None ,
149
149
extra_pip_args = [],
150
+ get_index_urls = None ,
151
+ python_version = None ,
152
+ logger = None ,
150
153
fail_fn = fail ):
151
154
"""Get the requirements with platforms that the requirements apply to.
152
155
@@ -161,6 +164,12 @@ def parse_requirements(
161
164
requirements_windows (label): The requirements file for windows OS.
162
165
extra_pip_args (string list): Extra pip arguments to perform extra validations and to
163
166
be joined with args fined in files.
167
+ get_index_urls: Callable[[ctx, list[str]], dict], a callable to get all
168
+ of the distribution URLs from a PyPI index. Accepts ctx and
169
+ distribution names to query.
170
+ python_version: str or None. This is needed when the get_index_urls is
171
+ specified. It should be of the form "3.x.x",
172
+ logger: repo_utils.logger or None, a simple struct to log diagnostic messages.
164
173
fail_fn (Callable[[str], None]): A failure function used in testing failure cases.
165
174
166
175
Returns:
@@ -317,26 +326,47 @@ def parse_requirements(
317
326
)
318
327
for_req .target_platforms .append (target_platform )
319
328
320
- return {
321
- whl_name : [
322
- struct (
323
- distribution = r .distribution ,
324
- srcs = r .srcs ,
325
- requirement_line = r .requirement_line ,
326
- target_platforms = sorted (r .target_platforms ),
327
- extra_pip_args = r .extra_pip_args ,
328
- download = r .download ,
329
- # Note, some lock file formats have URLs and dists stored in
330
- # the file, this field can be used for storing those values in
331
- # the future. This is also going to be used by the pypi_index
332
- # helper.
333
- whls = [],
334
- sdists = [],
329
+ index_urls = {}
330
+ if get_index_urls :
331
+ if not python_version :
332
+ fail_fn ("'python_version' must be provided" )
333
+ return None
334
+
335
+ index_urls = get_index_urls (
336
+ ctx ,
337
+ # Use list({}) as a way to have a set
338
+ list ({
339
+ req .distribution : None
340
+ for reqs in requirements_by_platform .values ()
341
+ for req in reqs .values ()
342
+ }),
343
+ )
344
+
345
+ ret = {}
346
+ for whl_name , reqs in requirements_by_platform .items ():
347
+ ret [whl_name ] = []
348
+ for r in sorted (reqs .values (), key = lambda r : r .requirement_line ):
349
+ whls , sdist = _add_dists (
350
+ r ,
351
+ index_urls .get (whl_name ),
352
+ python_version = python_version ,
353
+ logger = logger ,
335
354
)
336
- for r in sorted (reqs .values (), key = lambda r : r .requirement_line )
337
- ]
338
- for whl_name , reqs in requirements_by_platform .items ()
339
- }
355
+
356
+ ret .setdefault (whl_name , []).append (
357
+ struct (
358
+ distribution = r .distribution ,
359
+ srcs = r .srcs ,
360
+ requirement_line = r .requirement_line ,
361
+ target_platforms = sorted (r .target_platforms ),
362
+ extra_pip_args = r .extra_pip_args ,
363
+ download = r .download ,
364
+ whls = whls ,
365
+ sdist = sdist ,
366
+ ),
367
+ )
368
+
369
+ return ret
340
370
341
371
def select_requirement (requirements , * , platform ):
342
372
"""A simple function to get a requirement for a particular platform.
@@ -383,3 +413,58 @@ def host_platform(repository_os):
383
413
_translate_os (repository_os .name .lower ()),
384
414
_translate_cpu (repository_os .arch .lower ()),
385
415
)
416
+
417
+ def _add_dists (requirement , index_urls , python_version , logger = None ):
418
+ """Populate dists based on the information from the PyPI index.
419
+
420
+ This function will modify the given requirements_by_platform data structure.
421
+
422
+ Args:
423
+ requirement: The result of parse_requirements function.
424
+ index_urls: The result of simpleapi_download.
425
+ python_version: The version of the python interpreter.
426
+ logger: A logger for printing diagnostic info.
427
+ """
428
+ if not index_urls :
429
+ return [], None
430
+
431
+ whls = []
432
+ sdist = None
433
+
434
+ # TODO @aignas 2024-05-22: it is in theory possible to add all
435
+ # requirements by version instead of by sha256. This may be useful
436
+ # for some projects.
437
+ for sha256 in requirement .srcs .shas :
438
+ # For now if the artifact is marked as yanked we just ignore it.
439
+ #
440
+ # See https://packaging.python.org/en/latest/specifications/simple-repository-api/#adding-yank-support-to-the-simple-api
441
+
442
+ maybe_whl = index_urls .whls .get (sha256 )
443
+ if maybe_whl and not maybe_whl .yanked :
444
+ whls .append (maybe_whl )
445
+ continue
446
+
447
+ maybe_sdist = index_urls .sdists .get (sha256 )
448
+ if maybe_sdist and not maybe_sdist .yanked :
449
+ sdist = maybe_sdist
450
+ continue
451
+
452
+ if logger :
453
+ logger .warn ("Could not find a whl or an sdist with sha256={}" .format (sha256 ))
454
+
455
+ # Filter out the wheels that are incompatible with the target_platforms.
456
+ whls = select_whls (
457
+ whls = whls ,
458
+ want_abis = [
459
+ "none" ,
460
+ "abi3" ,
461
+ "cp" + python_version .replace ("." , "" ),
462
+ # Older python versions have wheels for the `*m` ABI.
463
+ "cp" + python_version .replace ("." , "" ) + "m" ,
464
+ ],
465
+ want_platforms = requirement .target_platforms ,
466
+ want_python_version = python_version ,
467
+ logger = logger ,
468
+ )
469
+
470
+ return whls , sdist
0 commit comments