blob: fb8350023c09c202c59f1342108026fa1cabd3d8 [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
30def 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
81class 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)