blob: 763fadd0788fd19c2cb2d5dc6cc3c2873e739603 [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
Yaakov Shaul730814a2019-09-10 13:58:25 -060010import collections
Yaakov Shaulcb1cfc32019-09-16 13:51:19 -060011import fileinput
Alex Klein87531182019-08-12 15:23:37 -060012import functools
Yaakov Shaul395ae832019-09-09 14:45:32 -060013import json
Evan Hernandezb51f1522019-08-15 11:29:40 -060014import os
Michael Mortensenb70e8a82019-10-10 18:43:41 -060015import re
Yaakov Shaulcb1cfc32019-09-16 13:51:19 -060016import sys
Alex Klein87531182019-08-12 15:23:37 -060017
Yaakov Shaul730814a2019-09-10 13:58:25 -060018
Alex Kleineb77ffa2019-05-28 14:47:44 -060019from chromite.lib import constants
Evan Hernandezb51f1522019-08-15 11:29:40 -060020from chromite.lib import cros_build_lib
Alex Klein4de25e82019-08-05 15:58:39 -060021from chromite.lib import cros_logging as logging
Alex Kleineb77ffa2019-05-28 14:47:44 -060022from chromite.lib import git
Michael Mortensenb70e8a82019-10-10 18:43:41 -060023from chromite.lib import osutils
Alex Kleineb77ffa2019-05-28 14:47:44 -060024from chromite.lib import portage_util
Alex Kleind6195b62019-08-06 16:01:16 -060025from chromite.lib import uprev_lib
Alex Kleineb77ffa2019-05-28 14:47:44 -060026
Alex Klein36b117f2019-09-30 15:13:46 -060027if cros_build_lib.IsInsideChroot():
28 from chromite.service import dependency
29
Alex Klein87531182019-08-12 15:23:37 -060030# Registered handlers for uprevving versioned packages.
31_UPREV_FUNCS = {}
32
Alex Kleineb77ffa2019-05-28 14:47:44 -060033
34class Error(Exception):
35 """Module's base error class."""
36
37
Alex Klein4de25e82019-08-05 15:58:39 -060038class UnknownPackageError(Error):
39 """Uprev attempted for a package without a registered handler."""
40
41
Alex Kleineb77ffa2019-05-28 14:47:44 -060042class UprevError(Error):
43 """An error occurred while uprevving packages."""
44
45
Michael Mortensenb70e8a82019-10-10 18:43:41 -060046class NoAndroidVersionError(Error):
47 """An error occurred while trying to determine the android version."""
48
49
50class NoAndroidBranchError(Error):
51 """An error occurred while trying to determine the android branch."""
52
53
54class NoAndroidTargetError(Error):
55 """An error occurred while trying to determine the android target."""
56
57
Alex Klein4de25e82019-08-05 15:58:39 -060058class AndroidIsPinnedUprevError(UprevError):
59 """Raised when we try to uprev while Android is pinned."""
60
61 def __init__(self, new_android_atom):
62 """Initialize a AndroidIsPinnedUprevError.
63
64 Args:
65 new_android_atom: The Android atom that we failed to
66 uprev to, due to Android being pinned.
67 """
68 assert new_android_atom
69 msg = ('Failed up uprev to Android version %s as Android was pinned.' %
70 new_android_atom)
71 super(AndroidIsPinnedUprevError, self).__init__(msg)
72 self.new_android_atom = new_android_atom
Alex Klein87531182019-08-12 15:23:37 -060073
74
Yaakov Shaul1eafe832019-09-10 16:50:26 -060075class EbuildManifestError(Error):
76 """Error when running ebuild manifest."""
77
78
Yaakov Shaul730814a2019-09-10 13:58:25 -060079UprevVersionedPackageModifications = collections.namedtuple(
80 'UprevVersionedPackageModifications', ('new_version', 'files'))
Alex Klein34afcbc2019-08-22 16:14:31 -060081
Yaakov Shaul730814a2019-09-10 13:58:25 -060082
83class UprevVersionedPackageResult(object):
84 """Data object for uprev_versioned_package."""
85
86 def __init__(self):
87 self.modified = []
88
89 def add_result(self, new_version, modified_files):
90 """Adds version/ebuilds tuple to result.
91
92 Args:
93 new_version: New version number of package.
94 modified_files: List of files modified for the given version.
95 """
96 result = UprevVersionedPackageModifications(new_version, modified_files)
97 self.modified.append(result)
98 return self
Alex Klein34afcbc2019-08-22 16:14:31 -060099
100 @property
101 def uprevved(self):
Yaakov Shaul730814a2019-09-10 13:58:25 -0600102 return bool(self.modified)
Alex Klein34afcbc2019-08-22 16:14:31 -0600103
104
Yaakov Shaulcb1cfc32019-09-16 13:51:19 -0600105def patch_ebuild_vars(ebuild_path, variables):
106 """Updates variables in ebuild.
107
108 Use this function rather than portage_util.EBuild.UpdateEBuild when you
109 want to preserve the variable position and quotes within the ebuild.
110
111 Args:
112 ebuild_path: The path of the ebuild.
113 variables: Dictionary of variables to update in ebuild.
114 """
115 try:
116 for line in fileinput.input(ebuild_path, inplace=1):
117 varname, eq, _ = line.partition('=')
118 if eq == '=' and varname.strip() in variables:
119 value = variables[varname]
120 sys.stdout.write('%s="%s"\n' % (varname, value))
121 else:
122 sys.stdout.write(line)
123 finally:
124 fileinput.close()
125
126
Alex Klein87531182019-08-12 15:23:37 -0600127def uprevs_versioned_package(package):
128 """Decorator to register package uprev handlers."""
129 assert package
130
131 def register(func):
132 """Registers |func| as a handler for |package|."""
133 _UPREV_FUNCS[package] = func
134
135 @functools.wraps(func)
136 def pass_through(*args, **kwargs):
137 return func(*args, **kwargs)
138
139 return pass_through
140
141 return register
142
143
Alex Klein4de25e82019-08-05 15:58:39 -0600144def uprev_android(tracking_branch, android_package, android_build_branch,
145 chroot, build_targets=None, android_version=None,
146 android_gts_build_branch=None):
147 """Returns the portage atom for the revved Android ebuild - see man emerge."""
148 command = ['cros_mark_android_as_stable',
149 '--tracking_branch=%s' % tracking_branch,
150 '--android_package=%s' % android_package,
151 '--android_build_branch=%s' % android_build_branch]
152 if build_targets:
153 command.append('--boards=%s' % ':'.join(bt.name for bt in build_targets))
154 if android_version:
155 command.append('--force_version=%s' % android_version)
156 if android_gts_build_branch:
157 command.append('--android_gts_build_branch=%s' % android_gts_build_branch)
158
Mike Frysinger45602c72019-09-22 02:15:11 -0400159 result = cros_build_lib.run(command, redirect_stdout=True,
160 enter_chroot=True,
161 chroot_args=chroot.get_enter_args())
Alex Klein4de25e82019-08-05 15:58:39 -0600162
163 android_atom = _parse_android_atom(result)
164 if not android_atom:
165 logging.info('Found nothing to rev.')
166 return None
167
168 for target in build_targets or []:
169 # Sanity check: We should always be able to merge the version of
170 # Android we just unmasked.
171 command = ['emerge-%s' % target.name, '-p', '--quiet',
172 '=%s' % android_atom]
173 try:
Mike Frysinger45602c72019-09-22 02:15:11 -0400174 cros_build_lib.run(command, enter_chroot=True,
175 chroot_args=chroot.get_enter_args())
Alex Klein4de25e82019-08-05 15:58:39 -0600176 except cros_build_lib.RunCommandError:
177 logging.error(
178 'Cannot emerge-%s =%s\nIs Android pinned to an older '
179 'version?', target, android_atom)
180 raise AndroidIsPinnedUprevError(android_atom)
181
182 return android_atom
183
184
185def _parse_android_atom(result):
186 """Helper to parse the atom from the cros_mark_android_as_stable output.
187
188 This function is largely just intended to make testing easier.
189
190 Args:
191 result (cros_build_lib.CommandResult): The cros_mark_android_as_stable
192 command result.
193 """
194 portage_atom_string = result.output.strip()
195
196 android_atom = None
197 if portage_atom_string:
198 android_atom = portage_atom_string.splitlines()[-1].partition('=')[-1]
199
200 return android_atom
201
202
Alex Kleineb77ffa2019-05-28 14:47:44 -0600203def uprev_build_targets(build_targets, overlay_type, chroot=None,
204 output_dir=None):
205 """Uprev the set provided build targets, or all if not specified.
206
207 Args:
208 build_targets (list[build_target_util.BuildTarget]|None): The build targets
209 whose overlays should be uprevved, empty or None for all.
210 overlay_type (str): One of the valid overlay types except None (see
211 constants.VALID_OVERLAYS).
212 chroot (chroot_lib.Chroot|None): The chroot to clean, if desired.
213 output_dir (str|None): The path to optionally dump result files.
214 """
215 # Need a valid overlay, but exclude None.
216 assert overlay_type and overlay_type in constants.VALID_OVERLAYS
217
218 if build_targets:
219 overlays = portage_util.FindOverlaysForBoards(
220 overlay_type, boards=[t.name for t in build_targets])
221 else:
222 overlays = portage_util.FindOverlays(overlay_type)
223
224 return uprev_overlays(overlays, build_targets=build_targets, chroot=chroot,
225 output_dir=output_dir)
226
227
228def uprev_overlays(overlays, build_targets=None, chroot=None, output_dir=None):
229 """Uprev the given overlays.
230
231 Args:
232 overlays (list[str]): The list of overlay paths.
233 build_targets (list[build_target_util.BuildTarget]|None): The build targets
234 to clean in |chroot|, if desired. No effect unless |chroot| is provided.
235 chroot (chroot_lib.Chroot|None): The chroot to clean, if desired.
236 output_dir (str|None): The path to optionally dump result files.
237
238 Returns:
239 list[str] - The paths to all of the modified ebuild files. This includes the
240 new files that were added (i.e. the new versions) and all of the removed
241 files (i.e. the old versions).
242 """
243 assert overlays
244
245 manifest = git.ManifestCheckout.Cached(constants.SOURCE_ROOT)
246
Alex Kleind6195b62019-08-06 16:01:16 -0600247 uprev_manager = uprev_lib.UprevOverlayManager(overlays, manifest,
248 build_targets=build_targets,
249 chroot=chroot,
250 output_dir=output_dir)
Alex Kleineb77ffa2019-05-28 14:47:44 -0600251 uprev_manager.uprev()
252
253 return uprev_manager.modified_ebuilds
254
255
Alex Klein87531182019-08-12 15:23:37 -0600256def uprev_versioned_package(package, build_targets, refs, chroot):
257 """Call registered uprev handler function for the package.
258
259 Args:
260 package (portage_util.CPV): The package being uprevved.
261 build_targets (list[build_target_util.BuildTarget]): The build targets to
262 clean on a successful uprev.
263 refs (list[uprev_lib.GitRef]):
264 chroot (chroot_lib.Chroot): The chroot to enter for cleaning.
265
266 Returns:
Alex Klein34afcbc2019-08-22 16:14:31 -0600267 UprevVersionedPackageResult: The result.
Alex Klein87531182019-08-12 15:23:37 -0600268 """
269 assert package
270
271 if package.cp not in _UPREV_FUNCS:
272 raise UnknownPackageError(
273 'Package "%s" does not have a registered handler.' % package.cp)
274
275 return _UPREV_FUNCS[package.cp](build_targets, refs, chroot)
276
277
Evan Hernandezb51f1522019-08-15 11:29:40 -0600278# TODO(evanhernandez): Remove this. Only a quick hack for testing.
279@uprevs_versioned_package('sample/sample')
280def uprev_sample(*_args, **_kwargs):
281 """Mimics an uprev by changing files in sandbox repos.
282
283 See: uprev_versioned_package.
284 """
285 paths = [
286 os.path.join(constants.SOURCE_ROOT, 'infra/dummies', repo, 'sample.txt')
287 for repo in ('general-sandbox', 'merge-sandbox')
288 ]
289
Yaakov Shaul730814a2019-09-10 13:58:25 -0600290 return UprevVersionedPackageResult().add_result('1.2.3', paths)
Evan Hernandezb51f1522019-08-15 11:29:40 -0600291
292
Yaakov Shaul395ae832019-09-09 14:45:32 -0600293@uprevs_versioned_package('afdo/kernel-profiles')
294def uprev_kernel_afdo(*_args, **_kwargs):
David Burger92485342019-09-10 17:52:45 -0600295 """Updates kernel ebuilds with versions from kernel_afdo.json.
Yaakov Shaul395ae832019-09-09 14:45:32 -0600296
297 See: uprev_versioned_package.
Yaakov Shaul1eafe832019-09-10 16:50:26 -0600298
299 Raises:
300 EbuildManifestError: When ebuild manifest does not complete successfuly.
Yaakov Shaul395ae832019-09-09 14:45:32 -0600301 """
302 path = os.path.join(constants.SOURCE_ROOT, 'src', 'third_party',
303 'toolchain-utils', 'afdo_metadata', 'kernel_afdo.json')
304
David Burger92485342019-09-10 17:52:45 -0600305 with open(path, 'r') as f:
306 versions = json.load(f)
Yaakov Shaul395ae832019-09-09 14:45:32 -0600307
Yaakov Shaul730814a2019-09-10 13:58:25 -0600308 result = UprevVersionedPackageResult()
Yaakov Shaul395ae832019-09-09 14:45:32 -0600309 for version, version_info in versions.items():
Yaakov Shauldd8b4112019-09-11 11:44:03 -0600310 path = os.path.join('src', 'third_party', 'chromiumos-overlay',
311 'sys-kernel', version)
312 ebuild_path = os.path.join(constants.SOURCE_ROOT, path,
313 '%s-9999.ebuild' % version)
Yaakov Shaula187b152019-09-11 12:41:32 -0600314 chroot_ebuild_path = os.path.join(constants.CHROOT_SOURCE_ROOT, path,
315 '%s-9999.ebuild' % version)
Yaakov Shaul730814a2019-09-10 13:58:25 -0600316 afdo_profile_version = version_info['name']
Yaakov Shaulcb1cfc32019-09-16 13:51:19 -0600317 patch_ebuild_vars(ebuild_path,
318 dict(AFDO_PROFILE_VERSION=afdo_profile_version))
Yaakov Shaul1eafe832019-09-10 16:50:26 -0600319
320 try:
Yaakov Shaul730814a2019-09-10 13:58:25 -0600321 cmd = ['ebuild', chroot_ebuild_path, 'manifest', '--force']
Mike Frysinger45602c72019-09-22 02:15:11 -0400322 cros_build_lib.run(cmd, enter_chroot=True)
Yaakov Shaul1eafe832019-09-10 16:50:26 -0600323 except cros_build_lib.RunCommandError as e:
324 raise EbuildManifestError(
325 'Error encountered when regenerating the manifest for ebuild: %s\n%s'
Yaakov Shaula187b152019-09-11 12:41:32 -0600326 % (chroot_ebuild_path, e), e)
Yaakov Shaul1eafe832019-09-10 16:50:26 -0600327
Yaakov Shauldd8b4112019-09-11 11:44:03 -0600328 manifest_path = os.path.join(constants.SOURCE_ROOT, path, 'Manifest')
Yaakov Shaul1eafe832019-09-10 16:50:26 -0600329
Yaakov Shaul730814a2019-09-10 13:58:25 -0600330 result.add_result(afdo_profile_version, [ebuild_path, manifest_path])
331
332 return result
Yaakov Shaul395ae832019-09-09 14:45:32 -0600333
334
Yaakov Shaul45990452019-09-16 14:52:06 -0600335@uprevs_versioned_package('afdo/chrome-profiles')
336def uprev_chrome_afdo(*_args, **_kwargs):
337 """Updates chrome ebuilds with versions from chrome_afdo.json.
338
339 See: uprev_versioned_package.
340
341 Raises:
342 EbuildManifestError: When ebuild manifest does not complete successfuly.
343 """
344 path = os.path.join(constants.SOURCE_ROOT, 'src', 'third_party',
345 'toolchain-utils', 'afdo_metadata', 'chrome_afdo.json')
346
347 with open(path, 'r') as f:
348 versions = json.load(f)
349
350 path = os.path.join('src', 'third_party', 'chromiumos-overlay',
351 'chromeos-base', 'chromeos-chrome')
352 ebuild_path = os.path.join(constants.SOURCE_ROOT, path,
353 'chromeos-chrome-9999.ebuild')
354 chroot_ebuild_path = os.path.join(constants.CHROOT_SOURCE_ROOT, path,
355 'chromeos-chrome-9999.ebuild')
356
357 result = UprevVersionedPackageResult()
358 for version, version_info in versions.items():
359 afdo_profile_version = version_info['name']
360 varname = 'AFDO_FILE["%s"]' % version
361 patch_ebuild_vars(ebuild_path, {varname: afdo_profile_version})
362
363 try:
364 cmd = ['ebuild', chroot_ebuild_path, 'manifest', '--force']
Mike Frysinger45602c72019-09-22 02:15:11 -0400365 cros_build_lib.run(cmd, enter_chroot=True)
Yaakov Shaul45990452019-09-16 14:52:06 -0600366 except cros_build_lib.RunCommandError as e:
367 raise EbuildManifestError(
368 'Error encountered when regenerating the manifest for ebuild: %s\n%s' %
369 (chroot_ebuild_path, e), e)
370
371 manifest_path = os.path.join(constants.SOURCE_ROOT, path, 'Manifest')
372 result.add_result('chromeos-chrome', [ebuild_path, manifest_path])
373
374 return result
375
376
Alex Klein87531182019-08-12 15:23:37 -0600377@uprevs_versioned_package(constants.CHROME_CP)
378def uprev_chrome(build_targets, refs, chroot):
379 """Uprev chrome and its related packages.
380
381 See: uprev_versioned_package.
382 """
383 # Determine the version from the refs (tags), i.e. the chrome versions are the
384 # tag names.
385 chrome_version = uprev_lib.get_chrome_version_from_refs(refs)
386
387 uprev_manager = uprev_lib.UprevChromeManager(
388 chrome_version, build_targets=build_targets, chroot=chroot)
David Burger37f48672019-09-18 17:07:56 -0600389 result = UprevVersionedPackageResult()
Alex Klein87531182019-08-12 15:23:37 -0600390 # Start with chrome itself, as we can't do anything else unless chrome
391 # uprevs successfully.
392 if not uprev_manager.uprev(constants.CHROME_CP):
David Burger37f48672019-09-18 17:07:56 -0600393 return result
Alex Klein87531182019-08-12 15:23:37 -0600394
395 # With a successful chrome rev, also uprev related packages.
396 for package in constants.OTHER_CHROME_PACKAGES:
397 uprev_manager.uprev(package)
398
David Burger37f48672019-09-18 17:07:56 -0600399 return result.add_result(chrome_version, uprev_manager.modified_ebuilds)
Alex Klein87531182019-08-12 15:23:37 -0600400
401
Alex Kleinbbef2b32019-08-27 10:38:50 -0600402def get_best_visible(atom, build_target=None):
403 """Returns the best visible CPV for the given atom.
404
405 Args:
406 atom (str): The atom to look up.
407 build_target (build_target_util.BuildTarget): The build target whose
Alex Kleinda39c6d2019-09-16 14:36:36 -0600408 sysroot should be searched, or the SDK if not provided.
Alex Kleinbbef2b32019-08-27 10:38:50 -0600409 """
David Burger1e0fe232019-07-01 14:52:07 -0600410 assert atom
Alex Kleinbbef2b32019-08-27 10:38:50 -0600411
412 board = build_target.name if build_target else None
413 return portage_util.PortageqBestVisible(atom, board=board)
Alex Kleinda39c6d2019-09-16 14:36:36 -0600414
415
416def has_prebuilt(atom, build_target=None):
417 """Check if a prebuilt exists.
418
419 Args:
420 atom (str): The package whose prebuilt is being queried.
421 build_target (build_target_util.BuildTarget): The build target whose
422 sysroot should be searched, or the SDK if not provided.
423 """
424 assert atom
425
426 board = build_target.name if build_target else None
427 return portage_util.HasPrebuilt(atom, board=board)
Alex Klein36b117f2019-09-30 15:13:46 -0600428
429
David Burger0f9dd4e2019-10-08 12:33:42 -0600430def builds(atom, build_target, packages=None):
Alex Klein36b117f2019-09-30 15:13:46 -0600431 """Check if |build_target| builds |atom| (has it in its depgraph)."""
432 cros_build_lib.AssertInsideChroot()
433
David Burger0f9dd4e2019-10-08 12:33:42 -0600434 graph = dependency.GetBuildDependency(build_target.name, packages)
Alex Klein36b117f2019-09-30 15:13:46 -0600435 return any(atom in package for package in graph['package_deps'])
Michael Mortensenb70e8a82019-10-10 18:43:41 -0600436
437
438def determine_android_package(board):
439 """Returns the active Android container package in use by the board.
440
441 Args:
442 board: The board name this is specific to.
443 """
444 packages = portage_util.GetPackageDependencies(board, 'virtual/target-os')
445 # We assume there is only one Android package in the depgraph.
446 for package in packages:
447 if package.startswith('chromeos-base/android-container-') or \
448 package.startswith('chromeos-base/android-vm-'):
449 return package
450 return None
451
452
453def determine_android_version(boards=None):
454 """Determine the current Android version in buildroot now and return it.
455
456 This uses the typical portage logic to determine which version of Android
457 is active right now in the buildroot.
458
459 Args:
460 boards: List of boards to check version of.
461
462 Returns:
463 The Android build ID of the container for the boards.
464
465 Raises:
466 NoAndroidVersionError: if no unique Android version can be determined.
467 """
468 if not boards:
469 return None
470 # Verify that all boards have the same version.
471 version = None
472 for board in boards:
473 package = determine_android_package(board)
474 if not package:
475 raise NoAndroidVersionError(
476 'Android version could not be determined for %s' % boards)
477 cpv = portage_util.SplitCPV(package)
478 if not cpv:
479 raise NoAndroidVersionError(
480 'Android version could not be determined for %s' % board)
481 if not version:
482 version = cpv.version_no_rev
483 elif version != cpv.version_no_rev:
484 raise NoAndroidVersionError(
485 'Different Android versions (%s vs %s) for %s' %
486 (version, cpv.version_no_rev, boards))
487 return version
488
489def determine_android_branch(board):
490 """Returns the Android branch in use by the active container ebuild."""
491 try:
492 android_package = determine_android_package(board)
493 except cros_build_lib.RunCommandError:
494 raise NoAndroidBranchError(
495 'Android branch could not be determined for %s' % board)
496 if not android_package:
497 raise NoAndroidBranchError(
498 'Android branch could not be determined for %s (no package?)' % board)
499 ebuild_path = portage_util.FindEbuildForBoardPackage(android_package, board)
500 # We assume all targets pull from the same branch and that we always
501 # have an ARM_TARGET, ARM_USERDEBUG_TARGET, or an X86_USERDEBUG_TARGET.
502 targets = ['ARM_TARGET', 'ARM_USERDEBUG_TARGET', 'X86_USERDEBUG_TARGET']
503 ebuild_content = osutils.SourceEnvironment(ebuild_path, targets)
504 for target in targets:
505 if target in ebuild_content:
506 branch = re.search(r'(.*?)-linux-', ebuild_content[target])
507 if branch is not None:
508 return branch.group(1)
509 raise NoAndroidBranchError(
510 'Android branch could not be determined for %s (ebuild empty?)' % board)
511
512
513def determine_android_target(board):
514 try:
515 android_package = determine_android_package(board)
516 except cros_build_lib.RunCommandError:
517 raise NoAndroidTargetError(
518 'Android Target could not be determined for %s' % board)
519 if not android_package:
520 raise NoAndroidTargetError(
521 'Android Target could not be determined for %s (no package?)' %
522 board)
523 if android_package.startswith('chromeos-base/android-vm-'):
524 return 'bertha'
525 elif android_package.startswith('chromeos-base/android-container-'):
526 return 'cheets'
527
528 raise NoAndroidTargetError(
529 'Android Target cannot be determined for the package: %s' %
530 android_package)