15bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 25bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone# Use of this source code is governed by a BSD-style license that can be 35bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone# found in the LICENSE file. 45bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 5c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteimport glob 6c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteimport logging 7c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteimport os 8c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteimport re 9c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteimport time 1067f06d6003bb11f03f925810272f595d79dce44aChris Masonefrom distutils import version 115bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 12c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteimport common 13c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnettefrom autotest_lib.client.common_lib import autotemp 14c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnettefrom autotest_lib.client.common_lib import error 15c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnettefrom autotest_lib.client.common_lib import utils 16c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette 175bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 185bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masoneclass ManifestVersionsException(Exception): 195bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Base class for exceptions from this package.""" 205bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone pass 215bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 225bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 235bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masoneclass QueryException(ManifestVersionsException): 245bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Raised to indicate a failure while searching for manifests.""" 255bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone pass 265bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 275bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 28c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnetteclass CloneException(ManifestVersionsException): 29c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette """Raised when `git clone` fails to create the repository.""" 30c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette pass 31c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette 32c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette 3328424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masonedef _SystemOutput(command, timeout=None, args=()): 3428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone """Shell out to run a command, expecting data on stderr. Return stdout. 3528424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 3628424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone Shells out to run |command|, optionally passing escaped |args|. 3728424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone Instead of logging stderr at ERROR level, will log at default 3828424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone stdout log level. Normal stdout is returned. 3928424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 4028424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone @param command: command string to execute. 4128424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone @param timeout: time limit in seconds before attempting to kill the 4228424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone running process. The function will take a few seconds longer 4328424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone than 'timeout' to complete if it has to kill the process. 4428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone @param args: sequence of strings of arguments to be given to the command 4528424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone inside " quotes after they have been escaped for that; each 4628424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone element in the sequence will be given as a separate command 4728424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone argument. 4828424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 4928424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone @return a string with the stdout output of the command. 5028424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone """ 5128424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone out = utils.run(command, timeout=timeout, ignore_status=False, 5228424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone stderr_is_expected=True, args=args).stdout 5328424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone return out.rstrip('\n') 5428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 5528424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 5628424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masonedef _System(command, timeout=None): 5728424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone """Run a command, expecting data on stderr. 5828424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 5928424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone @param command: command string to execute. 6028424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone @param timeout: timeout in seconds 6128424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone """ 6228424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone utils.run(command, timeout=timeout, ignore_status=False, 6328424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS, 6428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone stderr_is_expected=True) 6528424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 6628424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone 675bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masoneclass ManifestVersions(object): 685bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Class to allow discovery of manifests for new successful CrOS builds. 695bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 70f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian Manifest versions is a repository that contains information on which 71f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian builds passed/failed. This class is responsible for keeping a temp 72f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian copy of the repository up to date. 73f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian 74c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette @var _CLONE_RETRY_SECONDS: Number of seconds to wait before retrying 75c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette a failed `git clone` operation. 76c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette @var _CLONE_MAX_RETRIES: Maximum number of times to retry a failed 77c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette a failed `git clone` operation. 785bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @var _MANIFEST_VERSIONS_URL: URL of the internal manifest-versions git repo. 795bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @var _BOARD_MANIFEST_GLOB_PATTERN: pattern for shell glob for passed-build 805bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone manifests for a given board. 815bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @var _BOARD_MANIFEST_RE_PATTERN: pattern for regex that parses paths to 825bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone manifests for a given board. 835bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 845bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @var _git: absolute path of git binary. 855bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @var _tempdir: a scoped tempdir. Will be destroyed on instance deletion. 865bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """ 875bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 88c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette _CLONE_RETRY_SECONDS = 5 * 60 89c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette _CLONE_MAX_RETRIES = 60 * 60 / _CLONE_RETRY_SECONDS 903397c7c94475a37ec14a0e5648b1453cf5018aa3beeps _MANIFEST_VERSIONS_URL = ('https://chrome-internal-review.googlesource.com/' 915bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 'chromeos/manifest-versions.git') 9293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone _ANY_MANIFEST_GLOB_PATTERN = 'build-name/*/pass/' 935bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone _BOARD_MANIFEST_GLOB_PATTERN = 'build-name/%s-*/pass/' 940d00357df2bb69ac799fa1ac6ca81bb68b137c78Alex Miller _BOARD_MANIFEST_RE_PATTERN = (r'build-name/%s-([^-]+)' 95c7960d16423e63510f105c007e0e22b5a1d4a01fAlex Miller r'(?:-group)?/pass/(\d+)/([0-9.]+)\.xml') 96a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi _BOARD_BRANCH_MANIFEST_GLOB_PATTERN = 'build-name/%s-%s/pass/' 975bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 985bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 99f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian def __init__(self, tmp_repo_dir=None): 100f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian """Create a manifest versions manager. 101f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian 102f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian @param tmp_repo_dir: For use in testing, if one does not wish to repeatedly 103f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian clone the manifest versions repo that is currently a few GB in size. 104f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian """ 10528424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone self._git = _SystemOutput('which git') 106f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian if tmp_repo_dir: 107f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian self._tempdir = autotemp.dummy_dir(tmp_repo_dir) 108f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian else: 109f571aa6b964930e4cd7ea0569105bebe06b97070Prashanth Balasubramanian self._tempdir = autotemp.tempdir(unique_id='_suite_scheduler') 1105bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 1115bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 11293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone def AnyManifestsSinceRev(self, revision): 113a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi """Determine if any builds passed since git |revision|. 114a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi 115a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi @param revision: the git revision to look back to. 116a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi @return True if any builds have passed; False otherwise. 117a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi """ 118a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi manifest_paths = self._ExpandGlobMinusPrefix( 119a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi self._tempdir.name, self._ANY_MANIFEST_GLOB_PATTERN) 120a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi if not manifest_paths: 121a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi logging.error('No paths to check for manifests???') 122a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi return False 123a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi logging.info('Checking if any manifests landed since %s', revision) 124a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi log_cmd = self._BuildCommand('log', 125a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi revision + '..HEAD', 126a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi '--pretty="format:%H"', 127a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi '--', 128a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi ' '.join(manifest_paths)) 129a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi return _SystemOutput(log_cmd).strip() != '' 13093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 13193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 13267f06d6003bb11f03f925810272f595d79dce44aChris Masone def Initialize(self): 13367f06d6003bb11f03f925810272f595d79dce44aChris Masone """Set up internal state. Must be called before other methods. 13467f06d6003bb11f03f925810272f595d79dce44aChris Masone 135c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette Clone manifest-versions.git into tempdir managed by this instance. 13667f06d6003bb11f03f925810272f595d79dce44aChris Masone """ 137c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # If gerrit goes down during suite_scheduler operation, 138c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # we'll enter a loop like the following: 139c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 1. suite_scheduler fails executing some `git` command. 140c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 2. The failure is logged at ERROR level, causing an 141c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # e-mail notification of the failure. 142c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 3. suite_scheduler terminates. 143c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 4. Upstart respawns suite_scheduler. 144c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 5. suite_scheduler comes here to restart with a new 145c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # manifest-versions repo. 146c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 6. `git clone` fails, and we go back to step 2. 147c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # 148c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # We want to rate limit the e-mail notifications, so we 149c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # retry failed `git clone` operations for a time before we 150c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette # finally give up. 151c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette retry_count = 0 152c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette msg = None 153c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette while retry_count <= self._CLONE_MAX_RETRIES: 154c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette if retry_count: 155c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette time.sleep(self._CLONE_RETRY_SECONDS) 156c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette retry_count += 1 157c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette try: 158c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette logging.debug('Cloning manifest-versions.git,' 159c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette ' attempt %d.', retry_count) 160c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette self._Clone() 161c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette logging.debug('manifest-versions.git cloned.') 162c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette return 163c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette except error.CmdError as e: 164c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette msg = str(e) 165c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette logging.debug('Clone failed: %s', msg) 166c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette raise CloneException('Failed to clone %s after %d attempts: %s' % 167c9c2a8620c256fa17d16e6ddfa9a007a63f79fe2J. Richard Barnette (self._MANIFEST_VERSIONS_URL, retry_count, msg)) 16867f06d6003bb11f03f925810272f595d79dce44aChris Masone 16967f06d6003bb11f03f925810272f595d79dce44aChris Masone 17033ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng def ManifestsSinceDate(self, since_date, board): 17133ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng """Return map of branch:manifests for |board| since |since_date|. 17267f06d6003bb11f03f925810272f595d79dce44aChris Masone 17367f06d6003bb11f03f925810272f595d79dce44aChris Masone To fully specify a 'branch', one needs both the type and the numeric 17467f06d6003bb11f03f925810272f595d79dce44aChris Masone milestone the branch was cut for, e.g. ('release', '19') or 17567f06d6003bb11f03f925810272f595d79dce44aChris Masone ('factory', '17'). 17667f06d6003bb11f03f925810272f595d79dce44aChris Masone 17733ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng @param since_date: a datetime object, return all manifest files 17833ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng since |since_date| 17967f06d6003bb11f03f925810272f595d79dce44aChris Masone @param board: the board whose manifests we want to check for. 18067f06d6003bb11f03f925810272f595d79dce44aChris Masone @return {(branch_type, milestone): [manifests, oldest, to, newest]} 18167f06d6003bb11f03f925810272f595d79dce44aChris Masone """ 18293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone return self._GetManifests( 18393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone re.compile(self._BOARD_MANIFEST_RE_PATTERN % board), 18433ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng self._QueryManifestsSinceDate(since_date, board)) 18593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 18693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 18793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone def ManifestsSinceRev(self, rev, board): 18893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """Return map of branch:manifests for |board| since git |rev|. 18993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 19093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone To fully specify a 'branch', one needs both the type and the numeric 19193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone milestone the branch was cut for, e.g. ('release', '19') or 19293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone ('factory', '17'). 19393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 19493f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @param rev: return all manifest files from |rev| up to HEAD. 19593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @param board: the board whose manifests we want to check for. 19693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @return {(branch_type, milestone): [manifests, oldest, to, newest]} 19793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """ 19893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone return self._GetManifests( 19993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone re.compile(self._BOARD_MANIFEST_RE_PATTERN % board), 20093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone self._QueryManifestsSinceRev(rev, board)) 20193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 20293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 203a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi def GetLatestManifest(self, board, build_type, milestone=None): 204a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi """Get the latest manifest of a given board and type. 205a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi 206a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi @param board: the board whose manifests we want to check for. 207a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi @param build_type: Type of a build, e.g., release, factory or firmware. 208a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi @param milestone: Milestone to look for the latest manifest. Default to 209a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi None, i.e., use the latest milestone. 210a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi 211a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi @return: (milestone, manifest), e.g., (46, '7268.0.0') 212a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi 213a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi """ 214a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi milestones_folder = os.path.join( 215a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi self._tempdir.name, 216a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi self._BOARD_BRANCH_MANIFEST_GLOB_PATTERN % (board, build_type)) 217a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi if not milestone: 218a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi try: 219a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi milestone_names = os.listdir(milestones_folder) 220a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi except OSError: 221a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi milestone_names = None 222a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi if not milestone_names: 223a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi raise QueryException('There is no milestone existed in %s.' % 224a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi milestones_folder) 225a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi milestone = max([m for m in milestone_names if m.isdigit()]) 226a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi manifests_folder = os.path.join(milestones_folder, str(milestone)) 227a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi manifests = [m.strip('.xml') for m in os.listdir(manifests_folder) 228a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi if m.endswith('.xml')] 229a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi if not manifests: 230a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi raise QueryException('There is no build existed in %s.' % 231a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi manifests_folder) 232a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi manifests.sort(key=version.LooseVersion) 233a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi return milestone, manifests[-1] 234a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi 235a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi 23693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone def _GetManifests(self, matcher, manifest_paths): 23793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """Parse a list of manifest_paths into a map of branch:manifests. 23893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 23993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone Given a regexp |matcher| and a list of paths to manifest files, 24093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone parse the paths and build up a map of branches to manifests of 24193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone builds on those branches. 24293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 24393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone To fully specify a 'branch', one needs both the type and the numeric 24493f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone milestone the branch was cut for, e.g. ('release', '19') or 24593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone ('factory', '17'). 24693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 24793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @param matcher: a compiled regexp that can be used to parse info 24893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone out of the path to a manifest file. 24993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @param manifest_paths: an iterable of paths to manifest files. 25093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @return {(branch_type, milestone): [manifests, oldest, to, newest]} 25193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """ 25267f06d6003bb11f03f925810272f595d79dce44aChris Masone branch_manifests = {} 25393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone for manifest_path in manifest_paths: 2549273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone logging.debug('parsing manifest path %s', manifest_path) 2550d00357df2bb69ac799fa1ac6ca81bb68b137c78Alex Miller match = matcher.match(manifest_path) 2560d00357df2bb69ac799fa1ac6ca81bb68b137c78Alex Miller if not match: 25752ac9370d76ce669435589b71d0fdc57ef85535bFang Deng logging.warning('Failed to parse path %s, regex: %s', 258a25e0d48f2c15b7c4d430b671aaee9cb56c8fb1eDan Shi manifest_path, matcher.pattern) 2590d00357df2bb69ac799fa1ac6ca81bb68b137c78Alex Miller continue 2600d00357df2bb69ac799fa1ac6ca81bb68b137c78Alex Miller groups = match.groups() 261cdcc0ee70346096cbc88b2bef20daab1cb0dc4d6Alex Miller config_type, milestone, manifest = groups 262cdcc0ee70346096cbc88b2bef20daab1cb0dc4d6Alex Miller branch = branch_manifests.setdefault((config_type, milestone), []) 263cdcc0ee70346096cbc88b2bef20daab1cb0dc4d6Alex Miller branch.append(manifest) 26467f06d6003bb11f03f925810272f595d79dce44aChris Masone for manifest_list in branch_manifests.itervalues(): 26567f06d6003bb11f03f925810272f595d79dce44aChris Masone manifest_list.sort(key=version.LooseVersion) 26667f06d6003bb11f03f925810272f595d79dce44aChris Masone return branch_manifests 26767f06d6003bb11f03f925810272f595d79dce44aChris Masone 26867f06d6003bb11f03f925810272f595d79dce44aChris Masone 26993f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone def GetCheckpoint(self): 27093f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """Return the latest checked-out git revision in manifest-versions.git. 27193f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 27293f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @return the git hash of the latest git revision. 27393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """ 27428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone return _SystemOutput(self._BuildCommand('log', 27528424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone '--pretty="format:%H"', 27628424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone '--max-count=1')).strip() 27793f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 27893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone 27967f06d6003bb11f03f925810272f595d79dce44aChris Masone def Update(self): 28067f06d6003bb11f03f925810272f595d79dce44aChris Masone """Get latest manifest information.""" 28128424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone return _System(self._BuildCommand('pull')) 28267f06d6003bb11f03f925810272f595d79dce44aChris Masone 28367f06d6003bb11f03f925810272f595d79dce44aChris Masone 2845bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone def _BuildCommand(self, command, *args): 2855bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Build a git CLI |command|, passing space-delineated |args|. 2865bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 2875bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @param command: the git sub-command to use. 2885bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @param args: args for the git sub-command. Will be space-delineated. 2895bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @return a string with the above formatted into it. 2905bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """ 291bb3388570efebf3f3d07b9d71238190ce9fe0088Chris Masone return '%s --git-dir=%s --work-tree=%s %s %s' % ( 2925bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone self._git, os.path.join(self._tempdir.name, '.git'), 293bb3388570efebf3f3d07b9d71238190ce9fe0088Chris Masone self._tempdir.name, command, ' '.join(args)) 2945bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 2955bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 2965bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone def _Clone(self): 2975bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Clone self._MANIFEST_VERSIONS_URL into a local temp dir.""" 2985bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone # Can't use --depth here because the internal gerrit server doesn't 2995bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone # support it. Wish we could. http://crosbug.com/29047 300bb3388570efebf3f3d07b9d71238190ce9fe0088Chris Masone # Also, note that --work-tree and --git-dir interact oddly with 301bb3388570efebf3f3d07b9d71238190ce9fe0088Chris Masone # 'git clone', so we don't use them. 30228424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone _System('%s clone %s %s' % (self._git, 30328424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone self._MANIFEST_VERSIONS_URL, 30428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone self._tempdir.name)) 3055bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3065bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3075bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone def _ShowCmd(self): 3085bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Return a git command that shows file names added by commits.""" 3095bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone return self._BuildCommand('show', 3105bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone '--pretty="format:"', 3115bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone '--name-only', 3125bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone '--diff-filter=A') 3135bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3145bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 31593f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone def _QueryManifestsSinceRev(self, git_rev, board): 31693f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone """Get manifest filenames for |board|, since |git_rev|. 3175bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 31893f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone @param git_rev: check for manifests newer than this git commit. 3195bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @param board: the board whose manifests we want to check for. 3205bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @return whitespace-delineated 3215bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @raise QueryException if errors occur. 3225bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """ 32393f51d4da152538007d5b44a2dc9d2bbb1fe3429Chris Masone return self._QueryManifestsSince(git_rev + '..HEAD', board) 3245bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3255bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 32633ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng def _QueryManifestsSinceDate(self, since_date, board): 32733ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng """Return list of manifest files for |board| since |since_date|. 3285bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 32933ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng @param sync_date: a datetime object, return all manifest files 33033ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng since |since_date|. 3315bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @param board: the board whose manifests we want to check for. 3325bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @raise QueryException if errors occur. 3335bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """ 33433ea03b85c3f3b40be2813b4e5a87c8a2f3e9f8dFang Deng return self._QueryManifestsSince('--since="%s"' % since_date, board) 3355bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3365bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3379273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone def _ExpandGlobMinusPrefix(self, prefix, path_glob): 3389273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone """Expand |path_glob| under dir |prefix|, then remove |prefix|. 3399273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone 3409273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone Path-concatenate prefix and path_glob, then expand the resulting glob. 3419273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone Take the results and remove |prefix| (and path separator) from each. 3429273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone Return the resulting list. 3439273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone 3449273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone Assuming /tmp/foo/baz and /tmp/bar/baz both exist, 3459273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone _ExpandGlobMinusPrefix('/tmp', '*/baz') # ['bar/baz', 'foo/baz'] 3469273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone 3479273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone @param prefix: a path under which to expand |path_glob|. 3489273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone @param path_glob: the glob to expand. 3499273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone @return a list of paths relative to |prefix|, based on |path_glob|. 3509273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone """ 3519273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone full_glob = os.path.join(prefix, path_glob) 3529273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone return [p[len(prefix)+1:] for p in glob.iglob(full_glob)] 3539273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone 3549273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone 3555bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone def _QueryManifestsSince(self, since_spec, board): 3565bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """Return list of manifest files for |board|, since |since_spec|. 3575bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone 3585bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @param since_spec: a formatted arg to git log that specifies a starting 3595bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone point to list commits from, e.g. 3605bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone '--since="2 days ago"' or 'd34db33f..' 3615bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @param board: the board whose manifests we want to check for. 3625bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone @raise QueryException if git log or git show errors occur. 3635bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone """ 3649273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone manifest_paths = self._ExpandGlobMinusPrefix( 3659273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone self._tempdir.name, self._BOARD_MANIFEST_GLOB_PATTERN % board) 3665bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone log_cmd = self._BuildCommand('log', 3675bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone since_spec, 3685bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone '--pretty="format:%H"', 3695bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone '--', 3709273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone ' '.join(manifest_paths)) 3715bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone try: 3729273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone # If we pass nothing to git show, we get unexpected results. 3739273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone # So, return early if git log is going to give us nothing. 37428424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone if not manifest_paths or not _SystemOutput(log_cmd): 3759273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone return [] 37628424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone manifests = _SystemOutput('%s|xargs %s' % (log_cmd, 37728424c52f29f690c2dfa2a6c4d649f25edfada11Chris Masone self._ShowCmd())) 3785bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone except (IOError, OSError) as e: 3795bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone raise QueryException(e) 3809273c0ddfcdd07a298c2145270d7bf8cb9865e23Chris Masone logging.debug('found %s', manifests) 3815bde7dc7d8b0608feb43491d5ac648d11c890f54Chris Masone return [m for m in re.split('\s+', manifests) if m] 382