Skip to content

Commit c9bf062

Browse files
authored
Merge pull request #29247 from Rostepher/version-module
[Build System: build-script] Version module.
2 parents 4b76637 + 67d6b10 commit c9bf062

File tree

7 files changed

+325
-111
lines changed

7 files changed

+325
-111
lines changed

utils/build_swift/build_swift/argparse/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424

2525
from .actions import Action, Nargs
2626
from .parser import ArgumentParser
27-
from .types import (BoolType, ClangVersionType, CompilerVersion, PathType,
28-
RegexType, ShellSplitType, SwiftVersionType)
27+
from .types import (BoolType, ClangVersionType, PathType, RegexType,
28+
ShellSplitType, SwiftVersionType)
2929

3030

3131
__all__ = [
@@ -40,7 +40,6 @@
4040
'RawDescriptionHelpFormatter',
4141
'RawTextHelpFormatter',
4242

43-
'CompilerVersion',
4443
'BoolType',
4544
'FileType',
4645
'PathType',

utils/build_swift/build_swift/argparse/types.py

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,11 @@
1919
import re
2020
import shlex
2121

22-
import six
23-
2422
from . import ArgumentTypeError
23+
from ..versions import Version
2524

2625

2726
__all__ = [
28-
'CompilerVersion',
29-
3027
'BoolType',
3128
'PathType',
3229
'RegexType',
@@ -36,31 +33,6 @@
3633
]
3734

3835

39-
# -----------------------------------------------------------------------------
40-
41-
class CompilerVersion(object):
42-
"""Wrapper type around compiler version strings.
43-
"""
44-
45-
def __init__(self, *components):
46-
if len(components) == 1:
47-
if isinstance(components[0], six.string_types):
48-
components = components[0].split('.')
49-
elif isinstance(components[0], (list, tuple)):
50-
components = components[0]
51-
52-
if len(components) == 0:
53-
raise ValueError('compiler version cannot be empty')
54-
55-
self.components = tuple(int(part) for part in components)
56-
57-
def __eq__(self, other):
58-
return self.components == other.components
59-
60-
def __str__(self):
61-
return '.'.join([six.text_type(part) for part in self.components])
62-
63-
6436
# -----------------------------------------------------------------------------
6537

6638
def _repr(cls, args):
@@ -175,10 +147,8 @@ def __init__(self):
175147
ClangVersionType.ERROR_MESSAGE)
176148

177149
def __call__(self, value):
178-
matches = super(ClangVersionType, self).__call__(value)
179-
components = filter(lambda x: x is not None, matches.group(1, 2, 3, 5))
180-
181-
return CompilerVersion(components)
150+
super(ClangVersionType, self).__call__(value)
151+
return Version(value)
182152

183153

184154
class SwiftVersionType(RegexType):
@@ -195,10 +165,8 @@ def __init__(self):
195165
SwiftVersionType.ERROR_MESSAGE)
196166

197167
def __call__(self, value):
198-
matches = super(SwiftVersionType, self).__call__(value)
199-
components = filter(lambda x: x is not None, matches.group(1, 2, 4))
200-
201-
return CompilerVersion(components)
168+
super(SwiftVersionType, self).__call__(value)
169+
return Version(value)
202170

203171

204172
class ShellSplitType(object):

utils/build_swift/build_swift/defaults.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from __future__ import absolute_import, unicode_literals
1616

17-
from .argparse import CompilerVersion
17+
from .versions import Version
1818

1919

