blob: ca003d8b9fd52e27a958e7dc464cd5b18a6a05ba [file] [log] [blame]
Gilad Arnold84eb60c2012-11-05 23:46:10 -08001# 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
8import ConfigParser
9import bisect
10import os
11
12
Chris Sosa47c8a9a2013-01-02 14:48:03 -080013_RELEASE_CONFIG_FILE = os.path.join(os.path.dirname(__file__),
14 'release_config.ini')
Gilad Arnold84eb60c2012-11-05 23:46:10 -080015
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
23class ReleaseError(BaseException):
24 """Errors related to release and branch inference."""
25 pass
26
27
28class 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
Gilad Arnoldc33f6482012-12-20 13:12:22 -080044 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
Gilad Arnold84eb60c2012-11-05 23:46:10 -080049 """
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
Gilad Arnoldc33f6482012-12-20 13:12:22 -080055 self._sorted_shifted_branchpoint_rel_key_list = None
Gilad Arnold84eb60c2012-11-05 23:46:10 -080056
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' %
Gilad Arnold03901082012-11-19 07:29:02 -080087 (_RELEASE_CONFIG_FILE, e))
Gilad Arnold84eb60c2012-11-05 23:46:10 -080088 except ConfigParser.Error, e:
Gilad Arnold03901082012-11-19 07:29:02 -080089 raise ReleaseError('failed to load release config: %s' % e)
Gilad Arnold84eb60c2012-11-05 23:46:10 -080090
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.
Gilad Arnoldc33f6482012-12-20 13:12:22 -080098 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)
Gilad Arnold84eb60c2012-11-05 23:46:10 -0800127
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. """
Gilad Arnoldc33f6482012-12-20 13:12:22 -0800160 i = bisect.bisect_left(self._sorted_shifted_branchpoint_rel_key_list,
Gilad Arnold84eb60c2012-11-05 23:46:10 -0800161 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]