build_with_bazel.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #!/usr/bin/env python
  2. # SPDX-License-Identifier: GPL-2.0-only
  3. # Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
  4. import argparse
  5. import errno
  6. import glob
  7. import logging
  8. import os
  9. import re
  10. import sys
  11. import subprocess
  12. import stat
  13. HOST_TARGETS = ["dtc"]
  14. DEFAULT_SKIP_LIST = ["abi"]
  15. MSM_EXTENSIONS = "build/msm_kernel_extensions.bzl"
  16. DEFAULT_MSM_EXTENSIONS_SRC = "../msm-kernel/msm_kernel_extensions.bzl"
  17. DEFAULT_OUT_DIR = "{workspace}/out/msm-kernel-{target}-{variant}"
  18. class Target:
  19. def __init__(self, workspace, target, variant, bazel_label, out_dir=None):
  20. self.workspace = workspace
  21. self.target = target
  22. self.variant = variant
  23. self.bazel_label = bazel_label
  24. self.out_dir = out_dir
  25. def __lt__(self, other):
  26. return len(self.bazel_label) < len(other.bazel_label)
  27. def get_out_dir(self, suffix=None):
  28. if self.out_dir:
  29. out_dir = self.out_dir
  30. else:
  31. # Mirror the logic in msm_common.bzl:get_out_dir()
  32. if "allyes" in self.target:
  33. target_norm = self.target.replace("_", "-")
  34. else:
  35. target_norm = self.target.replace("-", "_")
  36. variant_norm = self.variant.replace("-", "_")
  37. out_dir = DEFAULT_OUT_DIR.format(
  38. workspace = self.workspace, target=target_norm, variant=variant_norm
  39. )
  40. if suffix:
  41. return os.path.join(out_dir, suffix)
  42. else:
  43. return out_dir
  44. class BazelBuilder:
  45. """Helper class for building with Bazel"""
  46. def __init__(self, target_list, skip_list, out_dir, dry_run, module, project, variant, user_opts):
  47. self.workspace = os.path.realpath(
  48. os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
  49. )
  50. self.bazel_bin = os.path.join(self.workspace, "tools", "bazel")
  51. if not os.path.exists(self.bazel_bin):
  52. logging.error("failed to find Bazel binary at %s", self.bazel_bin)
  53. sys.exit(1)
  54. self.kernel_dir = os.path.basename(
  55. (os.path.dirname(os.path.realpath(__file__)))
  56. )
  57. for t, v in target_list:
  58. if not t or not v:
  59. logging.error("invalid target_variant combo \"%s_%s\"", t, v)
  60. sys.exit(1)
  61. self.target_list = target_list
  62. self.skip_list = skip_list
  63. self.dry_run = dry_run
  64. self.user_opts = user_opts
  65. self.process_list = []
  66. if len(self.target_list) > 1 and out_dir:
  67. logging.error("cannot specify multiple targets with one out dir")
  68. sys.exit(1)
  69. else:
  70. self.out_dir = out_dir
  71. self.module = module
  72. self.project = project
  73. self.variant = variant
  74. self.setup_extensions()
  75. self.change_file_permission()
  76. self.prepare_sec_env_files()
  77. def __del__(self):
  78. for proc in self.process_list:
  79. try:
  80. proc.kill()
  81. proc.wait()
  82. except OSError:
  83. pass
  84. def setup_extensions(self):
  85. """Set up the extension files if needed"""
  86. for (ext, def_src) in [
  87. (MSM_EXTENSIONS, DEFAULT_MSM_EXTENSIONS_SRC),
  88. ]:
  89. ext_path = os.path.join(self.workspace, ext)
  90. # If the file doesn't exist or is a dead link, link to the default
  91. try:
  92. os.stat(ext_path)
  93. except OSError as e:
  94. if e.errno == errno.ENOENT:
  95. logging.info(
  96. "%s does not exist or is a broken symlink... linking to default at %s",
  97. ext,
  98. def_src,
  99. )
  100. if os.path.islink(ext_path):
  101. os.unlink(ext_path)
  102. os.symlink(def_src, ext_path)
  103. else:
  104. raise e
  105. def get_build_targets(self):
  106. """Query for build targets"""
  107. logging.info("Querying build targets...")
  108. targets = []
  109. for t, v in self.target_list:
  110. if v == "ALL":
  111. if self.out_dir:
  112. logging.error("cannot specify multiple targets (ALL variants) with one out dir")
  113. sys.exit(1)
  114. skip_list_re = [
  115. re.compile(r"//{}:{}_.*_{}_dist".format(self.kernel_dir, t, s))
  116. for s in self.skip_list
  117. ]
  118. query = 'filter("{}_.*_dist$", attr(generator_function, define_msm_platforms, {}/...))'.format(
  119. t, self.kernel_dir
  120. )
  121. else:
  122. skip_list_re = [
  123. re.compile(r"//{}:{}_{}_{}_dist".format(self.kernel_dir, t, v, s))
  124. for s in self.skip_list
  125. ]
  126. if self.module:
  127. query = 'filter("{}_{}_{}.*_dist$", attr(generator_function, define_msm_platforms, {}/...))'.format(
  128. t, v, self.module, self.kernel_dir
  129. )
  130. else:
  131. query = 'filter("{}_{}.*_dist$", attr(generator_function, define_msm_platforms, {}/...))'.format(
  132. t, v, self.kernel_dir
  133. )
  134. cmdline = [
  135. self.bazel_bin,
  136. "query",
  137. "--ui_event_filters=-info",
  138. query,
  139. ]
  140. logging.debug('Running "%s"', " ".join(cmdline))
  141. try:
  142. query_cmd = subprocess.Popen(
  143. cmdline, cwd=self.workspace, stdout=subprocess.PIPE
  144. )
  145. self.process_list.append(query_cmd)
  146. label_list = [l.decode("utf-8") for l in query_cmd.stdout.read().splitlines()]
  147. except Exception as e:
  148. logging.error(e)
  149. sys.exit(1)
  150. self.process_list.remove(query_cmd)
  151. if not label_list:
  152. logging.error(
  153. "failed to find any Bazel targets for target/variant combo %s_%s",
  154. t,
  155. v,
  156. )
  157. sys.exit(1)
  158. for label in label_list:
  159. if any((skip_re.match(label) for skip_re in skip_list_re)):
  160. continue
  161. if v == "ALL":
  162. real_variant = re.search(
  163. r"//{}:{}_([^_]+)_".format(self.kernel_dir, t), label
  164. ).group(1)
  165. else:
  166. real_variant = v
  167. targets.append(
  168. Target(self.workspace, t, real_variant, label, self.out_dir)
  169. )
  170. # Sort build targets by label string length to guarantee the base target goes
  171. # first when copying to output directory
  172. targets.sort()
  173. return targets
  174. def clean_legacy_generated_files(self):
  175. """Clean generated files from legacy build to avoid conflicts with Bazel"""
  176. for f in glob.glob("{}/msm-kernel/arch/arm64/configs/vendor/*_defconfig".format(self.workspace)):
  177. os.remove(f)
  178. def bazel(
  179. self,
  180. bazel_subcommand,
  181. targets,
  182. extra_options=None,
  183. bazel_target_opts=None,
  184. ):
  185. """Execute a bazel command"""
  186. cmdline = [self.bazel_bin, bazel_subcommand]
  187. if extra_options:
  188. cmdline.extend(extra_options)
  189. cmdline.extend([t.bazel_label for t in targets])
  190. if bazel_target_opts is not None:
  191. cmdline.extend(["--"] + bazel_target_opts)
  192. cmdline_str = " ".join(cmdline)
  193. try:
  194. logging.info('Running "%s"', cmdline_str)
  195. build_proc = subprocess.Popen(cmdline_str, cwd=self.workspace, shell=True)
  196. self.process_list.append(build_proc)
  197. build_proc.wait()
  198. if build_proc.returncode != 0:
  199. sys.exit(build_proc.returncode)
  200. except Exception as e:
  201. logging.error(e)
  202. sys.exit(1)
  203. self.process_list.remove(build_proc)
  204. def build_targets(self, targets):
  205. """Run "bazel build" on all targets in parallel"""
  206. self.bazel("build", targets, extra_options=self.user_opts)
  207. def run_targets(self, targets):
  208. """Run "bazel run" on all targets in serial (since bazel run cannot have multiple targets)"""
  209. for target in targets:
  210. # Set the output directory based on if it's a host target
  211. if any(
  212. re.match(r"//{}:.*_{}_dist".format(self.kernel_dir, h), target.bazel_label)
  213. for h in HOST_TARGETS
  214. ):
  215. out_dir = target.get_out_dir("host")
  216. else:
  217. out_dir = target.get_out_dir("dist")
  218. self.bazel(
  219. "run",
  220. [target],
  221. extra_options=self.user_opts,
  222. bazel_target_opts=["--dist_dir", out_dir]
  223. )
  224. self.write_opts(out_dir)
  225. def run_menuconfig(self):
  226. """Run menuconfig on all target-variant combos class is initialized with"""
  227. for t, v in self.target_list:
  228. menuconfig_label = "//{}:{}_{}_config".format(self.kernel_dir, t, v)
  229. menuconfig_target = [Target(self.workspace, t, v, menuconfig_label, self.out_dir)]
  230. self.bazel("run", menuconfig_target, bazel_target_opts=["menuconfig"])
  231. def write_opts(self, out_dir):
  232. with open(os.path.join(out_dir, "build_opts.txt"), "w") as opt_file:
  233. if self.user_opts:
  234. opt_file.write("{}".format("\n".join(self.user_opts)))
  235. opt_file.write("\n")
  236. def build(self):
  237. """Determine which targets to build, then build them"""
  238. targets_to_build = self.get_build_targets()
  239. if not targets_to_build:
  240. logging.error("no targets to build")
  241. sys.exit(1)
  242. if self.skip_list:
  243. self.user_opts.extend(["--//msm-kernel:skip_{}=true".format(s) for s in self.skip_list])
  244. self.user_opts.extend([
  245. "--user_kmi_symbol_lists=//msm-kernel:android/abi_gki_aarch64_qcom",
  246. "--ignore_missing_projects",
  247. ])
  248. if self.dry_run:
  249. self.user_opts.append("--nobuild")
  250. logging.debug(
  251. "Building the following targets:\n%s",
  252. "\n".join([t.bazel_label for t in targets_to_build])
  253. )
  254. self.clean_legacy_generated_files()
  255. logging.info("Building targets...")
  256. self.build_targets(targets_to_build)
  257. if not self.dry_run:
  258. self.run_targets(targets_to_build)
  259. def change_file_permission(self):
  260. # NOTE: ES1 Pre-Release - assign u+w attribute to
  261. file_permission = {
  262. os.path.join(self.workspace, "common", "android", "abi_gki_aarch64_qcom"): stat.S_IREAD | stat.S_IWRITE,
  263. os.path.join(self.workspace, "msm-kernel", "modules.vendor_blocklist.msm.*"): stat.S_IREAD | stat.S_IWRITE,
  264. }
  265. for name, permission in file_permission.items():
  266. for filename in glob.glob(name):
  267. os.chmod(filename, permission)
  268. def prepare_sec_env_files(self):
  269. self.sec_env_keys = {
  270. "LOCALVERSION": 1,
  271. "TARGET_BUILD_VARIANT": 1,
  272. "BUILD_NUMBER": 1,
  273. "CL_SYNC": 1,
  274. "SEC_FACTORY_BUILD":1,
  275. "SEC_BUILD_OPTION_DEBUG_LEVEL": 1,
  276. "SEC_BUILD_OPTION_CARRIER_ID": 1,
  277. "SEC_BUILD_OPTION_KNOX_CSB": 1,
  278. "SEC_BUILD_CONF_MODEL_SIGNING_NAME": 1,
  279. "SEC_BUILD_OPTION_EXTRA_BUILD_CONFIG": 1,
  280. "PROJECT_REGION": 1,
  281. "SEC_QUICKBUILD_ID": 1,
  282. }
  283. self.write_sec_env_mk_foramt(os.path.join(self.workspace, "common", "scripts", "sec_env.mk"))
  284. self.write_sec_env_mk_foramt(os.path.join(self.workspace, "msm-kernel", "scripts", "sec_env.mk"))
  285. self.write_sec_env_sh_foramt(os.path.join(self.workspace, "msm-kernel", "build.config.sec_env"))
  286. def write_sec_env_sh_foramt(
  287. self,
  288. filename
  289. ):
  290. f = open(filename, 'w')
  291. for key, value in os.environ.items():
  292. if key in self.sec_env_keys:
  293. data = "{}={}\n".format(key, value)
  294. f.write(data)
  295. f.close()
  296. def write_sec_env_mk_foramt(
  297. self,
  298. filename
  299. ):
  300. f = open(filename, 'w')
  301. for key, value in os.environ.items():
  302. if key in self.sec_env_keys:
  303. data = "{}:={}\n".format(key, value)
  304. f.write(data)
  305. f.close()
  306. def main():
  307. """Main script entrypoint"""
  308. parser = argparse.ArgumentParser(description="Build kernel platform with Bazel")
  309. parser.add_argument(
  310. "-t",
  311. "--target",
  312. metavar=("TARGET", "VARIANT"),
  313. action="append",
  314. nargs=2,
  315. required=True,
  316. help='Target and variant to build (e.g. -t kalama gki). May be passed multiple times. A special VARIANT may be passed, "ALL", which will build all variants for a particular target',
  317. )
  318. parser.add_argument(
  319. "-s",
  320. "--skip",
  321. metavar="BUILD_RULE",
  322. action="append",
  323. default=[],
  324. help="Skip specific build rules (e.g. --skip abl will skip the //msm-kernel:<target>_<variant>_abl build)",
  325. )
  326. parser.add_argument(
  327. "-o",
  328. "--out_dir",
  329. metavar="OUT_DIR",
  330. help='Specify the output distribution directory (by default, "$PWD/out/msm-kernel-<target>-variant")',
  331. )
  332. parser.add_argument(
  333. "--log",
  334. metavar="LEVEL",
  335. default="debug",
  336. choices=["debug", "info", "warning", "error"],
  337. help="Log level (debug, info, warning, error)",
  338. )
  339. parser.add_argument(
  340. "-c",
  341. "--menuconfig",
  342. action="store_true",
  343. help="Run menuconfig for <target>-<variant> and exit without building",
  344. )
  345. parser.add_argument(
  346. "-d",
  347. "--dry-run",
  348. action="store_true",
  349. help="Perform a dry-run of the build which will perform loading/analysis of build files",
  350. )
  351. parser.add_argument(
  352. "-m",
  353. "--module",
  354. metavar="MODULE",
  355. help="Specify the build module (e.g. --module abl will only process the //msm-kernel:<target>_<variant>_abl build)",
  356. )
  357. parser.add_argument(
  358. "-p",
  359. "--project",
  360. metavar="PROJECT",
  361. help="Specify the project fullname (e.g. --project mu3q_usa_singlew)",
  362. )
  363. parser.add_argument(
  364. "-v",
  365. "--variant",
  366. metavar="VARIANT",
  367. default="eng",
  368. help="Specify the build variant (e.g. --variant eng)",
  369. )
  370. args, user_opts = parser.parse_known_args(sys.argv[1:])
  371. logging.basicConfig(
  372. level=getattr(logging, args.log.upper()),
  373. format="[{}] %(levelname)s: %(message)s".format(os.path.basename(sys.argv[0])),
  374. )
  375. args.skip.extend(DEFAULT_SKIP_LIST)
  376. builder = BazelBuilder(args.target, args.skip, args.out_dir, args.dry_run, args.module, args.project, args.variant, user_opts)
  377. try:
  378. if args.menuconfig:
  379. builder.run_menuconfig()
  380. else:
  381. builder.build()
  382. except KeyboardInterrupt:
  383. logging.info("Received keyboard interrupt... exiting")
  384. del builder
  385. sys.exit(1)
  386. if args.dry_run:
  387. logging.info("Dry-run completed successfully!")
  388. else:
  389. logging.info("Build completed successfully!")
  390. if __name__ == "__main__":
  391. main()