|
16 | 16 |
|
17 | 17 | load("//python/private:stamp.bzl", "is_stamping_enabled")
|
18 | 18 | load(":py_package.bzl", "py_package_lib")
|
| 19 | +load(":py_wheel_normalize_pep440.bzl", "normalize_pep440") |
19 | 20 |
|
20 | 21 | PyWheelInfo = provider(
|
21 | 22 | doc = "Information about a wheel produced by `py_wheel`",
|
@@ -239,391 +240,6 @@ def _escape_filename_distribution_name(name):
|
239 | 240 | )
|
240 | 241 | return escaped
|
241 | 242 |
|
242 |
| -def normalize_pep440(version): |
243 |
| - """Escape the version component of a filename. |
244 |
| -
|
245 |
| - See https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode |
246 |
| - and https://peps.python.org/pep-0440/ |
247 |
| -
|
248 |
| - Args: |
249 |
| - version: version string to be normalized according to PEP 440. |
250 |
| -
|
251 |
| - Returns: |
252 |
| - string containing the normalized version. |
253 |
| - """ |
254 |
| - |
255 |
| - version = version.strip() # PEP 440: Leading and Trailing Whitespace |
256 |
| - contexts = [] |
257 |
| - |
258 |
| - def open_context(start): |
259 |
| - """Open an new parsing context. |
260 |
| -
|
261 |
| - If the current parsing step succeeds, call close_context(). |
262 |
| - If the current parsing step fails, call contexts.pop() to |
263 |
| - go back to how it was before we opened a new context. |
264 |
| -
|
265 |
| - Args: |
266 |
| - start: index into `version` indicating where the current |
267 |
| - parsing step starts. |
268 |
| - """ |
269 |
| - contexts.append({"norm": "", "start": start}) |
270 |
| - return contexts[-1] |
271 |
| - |
272 |
| - def close_context(): |
273 |
| - """Close the current context successfully and merge the results.""" |
274 |
| - finished = contexts.pop() |
275 |
| - contexts[-1]["norm"] += finished["norm"] |
276 |
| - contexts[-1]["start"] = finished["start"] |
277 |
| - |
278 |
| - def is_(reference): |
279 |
| - """Predicate testing a token for equality with `reference`.""" |
280 |
| - return lambda token: token == reference |
281 |
| - |
282 |
| - def is_not(reference): |
283 |
| - """Predicate testing a token for inequality with `reference`.""" |
284 |
| - return lambda token: token != reference |
285 |
| - |
286 |
| - def in_(reference): |
287 |
| - """Predicate testing if a token is in the list `reference`.""" |
288 |
| - return lambda token: token in reference |
289 |
| - |
290 |
| - def accept(predicate, value): |
291 |
| - """If `predicate` matches the next token, accept the token. |
292 |
| -
|
293 |
| - Accepting the token means adding it (according to `value`) to |
294 |
| - the running results maintained in context["norm"] and |
295 |
| - advancing the cursor in context["start"] to the next token in |
296 |
| - `version`. |
297 |
| -
|
298 |
| - Args: |
299 |
| - predicate: function taking a token and returning a boolean |
300 |
| - saying if we want to accept the token. |
301 |
| - value: the string to add if there's a match, or, if `value` |
302 |
| - is a function, the function to apply to the current token |
303 |
| - to get the string to add. |
304 |
| -
|
305 |
| - Returns: |
306 |
| - whether a token was accepted. |
307 |
| - """ |
308 |
| - |
309 |
| - context = contexts[-1] |
310 |
| - |
311 |
| - if context["start"] >= len(version): |
312 |
| - return False |
313 |
| - |
314 |
| - token = version[context["start"]] |
315 |
| - |
316 |
| - if predicate(token): |
317 |
| - if type(value) in ["function", "builtin_function_or_method"]: |
318 |
| - value = value(token) |
319 |
| - |
320 |
| - context["norm"] += value |
321 |
| - context["start"] += 1 |
322 |
| - return True |
323 |
| - |
324 |
| - return False |
325 |
| - |
326 |
| - def accept_placeholder(): |
327 |
| - """Accept a Bazel placeholder. |
328 |
| -
|
329 |
| - Placeholders aren't actually part of PEP 440, but are used for |
330 |
| - stamping purposes. A placeholder might be |
331 |
| - ``{BUILD_TIMESTAMP}``, for instance. We'll accept these as |
332 |
| - they are, assuming they will expand to something that makes |
333 |
| - sense where they appear. Before the stamping has happened, a |
334 |
| - resulting wheel file name containing a placeholder will not |
335 |
| - actually be valid. |
336 |
| -
|
337 |
| - """ |
338 |
| - context = open_context(contexts[-1]["start"]) |
339 |
| - |
340 |
| - if not accept(is_("{"), str): |
341 |
| - contexts.pop() |
342 |
| - return False |
343 |
| - |
344 |
| - start = context["start"] |
345 |
| - for _ in range(start, len(version) + 1): |
346 |
| - if not accept(is_not("}"), str): |
347 |
| - break |
348 |
| - |
349 |
| - if not accept(is_("}"), str): |
350 |
| - contexts.pop() |
351 |
| - return False |
352 |
| - |
353 |
| - close_context() |
354 |
| - return True |
355 |
| - |
356 |
| - def accept_digits(): |
357 |
| - """Accept multiple digits (or placeholders).""" |
358 |
| - |
359 |
| - def isdigit(token): |
360 |
| - return token.isdigit() |
361 |
| - |
362 |
| - context = open_context(contexts[-1]["start"]) |
363 |
| - start = context["start"] |
364 |
| - |
365 |
| - for i in range(start, len(version) + 1): |
366 |
| - if not accept(isdigit, str) and not accept_placeholder(): |
367 |
| - if i - start >= 1: |
368 |
| - if context["norm"].isdigit(): |
369 |
| - # PEP 440: Integer Normalization |
370 |
| - context["norm"] = str(int(context["norm"])) |
371 |
| - close_context() |
372 |
| - return True |
373 |
| - break |
374 |
| - |
375 |
| - contexts.pop() |
376 |
| - return False |
377 |
| - |
378 |
| - def accept_string(string, replacement): |
379 |
| - """Accept a `string` in the input. Output `replacement`.""" |
380 |
| - context = open_context(contexts[-1]["start"]) |
381 |
| - |
382 |
| - for character in string.elems(): |
383 |
| - if not accept(in_([character, character.upper()]), ""): |
384 |
| - contexts.pop() |
385 |
| - return False |
386 |
| - |
387 |
| - context["norm"] = replacement |
388 |
| - |
389 |
| - close_context() |
390 |
| - return True |
391 |
| - |
392 |
| - def accept_alnum(): |
393 |
| - """Accept an alphanumeric sequence.""" |
394 |
| - |
395 |
| - def isalnum(token): |
396 |
| - return token.isalnum() |
397 |
| - |
398 |
| - # PEP 440: Case sensitivity |
399 |
| - def lower(token): |
400 |
| - return token.lower() |
401 |
| - |
402 |
| - context = open_context(contexts[-1]["start"]) |
403 |
| - start = context["start"] |
404 |
| - |
405 |
| - for i in range(start, len(version) + 1): |
406 |
| - if not accept(isalnum, lower) and not accept_placeholder(): |
407 |
| - if i - start >= 1: |
408 |
| - close_context() |
409 |
| - return True |
410 |
| - break |
411 |
| - |
412 |
| - contexts.pop() |
413 |
| - return False |
414 |
| - |
415 |
| - def accept_dot_number(): |
416 |
| - """Accept a dot followed by digits.""" |
417 |
| - open_context(contexts[-1]["start"]) |
418 |
| - |
419 |
| - if accept(is_("."), ".") and accept_digits(): |
420 |
| - close_context() |
421 |
| - return True |
422 |
| - else: |
423 |
| - contexts.pop() |
424 |
| - return False |
425 |
| - |
426 |
| - def accept_dot_number_sequence(): |
427 |
| - """Accept a sequence of dot+digits.""" |
428 |
| - context = contexts[-1] |
429 |
| - start = context["start"] |
430 |
| - i = start |
431 |
| - |
432 |
| - for i in range(start, len(version) + 1): |
433 |
| - if not accept_dot_number(): |
434 |
| - break |
435 |
| - return i - start >= 1 |
436 |
| - |
437 |
| - def accept_separator_alnum(): |
438 |
| - """Accept a separator followed by an alphanumeric string.""" |
439 |
| - open_context(contexts[-1]["start"]) |
440 |
| - |
441 |
| - # PEP 440: Local version segments |
442 |
| - if ( |
443 |
| - accept(in_([".", "-", "_"]), ".") and |
444 |
| - (accept_digits() or accept_alnum()) |
445 |
| - ): |
446 |
| - close_context() |
447 |
| - return True |
448 |
| - |
449 |
| - contexts.pop() |
450 |
| - return False |
451 |
| - |
452 |
| - def accept_separator_alnum_sequence(): |
453 |
| - """Accept a sequence of separator+alphanumeric.""" |
454 |
| - context = contexts[-1] |
455 |
| - start = context["start"] |
456 |
| - i = start |
457 |
| - |
458 |
| - for i in range(start, len(version) + 1): |
459 |
| - if not accept_separator_alnum(): |
460 |
| - break |
461 |
| - |
462 |
| - return i - start >= 1 |
463 |
| - |
464 |
| - def accept_epoch(): |
465 |
| - """PEP 440: Version epochs.""" |
466 |
| - context = open_context(contexts[-1]["start"]) |
467 |
| - if accept_digits() and accept(is_("!"), "!"): |
468 |
| - if context["norm"] == "0!": |
469 |
| - contexts.pop() |
470 |
| - contexts[-1]["start"] = context["start"] |
471 |
| - else: |
472 |
| - close_context() |
473 |
| - return True |
474 |
| - else: |
475 |
| - contexts.pop() |
476 |
| - return False |
477 |
| - |
478 |
| - def accept_release(): |
479 |
| - """Accept the release segment, numbers separated by dots.""" |
480 |
| - open_context(contexts[-1]["start"]) |
481 |
| - |
482 |
| - if not accept_digits(): |
483 |
| - contexts.pop() |
484 |
| - return False |
485 |
| - |
486 |
| - accept_dot_number_sequence() |
487 |
| - close_context() |
488 |
| - return True |
489 |
| - |
490 |
| - def accept_pre_l(): |
491 |
| - """PEP 440: Pre-release spelling.""" |
492 |
| - open_context(contexts[-1]["start"]) |
493 |
| - |
494 |
| - if ( |
495 |
| - accept_string("alpha", "a") or |
496 |
| - accept_string("a", "a") or |
497 |
| - accept_string("beta", "b") or |
498 |
| - accept_string("b", "b") or |
499 |
| - accept_string("c", "rc") or |
500 |
| - accept_string("preview", "rc") or |
501 |
| - accept_string("pre", "rc") or |
502 |
| - accept_string("rc", "rc") |
503 |
| - ): |
504 |
| - close_context() |
505 |
| - return True |
506 |
| - else: |
507 |
| - contexts.pop() |
508 |
| - return False |
509 |
| - |
510 |
| - def accept_prerelease(): |
511 |
| - """PEP 440: Pre-releases.""" |
512 |
| - context = open_context(contexts[-1]["start"]) |
513 |
| - |
514 |
| - # PEP 440: Pre-release separators |
515 |
| - accept(in_(["-", "_", "."]), "") |
516 |
| - |
517 |
| - if not accept_pre_l(): |
518 |
| - contexts.pop() |
519 |
| - return False |
520 |
| - |
521 |
| - accept(in_(["-", "_", "."]), "") |
522 |
| - |
523 |
| - if not accept_digits(): |
524 |
| - # PEP 440: Implicit pre-release number |
525 |
| - context["norm"] += "0" |
526 |
| - |
527 |
| - close_context() |
528 |
| - return True |
529 |
| - |
530 |
| - def accept_implicit_postrelease(): |
531 |
| - """PEP 440: Implicit post releases.""" |
532 |
| - context = open_context(contexts[-1]["start"]) |
533 |
| - |
534 |
| - if accept(is_("-"), "") and accept_digits(): |
535 |
| - context["norm"] = ".post" + context["norm"] |
536 |
| - close_context() |
537 |
| - return True |
538 |
| - |
539 |
| - contexts.pop() |
540 |
| - return False |
541 |
| - |
542 |
| - def accept_explicit_postrelease(): |
543 |
| - """PEP 440: Post-releases.""" |
544 |
| - context = open_context(contexts[-1]["start"]) |
545 |
| - |
546 |
| - # PEP 440: Post release separators |
547 |
| - if not accept(in_(["-", "_", "."]), "."): |
548 |
| - context["norm"] += "." |
549 |
| - |
550 |
| - # PEP 440: Post release spelling |
551 |
| - if ( |
552 |
| - accept_string("post", "post") or |
553 |
| - accept_string("rev", "post") or |
554 |
| - accept_string("r", "post") |
555 |
| - ): |
556 |
| - accept(in_(["-", "_", "."]), "") |
557 |
| - |
558 |
| - if not accept_digits(): |
559 |
| - # PEP 440: Implicit post release number |
560 |
| - context["norm"] += "0" |
561 |
| - |
562 |
| - close_context() |
563 |
| - return True |
564 |
| - |
565 |
| - contexts.pop() |
566 |
| - return False |
567 |
| - |
568 |
| - def accept_postrelease(): |
569 |
| - """PEP 440: Post-releases.""" |
570 |
| - open_context(contexts[-1]["start"]) |
571 |
| - |
572 |
| - if accept_implicit_postrelease() or accept_explicit_postrelease(): |
573 |
| - close_context() |
574 |
| - return True |
575 |
| - |
576 |
| - contexts.pop() |
577 |
| - return False |
578 |
| - |
579 |
| - def accept_devrelease(): |
580 |
| - """PEP 440: Developmental releases.""" |
581 |
| - context = open_context(contexts[-1]["start"]) |
582 |
| - |
583 |
| - # PEP 440: Development release separators |
584 |
| - if not accept(in_(["-", "_", "."]), "."): |
585 |
| - context["norm"] += "." |
586 |
| - |
587 |
| - if accept_string("dev", "dev"): |
588 |
| - accept(in_(["-", "_", "."]), "") |
589 |
| - |
590 |
| - if not accept_digits(): |
591 |
| - # PEP 440: Implicit development release number |
592 |
| - context["norm"] += "0" |
593 |
| - |
594 |
| - close_context() |
595 |
| - return True |
596 |
| - |
597 |
| - contexts.pop() |
598 |
| - return False |
599 |
| - |
600 |
| - def accept_local(): |
601 |
| - """PEP 440: Local version identifiers.""" |
602 |
| - open_context(contexts[-1]["start"]) |
603 |
| - |
604 |
| - if accept(is_("+"), "+") and accept_alnum(): |
605 |
| - accept_separator_alnum_sequence() |
606 |
| - close_context() |
607 |
| - return True |
608 |
| - |
609 |
| - contexts.pop() |
610 |
| - return False |
611 |
| - |
612 |
| - open_context(0) |
613 |
| - accept(is_("v"), "") # PEP 440: Preceding v character |
614 |
| - accept_epoch() |
615 |
| - accept_release() |
616 |
| - accept_prerelease() |
617 |
| - accept_postrelease() |
618 |
| - accept_devrelease() |
619 |
| - accept_local() |
620 |
| - if version[contexts[-1]["start"]:]: |
621 |
| - fail( |
622 |
| - "Failed to parse PEP 440 version identifier '%s'." % version, |
623 |
| - "Parse error at '%s'" % version[contexts[-1]["start"]:], |
624 |
| - ) |
625 |
| - return contexts[-1]["norm"] |
626 |
| - |
627 | 243 | def _escape_filename_segment(segment):
|
628 | 244 | """Escape a segment of the wheel filename.
|
629 | 245 |
|
|
0 commit comments