Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_best_revision.py
1 #!/usr/bin/python
2
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.
6
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.
9 """
10
11 import distutils.version
12 import logging
13 import os
14 import shutil
15 import tempfile
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
27
28
29 class LKGMNotFound(Exception):
30   """Raised if a newer valid LKGM could not be found."""
31
32
33 class LKGMNotCommitted(Exception):
34   """Raised if we could not submit a new LKGM."""
35
36
37 class ChromeCommitter(object):
38   """Committer object responsible for obtaining a new LKGM and committing it."""
39
40   _COMMIT_MSG = ('Automated Commit: Committing new LKGM version %(version)s '
41                  'for chromeos.')
42   _CANDIDATES_TO_CONSIDER = 10
43
44   _SLEEP_TIMEOUT = 30
45   _TREE_TIMEOUT = 7200
46
47   def __init__(self, checkout_dir, dryrun):
48     self._checkout_dir = checkout_dir
49     self._dryrun = dryrun
50     self._lkgm = None
51     self._old_lkgm = None
52
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)])
58
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,
61                                self._checkout_dir])
62     cros_build_lib.RunCommand(['svn', 'update', constants.CHROME_LKGM_FILE],
63                               cwd=self._checkout_dir)
64
65     self._old_lkgm = osutils.ReadFile(
66         os.path.join(self._checkout_dir, constants.CHROME_LKGM_FILE))
67
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)
73
74     # Strip gs://<path> prefix and trailing /'s.
75     versions = [os.path.basename(v.rstrip('/')) for v in version_paths]
76
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]
84
85   def FindNewLKGM(self):
86     """Finds a new LKGM for chrome from previous chromeos releases."""
87     versions = self._GetLatestCanaryVersions()
88     if not versions:
89       raise LKGMNotFound('No valid LKGM found newer than the old LKGM.')
90
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',
94                  ' '.join(canaries))
95
96     # Scores are based on passing builders.
97     version_scores = {}
98     for version in versions:
99       for builder in canaries:
100         status = manifest_version.BuildSpecsManager.GetBuildStatus(
101             builder, version, retries=0)
102         if status:
103           if status.Passed():
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
108             break
109
110       logging.info('Version %s had score %d', version,
111                    version_scores.get(version, 0))
112
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.')
118
119     self._lkgm = lkgm
120
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?')
126
127     # Add the new versioned file.
128     osutils.WriteFile(
129         os.path.join(self._checkout_dir, constants.CHROME_LKGM_FILE),
130         self._lkgm)
131
132     add_cmd = ['svn', 'add', constants.CHROME_LKGM_FILE]
133     cros_build_lib.RunCommand(add_cmd, cwd=self._checkout_dir)
134
135     # Commit it!
136     commit_cmd = ['svn', 'commit', '--message',
137                   self. _COMMIT_MSG % dict(version=self._lkgm)]
138
139     if not timeout_util.IsTreeOpen(gclient.STATUS_URL,
140         self._SLEEP_TIMEOUT, timeout=self._TREE_TIMEOUT):
141       raise LKGMNotCommitted('Chromium Tree is closed')
142
143     # Sadly svn commit does not have a dryrun option.
144     if not self._dryrun:
145       try:
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)
149     else:
150       logging.info('Would have run: %s', ' '.join(commit_cmd))
151
152   def UpdateLatestFilesForBot(self, config, versions):
153     """Update the LATEST files, for a given bot, in Google Storage.
154
155     Args:
156       config: The builder config to update.
157       versions: Versions of ChromeOS to look at, sorted in descending order.
158     """
159     base_url = cbuildbot_archive.GetBaseUploadURI(config)
160     acl = cbuildbot_archive.GetUploadACL(config)
161     latest_url = None
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:
171         try:
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:
175           found = True
176
177       if found:
178         cros_build_lib.Info('Found %s', url)
179         latest_url = url
180
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,
187                                    processes=100)
188
189
190 def _GetParser():
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.")
195   return parser
196
197
198 def main(argv):
199   parser = _GetParser()
200   args = parser.parse_args(argv)
201
202   checkout_dir = tempfile.mkdtemp(prefix='cbr_chrome_checkout')
203   try:
204     committer = ChromeCommitter(checkout_dir, dryrun=args.dryrun)
205     committer.CheckoutChromeLKGM()
206     committer.UpdateLatestFiles()
207     committer.FindNewLKGM()
208     committer.CommitNewLKGM()
209     return 0
210   finally:
211     shutil.rmtree(checkout_dir)