| # -*- coding: utf-8 -*- |
| # Copyright 2018 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Sysroot service.""" |
| |
| from __future__ import print_function |
| |
| import os |
| |
| from chromite.lib import constants |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import osutils |
| from chromite.lib import portage_util |
| from chromite.lib import sysroot_lib |
| from chromite.lib import workon_helper |
| from chromite.utils import metrics |
| |
| |
| class Error(Exception): |
| """Base error class for the module.""" |
| |
| |
| class InvalidArgumentsError(Error): |
| """Invalid arguments.""" |
| |
| |
| class NotInChrootError(Error): |
| """When SetupBoard is run outside of the chroot.""" |
| |
| |
| class UpdateChrootError(Error): |
| """Error occurred when running update chroot.""" |
| |
| |
| class SetupBoardRunConfig(object): |
| """Value object for full setup board run configurations.""" |
| |
| def __init__(self, set_default=False, force=False, usepkg=True, jobs=None, |
| regen_configs=False, quiet=False, update_toolchain=True, |
| upgrade_chroot=True, init_board_pkgs=True, local_build=False, |
| toolchain_changed=False, package_indexes=None): |
| """Initialize method. |
| |
| Args: |
| set_default (bool): Whether to set the passed board as the default. |
| force (bool): Force a new sysroot creation when it already exists. |
| usepkg (bool): Whether to use binary packages to bootstrap. |
| jobs (int): Max number of simultaneous packages to build. |
| regen_configs (bool): Whether to only regen the configs. |
| quiet (bool): Whether to print notification when sysroot exists. |
| update_toolchain (bool): Update the toolchain? |
| upgrade_chroot (bool): Upgrade the chroot before building? |
| init_board_pkgs (bool): Emerging packages to sysroot? |
| local_build (bool): Bootstrap only from local packages? |
| toolchain_changed (bool): Has a toolchain change occurred? Implies |
| 'force'. |
| package_indexes (list[PackageIndexInfo]): List of information about |
| available prebuilts, youngest first, or None. |
| """ |
| |
| self.set_default = set_default |
| self.force = force or toolchain_changed |
| self.usepkg = usepkg |
| self.jobs = jobs |
| self.regen_configs = regen_configs |
| self.quiet = quiet |
| self.update_toolchain = update_toolchain |
| self.update_chroot = upgrade_chroot |
| self.init_board_pkgs = init_board_pkgs |
| self.local_build = local_build |
| self.package_indexes = package_indexes or [] |
| |
| def GetUpdateChrootArgs(self): |
| """Create a list containing the relevant update_chroot arguments. |
| |
| Returns: |
| list[str] |
| """ |
| args = [] |
| if self.usepkg: |
| args += ['--usepkg'] |
| else: |
| args += ['--nousepkg'] |
| |
| if self.jobs: |
| args += ['--jobs', str(self.jobs)] |
| |
| if not self.update_toolchain: |
| args += ['--skip_toolchain_update'] |
| |
| return args |
| |
| |
| class BuildPackagesRunConfig(object): |
| """Value object to hold build packages run configs.""" |
| |
| def __init__(self, usepkg=True, install_debug_symbols=False, |
| packages=None, use_flags=None, use_goma=False, |
| incremental_build=True, package_indexes=None): |
| """Init method. |
| |
| Args: |
| usepkg (bool): Whether to use binpkgs or build from source. False |
| currently triggers a local build, which will enable local reuse. |
| install_debug_symbols (bool): Whether to include the debug symbols for all |
| packages. |
| packages (list[str]|None): The list of packages to install, by default |
| install all packages for the target. |
| use_flags (list[str]|None): A list of use flags to set. |
| use_goma (bool): Whether to enable goma. |
| incremental_build (bool): Whether to treat the build as an incremental |
| build or a fresh build. Always treating it as an incremental build is |
| safe, but certain operations can be faster when we know we are doing |
| a fresh build. |
| package_indexes (list[PackageIndexInfo]): List of information about |
| available prebuilts, youngest first, or None. |
| """ |
| self.usepkg = usepkg |
| self.install_debug_symbols = install_debug_symbols |
| self.packages = packages |
| self.use_flags = use_flags |
| self.use_goma = use_goma |
| self.is_incremental = incremental_build |
| self.package_indexes = package_indexes or [] |
| |
| def GetBuildPackagesArgs(self): |
| """Get the build_packages script arguments.""" |
| # Defaults for the builder. |
| # TODO(saklein): Parametrize/rework the defaults when build_packages is |
| # ported to chromite. |
| args = [ |
| '--accept_licenses', |
| '@CHROMEOS', |
| '--skip_chroot_upgrade', |
| '--nouse_any_chrome', |
| ] |
| |
| if not self.usepkg: |
| args.append('--nousepkg') |
| |
| if self.install_debug_symbols: |
| args.append('--withdebugsymbols') |
| |
| if self.use_goma: |
| args.append('--run_goma') |
| |
| if not self.is_incremental: |
| args.append('--nowithrevdeps') |
| |
| if self.packages: |
| args.extend(self.packages) |
| |
| return args |
| |
| def HasUseFlags(self): |
| """Check if we have use flags.""" |
| return bool(self.use_flags) |
| |
| def GetUseFlags(self): |
| """Get the use flags as a single string.""" |
| use_flags = self.use_flags |
| if use_flags: |
| # We have use flags to set, but we need to append them to any existing |
| # use flags rather than overwrite them completely. |
| # TODO(saklein) Add config for whether to extend or overwrite? |
| existing_flags = os.environ.get('USE', '').split() |
| existing_flags.extend(use_flags) |
| use_flags = existing_flags |
| |
| return ' '.join(use_flags) |
| |
| return None |
| |
| def GetEnv(self): |
| """Get the env from this config.""" |
| env = {} |
| if self.HasUseFlags(): |
| env['USE'] = self.GetUseFlags() |
| |
| if self.use_goma: |
| env['USE_GOMA'] = 'true' |
| |
| if self.package_indexes: |
| env['PORTAGE_BINHOST'] = ' '.join( |
| x.location for x in reversed(self.package_indexes)) |
| |
| return env |
| |
| |
| def SetupBoard(target, accept_licenses=None, run_configs=None): |
| """Run the full process to setup a board's sysroot. |
| |
| This is the entry point to run the setup_board script. |
| |
| Args: |
| target (build_target_lib.BuildTarget): The build target configuration. |
| accept_licenses (str|None): The additional licenses to accept. |
| run_configs (SetupBoardRunConfig): The run configs. |
| |
| Raises: |
| sysroot_lib.ToolchainInstallError when the toolchain fails to install. |
| """ |
| if not cros_build_lib.IsInsideChroot(): |
| # TODO(saklein) switch to build out command and run inside chroot. |
| raise NotInChrootError('SetupBoard must be run from inside the chroot') |
| |
| # Make sure we have valid run configs setup. |
| run_configs = run_configs or SetupBoardRunConfig() |
| |
| sysroot = Create(target, run_configs, accept_licenses) |
| |
| if run_configs.regen_configs: |
| # We're now done if we're only regenerating the configs. |
| return |
| |
| InstallToolchain(target, sysroot, run_configs) |
| |
| |
| def Create(target, run_configs, accept_licenses): |
| """Create a sysroot. |
| |
| This entry point is the subset of the full setup process that does the |
| creation and configuration of a sysroot, including installing portage. |
| |
| Args: |
| target (build_target.BuildTarget): The build target being installed in the |
| sysroot being created. |
| run_configs (SetupBoardRunConfig): The run configs. |
| accept_licenses (str|None): The additional licenses to accept. |
| """ |
| cros_build_lib.AssertInsideChroot() |
| |
| sysroot = sysroot_lib.Sysroot(target.root) |
| |
| if sysroot.Exists() and not run_configs.force and not run_configs.quiet: |
| logging.warning('Board output directory already exists: %s\n' |
| 'Use --force to clobber the board root and start again.', |
| sysroot.path) |
| |
| # Override regen_configs setting to force full setup run if the sysroot does |
| # not exist. |
| run_configs.regen_configs = run_configs.regen_configs and sysroot.Exists() |
| |
| # Make sure the chroot is fully up to date before we start unless the |
| # chroot update is explicitly disabled. |
| if run_configs.update_chroot: |
| logging.info('Updating chroot.') |
| update_chroot = [os.path.join(constants.CROSUTILS_DIR, 'update_chroot'), |
| '--toolchain_boards', target.name] |
| update_chroot += run_configs.GetUpdateChrootArgs() |
| try: |
| cros_build_lib.run(update_chroot) |
| except cros_build_lib.RunCommandError: |
| raise UpdateChrootError('Error occurred while updating the chroot.' |
| 'See the logs for more information.') |
| |
| # Delete old sysroot to force a fresh start if requested. |
| if sysroot.Exists() and run_configs.force: |
| sysroot.Delete(background=True) |
| |
| # Step 1: Create folders. |
| # Dependencies: None. |
| # Create the skeleton. |
| logging.info('Creating sysroot directories.') |
| _CreateSysrootSkeleton(sysroot) |
| |
| # Step 2: Standalone configurations. |
| # Dependencies: Folders exist. |
| # Install main, board setup, and user make.conf files. |
| logging.info('Installing configurations into sysroot.') |
| _InstallConfigs(sysroot, target) |
| |
| # Step 3: Portage configurations. |
| # Dependencies: make.conf.board_setup. |
| # Create the command wrappers, choose profile, and make.conf.board. |
| # Refresh the workon symlinks to compensate for crbug.com/679831. |
| logging.info('Setting up portage in the sysroot.') |
| _InstallPortageConfigs(sysroot, target, accept_licenses, |
| run_configs.local_build, |
| package_indexes=run_configs.package_indexes) |
| |
| # Developer Experience Step: Set default board (if requested) to allow |
| # running later commands without needing to pass the --board argument. |
| if run_configs.set_default: |
| cros_build_lib.SetDefaultBoard(target.name) |
| |
| return sysroot |
| |
| |
| def GenerateArchive(output_dir, build_target_name, pkg_list): |
| """Generate a sysroot tarball for informational builders. |
| |
| Args: |
| output_dir (string): Directory to contain the created the sysroot. |
| build_target_name (string): The build target for the sysroot being created. |
| pkg_list (list[string]|None): List of 'category/package' package strings. |
| |
| Returns: |
| Path to the sysroot tar file. |
| """ |
| cmd = ['cros_generate_sysroot', |
| '--out-file', constants.TARGET_SYSROOT_TAR, |
| '--out-dir', output_dir, |
| '--board', build_target_name, |
| '--package', ' '.join(pkg_list)] |
| cros_build_lib.run(cmd, cwd=constants.SOURCE_ROOT) |
| return os.path.join(output_dir, constants.TARGET_SYSROOT_TAR) |
| |
| def CreateSimpleChromeSysroot(target, use_flags): |
| """Create a sysroot for SimpleChrome to use. |
| |
| Args: |
| target (build_target.BuildTarget): The build target being installed for the |
| sysroot being created. |
| use_flags (list[string]|None): Additional USE flags for building chrome. |
| |
| Returns: |
| Path to the sysroot tar file. |
| """ |
| extra_env = {} |
| if use_flags: |
| extra_env['USE'] = ' '.join(use_flags) |
| with osutils.TempDir(delete=False) as tempdir: |
| cmd = ['cros_generate_sysroot', '--out-dir', tempdir, '--board', |
| target, '--deps-only', '--package', constants.CHROME_CP] |
| cros_build_lib.run(cmd, cwd=constants.SOURCE_ROOT, enter_chroot=True, |
| extra_env=extra_env) |
| sysroot_tar_path = os.path.join(tempdir, constants.CHROME_SYSROOT_TAR) |
| return sysroot_tar_path |
| |
| def InstallToolchain(target, sysroot, run_configs): |
| """Update the toolchain to a sysroot. |
| |
| This entry point just installs the target's toolchain into the sysroot. |
| Everything else must have been done already for this to be successful. |
| |
| Args: |
| target (build_target_lib.BuildTarget): The target whose toolchain is being |
| installed. |
| sysroot (sysroot_lib.Sysroot): The sysroot where the toolchain is being |
| installed. |
| run_configs (SetupBoardRunConfig): The run configs. |
| """ |
| cros_build_lib.AssertInsideChroot() |
| if not sysroot.Exists(): |
| # Sanity check before we try installing anything. |
| raise ValueError('The sysroot must exist, run Create first.') |
| |
| # Step 4: Install toolchain and packages. |
| # Dependencies: Portage configs and wrappers have been installed. |
| if run_configs.init_board_pkgs: |
| logging.info('Updating toolchain.') |
| # Use the local packages if we're doing a local only build or usepkg is set. |
| local_init = run_configs.usepkg or run_configs.local_build |
| _InstallToolchain(sysroot, target, local_init=local_init) |
| |
| |
| def BuildPackages(target, sysroot, run_configs): |
| """Build and install packages into a sysroot. |
| |
| Args: |
| target (build_target_lib.BuildTarget): The target whose packages are being |
| installed. |
| sysroot (sysroot_lib.Sysroot): The sysroot where the packages are being |
| installed. |
| run_configs (BuildPackagesRunConfig): The run configs. |
| """ |
| cros_build_lib.AssertInsideChroot() |
| |
| cmd = [os.path.join(constants.CROSUTILS_DIR, 'build_packages'), |
| '--board', target.name, '--board_root', sysroot.path] |
| cmd += run_configs.GetBuildPackagesArgs() |
| |
| extra_env = run_configs.GetEnv() |
| extra_env['USE_NEW_PARALLEL_EMERGE'] = '1' |
| with osutils.TempDir() as tempdir: |
| extra_env[constants.CROS_METRICS_DIR_ENVVAR] = tempdir |
| |
| try: |
| # REVIEW: discuss which dimensions to flatten into the metric |
| # name other than target.name... |
| with metrics.timer('service.sysroot.BuildPackages.RunCommand'): |
| cros_build_lib.run(cmd, extra_env=extra_env) |
| except cros_build_lib.RunCommandError as e: |
| failed_pkgs = portage_util.ParseDieHookStatusFile(tempdir) |
| raise sysroot_lib.PackageInstallError( |
| str(e), e.result, exception=e, packages=failed_pkgs) |
| |
| |
| def _CreateSysrootSkeleton(sysroot): |
| """Create the sysroot skeleton. |
| |
| Dependencies: None. |
| Creates the sysroot directory structure and installs the portage hooks. |
| |
| Args: |
| sysroot (sysroot_lib.Sysroot): The sysroot. |
| """ |
| sysroot.CreateSkeleton() |
| |
| |
| def _InstallConfigs(sysroot, target): |
| """Install standalone configuration files into the sysroot. |
| |
| Dependencies: The sysroot exists (i.e. CreateSysrootSkeleton). |
| Installs the main, board setup, and user make.conf files. |
| |
| Args: |
| sysroot (sysroot_lib.Sysroot): The sysroot. |
| target (build_target.BuildTarget): The build target being setup in |
| the sysroot. |
| """ |
| sysroot.InstallMakeConf() |
| sysroot.InstallMakeConfBoardSetup(target.name) |
| sysroot.InstallMakeConfUser() |
| |
| |
| def _InstallPortageConfigs(sysroot, target, accept_licenses, local_build, |
| package_indexes=None): |
| """Install portage wrappers and configurations. |
| |
| Dependencies: make.conf.board_setup (InstallConfigs). |
| Create the command wrappers, choose profile, and generate make.conf.board. |
| Refresh the workon symlinks to compensate for crbug.com/679831. |
| |
| Args: |
| sysroot (sysroot_lib.Sysroot): The sysroot. |
| target (build_target.BuildTarget): The build target being installed |
| in the sysroot. |
| accept_licenses (str): Additional accepted licenses as a string. |
| local_build (bool): If the build is a local only build. |
| package_indexes (list[PackageIndexInfo]): List of information about |
| available prebuilts, youngest first, or None. |
| """ |
| sysroot.CreateAllWrappers(friendly_name=target.name) |
| _ChooseProfile(target, sysroot) |
| _RefreshWorkonSymlinks(target.name, sysroot) |
| # Must be done after the profile is chosen or binhosts may be incomplete. |
| sysroot.InstallMakeConfBoard(accepted_licenses=accept_licenses, |
| local_only=local_build, |
| package_indexes=package_indexes) |
| |
| |
| def _InstallToolchain(sysroot, target, local_init=True): |
| """Install toolchain and packages. |
| |
| Dependencies: Portage configs and wrappers have been installed |
| (InstallPortageConfigs). |
| Install the toolchain and the implicit dependencies. |
| |
| Args: |
| sysroot (sysroot_lib.Sysroot): The sysroot to install to. |
| target (build_target.BuildTarget): The build target whose toolchain is |
| being installed. |
| local_init (bool): Whether to use local packages to bootstrap implicit |
| dependencies. |
| """ |
| sysroot.UpdateToolchain(target.name, local_init=local_init) |
| |
| |
| def _RefreshWorkonSymlinks(target, sysroot): |
| """Force refresh the workon symlinks. |
| |
| Create an instance of the WorkonHelper, which will recreate all symlinks |
| to masked/unmasked packages currently worked on in case the sysroot was |
| recreated (crbug.com/679831). |
| |
| This was done with a call to `cros_workon list` in the bash version of |
| the script, but all we actually need is for the WorkonHelper to be |
| instantiated since it refreshes the symlinks in its __init__. |
| |
| Args: |
| target (str): The build target name. |
| sysroot (sysroot_lib.Sysroot): The board's sysroot. |
| """ |
| workon_helper.WorkonHelper(sysroot.path, friendly_name=target) |
| |
| |
| def _ChooseProfile(target, sysroot): |
| """Helper function to execute cros_choose_profile. |
| |
| TODO(saklein) Refactor cros_choose_profile to avoid needing the run |
| call here, and by extension this method all together. |
| |
| Args: |
| target (build_target_lib.BuildTarget): The build target whose profile is |
| being chosen. |
| sysroot (sysroot_lib.Sysroot): The sysroot for which the profile is |
| being chosen. |
| """ |
| choose_profile = ['cros_choose_profile', '--board', target.name, |
| '--board-root', sysroot.path] |
| if target.profile: |
| # Chooses base by default, only override when we have a passed param. |
| choose_profile += ['--profile', target.profile] |
| try: |
| cros_build_lib.run(choose_profile, print_cmd=False) |
| except cros_build_lib.RunCommandError as e: |
| logging.error('Selecting profile failed, removing incomplete board ' |
| 'directory!') |
| sysroot.Delete() |
| raise e |