123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- #!/usr/bin/env python
- # SPDX-License-Identifier: GPL-2.0-only
- # Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
- import argparse
- import errno
- import glob
- import logging
- import os
- import re
- import sys
- import subprocess
- import stat
- HOST_TARGETS = ["dtc"]
- DEFAULT_SKIP_LIST = ["abi"]
- MSM_EXTENSIONS = "build/msm_kernel_extensions.bzl"
- DEFAULT_MSM_EXTENSIONS_SRC = "../msm-kernel/msm_kernel_extensions.bzl"
- DEFAULT_OUT_DIR = "{workspace}/out/msm-kernel-{target}-{variant}"
- class Target:
- def __init__(self, workspace, target, variant, bazel_label, out_dir=None):
- self.workspace = workspace
- self.target = target
- self.variant = variant
- self.bazel_label = bazel_label
- self.out_dir = out_dir
- def __lt__(self, other):
- return len(self.bazel_label) < len(other.bazel_label)
- def get_out_dir(self, suffix=None):
- if self.out_dir:
- out_dir = self.out_dir
- else:
- # Mirror the logic in msm_common.bzl:get_out_dir()
- if "allyes" in self.target:
- target_norm = self.target.replace("_", "-")
- else:
- target_norm = self.target.replace("-", "_")
- variant_norm = self.variant.replace("-", "_")
- out_dir = DEFAULT_OUT_DIR.format(
- workspace = self.workspace, target=target_norm, variant=variant_norm
- )
- if suffix:
- return os.path.join(out_dir, suffix)
- else:
- return out_dir
- class BazelBuilder:
- """Helper class for building with Bazel"""
- def __init__(self, target_list, skip_list, out_dir, dry_run, module, project, variant, user_opts):
- self.workspace = os.path.realpath(
- os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")
- )
- self.bazel_bin = os.path.join(self.workspace, "tools", "bazel")
- if not os.path.exists(self.bazel_bin):
- logging.error("failed to find Bazel binary at %s", self.bazel_bin)
- sys.exit(1)
- self.kernel_dir = os.path.basename(
- (os.path.dirname(os.path.realpath(__file__)))
- )
- for t, v in target_list:
- if not t or not v:
- logging.error("invalid target_variant combo \"%s_%s\"", t, v)
- sys.exit(1)
- self.target_list = target_list
- self.skip_list = skip_list
- self.dry_run = dry_run
- self.user_opts = user_opts
- self.process_list = []
- if len(self.target_list) > 1 and out_dir:
- logging.error("cannot specify multiple targets with one out dir")
- sys.exit(1)
- else:
- self.out_dir = out_dir
- self.module = module
- self.project = project
- self.variant = variant
- self.setup_extensions()
- self.change_file_permission()
- self.prepare_sec_env_files()
- def __del__(self):
- for proc in self.process_list:
- try:
- proc.kill()
- proc.wait()
- except OSError:
- pass
- def setup_extensions(self):
- """Set up the extension files if needed"""
- for (ext, def_src) in [
- (MSM_EXTENSIONS, DEFAULT_MSM_EXTENSIONS_SRC),
- ]:
- ext_path = os.path.join(self.workspace, ext)
- # If the file doesn't exist or is a dead link, link to the default
- try:
- os.stat(ext_path)
- except OSError as e:
- if e.errno == errno.ENOENT:
- logging.info(
- "%s does not exist or is a broken symlink... linking to default at %s",
- ext,
- def_src,
- )
- if os.path.islink(ext_path):
- os.unlink(ext_path)
- os.symlink(def_src, ext_path)
- else:
- raise e
- def get_build_targets(self):
- """Query for build targets"""
- logging.info("Querying build targets...")
- targets = []
- for t, v in self.target_list:
- if v == "ALL":
- if self.out_dir:
- logging.error("cannot specify multiple targets (ALL variants) with one out dir")
- sys.exit(1)
- skip_list_re = [
- re.compile(r"//{}:{}_.*_{}_dist".format(self.kernel_dir, t, s))
- for s in self.skip_list
- ]
- query = 'filter("{}_.*_dist$", attr(generator_function, define_msm_platforms, {}/...))'.format(
- t, self.kernel_dir
- )
- else:
- skip_list_re = [
- re.compile(r"//{}:{}_{}_{}_dist".format(self.kernel_dir, t, v, s))
- for s in self.skip_list
- ]
- if self.module:
- query = 'filter("{}_{}_{}.*_dist$", attr(generator_function, define_msm_platforms, {}/...))'.format(
- t, v, self.module, self.kernel_dir
- )
- else:
- query = 'filter("{}_{}.*_dist$", attr(generator_function, define_msm_platforms, {}/...))'.format(
- t, v, self.kernel_dir
- )
- cmdline = [
- self.bazel_bin,
- "query",
- "--ui_event_filters=-info",
- query,
- ]
- logging.debug('Running "%s"', " ".join(cmdline))
- try:
- query_cmd = subprocess.Popen(
- cmdline, cwd=self.workspace, stdout=subprocess.PIPE
- )
- self.process_list.append(query_cmd)
- label_list = [l.decode("utf-8") for l in query_cmd.stdout.read().splitlines()]
- except Exception as e:
- logging.error(e)
- sys.exit(1)
- self.process_list.remove(query_cmd)
- if not label_list:
- logging.error(
- "failed to find any Bazel targets for target/variant combo %s_%s",
- t,
- v,
- )
- sys.exit(1)
- for label in label_list:
- if any((skip_re.match(label) for skip_re in skip_list_re)):
- continue
- if v == "ALL":
- real_variant = re.search(
- r"//{}:{}_([^_]+)_".format(self.kernel_dir, t), label
- ).group(1)
- else:
- real_variant = v
- targets.append(
- Target(self.workspace, t, real_variant, label, self.out_dir)
- )
- # Sort build targets by label string length to guarantee the base target goes
- # first when copying to output directory
- targets.sort()
- return targets
- def clean_legacy_generated_files(self):
- """Clean generated files from legacy build to avoid conflicts with Bazel"""
- for f in glob.glob("{}/msm-kernel/arch/arm64/configs/vendor/*_defconfig".format(self.workspace)):
- os.remove(f)
- def bazel(
- self,
- bazel_subcommand,
- targets,
- extra_options=None,
- bazel_target_opts=None,
- ):
- """Execute a bazel command"""
- cmdline = [self.bazel_bin, bazel_subcommand]
- if extra_options:
- cmdline.extend(extra_options)
- cmdline.extend([t.bazel_label for t in targets])
- if bazel_target_opts is not None:
- cmdline.extend(["--"] + bazel_target_opts)
- cmdline_str = " ".join(cmdline)
- try:
- logging.info('Running "%s"', cmdline_str)
- build_proc = subprocess.Popen(cmdline_str, cwd=self.workspace, shell=True)
- self.process_list.append(build_proc)
- build_proc.wait()
- if build_proc.returncode != 0:
- sys.exit(build_proc.returncode)
- except Exception as e:
- logging.error(e)
- sys.exit(1)
- self.process_list.remove(build_proc)
- def build_targets(self, targets):
- """Run "bazel build" on all targets in parallel"""
- self.bazel("build", targets, extra_options=self.user_opts)
- def run_targets(self, targets):
- """Run "bazel run" on all targets in serial (since bazel run cannot have multiple targets)"""
- for target in targets:
- # Set the output directory based on if it's a host target
- if any(
- re.match(r"//{}:.*_{}_dist".format(self.kernel_dir, h), target.bazel_label)
- for h in HOST_TARGETS
- ):
- out_dir = target.get_out_dir("host")
- else:
- out_dir = target.get_out_dir("dist")
- self.bazel(
- "run",
- [target],
- extra_options=self.user_opts,
- bazel_target_opts=["--dist_dir", out_dir]
- )
- self.write_opts(out_dir)
- def run_menuconfig(self):
- """Run menuconfig on all target-variant combos class is initialized with"""
- for t, v in self.target_list:
- menuconfig_label = "//{}:{}_{}_config".format(self.kernel_dir, t, v)
- menuconfig_target = [Target(self.workspace, t, v, menuconfig_label, self.out_dir)]
- self.bazel("run", menuconfig_target, bazel_target_opts=["menuconfig"])
- def write_opts(self, out_dir):
- with open(os.path.join(out_dir, "build_opts.txt"), "w") as opt_file:
- if self.user_opts:
- opt_file.write("{}".format("\n".join(self.user_opts)))
- opt_file.write("\n")
- def build(self):
- """Determine which targets to build, then build them"""
- targets_to_build = self.get_build_targets()
- if not targets_to_build:
- logging.error("no targets to build")
- sys.exit(1)
- if self.skip_list:
- self.user_opts.extend(["--//msm-kernel:skip_{}=true".format(s) for s in self.skip_list])
- self.user_opts.extend([
- "--user_kmi_symbol_lists=//msm-kernel:android/abi_gki_aarch64_qcom",
- "--ignore_missing_projects",
- ])
- if self.dry_run:
- self.user_opts.append("--nobuild")
- logging.debug(
- "Building the following targets:\n%s",
- "\n".join([t.bazel_label for t in targets_to_build])
- )
- self.clean_legacy_generated_files()
- logging.info("Building targets...")
- self.build_targets(targets_to_build)
- if not self.dry_run:
- self.run_targets(targets_to_build)
- def change_file_permission(self):
- # NOTE: ES1 Pre-Release - assign u+w attribute to
- file_permission = {
- os.path.join(self.workspace, "common", "android", "abi_gki_aarch64_qcom"): stat.S_IREAD | stat.S_IWRITE,
- os.path.join(self.workspace, "msm-kernel", "modules.vendor_blocklist.msm.*"): stat.S_IREAD | stat.S_IWRITE,
- }
- for name, permission in file_permission.items():
- for filename in glob.glob(name):
- os.chmod(filename, permission)
- def prepare_sec_env_files(self):
- self.sec_env_keys = {
- "LOCALVERSION": 1,
- "TARGET_BUILD_VARIANT": 1,
- "BUILD_NUMBER": 1,
- "CL_SYNC": 1,
- "SEC_FACTORY_BUILD":1,
- "SEC_BUILD_OPTION_DEBUG_LEVEL": 1,
- "SEC_BUILD_OPTION_CARRIER_ID": 1,
- "SEC_BUILD_OPTION_KNOX_CSB": 1,
- "SEC_BUILD_CONF_MODEL_SIGNING_NAME": 1,
- "SEC_BUILD_OPTION_EXTRA_BUILD_CONFIG": 1,
- "PROJECT_REGION": 1,
- "SEC_QUICKBUILD_ID": 1,
- }
- self.write_sec_env_mk_foramt(os.path.join(self.workspace, "common", "scripts", "sec_env.mk"))
- self.write_sec_env_mk_foramt(os.path.join(self.workspace, "msm-kernel", "scripts", "sec_env.mk"))
- self.write_sec_env_sh_foramt(os.path.join(self.workspace, "msm-kernel", "build.config.sec_env"))
- def write_sec_env_sh_foramt(
- self,
- filename
- ):
- f = open(filename, 'w')
- for key, value in os.environ.items():
- if key in self.sec_env_keys:
- data = "{}={}\n".format(key, value)
- f.write(data)
- f.close()
- def write_sec_env_mk_foramt(
- self,
- filename
- ):
- f = open(filename, 'w')
- for key, value in os.environ.items():
- if key in self.sec_env_keys:
- data = "{}:={}\n".format(key, value)
- f.write(data)
- f.close()
- def main():
- """Main script entrypoint"""
- parser = argparse.ArgumentParser(description="Build kernel platform with Bazel")
- parser.add_argument(
- "-t",
- "--target",
- metavar=("TARGET", "VARIANT"),
- action="append",
- nargs=2,
- required=True,
- 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',
- )
- parser.add_argument(
- "-s",
- "--skip",
- metavar="BUILD_RULE",
- action="append",
- default=[],
- help="Skip specific build rules (e.g. --skip abl will skip the //msm-kernel:<target>_<variant>_abl build)",
- )
- parser.add_argument(
- "-o",
- "--out_dir",
- metavar="OUT_DIR",
- help='Specify the output distribution directory (by default, "$PWD/out/msm-kernel-<target>-variant")',
- )
- parser.add_argument(
- "--log",
- metavar="LEVEL",
- default="debug",
- choices=["debug", "info", "warning", "error"],
- help="Log level (debug, info, warning, error)",
- )
- parser.add_argument(
- "-c",
- "--menuconfig",
- action="store_true",
- help="Run menuconfig for <target>-<variant> and exit without building",
- )
- parser.add_argument(
- "-d",
- "--dry-run",
- action="store_true",
- help="Perform a dry-run of the build which will perform loading/analysis of build files",
- )
- parser.add_argument(
- "-m",
- "--module",
- metavar="MODULE",
- help="Specify the build module (e.g. --module abl will only process the //msm-kernel:<target>_<variant>_abl build)",
- )
- parser.add_argument(
- "-p",
- "--project",
- metavar="PROJECT",
- help="Specify the project fullname (e.g. --project mu3q_usa_singlew)",
- )
- parser.add_argument(
- "-v",
- "--variant",
- metavar="VARIANT",
- default="eng",
- help="Specify the build variant (e.g. --variant eng)",
- )
- args, user_opts = parser.parse_known_args(sys.argv[1:])
- logging.basicConfig(
- level=getattr(logging, args.log.upper()),
- format="[{}] %(levelname)s: %(message)s".format(os.path.basename(sys.argv[0])),
- )
- args.skip.extend(DEFAULT_SKIP_LIST)
- builder = BazelBuilder(args.target, args.skip, args.out_dir, args.dry_run, args.module, args.project, args.variant, user_opts)
- try:
- if args.menuconfig:
- builder.run_menuconfig()
- else:
- builder.build()
- except KeyboardInterrupt:
- logging.info("Received keyboard interrupt... exiting")
- del builder
- sys.exit(1)
- if args.dry_run:
- logging.info("Dry-run completed successfully!")
- else:
- logging.info("Build completed successfully!")
- if __name__ == "__main__":
- main()
|