sync.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. from __future__ import annotations
  2. import configparser
  3. import os
  4. import shlex
  5. import subprocess
  6. from devenv import constants
  7. from devenv.lib import colima, config, fs, limactl, proc, venv, volta
  8. # TODO: need to replace this with a nicer process executor in devenv.lib
  9. def run_procs(
  10. repo: str,
  11. reporoot: str,
  12. venv_path: str,
  13. _procs: tuple[tuple[str, tuple[str, ...]], ...],
  14. ) -> bool:
  15. procs: list[tuple[str, tuple[str, ...], subprocess.Popen[bytes]]] = []
  16. for name, cmd in _procs:
  17. print(f"⏳ {name}")
  18. if constants.DEBUG:
  19. proc.xtrace(cmd)
  20. procs.append(
  21. (
  22. name,
  23. cmd,
  24. subprocess.Popen(
  25. cmd,
  26. stdout=subprocess.PIPE,
  27. stderr=subprocess.STDOUT,
  28. env={
  29. **constants.user_environ,
  30. **proc.base_env,
  31. "VIRTUAL_ENV": venv_path,
  32. "VOLTA_HOME": f"{reporoot}/.devenv/bin/volta-home",
  33. "PATH": f"{venv_path}/bin:{reporoot}/.devenv/bin:{proc.base_path}",
  34. },
  35. cwd=reporoot,
  36. ),
  37. )
  38. )
  39. all_good = True
  40. for name, final_cmd, p in procs:
  41. out, _ = p.communicate()
  42. if p.returncode != 0:
  43. all_good = False
  44. print(
  45. f"""
  46. ❌ {name}
  47. failed command (code p.returncode):
  48. {shlex.join(final_cmd)}
  49. Output:
  50. {out.decode()}
  51. """
  52. )
  53. else:
  54. print(f"✅ {name}")
  55. return all_good
  56. def main(context: dict[str, str]) -> int:
  57. repo = context["repo"]
  58. reporoot = context["reporoot"]
  59. FRONTEND_ONLY = os.environ.get("SENTRY_DEVENV_FRONTEND_ONLY") is not None
  60. # venv's still needed for frontend because repo-local devenv and pre-commit
  61. # exist inside it
  62. venv_dir, python_version, requirements, editable_paths, bins = venv.get(reporoot, repo)
  63. url, sha256 = config.get_python(reporoot, python_version)
  64. print(f"ensuring {repo} venv at {venv_dir}...")
  65. venv.ensure(venv_dir, python_version, url, sha256)
  66. # TODO: move volta version into per-repo config
  67. try:
  68. volta.install(reporoot)
  69. except TypeError:
  70. # this is needed for devenv <=1.4.0,>1.2.3 to finish syncing and therefore update itself
  71. volta.install()
  72. if constants.DARWIN:
  73. repo_config = configparser.ConfigParser()
  74. repo_config.read(f"{reporoot}/devenv/config.ini")
  75. try:
  76. colima.install(
  77. repo_config["colima"]["version"],
  78. repo_config["colima"][constants.SYSTEM_MACHINE],
  79. repo_config["colima"][f"{constants.SYSTEM_MACHINE}_sha256"],
  80. reporoot,
  81. )
  82. except TypeError:
  83. # this is needed for devenv <=1.4.0,>1.2.3 to finish syncing and therefore update itself
  84. colima.install(
  85. repo_config["colima"]["version"],
  86. repo_config["colima"][constants.SYSTEM_MACHINE],
  87. repo_config["colima"][f"{constants.SYSTEM_MACHINE}_sha256"],
  88. )
  89. # TODO: move limactl version into per-repo config
  90. try:
  91. limactl.install(reporoot)
  92. except TypeError:
  93. # this is needed for devenv <=1.4.0,>1.2.3 to finish syncing and therefore update itself
  94. limactl.install()
  95. if not run_procs(
  96. repo,
  97. reporoot,
  98. venv_dir,
  99. (
  100. ("javascript dependencies", ("make", "install-js-dev")),
  101. # could opt out of syncing python if FRONTEND_ONLY but only if repo-local devenv
  102. # and pre-commit were moved to inside devenv and not the sentry venv
  103. ("python dependencies", ("make", "install-py-dev")),
  104. ),
  105. ):
  106. return 1
  107. if not run_procs(
  108. repo,
  109. reporoot,
  110. venv_dir,
  111. (("pre-commit dependencies", ("pre-commit", "install", "--install-hooks", "-f")),),
  112. ):
  113. return 1
  114. fs.ensure_symlink("../../config/hooks/post-merge", f"{reporoot}/.git/hooks/post-merge")
  115. if not os.path.exists(f"{constants.home}/.sentry/config.yml") or not os.path.exists(
  116. f"{constants.home}/.sentry/sentry.conf.py"
  117. ):
  118. proc.run((f"{venv_dir}/bin/sentry", "init", "--dev"))
  119. # Frontend engineers don't necessarily always have devservices running and
  120. # can configure to skip them to save on local resources
  121. if FRONTEND_ONLY:
  122. print("Skipping python migrations since SENTRY_DEVENV_FRONTEND_ONLY is set.")
  123. return 0
  124. # TODO: check healthchecks for redis and postgres to short circuit this
  125. proc.run(
  126. (
  127. f"{venv_dir}/bin/{repo}",
  128. "devservices",
  129. "up",
  130. "redis",
  131. "postgres",
  132. ),
  133. pathprepend=f"{reporoot}/.devenv/bin",
  134. exit=True,
  135. )
  136. if run_procs(
  137. repo,
  138. reporoot,
  139. venv_dir,
  140. (
  141. (
  142. "python migrations",
  143. ("make", "apply-migrations"),
  144. ),
  145. ),
  146. ):
  147. return 0
  148. return 1