blob: 12f72c4a8a4776b3f011cb393533af9e04285316 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2015 The ChromiumOS Authors
David Pursell9476bf42015-03-30 13:34:27 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Alex Kleinaaddc932020-01-30 15:02:24 -07005"""Deploy packages onto a target device.
6
7Integration tests for this file can be found at cli/cros/tests/cros_vm_tests.py.
8See that file for more information.
9"""
David Pursell9476bf42015-03-30 13:34:27 -070010
Mike Frysinger93e8ffa2019-07-03 20:24:18 -040011from __future__ import division
David Pursell9476bf42015-03-30 13:34:27 -070012
Xiaochu Liu2726e7c2019-07-18 10:28:10 -070013import bz2
David Pursell9476bf42015-03-30 13:34:27 -070014import fnmatch
Ralph Nathane01ccf12015-04-16 10:40:32 -070015import functools
David Pursell9476bf42015-03-30 13:34:27 -070016import json
Chris McDonald14ac61d2021-07-21 11:49:56 -060017import logging
David Pursell9476bf42015-03-30 13:34:27 -070018import os
Jae Hoon Kim2376e142022-09-03 00:18:58 +000019from pathlib import Path
Xiaochu Liu2726e7c2019-07-18 10:28:10 -070020import tempfile
David Pursell9476bf42015-03-30 13:34:27 -070021
Ralph Nathane01ccf12015-04-16 10:40:32 -070022from chromite.cli import command
Mike Frysinger06a51c82021-04-06 11:39:17 -040023from chromite.lib import build_target_lib
Ram Chandrasekar56152ec2021-11-22 17:10:41 +000024from chromite.lib import constants
David Pursell9476bf42015-03-30 13:34:27 -070025from chromite.lib import cros_build_lib
Alex Klein18a60af2020-06-11 12:08:47 -060026from chromite.lib import dlc_lib
Ralph Nathane01ccf12015-04-16 10:40:32 -070027from chromite.lib import operation
Xiaochu Liu2726e7c2019-07-18 10:28:10 -070028from chromite.lib import osutils
David Pursell9476bf42015-03-30 13:34:27 -070029from chromite.lib import portage_util
David Pursell9476bf42015-03-30 13:34:27 -070030from chromite.lib import remote_access
Kimiyuki Onakaa4ec7f62020-08-25 13:58:48 +090031from chromite.lib import workon_helper
Alex Klein18a60af2020-06-11 12:08:47 -060032from chromite.lib.parser import package_info
33
Chris McDonald14ac61d2021-07-21 11:49:56 -060034
David Pursell9476bf42015-03-30 13:34:27 -070035try:
Alex Klein1699fab2022-09-08 08:46:06 -060036 import portage
David Pursell9476bf42015-03-30 13:34:27 -070037except ImportError:
Alex Klein1699fab2022-09-08 08:46:06 -060038 if cros_build_lib.IsInsideChroot():
39 raise
David Pursell9476bf42015-03-30 13:34:27 -070040
41
Alex Klein1699fab2022-09-08 08:46:06 -060042_DEVICE_BASE_DIR = "/usr/local/tmp/cros-deploy"
David Pursell9476bf42015-03-30 13:34:27 -070043# This is defined in src/platform/dev/builder.py
Alex Klein1699fab2022-09-08 08:46:06 -060044_STRIPPED_PACKAGES_DIR = "stripped-packages"
David Pursell9476bf42015-03-30 13:34:27 -070045
46_MAX_UPDATES_NUM = 10
47_MAX_UPDATES_WARNING = (
Alex Klein1699fab2022-09-08 08:46:06 -060048 "You are about to update a large number of installed packages, which "
49 "might take a long time, fail midway, or leave the target in an "
50 "inconsistent state. It is highly recommended that you flash a new image "
51 "instead."
52)
David Pursell9476bf42015-03-30 13:34:27 -070053
Alex Klein1699fab2022-09-08 08:46:06 -060054_DLC_ID = "DLC_ID"
55_DLC_PACKAGE = "DLC_PACKAGE"
56_DLC_ENABLED = "DLC_ENABLED"
57_ENVIRONMENT_FILENAME = "environment.bz2"
58_DLC_INSTALL_ROOT = "/var/cache/dlc"
Xiaochu Liu2726e7c2019-07-18 10:28:10 -070059
David Pursell9476bf42015-03-30 13:34:27 -070060
61class DeployError(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060062 """Thrown when an unrecoverable error is encountered during deploy."""
David Pursell9476bf42015-03-30 13:34:27 -070063
64
Ralph Nathane01ccf12015-04-16 10:40:32 -070065class BrilloDeployOperation(operation.ProgressBarOperation):
Alex Klein1699fab2022-09-08 08:46:06 -060066 """ProgressBarOperation specific for brillo deploy."""
Ralph Nathane01ccf12015-04-16 10:40:32 -070067
Alex Klein1699fab2022-09-08 08:46:06 -060068 # These two variables are used to validate the output in the VM integration
69 # tests. Changes to the output must be reflected here.
70 MERGE_EVENTS = (
71 "Preparing local packages",
72 "NOTICE: Copying binpkgs",
73 "NOTICE: Installing",
74 "been installed.",
75 "Please restart any updated",
76 )
77 UNMERGE_EVENTS = (
78 "NOTICE: Unmerging",
79 "been uninstalled.",
80 "Please restart any updated",
81 )
Ralph Nathane01ccf12015-04-16 10:40:32 -070082
Alex Klein1699fab2022-09-08 08:46:06 -060083 def __init__(self, emerge):
84 """Construct BrilloDeployOperation object.
Ralph Nathane01ccf12015-04-16 10:40:32 -070085
Alex Klein1699fab2022-09-08 08:46:06 -060086 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -060087 emerge: True if emerge, False is unmerge.
Alex Klein1699fab2022-09-08 08:46:06 -060088 """
89 super().__init__()
90 if emerge:
91 self._events = self.MERGE_EVENTS
92 else:
93 self._events = self.UNMERGE_EVENTS
94 self._total = len(self._events)
95 self._completed = 0
96
97 def ParseOutput(self, output=None):
98 """Parse the output of brillo deploy to update a progress bar."""
99 stdout = self._stdout.read()
100 stderr = self._stderr.read()
101 output = stdout + stderr
102 for event in self._events:
103 self._completed += output.count(event)
104 self.ProgressBar(self._completed / self._total)
Ralph Nathane01ccf12015-04-16 10:40:32 -0700105
106
David Pursell9476bf42015-03-30 13:34:27 -0700107class _InstallPackageScanner(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600108 """Finds packages that need to be installed on a target device.
David Pursell9476bf42015-03-30 13:34:27 -0700109
Alex Klein1699fab2022-09-08 08:46:06 -0600110 Scans the sysroot bintree, beginning with a user-provided list of packages,
111 to find all packages that need to be installed. If so instructed,
112 transitively scans forward (mandatory) and backward (optional) dependencies
113 as well. A package will be installed if missing on the target (mandatory
114 packages only), or it will be updated if its sysroot version and build time
115 are different from the target. Common usage:
David Pursell9476bf42015-03-30 13:34:27 -0700116
Alex Klein53cc3bf2022-10-13 08:50:01 -0600117 pkg_scanner = _InstallPackageScanner(sysroot)
118 pkgs = pkg_scanner.Run(...)
Alex Klein1699fab2022-09-08 08:46:06 -0600119 """
David Pursell9476bf42015-03-30 13:34:27 -0700120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 class VartreeError(Exception):
122 """An error in the processing of the installed packages tree."""
David Pursell9476bf42015-03-30 13:34:27 -0700123
Alex Klein1699fab2022-09-08 08:46:06 -0600124 class BintreeError(Exception):
125 """An error in the processing of the source binpkgs tree."""
David Pursell9476bf42015-03-30 13:34:27 -0700126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 class PkgInfo(object):
128 """A record containing package information."""
David Pursell9476bf42015-03-30 13:34:27 -0700129
Alex Klein1699fab2022-09-08 08:46:06 -0600130 __slots__ = ("cpv", "build_time", "rdeps_raw", "rdeps", "rev_rdeps")
David Pursell9476bf42015-03-30 13:34:27 -0700131
Alex Klein1699fab2022-09-08 08:46:06 -0600132 def __init__(
133 self, cpv, build_time, rdeps_raw, rdeps=None, rev_rdeps=None
134 ):
135 self.cpv = cpv
136 self.build_time = build_time
137 self.rdeps_raw = rdeps_raw
138 self.rdeps = set() if rdeps is None else rdeps
139 self.rev_rdeps = set() if rev_rdeps is None else rev_rdeps
David Pursell9476bf42015-03-30 13:34:27 -0700140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 # Python snippet for dumping vartree info on the target. Instantiate using
142 # _GetVartreeSnippet().
143 _GET_VARTREE = """
David Pursell9476bf42015-03-30 13:34:27 -0700144import json
Gwendal Grignou99e6f532018-10-25 12:16:28 -0700145import os
146import portage
147
148# Normalize the path to match what portage will index.
149target_root = os.path.normpath('%(root)s')
150if not target_root.endswith('/'):
151 target_root += '/'
152trees = portage.create_trees(target_root=target_root, config_root='/')
153vartree = trees[target_root]['vartree']
David Pursell9476bf42015-03-30 13:34:27 -0700154pkg_info = []
155for cpv in vartree.dbapi.cpv_all():
156 slot, rdep_raw, build_time = vartree.dbapi.aux_get(
157 cpv, ('SLOT', 'RDEPEND', 'BUILD_TIME'))
158 pkg_info.append((cpv, slot, rdep_raw, build_time))
159
160print(json.dumps(pkg_info))
161"""
162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 def __init__(self, sysroot):
164 self.sysroot = sysroot
165 # Members containing the sysroot (binpkg) and target (installed) package DB.
166 self.target_db = None
167 self.binpkgs_db = None
168 # Members for managing the dependency resolution work queue.
169 self.queue = None
170 self.seen = None
171 self.listed = None
David Pursell9476bf42015-03-30 13:34:27 -0700172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 @staticmethod
174 def _GetCP(cpv):
175 """Returns the CP value for a given CPV string."""
176 attrs = package_info.SplitCPV(cpv, strict=False)
177 if not attrs.cp:
178 raise ValueError("Cannot get CP value for %s" % cpv)
179 return attrs.cp
David Pursell9476bf42015-03-30 13:34:27 -0700180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 @staticmethod
182 def _InDB(cp, slot, db):
183 """Returns whether CP and slot are found in a database (if provided)."""
184 cp_slots = db.get(cp) if db else None
185 return cp_slots is not None and (not slot or slot in cp_slots)
David Pursell9476bf42015-03-30 13:34:27 -0700186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 @staticmethod
188 def _AtomStr(cp, slot):
189 """Returns 'CP:slot' if slot is non-empty, else just 'CP'."""
190 return "%s:%s" % (cp, slot) if slot else cp
David Pursell9476bf42015-03-30 13:34:27 -0700191
Alex Klein1699fab2022-09-08 08:46:06 -0600192 @classmethod
193 def _GetVartreeSnippet(cls, root="/"):
194 """Returns a code snippet for dumping the vartree on the target.
David Pursell9476bf42015-03-30 13:34:27 -0700195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600197 root: The installation root.
David Pursell9476bf42015-03-30 13:34:27 -0700198
Alex Klein1699fab2022-09-08 08:46:06 -0600199 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600200 The said code snippet (string) with parameters filled in.
Alex Klein1699fab2022-09-08 08:46:06 -0600201 """
202 return cls._GET_VARTREE % {"root": root}
David Pursell9476bf42015-03-30 13:34:27 -0700203
Alex Klein1699fab2022-09-08 08:46:06 -0600204 @classmethod
205 def _StripDepAtom(cls, dep_atom, installed_db=None):
206 """Strips a dependency atom and returns a (CP, slot) pair."""
207 # TODO(garnold) This is a gross simplification of ebuild dependency
208 # semantics, stripping and ignoring various qualifiers (versions, slots,
209 # USE flag, negation) and will likely need to be fixed. chromium:447366.
David Pursell9476bf42015-03-30 13:34:27 -0700210
Alex Klein1699fab2022-09-08 08:46:06 -0600211 # Ignore unversioned blockers, leaving them for the user to resolve.
212 if dep_atom[0] == "!" and dep_atom[1] not in "<=>~":
213 return None, None
David Pursell9476bf42015-03-30 13:34:27 -0700214
Alex Klein1699fab2022-09-08 08:46:06 -0600215 cp = dep_atom
216 slot = None
217 require_installed = False
David Pursell9476bf42015-03-30 13:34:27 -0700218
Alex Klein1699fab2022-09-08 08:46:06 -0600219 # Versioned blockers should be updated, but only if already installed.
220 # These are often used for forcing cascaded updates of multiple packages,
221 # so we're treating them as ordinary constraints with hopes that it'll lead
222 # to the desired result.
223 if cp.startswith("!"):
224 cp = cp.lstrip("!")
225 require_installed = True
David Pursell9476bf42015-03-30 13:34:27 -0700226
Alex Klein1699fab2022-09-08 08:46:06 -0600227 # Remove USE flags.
228 if "[" in cp:
229 cp = cp[: cp.index("[")] + cp[cp.index("]") + 1 :]
David Pursell9476bf42015-03-30 13:34:27 -0700230
Alex Klein1699fab2022-09-08 08:46:06 -0600231 # Separate the slot qualifier and strip off subslots.
232 if ":" in cp:
233 cp, slot = cp.split(":")
234 for delim in ("/", "="):
235 slot = slot.split(delim, 1)[0]
David Pursell9476bf42015-03-30 13:34:27 -0700236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 # Strip version wildcards (right), comparators (left).
238 cp = cp.rstrip("*")
239 cp = cp.lstrip("<=>~")
David Pursell9476bf42015-03-30 13:34:27 -0700240
Alex Klein1699fab2022-09-08 08:46:06 -0600241 # Turn into CP form.
242 cp = cls._GetCP(cp)
David Pursell9476bf42015-03-30 13:34:27 -0700243
Alex Klein1699fab2022-09-08 08:46:06 -0600244 if require_installed and not cls._InDB(cp, None, installed_db):
245 return None, None
David Pursell9476bf42015-03-30 13:34:27 -0700246
Alex Klein1699fab2022-09-08 08:46:06 -0600247 return cp, slot
David Pursell9476bf42015-03-30 13:34:27 -0700248
Alex Klein1699fab2022-09-08 08:46:06 -0600249 @classmethod
250 def _ProcessDepStr(cls, dep_str, installed_db, avail_db):
251 """Resolves and returns a list of dependencies from a dependency string.
David Pursell9476bf42015-03-30 13:34:27 -0700252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 This parses a dependency string and returns a list of package names and
254 slots. Other atom qualifiers (version, sub-slot, block) are ignored. When
255 resolving disjunctive deps, we include all choices that are fully present
256 in |installed_db|. If none is present, we choose an arbitrary one that is
257 available.
David Pursell9476bf42015-03-30 13:34:27 -0700258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600260 dep_str: A raw dependency string.
261 installed_db: A database of installed packages.
262 avail_db: A database of packages available for installation.
David Pursell9476bf42015-03-30 13:34:27 -0700263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600265 A list of pairs (CP, slot).
David Pursell9476bf42015-03-30 13:34:27 -0700266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600268 ValueError: the dependencies string is malformed.
Alex Klein1699fab2022-09-08 08:46:06 -0600269 """
David Pursell9476bf42015-03-30 13:34:27 -0700270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 def ProcessSubDeps(dep_exp, disjunct):
272 """Parses and processes a dependency (sub)expression."""
273 deps = set()
274 default_deps = set()
275 sub_disjunct = False
276 for dep_sub_exp in dep_exp:
277 sub_deps = set()
David Pursell9476bf42015-03-30 13:34:27 -0700278
Alex Klein1699fab2022-09-08 08:46:06 -0600279 if isinstance(dep_sub_exp, (list, tuple)):
280 sub_deps = ProcessSubDeps(dep_sub_exp, sub_disjunct)
281 sub_disjunct = False
282 elif sub_disjunct:
283 raise ValueError("Malformed disjunctive operation in deps")
284 elif dep_sub_exp == "||":
285 sub_disjunct = True
286 elif dep_sub_exp.endswith("?"):
287 raise ValueError("Dependencies contain a conditional")
288 else:
289 cp, slot = cls._StripDepAtom(dep_sub_exp, installed_db)
290 if cp:
291 sub_deps = set([(cp, slot)])
292 elif disjunct:
293 raise ValueError("Atom in disjunct ignored")
David Pursell9476bf42015-03-30 13:34:27 -0700294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 # Handle sub-deps of a disjunctive expression.
296 if disjunct:
297 # Make the first available choice the default, for use in case that
298 # no option is installed.
299 if (
300 not default_deps
301 and avail_db is not None
302 and all(
303 cls._InDB(cp, slot, avail_db)
304 for cp, slot in sub_deps
305 )
306 ):
307 default_deps = sub_deps
David Pursell9476bf42015-03-30 13:34:27 -0700308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 # If not all sub-deps are installed, then don't consider them.
310 if not all(
311 cls._InDB(cp, slot, installed_db)
312 for cp, slot in sub_deps
313 ):
314 sub_deps = set()
David Pursell9476bf42015-03-30 13:34:27 -0700315
Alex Klein1699fab2022-09-08 08:46:06 -0600316 deps.update(sub_deps)
David Pursell9476bf42015-03-30 13:34:27 -0700317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 return deps or default_deps
David Pursell9476bf42015-03-30 13:34:27 -0700319
Alex Klein1699fab2022-09-08 08:46:06 -0600320 try:
321 return ProcessSubDeps(portage.dep.paren_reduce(dep_str), False)
322 except portage.exception.InvalidDependString as e:
323 raise ValueError("Invalid dep string: %s" % e)
324 except ValueError as e:
325 raise ValueError("%s: %s" % (e, dep_str))
David Pursell9476bf42015-03-30 13:34:27 -0700326
Alex Klein1699fab2022-09-08 08:46:06 -0600327 def _BuildDB(
328 self, cpv_info, process_rdeps, process_rev_rdeps, installed_db=None
329 ):
330 """Returns a database of packages given a list of CPV info.
David Pursell9476bf42015-03-30 13:34:27 -0700331
Alex Klein1699fab2022-09-08 08:46:06 -0600332 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600333 cpv_info: A list of tuples containing package CPV and attributes.
334 process_rdeps: Whether to populate forward dependencies.
335 process_rev_rdeps: Whether to populate reverse dependencies.
336 installed_db: A database of installed packages for filtering
337 disjunctive choices against; if None, using own built database.
David Pursell9476bf42015-03-30 13:34:27 -0700338
Alex Klein1699fab2022-09-08 08:46:06 -0600339 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600340 A map from CP values to another dictionary that maps slots
341 to package attribute tuples. Tuples contain a CPV value
342 (string), build time (string), runtime dependencies (set),
343 and reverse dependencies (set, empty if not populated).
David Pursell9476bf42015-03-30 13:34:27 -0700344
Alex Klein1699fab2022-09-08 08:46:06 -0600345 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600346 ValueError: If more than one CPV occupies a single slot.
Alex Klein1699fab2022-09-08 08:46:06 -0600347 """
348 db = {}
349 logging.debug("Populating package DB...")
350 for cpv, slot, rdeps_raw, build_time in cpv_info:
351 cp = self._GetCP(cpv)
352 cp_slots = db.setdefault(cp, dict())
353 if slot in cp_slots:
354 raise ValueError(
355 "More than one package found for %s"
356 % self._AtomStr(cp, slot)
357 )
358 logging.debug(
359 " %s -> %s, built %s, raw rdeps: %s",
360 self._AtomStr(cp, slot),
361 cpv,
362 build_time,
363 rdeps_raw,
364 )
365 cp_slots[slot] = self.PkgInfo(cpv, build_time, rdeps_raw)
David Pursell9476bf42015-03-30 13:34:27 -0700366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 avail_db = db
368 if installed_db is None:
369 installed_db = db
370 avail_db = None
David Pursell9476bf42015-03-30 13:34:27 -0700371
Alex Klein1699fab2022-09-08 08:46:06 -0600372 # Add approximate forward dependencies.
David Pursell9476bf42015-03-30 13:34:27 -0700373 if process_rdeps:
Alex Klein1699fab2022-09-08 08:46:06 -0600374 logging.debug("Populating forward dependencies...")
375 for cp, cp_slots in db.items():
376 for slot, pkg_info in cp_slots.items():
377 pkg_info.rdeps.update(
378 self._ProcessDepStr(
379 pkg_info.rdeps_raw, installed_db, avail_db
380 )
381 )
382 logging.debug(
383 " %s (%s) processed rdeps: %s",
384 self._AtomStr(cp, slot),
385 pkg_info.cpv,
386 " ".join(
387 [
388 self._AtomStr(rdep_cp, rdep_slot)
389 for rdep_cp, rdep_slot in pkg_info.rdeps
390 ]
391 ),
392 )
393
394 # Add approximate reverse dependencies (optional).
David Pursell9476bf42015-03-30 13:34:27 -0700395 if process_rev_rdeps:
Alex Klein1699fab2022-09-08 08:46:06 -0600396 logging.debug("Populating reverse dependencies...")
397 for cp, cp_slots in db.items():
398 for slot, pkg_info in cp_slots.items():
399 for rdep_cp, rdep_slot in pkg_info.rdeps:
400 to_slots = db.get(rdep_cp)
401 if not to_slots:
402 continue
David Pursell9476bf42015-03-30 13:34:27 -0700403
Alex Klein1699fab2022-09-08 08:46:06 -0600404 for to_slot, to_pkg_info in to_slots.items():
405 if rdep_slot and to_slot != rdep_slot:
406 continue
407 logging.debug(
408 " %s (%s) added as rev rdep for %s (%s)",
409 self._AtomStr(cp, slot),
410 pkg_info.cpv,
411 self._AtomStr(rdep_cp, to_slot),
412 to_pkg_info.cpv,
413 )
414 to_pkg_info.rev_rdeps.add((cp, slot))
David Pursell9476bf42015-03-30 13:34:27 -0700415
Alex Klein1699fab2022-09-08 08:46:06 -0600416 return db
David Pursell9476bf42015-03-30 13:34:27 -0700417
Alex Klein1699fab2022-09-08 08:46:06 -0600418 def _InitTargetVarDB(self, device, root, process_rdeps, process_rev_rdeps):
419 """Initializes a dictionary of packages installed on |device|."""
420 get_vartree_script = self._GetVartreeSnippet(root)
421 try:
Mike Frysingerc0780a62022-08-29 04:41:56 -0400422 result = device.agent.RemoteSh(
Alex Klein1699fab2022-09-08 08:46:06 -0600423 ["python"], remote_sudo=True, input=get_vartree_script
424 )
425 except cros_build_lib.RunCommandError as e:
426 logging.error("Cannot get target vartree:\n%s", e.stderr)
427 raise
David Pursell9476bf42015-03-30 13:34:27 -0700428
Alex Klein1699fab2022-09-08 08:46:06 -0600429 try:
430 self.target_db = self._BuildDB(
431 json.loads(result.stdout), process_rdeps, process_rev_rdeps
432 )
433 except ValueError as e:
434 raise self.VartreeError(str(e))
David Pursell9476bf42015-03-30 13:34:27 -0700435
Alex Klein1699fab2022-09-08 08:46:06 -0600436 def _InitBinpkgDB(self, process_rdeps):
437 """Initializes a dictionary of binary packages for updating the target."""
438 # Get build root trees; portage indexes require a trailing '/'.
439 build_root = os.path.join(self.sysroot, "")
440 trees = portage.create_trees(
441 target_root=build_root, config_root=build_root
442 )
443 bintree = trees[build_root]["bintree"]
444 binpkgs_info = []
445 for cpv in bintree.dbapi.cpv_all():
446 slot, rdep_raw, build_time = bintree.dbapi.aux_get(
447 cpv, ["SLOT", "RDEPEND", "BUILD_TIME"]
448 )
449 binpkgs_info.append((cpv, slot, rdep_raw, build_time))
David Pursell9476bf42015-03-30 13:34:27 -0700450
Alex Klein1699fab2022-09-08 08:46:06 -0600451 try:
452 self.binpkgs_db = self._BuildDB(
453 binpkgs_info, process_rdeps, False, installed_db=self.target_db
454 )
455 except ValueError as e:
456 raise self.BintreeError(str(e))
David Pursell9476bf42015-03-30 13:34:27 -0700457
Alex Klein1699fab2022-09-08 08:46:06 -0600458 def _InitDepQueue(self):
459 """Initializes the dependency work queue."""
460 self.queue = set()
461 self.seen = {}
462 self.listed = set()
David Pursell9476bf42015-03-30 13:34:27 -0700463
Alex Klein1699fab2022-09-08 08:46:06 -0600464 def _EnqDep(self, dep, listed, optional):
465 """Enqueues a dependency if not seen before or if turned non-optional."""
466 if dep in self.seen and (optional or not self.seen[dep]):
467 return False
David Pursell9476bf42015-03-30 13:34:27 -0700468
Alex Klein1699fab2022-09-08 08:46:06 -0600469 self.queue.add(dep)
470 self.seen[dep] = optional
471 if listed:
472 self.listed.add(dep)
473 return True
David Pursell9476bf42015-03-30 13:34:27 -0700474
Alex Klein1699fab2022-09-08 08:46:06 -0600475 def _DeqDep(self):
476 """Dequeues and returns a dependency, its listed and optional flags.
David Pursell9476bf42015-03-30 13:34:27 -0700477
Alex Klein1699fab2022-09-08 08:46:06 -0600478 This returns listed packages first, if any are present, to ensure that we
479 correctly mark them as such when they are first being processed.
480 """
481 if self.listed:
482 dep = self.listed.pop()
483 self.queue.remove(dep)
484 listed = True
485 else:
486 dep = self.queue.pop()
487 listed = False
David Pursell9476bf42015-03-30 13:34:27 -0700488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 return dep, listed, self.seen[dep]
David Pursell9476bf42015-03-30 13:34:27 -0700490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 def _FindPackageMatches(self, cpv_pattern):
492 """Returns list of binpkg (CP, slot) pairs that match |cpv_pattern|.
David Pursell9476bf42015-03-30 13:34:27 -0700493
Alex Klein1699fab2022-09-08 08:46:06 -0600494 This is breaking |cpv_pattern| into its C, P and V components, each of
495 which may or may not be present or contain wildcards. It then scans the
496 binpkgs database to find all atoms that match these components, returning a
497 list of CP and slot qualifier. When the pattern does not specify a version,
498 or when a CP has only one slot in the binpkgs database, we omit the slot
499 qualifier in the result.
David Pursell9476bf42015-03-30 13:34:27 -0700500
Alex Klein1699fab2022-09-08 08:46:06 -0600501 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600502 cpv_pattern: A CPV pattern, potentially partial and/or having
503 wildcards.
David Pursell9476bf42015-03-30 13:34:27 -0700504
Alex Klein1699fab2022-09-08 08:46:06 -0600505 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600506 A list of (CPV, slot) pairs of packages in the binpkgs database that
507 match the pattern.
Alex Klein1699fab2022-09-08 08:46:06 -0600508 """
509 attrs = package_info.SplitCPV(cpv_pattern, strict=False)
510 cp_pattern = os.path.join(attrs.category or "*", attrs.package or "*")
511 matches = []
512 for cp, cp_slots in self.binpkgs_db.items():
513 if not fnmatch.fnmatchcase(cp, cp_pattern):
514 continue
David Pursell9476bf42015-03-30 13:34:27 -0700515
Alex Klein1699fab2022-09-08 08:46:06 -0600516 # If no version attribute was given or there's only one slot, omit the
517 # slot qualifier.
518 if not attrs.version or len(cp_slots) == 1:
519 matches.append((cp, None))
520 else:
521 cpv_pattern = "%s-%s" % (cp, attrs.version)
522 for slot, pkg_info in cp_slots.items():
523 if fnmatch.fnmatchcase(pkg_info.cpv, cpv_pattern):
524 matches.append((cp, slot))
David Pursell9476bf42015-03-30 13:34:27 -0700525
Alex Klein1699fab2022-09-08 08:46:06 -0600526 return matches
David Pursell9476bf42015-03-30 13:34:27 -0700527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 def _FindPackage(self, pkg):
529 """Returns the (CP, slot) pair for a package matching |pkg|.
David Pursell9476bf42015-03-30 13:34:27 -0700530
Alex Klein1699fab2022-09-08 08:46:06 -0600531 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600532 pkg: Path to a binary package or a (partial) package CPV specifier.
David Pursell9476bf42015-03-30 13:34:27 -0700533
Alex Klein1699fab2022-09-08 08:46:06 -0600534 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600535 A (CP, slot) pair for the given package; slot may be None
536 (unspecified).
David Pursell9476bf42015-03-30 13:34:27 -0700537
Alex Klein1699fab2022-09-08 08:46:06 -0600538 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600539 ValueError: if |pkg| is not a binpkg file nor does it match
540 something that's in the bintree.
Alex Klein1699fab2022-09-08 08:46:06 -0600541 """
542 if pkg.endswith(".tbz2") and os.path.isfile(pkg):
543 package = os.path.basename(os.path.splitext(pkg)[0])
544 category = os.path.basename(os.path.dirname(pkg))
545 return self._GetCP(os.path.join(category, package)), None
David Pursell9476bf42015-03-30 13:34:27 -0700546
Alex Klein1699fab2022-09-08 08:46:06 -0600547 matches = self._FindPackageMatches(pkg)
548 if not matches:
549 raise ValueError("No package found for %s" % pkg)
Xiaochu Liu2726e7c2019-07-18 10:28:10 -0700550
Alex Klein1699fab2022-09-08 08:46:06 -0600551 idx = 0
552 if len(matches) > 1:
553 # Ask user to pick among multiple matches.
554 idx = cros_build_lib.GetChoice(
555 "Multiple matches found for %s: " % pkg,
556 ["%s:%s" % (cp, slot) if slot else cp for cp, slot in matches],
557 )
Xiaochu Liu2726e7c2019-07-18 10:28:10 -0700558
Alex Klein1699fab2022-09-08 08:46:06 -0600559 return matches[idx]
560
561 def _NeedsInstall(self, cpv, slot, build_time, optional):
562 """Returns whether a package needs to be installed on the target.
563
564 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600565 cpv: Fully qualified CPV (string) of the package.
566 slot: Slot identifier (string).
567 build_time: The BUILT_TIME value (string) of the binpkg.
568 optional: Whether package is optional on the target.
Alex Klein1699fab2022-09-08 08:46:06 -0600569
570 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600571 A tuple (install, update) indicating whether to |install| the
572 package and whether it is an |update| to an existing package.
Alex Klein1699fab2022-09-08 08:46:06 -0600573
574 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600575 ValueError: if slot is not provided.
Alex Klein1699fab2022-09-08 08:46:06 -0600576 """
577 # If not checking installed packages, always install.
578 if not self.target_db:
579 return True, False
580
581 cp = self._GetCP(cpv)
582 target_pkg_info = self.target_db.get(cp, dict()).get(slot)
583 if target_pkg_info is not None:
584 if cpv != target_pkg_info.cpv:
585 attrs = package_info.SplitCPV(cpv)
586 target_attrs = package_info.SplitCPV(target_pkg_info.cpv)
587 logging.debug(
588 "Updating %s: version (%s) different on target (%s)",
589 cp,
590 attrs.version,
591 target_attrs.version,
592 )
593 return True, True
594
595 if build_time != target_pkg_info.build_time:
596 logging.debug(
597 "Updating %s: build time (%s) different on target (%s)",
598 cpv,
599 build_time,
600 target_pkg_info.build_time,
601 )
602 return True, True
603
604 logging.debug(
605 "Not updating %s: already up-to-date (%s, built %s)",
606 cp,
607 target_pkg_info.cpv,
608 target_pkg_info.build_time,
609 )
610 return False, False
611
612 if optional:
613 logging.debug(
614 "Not installing %s: missing on target but optional", cp
615 )
616 return False, False
617
618 logging.debug(
619 "Installing %s: missing on target and non-optional (%s)", cp, cpv
620 )
621 return True, False
622
623 def _ProcessDeps(self, deps, reverse):
624 """Enqueues dependencies for processing.
625
626 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600627 deps: List of dependencies to enqueue.
628 reverse: Whether these are reverse dependencies.
Alex Klein1699fab2022-09-08 08:46:06 -0600629 """
630 if not deps:
631 return
632
633 logging.debug(
634 "Processing %d %s dep(s)...",
635 len(deps),
636 "reverse" if reverse else "forward",
637 )
638 num_already_seen = 0
639 for dep in deps:
640 if self._EnqDep(dep, False, reverse):
641 logging.debug(" Queued dep %s", dep)
642 else:
643 num_already_seen += 1
644
645 if num_already_seen:
646 logging.debug("%d dep(s) already seen", num_already_seen)
647
648 def _ComputeInstalls(self, process_rdeps, process_rev_rdeps):
649 """Returns a dictionary of packages that need to be installed on the target.
650
651 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600652 process_rdeps: Whether to trace forward dependencies.
653 process_rev_rdeps: Whether to trace backward dependencies as well.
Alex Klein1699fab2022-09-08 08:46:06 -0600654
655 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600656 A dictionary mapping CP values (string) to tuples containing
657 a CPV (string), a slot (string), a boolean indicating whether
658 the package was initially listed in the queue, and a boolean
659 indicating whether this is an update to an existing package.
Alex Klein1699fab2022-09-08 08:46:06 -0600660 """
661 installs = {}
662 while self.queue:
663 dep, listed, optional = self._DeqDep()
664 cp, required_slot = dep
665 if cp in installs:
666 logging.debug("Already updating %s", cp)
667 continue
668
669 cp_slots = self.binpkgs_db.get(cp, dict())
670 logging.debug(
671 "Checking packages matching %s%s%s...",
672 cp,
673 " (slot: %s)" % required_slot if required_slot else "",
674 " (optional)" if optional else "",
675 )
676 num_processed = 0
677 for slot, pkg_info in cp_slots.items():
678 if required_slot and slot != required_slot:
679 continue
680
681 num_processed += 1
682 logging.debug(" Checking %s...", pkg_info.cpv)
683
684 install, update = self._NeedsInstall(
685 pkg_info.cpv, slot, pkg_info.build_time, optional
686 )
687 if not install:
688 continue
689
690 installs[cp] = (pkg_info.cpv, slot, listed, update)
691
692 # Add forward and backward runtime dependencies to queue.
693 if process_rdeps:
694 self._ProcessDeps(pkg_info.rdeps, False)
695 if process_rev_rdeps:
696 target_pkg_info = self.target_db.get(cp, dict()).get(slot)
697 if target_pkg_info:
698 self._ProcessDeps(target_pkg_info.rev_rdeps, True)
699
700 if num_processed == 0:
701 logging.warning(
702 "No qualified bintree package corresponding to %s", cp
703 )
704
705 return installs
706
707 def _SortInstalls(self, installs):
708 """Returns a sorted list of packages to install.
709
710 Performs a topological sort based on dependencies found in the binary
711 package database.
712
713 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600714 installs: Dictionary of packages to install indexed by CP.
Alex Klein1699fab2022-09-08 08:46:06 -0600715
716 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600717 A list of package CPVs (string).
Alex Klein1699fab2022-09-08 08:46:06 -0600718
719 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600720 ValueError: If dependency graph contains a cycle.
Alex Klein1699fab2022-09-08 08:46:06 -0600721 """
722 not_visited = set(installs.keys())
723 curr_path = []
724 sorted_installs = []
725
726 def SortFrom(cp):
727 """Traverses dependencies recursively, emitting nodes in reverse order."""
728 cpv, slot, _, _ = installs[cp]
729 if cpv in curr_path:
730 raise ValueError(
731 "Dependencies contain a cycle: %s -> %s"
732 % (" -> ".join(curr_path[curr_path.index(cpv) :]), cpv)
733 )
734 curr_path.append(cpv)
735 for rdep_cp, _ in self.binpkgs_db[cp][slot].rdeps:
736 if rdep_cp in not_visited:
737 not_visited.remove(rdep_cp)
738 SortFrom(rdep_cp)
739
740 sorted_installs.append(cpv)
741 curr_path.pop()
742
743 # So long as there's more packages, keep expanding dependency paths.
744 while not_visited:
745 SortFrom(not_visited.pop())
746
747 return sorted_installs
748
749 def _EnqListedPkg(self, pkg):
750 """Finds and enqueues a listed package."""
751 cp, slot = self._FindPackage(pkg)
752 if cp not in self.binpkgs_db:
753 raise self.BintreeError(
754 "Package %s not found in binpkgs tree" % pkg
755 )
756 self._EnqDep((cp, slot), True, False)
757
758 def _EnqInstalledPkgs(self):
759 """Enqueues all available binary packages that are already installed."""
760 for cp, cp_slots in self.binpkgs_db.items():
761 target_cp_slots = self.target_db.get(cp)
762 if target_cp_slots:
763 for slot in cp_slots.keys():
764 if slot in target_cp_slots:
765 self._EnqDep((cp, slot), True, False)
766
767 def Run(
768 self,
769 device,
770 root,
771 listed_pkgs,
772 update,
773 process_rdeps,
774 process_rev_rdeps,
775 ):
776 """Computes the list of packages that need to be installed on a target.
777
778 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600779 device: Target handler object.
780 root: Package installation root.
781 listed_pkgs: Package names/files listed by the user.
782 update: Whether to read the target's installed package database.
783 process_rdeps: Whether to trace forward dependencies.
784 process_rev_rdeps: Whether to trace backward dependencies as well.
Alex Klein1699fab2022-09-08 08:46:06 -0600785
786 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600787 A tuple (sorted, listed, num_updates, install_attrs) where |sorted|
788 is a list of package CPVs (string) to install on the target
789 in an order that satisfies their inter-dependencies, |listed|
790 the subset that was requested by the user, and |num_updates|
791 the number of packages being installed over preexisting
792 versions. Note that installation order should be reversed for
793 removal, |install_attrs| is a dictionary mapping a package
794 CPV (string) to some of its extracted environment attributes.
Alex Klein1699fab2022-09-08 08:46:06 -0600795 """
796 if process_rev_rdeps and not process_rdeps:
797 raise ValueError(
798 "Must processing forward deps when processing rev deps"
799 )
800 if process_rdeps and not update:
801 raise ValueError(
802 "Must check installed packages when processing deps"
803 )
804
805 if update:
806 logging.info("Initializing target intalled packages database...")
807 self._InitTargetVarDB(
808 device, root, process_rdeps, process_rev_rdeps
809 )
810
811 logging.info("Initializing binary packages database...")
812 self._InitBinpkgDB(process_rdeps)
813
814 logging.info("Finding listed package(s)...")
815 self._InitDepQueue()
816 for pkg in listed_pkgs:
817 if pkg == "@installed":
818 if not update:
819 raise ValueError(
820 "Must check installed packages when updating all of them."
821 )
822 self._EnqInstalledPkgs()
823 else:
824 self._EnqListedPkg(pkg)
825
826 logging.info("Computing set of packages to install...")
827 installs = self._ComputeInstalls(process_rdeps, process_rev_rdeps)
828
829 num_updates = 0
830 listed_installs = []
831 for cpv, _, listed, isupdate in installs.values():
832 if listed:
833 listed_installs.append(cpv)
834 if isupdate:
835 num_updates += 1
836
837 logging.info(
838 "Processed %d package(s), %d will be installed, %d are "
839 "updating existing packages",
840 len(self.seen),
841 len(installs),
842 num_updates,
843 )
844
845 sorted_installs = self._SortInstalls(installs)
846
847 install_attrs = {}
848 for pkg in sorted_installs:
849 pkg_path = os.path.join(root, portage_util.VDB_PATH, pkg)
850 dlc_id, dlc_package = _GetDLCInfo(device, pkg_path, from_dut=True)
851 install_attrs[pkg] = {}
852 if dlc_id and dlc_package:
853 install_attrs[pkg][_DLC_ID] = dlc_id
854
855 return sorted_installs, listed_installs, num_updates, install_attrs
David Pursell9476bf42015-03-30 13:34:27 -0700856
857
Mike Frysinger63d35512021-01-26 23:16:13 -0500858def _Emerge(device, pkg_paths, root, extra_args=None):
Alex Klein1699fab2022-09-08 08:46:06 -0600859 """Copies |pkg_paths| to |device| and emerges them.
David Pursell9476bf42015-03-30 13:34:27 -0700860
Alex Klein1699fab2022-09-08 08:46:06 -0600861 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600862 device: A ChromiumOSDevice object.
863 pkg_paths: (Local) paths to binary packages.
864 root: Package installation root path.
865 extra_args: Extra arguments to pass to emerge.
David Pursell9476bf42015-03-30 13:34:27 -0700866
Alex Klein1699fab2022-09-08 08:46:06 -0600867 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600868 DeployError: Unrecoverable error during emerge.
Alex Klein1699fab2022-09-08 08:46:06 -0600869 """
Mike Frysinger63d35512021-01-26 23:16:13 -0500870
Alex Klein1699fab2022-09-08 08:46:06 -0600871 def path_to_name(pkg_path):
872 return os.path.basename(pkg_path)
Mike Frysinger63d35512021-01-26 23:16:13 -0500873
Alex Klein1699fab2022-09-08 08:46:06 -0600874 def path_to_category(pkg_path):
875 return os.path.basename(os.path.dirname(pkg_path))
David Pursell9476bf42015-03-30 13:34:27 -0700876
Alex Klein1699fab2022-09-08 08:46:06 -0600877 pkg_names = ", ".join(path_to_name(x) for x in pkg_paths)
David Pursell9476bf42015-03-30 13:34:27 -0700878
Alex Klein1699fab2022-09-08 08:46:06 -0600879 pkgroot = os.path.join(device.work_dir, "packages")
880 portage_tmpdir = os.path.join(device.work_dir, "portage-tmp")
881 # Clean out the dirs first if we had a previous emerge on the device so as to
882 # free up space for this emerge. The last emerge gets implicitly cleaned up
883 # when the device connection deletes its work_dir.
884 device.run(
885 f"cd {device.work_dir} && "
886 f"rm -rf packages portage-tmp && "
887 f"mkdir -p portage-tmp packages && "
888 f"cd packages && "
889 f'mkdir -p {" ".join(set(path_to_category(x) for x in pkg_paths))}',
890 shell=True,
891 remote_sudo=True,
892 )
Mike Frysinger63d35512021-01-26 23:16:13 -0500893
Alex Klein1699fab2022-09-08 08:46:06 -0600894 logging.info("Use portage temp dir %s", portage_tmpdir)
David Pursell9476bf42015-03-30 13:34:27 -0700895
Mike Frysinger63d35512021-01-26 23:16:13 -0500896 # This message is read by BrilloDeployOperation.
Alex Klein1699fab2022-09-08 08:46:06 -0600897 logging.notice("Copying binpkgs to device.")
898 for pkg_path in pkg_paths:
899 pkg_name = path_to_name(pkg_path)
900 logging.info("Copying %s", pkg_name)
901 pkg_dir = os.path.join(pkgroot, path_to_category(pkg_path))
902 device.CopyToDevice(
903 pkg_path, pkg_dir, mode="rsync", remote_sudo=True, compress=False
904 )
905
906 # This message is read by BrilloDeployOperation.
907 logging.notice("Installing: %s", pkg_names)
908
909 # We set PORTAGE_CONFIGROOT to '/usr/local' because by default all
910 # chromeos-base packages will be skipped due to the configuration
911 # in /etc/protage/make.profile/package.provided. However, there is
912 # a known bug that /usr/local/etc/portage is not setup properly
913 # (crbug.com/312041). This does not affect `cros deploy` because
914 # we do not use the preset PKGDIR.
915 extra_env = {
916 "FEATURES": "-sandbox",
917 "PKGDIR": pkgroot,
918 "PORTAGE_CONFIGROOT": "/usr/local",
919 "PORTAGE_TMPDIR": portage_tmpdir,
920 "PORTDIR": device.work_dir,
921 "CONFIG_PROTECT": "-*",
922 }
923
924 # --ignore-built-slot-operator-deps because we don't rebuild everything.
925 # It can cause errors, but that's expected with cros deploy since it's just a
926 # best effort to prevent developers avoid rebuilding an image every time.
927 cmd = [
928 "emerge",
929 "--usepkg",
930 "--ignore-built-slot-operator-deps=y",
931 "--root",
932 root,
933 ] + [os.path.join(pkgroot, *x.split("/")[-2:]) for x in pkg_paths]
934 if extra_args:
935 cmd.append(extra_args)
936
937 logging.warning(
938 "Ignoring slot dependencies! This may break things! e.g. "
939 "packages built against the old version may not be able to "
940 "load the new .so. This is expected, and you will just need "
941 "to build and flash a new image if you have problems."
942 )
943 try:
944 result = device.run(
945 cmd,
946 extra_env=extra_env,
947 remote_sudo=True,
948 capture_output=True,
949 debug_level=logging.INFO,
950 )
951
952 pattern = (
953 "A requested package will not be merged because "
954 "it is listed in package.provided"
955 )
956 output = result.stderr.replace("\n", " ").replace("\r", "")
957 if pattern in output:
958 error = (
959 "Package failed to emerge: %s\n"
960 "Remove %s from /etc/portage/make.profile/"
961 "package.provided/chromeos-base.packages\n"
962 "(also see crbug.com/920140 for more context)\n"
963 % (pattern, pkg_name)
964 )
965 cros_build_lib.Die(error)
966 except Exception:
967 logging.error("Failed to emerge packages %s", pkg_names)
968 raise
969 else:
970 # This message is read by BrilloDeployOperation.
971 logging.notice("Packages have been installed.")
David Pursell9476bf42015-03-30 13:34:27 -0700972
973
Qijiang Fand5958192019-07-26 12:32:36 +0900974def _RestoreSELinuxContext(device, pkgpath, root):
Alex Klein1699fab2022-09-08 08:46:06 -0600975 """Restore SELinux context for files in a given package.
Qijiang Fan8a945032019-04-25 20:53:29 +0900976
Alex Klein1699fab2022-09-08 08:46:06 -0600977 This reads the tarball from pkgpath, and calls restorecon on device to
978 restore SELinux context for files listed in the tarball, assuming those files
979 are installed to /
Qijiang Fan8a945032019-04-25 20:53:29 +0900980
Alex Klein1699fab2022-09-08 08:46:06 -0600981 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -0600982 device: a ChromiumOSDevice object
983 pkgpath: path to tarball
984 root: Package installation root path.
Alex Klein1699fab2022-09-08 08:46:06 -0600985 """
986 pkgroot = os.path.join(device.work_dir, "packages")
987 pkg_dirname = os.path.basename(os.path.dirname(pkgpath))
988 pkgpath_device = os.path.join(
989 pkgroot, pkg_dirname, os.path.basename(pkgpath)
990 )
991 # Testing shows restorecon splits on newlines instead of spaces.
992 device.run(
993 [
994 "cd",
995 root,
996 "&&",
997 "tar",
998 "tf",
999 pkgpath_device,
1000 "|",
1001 "restorecon",
1002 "-i",
1003 "-f",
1004 "-",
1005 ],
1006 remote_sudo=True,
1007 )
Qijiang Fan352d0eb2019-02-25 13:10:08 +09001008
1009
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001010def _GetPackagesByCPV(cpvs, strip, sysroot):
Alex Klein1699fab2022-09-08 08:46:06 -06001011 """Returns paths to binary packages corresponding to |cpvs|.
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001012
Alex Klein1699fab2022-09-08 08:46:06 -06001013 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001014 cpvs: List of CPV components given by package_info.SplitCPV().
1015 strip: True to run strip_package.
1016 sysroot: Sysroot path.
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001017
Alex Klein1699fab2022-09-08 08:46:06 -06001018 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001019 List of paths corresponding to |cpvs|.
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001020
Alex Klein1699fab2022-09-08 08:46:06 -06001021 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001022 DeployError: If a package is missing.
Alex Klein1699fab2022-09-08 08:46:06 -06001023 """
1024 packages_dir = None
1025 if strip:
1026 try:
1027 cros_build_lib.run(
1028 [
1029 os.path.join(
1030 constants.CHROMITE_SCRIPTS_DIR, "strip_package"
1031 ),
1032 "--sysroot",
1033 sysroot,
1034 ]
1035 + [cpv.cpf for cpv in cpvs]
1036 )
1037 packages_dir = _STRIPPED_PACKAGES_DIR
1038 except cros_build_lib.RunCommandError:
1039 logging.error(
1040 "Cannot strip packages %s", " ".join([str(cpv) for cpv in cpvs])
1041 )
1042 raise
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001043
Alex Klein1699fab2022-09-08 08:46:06 -06001044 paths = []
1045 for cpv in cpvs:
1046 path = portage_util.GetBinaryPackagePath(
1047 cpv.category,
1048 cpv.package,
1049 cpv.version,
1050 sysroot=sysroot,
1051 packages_dir=packages_dir,
1052 )
1053 if not path:
1054 raise DeployError("Missing package %s." % cpv)
1055 paths.append(path)
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001056
Alex Klein1699fab2022-09-08 08:46:06 -06001057 return paths
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001058
1059
1060def _GetPackagesPaths(pkgs, strip, sysroot):
Alex Klein1699fab2022-09-08 08:46:06 -06001061 """Returns paths to binary |pkgs|.
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001062
Alex Klein1699fab2022-09-08 08:46:06 -06001063 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001064 pkgs: List of package CPVs string.
1065 strip: Whether or not to run strip_package for CPV packages.
1066 sysroot: The sysroot path.
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001067
Alex Klein1699fab2022-09-08 08:46:06 -06001068 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001069 List of paths corresponding to |pkgs|.
Alex Klein1699fab2022-09-08 08:46:06 -06001070 """
1071 cpvs = [package_info.SplitCPV(p) for p in pkgs]
1072 return _GetPackagesByCPV(cpvs, strip, sysroot)
Gilad Arnold0e1b1da2015-06-10 06:41:05 -07001073
1074
Mike Frysinger22bb5502021-01-29 13:05:46 -05001075def _Unmerge(device, pkgs, root):
Alex Klein1699fab2022-09-08 08:46:06 -06001076 """Unmerges |pkgs| on |device|.
David Pursell9476bf42015-03-30 13:34:27 -07001077
Alex Klein1699fab2022-09-08 08:46:06 -06001078 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001079 device: A RemoteDevice object.
1080 pkgs: Package names.
1081 root: Package installation root path.
Alex Klein1699fab2022-09-08 08:46:06 -06001082 """
1083 pkg_names = ", ".join(os.path.basename(x) for x in pkgs)
Mike Frysinger22bb5502021-01-29 13:05:46 -05001084 # This message is read by BrilloDeployOperation.
Alex Klein1699fab2022-09-08 08:46:06 -06001085 logging.notice("Unmerging %s.", pkg_names)
1086 cmd = ["qmerge", "--yes"]
1087 # Check if qmerge is available on the device. If not, use emerge.
1088 if device.run(["qmerge", "--version"], check=False).returncode != 0:
1089 cmd = ["emerge"]
1090
1091 cmd += ["--unmerge", "--root", root]
1092 cmd.extend("f={x}" for x in pkgs)
1093 try:
1094 # Always showing the emerge output for clarity.
1095 device.run(
1096 cmd,
1097 capture_output=False,
1098 remote_sudo=True,
1099 debug_level=logging.INFO,
1100 )
1101 except Exception:
1102 logging.error("Failed to unmerge packages %s", pkg_names)
1103 raise
1104 else:
1105 # This message is read by BrilloDeployOperation.
1106 logging.notice("Packages have been uninstalled.")
David Pursell9476bf42015-03-30 13:34:27 -07001107
1108
1109def _ConfirmDeploy(num_updates):
Alex Klein1699fab2022-09-08 08:46:06 -06001110 """Returns whether we can continue deployment."""
1111 if num_updates > _MAX_UPDATES_NUM:
1112 logging.warning(_MAX_UPDATES_WARNING)
1113 return cros_build_lib.BooleanPrompt(default=False)
David Pursell9476bf42015-03-30 13:34:27 -07001114
Alex Klein1699fab2022-09-08 08:46:06 -06001115 return True
David Pursell9476bf42015-03-30 13:34:27 -07001116
1117
Andrew06a5f812020-01-23 08:08:32 -08001118def _EmergePackages(pkgs, device, strip, sysroot, root, board, emerge_args):
Alex Klein1699fab2022-09-08 08:46:06 -06001119 """Call _Emerge for each package in pkgs."""
Ben Pastene5f03b052019-08-12 18:03:24 -07001120 if device.IsSELinuxAvailable():
Alex Klein1699fab2022-09-08 08:46:06 -06001121 enforced = device.IsSELinuxEnforced()
1122 if enforced:
1123 device.run(["setenforce", "0"])
1124 else:
1125 enforced = False
Andrewc7e1c6b2020-02-27 16:03:53 -08001126
Alex Klein1699fab2022-09-08 08:46:06 -06001127 dlc_deployed = False
1128 # This message is read by BrilloDeployOperation.
1129 logging.info("Preparing local packages for transfer.")
1130 pkg_paths = _GetPackagesPaths(pkgs, strip, sysroot)
1131 # Install all the packages in one pass so inter-package blockers work.
1132 _Emerge(device, pkg_paths, root, extra_args=emerge_args)
1133 logging.info("Updating SELinux settings & DLC images.")
1134 for pkg_path in pkg_paths:
1135 if device.IsSELinuxAvailable():
1136 _RestoreSELinuxContext(device, pkg_path, root)
Mike Frysinger5f4c2742021-02-08 14:37:23 -05001137
Alex Klein1699fab2022-09-08 08:46:06 -06001138 dlc_id, dlc_package = _GetDLCInfo(device, pkg_path, from_dut=False)
1139 if dlc_id and dlc_package:
1140 _DeployDLCImage(device, sysroot, board, dlc_id, dlc_package)
1141 dlc_deployed = True
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001142
Alex Klein1699fab2022-09-08 08:46:06 -06001143 if dlc_deployed:
1144 # Clean up empty directories created by emerging DLCs.
1145 device.run(
1146 [
1147 "test",
1148 "-d",
1149 "/build/rootfs",
1150 "&&",
1151 "rmdir",
1152 "--ignore-fail-on-non-empty",
1153 "/build/rootfs",
1154 "/build",
1155 ],
1156 check=False,
1157 )
Mike Frysinger4eb5f4e2021-01-26 21:48:37 -05001158
Alex Klein1699fab2022-09-08 08:46:06 -06001159 if enforced:
1160 device.run(["setenforce", "1"])
1161
1162 # Restart dlcservice so it picks up the newly installed DLC modules (in case
1163 # we installed new DLC images).
1164 if dlc_deployed:
1165 device.run(["restart", "dlcservice"])
Ralph Nathane01ccf12015-04-16 10:40:32 -07001166
1167
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001168def _UnmergePackages(pkgs, device, root, pkgs_attrs):
Alex Klein1699fab2022-09-08 08:46:06 -06001169 """Call _Unmege for each package in pkgs."""
1170 dlc_uninstalled = False
1171 _Unmerge(device, pkgs, root)
1172 logging.info("Cleaning up DLC images.")
1173 for pkg in pkgs:
1174 if _UninstallDLCImage(device, pkgs_attrs[pkg]):
1175 dlc_uninstalled = True
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001176
Alex Klein1699fab2022-09-08 08:46:06 -06001177 # Restart dlcservice so it picks up the uninstalled DLC modules (in case we
1178 # uninstalled DLC images).
1179 if dlc_uninstalled:
1180 device.run(["restart", "dlcservice"])
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001181
1182
1183def _UninstallDLCImage(device, pkg_attrs):
Alex Klein1699fab2022-09-08 08:46:06 -06001184 """Uninstall a DLC image."""
1185 if _DLC_ID in pkg_attrs:
1186 dlc_id = pkg_attrs[_DLC_ID]
1187 logging.notice("Uninstalling DLC image for %s", dlc_id)
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001188
Alex Klein1699fab2022-09-08 08:46:06 -06001189 device.run(["dlcservice_util", "--uninstall", "--id=%s" % dlc_id])
1190 return True
1191 else:
1192 logging.debug("DLC_ID not found in package")
1193 return False
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001194
1195
Andrew06a5f812020-01-23 08:08:32 -08001196def _DeployDLCImage(device, sysroot, board, dlc_id, dlc_package):
Alex Klein1699fab2022-09-08 08:46:06 -06001197 """Deploy (install and mount) a DLC image.
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001198
Alex Klein1699fab2022-09-08 08:46:06 -06001199 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001200 device: A device object.
1201 sysroot: The sysroot path.
1202 board: Board to use.
1203 dlc_id: The DLC ID.
1204 dlc_package: The DLC package name.
Alex Klein1699fab2022-09-08 08:46:06 -06001205 """
1206 # Requires `sudo_rm` because installations of files are running with sudo.
1207 with osutils.TempDir(sudo_rm=True) as tempdir:
1208 temp_rootfs = Path(tempdir)
1209 # Build the DLC image if the image is outdated or doesn't exist.
1210 dlc_lib.InstallDlcImages(
1211 sysroot=sysroot, rootfs=temp_rootfs, dlc_id=dlc_id, board=board
1212 )
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001213
Alex Klein1699fab2022-09-08 08:46:06 -06001214 logging.debug("Uninstall DLC %s if it is installed.", dlc_id)
1215 try:
1216 device.run(["dlcservice_util", "--uninstall", "--id=%s" % dlc_id])
1217 except cros_build_lib.RunCommandError as e:
1218 logging.info(
1219 "Failed to uninstall DLC:%s. Continue anyway.", e.stderr
1220 )
1221 except Exception:
1222 logging.error("Failed to uninstall DLC.")
1223 raise
Andrewc7e1c6b2020-02-27 16:03:53 -08001224
Alex Klein1699fab2022-09-08 08:46:06 -06001225 # TODO(andrewlassalle): Copy the DLC image to the preload location instead
1226 # of to dlc_a and dlc_b, and let dlcserive install the images to their final
1227 # location.
1228 logging.notice("Deploy the DLC image for %s", dlc_id)
1229 dlc_img_path_src = os.path.join(
1230 sysroot,
1231 dlc_lib.DLC_BUILD_DIR,
1232 dlc_id,
1233 dlc_package,
1234 dlc_lib.DLC_IMAGE,
1235 )
1236 dlc_img_path = os.path.join(_DLC_INSTALL_ROOT, dlc_id, dlc_package)
1237 dlc_img_path_a = os.path.join(dlc_img_path, "dlc_a")
1238 dlc_img_path_b = os.path.join(dlc_img_path, "dlc_b")
1239 # Create directories for DLC images.
1240 device.run(["mkdir", "-p", dlc_img_path_a, dlc_img_path_b])
1241 # Copy images to the destination directories.
1242 device.CopyToDevice(
1243 dlc_img_path_src,
1244 os.path.join(dlc_img_path_a, dlc_lib.DLC_IMAGE),
1245 mode="rsync",
1246 )
1247 device.run(
1248 [
1249 "cp",
1250 os.path.join(dlc_img_path_a, dlc_lib.DLC_IMAGE),
1251 os.path.join(dlc_img_path_b, dlc_lib.DLC_IMAGE),
1252 ]
1253 )
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001254
Alex Klein1699fab2022-09-08 08:46:06 -06001255 # Set the proper perms and ownership so dlcservice can access the image.
1256 device.run(["chmod", "-R", "u+rwX,go+rX,go-w", _DLC_INSTALL_ROOT])
1257 device.run(["chown", "-R", "dlcservice:dlcservice", _DLC_INSTALL_ROOT])
Jae Hoon Kim2376e142022-09-03 00:18:58 +00001258
Alex Klein1699fab2022-09-08 08:46:06 -06001259 # Copy metadata to device.
1260 dest_meta_dir = Path("/") / dlc_lib.DLC_META_DIR / dlc_id / dlc_package
1261 device.run(["mkdir", "-p", dest_meta_dir])
1262 src_meta_dir = os.path.join(
1263 sysroot,
1264 dlc_lib.DLC_BUILD_DIR,
1265 dlc_id,
1266 dlc_package,
1267 dlc_lib.DLC_TMP_META_DIR,
1268 )
1269 device.CopyToDevice(
1270 src_meta_dir + "/",
1271 dest_meta_dir,
1272 mode="rsync",
1273 recursive=True,
1274 remote_sudo=True,
1275 )
Jae Hoon Kim2376e142022-09-03 00:18:58 +00001276
Alex Klein1699fab2022-09-08 08:46:06 -06001277 # TODO(kimjae): Make this generic so it recomputes all the DLCs + copies
1278 # over a fresh list of dm-verity digests instead of appending and keeping
1279 # the stale digests when developers are testing.
Jae Hoon Kim2376e142022-09-03 00:18:58 +00001280
Alex Klein1699fab2022-09-08 08:46:06 -06001281 # Copy the LoadPin dm-verity digests to device.
1282 loadpin = dlc_lib.DLC_LOADPIN_TRUSTED_VERITY_DIGESTS
1283 dst_loadpin = Path("/") / dlc_lib.DLC_META_DIR / loadpin
1284 src_loadpin = temp_rootfs / dlc_lib.DLC_META_DIR / loadpin
1285 if src_loadpin.exists():
1286 digests = set(osutils.ReadFile(src_loadpin).split())
1287 try:
1288 digests.update(device.CatFile(dst_loadpin).split())
1289 except remote_access.CatFileError:
1290 pass
Jae Hoon Kim2376e142022-09-03 00:18:58 +00001291
Alex Klein1699fab2022-09-08 08:46:06 -06001292 with tempfile.NamedTemporaryFile(dir=temp_rootfs) as f:
1293 osutils.WriteFile(f.name, "\n".join(digests))
1294 device.CopyToDevice(
1295 f.name, dst_loadpin, mode="rsync", remote_sudo=True
1296 )
Andrew67b5fa72020-02-05 14:14:48 -08001297
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001298
1299def _GetDLCInfo(device, pkg_path, from_dut):
Alex Klein1699fab2022-09-08 08:46:06 -06001300 """Returns information of a DLC given its package path.
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001301
Alex Klein1699fab2022-09-08 08:46:06 -06001302 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001303 device: commandline.Device object; None to use the default device.
1304 pkg_path: path to the package.
1305 from_dut: True if extracting DLC info from DUT, False if extracting DLC
1306 info from host.
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001307
Alex Klein1699fab2022-09-08 08:46:06 -06001308 Returns:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001309 A tuple (dlc_id, dlc_package).
Alex Klein1699fab2022-09-08 08:46:06 -06001310 """
1311 environment_content = ""
1312 if from_dut:
1313 # On DUT, |pkg_path| is the directory which contains environment file.
1314 environment_path = os.path.join(pkg_path, _ENVIRONMENT_FILENAME)
1315 try:
1316 environment_data = device.CatFile(
1317 environment_path, max_size=None, encoding=None
1318 )
1319 except remote_access.CatFileError:
1320 # The package is not installed on DUT yet. Skip extracting info.
1321 return None, None
1322 else:
1323 # On host, pkg_path is tbz2 file which contains environment file.
1324 # Extract the metadata of the package file.
1325 data = portage.xpak.tbz2(pkg_path).get_data()
1326 environment_data = data[_ENVIRONMENT_FILENAME.encode("utf-8")]
1327
1328 # Extract the environment metadata.
1329 environment_content = bz2.decompress(environment_data)
1330
1331 with tempfile.NamedTemporaryFile() as f:
1332 # Dumps content into a file so we can use osutils.SourceEnvironment.
1333 path = os.path.realpath(f.name)
1334 osutils.WriteFile(path, environment_content, mode="wb")
1335 content = osutils.SourceEnvironment(
1336 path, (_DLC_ID, _DLC_PACKAGE, _DLC_ENABLED)
1337 )
1338
1339 dlc_enabled = content.get(_DLC_ENABLED)
1340 if dlc_enabled is not None and (
1341 dlc_enabled is False or str(dlc_enabled) == "false"
1342 ):
1343 logging.info("Installing DLC in rootfs.")
1344 return None, None
1345 return content.get(_DLC_ID), content.get(_DLC_PACKAGE)
1346
1347
1348def Deploy(
1349 device,
1350 packages,
1351 board=None,
1352 emerge=True,
1353 update=False,
1354 deep=False,
1355 deep_rev=False,
1356 clean_binpkg=True,
1357 root="/",
1358 strip=True,
1359 emerge_args=None,
1360 ssh_private_key=None,
1361 ping=True,
1362 force=False,
1363 dry_run=False,
1364):
1365 """Deploys packages to a device.
1366
1367 Args:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001368 device: commandline.Device object; None to use the default device.
1369 packages: List of packages (strings) to deploy to device.
1370 board: Board to use; None to automatically detect.
1371 emerge: True to emerge package, False to unmerge.
1372 update: Check installed version on device.
1373 deep: Install dependencies also. Implies |update|.
1374 deep_rev: Install reverse dependencies. Implies |deep|.
1375 clean_binpkg: Clean outdated binary packages.
1376 root: Package installation root path.
1377 strip: Run strip_package to filter out preset paths in the package.
1378 emerge_args: Extra arguments to pass to emerge.
1379 ssh_private_key: Path to an SSH private key file; None to use test keys.
1380 ping: True to ping the device before trying to connect.
1381 force: Ignore confidence checks and prompts.
1382 dry_run: Print deployment plan but do not deploy anything.
Alex Klein1699fab2022-09-08 08:46:06 -06001383
1384 Raises:
Alex Klein53cc3bf2022-10-13 08:50:01 -06001385 ValueError: Invalid parameter or parameter combination.
1386 DeployError: Unrecoverable failure during deploy.
Alex Klein1699fab2022-09-08 08:46:06 -06001387 """
1388 if deep_rev:
1389 deep = True
1390 if deep:
1391 update = True
1392
1393 if not packages:
1394 raise DeployError("No packages provided, nothing to deploy.")
1395
1396 if update and not emerge:
1397 raise ValueError("Cannot update and unmerge.")
1398
1399 if device:
1400 hostname, username, port = device.hostname, device.username, device.port
1401 else:
1402 hostname, username, port = None, None, None
1403
1404 lsb_release = None
1405 sysroot = None
Mike Frysingeracd06cd2021-01-27 13:33:52 -05001406 try:
Alex Klein1699fab2022-09-08 08:46:06 -06001407 # Somewhat confusing to clobber, but here we are.
1408 # pylint: disable=redefined-argument-from-local
1409 with remote_access.ChromiumOSDeviceHandler(
1410 hostname,
1411 port=port,
1412 username=username,
1413 private_key=ssh_private_key,
1414 base_dir=_DEVICE_BASE_DIR,
1415 ping=ping,
1416 ) as device:
1417 lsb_release = device.lsb_release
Mike Frysingeracd06cd2021-01-27 13:33:52 -05001418
Alex Klein1699fab2022-09-08 08:46:06 -06001419 board = cros_build_lib.GetBoard(
1420 device_board=device.board, override_board=board
1421 )
1422 if not force and board != device.board:
1423 raise DeployError(
1424 "Device (%s) is incompatible with board %s. Use "
1425 "--force to deploy anyway." % (device.board, board)
1426 )
Xiaochu Liu2726e7c2019-07-18 10:28:10 -07001427
Alex Klein1699fab2022-09-08 08:46:06 -06001428 sysroot = build_target_lib.get_default_sysroot_path(board)
Andrew67b5fa72020-02-05 14:14:48 -08001429
Alex Klein1699fab2022-09-08 08:46:06 -06001430 # Don't bother trying to clean for unmerges. We won't use the local db,
1431 # and it just slows things down for the user.
1432 if emerge and clean_binpkg:
1433 logging.notice(
1434 "Cleaning outdated binary packages from %s", sysroot
1435 )
1436 portage_util.CleanOutdatedBinaryPackages(sysroot)
Ralph Nathane01ccf12015-04-16 10:40:32 -07001437
Alex Klein1699fab2022-09-08 08:46:06 -06001438 # Remount rootfs as writable if necessary.
1439 if not device.MountRootfsReadWrite():
1440 raise DeployError(
1441 "Cannot remount rootfs as read-write. Exiting."
1442 )
Ralph Nathane01ccf12015-04-16 10:40:32 -07001443
Alex Klein1699fab2022-09-08 08:46:06 -06001444 # Obtain list of packages to upgrade/remove.
1445 pkg_scanner = _InstallPackageScanner(sysroot)
1446 pkgs, listed, num_updates, pkgs_attrs = pkg_scanner.Run(
1447 device, root, packages, update, deep, deep_rev
1448 )
1449 if emerge:
1450 action_str = "emerge"
1451 else:
1452 pkgs.reverse()
1453 action_str = "unmerge"
David Pursell9476bf42015-03-30 13:34:27 -07001454
Alex Klein1699fab2022-09-08 08:46:06 -06001455 if not pkgs:
1456 logging.notice("No packages to %s", action_str)
1457 return
David Pursell9476bf42015-03-30 13:34:27 -07001458
Alex Klein1699fab2022-09-08 08:46:06 -06001459 # Warn when the user installs & didn't `cros workon start`.
1460 if emerge:
1461 all_workon = workon_helper.WorkonHelper(sysroot).ListAtoms(
1462 use_all=True
1463 )
1464 worked_on_cps = workon_helper.WorkonHelper(sysroot).ListAtoms()
1465 for package in listed:
1466 cp = package_info.SplitCPV(package).cp
1467 if cp in all_workon and cp not in worked_on_cps:
1468 logging.warning(
1469 "Are you intentionally deploying unmodified packages, or did "
1470 "you forget to run `cros workon --board=$BOARD start %s`?",
1471 cp,
1472 )
David Pursell9476bf42015-03-30 13:34:27 -07001473
Alex Klein1699fab2022-09-08 08:46:06 -06001474 logging.notice("These are the packages to %s:", action_str)
1475 for i, pkg in enumerate(pkgs):
1476 logging.notice(
1477 "%s %d) %s", "*" if pkg in listed else " ", i + 1, pkg
1478 )
Gilad Arnolda0a98062015-07-07 08:34:27 -07001479
Alex Klein1699fab2022-09-08 08:46:06 -06001480 if dry_run or not _ConfirmDeploy(num_updates):
1481 return
David Pursell9476bf42015-03-30 13:34:27 -07001482
Alex Klein1699fab2022-09-08 08:46:06 -06001483 # Select function (emerge or unmerge) and bind args.
1484 if emerge:
1485 func = functools.partial(
1486 _EmergePackages,
1487 pkgs,
1488 device,
1489 strip,
1490 sysroot,
1491 root,
1492 board,
1493 emerge_args,
1494 )
1495 else:
1496 func = functools.partial(
1497 _UnmergePackages, pkgs, device, root, pkgs_attrs
1498 )
David Pursell2e773382015-04-03 14:30:47 -07001499
Alex Klein1699fab2022-09-08 08:46:06 -06001500 # Call the function with the progress bar or with normal output.
1501 if command.UseProgressBar():
1502 op = BrilloDeployOperation(emerge)
1503 op.Run(func, log_level=logging.DEBUG)
1504 else:
1505 func()
David Pursell9476bf42015-03-30 13:34:27 -07001506
Alex Klein1699fab2022-09-08 08:46:06 -06001507 if device.IsSELinuxAvailable():
1508 if sum(x.count("selinux-policy") for x in pkgs):
1509 logging.warning(
1510 "Deploying SELinux policy will not take effect until reboot. "
1511 "SELinux policy is loaded by init. Also, changing the security "
1512 "contexts (labels) of a file will require building a new image "
1513 "and flashing the image onto the device."
1514 )
Bertrand SIMONNET60c94492015-04-30 17:46:28 -07001515
Alex Klein1699fab2022-09-08 08:46:06 -06001516 # This message is read by BrilloDeployOperation.
Mike Frysinger5c7b9512020-12-04 02:30:56 -05001517 logging.warning(
Alex Klein1699fab2022-09-08 08:46:06 -06001518 "Please restart any updated services on the device, "
1519 "or just reboot it."
1520 )
1521 except Exception:
1522 if lsb_release:
1523 lsb_entries = sorted(lsb_release.items())
1524 logging.info(
1525 "Following are the LSB version details of the device:\n%s",
1526 "\n".join("%s=%s" % (k, v) for k, v in lsb_entries),
1527 )
1528 raise