__init__.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. """
  2. Python shims for the PEP 517 and PEP 660 build backend.
  3. Major imports in this module are required to be lazy:
  4. ```
  5. $ hyperfine \
  6. "/usr/bin/python3 -c \"print('hi')\"" \
  7. "/usr/bin/python3 -c \"from subprocess import check_call; print('hi')\""
  8. Base: Time (mean ± σ): 11.0 ms ± 1.7 ms [User: 8.5 ms, System: 2.5 ms]
  9. With import: Time (mean ± σ): 15.2 ms ± 2.0 ms [User: 12.3 ms, System: 2.9 ms]
  10. Base 1.38 ± 0.28 times faster than with import
  11. ```
  12. The same thing goes for the typing module, so we use Python 3.10 type annotations that
  13. don't require importing typing but then quote them so earlier Python version ignore
  14. them while IDEs and type checker can see through the quotes.
  15. """
  16. TYPE_CHECKING = False
  17. if TYPE_CHECKING:
  18. from collections.abc import Mapping, Sequence # noqa:I001
  19. from typing import Any # noqa:I001
  20. # Use the `uv build-backend` command rather than `uv-build`. This option is provided
  21. # for downstream distributions who provide `uv` and wish to avoid building a partially
  22. # overlapping `uv-build` executable.
  23. USE_UV_EXECUTABLE = False
  24. def warn_config_settings(config_settings: "Mapping[Any, Any] | None" = None) -> None:
  25. import sys
  26. if config_settings:
  27. print("Warning: Config settings are not supported", file=sys.stderr)
  28. def call(
  29. args: "Sequence[str]", config_settings: "Mapping[Any, Any] | None" = None
  30. ) -> str:
  31. """Invoke a uv subprocess and return the filename from stdout."""
  32. import shutil
  33. import subprocess
  34. import sys
  35. warn_config_settings(config_settings)
  36. uv_bin_name = "uv" if USE_UV_EXECUTABLE else "uv-build"
  37. # Unlike `find_uv_bin`, this mechanism must work according to PEP 517
  38. uv_bin = shutil.which(uv_bin_name)
  39. if uv_bin is None:
  40. raise RuntimeError(f"{uv_bin_name} was not properly installed")
  41. build_backend_args = ["build-backend"] if USE_UV_EXECUTABLE else []
  42. # Forward stderr, capture stdout for the filename
  43. result = subprocess.run(
  44. [uv_bin, *build_backend_args, *args], stdout=subprocess.PIPE
  45. )
  46. if result.returncode != 0:
  47. sys.exit(result.returncode)
  48. # If there was extra stdout, forward it (there should not be extra stdout)
  49. stdout = result.stdout.decode("utf-8").strip().splitlines(keepends=True)
  50. sys.stdout.writelines(stdout[:-1])
  51. # Fail explicitly instead of an irrelevant stacktrace
  52. if not stdout:
  53. print(
  54. f"{uv_bin_name} subprocess did not return a filename on stdout",
  55. file=sys.stderr,
  56. )
  57. sys.exit(1)
  58. return stdout[-1].strip()
  59. def build_sdist(
  60. sdist_directory: str, config_settings: "Mapping[Any, Any] | None" = None
  61. ) -> str:
  62. """PEP 517 hook `build_sdist`."""
  63. args = ["build-sdist", sdist_directory]
  64. return call(args, config_settings)
  65. def build_wheel(
  66. wheel_directory: str,
  67. config_settings: "Mapping[Any, Any] | None" = None,
  68. metadata_directory: "str | None" = None,
  69. ) -> str:
  70. """PEP 517 hook `build_wheel`."""
  71. args = ["build-wheel", wheel_directory]
  72. if metadata_directory:
  73. args.extend([metadata_directory])
  74. return call(args, config_settings)
  75. def get_requires_for_build_sdist(
  76. config_settings: "Mapping[Any, Any] | None" = None,
  77. ) -> "Sequence[str]":
  78. """PEP 517 hook `get_requires_for_build_sdist`."""
  79. warn_config_settings(config_settings)
  80. return []
  81. def get_requires_for_build_wheel(
  82. config_settings: "Mapping[Any, Any] | None" = None,
  83. ) -> "Sequence[str]":
  84. """PEP 517 hook `get_requires_for_build_wheel`."""
  85. warn_config_settings(config_settings)
  86. return []
  87. def prepare_metadata_for_build_wheel(
  88. metadata_directory: str, config_settings: "Mapping[Any, Any] | None" = None
  89. ) -> str:
  90. """PEP 517 hook `prepare_metadata_for_build_wheel`."""
  91. args = ["prepare-metadata-for-build-wheel", metadata_directory]
  92. return call(args, config_settings)
  93. def build_editable(
  94. wheel_directory: str,
  95. config_settings: "Mapping[Any, Any] | None" = None,
  96. metadata_directory: "str | None" = None,
  97. ) -> str:
  98. """PEP 660 hook `build_editable`."""
  99. args = ["build-editable", wheel_directory]
  100. if metadata_directory:
  101. args.extend([metadata_directory])
  102. return call(args, config_settings)
  103. def get_requires_for_build_editable(
  104. config_settings: "Mapping[Any, Any] | None" = None,
  105. ) -> "Sequence[str]":
  106. """PEP 660 hook `get_requires_for_build_editable`."""
  107. warn_config_settings(config_settings)
  108. return []
  109. def prepare_metadata_for_build_editable(
  110. metadata_directory: str, config_settings: "Mapping[Any, Any] | None" = None
  111. ) -> str:
  112. """PEP 660 hook `prepare_metadata_for_build_editable`."""
  113. args = ["prepare-metadata-for-build-editable", metadata_directory]
  114. return call(args, config_settings)