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