blob: 660e021acf6c4e005e972f55f93db9d6297ab915 [file] [log] [blame]
Alex Kleineb77ffa2019-05-28 14:47:44 -06001# -*- 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
8from __future__ import print_function
9
10import os
11
12from chromite.cbuildbot import manifest_version
13from chromite.lib import constants
14from chromite.lib import cros_build_lib
15from chromite.lib import cros_logging as logging
16from chromite.lib import git
17from chromite.lib import osutils
18from chromite.lib import parallel
19from chromite.lib import portage_util
20
21
22class Error(Exception):
23 """Module's base error class."""
24
25
26class UprevError(Error):
27 """An error occurred while uprevving packages."""
28
29
Alex Kleineb77ffa2019-05-28 14:47:44 -060030def 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
55def 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
David Burger1e0fe232019-07-01 14:52:07 -060081def get_best_visible(atom):
82 """Returns the best visible CPV for the given atom."""
83 assert atom
84 return portage_util.PortageqBestVisible(atom)
85
86
Alex Kleineb77ffa2019-05-28 14:47:44 -060087class UprevManager(object):
88 """Class to handle the uprev process.
89
90 TODO (saklein): The manifest object for this class is used deep in the
91 portage_util uprev process. Look into whether it's possible to redo it so
92 the manifest isn't required.
93 """
94
95 def __init__(self, overlays, manifest, build_targets=None, chroot=None,
96 output_dir=None):
97 """Init function.
98
99 Args:
100 overlays (list[str]): The overlays to search for ebuilds.
101 manifest (git.ManifestCheckout): The manifest object.
102 build_targets (list[build_target_util.BuildTarget]|None): The build
103 targets to clean in |chroot|, if desired. No effect unless |chroot| is
104 provided.
105 chroot (chroot_lib.Chroot|None): The chroot to clean, if desired.
106 output_dir (str|None): The path to optionally dump result files.
107 """
108 self.overlays = overlays
109 self.manifest = manifest
110 self.build_targets = build_targets or []
111 self.chroot = chroot
112 self.output_dir = output_dir
113
114 self._revved_packages = None
115 self._new_package_atoms = None
116 self._new_ebuild_files = None
117 self._removed_ebuild_files = None
118 self._overlay_ebuilds = None
119
120 @property
121 def modified_ebuilds(self):
122 if self._new_ebuild_files is not None:
123 return self._new_ebuild_files + self._removed_ebuild_files
124 else:
125 return []
126
127 def uprev(self):
128 self._populate_overlay_ebuilds()
129
130 with parallel.Manager() as manager:
131 # Contains the list of packages we actually revved.
132 self._revved_packages = manager.list()
133 # The new package atoms for cleanup.
134 self._new_package_atoms = manager.list()
135 # The list of added ebuild files.
136 self._new_ebuild_files = manager.list()
137 # The list of removed ebuild files.
138 self._removed_ebuild_files = manager.list()
139
140 inputs = [[overlay] for overlay in self.overlays]
141 parallel.RunTasksInProcessPool(self._uprev_overlay, inputs)
142
143 self._revved_packages = list(self._revved_packages)
144 self._new_package_atoms = list(self._new_package_atoms)
145 self._new_ebuild_files = list(self._new_ebuild_files)
146 self._removed_ebuild_files = list(self._removed_ebuild_files)
147
148 self._clean_stale_packages()
149
150 if self.output_dir and os.path.exists(self.output_dir):
151 # Write out dumps of the results. This is largely meant for sanity
152 # checking results.
153 osutils.WriteFile(os.path.join(self.output_dir, 'revved_packages'),
154 '\n'.join(self._revved_packages))
155 osutils.WriteFile(os.path.join(self.output_dir, 'new_package_atoms'),
156 '\n'.join(self._new_package_atoms))
157 osutils.WriteFile(os.path.join(self.output_dir, 'new_ebuild_files'),
158 '\n'.join(self._new_ebuild_files))
159 osutils.WriteFile(os.path.join(self.output_dir, 'removed_ebuild_files'),
160 '\n'.join(self._removed_ebuild_files))
161
162 def _uprev_overlay(self, overlay):
163 """Execute uprevs for an overlay.
164
165 Args:
166 overlay: The overlay to uprev.
167 """
168 if not os.path.isdir(overlay):
169 logging.warning('Skipping %s, which is not a directory.', overlay)
170 return
171
172 ebuilds = self._overlay_ebuilds.get(overlay, [])
173 if not ebuilds:
174 return
175
176 inputs = [[overlay, ebuild] for ebuild in ebuilds]
177 parallel.RunTasksInProcessPool(self._uprev_ebuild, inputs)
178
179 def _uprev_ebuild(self, overlay, ebuild):
180 """Work on a single ebuild.
181
182 Args:
183 overlay: The overlay the ebuild belongs to.
184 ebuild: The ebuild to work on.
185 """
186 logging.debug('Working on %s, info %s', ebuild.package,
187 ebuild.cros_workon_vars)
188 try:
189 result = ebuild.RevWorkOnEBuild(
190 os.path.join(constants.SOURCE_ROOT, 'src'), self.manifest)
191 except (OSError, IOError):
192 logging.warning(
193 'Cannot rev %s\n'
194 'Note you will have to go into %s '
195 'and reset the git repo yourself.', ebuild.package, overlay)
196 raise
197
198 if result:
199 new_package, ebuild_path_to_add, ebuild_path_to_remove = result
200
201 if ebuild_path_to_add:
202 self._new_ebuild_files.append(ebuild_path_to_add)
203 if ebuild_path_to_remove:
204 osutils.SafeUnlink(ebuild_path_to_remove)
205 self._removed_ebuild_files.append(ebuild_path_to_remove)
206
207 self._revved_packages.append(ebuild.package)
208 self._new_package_atoms.append('=%s' % new_package)
209
210 def _populate_overlay_ebuilds(self):
211 """Populates the overlay to ebuilds mapping."""
212 # See crrev.com/c/1257944 for origins of this.
213 root_version = manifest_version.VersionInfo.from_repo(constants.SOURCE_ROOT)
214 subdir_removal = manifest_version.VersionInfo('10363.0.0')
215 require_subdir_support = root_version < subdir_removal
216
217 # Parameters lost to the current narrow implementation.
218 use_all = True
219 package_list = []
220 force = False
221
222 overlay_ebuilds = {}
223 inputs = [[overlay, use_all, package_list, force, require_subdir_support]
224 for overlay in self.overlays]
225 result = parallel.RunTasksInProcessPool(
226 portage_util.GetOverlayEBuilds, inputs)
227 for idx, ebuilds in enumerate(result):
228 overlay_ebuilds[self.overlays[idx]] = ebuilds
229
230 self._overlay_ebuilds = overlay_ebuilds
231
232 def _clean_stale_packages(self):
233 """Cleans up stale package info from a previous build."""
234 if not self.chroot or not self.chroot.exists():
235 return
236
237 if self._new_package_atoms:
238 logging.info('Cleaning up stale packages %s.', self._new_package_atoms)
239
240 # First unmerge all the packages for a board, then eclean it.
241 # We need these two steps to run in order (unmerge/eclean),
242 # but we can let all the boards run in parallel.
243 def _do_clean_stale_packages(board):
244 if board:
245 suffix = '-' + board
246 runcmd = cros_build_lib.RunCommand
247 else:
248 suffix = ''
249 runcmd = cros_build_lib.SudoRunCommand
250
251 emerge, eclean = 'emerge' + suffix, 'eclean' + suffix
252 if not osutils.FindMissingBinaries([emerge, eclean]):
253 if self._new_package_atoms:
254 # If nothing was found to be unmerged, emerge will exit(1).
255 result = runcmd([emerge, '-q', '--unmerge'] + self._new_package_atoms,
256 enter_chroot=True,
257 chroot_args=self.chroot.get_enter_args(),
258 extra_env={'CLEAN_DELAY': '0'}, error_code_ok=True,
259 cwd=constants.SOURCE_ROOT)
260 if result.returncode not in (0, 1):
261 raise cros_build_lib.RunCommandError('unexpected error', result)
262
263 runcmd([eclean, '-d', 'packages'],
264 cwd=constants.SOURCE_ROOT, enter_chroot=True,
265 chroot_args=self.chroot.get_enter_args(), redirect_stdout=True,
266 redirect_stderr=True)
267
268 tasks = []
269 for build_target in self.build_targets:
270 tasks.append([build_target.name])
271 tasks.append([None])
272
273 parallel.RunTasksInProcessPool(_do_clean_stale_packages, tasks)