Upstream version 8.36.161.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / scripts / cros_mark_chrome_as_stable.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 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 """This module uprevs Chrome for cbuildbot.
7
8 After calling, it prints outs CHROME_VERSION_ATOM=(version atom string).  A
9 caller could then use this atom with emerge to build the newly uprevved version
10 of Chrome e.g.
11
12 ./cros_mark_chrome_as_stable tot
13 Returns chrome-base/chromeos-chrome-8.0.552.0_alpha_r1
14
15 emerge-x86-generic =chrome-base/chromeos-chrome-8.0.552.0_alpha_r1
16 """
17
18 import filecmp
19 import optparse
20 import os
21 import re
22 import sys
23 import time
24
25 from chromite.buildbot import constants
26 from chromite.buildbot import portage_utilities
27 from chromite.lib import cros_build_lib
28 from chromite.lib import gclient
29 from chromite.lib import git
30 from chromite.scripts import cros_mark_as_stable
31
32 # Helper regex's for finding ebuilds.
33 _CHROME_VERSION_REGEX = r'\d+\.\d+\.\d+\.\d+'
34 _NON_STICKY_REGEX = r'%s[(_rc.*)|(_alpha.*)]+' % _CHROME_VERSION_REGEX
35
36 # Dir where all the action happens.
37 _CHROME_OVERLAY_DIR = ('%(srcroot)s/third_party/chromiumos-overlay/' +
38                        constants.CHROME_CP)
39
40 _GIT_COMMIT_MESSAGE = ('Marking %(chrome_rev)s for chrome ebuild with version '
41                        '%(chrome_version)s as stable.')
42
43 # URLs that print lists of chrome revisions between two versions of the browser.
44 _CHROME_VERSION_URL = ('http://omahaproxy.appspot.com/changelog?'
45                        'old_version=%(old)s&new_version=%(new)s')
46
47 # Only print links when we rev these types.
48 _REV_TYPES_FOR_LINKS = [constants.CHROME_REV_LATEST,
49                         constants.CHROME_REV_STICKY]
50
51 _CHROME_SVN_TAG = 'CROS_SVN_COMMIT'
52
53
54 def _GetSvnUrl(base_url):
55   """Returns the path to the svn url for the given chrome branch."""
56   return os.path.join(base_url, 'trunk')
57
58
59 def _GetVersionContents(chrome_version_info):
60   """Returns the current Chromium version, from the contents of a VERSION file.
61
62   Args:
63      chrome_version_info: The contents of a chromium VERSION file.
64   """
65   chrome_version_array = []
66   for line in chrome_version_info.splitlines():
67     chrome_version_array.append(line.rpartition('=')[2])
68
69   return '.'.join(chrome_version_array)
70
71 def _GetSpecificVersionUrl(base_url, revision, time_to_wait=600):
72   """Returns the Chromium version, from a repository URL and version.
73
74   Args:
75      base_url: URL for the root of the chromium checkout.
76      revision: the SVN revision we want to use.
77      time_to_wait: the minimum period before abandoning our wait for the
78          desired revision to be present.
79   """
80   svn_url = os.path.join(_GetSvnUrl(base_url), 'src', 'chrome', 'VERSION')
81   if not revision or not (int(revision) > 0):
82     raise Exception('Revision must be positive, got %s' % revision)
83
84   start = time.time()
85   # Use the fact we are SVN, hence ordered.
86   # Dodge the fact it will silently ignore the revision if it is not
87   # yet known.  (i.e. too high)
88   repo_version = gclient.GetTipOfTrunkSvnRevision(base_url)
89   while revision > repo_version:
90     if time.time() - start > time_to_wait:
91       raise Exception('Timeout Exceeeded')
92
93     msg = 'Repository only has version %s, looking for %s.  Sleeping...'
94     cros_build_lib.Info(msg, repo_version, revision)
95     time.sleep(30)
96     repo_version = gclient.GetTipOfTrunkSvnRevision(base_url)
97
98   chrome_version_info = cros_build_lib.RunCommand(
99       ['svn', 'cat', '-r', revision, svn_url],
100       redirect_stdout=True,
101       error_message='Could not read version file at %s revision %s.' %
102                     (svn_url, revision)).output
103
104   return _GetVersionContents(chrome_version_info)
105
106
107 def _GetTipOfTrunkVersionFile(root):
108   """Returns the current Chromium version, from a file in a checkout.
109
110   Args:
111      root: path to the root of the chromium checkout.
112   """
113   version_file = os.path.join(root, 'src', 'chrome', 'VERSION')
114   chrome_version_info = cros_build_lib.RunCommand(
115       ['cat', version_file],
116       redirect_stdout=True,
117       error_message='Could not read version file at %s.' % version_file).output
118
119   return _GetVersionContents(chrome_version_info)
120
121
122 def _GetLatestRelease(base_url, branch=None):
123   """Gets the latest release version from the buildspec_url for the branch.
124
125   Args:
126     base_url: Base URL for the SVN repository.
127     branch: If set, gets the latest release for branch, otherwise latest
128       release.
129
130   Returns:
131     Latest version string.
132   """
133   buildspec_url = os.path.join(base_url, 'releases')
134   svn_ls = cros_build_lib.RunCommand(['svn', 'ls', buildspec_url],
135                                      redirect_stdout=True).output
136   sorted_ls = cros_build_lib.RunCommand(['sort', '--version-sort', '-r'],
137                                         input=svn_ls,
138                                         redirect_stdout=True).output
139   if branch:
140     chrome_version_re = re.compile(r'^%s\.\d+.*' % branch)
141   else:
142     chrome_version_re = re.compile(r'^[0-9]+\..*')
143
144   for chrome_version in sorted_ls.splitlines():
145     if chrome_version_re.match(chrome_version):
146       deps_url = os.path.join(buildspec_url, chrome_version, 'DEPS')
147       deps_check = cros_build_lib.RunCommand(['svn', 'ls', deps_url],
148                                              error_code_ok=True,
149                                              redirect_stdout=True).output
150       if deps_check == 'DEPS\n':
151         return chrome_version.rstrip('/')
152
153   return None
154
155
156 def _GetStickyEBuild(stable_ebuilds):
157   """Returns the sticky ebuild."""
158   sticky_ebuilds = []
159   non_sticky_re = re.compile(_NON_STICKY_REGEX)
160   for ebuild in stable_ebuilds:
161     if not non_sticky_re.match(ebuild.version):
162       sticky_ebuilds.append(ebuild)
163
164   if not sticky_ebuilds:
165     raise Exception('No sticky ebuilds found')
166   elif len(sticky_ebuilds) > 1:
167     cros_build_lib.Warning('More than one sticky ebuild found')
168
169   return portage_utilities.BestEBuild(sticky_ebuilds)
170
171
172 class ChromeEBuild(portage_utilities.EBuild):
173   """Thin sub-class of EBuild that adds a chrome_version field."""
174   chrome_version_re = re.compile(r'.*%s-(%s|9999).*' % (
175       constants.CHROME_PN, _CHROME_VERSION_REGEX))
176   chrome_version = ''
177
178   def __init__(self, path):
179     portage_utilities.EBuild.__init__(self, path)
180     re_match = self.chrome_version_re.match(self.ebuild_path_no_revision)
181     if re_match:
182       self.chrome_version = re_match.group(1)
183
184   def __str__(self):
185     return self.ebuild_path
186
187
188 def FindChromeCandidates(overlay_dir):
189   """Return a tuple of chrome's unstable ebuild and stable ebuilds.
190
191   Args:
192     overlay_dir: The path to chrome's portage overlay dir.
193
194   Returns:
195     Tuple [unstable_ebuild, stable_ebuilds].
196
197   Raises:
198     Exception: if no unstable ebuild exists for Chrome.
199   """
200   stable_ebuilds = []
201   unstable_ebuilds = []
202   for path in [
203       os.path.join(overlay_dir, entry) for entry in os.listdir(overlay_dir)]:
204     if path.endswith('.ebuild'):
205       ebuild = ChromeEBuild(path)
206       if not ebuild.chrome_version:
207         cros_build_lib.Warning('Poorly formatted ebuild found at %s' % path)
208       else:
209         if '9999' in ebuild.version:
210           unstable_ebuilds.append(ebuild)
211         else:
212           stable_ebuilds.append(ebuild)
213
214   # Apply some sanity checks.
215   if not unstable_ebuilds:
216     raise Exception('Missing 9999 ebuild for %s' % overlay_dir)
217   if not stable_ebuilds:
218     cros_build_lib.Warning('Missing stable ebuild for %s' % overlay_dir)
219
220   return portage_utilities.BestEBuild(unstable_ebuilds), stable_ebuilds
221
222
223 def FindChromeUprevCandidate(stable_ebuilds, chrome_rev, sticky_branch):
224   """Finds the Chrome uprev candidate for the given chrome_rev.
225
226   Using the pre-flight logic, this means the stable ebuild you are uprevving
227   from.  The difference here is that the version could be different and in
228   that case we want to find it to delete it.
229
230   Args:
231     stable_ebuilds: A list of stable ebuilds.
232     chrome_rev: The chrome_rev designating which candidate to find.
233     sticky_branch: The the branch that is currently sticky with Major/Minor
234       components.  For example: 9.0.553. Can be None but not if chrome_rev
235       is CHROME_REV_STICKY.
236
237   Returns:
238     The EBuild, otherwise None if none found.
239   """
240   candidates = []
241   if chrome_rev in [constants.CHROME_REV_LOCAL, constants.CHROME_REV_TOT,
242                     constants.CHROME_REV_SPEC]:
243     # These are labelled alpha, for historic reasons,
244     # not just for the fun of confusion.
245     chrome_branch_re = re.compile(r'%s.*_alpha.*' % _CHROME_VERSION_REGEX)
246     for ebuild in stable_ebuilds:
247       if chrome_branch_re.search(ebuild.version):
248         candidates.append(ebuild)
249
250   elif chrome_rev == constants.CHROME_REV_STICKY:
251     assert sticky_branch is not None
252     chrome_branch_re = re.compile(r'%s\..*' % sticky_branch)
253     for ebuild in stable_ebuilds:
254       if chrome_branch_re.search(ebuild.version):
255         candidates.append(ebuild)
256
257   else:
258     chrome_branch_re = re.compile(r'%s.*_rc.*' % _CHROME_VERSION_REGEX)
259     for ebuild in stable_ebuilds:
260       if chrome_branch_re.search(ebuild.version):
261         candidates.append(ebuild)
262
263   if candidates:
264     return portage_utilities.BestEBuild(candidates)
265   else:
266     return None
267
268 def _AnnotateAndPrint(text, url):
269   """Add buildbot trappings to print <a href='url'>text</a> in the waterfall.
270
271   Args:
272     text: Anchor text for the link
273     url: the URL to which to link
274   """
275   print >> sys.stderr, '\n@@@STEP_LINK@%(text)s@%(url)s@@@' % { 'text': text,
276                                                               'url': url }
277
278 def GetChromeRevisionLinkFromVersions(old_chrome_version, chrome_version):
279   """Return appropriately formatted link to revision info, given versions
280
281   Given two chrome version strings (e.g. 9.0.533.0), generate a link to a
282   page that prints the Chromium revisions between those two versions.
283
284   Args:
285     old_chrome_version: version to diff from
286     chrome_version: version to which to diff
287
288   Returns:
289     The desired URL.
290   """
291   return _CHROME_VERSION_URL % { 'old': old_chrome_version,
292                                  'new': chrome_version }
293
294 def GetChromeRevisionListLink(old_chrome, new_chrome, chrome_rev):
295   """Returns a link to the list of revisions between two Chromium versions
296
297   Given two ChromeEBuilds and the kind of rev we're doing, generate a
298   link to a page that prints the Chromium changes between those two
299   revisions, inclusive.
300
301   Args:
302     old_chrome: ebuild for the version to diff from
303     new_chrome: ebuild for the version to which to diff
304     chrome_rev: one of constants.VALID_CHROME_REVISIONS
305
306   Returns:
307     The desired URL.
308   """
309   assert chrome_rev in _REV_TYPES_FOR_LINKS
310   return GetChromeRevisionLinkFromVersions(old_chrome.chrome_version,
311                                            new_chrome.chrome_version)
312
313 def MarkChromeEBuildAsStable(stable_candidate, unstable_ebuild, chrome_rev,
314                              chrome_version, commit, overlay_dir):
315   r"""Uprevs the chrome ebuild specified by chrome_rev.
316
317   This is the main function that uprevs the chrome_rev from a stable candidate
318   to its new version.
319
320   Args:
321     stable_candidate: ebuild that corresponds to the stable ebuild we are
322       revving from.  If None, builds the a new ebuild given the version
323       and logic for chrome_rev type with revision set to 1.
324     unstable_ebuild: ebuild corresponding to the unstable ebuild for chrome.
325     chrome_rev: one of constants.VALID_CHROME_REVISIONS or LOCAL
326       constants.CHROME_REV_SPEC -  Requires commit value.  Revs the ebuild for
327         the specified version and uses the portage suffix of _alpha.
328       constants.CHROME_REV_TOT -  Requires commit value.  Revs the ebuild for
329         the TOT version and uses the portage suffix of _alpha.
330       constants.CHROME_REV_LOCAL - Requires a chrome_root. Revs the ebuild for
331         the local version and uses the portage suffix of _alpha.
332       constants.CHROME_REV_LATEST - This uses the portage suffix of _rc as they
333         are release candidates for the next sticky version.
334       constants.CHROME_REV_STICKY -  Revs the sticky version.
335     chrome_version: The \d.\d.\d.\d version of Chrome.
336     commit: Used with constants.CHROME_REV_TOT.  The svn revision of chrome.
337     overlay_dir: Path to the chromeos-chrome package dir.
338
339   Returns:
340     Full portage version atom (including rc's, etc) that was revved.
341   """
342   def IsTheNewEBuildRedundant(new_ebuild, stable_ebuild):
343     """Returns True if the new ebuild is redundant.
344
345     This is True if there if the current stable ebuild is the exact same copy
346     of the new one.
347     """
348     if not stable_ebuild:
349       return False
350
351     if stable_candidate.chrome_version == new_ebuild.chrome_version:
352       return filecmp.cmp(
353           new_ebuild.ebuild_path, stable_ebuild.ebuild_path, shallow=False)
354
355   # Mark latest release and sticky branches as stable.
356   mark_stable = chrome_rev not in [constants.CHROME_REV_TOT,
357                                    constants.CHROME_REV_SPEC,
358                                    constants.CHROME_REV_LOCAL]
359
360   # Case where we have the last stable candidate with same version just rev.
361   if stable_candidate and stable_candidate.chrome_version == chrome_version:
362     new_ebuild_path = '%s-r%d.ebuild' % (
363         stable_candidate.ebuild_path_no_revision,
364         stable_candidate.current_revision + 1)
365   else:
366     suffix = 'rc' if mark_stable else 'alpha'
367     pf = '%s-%s_%s-r1' % (constants.CHROME_PN, chrome_version, suffix)
368     new_ebuild_path = os.path.join(overlay_dir, '%s.ebuild' % pf)
369
370   chrome_variables = dict()
371   if commit:
372     chrome_variables[_CHROME_SVN_TAG] = commit
373
374   portage_utilities.EBuild.MarkAsStable(
375       unstable_ebuild.ebuild_path, new_ebuild_path,
376       chrome_variables, make_stable=mark_stable)
377   new_ebuild = ChromeEBuild(new_ebuild_path)
378
379   # Determine whether this is ebuild is redundant.
380   if IsTheNewEBuildRedundant(new_ebuild, stable_candidate):
381     msg = 'Previous ebuild with same version found and ebuild is redundant.'
382     cros_build_lib.Info(msg)
383     os.unlink(new_ebuild_path)
384     return None
385
386   if stable_candidate and chrome_rev in _REV_TYPES_FOR_LINKS:
387     _AnnotateAndPrint('Chromium revisions',
388                       GetChromeRevisionListLink(stable_candidate,
389                                                 new_ebuild,
390                                                 chrome_rev))
391
392   git.RunGit(overlay_dir, ['add', new_ebuild_path])
393   if stable_candidate and not stable_candidate.IsSticky():
394     git.RunGit(overlay_dir, ['rm', stable_candidate.ebuild_path])
395
396   portage_utilities.EBuild.CommitChange(
397       _GIT_COMMIT_MESSAGE % {'chrome_rev': chrome_rev,
398                              'chrome_version': chrome_version},
399       overlay_dir)
400
401   return '%s-%s' % (new_ebuild.package, new_ebuild.version)
402
403
404 def ParseMaxRevision(revision_list):
405   """Returns the max revision from a list of url@revision string."""
406   revision_re = re.compile(r'.*@(\d+)')
407
408   def RevisionKey(revision):
409     return revision_re.match(revision).group(1)
410
411   max_revision = max(revision_list.split(), key=RevisionKey)
412   return max_revision.rpartition('@')[2]
413
414
415 def main(_argv):
416   usage_options = '|'.join(constants.VALID_CHROME_REVISIONS)
417   usage = '%s OPTIONS [%s]' % (__file__, usage_options)
418   parser = optparse.OptionParser(usage)
419   parser.add_option('-b', '--boards', default='x86-generic')
420   parser.add_option('-c', '--chrome_url', default=gclient.GetBaseURLs()[0])
421   parser.add_option('-f', '--force_revision', default=None)
422   parser.add_option('-s', '--srcroot', default=os.path.join(os.environ['HOME'],
423                                                             'trunk', 'src'),
424                     help='Path to the src directory')
425   parser.add_option('-t', '--tracking_branch', default='cros/master',
426                     help='Branch we are tracking changes against')
427   (options, args) = parser.parse_args()
428
429   if len(args) != 1 or args[0] not in constants.VALID_CHROME_REVISIONS:
430     parser.error('Commit requires arg set to one of %s.'
431                  % constants.VALID_CHROME_REVISIONS)
432
433   overlay_dir = os.path.abspath(_CHROME_OVERLAY_DIR %
434                                 {'srcroot': options.srcroot})
435   chrome_rev = args[0]
436   version_to_uprev = None
437   commit_to_use = None
438   sticky_branch = None
439
440   (unstable_ebuild, stable_ebuilds) = FindChromeCandidates(overlay_dir)
441
442   if chrome_rev == constants.CHROME_REV_LOCAL:
443     if 'CHROME_ROOT' in os.environ:
444       chrome_root = os.environ['CHROME_ROOT']
445     else:
446       chrome_root = os.path.join(os.environ['HOME'], 'chrome_root')
447
448     version_to_uprev = _GetTipOfTrunkVersionFile(chrome_root)
449     commit_to_use = 'Unknown'
450     cros_build_lib.Info('Using local source, versioning is untrustworthy.')
451   elif chrome_rev == constants.CHROME_REV_SPEC:
452     commit_to_use = options.force_revision
453     if '@' in commit_to_use:
454       commit_to_use = ParseMaxRevision(commit_to_use)
455     version_to_uprev = _GetSpecificVersionUrl(options.chrome_url,
456                                               commit_to_use)
457   elif chrome_rev == constants.CHROME_REV_TOT:
458     commit_to_use = gclient.GetTipOfTrunkSvnRevision(options.chrome_url)
459     version_to_uprev = _GetSpecificVersionUrl(options.chrome_url,
460                                               commit_to_use)
461   elif chrome_rev == constants.CHROME_REV_LATEST:
462     version_to_uprev = _GetLatestRelease(options.chrome_url)
463   else:
464     sticky_ebuild = _GetStickyEBuild(stable_ebuilds)
465     sticky_version = sticky_ebuild.chrome_version
466     sticky_branch = sticky_version.rpartition('.')[0]
467     version_to_uprev = _GetLatestRelease(options.chrome_url, sticky_branch)
468
469   stable_candidate = FindChromeUprevCandidate(stable_ebuilds, chrome_rev,
470                                               sticky_branch)
471
472   if stable_candidate:
473     cros_build_lib.Info('Stable candidate found %s' % stable_candidate)
474   else:
475     cros_build_lib.Info('No stable candidate found.')
476
477   tracking_branch = 'remotes/m/%s' % os.path.basename(options.tracking_branch)
478   existing_branch = git.GetCurrentBranch(overlay_dir)
479   work_branch = cros_mark_as_stable.GitBranch(constants.STABLE_EBUILD_BRANCH,
480                                               tracking_branch, overlay_dir)
481   work_branch.CreateBranch()
482
483   # In the case of uprevving overlays that have patches applied to them,
484   # include the patched changes in the stabilizing branch.
485   if existing_branch:
486     git.RunGit(overlay_dir, ['rebase', existing_branch])
487
488   chrome_version_atom = MarkChromeEBuildAsStable(
489       stable_candidate, unstable_ebuild, chrome_rev, version_to_uprev,
490       commit_to_use, overlay_dir)
491   # Explicit print to communicate to caller.
492   if chrome_version_atom:
493     cros_mark_as_stable.CleanStalePackages(options.boards.split(':'),
494                                            [chrome_version_atom])
495     print 'CHROME_VERSION_ATOM=%s' % chrome_version_atom