bazel.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. # Copyright Materialize, Inc. and contributors. All rights reserved.
  2. #
  3. # Use of this software is governed by the Business Source License
  4. # included in the LICENSE file at the root of this repository.
  5. #
  6. # As of the Change Date specified in that file, in accordance with
  7. # the Business Source License, use of this software will be governed
  8. # by the Apache License, Version 2.0.
  9. #
  10. # bazel.py — build and test with bazel
  11. import argparse
  12. import os
  13. import pathlib
  14. import subprocess
  15. from pathlib import Path
  16. from materialize import MZ_ROOT, bazel, ui
  17. from materialize.bazel import remote_cache_arg
  18. from materialize.build_config import BuildConfig
  19. def main() -> int:
  20. parser = argparse.ArgumentParser(
  21. prog="bazel",
  22. description="Build, run, test, and generate packages with Bazel.",
  23. )
  24. parser.add_argument("action", help="Action to run.")
  25. (args, sub_args) = parser.parse_known_args()
  26. config = BuildConfig.read()
  27. # Always update our side-channel git hash incase some command needs it.
  28. bazel.write_git_hash()
  29. if args.action == "gen":
  30. gen_cmd(config, sub_args)
  31. elif args.action == "fmt":
  32. fmt_cmd(config, sub_args)
  33. elif args.action == "output_path":
  34. output_path_cmd(config, sub_args)
  35. elif args.action == "check":
  36. check_cmd(config, sub_args)
  37. elif args.action == "integrity":
  38. integrity_cmd(config, sub_args)
  39. else:
  40. bazel_cmd(config, [args.action] + sub_args)
  41. return 0
  42. def check_cmd(config: BuildConfig, args: list[str]):
  43. """
  44. Invokes a `bazel build` with `cargo check` like behavior.
  45. Still experimental, is known to fail with crates that have pipelined compilation explicitly
  46. disabled.
  47. """
  48. check_args = ["build", "--config=check", *args]
  49. bazel_cmd(config, check_args)
  50. def gen_cmd(config: BuildConfig, args: list[str]):
  51. """Invokes the gen function."""
  52. parser = argparse.ArgumentParser(
  53. prog="gen", description="Generate BUILD.bazel files."
  54. )
  55. parser.add_argument("--check", action="store_true")
  56. parser.add_argument(
  57. "path",
  58. type=pathlib.Path,
  59. help="Path to a Cargo.toml file to generate a BUILD.bazel for.",
  60. nargs="?",
  61. )
  62. gen_args = parser.parse_args(args=args)
  63. if gen_args.path:
  64. path = Path(os.path.abspath(gen_args.path))
  65. else:
  66. path = None
  67. gen(config, path, gen_args.check)
  68. def fmt_cmd(config: BuildConfig, args: list[str]):
  69. """Invokes the fmt function."""
  70. assert len(args) <= 1, "expected at most one path to format"
  71. path = args[0] if len(args) == 1 else None
  72. fmt(config, path)
  73. def output_path_cmd(config: BuildConfig, args: list[str]):
  74. """Invokes the output_path function."""
  75. assert len(args) == 1, "expected a single Bazel target"
  76. target = args[0]
  77. paths = bazel.output_paths(target)
  78. for path in paths:
  79. print(path)
  80. def integrity_cmd(config: BuildConfig, args: list[str]):
  81. """Calculate the integrity value for a file."""
  82. if args[0] == "toolchains":
  83. assert len(args) == 3, "expected <stable version> <nightly version>"
  84. stable = args[1]
  85. nightly = args[2]
  86. hashes = bazel.toolchain_hashes(stable, nightly)
  87. print(hashes)
  88. else:
  89. for path in args:
  90. integrity = bazel.calc_ingerity(path)
  91. print(integrity)
  92. def bazel_cmd(config: BuildConfig, args: list[str]):
  93. """Forwards all arguments to Bazel, possibly with extra configuration."""
  94. remote_cache = remote_cache_arg(config)
  95. try:
  96. subprocess.run(["bazel", *args, *remote_cache], check=True)
  97. except:
  98. # Don't print any python backtrace because it's never useful. Instead
  99. # just exit the process.
  100. exit(1)
  101. def gen(config: BuildConfig, path: Path | None, check: bool):
  102. """
  103. Generates BUILD.bazel files from Cargo.toml.
  104. Defaults to generating for the entire Cargo Workspace, or only a single
  105. Cargo.toml, if a path is provided.
  106. """
  107. if not path:
  108. path = MZ_ROOT / "Cargo.toml"
  109. check_arg = []
  110. if check:
  111. check_arg += ["--check"]
  112. remote_cache = remote_cache_arg(config)
  113. cmd_args = [
  114. "bazel",
  115. "run",
  116. *remote_cache,
  117. # TODO(parkmycar): Once bin/bazel gen is more stable in CI, enable this
  118. # config to make the output less noisy.
  119. # "--config=script",
  120. "//misc/bazel/tools:cargo-gazelle",
  121. "--",
  122. *check_arg,
  123. f"{str(path)}",
  124. ]
  125. subprocess.run(cmd_args, check=True)
  126. def fmt(config: BuildConfig, path):
  127. """
  128. Formats all of the `BUILD`, `.bzl`, and `WORKSPACE` files at the provided path.
  129. Defaults to formatting the entire Materialize repository.
  130. """
  131. if not path:
  132. path = MZ_ROOT
  133. if subprocess.run(["which", "bazel"]).returncode != 0:
  134. ui.warn("couldn't find 'bazel' skipping formatting of BUILD files")
  135. return
  136. # Note: No remote cache is needed here since we're just running an already
  137. # built binary.
  138. cmd_args = [
  139. "bazel",
  140. "run",
  141. "--config=script",
  142. "//misc/bazel/tools:buildifier",
  143. "--",
  144. "-r",
  145. f"{str(path)}",
  146. ]
  147. subprocess.run(cmd_args, check=True)
  148. def output_path(target) -> list[pathlib.Path]:
  149. """Returns the absolute path of the Bazel target."""
  150. cmd_args = ["bazel", "cquery", f"{target}", "--output=files"]
  151. paths = subprocess.check_output(
  152. cmd_args, text=True, stderr=subprocess.DEVNULL
  153. ).splitlines()
  154. return [pathlib.Path(path) for path in paths]
  155. if __name__ == "__main__":
  156. main()