Home | History | Annotate | Download | only in autoupdate
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Modules for obtaining Chrome OS release info."""
      6 
      7 
      8 import ConfigParser
      9 import bisect
     10 import os
     11 
     12 
     13 _RELEASE_CONFIG_FILE = os.path.join(os.path.dirname(__file__),
     14                                     'release_config.ini')
     15 
     16 # Prefix for brachpoint definitions in the config file.
     17 _CONF_BRANCH_SECTION = 'BRANCH'
     18 _CONF_BRANCH_POINTS_OPT = 'branch_points'
     19 _CONF_BRANCH_POINT_OPT_PREFIX = 'bp_'
     20 _CONF_NEXT_BRANCH_OPT = 'next_branch'
     21 
     22 
     23 class ReleaseError(BaseException):
     24     """Errors related to release and branch inference."""
     25     pass
     26 
     27 
     28 class ReleaseInfo(object):
     29     """Provides reference information about Chrome OS releases.
     30 
     31     Currently, this class serves for mapping between releases and branches /
     32     release milestones. The information lives in a .ini file at the current
     33     directory, which has a single section [BRANCH] containing
     34 
     35       branch_points: comma-separated list of release branches (e.g. R10, R11,
     36       ...)
     37 
     38       bp_XYZ: for each branch listed above, a variable that maps to the Chrome
     39       OS release at that branchpoint (e.g. bp_r10: 0.10.156.0). Note that .ini
     40       file variables are case-insensitive.
     41 
     42       next_branch: the name of the current (unforked) branch (e.g. R24)
     43 
     44     It is also worth noting that a branch point X.Y.Z (alternatively, W.X.Y.Z)
     45     of some branch R denotes the build number X (repsectively, W) that
     46     constitutes the said branch. Therefore, it is only from build X+1 (W+1) and
     47     onward that releases will be tagged with R+1.
     48 
     49     """
     50     def __init__(self):
     51         self._release_config = None
     52         self._branchpoint_dict = None
     53         self._next_branch = None
     54         self._sorted_branchpoint_list = None
     55         self._sorted_shifted_branchpoint_rel_key_list = None
     56 
     57     def initialize(self):
     58         """Read release config and initialize lookup data structures."""
     59         self._release_config = ConfigParser.ConfigParser()
     60         try:
     61             self._release_config.readfp(open(_RELEASE_CONFIG_FILE))
     62 
     63             # Build branchpoint dictionary.
     64             branchpoint_list_str = self._release_config.get(
     65                     _CONF_BRANCH_SECTION, _CONF_BRANCH_POINTS_OPT)
     66             if branchpoint_list_str:
     67                 branchpoint_list = map(str.strip,
     68                                        branchpoint_list_str.split(','))
     69             else:
     70                 branchpoint_list = []
     71 
     72             self._branchpoint_dict = {}
     73             for branchpoint in branchpoint_list:
     74                 self._branchpoint_dict[branchpoint] = (
     75                           self._release_config.get(
     76                                   _CONF_BRANCH_SECTION,
     77                                   _CONF_BRANCH_POINT_OPT_PREFIX + branchpoint))
     78 
     79             # Get next branch name.
     80             self._next_branch = self._release_config.get(_CONF_BRANCH_SECTION,
     81                                                          _CONF_NEXT_BRANCH_OPT)
     82             if not self._next_branch:
     83                 raise ReleaseError("missing `%s' option" %
     84                                    _CONF_NEXT_BRANCH_OPT)
     85         except IOError, e:
     86             raise ReleaseError('failed to open release config file (%s): %s' %
     87                                (_RELEASE_CONFIG_FILE, e))
     88         except ConfigParser.Error, e:
     89             raise ReleaseError('failed to load release config: %s' % e)
     90 
     91         # Infer chronologically sorted list of branchpoints.
     92         self._sorted_branchpoint_list = self._branchpoint_dict.items()
     93         self._sorted_branchpoint_list.append((self._next_branch, '99999.0.0'))
     94         self._sorted_branchpoint_list.sort(
     95                 key=lambda (branch, release): self._release_key(release))
     96 
     97         # Also store a sorted list of branchpoint release keys, for easy lookup.
     98         self._sorted_shifted_branchpoint_rel_key_list = [
     99                 self._release_key(self._next_build_number_release(release))
    100                 for (branch, release) in self._sorted_branchpoint_list]
    101 
    102 
    103     def _next_build_number_release(self, release):
    104         """Returns the release of the next build following a given release.
    105 
    106         Given a release number 'X.Y.Z' (new scheme) or '0.X.Y.Z' (old scheme)
    107         it will return 'X+1.0.0' or '0.X+1.0.0', respectively.
    108 
    109         @param release: the release number in dotted notation (string)
    110 
    111         @return The release number of the next build.
    112 
    113         @raise ReleaseError if the release is malformed.
    114 
    115         """
    116         release_components = release.split('.')
    117         if len(release_components) == 4 and release_components[0] == '0':
    118             prepend = '0.'
    119             x = int(release_components[1])
    120         elif len(release_components) != 3:
    121             raise ReleaseError('invalid release number: %s' % release)
    122         else:
    123             prepend = ''
    124             x = int(release_components[0])
    125 
    126         return '%s%s.0.0' % (prepend, x + 1)
    127 
    128 
    129     def _release_key(self, release):
    130         """Convert a Chrome OS release string into an integer key.
    131 
    132         This translates a release string 'X.Y.Z' (new scheme) or 'W.X.Y.Z' (old
    133         scheme where W = 0) into an integer whose value equals X * 10^7 + Y *
    134         10^3 + Z, assuming that Y < 10^4 and Z < 10^3, and will scale safely to
    135         any foreseeable major release number (X).
    136 
    137         @param release: the release number in dotted notation (string)
    138 
    139         @return A unique integer key representing the release.
    140 
    141         @raise ReleaseError if the release is malformed.
    142 
    143         """
    144         release_components = release.split('.')
    145         if len(release_components) == 4 and release_components[0] == '0':
    146             release_components = release_components[1:]
    147         elif len(release_components) != 3:
    148             raise ReleaseError('invalid release number: %s' % release)
    149         x, y, z = [int(s) for s in release_components]
    150         return x * 10000000 + y * 1000 + z
    151 
    152 
    153     def get_branch_list(self):
    154         """Retruns chronologically sorted list of branch names."""
    155         return [branch for (branch, release) in self._sorted_branchpoint_list]
    156 
    157 
    158     def get_branch(self, release):
    159         """Returns the branch name of a given release version. """
    160         i = bisect.bisect_left(self._sorted_shifted_branchpoint_rel_key_list,
    161                                self._release_key(release))
    162         return self._sorted_branchpoint_list[i][0] if i else None
    163 
    164 
    165     def get_branchpoint_release(self, branch):
    166         """Returns the branchpoint release of a given branch.
    167 
    168         Returns None if given name is the next branch.
    169 
    170         @raise KeyError if branch name not known
    171 
    172         """
    173         if branch == self._next_branch:
    174             return None
    175         return self._branchpoint_dict[branch]
    176