2 # Copyright 2012 OpenStack Foundation
3 # Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
5 # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 # not use this file except in compliance with the License. You may obtain
7 # a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 # License for the specific language governing permissions and limitations
18 Utilities for consuming the version from pkg_resources.
34 class SemanticVersion(object):
35 """A pure semantic version independent of serialisation.
37 See the pbr doc 'semver' for details on the semantics.
41 self, major, minor=0, patch=0, prerelease_type=None,
42 prerelease=None, dev_count=None):
43 """Create a SemanticVersion.
45 :param major: Major component of the version.
46 :param minor: Minor component of the version. Defaults to 0.
47 :param patch: Patch level component. Defaults to 0.
48 :param prerelease_type: What sort of prerelease version this is -
49 one of a(alpha), b(beta) or rc(release candidate).
50 :param prerelease: For prerelease versions, what number prerelease.
52 :param dev_count: How many commits since the last release.
57 self._prerelease_type = prerelease_type
58 self._prerelease = prerelease
59 if self._prerelease_type and not self._prerelease:
61 self._dev_count = dev_count or 0 # Normalise 0 to None.
63 def __eq__(self, other):
64 if not isinstance(other, SemanticVersion):
66 return self.__dict__ == other.__dict__
69 return sum(map(hash, self.__dict__.values()))
72 """Return a key for sorting SemanticVersion's on."""
74 # - final is after rc's, so we make that a/b/rc/z
75 # - dev==None is after all other devs, so we use sys.maxsize there.
76 # - unqualified dev releases come before any pre-releases.
78 # (major, minor, patch) - gets the major grouping.
79 # (0|1) unqualified dev flag
80 # (a/b/rc/z) - release segment grouping
82 # dev count, maxsize for releases.
83 rc_lookup = {'a': 'a', 'b': 'b', 'rc': 'rc', None: 'z'}
84 if self._dev_count and not self._prerelease_type:
89 self._major, self._minor, self._patch,
91 rc_lookup[self._prerelease_type], self._prerelease,
92 self._dev_count or sys.maxsize)
94 def __lt__(self, other):
95 """Compare self and other, another Semantic Version."""
96 # NB(lifeless) this could perhaps be rewritten as
97 # lt (tuple_of_one, tuple_of_other) with a single check for
98 # the typeerror corner cases - that would likely be faster
99 # if this ever becomes performance sensitive.
100 if not isinstance(other, SemanticVersion):
101 raise TypeError("ordering to non-SemanticVersion is undefined")
102 return self._sort_key() < other._sort_key()
104 def __le__(self, other):
105 return self == other or self < other
107 def __ge__(self, other):
108 return not self < other
110 def __gt__(self, other):
111 return not self <= other
113 def __ne__(self, other):
114 return not self == other
117 return "pbr.version.SemanticVersion(%s)" % self.release_string()
120 def from_pip_string(klass, version_string):
121 """Create a SemanticVersion from a pip version string.
123 This method will parse a version like 1.3.0 into a SemanticVersion.
125 This method is responsible for accepting any version string that any
126 older version of pbr ever created.
128 Therefore: versions like 1.3.0a1 versions are handled, parsed into a
129 canonical form and then output - resulting in 1.3.0.0a1.
130 Pre pbr-semver dev versions like 0.10.1.3.g83bef74 will be parsed but
131 output as 0.10.1.dev3.g83bef74.
133 :raises ValueError: Never tagged versions sdisted by old pbr result in
134 just the git hash, e.g. '1234567' which poses a substantial problem
135 since they collide with the semver versions when all the digits are
136 numerals. Such versions will result in a ValueError being thrown if
137 any non-numeric digits are present. They are an exception to the
138 general case of accepting anything we ever output, since they were
139 never intended and would permanently mess up versions on PyPI if
140 ever released - we're treating that as a critical bug that we ever
141 made them and have stopped doing that.
145 return klass._from_pip_string_unsafe(version_string)
147 raise ValueError("Invalid version %r" % version_string)
150 def _from_pip_string_unsafe(klass, version_string):
151 # Versions need to start numerically, ignore if not
152 version_string = version_string.lstrip('vV')
153 if not version_string[:1].isdigit():
154 raise ValueError("Invalid version %r" % version_string)
155 input_components = version_string.split('.')
156 # decimals first (keep pre-release and dev/hashes to the right)
157 components = [c for c in input_components if c.isdigit()]
158 digit_len = len(components)
160 raise ValueError("Invalid version %r" % version_string)
162 if (digit_len < len(input_components) and
163 input_components[digit_len][0].isdigit()):
164 # Handle X.YaZ - Y is a digit not a leadin to pre-release.
165 mixed_component = input_components[digit_len]
166 last_component = ''.join(itertools.takewhile(
167 lambda x: x.isdigit(), mixed_component))
168 components.append(last_component)
169 input_components[digit_len:digit_len + 1] = [
170 last_component, mixed_component[len(last_component):]]
172 components.extend([0] * (3 - digit_len))
173 components.extend(input_components[digit_len:])
174 major = int(components[0])
175 minor = int(components[1])
178 prerelease_type = None
181 def _parse_type(segment):
182 # Discard leading digits (the 0 in 0a1)
183 isdigit = operator.methodcaller('isdigit')
184 segment = ''.join(itertools.dropwhile(isdigit, segment))
185 isalpha = operator.methodcaller('isalpha')
186 prerelease_type = ''.join(itertools.takewhile(isalpha, segment))
187 prerelease = segment[len(prerelease_type)::]
188 return prerelease_type, int(prerelease)
189 if _is_int(components[2]):
190 patch = int(components[2])
192 # legacy version e.g. 1.2.0a1 (canonical is 1.2.0.0a1)
193 # or 1.2.dev4.g1234 or 1.2.b4
195 components[2:2] = [0]
196 remainder = components[3:]
197 remainder_starts_with_int = False
199 if remainder and int(remainder[0]):
200 remainder_starts_with_int = True
203 if remainder_starts_with_int:
204 # old dev format - 0.1.2.3.g1234
205 dev_count = int(remainder[0])
207 if remainder and (remainder[0][0] == '0' or
208 remainder[0][0] in ('a', 'b', 'r')):
209 # Current RC/beta layout
210 prerelease_type, prerelease = _parse_type(remainder[0])
211 remainder = remainder[1:]
213 component = remainder[0]
214 if component.startswith('dev'):
215 dev_count = int(component[3:])
216 elif component.startswith('post'):
218 post_count = int(component[4:])
221 'Unknown remainder %r in %r'
222 % (remainder, version_string))
223 remainder = remainder[1:]
224 result = SemanticVersion(
225 major, minor, patch, prerelease_type=prerelease_type,
226 prerelease=prerelease, dev_count=dev_count)
230 'Cannot combine postN and devN - no mapping in %r'
232 result = result.increment().to_dev(post_count)
235 def brief_string(self):
236 """Return the short version minus any alpha/beta tags."""
237 return "%s.%s.%s" % (self._major, self._minor, self._patch)
239 def debian_string(self):
240 """Return the version number to use when building a debian package.
242 This translates the PEP440/semver precedence rules into Debian version
245 return self._long_version("~")
248 """Return a decremented SemanticVersion.
250 Decrementing versions doesn't make a lot of sense - this method only
251 exists to support rendering of pre-release versions strings into
252 serialisations (such as rpm) with no sort-before operator.
254 The 9999 magic version component is from the spec on this - pbr-semver.
256 :return: A new SemanticVersion object.
259 new_patch = self._patch - 1
260 new_minor = self._minor
261 new_major = self._major
265 new_minor = self._minor - 1
266 new_major = self._major
270 new_major = self._major - 1
273 return SemanticVersion(
274 new_major, new_minor, new_patch)
276 def increment(self, minor=False, major=False):
277 """Return an incremented SemanticVersion.
279 The default behaviour is to perform a patch level increment. When
280 incrementing a prerelease version, the patch level is not changed
281 - the prerelease serial is changed (e.g. beta 0 -> beta 1).
283 Incrementing non-pre-release versions will not introduce pre-release
284 versions - except when doing a patch incremental to a pre-release
285 version the new version will only consist of major/minor/patch.
287 :param minor: Increment the minor version.
288 :param major: Increment the major version.
289 :return: A new SemanticVersion object.
291 if self._prerelease_type:
292 new_prerelease_type = self._prerelease_type
293 new_prerelease = self._prerelease + 1
294 new_patch = self._patch
296 new_prerelease_type = None
297 new_prerelease = None
298 new_patch = self._patch + 1
300 new_minor = self._minor + 1
302 new_prerelease_type = None
303 new_prerelease = None
305 new_minor = self._minor
307 new_major = self._major + 1
310 new_prerelease_type = None
311 new_prerelease = None
313 new_major = self._major
314 return SemanticVersion(
315 new_major, new_minor, new_patch,
316 new_prerelease_type, new_prerelease)
318 def _long_version(self, pre_separator, rc_marker=""):
319 """Construct a long string version of this semver.
321 :param pre_separator: What separator to use between components
322 that sort before rather than after. If None, use . and lower the
323 version number of the component to preserve sorting. (Used for
326 if ((self._prerelease_type or self._dev_count)
327 and pre_separator is None):
328 segments = [self.decrement().brief_string()]
331 segments = [self.brief_string()]
332 if self._prerelease_type:
334 "%s%s%s%s" % (pre_separator, rc_marker, self._prerelease_type,
337 if not self._prerelease_type:
338 segments.append(pre_separator)
341 segments.append('dev')
342 segments.append(self._dev_count)
343 return "".join(str(s) for s in segments)
345 def release_string(self):
346 """Return the full version of the package.
348 This including suffixes indicating VCS status.
350 return self._long_version(".", "0")
352 def rpm_string(self):
353 """Return the version number to use when building an RPM package.
355 This translates the PEP440/semver precedence rules into RPM version
356 sorting operators. Because RPM has no sort-before operator (such as the
357 ~ operator in dpkg), we show all prerelease versions as being versions
358 of the release before.
360 return self._long_version(None)
362 def to_dev(self, dev_count):
363 """Return a development version of this semver.
365 :param dev_count: The number of commits since the last release.
367 return SemanticVersion(
368 self._major, self._minor, self._patch, self._prerelease_type,
369 self._prerelease, dev_count=dev_count)
371 def version_tuple(self):
372 """Present the version as a version_info tuple.
374 For documentation on version_info tuples see the Python
375 documentation for sys.version_info.
377 Since semver and PEP-440 represent overlapping but not subsets of
378 versions, we have to have some heuristic / mapping rules, and have
379 extended the releaselevel field to have alphadev, betadev and
380 candidatedev values. When they are present the dev count is used
381 to provide the serial.
382 - a/b/rc take precedence.
383 - if there is no pre-release version the dev version is used.
384 - serial is taken from the dev/a/b/c component.
385 - final non-dev versions never get serials.
387 segments = [self._major, self._minor, self._patch]
388 if self._prerelease_type:
389 type_map = {('a', False): 'alpha',
390 ('b', False): 'beta',
391 ('rc', False): 'candidate',
392 ('a', True): 'alphadev',
393 ('b', True): 'betadev',
394 ('rc', True): 'candidatedev',
397 type_map[(self._prerelease_type, bool(self._dev_count))])
398 segments.append(self._dev_count or self._prerelease)
399 elif self._dev_count:
400 segments.append('dev')
401 segments.append(self._dev_count - 1)
403 segments.append('final')
405 return tuple(segments)
408 class VersionInfo(object):
410 def __init__(self, package):
411 """Object that understands versioning for a package
413 :param package: name of the python package, such as glance, or
416 self.package = package
418 self._cached_version = None
419 self._semantic = None
422 """Make the VersionInfo object behave like a string."""
423 return self.version_string()
426 """Include the name."""
427 return "pbr.version.VersionInfo(%s:%s)" % (
428 self.package, self.version_string())
430 def _get_version_from_pkg_resources(self):
431 """Obtain a version from pkg_resources or setup-time logic if missing.
433 This will try to get the version of the package from the pkg_resources
434 record associated with the package, and if there is no such record
435 falls back to the logic sdist would use.
437 # Lazy import because pkg_resources is costly to import so defer until
438 # we absolutely need it.
441 requirement = pkg_resources.Requirement.parse(self.package)
442 provider = pkg_resources.get_provider(requirement)
443 result_string = provider.version
444 except pkg_resources.DistributionNotFound:
445 # The most likely cause for this is running tests in a tree
446 # produced from a tarball where the package itself has not been
447 # installed into anything. Revert to setup-time logic.
448 from pbr import packaging
449 result_string = packaging.get_version(self.package)
450 return SemanticVersion.from_pip_string(result_string)
452 def release_string(self):
453 """Return the full version of the package.
455 This including suffixes indicating VCS status.
457 return self.semantic_version().release_string()
459 def semantic_version(self):
460 """Return the SemanticVersion object for this version."""
461 if self._semantic is None:
462 self._semantic = self._get_version_from_pkg_resources()
463 return self._semantic
465 def version_string(self):
466 """Return the short version minus any alpha/beta tags."""
467 return self.semantic_version().brief_string()
469 # Compatibility functions
470 canonical_version_string = version_string
471 version_string_with_vcs = release_string
473 def cached_version_string(self, prefix=""):
474 """Return a cached version string.
476 This will return a cached version string if one is already cached,
477 irrespective of prefix. If none is cached, one will be created with
478 prefix and then cached and returned.
480 if not self._cached_version:
481 self._cached_version = "%s%s" % (prefix,
482 self.version_string())
483 return self._cached_version