3 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
7 """This program examines success/fail history for Chrome/ium OS builds and
8 checks in a LKGM version for Chrome OS for other consumers.
11 import distutils.version
16 from chromite.buildbot import cbuildbot_archive
17 from chromite.buildbot import cbuildbot_config
18 from chromite.buildbot import constants
19 from chromite.buildbot import manifest_version
20 from chromite.lib import commandline
21 from chromite.lib import cros_build_lib
22 from chromite.lib import gclient
23 from chromite.lib import gs
24 from chromite.lib import osutils
25 from chromite.lib import parallel
26 from chromite.lib import timeout_util
29 class LKGMNotFound(Exception):
30 """Raised if a newer valid LKGM could not be found."""
33 class LKGMNotCommitted(Exception):
34 """Raised if we could not submit a new LKGM."""
37 class ChromeCommitter(object):
38 """Committer object responsible for obtaining a new LKGM and committing it."""
40 _COMMIT_MSG = ('Automated Commit: Committing new LKGM version %(version)s '
42 _CANDIDATES_TO_CONSIDER = 10
47 def __init__(self, checkout_dir, dryrun):
48 self._checkout_dir = checkout_dir
53 def CheckoutChromeLKGM(self):
54 """Checkout chromeos LKGM file for chrome into tmp checkout dir."""
55 # We function only on an empty directory.
56 lkgm_dir = '/'.join([gclient.CHROME_COMMITTER_URL,
57 os.path.dirname(constants.SVN_CHROME_LKGM)])
59 # We checkout the bare necessities to get the .svn/file needed to commit.
60 cros_build_lib.RunCommand(['svn', 'checkout', '--depth=empty', lkgm_dir,
62 cros_build_lib.RunCommand(['svn', 'update', constants.CHROME_LKGM_FILE],
63 cwd=self._checkout_dir)
65 self._old_lkgm = osutils.ReadFile(
66 os.path.join(self._checkout_dir, constants.CHROME_LKGM_FILE))
68 @cros_build_lib.MemoizedSingleCall
69 def _GetLatestCanaryVersions(self):
70 """Returns the latest CANDIDATES_TO_CONSIDER canary versions."""
71 gs_handle = gs.GSContext()
72 version_paths = gs_handle.LS(manifest_version.BUILD_STATUS_URL)
74 # Strip gs://<path> prefix and trailing /'s.
75 versions = [os.path.basename(v.rstrip('/')) for v in version_paths]
77 lv = distutils.version.LooseVersion
78 # We only care about canary versions which always end in 0.0.
79 canary_versions = [v for v in versions if v.endswith('.0.0')]
80 new_canary_versions = [v for v in canary_versions
81 if lv(v) > lv(self._old_lkgm)]
82 return sorted(new_canary_versions, key=lv,
83 reverse=True)[0:self._CANDIDATES_TO_CONSIDER]
85 def FindNewLKGM(self):
86 """Finds a new LKGM for chrome from previous chromeos releases."""
87 versions = self._GetLatestCanaryVersions()
89 raise LKGMNotFound('No valid LKGM found newer than the old LKGM.')
91 canaries = cbuildbot_config.GetCanariesForChromeLKGM()
92 logging.info('Considering the following versions: %s', ' '.join(versions))
93 logging.info('Using scores from the following canaries: %s',
96 # Scores are based on passing builders.
98 for version in versions:
99 for builder in canaries:
100 status = manifest_version.BuildSpecsManager.GetBuildStatus(
101 builder, version, retries=0)
104 version_scores[version] = version_scores.get(version, 0) + 1
105 elif status.Failed():
106 # We don't consider builds with any reporting failures.
107 version_scores[version] = 0
110 logging.info('Version %s had score %d', version,
111 version_scores.get(version, 0))
113 # We want to get the version with the highest score. In case of a tie, we
114 # want to choose the highest version.
115 lkgm = max((v, k) for k, v in version_scores.iteritems())[1]
116 if not version_scores[lkgm] > 0:
117 raise LKGMNotFound('No valid LKGM found. Scores are too low.')
121 def CommitNewLKGM(self):
122 """Commits the new LKGM file using our template commit message."""
123 lv = distutils.version.LooseVersion
124 if not self._lkgm and not lv(self._lkgm) < lv(self._old_lkgm):
125 raise LKGMNotFound('No valid LKGM found. Did you run FindNewLKGM?')
127 # Add the new versioned file.
129 os.path.join(self._checkout_dir, constants.CHROME_LKGM_FILE),
132 add_cmd = ['svn', 'add', constants.CHROME_LKGM_FILE]
133 cros_build_lib.RunCommand(add_cmd, cwd=self._checkout_dir)
136 commit_cmd = ['svn', 'commit', '--message',
137 self. _COMMIT_MSG % dict(version=self._lkgm)]
139 if not timeout_util.IsTreeOpen(gclient.STATUS_URL,
140 self._SLEEP_TIMEOUT, timeout=self._TREE_TIMEOUT):
141 raise LKGMNotCommitted('Chromium Tree is closed')
143 # Sadly svn commit does not have a dryrun option.
146 cros_build_lib.RunCommand(commit_cmd, cwd=self._checkout_dir)
147 except cros_build_lib.RunCommandError as e:
148 raise LKGMNotCommitted('Could not submit LKGM: %r' % e)
150 logging.info('Would have run: %s', ' '.join(commit_cmd))
152 def UpdateLatestFilesForBot(self, config, versions):
153 """Update the LATEST files, for a given bot, in Google Storage.
156 config: The builder config to update.
157 versions: Versions of ChromeOS to look at, sorted in descending order.
159 base_url = cbuildbot_archive.GetBaseUploadURI(config)
160 acl = cbuildbot_archive.GetUploadACL(config)
162 # gs.GSContext skips over all commands (including read-only checks)
163 # when dry_run is True, so we have to create two context objects.
164 # TODO(davidjames): Fix this.
165 gs_ctx = gs.GSContext()
166 copy_ctx = gs.GSContext(dry_run=self._dryrun)
167 for version in reversed(versions):
168 url = os.path.join(base_url, 'LATEST-%s' % version)
169 found = gs_ctx.Exists(url, print_cmd=False)
170 if not found and latest_url:
172 copy_ctx.Copy(latest_url, url, version=0, acl=acl)
173 cros_build_lib.Info('Copied %s -> %s', latest_url, url)
174 except gs.GSContextPreconditionFailed:
178 cros_build_lib.Info('Found %s', url)
181 def UpdateLatestFiles(self):
182 """Update the LATEST files since LKGM, in Google Storage."""
183 ext_cfgs, int_cfgs = cbuildbot_config.FindFullConfigsForBoard(board=None)
184 versions = self._GetLatestCanaryVersions() + [self._old_lkgm]
185 tasks = [[cfg, versions] for cfg in ext_cfgs + int_cfgs]
186 parallel.RunTasksInProcessPool(self.UpdateLatestFilesForBot, tasks,
191 """Returns the parser to use for this module."""
192 parser = commandline.ArgumentParser(usage=__doc__, caching=True)
193 parser.add_argument('--dryrun', action='store_true', default=False,
194 help="Find the next LKGM but don't commit it.")
199 parser = _GetParser()
200 args = parser.parse_args(argv)
202 checkout_dir = tempfile.mkdtemp(prefix='cbr_chrome_checkout')
204 committer = ChromeCommitter(checkout_dir, dryrun=args.dryrun)
205 committer.CheckoutChromeLKGM()
206 committer.UpdateLatestFiles()
207 committer.FindNewLKGM()
208 committer.CommitNewLKGM()
211 shutil.rmtree(checkout_dir)