1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Routines and classes for working with Portage overlays and ebuilds."""
12 import multiprocessing
18 from chromite.buildbot import constants
19 from chromite.lib import cros_build_lib
20 from chromite.lib import gerrit
21 from chromite.lib import git
22 from chromite.lib import osutils
24 _PRIVATE_PREFIX = '%(buildroot)s/src/private-overlays'
26 '%s/chromeos-overlay' % _PRIVATE_PREFIX,
27 '%s/chromeos-partner-overlay' % _PRIVATE_PREFIX,
28 '%(buildroot)s/src/third_party/chromiumos-overlay',
29 '%(buildroot)s/src/third_party/portage-stable',
32 # Define datastructures for holding PV and CPV objects.
33 _PV_FIELDS = ['pv', 'package', 'version', 'version_no_rev', 'rev']
34 PV = collections.namedtuple('PV', _PV_FIELDS)
35 CPV = collections.namedtuple('CPV', ['category'] + _PV_FIELDS)
37 # Package matching regexp, as dictated by package manager specification:
38 # http://www.gentoo.org/proj/en/qa/pms.xml
39 _pkg = r'(?P<package>' + r'[\w+][\w+-]*)'
40 _ver = r'(?P<version>' + \
41 r'(?P<version_no_rev>(\d+)((\.\d+)*)([a-z]?)' + \
42 r'((_(pre|p|beta|alpha|rc)\d*)*))' + \
43 r'(-(?P<rev>r(\d+)))?)'
44 _pvr_re = re.compile(r'^(?P<pv>%s-%s)$' % (_pkg, _ver), re.VERBOSE)
46 # This regex matches blank lines, commented lines, and the EAPI line.
47 _blank_or_eapi_re = re.compile(r'^\s*(?:#|EAPI=|$)')
50 def _ListOverlays(board=None, buildroot=constants.SOURCE_ROOT):
51 """Return the list of overlays to use for a given buildbot.
53 Always returns all overlays, and does not perform any filtering.
56 board: Board to look at.
57 buildroot: Source root to find overlays.
59 overlays, patterns = [], []
61 patterns += ['overlay*']
63 board_no_variant, _, variant = board.partition('_')
64 patterns += ['overlay-%s' % board_no_variant]
66 patterns += ['overlay-variant-%s' % board.replace('_', '-')]
68 for d in _GLOBAL_OVERLAYS:
69 d %= dict(buildroot=buildroot)
74 overlays += glob.glob('%s/src/overlays/%s' % (buildroot, p))
75 overlays += glob.glob('%s/src/private-overlays/%s-private' % (buildroot, p))
80 def FindOverlays(overlay_type, board=None, buildroot=constants.SOURCE_ROOT):
81 """Return the list of overlays to use for a given buildbot.
84 overlay_type: A string describing which overlays you want.
85 'private': Just the private overlays.
86 'public': Just the public overlays.
87 'both': Both the public and private overlays.
88 board: Board to look at.
89 buildroot: Source root to find overlays.
91 overlays = _ListOverlays(board=board, buildroot=buildroot)
92 private_prefix = _PRIVATE_PREFIX % dict(buildroot=buildroot)
93 if overlay_type == constants.PRIVATE_OVERLAYS:
94 return [x for x in overlays if x.startswith(private_prefix)]
95 elif overlay_type == constants.PUBLIC_OVERLAYS:
96 return [x for x in overlays if not x.startswith(private_prefix)]
97 elif overlay_type == constants.BOTH_OVERLAYS:
100 assert overlay_type is None
104 def ReadOverlayFile(filename, overlay_type='both', board=None,
105 buildroot=constants.SOURCE_ROOT):
106 """Attempt to open a file in the overlay directories.
109 filename: Path to open inside the overlay.
110 overlay_type: A string describing which overlays you want.
111 'private': Just the private overlays.
112 'public': Just the public overlays.
113 'both': Both the public and private overlays.
114 board: Board to look at.
115 buildroot: Source root to find overlays.
118 The contents of the file, or None if no files could be opened.
120 for overlay in FindOverlays(overlay_type, board, buildroot):
122 return osutils.ReadFile(os.path.join(overlay, filename))
124 if e.errno != os.errno.ENOENT:
128 class MissingOverlayException(Exception):
129 """This exception indicates that a needed overlay is missing."""
132 def FindPrimaryOverlay(overlay_type, board, buildroot=constants.SOURCE_ROOT):
133 """Return the primary overlay to use for a given buildbot.
135 An overlay is only considered a primary overlay if it has a make.conf and a
136 toolchain.conf. If multiple primary overlays are found, the first primary
140 overlay_type: A string describing which overlays you want.
141 'private': Just the private overlays.
142 'public': Just the public overlays.
143 'both': Both the public and private overlays.
144 board: Board to look at.
145 buildroot: Path to root of build directory.
148 MissingOverlayException: No primary overlay found.
150 for overlay in FindOverlays(overlay_type, board, buildroot):
151 if (os.path.exists(os.path.join(overlay, 'make.conf')) and
152 os.path.exists(os.path.join(overlay, 'toolchain.conf'))):
154 raise MissingOverlayException('No primary overlay found for board=%r' % board)
157 def GetOverlayName(overlay):
159 return open('%s/profiles/repo_name' % overlay).readline().rstrip()
161 # Not all overlays have a repo_name, so don't make a fuss.
165 class EBuildVersionFormatException(Exception):
166 """Exception for bad ebuild version string format."""
167 def __init__(self, filename):
168 self.filename = filename
169 message = ('Ebuild file name %s '
170 'does not match expected format.' % filename)
171 super(EBuildVersionFormatException, self).__init__(message)
174 class EbuildFormatIncorrectException(Exception):
175 """Exception for bad ebuild format."""
176 def __init__(self, filename, message):
177 message = 'Ebuild %s has invalid format: %s ' % (filename, message)
178 super(EbuildFormatIncorrectException, self).__init__(message)
181 class EBuild(object):
182 """Wrapper class for information about an ebuild."""
185 _PACKAGE_VERSION_PATTERN = re.compile(
186 r'.*-(([0-9][0-9a-z_.]*)(-r[0-9]+)?)[.]ebuild')
187 _WORKON_COMMIT_PATTERN = re.compile(r'^CROS_WORKON_COMMIT="(.*)"$')
189 # A structure to hold computed values of CROS_WORKON_*.
190 CrosWorkonVars = collections.namedtuple(
191 'CrosWorkonVars', ('localname', 'project', 'subdir'))
194 def _Print(cls, message):
195 """Verbose print function."""
197 cros_build_lib.Info(message)
200 def _RunCommand(cls, command, **kwargs):
201 kwargs.setdefault('capture_output', True)
202 return cros_build_lib.RunCommand(
203 command, print_cmd=cls.VERBOSE, **kwargs).output
206 def _RunGit(cls, cwd, command, **kwargs):
207 result = git.RunGit(cwd, command, print_cmd=cls.VERBOSE, **kwargs)
208 return None if result is None else result.output
211 """Returns True if the ebuild is sticky."""
212 return self.is_stable and self.current_revision == 0
215 def UpdateEBuild(cls, ebuild_path, variables, redirect_file=None,
217 """Static function that updates WORKON information in the ebuild.
219 This function takes an ebuild_path and updates WORKON information.
222 ebuild_path: The path of the ebuild.
223 variables: Dictionary of variables to update in ebuild.
224 redirect_file: Optionally redirect output of new ebuild somewhere else.
225 make_stable: Actually make the ebuild stable.
228 for line in fileinput.input(ebuild_path, inplace=1):
229 # Has to be done here to get changes to sys.stdout from fileinput.input.
230 if not redirect_file:
231 redirect_file = sys.stdout
233 # Always add variables at the top of the ebuild, before the first
234 # nonblank line other than the EAPI line.
235 if not written and not _blank_or_eapi_re.match(line):
236 for key, value in sorted(variables.items()):
237 assert key is not None and value is not None
238 redirect_file.write('%s=%s\n' % (key, value))
241 # Mark KEYWORDS as stable by removing ~'s.
242 if line.startswith('KEYWORDS=') and make_stable:
243 line = line.replace('~', '')
245 varname, eq, _ = line.partition('=')
246 if not (eq == '=' and varname.strip() in variables):
247 # Don't write out the old value of the variable.
248 redirect_file.write(line)
253 def MarkAsStable(cls, unstable_ebuild_path, new_stable_ebuild_path,
254 variables, redirect_file=None, make_stable=True):
255 """Static function that creates a revved stable ebuild.
257 This function assumes you have already figured out the name of the new
258 stable ebuild path and then creates that file from the given unstable
259 ebuild and marks it as stable. If the commit_value is set, it also
260 set the commit_keyword=commit_value pair in the ebuild.
263 unstable_ebuild_path: The path to the unstable ebuild.
264 new_stable_ebuild_path: The path you want to use for the new stable
266 variables: Dictionary of variables to update in ebuild.
267 redirect_file: Optionally redirect output of new ebuild somewhere else.
268 make_stable: Actually make the ebuild stable.
270 shutil.copyfile(unstable_ebuild_path, new_stable_ebuild_path)
271 EBuild.UpdateEBuild(new_stable_ebuild_path, variables, redirect_file,
275 def CommitChange(cls, message, overlay):
276 """Commits current changes in git locally with given commit message.
279 message: the commit string to write when committing to git.
280 overlay: directory in which to commit the changes.
283 RunCommandError: Error occurred while committing.
285 logging.info('Committing changes with commit message: %s', message)
286 git_commit_cmd = ['commit', '-a', '-m', message]
287 cls._RunGit(overlay, git_commit_cmd)
289 def __init__(self, path):
290 """Sets up data about an ebuild from its path.
293 path: Path to the ebuild.
295 self._overlay, self._category, self._pkgname, filename = path.rsplit('/', 3)
296 m = self._PACKAGE_VERSION_PATTERN.match(filename)
298 raise EBuildVersionFormatException(filename)
299 self.version, self.version_no_rev, revision = m.groups()
300 if revision is not None:
301 self.current_revision = int(revision.replace('-r', ''))
303 self.current_revision = 0
304 self.package = '%s/%s' % (self._category, self._pkgname)
306 self._ebuild_path_no_version = os.path.join(
307 os.path.dirname(path), self._pkgname)
308 self.ebuild_path_no_revision = '%s-%s' % (
309 self._ebuild_path_no_version, self.version_no_rev)
310 self._unstable_ebuild_path = '%s-9999.ebuild' % (
311 self._ebuild_path_no_version)
312 self.ebuild_path = path
314 self.is_workon = False
315 self.is_stable = False
316 self.is_blacklisted = False
317 self._ReadEBuild(path)
319 def _ReadEBuild(self, path):
320 """Determine the settings of `is_workon` and `is_stable`.
322 `is_workon` is determined by whether the ebuild inherits from
323 the 'cros-workon' eclass. `is_stable` is determined by whether
324 there's a '~' in the KEYWORDS setting in the ebuild.
326 This function is separate from __init__() to allow unit tests to
329 for line in fileinput.input(path):
330 if line.startswith('inherit ') and 'cros-workon' in line:
331 self.is_workon = True
332 elif line.startswith('KEYWORDS='):
333 for keyword in line.split('=', 1)[1].strip("\"'").split():
334 if not keyword.startswith('~') and keyword != '-*':
335 self.is_stable = True
336 elif line.startswith('CROS_WORKON_BLACKLIST='):
337 self.is_blacklisted = True
341 def GetCrosWorkonVars(ebuild_path, pkg_name):
342 """Return computed (as sourced ebuild script) values of:
344 * CROS_WORKON_LOCALNAME
345 * CROS_WORKON_PROJECT
349 ebuild_path: Path to the ebuild file (e.g: platform2-9999.ebuild).
350 pkg_name: The package name (e.g.: platform2).
353 A CrosWorkonVars tuple.
356 'CROS_WORKON_LOCALNAME',
357 'CROS_WORKON_PROJECT',
358 'CROS_WORKON_SUBDIR',
361 'CROS_WORKON_LOCALNAME': pkg_name,
362 'CROS_WORKON_PROJECT': pkg_name,
363 'CROS_WORKON_SUBDIR': '',
365 settings = osutils.SourceEnvironment(ebuild_path, workon_vars, env=env)
366 localnames = settings['CROS_WORKON_LOCALNAME'].split(',')
367 projects = settings['CROS_WORKON_PROJECT'].split(',')
368 subdirs = settings['CROS_WORKON_SUBDIR'].split(',')
370 return EBuild.CrosWorkonVars(localnames, projects, subdirs)
372 def GetSourcePath(self, srcroot, manifest):
373 """Get the project and path for this ebuild.
375 The path is guaranteed to exist, be a directory, and be absolute.
378 localnames, projects, subdirs = EBuild.GetCrosWorkonVars(
379 self._unstable_ebuild_path, self._pkgname)
380 # Sanity checks and completion.
381 # Each project specification has to have the same amount of items.
382 if len(projects) != len(localnames):
383 raise EbuildFormatIncorrectException(self._unstable_ebuild_path,
384 'Number of _PROJECT and _LOCALNAME items don\'t match.')
385 # Subdir must be either 0,1 or len(project)
386 if len(projects) != len(subdirs) and len(subdirs) > 1:
387 raise EbuildFormatIncorrectException(self._unstable_ebuild_path,
388 'Incorrect number of _SUBDIR items.')
389 # If there's one, apply it to all.
390 if len(subdirs) == 1:
391 subdirs = subdirs * len(projects)
392 # If there is none, make an empty list to avoid exceptions later.
393 if len(subdirs) == 0:
394 subdirs = [''] * len(projects)
397 if self._category == 'chromeos-base':
398 dir_ = '' # 'platform2'
402 # Once all targets are moved from platform to platform2, uncomment
403 # the following lines as well as dir_ = 'platform2' above,
404 # and delete the loop that builds |subdir_paths| below.
406 # subdir_paths = [os.path.realpath(os.path.join(srcroot, dir_, l, s))
407 # for l, s in zip(localnames, subdirs)]
410 for local, sub in zip(localnames, subdirs):
411 subdir_path = os.path.realpath(os.path.join(srcroot, dir_, local, sub))
412 if dir_ == '' and not os.path.isdir(subdir_path):
413 subdir_path = os.path.realpath(os.path.join(srcroot, 'platform',
415 subdir_paths.append(subdir_path)
417 for subdir_path, project in zip(subdir_paths, projects):
418 if not os.path.isdir(subdir_path):
419 cros_build_lib.Die('Source repository %s '
420 'for project %s does not exist.' % (subdir_path,
422 # Verify that we're grabbing the commit id from the right project name.
423 real_project = manifest.FindCheckoutFromPath(subdir_path)['name']
424 if project != real_project:
425 cros_build_lib.Die('Project name mismatch for %s '
426 '(found %s, expected %s)' % (subdir_path,
429 return projects, subdir_paths
431 def GetCommitId(self, srcdir):
432 """Get the commit id for this ebuild."""
433 output = self._RunGit(srcdir, ['rev-parse', 'HEAD'])
435 cros_build_lib.Die('Cannot determine HEAD commit for %s' % srcdir)
436 return output.rstrip()
438 def GetTreeId(self, srcdir):
439 """Get the SHA1 of the source tree for this ebuild.
441 Unlike the commit hash, the SHA1 of the source tree is unaffected by the
442 history of the repository, or by commit messages.
444 output = self._RunGit(srcdir, ['log', '-1', '--format=%T'])
446 cros_build_lib.Die('Cannot determine HEAD tree hash for %s' % srcdir)
447 return output.rstrip()
449 def GetVersion(self, srcroot, manifest, default):
450 """Get the base version number for this ebuild.
452 The version is provided by the ebuild through a specific script in
453 the $FILESDIR (chromeos-version.sh).
455 vers_script = os.path.join(os.path.dirname(self._ebuild_path_no_version),
456 'files', 'chromeos-version.sh')
458 if not os.path.exists(vers_script):
461 srcdirs = self.GetSourcePath(srcroot, manifest)[1]
463 # The chromeos-version script will output a usable raw version number,
464 # or nothing in case of error or no available version
466 output = self._RunCommand([vers_script] + srcdirs).strip()
467 except cros_build_lib.RunCommandError as e:
468 cros_build_lib.Die('Package %s chromeos-version.sh failed: %s' %
472 cros_build_lib.Die('Package %s has a chromeos-version.sh script but '
473 'it returned no valid version for "%s"' %
474 (self._pkgname, ' '.join(srcdirs)))
479 def FormatBashArray(unformatted_list):
480 """Returns a python list in a bash array format.
482 If the list only has one item, format as simple quoted value.
483 That is both backwards-compatible and more readable.
486 unformatted_list: an iterable to format as a bash array. This variable
487 has to be sanitized first, as we don't do any safeties.
490 A text string that can be used by bash as array declaration.
492 if len(unformatted_list) > 1:
493 return '("%s")' % '" "'.join(unformatted_list)
495 return '"%s"' % unformatted_list[0]
497 def RevWorkOnEBuild(self, srcroot, manifest, redirect_file=None):
498 """Revs a workon ebuild given the git commit hash.
500 By default this class overwrites a new ebuild given the normal
501 ebuild rev'ing logic. However, a user can specify a redirect_file
502 to redirect the new stable ebuild to another file.
505 srcroot: full path to the 'src' subdirectory in the source
507 manifest: git.ManifestCheckout object.
508 redirect_file: Optional file to write the new ebuild. By default
509 it is written using the standard rev'ing logic. This file must be
510 opened and closed by the caller.
513 If the revved package is different than the old ebuild, return the full
514 revved package name, including the version number. Otherwise, return None.
517 OSError: Error occurred while creating a new ebuild.
518 IOError: Error occurred while writing to the new revved ebuild file.
522 stable_version_no_rev = self.GetVersion(srcroot, manifest,
525 # If given unstable ebuild, use preferred version rather than 9999.
526 stable_version_no_rev = self.GetVersion(srcroot, manifest, '0.0.1')
528 new_version = '%s-r%d' % (
529 stable_version_no_rev, self.current_revision + 1)
530 new_stable_ebuild_path = '%s-%s.ebuild' % (
531 self._ebuild_path_no_version, new_version)
533 self._Print('Creating new stable ebuild %s' % new_stable_ebuild_path)
534 if not os.path.exists(self._unstable_ebuild_path):
535 cros_build_lib.Die('Missing unstable ebuild: %s' %
536 self._unstable_ebuild_path)
538 srcdirs = self.GetSourcePath(srcroot, manifest)[1]
539 commit_ids = map(self.GetCommitId, srcdirs)
540 tree_ids = map(self.GetTreeId, srcdirs)
541 variables = dict(CROS_WORKON_COMMIT=self.FormatBashArray(commit_ids),
542 CROS_WORKON_TREE=self.FormatBashArray(tree_ids))
543 self.MarkAsStable(self._unstable_ebuild_path, new_stable_ebuild_path,
544 variables, redirect_file)
546 old_ebuild_path = self.ebuild_path
547 if filecmp.cmp(old_ebuild_path, new_stable_ebuild_path, shallow=False):
548 os.unlink(new_stable_ebuild_path)
551 self._Print('Adding new stable ebuild to git')
552 self._RunGit(self._overlay, ['add', new_stable_ebuild_path])
555 self._Print('Removing old ebuild from git')
556 self._RunGit(self._overlay, ['rm', old_ebuild_path])
558 return '%s-%s' % (self.package, new_version)
561 def GitRepoHasChanges(cls, directory):
562 """Returns True if there are changes in the given directory."""
563 # Refresh the index first. This squashes just metadata changes.
564 cls._RunGit(directory, ['update-index', '-q', '--refresh'])
565 output = cls._RunGit(directory, ['diff-index', '--name-only', 'HEAD'])
566 return output not in [None, '']
569 def _GetSHA1ForPath(manifest, path):
570 """Get the latest SHA1 for a given project from Gerrit.
572 This function looks up the remote and branch for a given project in the
573 manifest, and uses this to lookup the SHA1 from Gerrit. This only makes
574 sense for unpinned manifests.
577 manifest: git.ManifestCheckout object.
578 path: Path of project.
581 Exception if the manifest is pinned.
583 checkout = manifest.FindCheckoutFromPath(path)
584 project = checkout['name']
585 helper = gerrit.GetGerritHelper(checkout['remote'])
586 manifest_branch = checkout['revision']
587 branch = git.StripRefsHeads(manifest_branch)
588 return helper.GetLatestSHA1ForBranch(project, branch)
591 def _GetEBuildPaths(buildroot, manifest, overlay_list, changes):
592 """Calculate ebuild->path map for changed ebuilds.
595 buildroot: Path to root of build directory.
596 manifest: git.ManifestCheckout object.
597 overlay_list: List of all overlays.
598 changes: Changes from Gerrit that are being pushed.
601 A dictionary mapping changed ebuilds to lists of associated paths.
603 directory_src = os.path.join(buildroot, 'src')
604 overlay_dict = dict((o, []) for o in overlay_list)
605 BuildEBuildDictionary(overlay_dict, True, None)
606 changed_paths = set(c.GetCheckout(manifest).GetPath(absolute=True)
609 for ebuilds in overlay_dict.itervalues():
610 for ebuild in ebuilds:
611 _projects, paths = ebuild.GetSourcePath(directory_src, manifest)
612 if changed_paths.intersection(paths):
613 ebuild_projects[ebuild] = paths
615 return ebuild_projects
618 def UpdateCommitHashesForChanges(cls, changes, buildroot, manifest):
619 """Updates the commit hashes for the EBuilds uprevved in changes.
622 changes: Changes from Gerrit that are being pushed.
623 buildroot: Path to root of build directory.
624 manifest: git.ManifestCheckout object.
627 overlay_list = FindOverlays(constants.BOTH_OVERLAYS, buildroot=buildroot)
628 ebuild_paths = cls._GetEBuildPaths(buildroot, manifest, overlay_list,
630 for ebuild, paths in ebuild_paths.iteritems():
631 # Calculate any SHA1s that are not already in path_sha1s.
632 for path in set(paths).difference(path_sha1s):
633 path_sha1s[path] = cls._GetSHA1ForPath(manifest, path)
635 sha1s = [path_sha1s[path] for path in paths]
636 logging.info('Updating ebuild for package %s with commit hashes %r',
637 ebuild.package, sha1s)
638 updates = dict(CROS_WORKON_COMMIT=cls.FormatBashArray(sha1s))
639 EBuild.UpdateEBuild(ebuild.ebuild_path, updates)
641 # Commit any changes to all overlays.
642 for overlay in overlay_list:
643 if EBuild.GitRepoHasChanges(overlay):
644 EBuild.CommitChange('Updating commit hashes in ebuilds '
645 'to match remote repository.', overlay=overlay)
648 def BestEBuild(ebuilds):
649 """Returns the newest EBuild from a list of EBuild objects."""
650 # pylint: disable=F0401
651 from portage.versions import vercmp
653 for ebuild in ebuilds[1:]:
654 if vercmp(winner.version, ebuild.version) < 0:
659 def _FindUprevCandidates(files):
660 """Return the uprev candidate ebuild from a specified list of files.
662 Usually an uprev candidate is a the stable ebuild in a cros_workon
663 directory. However, if no such stable ebuild exists (someone just
664 checked in the 9999 ebuild), this is the unstable ebuild.
666 If the package isn't a cros_workon package, return None.
669 files: List of files in a package directory.
672 unstable_ebuilds = []
674 if not path.endswith('.ebuild') or os.path.islink(path):
676 ebuild = EBuild(path)
677 if not ebuild.is_workon or ebuild.is_blacklisted:
680 if ebuild.version == '9999':
681 cros_build_lib.Die('KEYWORDS in 9999 ebuild should not be stable %s'
683 stable_ebuilds.append(ebuild)
685 unstable_ebuilds.append(ebuild)
687 # If both ebuild lists are empty, the passed in file list was for
688 # a non-workon package.
689 if not unstable_ebuilds:
691 path = os.path.dirname(stable_ebuilds[0].ebuild_path)
692 cros_build_lib.Die('Missing 9999 ebuild in %s' % path)
695 path = os.path.dirname(unstable_ebuilds[0].ebuild_path)
696 if len(unstable_ebuilds) > 1:
697 cros_build_lib.Die('Found multiple unstable ebuilds in %s' % path)
699 if not stable_ebuilds:
700 cros_build_lib.Warning('Missing stable ebuild in %s' % path)
701 return unstable_ebuilds[0]
703 if len(stable_ebuilds) == 1:
704 return stable_ebuilds[0]
706 stable_versions = set(ebuild.version_no_rev for ebuild in stable_ebuilds)
707 if len(stable_versions) > 1:
708 package = stable_ebuilds[0].package
709 message = 'Found multiple stable ebuild versions in %s:' % path
710 for version in stable_versions:
711 message += '\n %s-%s' % (package, version)
712 cros_build_lib.Die(message)
714 uprev_ebuild = max(stable_ebuilds, key=lambda eb: eb.current_revision)
715 for ebuild in stable_ebuilds:
716 if ebuild != uprev_ebuild:
717 cros_build_lib.Warning('Ignoring stable ebuild revision %s in %s' %
718 (ebuild.version, path))
722 def BuildEBuildDictionary(overlays, use_all, packages):
723 """Build a dictionary of the ebuilds in the specified overlays.
725 overlays: A map which maps overlay directories to arrays of stable EBuilds
726 inside said directories.
727 use_all: Whether to include all ebuilds in the specified directories.
728 If true, then we gather all packages in the directories regardless
729 of whether they are in our set of packages.
730 packages: A set of the packages we want to gather. If use_all is
731 True, this argument is ignored, and should be None.
733 for overlay in overlays:
734 for package_dir, _dirs, files in os.walk(overlay):
735 # Add stable ebuilds to overlays[overlay].
736 paths = [os.path.join(package_dir, path) for path in files]
737 ebuild = _FindUprevCandidates(paths)
739 # If the --all option isn't used, we only want to update packages that
741 if ebuild and (use_all or ebuild.package in packages):
742 overlays[overlay].append(ebuild)
745 def RegenCache(overlay):
746 """Regenerate the cache of the specified overlay.
748 overlay: The tree to regenerate the cache for.
750 repo_name = GetOverlayName(overlay)
754 layout = cros_build_lib.LoadKeyValueFile('%s/metadata/layout.conf' % overlay,
756 if layout.get('cache-format') != 'md5-dict':
759 # Regen for the whole repo.
760 cros_build_lib.RunCommand(['egencache', '--update', '--repo', repo_name,
761 '--jobs', str(multiprocessing.cpu_count())])
762 # If there was nothing new generated, then let's just bail.
763 result = git.RunGit(overlay, ['status', '-s', 'metadata/'])
764 if not result.output:
766 # Explicitly add any new files to the index.
767 git.RunGit(overlay, ['add', 'metadata/'])
768 # Explicitly tell git to also include rm-ed files.
769 git.RunGit(overlay, ['commit', '-m', 'regen cache', 'metadata/'])
772 def ParseBashArray(value):
773 """Parse a valid bash array into python list."""
774 # The syntax for bash arrays is nontrivial, so let's use bash to do the
775 # heavy lifting for us.
777 # Because %s may contain bash comments (#), put a clever newline in the way.
778 cmd = 'ARR=%s\nIFS=%s; echo -n "${ARR[*]}"' % (value, sep)
779 return cros_build_lib.RunCommand(
780 cmd, print_cmd=False, shell=True, capture_output=True).output.split(sep)
783 def GetWorkonProjectMap(overlay, subdirectories):
784 """Get the project -> ebuild mapping for cros_workon ebuilds.
787 overlay: Overlay to look at.
788 subdirectories: List of subdirectories to look in on the overlay.
791 A list of (filename, projects) tuples for cros-workon ebuilds in the
792 given overlay under the given subdirectories.
794 # Search ebuilds for project names, ignoring non-existent directories.
795 for subdir in subdirectories:
796 for root, _dirs, files in os.walk(os.path.join(overlay, subdir)):
797 for filename in files:
798 if filename.endswith('-9999.ebuild'):
799 full_path = os.path.join(root, filename)
800 pkg_name = os.path.basename(root)
801 _, projects, _ = EBuild.GetCrosWorkonVars(full_path, pkg_name)
802 relpath = os.path.relpath(full_path, start=overlay)
803 yield relpath, projects
806 def SplitEbuildPath(path):
807 """Split an ebuild path into its components.
809 Given a specified ebuild filename, returns $CATEGORY, $PN, $P. It does not
810 perform any check on ebuild name elements or their validity, merely splits
811 a filename, absolute or relative, and returns the last 3 components.
813 Example: For /any/path/chromeos-base/power_manager/power_manager-9999.ebuild,
814 returns ('chromeos-base', 'power_manager', 'power_manager-9999').
819 return os.path.splitext(path)[0].rsplit('/', 3)[-3:]
822 def SplitPV(pv, strict=True):
823 """Takes a PV value and splits it into individual components.
826 pv: Package name and version.
827 strict: If True, returns None if version or package name is missing.
828 Otherwise, only package name is mandatory.
831 A collection with named members:
832 pv, package, version, version_no_rev, rev
834 m = _pvr_re.match(pv)
836 if m is None and strict:
840 return PV(**{'pv': None, 'package': pv, 'version': None,
841 'version_no_rev': None, 'rev': None})
843 return PV(**m.groupdict())
846 def SplitCPV(cpv, strict=True):
847 """Splits a CPV value into components.
850 cpv: Category, package name, and version of a package.
851 strict: If True, returns None if any of the components is missing.
852 Otherwise, only package name is mandatory.
855 A collection with named members:
856 category, pv, package, version, version_no_rev, rev
858 chunks = cpv.split('/')
860 raise ValueError('Unexpected package format %s' % cpv)
866 m = SplitPV(chunks[-1], strict=strict)
867 if strict and (category is None or m is None):
869 # pylint: disable=W0212
870 return CPV(category=category, **m._asdict())
873 def FindWorkonProjects(packages):
874 """Find the projects associated with the specified cros_workon packages.
877 packages: List of cros_workon packages.
880 The set of projects associated with the specified cros_workon packages.
883 buildroot, both = constants.SOURCE_ROOT, constants.BOTH_OVERLAYS
884 for overlay in FindOverlays(both, buildroot=buildroot):
885 for _, projects in GetWorkonProjectMap(overlay, packages):
886 all_projects.update(projects)
890 def ListInstalledPackages(sysroot):
891 """Lists all portage packages in a given portage-managed root.
893 Assumes the existence of a /var/db/pkg package database.
896 sysroot: The root being inspected.
899 A list of (cp,v) tuples in the given sysroot.
901 vdb_path = os.path.join(sysroot, 'var/db/pkg')
902 ebuild_pattern = os.path.join(vdb_path, '*/*/*.ebuild')
904 for path in glob.glob(ebuild_pattern):
905 category, package, packagecheck = SplitEbuildPath(path)
906 pv = SplitPV(package)
907 if package == packagecheck and pv is not None:
908 packages.append(('%s/%s' % (category, pv.package), pv.version))
912 def BestVisible(atom, board=None, pkg_type='ebuild',
913 buildroot=constants.SOURCE_ROOT):
914 """Get the best visible ebuild CPV for the given atom.
918 board: Board to look at. By default, look in chroot.
919 pkg_type: Package type (ebuild, binary, or installed).
925 portageq = 'portageq' if board is None else 'portageq-%s' % board
926 root = cros_build_lib.GetSysroot(board=board)
927 cmd = [portageq, 'best_visible', root, pkg_type, atom]
928 result = cros_build_lib.RunCommand(
929 cmd, cwd=buildroot, enter_chroot=True, debug_level=logging.DEBUG,
931 return SplitCPV(result.output.strip())
934 def IsPackageInstalled(package, sysroot='/'):
935 """Return whether a portage package is in a given portage-managed root.
938 package: The CP to look for.
939 sysroot: The root being inspected.
941 for key, _version in ListInstalledPackages(sysroot):
948 def FindPackageNameMatches(pkg_str, board=None):
949 """Finds a list of installed packages matching |pkg_str|.
952 pkg_str: The package name with optional category, version, and slot.
953 board: The board to insepct.
956 A list of matched CPV objects.
960 cmd = ['equery-%s' % board]
962 cmd += ['list', pkg_str]
963 result = cros_build_lib.RunCommand(
964 cmd, capture_output=True, error_code_ok=True)
967 if result.returncode == 0:
968 matches = [SplitCPV(x) for x in result.output.splitlines()]
973 def GetBinaryPackageDir(sysroot='/', packages_dir=None):
974 """Returns the binary package directory of |sysroot|."""
975 dir_name = packages_dir if packages_dir else 'packages'
976 return os.path.join(sysroot, dir_name)
979 def GetBinaryPackagePath(c, p, v, sysroot='/', packages_dir=None):
980 """Returns the path to the binary package.
986 sysroot: The root being inspected.
987 packages_dir: Name of the packages directory in |sysroot|.
990 The path to the binary package.
992 pkgdir = GetBinaryPackageDir(sysroot=sysroot, packages_dir=packages_dir)
993 path = os.path.join(pkgdir, c, '%s-%s.tbz2' % (p, v))
994 if not os.path.exists(path):
995 raise ValueError('Cannot find the binary package %s!' % path)
1000 def CleanOutdatedBinaryPackages(board):
1001 """Cleans outdated binary packages for |board|."""
1002 return cros_build_lib.RunCommand(['eclean-%s' % board, '-d', 'packages'])