auditwheel_wrapper.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/env python
  2. # Copyright 2022 The Matrix.org Foundation C.I.C.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # Wraps `auditwheel repair` to first check if we're repairing a potentially abi3
  16. # compatible wheel, if so rename the wheel before repairing it.
  17. import argparse
  18. import os
  19. import subprocess
  20. from typing import Optional
  21. from zipfile import ZipFile
  22. from packaging.tags import Tag
  23. from packaging.utils import parse_wheel_filename
  24. from packaging.version import Version
  25. def check_is_abi3_compatible(wheel_file: str) -> None:
  26. """Check the contents of the built wheel for any `.so` files that are *not*
  27. abi3 compatible.
  28. """
  29. with ZipFile(wheel_file, "r") as wheel:
  30. for file in wheel.namelist():
  31. if not file.endswith(".so"):
  32. continue
  33. if not file.endswith(".abi3.so"):
  34. raise Exception(f"Found non-abi3 lib: {file}")
  35. def cpython(wheel_file: str, name: str, version: Version, tag: Tag) -> str:
  36. """Replaces the cpython wheel file with a ABI3 compatible wheel"""
  37. if tag.abi == "abi3":
  38. # Nothing to do.
  39. return wheel_file
  40. check_is_abi3_compatible(wheel_file)
  41. # HACK: it seems that some older versions of pip will consider a wheel marked
  42. # as macosx_11_0 as incompatible with Big Sur. I haven't done the full archaeology
  43. # here; there are some clues in
  44. # https://github.com/pantsbuild/pants/pull/12857
  45. # https://github.com/pypa/pip/issues/9138
  46. # https://github.com/pypa/packaging/pull/319
  47. # Empirically this seems to work, note that macOS 11 and 10.16 are the same,
  48. # both versions are valid for backwards compatibility.
  49. platform = tag.platform.replace("macosx_11_0", "macosx_10_16")
  50. abi3_tag = Tag(tag.interpreter, "abi3", platform)
  51. dirname = os.path.dirname(wheel_file)
  52. new_wheel_file = os.path.join(
  53. dirname,
  54. f"{name}-{version}-{abi3_tag}.whl",
  55. )
  56. os.rename(wheel_file, new_wheel_file)
  57. print("Renamed wheel to", new_wheel_file)
  58. return new_wheel_file
  59. def main(wheel_file: str, dest_dir: str, archs: Optional[str]) -> None:
  60. """Entry point"""
  61. # Parse the wheel file name into its parts. Note that `parse_wheel_filename`
  62. # normalizes the package name (i.e. it converts matrix_synapse ->
  63. # matrix-synapse), which is not what we want.
  64. _, version, build, tags = parse_wheel_filename(os.path.basename(wheel_file))
  65. name = os.path.basename(wheel_file).split("-")[0]
  66. if len(tags) != 1:
  67. # We expect only a wheel file with only a single tag
  68. raise Exception(f"Unexpectedly found multiple tags: {tags}")
  69. tag = next(iter(tags))
  70. if build:
  71. # We don't use build tags in Synapse
  72. raise Exception(f"Unexpected build tag: {build}")
  73. # If the wheel is for cpython then convert it into an abi3 wheel.
  74. if tag.interpreter.startswith("cp"):
  75. wheel_file = cpython(wheel_file, name, version, tag)
  76. # Finally, repair the wheel.
  77. if archs is not None:
  78. # If we are given archs then we are on macos and need to use
  79. # `delocate-listdeps`.
  80. subprocess.run(["delocate-listdeps", wheel_file], check=True)
  81. subprocess.run(
  82. ["delocate-wheel", "--require-archs", archs, "-w", dest_dir, wheel_file],
  83. check=True,
  84. )
  85. else:
  86. subprocess.run(["auditwheel", "repair", "-w", dest_dir, wheel_file], check=True)
  87. if __name__ == "__main__":
  88. parser = argparse.ArgumentParser(description="Tag wheel as abi3 and repair it.")
  89. parser.add_argument(
  90. "--wheel-dir",
  91. "-w",
  92. metavar="WHEEL_DIR",
  93. help="Directory to store delocated wheels",
  94. required=True,
  95. )
  96. parser.add_argument(
  97. "--require-archs",
  98. metavar="archs",
  99. default=None,
  100. )
  101. parser.add_argument(
  102. "wheel_file",
  103. metavar="WHEEL_FILE",
  104. )
  105. args = parser.parse_args()
  106. wheel_file = args.wheel_file
  107. wheel_dir = args.wheel_dir
  108. archs = args.require_archs
  109. main(wheel_file, wheel_dir, archs)