| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Modules for obtaining Chrome OS release info.""" |
| |
| |
| import ConfigParser |
| import bisect |
| import os |
| |
| |
| _RELEASE_CONFIG_FILE = os.path.join(os.path.dirname(__file__), |
| 'release_config.ini') |
| |
| # Prefix for brachpoint definitions in the config file. |
| _CONF_BRANCH_SECTION = 'BRANCH' |
| _CONF_BRANCH_POINTS_OPT = 'branch_points' |
| _CONF_BRANCH_POINT_OPT_PREFIX = 'bp_' |
| _CONF_NEXT_BRANCH_OPT = 'next_branch' |
| |
| |
| class ReleaseError(BaseException): |
| """Errors related to release and branch inference.""" |
| pass |
| |
| |
| class ReleaseInfo(object): |
| """Provides reference information about Chrome OS releases. |
| |
| Currently, this class serves for mapping between releases and branches / |
| release milestones. The information lives in a .ini file at the current |
| directory, which has a single section [BRANCH] containing |
| |
| branch_points: comma-separated list of release branches (e.g. R10, R11, |
| ...) |
| |
| bp_XYZ: for each branch listed above, a variable that maps to the Chrome |
| OS release at that branchpoint (e.g. bp_r10: 0.10.156.0). Note that .ini |
| file variables are case-insensitive. |
| |
| next_branch: the name of the current (unforked) branch (e.g. R24) |
| |
| It is also worth noting that a branch point X.Y.Z (alternatively, W.X.Y.Z) |
| of some branch R denotes the build number X (repsectively, W) that |
| constitutes the said branch. Therefore, it is only from build X+1 (W+1) and |
| onward that releases will be tagged with R+1. |
| |
| """ |
| def __init__(self): |
| self._release_config = None |
| self._branchpoint_dict = None |
| self._next_branch = None |
| self._sorted_branchpoint_list = None |
| self._sorted_shifted_branchpoint_rel_key_list = None |
| |
| def initialize(self): |
| """Read release config and initialize lookup data structures.""" |
| self._release_config = ConfigParser.ConfigParser() |
| try: |
| self._release_config.readfp(open(_RELEASE_CONFIG_FILE)) |
| |
| # Build branchpoint dictionary. |
| branchpoint_list_str = self._release_config.get( |
| _CONF_BRANCH_SECTION, _CONF_BRANCH_POINTS_OPT) |
| if branchpoint_list_str: |
| branchpoint_list = map(str.strip, |
| branchpoint_list_str.split(',')) |
| else: |
| branchpoint_list = [] |
| |
| self._branchpoint_dict = {} |
| for branchpoint in branchpoint_list: |
| self._branchpoint_dict[branchpoint] = ( |
| self._release_config.get( |
| _CONF_BRANCH_SECTION, |
| _CONF_BRANCH_POINT_OPT_PREFIX + branchpoint)) |
| |
| # Get next branch name. |
| self._next_branch = self._release_config.get(_CONF_BRANCH_SECTION, |
| _CONF_NEXT_BRANCH_OPT) |
| if not self._next_branch: |
| raise ReleaseError("missing `%s' option" % |
| _CONF_NEXT_BRANCH_OPT) |
| except IOError, e: |
| raise ReleaseError('failed to open release config file (%s): %s' % |
| (_RELEASE_CONFIG_FILE, e)) |
| except ConfigParser.Error, e: |
| raise ReleaseError('failed to load release config: %s' % e) |
| |
| # Infer chronologically sorted list of branchpoints. |
| self._sorted_branchpoint_list = self._branchpoint_dict.items() |
| self._sorted_branchpoint_list.append((self._next_branch, '99999.0.0')) |
| self._sorted_branchpoint_list.sort( |
| key=lambda (branch, release): self._release_key(release)) |
| |
| # Also store a sorted list of branchpoint release keys, for easy lookup. |
| self._sorted_shifted_branchpoint_rel_key_list = [ |
| self._release_key(self._next_build_number_release(release)) |
| for (branch, release) in self._sorted_branchpoint_list] |
| |
| |
| def _next_build_number_release(self, release): |
| """Returns the release of the next build following a given release. |
| |
| Given a release number 'X.Y.Z' (new scheme) or '0.X.Y.Z' (old scheme) |
| it will return 'X+1.0.0' or '0.X+1.0.0', respectively. |
| |
| @param release: the release number in dotted notation (string) |
| |
| @return The release number of the next build. |
| |
| @raise ReleaseError if the release is malformed. |
| |
| """ |
| release_components = release.split('.') |
| if len(release_components) == 4 and release_components[0] == '0': |
| prepend = '0.' |
| x = int(release_components[1]) |
| elif len(release_components) != 3: |
| raise ReleaseError('invalid release number: %s' % release) |
| else: |
| prepend = '' |
| x = int(release_components[0]) |
| |
| return '%s%s.0.0' % (prepend, x + 1) |
| |
| |
| def _release_key(self, release): |
| """Convert a Chrome OS release string into an integer key. |
| |
| This translates a release string 'X.Y.Z' (new scheme) or 'W.X.Y.Z' (old |
| scheme where W = 0) into an integer whose value equals X * 10^7 + Y * |
| 10^3 + Z, assuming that Y < 10^4 and Z < 10^3, and will scale safely to |
| any foreseeable major release number (X). |
| |
| @param release: the release number in dotted notation (string) |
| |
| @return A unique integer key representing the release. |
| |
| @raise ReleaseError if the release is malformed. |
| |
| """ |
| release_components = release.split('.') |
| if len(release_components) == 4 and release_components[0] == '0': |
| release_components = release_components[1:] |
| elif len(release_components) != 3: |
| raise ReleaseError('invalid release number: %s' % release) |
| x, y, z = [int(s) for s in release_components] |
| return x * 10000000 + y * 1000 + z |
| |
| |
| def get_branch_list(self): |
| """Retruns chronologically sorted list of branch names.""" |
| return [branch for (branch, release) in self._sorted_branchpoint_list] |
| |
| |
| def get_branch(self, release): |
| """Returns the branch name of a given release version. """ |
| i = bisect.bisect_left(self._sorted_shifted_branchpoint_rel_key_list, |
| self._release_key(release)) |
| return self._sorted_branchpoint_list[i][0] if i else None |
| |
| |
| def get_branchpoint_release(self, branch): |
| """Returns the branchpoint release of a given branch. |
| |
| Returns None if given name is the next branch. |
| |
| @raise KeyError if branch name not known |
| |
| """ |
| if branch == self._next_branch: |
| return None |
| return self._branchpoint_dict[branch] |