Alex Klein | eb77ffa | 2019-05-28 14:47:44 -0600 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | # Copyright 2019 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Package utility functionality.""" |
| 7 | |
| 8 | from __future__ import print_function |
| 9 | |
| 10 | import os |
| 11 | |
| 12 | from chromite.cbuildbot import manifest_version |
| 13 | from chromite.lib import constants |
| 14 | from chromite.lib import cros_build_lib |
| 15 | from chromite.lib import cros_logging as logging |
| 16 | from chromite.lib import git |
| 17 | from chromite.lib import osutils |
| 18 | from chromite.lib import parallel |
| 19 | from chromite.lib import portage_util |
| 20 | |
| 21 | |
| 22 | class Error(Exception): |
| 23 | """Module's base error class.""" |
| 24 | |
| 25 | |
| 26 | class UprevError(Error): |
| 27 | """An error occurred while uprevving packages.""" |
| 28 | |
| 29 | |
Alex Klein | eb77ffa | 2019-05-28 14:47:44 -0600 | [diff] [blame] | 30 | def uprev_build_targets(build_targets, overlay_type, chroot=None, |
| 31 | output_dir=None): |
| 32 | """Uprev the set provided build targets, or all if not specified. |
| 33 | |
| 34 | Args: |
| 35 | build_targets (list[build_target_util.BuildTarget]|None): The build targets |
| 36 | whose overlays should be uprevved, empty or None for all. |
| 37 | overlay_type (str): One of the valid overlay types except None (see |
| 38 | constants.VALID_OVERLAYS). |
| 39 | chroot (chroot_lib.Chroot|None): The chroot to clean, if desired. |
| 40 | output_dir (str|None): The path to optionally dump result files. |
| 41 | """ |
| 42 | # Need a valid overlay, but exclude None. |
| 43 | assert overlay_type and overlay_type in constants.VALID_OVERLAYS |
| 44 | |
| 45 | if build_targets: |
| 46 | overlays = portage_util.FindOverlaysForBoards( |
| 47 | overlay_type, boards=[t.name for t in build_targets]) |
| 48 | else: |
| 49 | overlays = portage_util.FindOverlays(overlay_type) |
| 50 | |
| 51 | return uprev_overlays(overlays, build_targets=build_targets, chroot=chroot, |
| 52 | output_dir=output_dir) |
| 53 | |
| 54 | |
| 55 | def uprev_overlays(overlays, build_targets=None, chroot=None, output_dir=None): |
| 56 | """Uprev the given overlays. |
| 57 | |
| 58 | Args: |
| 59 | overlays (list[str]): The list of overlay paths. |
| 60 | build_targets (list[build_target_util.BuildTarget]|None): The build targets |
| 61 | to clean in |chroot|, if desired. No effect unless |chroot| is provided. |
| 62 | chroot (chroot_lib.Chroot|None): The chroot to clean, if desired. |
| 63 | output_dir (str|None): The path to optionally dump result files. |
| 64 | |
| 65 | Returns: |
| 66 | list[str] - The paths to all of the modified ebuild files. This includes the |
| 67 | new files that were added (i.e. the new versions) and all of the removed |
| 68 | files (i.e. the old versions). |
| 69 | """ |
| 70 | assert overlays |
| 71 | |
| 72 | manifest = git.ManifestCheckout.Cached(constants.SOURCE_ROOT) |
| 73 | |
| 74 | uprev_manager = UprevManager(overlays, manifest, build_targets=build_targets, |
| 75 | chroot=chroot, output_dir=output_dir) |
| 76 | uprev_manager.uprev() |
| 77 | |
| 78 | return uprev_manager.modified_ebuilds |
| 79 | |
| 80 | |
| 81 | class UprevManager(object): |
| 82 | """Class to handle the uprev process. |
| 83 | |
| 84 | TODO (saklein): The manifest object for this class is used deep in the |
| 85 | portage_util uprev process. Look into whether it's possible to redo it so |
| 86 | the manifest isn't required. |
| 87 | """ |
| 88 | |
| 89 | def __init__(self, overlays, manifest, build_targets=None, chroot=None, |
| 90 | output_dir=None): |
| 91 | """Init function. |
| 92 | |
| 93 | Args: |
| 94 | overlays (list[str]): The overlays to search for ebuilds. |
| 95 | manifest (git.ManifestCheckout): The manifest object. |
| 96 | build_targets (list[build_target_util.BuildTarget]|None): The build |
| 97 | targets to clean in |chroot|, if desired. No effect unless |chroot| is |
| 98 | provided. |
| 99 | chroot (chroot_lib.Chroot|None): The chroot to clean, if desired. |
| 100 | output_dir (str|None): The path to optionally dump result files. |
| 101 | """ |
| 102 | self.overlays = overlays |
| 103 | self.manifest = manifest |
| 104 | self.build_targets = build_targets or [] |
| 105 | self.chroot = chroot |
| 106 | self.output_dir = output_dir |
| 107 | |
| 108 | self._revved_packages = None |
| 109 | self._new_package_atoms = None |
| 110 | self._new_ebuild_files = None |
| 111 | self._removed_ebuild_files = None |
| 112 | self._overlay_ebuilds = None |
| 113 | |
| 114 | @property |
| 115 | def modified_ebuilds(self): |
| 116 | if self._new_ebuild_files is not None: |
| 117 | return self._new_ebuild_files + self._removed_ebuild_files |
| 118 | else: |
| 119 | return [] |
| 120 | |
| 121 | def uprev(self): |
| 122 | self._populate_overlay_ebuilds() |
| 123 | |
| 124 | with parallel.Manager() as manager: |
| 125 | # Contains the list of packages we actually revved. |
| 126 | self._revved_packages = manager.list() |
| 127 | # The new package atoms for cleanup. |
| 128 | self._new_package_atoms = manager.list() |
| 129 | # The list of added ebuild files. |
| 130 | self._new_ebuild_files = manager.list() |
| 131 | # The list of removed ebuild files. |
| 132 | self._removed_ebuild_files = manager.list() |
| 133 | |
| 134 | inputs = [[overlay] for overlay in self.overlays] |
| 135 | parallel.RunTasksInProcessPool(self._uprev_overlay, inputs) |
| 136 | |
| 137 | self._revved_packages = list(self._revved_packages) |
| 138 | self._new_package_atoms = list(self._new_package_atoms) |
| 139 | self._new_ebuild_files = list(self._new_ebuild_files) |
| 140 | self._removed_ebuild_files = list(self._removed_ebuild_files) |
| 141 | |
| 142 | self._clean_stale_packages() |
| 143 | |
| 144 | if self.output_dir and os.path.exists(self.output_dir): |
| 145 | # Write out dumps of the results. This is largely meant for sanity |
| 146 | # checking results. |
| 147 | osutils.WriteFile(os.path.join(self.output_dir, 'revved_packages'), |
| 148 | '\n'.join(self._revved_packages)) |
| 149 | osutils.WriteFile(os.path.join(self.output_dir, 'new_package_atoms'), |
| 150 | '\n'.join(self._new_package_atoms)) |
| 151 | osutils.WriteFile(os.path.join(self.output_dir, 'new_ebuild_files'), |
| 152 | '\n'.join(self._new_ebuild_files)) |
| 153 | osutils.WriteFile(os.path.join(self.output_dir, 'removed_ebuild_files'), |
| 154 | '\n'.join(self._removed_ebuild_files)) |
| 155 | |
| 156 | def _uprev_overlay(self, overlay): |
| 157 | """Execute uprevs for an overlay. |
| 158 | |
| 159 | Args: |
| 160 | overlay: The overlay to uprev. |
| 161 | """ |
| 162 | if not os.path.isdir(overlay): |
| 163 | logging.warning('Skipping %s, which is not a directory.', overlay) |
| 164 | return |
| 165 | |
| 166 | ebuilds = self._overlay_ebuilds.get(overlay, []) |
| 167 | if not ebuilds: |
| 168 | return |
| 169 | |
| 170 | inputs = [[overlay, ebuild] for ebuild in ebuilds] |
| 171 | parallel.RunTasksInProcessPool(self._uprev_ebuild, inputs) |
| 172 | |
| 173 | def _uprev_ebuild(self, overlay, ebuild): |
| 174 | """Work on a single ebuild. |
| 175 | |
| 176 | Args: |
| 177 | overlay: The overlay the ebuild belongs to. |
| 178 | ebuild: The ebuild to work on. |
| 179 | """ |
| 180 | logging.debug('Working on %s, info %s', ebuild.package, |
| 181 | ebuild.cros_workon_vars) |
| 182 | try: |
| 183 | result = ebuild.RevWorkOnEBuild( |
| 184 | os.path.join(constants.SOURCE_ROOT, 'src'), self.manifest) |
| 185 | except (OSError, IOError): |
| 186 | logging.warning( |
| 187 | 'Cannot rev %s\n' |
| 188 | 'Note you will have to go into %s ' |
| 189 | 'and reset the git repo yourself.', ebuild.package, overlay) |
| 190 | raise |
| 191 | |
| 192 | if result: |
| 193 | new_package, ebuild_path_to_add, ebuild_path_to_remove = result |
| 194 | |
| 195 | if ebuild_path_to_add: |
| 196 | self._new_ebuild_files.append(ebuild_path_to_add) |
| 197 | if ebuild_path_to_remove: |
| 198 | osutils.SafeUnlink(ebuild_path_to_remove) |
| 199 | self._removed_ebuild_files.append(ebuild_path_to_remove) |
| 200 | |
| 201 | self._revved_packages.append(ebuild.package) |
| 202 | self._new_package_atoms.append('=%s' % new_package) |
| 203 | |
| 204 | def _populate_overlay_ebuilds(self): |
| 205 | """Populates the overlay to ebuilds mapping.""" |
| 206 | # See crrev.com/c/1257944 for origins of this. |
| 207 | root_version = manifest_version.VersionInfo.from_repo(constants.SOURCE_ROOT) |
| 208 | subdir_removal = manifest_version.VersionInfo('10363.0.0') |
| 209 | require_subdir_support = root_version < subdir_removal |
| 210 | |
| 211 | # Parameters lost to the current narrow implementation. |
| 212 | use_all = True |
| 213 | package_list = [] |
| 214 | force = False |
| 215 | |
| 216 | overlay_ebuilds = {} |
| 217 | inputs = [[overlay, use_all, package_list, force, require_subdir_support] |
| 218 | for overlay in self.overlays] |
| 219 | result = parallel.RunTasksInProcessPool( |
| 220 | portage_util.GetOverlayEBuilds, inputs) |
| 221 | for idx, ebuilds in enumerate(result): |
| 222 | overlay_ebuilds[self.overlays[idx]] = ebuilds |
| 223 | |
| 224 | self._overlay_ebuilds = overlay_ebuilds |
| 225 | |
| 226 | def _clean_stale_packages(self): |
| 227 | """Cleans up stale package info from a previous build.""" |
| 228 | if not self.chroot or not self.chroot.exists(): |
| 229 | return |
| 230 | |
| 231 | if self._new_package_atoms: |
| 232 | logging.info('Cleaning up stale packages %s.', self._new_package_atoms) |
| 233 | |
| 234 | # First unmerge all the packages for a board, then eclean it. |
| 235 | # We need these two steps to run in order (unmerge/eclean), |
| 236 | # but we can let all the boards run in parallel. |
| 237 | def _do_clean_stale_packages(board): |
| 238 | if board: |
| 239 | suffix = '-' + board |
| 240 | runcmd = cros_build_lib.RunCommand |
| 241 | else: |
| 242 | suffix = '' |
| 243 | runcmd = cros_build_lib.SudoRunCommand |
| 244 | |
| 245 | emerge, eclean = 'emerge' + suffix, 'eclean' + suffix |
| 246 | if not osutils.FindMissingBinaries([emerge, eclean]): |
| 247 | if self._new_package_atoms: |
| 248 | # If nothing was found to be unmerged, emerge will exit(1). |
| 249 | result = runcmd([emerge, '-q', '--unmerge'] + self._new_package_atoms, |
| 250 | enter_chroot=True, |
| 251 | chroot_args=self.chroot.get_enter_args(), |
| 252 | extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True, |
| 253 | cwd=constants.SOURCE_ROOT) |
| 254 | if result.returncode not in (0, 1): |
| 255 | raise cros_build_lib.RunCommandError('unexpected error', result) |
| 256 | |
| 257 | runcmd([eclean, '-d', 'packages'], |
| 258 | cwd=constants.SOURCE_ROOT, enter_chroot=True, |
| 259 | chroot_args=self.chroot.get_enter_args(), redirect_stdout=True, |
| 260 | redirect_stderr=True) |
| 261 | |
| 262 | tasks = [] |
| 263 | for build_target in self.build_targets: |
| 264 | tasks.append([build_target.name]) |
| 265 | tasks.append([None]) |
| 266 | |
| 267 | parallel.RunTasksInProcessPool(_do_clean_stale_packages, tasks) |