build_release 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env python3
  2. # Copyright 2023 The ChromiumOS Authors
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import argparse
  6. import json
  7. import os
  8. import subprocess
  9. import sys
  10. from itertools import chain, product, starmap
  11. from pathlib import Path
  12. from typing import Dict, Iterable, List, NamedTuple
  13. from impl.common import CROSVM_ROOT, Triple, verbose
  14. from impl.test_config import DO_NOT_BUILD_RISCV64
  15. USAGE = """\
  16. Build crosvm with release (optimized) profile.
  17. To target local machine:
  18. $ ./tools/build_release
  19. To cross-compile for aarch64, armhf or windows you can use:
  20. $ ./tools/build_release --platform=aarch64
  21. $ ./tools/build_release --platform=armhf
  22. $ ./tools/build_release --platform=mingw64
  23. """
  24. # We only need PGO for main binary, but for consistency, only exclude incompatible parts from PGO
  25. PGO_EXCLUDE = ["crosvm_control"]
  26. class Executable(NamedTuple):
  27. """Container for info about an executable generated by cargo build/test."""
  28. binary_path: Path
  29. crate_name: str
  30. cargo_target: str
  31. kind: str
  32. is_test: bool
  33. is_fresh: bool
  34. @property
  35. def name(self):
  36. return f"{self.crate_name}:{self.cargo_target}"
  37. def cargo(
  38. cargo_command: str,
  39. cwd: Path,
  40. flags: List[str],
  41. env: Dict[str, str],
  42. ) -> Iterable[Executable]:
  43. """
  44. Executes a cargo command and returns the list of test binaries generated.
  45. The build log will be hidden by default and only printed if the build
  46. fails. In VERBOSE mode the output will be streamed directly.
  47. Note: Exits the program if the build fails.
  48. """
  49. message_format = "json-diagnostic-rendered-ansi" if sys.stdout.isatty() else "json"
  50. cmd = [
  51. "cargo",
  52. cargo_command,
  53. f"--message-format={message_format}",
  54. *flags,
  55. ]
  56. if verbose():
  57. print("$", " ".join(cmd))
  58. process = subprocess.Popen(
  59. cmd,
  60. cwd=cwd,
  61. stdout=subprocess.PIPE,
  62. stderr=subprocess.STDOUT,
  63. text=True,
  64. env=env,
  65. )
  66. messages: List[str] = []
  67. # Read messages as cargo is running.
  68. assert process.stdout
  69. for line in iter(process.stdout.readline, ""):
  70. # any non-json line is a message to print
  71. if not line.startswith("{"):
  72. if verbose():
  73. print(line.rstrip())
  74. messages.append(line.rstrip())
  75. continue
  76. json_line = json.loads(line)
  77. # 'message' type lines will be printed
  78. if json_line.get("message"):
  79. message = json_line.get("message").get("rendered")
  80. if verbose():
  81. print(message)
  82. messages.append(message)
  83. # Collect info about test executables produced
  84. elif json_line.get("executable"):
  85. yield Executable(
  86. Path(json_line.get("executable")),
  87. crate_name=json_line.get("package_id", "").split(" ")[0],
  88. cargo_target=json_line.get("target").get("name"),
  89. kind=json_line.get("target").get("kind")[0],
  90. is_test=json_line.get("profile", {}).get("test", False),
  91. is_fresh=json_line.get("fresh", False),
  92. )
  93. if process.wait() != 0:
  94. if not verbose():
  95. for message in messages:
  96. print(message)
  97. sys.exit(-1)
  98. def main():
  99. parser = argparse.ArgumentParser(usage=USAGE)
  100. parser.add_argument(
  101. "--build-target",
  102. "--platform",
  103. "-p",
  104. help=(
  105. "Override the cargo triple to build. Shorthands are available: (x86_64, armhf, "
  106. + "aarch64, mingw64, msvc64)."
  107. ),
  108. )
  109. parser.add_argument(
  110. "--json",
  111. action="store_true",
  112. help="Output in JSON instead of human readable format.",
  113. )
  114. parser.add_argument("--strip", action="store_true", help="Strip output binaries")
  115. parser.add_argument(
  116. "--build-profile", help="Select compile profile, default to release.", default="release"
  117. )
  118. pgo_group = parser.add_mutually_exclusive_group()
  119. pgo_group.add_argument(
  120. "--profile-generate",
  121. help="Target directory to generate profile when running, must use absolute path",
  122. )
  123. pgo_group.add_argument(
  124. "--profile-use", help="Profile file used for PGO, must use absolute path"
  125. )
  126. parser.add_argument("cargo_arguments", nargs="*", help="Extra arguments pass to cargo")
  127. args = parser.parse_args()
  128. if args.profile_generate and (
  129. not os.path.isabs(args.profile_generate) or not os.path.isdir(args.profile_generate)
  130. ):
  131. raise ValueError("--profile-generate argument is not an absolute path to a folder")
  132. if args.profile_use and (
  133. not os.path.isabs(args.profile_use) or not os.path.isfile(args.profile_use)
  134. ):
  135. raise ValueError("--profile-use argument is not an absolute path to a file")
  136. build_target = Triple.from_shorthand(args.build_target) if args.build_target else None
  137. build_target = build_target or Triple.host_default()
  138. exclude_args = [
  139. f"--exclude={x}" for x in PGO_EXCLUDE if args.profile_generate or args.profile_use
  140. ]
  141. if build_target == Triple.from_shorthand("riscv64"):
  142. exclude_args += ["--exclude=" + s for s in DO_NOT_BUILD_RISCV64]
  143. features = build_target.feature_flag
  144. cargo_args = [
  145. "--profile",
  146. args.build_profile,
  147. "--features=" + features,
  148. f"--target={build_target}",
  149. "--workspace",
  150. *exclude_args,
  151. *args.cargo_arguments,
  152. ]
  153. build_env = os.environ.copy()
  154. build_env.update(build_target.get_cargo_env())
  155. build_env.setdefault("RUSTFLAGS", "")
  156. build_env["RUSTFLAGS"] += " -D warnings"
  157. if args.strip:
  158. build_env["RUSTFLAGS"] += " -C strip=symbols"
  159. if args.profile_generate:
  160. build_env["RUSTFLAGS"] += " -C profile-generate=" + args.profile_generate
  161. if args.profile_use:
  162. build_env["RUSTFLAGS"] += " -C profile-use=" + args.profile_use
  163. executables = list(cargo("build", CROSVM_ROOT, cargo_args, build_env))
  164. if args.json:
  165. result = {}
  166. for exe in executables:
  167. assert exe.cargo_target not in result
  168. result[exe.cargo_target] = str(exe.binary_path)
  169. print(json.dumps(result))
  170. else:
  171. print("Release binaries:")
  172. for exe in executables:
  173. print(f"Name: {exe.cargo_target}")
  174. print(f"Path: {str(exe.binary_path)}")
  175. print()
  176. if __name__ == "__main__":
  177. main()