run_bazel.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #!/usr/bin/env python3
  2. """Runs bazel on arguments.
  3. This is provided for other scripts to run bazel without requiring it be
  4. manually installed.
  5. """
  6. __copyright__ = """
  7. Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  8. Exceptions. See /LICENSE for license information.
  9. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  10. """
  11. import argparse
  12. import subprocess
  13. import sys
  14. import time
  15. import scripts_utils
  16. def main() -> None:
  17. parser = argparse.ArgumentParser(description="Runs bazel.")
  18. parser.add_argument(
  19. "--attempts",
  20. metavar="COUNT",
  21. type=int,
  22. default=1,
  23. help="The number of attempts to execute the command, automatically "
  24. "retrying errors that may be transient.",
  25. )
  26. parser.add_argument(
  27. "--jobs-on-last-attempt",
  28. metavar="COUNT",
  29. type=int,
  30. help="Sets the number of jobs in user.bazelrc on the last attempt. If "
  31. "there is only one attempt, this will be set immediately.",
  32. )
  33. parser.add_argument(
  34. "--retry-all-errors",
  35. action="store_true",
  36. help="Retries permanent errors in addition to transient.",
  37. )
  38. script_args, bazel_args = parser.parse_known_args()
  39. bazel = scripts_utils.locate_bazel()
  40. attempt = 0
  41. while True:
  42. attempt += 1
  43. if attempt == script_args.attempts and script_args.jobs_on_last_attempt:
  44. with open("user.bazelrc", "a") as bazelrc:
  45. bazelrc.write(
  46. f"build --jobs={script_args.jobs_on_last_attempt}\n"
  47. )
  48. p = subprocess.run([bazel] + bazel_args)
  49. # If this was the last attempt, or it succeeded, we're done.
  50. if attempt == script_args.attempts or p.returncode == 0:
  51. exit(p.returncode)
  52. # Several error codes are reliably permanent, break immediately.
  53. # `1` -- The build failed.
  54. # `2` -- Command line or environment problem.
  55. # `3` -- Tests failed or timed out, we don't retry at this layer
  56. # on execution timeout.
  57. # `4` -- Test command but no tests found.
  58. # `8` -- Explicitly interrupted build.
  59. #
  60. # Note that `36` is documented as "likely permanent", but we retry
  61. # it as most of our transient failures actually produce that error
  62. # code.
  63. perm_error = (1, 2, 3, 4, 8)
  64. if not script_args.retry_all_errors and p.returncode in perm_error:
  65. exit(p.returncode)
  66. print(
  67. f"Retrying exit code {p.returncode} because it may be transient..."
  68. )
  69. # Also sleep a bit to try to skip over transient machine load.
  70. time.sleep(attempt)
  71. if __name__ == "__main__":
  72. try:
  73. main()
  74. except KeyboardInterrupt:
  75. sys.exit(1)