2020
__all__ = [
@@ -42,8 +42,8 @@
4242
CMAKE_GENERATOR = 'Ninja'
4343

4444
COMPILER_VENDOR = 'none'
45-
SWIFT_USER_VISIBLE_VERSION = CompilerVersion('5.2')
46-
CLANG_USER_VISIBLE_VERSION = CompilerVersion('7.0.0')
45+
SWIFT_USER_VISIBLE_VERSION = Version('5.2')
46+
CLANG_USER_VISIBLE_VERSION = Version('7.0.0')
4747
SWIFT_ANALYZE_CODE_COVERAGE = 'false'
4848

4949
DARWIN_XCRUN_TOOLCHAIN = 'default'
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# This source file is part of the Swift.org open source project
2+
#
3+
# Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
4+
# Licensed under Apache License v2.0 with Runtime Library Exception
5+
#
6+
# See https://swift.org/LICENSE.txt for license information
7+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
9+
10+
"""
11+
Version parsing classes.
12+
"""
13+
14+
15+
from __future__ import absolute_import, unicode_literals
16+
17+
import functools
18+
19+
import six
20+
21+
22+
__all__ = [
23+
'InvalidVersionError',
24+
'Version',
25+
]
26+
27+
28+
# -----------------------------------------------------------------------------
29+
# Version Parsing
30+
31+
class _ComponentType(object):
32+
"""Poor-man's enum representing all valid version character groups.
33+
"""
34+
35+
def __init__(self, name):
36+
self.name = name
37+
38+
def __eq__(self, other):
39+
if not isinstance(other, _ComponentType):
40+
return NotImplemented
41+
42+
return self.name == other.name
43+
44+
def __ne__(self, other):
45+
return not self.__eq__(other)
46+
47+
@classmethod
48+
def _register(cls, name):
49+
setattr(cls, name, cls(name))
50+
51+
52+
_ComponentType._register('ALPHA_LOWER')
53+
_ComponentType._register('ALPHA_UPPER')
54+
_ComponentType._register('DOT')
55+
_ComponentType._register('NUMERIC')
56+
_ComponentType._register('OTHER')
57+
58+
59+
def _get_component_type(component):
60+
"""Classifies a component into one of the registered component types.
61+
"""
62+
63+
if len(component) <= 0:
64+
raise ValueError('Empty component')
65+
66+
if component == '.':
67+
return _ComponentType.DOT
68+
69+
if component.isdigit():
70+
return _ComponentType.NUMERIC
71+
72+
if component.isalpha():
73+
if component.isupper():
74+
return _ComponentType.ALPHA_UPPER
75+
elif component.islower():
76+
return _ComponentType.ALPHA_LOWER
77+
else:
78+
raise ValueError('Unknown component type for {!r}'.format(
79+
component))
80+
81+
return _ComponentType.OTHER
82+
83+
84+
def _try_cast(obj, cls):
85+
"""Attempts to cast an object to a class, returning the resulting casted
86+
object or the original object if the cast raises a ValueError.
87+
"""
88+
89+
try:
90+
return cls(obj)
91+
except ValueError:
92+
return obj
93+
94+
95+
def _split_version(version):
96+
"""Splits a version string into a tuple of components using similar rules
97+
to distutils.version.LooseVersion. All version strings are valid, but the
98+
outcome will only split on boundries between:
99+
100+
* lowercase alpha characters
101+
* uppercase alpha characters
102+
* numeric characters
103+
* the literal '.' (dot) character
104+
105+
All other characters are grouped into an "other" category.
106+
107+
Numeric components are converted into integers in the resulting tuple.
108+
109+
An empty tuple is returned for the empty string.
110+
111+
```
112+
>>> _split_version('1000.2.108')
113+
(1000, 2, 28)
114+
115+
>>> _split_version('10A23b')
116+
(10, 'A', 23, 'b')
117+
118+
>>> _split_version('10.23-beta4')
119+
(10, 23, '-', 'beta', 4)
120+
121+
>>> _split_version('FOObarBAZqux')
122+
('FOO', 'bar', 'BAZ', 'qux')
123+
```
124+
"""
125+
126+
if len(version) < 1:
127+
return tuple()
128+
129+
components = []
130+
131+
part = version[0]
132+
part_type = _get_component_type(part)
133+
134+
for char in version[1:]:
135+
char_type = _get_component_type(char)
136+
137+
if part_type == char_type:
138+
part += char
139+
else:
140+
components.append(part)
141+
part = char
142+
part_type = char_type
143+
144+
# Add last part
145+
components.append(part)
146+
147+
# Remove '.' groups and try casting components to ints
148+
components = (_try_cast(c, int) for c in components if c != '.')
149+
150+
return tuple(components)
151+
152+
153+
# -----------------------------------------------------------------------------
154+
# Versions
155+
156+
class InvalidVersionError(Exception):
157+
"""Error indicating an invalid version was encountered.
158+
"""
159+
160+
def __init__(self, version, msg=None):
161+
self.version = version
162+
163+
if msg is None:
164+
msg = 'Invalid version: {}'.format(self.version)
165+
166+
super(InvalidVersionError, self).__init__(msg)
167+
168+
169+
@functools.total_ordering
170+
class Version(object):
171+
"""Similar to the standard distutils.versons.LooseVersion, but with a
172+
little more wiggle-room for alpha characters.
173+
"""
174+
175+
__slots__ = ('components', '_str')
176+
177+
def __init__(self, version):
178+
version = six.text_type(version)
179+
180+
# Save the version string since it's impossible to reconstruct it from
181+
# just the parsed components
182+
self._str = version
183+
184+
# Parse version components
185+
self.components = _split_version(version)
186+
187+
def __eq__(self, other):
188+
if not isinstance(other, Version):
189+
return NotImplemented
190+
191+
return self.components == other.components
192+
193+
# NOTE: Python 2 compatibility.
194+
def __ne__(self, other):
195+
return not self == other
196+
197+
def __lt__(self, other):
198+
if not isinstance(other, Version):
199+
return NotImplemented
200+
201+
return self.components < other.components
202+
203+
def __hash__(self):
204+
return hash(self.components)
205+
206+
def __str__(self):
207+
return self._str
208+
209+
def __repr__(self):
210+
return '{}({!r})'.format(type(self).__name__, self._str)

0 commit comments

Comments
 (0)