freeze_requirements.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. from __future__ import annotations
  2. import argparse
  3. from concurrent.futures import Future, ThreadPoolExecutor
  4. from os.path import abspath
  5. from subprocess import CalledProcessError, run
  6. from typing import Sequence
  7. from tools.lib import gitroot
  8. def worker(args: tuple[str, ...]) -> None:
  9. # pip-compile doesn't let you customize the header, so we write
  10. # one ourselves. However, pip-compile needs -o DEST otherwise
  11. # it will bump >= pins even if they're satisfied. So, we need to
  12. # unfortunately rewrite the whole file.
  13. dest = args[-1]
  14. try:
  15. run(args, check=True, capture_output=True)
  16. except CalledProcessError as e:
  17. raise e
  18. with open(dest, "rb+") as f:
  19. content = f.read()
  20. f.seek(0, 0)
  21. f.write(
  22. b"""# DO NOT MODIFY. This file was generated with `make freeze-requirements`.
  23. """
  24. + content
  25. )
  26. def check_futures(futures: list[Future[None]]) -> int:
  27. rc = 0
  28. for future in futures:
  29. try:
  30. future.result()
  31. except CalledProcessError as e:
  32. rc = 1
  33. print(
  34. f"""`{e.cmd}` returned code {e.returncode}
  35. stdout:
  36. {e.stdout.decode()}
  37. stderr:
  38. {e.stderr.decode()}
  39. """
  40. )
  41. return rc
  42. def main(argv: Sequence[str] | None = None) -> int:
  43. parser = argparse.ArgumentParser()
  44. parser.add_argument("repo", type=str, help="Repository name.")
  45. args = parser.parse_args(argv)
  46. repo = args.repo
  47. base_path = abspath(gitroot())
  48. base_cmd = (
  49. "pip-compile",
  50. "--allow-unsafe",
  51. "--no-annotate",
  52. "--no-header",
  53. "--quiet",
  54. "--strip-extras",
  55. "--index-url=https://pypi.devinfra.sentry.io/simple",
  56. )
  57. executor = ThreadPoolExecutor(max_workers=2)
  58. futures = []
  59. if repo == "sentry":
  60. futures.append(
  61. executor.submit(
  62. worker,
  63. (
  64. *base_cmd,
  65. f"{base_path}/requirements-base.txt",
  66. f"{base_path}/requirements-getsentry.txt",
  67. "-o",
  68. f"{base_path}/requirements-frozen.txt",
  69. ),
  70. )
  71. )
  72. futures.append(
  73. executor.submit(
  74. worker,
  75. (
  76. *base_cmd,
  77. f"{base_path}/requirements-base.txt",
  78. f"{base_path}/requirements-getsentry.txt",
  79. f"{base_path}/requirements-dev.txt",
  80. "-o",
  81. f"{base_path}/requirements-dev-frozen.txt",
  82. ),
  83. )
  84. )
  85. elif repo == "getsentry":
  86. futures.append(
  87. executor.submit(
  88. worker,
  89. (
  90. *base_cmd,
  91. f"{base_path}/requirements-base.txt",
  92. # This is downloaded with bin/bump-sentry.
  93. f"{base_path}/sentry-requirements-frozen.txt",
  94. "-o",
  95. f"{base_path}/requirements-frozen.txt",
  96. ),
  97. )
  98. )
  99. # getsentry shares sentry's requirements-dev.
  100. futures.append(
  101. executor.submit(
  102. worker,
  103. (
  104. *base_cmd,
  105. f"{base_path}/requirements-base.txt",
  106. # This is downloaded with bin/bump-sentry.
  107. f"{base_path}/sentry-requirements-dev-frozen.txt",
  108. "-o",
  109. f"{base_path}/requirements-dev-frozen.txt",
  110. ),
  111. )
  112. )
  113. else:
  114. raise AssertionError(f"what repo? {repo=}")
  115. rc = check_futures(futures)
  116. executor.shutdown()
  117. return rc
  118. if __name__ == "__main__":
  119. raise SystemExit(main())