184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold# Use of this source code is governed by a BSD-style license that can be
384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold# found in the LICENSE file.
484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold"""Modules for obtaining Chrome OS release info."""
684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnoldimport ConfigParser
984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnoldimport bisect
1084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnoldimport os
1184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
1284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
1347c8a9a7cf24e33dc66fccf64d7679646f9030b6Chris Sosa_RELEASE_CONFIG_FILE = os.path.join(os.path.dirname(__file__),
1447c8a9a7cf24e33dc66fccf64d7679646f9030b6Chris Sosa                                    'release_config.ini')
1584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
1684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold# Prefix for brachpoint definitions in the config file.
1784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold_CONF_BRANCH_SECTION = 'BRANCH'
1884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold_CONF_BRANCH_POINTS_OPT = 'branch_points'
1984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold_CONF_BRANCH_POINT_OPT_PREFIX = 'bp_'
2084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold_CONF_NEXT_BRANCH_OPT = 'next_branch'
2184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
2284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
2384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnoldclass ReleaseError(BaseException):
2484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    """Errors related to release and branch inference."""
2584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    pass
2684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
2784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
2884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnoldclass ReleaseInfo(object):
2984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    """Provides reference information about Chrome OS releases.
3084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
3184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    Currently, this class serves for mapping between releases and branches /
3284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    release milestones. The information lives in a .ini file at the current
3384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    directory, which has a single section [BRANCH] containing
3484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
3584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold      branch_points: comma-separated list of release branches (e.g. R10, R11,
3684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold      ...)
3784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
3884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold      bp_XYZ: for each branch listed above, a variable that maps to the Chrome
3984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold      OS release at that branchpoint (e.g. bp_r10: 0.10.156.0). Note that .ini
4084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold      file variables are case-insensitive.
4184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
4284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold      next_branch: the name of the current (unforked) branch (e.g. R24)
4384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
44c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold    It is also worth noting that a branch point X.Y.Z (alternatively, W.X.Y.Z)
45c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold    of some branch R denotes the build number X (repsectively, W) that
46c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold    constitutes the said branch. Therefore, it is only from build X+1 (W+1) and
47c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold    onward that releases will be tagged with R+1.
48c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
4984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    """
5084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    def __init__(self):
5184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._release_config = None
5284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._branchpoint_dict = None
5384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._next_branch = None
5484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._sorted_branchpoint_list = None
55c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        self._sorted_shifted_branchpoint_rel_key_list = None
5684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
5784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    def initialize(self):
5884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """Read release config and initialize lookup data structures."""
5984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._release_config = ConfigParser.ConfigParser()
6084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        try:
6184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            self._release_config.readfp(open(_RELEASE_CONFIG_FILE))
6284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
6384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            # Build branchpoint dictionary.
6484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            branchpoint_list_str = self._release_config.get(
6584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                    _CONF_BRANCH_SECTION, _CONF_BRANCH_POINTS_OPT)
6684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            if branchpoint_list_str:
6784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                branchpoint_list = map(str.strip,
6884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                                       branchpoint_list_str.split(','))
6984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            else:
7084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                branchpoint_list = []
7184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
7284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            self._branchpoint_dict = {}
7384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            for branchpoint in branchpoint_list:
7484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                self._branchpoint_dict[branchpoint] = (
7584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                          self._release_config.get(
7684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                                  _CONF_BRANCH_SECTION,
7784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                                  _CONF_BRANCH_POINT_OPT_PREFIX + branchpoint))
7884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
7984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            # Get next branch name.
8084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            self._next_branch = self._release_config.get(_CONF_BRANCH_SECTION,
8184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                                                         _CONF_NEXT_BRANCH_OPT)
8284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            if not self._next_branch:
8384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                raise ReleaseError("missing `%s' option" %
8484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                                   _CONF_NEXT_BRANCH_OPT)
8584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        except IOError, e:
8684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            raise ReleaseError('failed to open release config file (%s): %s' %
8703901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold                               (_RELEASE_CONFIG_FILE, e))
8884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        except ConfigParser.Error, e:
8903901089c0db27508a6b2ff61ae8b75eba77cf37Gilad Arnold            raise ReleaseError('failed to load release config: %s' % e)
9084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
9184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        # Infer chronologically sorted list of branchpoints.
9284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._sorted_branchpoint_list = self._branchpoint_dict.items()
9384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._sorted_branchpoint_list.append((self._next_branch, '99999.0.0'))
9484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        self._sorted_branchpoint_list.sort(
9584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                key=lambda (branch, release): self._release_key(release))
9684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
9784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        # Also store a sorted list of branchpoint release keys, for easy lookup.
98c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        self._sorted_shifted_branchpoint_rel_key_list = [
99c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold                self._release_key(self._next_build_number_release(release))
100c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold                for (branch, release) in self._sorted_branchpoint_list]
101c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
102c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
103c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold    def _next_build_number_release(self, release):
104c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        """Returns the release of the next build following a given release.
105c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
106c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        Given a release number 'X.Y.Z' (new scheme) or '0.X.Y.Z' (old scheme)
107c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        it will return 'X+1.0.0' or '0.X+1.0.0', respectively.
108c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
109c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        @param release: the release number in dotted notation (string)
110c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
111c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        @return The release number of the next build.
112c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
113c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        @raise ReleaseError if the release is malformed.
114c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
115c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        """
116c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        release_components = release.split('.')
117c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        if len(release_components) == 4 and release_components[0] == '0':
118c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold            prepend = '0.'
119c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold            x = int(release_components[1])
120c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        elif len(release_components) != 3:
121c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold            raise ReleaseError('invalid release number: %s' % release)
122c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        else:
123c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold            prepend = ''
124c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold            x = int(release_components[0])
125c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold
126c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        return '%s%s.0.0' % (prepend, x + 1)
12784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
12884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
12984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    def _release_key(self, release):
13084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """Convert a Chrome OS release string into an integer key.
13184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
13284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        This translates a release string 'X.Y.Z' (new scheme) or 'W.X.Y.Z' (old
13384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        scheme where W = 0) into an integer whose value equals X * 10^7 + Y *
13484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        10^3 + Z, assuming that Y < 10^4 and Z < 10^3, and will scale safely to
13584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        any foreseeable major release number (X).
13684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
13784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        @param release: the release number in dotted notation (string)
13884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
13984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        @return A unique integer key representing the release.
14084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
14184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        @raise ReleaseError if the release is malformed.
14284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
14384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """
14484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        release_components = release.split('.')
14584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        if len(release_components) == 4 and release_components[0] == '0':
14684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            release_components = release_components[1:]
14784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        elif len(release_components) != 3:
14884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            raise ReleaseError('invalid release number: %s' % release)
14984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        x, y, z = [int(s) for s in release_components]
15084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        return x * 10000000 + y * 1000 + z
15184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
15284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
15384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    def get_branch_list(self):
15484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """Retruns chronologically sorted list of branch names."""
15584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        return [branch for (branch, release) in self._sorted_branchpoint_list]
15684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
15784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
15884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    def get_branch(self, release):
15984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """Returns the branch name of a given release version. """
160c33f648ebe4c7b1f4f304dbfcbc1f0c80597becdGilad Arnold        i = bisect.bisect_left(self._sorted_shifted_branchpoint_rel_key_list,
16184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold                               self._release_key(release))
16284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        return self._sorted_branchpoint_list[i][0] if i else None
16384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
16484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
16584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold    def get_branchpoint_release(self, branch):
16684eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """Returns the branchpoint release of a given branch.
16784eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
16884eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        Returns None if given name is the next branch.
16984eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
17084eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        @raise KeyError if branch name not known
17184eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold
17284eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        """
17384eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        if branch == self._next_branch:
17484eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold            return None
17584eb60cb5d66d6052407ebae4f1aa38000c9cbcfGilad Arnold        return self._branchpoint_dict[branch]
176