| # Copyright 2023 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """cros build-packages updates the set of binary packages needed by ChromiumOS. |
| |
| The build-packages process cross compiles all packages that have been |
| updated into the given sysroot and builds binary packages as a side-effect. |
| The output packages will be used by `cros build-image` to create a bootable |
| ChromiumOS image. |
| |
| If packages are specified in the command line, only build those specific |
| packages and any dependencies they might need. |
| """ |
| |
| import argparse |
| import logging |
| import os |
| from typing import Optional |
| import urllib.error |
| import urllib.request |
| |
| from chromite.third_party.opentelemetry import trace |
| from chromite.third_party.opentelemetry.trace import status |
| |
| from chromite.cli import command |
| from chromite.lib import build_target_lib |
| from chromite.lib import chromite_config |
| from chromite.lib import commandline |
| from chromite.lib import cros_build_lib |
| from chromite.lib import sysroot_lib |
| from chromite.service import sysroot |
| from chromite.utils import telemetry |
| from chromite.utils import timer |
| |
| |
| def build_shell_bool_style_args( |
| parser: commandline.ArgumentParser, |
| name: str, |
| default_val: bool, |
| help_str: str, |
| deprecation_note: str, |
| alternate_name: Optional[str] = None, |
| ) -> None: |
| """Build the shell boolean input argument equivalent. |
| |
| There are two cases which we will need to handle, |
| case 1: A shell boolean arg, which doesn't need to be re-worded in python. |
| case 2: A shell boolean arg, which needs to be re-worded in python. |
| Example below. |
| For Case 1, for a given input arg name 'argA', we create three python |
| arguments. |
| --argA, --noargA, --no-argA. The arguments --argA and --no-argA will be |
| retained after deprecating --noargA. |
| For Case 2, for a given input arg name 'arg_A' we need to use alternate |
| argument name 'arg-A'. we create four python arguments in this case. |
| --arg_A, --noarg_A, --arg-A, --no-arg-A. The first two arguments will be |
| deprecated later. |
| TODO(b/218522717): Remove the creation of --noargA in case 1 and --arg_A and |
| --noarg_A in case 2. |
| |
| Args: |
| parser: The parser to update. |
| name: The input argument name. This will be used as 'dest' variable |
| name. |
| default_val: The default value to assign. |
| help_str: The help string for the input argument. |
| deprecation_note: A deprecation note to use. |
| alternate_name: Alternate argument to be used after deprecation. |
| """ |
| arg = f"--{name}" |
| shell_narg = f"--no{name}" |
| py_narg = f"--no-{name}" |
| alt_arg = f"--{alternate_name}" if alternate_name else None |
| alt_py_narg = f"--no-{alternate_name}" if alternate_name else None |
| default_val_str = f"{help_str} (Default: %(default)s)." |
| |
| if alternate_name: |
| parser.add_argument( |
| alt_arg, |
| action="store_true", |
| default=default_val, |
| dest=name, |
| help=default_val_str, |
| ) |
| parser.add_argument( |
| alt_py_narg, |
| action="store_false", |
| dest=name, |
| help="Don't " + help_str.lower(), |
| ) |
| |
| parser.add_argument( |
| arg, |
| action="store_true", |
| default=default_val, |
| dest=name, |
| deprecated=deprecation_note % alt_arg if alternate_name else None, |
| help=default_val_str if not alternate_name else argparse.SUPPRESS, |
| ) |
| parser.add_argument( |
| shell_narg, |
| action="store_false", |
| dest=name, |
| deprecated=deprecation_note % alt_py_narg |
| if alternate_name |
| else py_narg, |
| help=argparse.SUPPRESS, |
| ) |
| |
| if not alternate_name: |
| parser.add_argument( |
| py_narg, |
| action="store_false", |
| dest=name, |
| help="Don't " + help_str.lower(), |
| ) |
| |
| |
| tracer = trace.get_tracer(__name__) |
| |
| |
| @command.command_decorator("build-packages") |
| class BuildPackagesCommand(command.CliCommand): |
| """Update the set of binary packages used by ChromiumOS.""" |
| |
| @classmethod |
| def AddParser(cls, parser: commandline.ArgumentParser): |
| """Build the parser. |
| |
| Args: |
| parser: The argument parser. |
| """ |
| super().AddParser(parser) |
| |
| deprecation_note = "Argument will be removed July 2022. Use %s instead." |
| parser.add_argument( |
| "-b", |
| "--board", |
| "--build-target", |
| dest="board", |
| default=cros_build_lib.GetDefaultBoard(), |
| help="The board to build packages for.", |
| ) |
| |
| build_shell_bool_style_args( |
| parser, |
| "usepkg", |
| True, |
| "Use binary packages to bootstrap when possible.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| parser, |
| "usepkgonly", |
| False, |
| "Use binary packages only to bootstrap; abort if any are missing.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| parser, |
| "workon", |
| True, |
| "Force-build workon packages.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| parser, |
| "withrevdeps", |
| True, |
| "Calculate reverse dependencies on changed ebuilds.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| parser, |
| "cleanbuild", |
| False, |
| "Delete sysroot if it exists before building.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| parser, |
| "pretend", |
| False, |
| "Pretend building packages, just display which packages would have " |
| "been installed.", |
| deprecation_note, |
| ) |
| |
| # The --sysroot flag specifies the environment variables ROOT and |
| # PKGDIR. This allows fetching and emerging of all packages to |
| # specified sysroot. Note that --sysroot will setup the board normally |
| # in /build/$BOARD, if it's not setup yet. It also expects the toolchain |
| # to already be installed in the sysroot. |
| # --usepkgonly and --norebuild are required, because building is not |
| # supported when board_root is set. |
| parser.add_argument( |
| "--sysroot", type="path", help="Emerge packages to sysroot." |
| ) |
| parser.add_argument( |
| "--board_root", |
| type="path", |
| dest="sysroot", |
| deprecated=deprecation_note % "--sysroot", |
| help=argparse.SUPPRESS, |
| ) |
| |
| # CPU Governor related options. |
| group = parser.add_argument_group("CPU Governor Options") |
| build_shell_bool_style_args( |
| group, |
| "autosetgov", |
| False, |
| "Automatically set cpu governor to 'performance'.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "autosetgov_sticky", |
| False, |
| "Remember --autosetgov setting for future runs.", |
| deprecation_note, |
| alternate_name="autosetgov-sticky", |
| ) |
| |
| # Chrome building related options. |
| group = parser.add_argument_group("Chrome Options") |
| build_shell_bool_style_args( |
| group, |
| "use_any_chrome", |
| True, |
| "Use any Chrome prebuilt available, even if the prebuilt doesn't " |
| "match exactly.", |
| deprecation_note, |
| alternate_name="use-any-chrome", |
| ) |
| build_shell_bool_style_args( |
| group, |
| "internal", |
| False, |
| "Build the internal version of chrome (set the chrome_internal USE " |
| "flag).", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "chrome", |
| False, |
| "Ensure chrome instead of chromium. Alias for " |
| "--internal --no-use-any-chrome.", |
| deprecation_note, |
| ) |
| |
| # Setup board related options. |
| group = parser.add_argument_group("Setup Board Config Options") |
| build_shell_bool_style_args( |
| group, |
| "skip_chroot_upgrade", |
| False, |
| "Skip the automatic chroot upgrade; use with care.", |
| deprecation_note, |
| alternate_name="skip-chroot-upgrade", |
| ) |
| build_shell_bool_style_args( |
| group, |
| "skip_toolchain_update", |
| False, |
| "Skip automatic toolchain update", |
| deprecation_note, |
| alternate_name="skip-toolchain-update", |
| ) |
| build_shell_bool_style_args( |
| group, |
| "skip_setup_board", |
| False, |
| "Skip running setup_board. Implies " |
| "--skip-chroot-upgrade --skip-toolchain-update.", |
| deprecation_note, |
| alternate_name="skip-setup-board", |
| ) |
| |
| # Image Type selection related options. |
| group = parser.add_argument_group("Image Type Options") |
| build_shell_bool_style_args( |
| group, |
| "withdev", |
| True, |
| "Build useful developer friendly utilities.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "withdebug", |
| True, |
| "Build debug versions of Chromium-OS-specific packages.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "withfactory", |
| True, |
| "Build factory installer.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "withtest", |
| True, |
| "Build packages required for testing.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "withautotest", |
| True, |
| "Build autotest client code.", |
| deprecation_note, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "withdebugsymbols", |
| False, |
| "Install the debug symbols for all packages.", |
| deprecation_note, |
| ) |
| |
| # Advanced Options. |
| group = parser.add_argument_group("Advanced Options") |
| group.add_argument( |
| "--accept-licenses", help="Licenses to append to the accept list." |
| ) |
| group.add_argument( |
| "--accept_licenses", |
| deprecated=deprecation_note % "--accept-licenses", |
| help=argparse.SUPPRESS, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "eclean", |
| True, |
| "Run eclean to delete old binpkgs.", |
| deprecation_note, |
| ) |
| group.add_argument( |
| "--jobs", |
| type=int, |
| default=os.cpu_count(), |
| help=( |
| "Number of packages to build in parallel. (Default: " |
| "%(default)s)" |
| ), |
| ) |
| build_shell_bool_style_args( |
| group, |
| "rebuild", |
| True, |
| "Automatically rebuild dependencies.", |
| deprecation_note, |
| ) |
| # TODO(b/218522717): Remove the --nonorebuild argument support. |
| group.add_argument( |
| "--nonorebuild", |
| action="store_true", |
| dest="rebuild", |
| deprecated=deprecation_note % "--rebuild", |
| help=argparse.SUPPRESS, |
| ) |
| build_shell_bool_style_args( |
| group, |
| "expandedbinhosts", |
| True, |
| "Allow expanded binhost inheritance.", |
| deprecation_note, |
| ) |
| group.add_argument( |
| "--backtrack", |
| type=int, |
| default=sysroot.BACKTRACK_DEFAULT, |
| help="See emerge --backtrack.", |
| ) |
| |
| # The --reuse-pkgs-from-local-boards flag tells Portage to share binary |
| # packages between boards that are built locally, so that the total time |
| # required to build several boards is reduced. This flag is only useful |
| # when you are not able to use remote binary packages, since remote |
| # binary packages are usually more up to date than anything you have |
| # locally. |
| build_shell_bool_style_args( |
| group, |
| "reuse_pkgs_from_local_boards", |
| False, |
| "Bootstrap from local packages instead of remote packages.", |
| deprecation_note, |
| alternate_name="reuse-pkgs-from-local-boards", |
| ) |
| |
| # --run-goma option is designed to be used on bots. |
| # If you're trying to build packages with goma in your local dev env, |
| # this is *not* the option you're looking for. Please see comments |
| # below. This option; 1) starts goma, 2) builds packages (expecting |
| # that goma is used), then 3) stops goma explicitly. |
| # 4) is a request from the goma team, so that stats/logs can be taken. |
| # Note: GOMA_DIR is expected to be passed via env var. |
| # |
| # In local dev env cases, compiler_proxy is expected to keep running. |
| # In such a case; |
| # $ python ${GOMA_DIR}/goma_ctl.py ensure_start |
| # $ cros build-image (... and options without --run-goma ...) |
| # is an expected commandline sequence. If you set --run-goma flag while |
| # compiler_proxy is already running, the existing compiler_proxy will be |
| # stopped. |
| build_shell_bool_style_args( |
| group, |
| "run_goma", |
| False, |
| "When set, (re)starts goma, builds packages, and then stops goma.", |
| deprecation_note, |
| alternate_name="run-goma", |
| ) |
| # This option is for building chrome remotely. |
| # 1) starts reproxy 2) builds chrome with reproxy and 3) stops reproxy |
| # so logs/stats can be collected. |
| # Note: RECLIENT_DIR and REPROXY_CFG env var will be deprecated July |
| # 2022. Use --reclient-dir and --reproxy-cfg input options instead. |
| build_shell_bool_style_args( |
| group, |
| "run_remoteexec", |
| False, |
| "If set to true, starts RBE reproxy, builds packages, and then " |
| "stops reproxy.", |
| deprecation_note, |
| ) |
| |
| build_shell_bool_style_args( |
| group, |
| "bazel", |
| False, |
| "Use Bazel to build packages.", |
| deprecation_note, |
| ) |
| |
| parser.add_argument("packages", nargs="*", help="Packages to build.") |
| return parser |
| |
| @classmethod |
| def ProcessOptions( |
| cls, |
| parser: commandline.ArgumentParser, |
| options: commandline.ArgumentNamespace, |
| ): |
| options.parser = parser |
| |
| if options.chrome: |
| options.internal_chrome = True |
| options.use_any_chrome = False |
| |
| options.setup_board_run_config = sysroot.SetupBoardRunConfig( |
| force=options.cleanbuild, |
| usepkg=options.usepkg, |
| jobs=options.jobs, |
| quiet=True, |
| update_toolchain=not options.skip_toolchain_update, |
| upgrade_chroot=not options.skip_chroot_upgrade, |
| local_build=options.reuse_pkgs_from_local_boards, |
| expanded_binhost_inheritance=options.expandedbinhosts, |
| use_cq_prebuilts=options.usepkg, |
| backtrack=options.backtrack, |
| ) |
| options.build_run_config = sysroot.BuildPackagesRunConfig( |
| usepkg=options.usepkg, |
| install_debug_symbols=options.withdebugsymbols, |
| packages=options.packages, |
| use_goma=options.run_goma, |
| use_remoteexec=options.run_remoteexec, |
| incremental_build=options.withrevdeps, |
| dryrun=options.pretend, |
| usepkgonly=options.usepkgonly, |
| workon=options.workon, |
| install_auto_test=options.withautotest, |
| autosetgov=options.autosetgov, |
| autosetgov_sticky=options.autosetgov_sticky, |
| use_any_chrome=options.use_any_chrome, |
| internal_chrome=options.internal, |
| clean_build=options.cleanbuild, |
| eclean=options.eclean, |
| rebuild_dep=options.rebuild, |
| jobs=options.jobs, |
| local_pkg=options.reuse_pkgs_from_local_boards, |
| dev_image=options.withdev, |
| factory_image=options.withfactory, |
| test_image=options.withtest, |
| debug_version=options.withdebug, |
| backtrack=options.backtrack, |
| bazel=options.bazel, |
| ) |
| |
| @timer.timed("Elapsed time (cros build-packages)") |
| def Run(self): |
| commandline.RunInsideChroot() |
| |
| chromite_config.initialize() |
| telemetry.initialize( |
| chromite_config.TELEMETRY_CONFIG, debug=self.options.debug |
| ) |
| build_packages(self.options) |
| |
| |
| @tracer.start_as_current_span("cli.cros.cros_build_packages.build_packages") |
| def build_packages(opts: commandline.ArgumentNamespace): |
| span = trace.get_current_span() |
| |
| # If the opts.board is not set, then it means user hasn't specified a |
| # default board in 'src/scripts/.default_board' and didn't specify it as |
| # input argument. |
| if not opts.board: |
| span.add_event( |
| "exception", |
| attributes={ |
| "exception.type": "ArgumentMissingError", |
| "exception.message": "--board is required", |
| }, |
| ) |
| span.set_status(status.StatusCode.ERROR) |
| opts.parser.error("--board is required") |
| |
| build_target = build_target_lib.BuildTarget( |
| opts.board, build_root=opts.sysroot |
| ) |
| span.set_attributes( |
| { |
| "board": build_target.name, |
| "packages": opts.packages or [], |
| "workon": opts.workon is True, |
| "bazel": opts.bazel is True, |
| } |
| ) |
| |
| board_root = sysroot_lib.Sysroot(build_target.root) |
| |
| try: |
| # TODO(xcl): Update run_configs to have a common base set of configs for |
| # setup_board and cros build-packages. |
| if not opts.skip_setup_board: |
| sysroot.SetupBoard( |
| build_target, |
| accept_licenses=opts.accept_licenses, |
| run_configs=opts.setup_board_run_config, |
| ) |
| |
| sysroot.BuildPackages(build_target, board_root, opts.build_run_config) |
| except sysroot_lib.PackageInstallError as e: |
| try: |
| with urllib.request.urlopen( |
| "https://chromiumos-status.appspot.com/current?format=raw" |
| ) as request: |
| logging.notice("Tree Status: %s", request.read().decode()) |
| except urllib.error.HTTPError: |
| pass |
| span.record_exception(e) |
| span.set_status(status.StatusCode.ERROR, str(e)) |
| cros_build_lib.Die(e) |