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.
6 """This module uprevs Chrome for cbuildbot.
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
12 ./cros_mark_chrome_as_stable tot
13 Returns chrome-base/chromeos-chrome-8.0.552.0_alpha_r1
15 emerge-x86-generic =chrome-base/chromeos-chrome-8.0.552.0_alpha_r1
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
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
36 # Dir where all the action happens.
37 _CHROME_OVERLAY_DIR = ('%(srcroot)s/third_party/chromiumos-overlay/' +
40 _GIT_COMMIT_MESSAGE = ('Marking %(chrome_rev)s for chrome ebuild with version '
41 '%(chrome_version)s as stable.')
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')
47 # Only print links when we rev these types.
48 _REV_TYPES_FOR_LINKS = [constants.CHROME_REV_LATEST,
49 constants.CHROME_REV_STICKY]
51 _CHROME_SVN_TAG = 'CROS_SVN_COMMIT'
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')
59 def _GetVersionContents(chrome_version_info):
60 """Returns the current Chromium version, from the contents of a VERSION file.
63 chrome_version_info: The contents of a chromium VERSION file.
65 chrome_version_array = []
66 for line in chrome_version_info.splitlines():
67 chrome_version_array.append(line.rpartition('=')[2])
69 return '.'.join(chrome_version_array)
71 def _GetSpecificVersionUrl(base_url, revision, time_to_wait=600):
72 """Returns the Chromium version, from a repository URL and version.
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.
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)
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')
93 msg = 'Repository only has version %s, looking for %s. Sleeping...'
94 cros_build_lib.Info(msg, repo_version, revision)
96 repo_version = gclient.GetTipOfTrunkSvnRevision(base_url)
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
104 return _GetVersionContents(chrome_version_info)
107 def _GetTipOfTrunkVersionFile(root):
108 """Returns the current Chromium version, from a file in a checkout.
111 root: path to the root of the chromium checkout.
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
119 return _GetVersionContents(chrome_version_info)
122 def _GetLatestRelease(base_url, branch=None):
123 """Gets the latest release version from the buildspec_url for the branch.
126 base_url: Base URL for the SVN repository.
127 branch: If set, gets the latest release for branch, otherwise latest
131 Latest version string.
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'],
138 redirect_stdout=True).output
140 chrome_version_re = re.compile(r'^%s\.\d+.*' % branch)
142 chrome_version_re = re.compile(r'^[0-9]+\..*')
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],
149 redirect_stdout=True).output
150 if deps_check == 'DEPS\n':
151 return chrome_version.rstrip('/')
156 def _GetStickyEBuild(stable_ebuilds):
157 """Returns the sticky ebuild."""
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)
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')
169 return portage_utilities.BestEBuild(sticky_ebuilds)
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))
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)
182 self.chrome_version = re_match.group(1)
185 return self.ebuild_path
188 def FindChromeCandidates(overlay_dir):
189 """Return a tuple of chrome's unstable ebuild and stable ebuilds.
192 overlay_dir: The path to chrome's portage overlay dir.
195 Tuple [unstable_ebuild, stable_ebuilds].
198 Exception: if no unstable ebuild exists for Chrome.
201 unstable_ebuilds = []
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)
209 if '9999' in ebuild.version:
210 unstable_ebuilds.append(ebuild)
212 stable_ebuilds.append(ebuild)
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)
220 return portage_utilities.BestEBuild(unstable_ebuilds), stable_ebuilds
223 def FindChromeUprevCandidate(stable_ebuilds, chrome_rev, sticky_branch):
224 """Finds the Chrome uprev candidate for the given chrome_rev.
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.
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.
238 The EBuild, otherwise None if none found.
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)
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)
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)
264 return portage_utilities.BestEBuild(candidates)
268 def _AnnotateAndPrint(text, url):
269 """Add buildbot trappings to print <a href='url'>text</a> in the waterfall.
272 text: Anchor text for the link
273 url: the URL to which to link
275 print >> sys.stderr, '\n@@@STEP_LINK@%(text)s@%(url)s@@@' % { 'text': text,
278 def GetChromeRevisionLinkFromVersions(old_chrome_version, chrome_version):
279 """Return appropriately formatted link to revision info, given versions
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.
285 old_chrome_version: version to diff from
286 chrome_version: version to which to diff
291 return _CHROME_VERSION_URL % { 'old': old_chrome_version,
292 'new': chrome_version }
294 def GetChromeRevisionListLink(old_chrome, new_chrome, chrome_rev):
295 """Returns a link to the list of revisions between two Chromium versions
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.
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
309 assert chrome_rev in _REV_TYPES_FOR_LINKS
310 return GetChromeRevisionLinkFromVersions(old_chrome.chrome_version,
311 new_chrome.chrome_version)
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.
317 This is the main function that uprevs the chrome_rev from a stable candidate
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.
340 Full portage version atom (including rc's, etc) that was revved.
342 def IsTheNewEBuildRedundant(new_ebuild, stable_ebuild):
343 """Returns True if the new ebuild is redundant.
345 This is True if there if the current stable ebuild is the exact same copy
348 if not stable_ebuild:
351 if stable_candidate.chrome_version == new_ebuild.chrome_version:
353 new_ebuild.ebuild_path, stable_ebuild.ebuild_path, shallow=False)
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]
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)
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)
370 chrome_variables = dict()
372 chrome_variables[_CHROME_SVN_TAG] = commit
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)
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)
386 if stable_candidate and chrome_rev in _REV_TYPES_FOR_LINKS:
387 _AnnotateAndPrint('Chromium revisions',
388 GetChromeRevisionListLink(stable_candidate,
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])
396 portage_utilities.EBuild.CommitChange(
397 _GIT_COMMIT_MESSAGE % {'chrome_rev': chrome_rev,
398 'chrome_version': chrome_version},
401 return '%s-%s' % (new_ebuild.package, new_ebuild.version)
404 def ParseMaxRevision(revision_list):
405 """Returns the max revision from a list of url@revision string."""
406 revision_re = re.compile(r'.*@(\d+)')
408 def RevisionKey(revision):
409 return revision_re.match(revision).group(1)
411 max_revision = max(revision_list.split(), key=RevisionKey)
412 return max_revision.rpartition('@')[2]
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'],
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()
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)
433 overlay_dir = os.path.abspath(_CHROME_OVERLAY_DIR %
434 {'srcroot': options.srcroot})
436 version_to_uprev = None
440 (unstable_ebuild, stable_ebuilds) = FindChromeCandidates(overlay_dir)
442 if chrome_rev == constants.CHROME_REV_LOCAL:
443 if 'CHROME_ROOT' in os.environ:
444 chrome_root = os.environ['CHROME_ROOT']
446 chrome_root = os.path.join(os.environ['HOME'], 'chrome_root')
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,
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,
461 elif chrome_rev == constants.CHROME_REV_LATEST:
462 version_to_uprev = _GetLatestRelease(options.chrome_url)
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)
469 stable_candidate = FindChromeUprevCandidate(stable_ebuilds, chrome_rev,
473 cros_build_lib.Info('Stable candidate found %s' % stable_candidate)
475 cros_build_lib.Info('No stable candidate found.')
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()
483 # In the case of uprevving overlays that have patches applied to them,
484 # include the patched changes in the stabilizing branch.
486 git.RunGit(overlay_dir, ['rebase', existing_branch])
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