Skip to content

Commit 8a41838

Browse files
authored
Switch to bumping the version file manually (#2581)
* Remove unmaintained and brittle version bump CI * Replace obsolete version parsing logic * Update version.py to only parse the Python-style version strings * Update tests * Add more edge cases, including mutual exclusivity between dev previews and release candidates * Run ./make.py format
1 parent 2501fcf commit 8a41838

File tree

4 files changed

+63
-113
lines changed

4 files changed

+63
-113
lines changed

.github/workflows/bump_version.yml

Lines changed: 0 additions & 24 deletions
This file was deleted.

.github/workflows/push_build_to_test_pypi.yml

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,7 @@ on:
55

66
jobs:
77
# --- Bump version
8-
bump-version:
9-
10-
runs-on: ubuntu-latest
11-
environment: deploy-pypi-test
12-
13-
steps:
14-
- name: Checkout code
15-
uses: actions/checkout@v4
16-
- name: Bump versions
17-
uses: remorses/bump-version@js
18-
with:
19-
version_file: ./arcade/VERSION
20-
prerelease_tag: dev
21-
env:
22-
GITHUB_TOKEN: ${{ secrets.MY_TOKEN }}
8+
# ( this is manual until we find a better-tested, maintained bump action )
239

2410
# --- Deploy to pypi
2511
deploy-to-pypi-test:

arcade/version.py

Lines changed: 50 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,43 +13,16 @@
1313
# Using file=sys.stderr prints to the error stream (usually prints red)
1414
print("This game requires Arcade 3.0.0+ to run!", file=sys.stderr)
1515
16+
The ``VERSION`` constant in this module will be loaded from a file in the
17+
same directory. It will contain the following:
1618
17-
Arcade contributors will benefit from understanding how and why
18-
this file loads and converts the contents of the ``VERSION`` file.
19+
#. major
20+
#. minor
21+
#. point
22+
#. (Optional) one and _only_ one of:
1923
20-
After a release build succeeds, GitHub's CI is configured to do
21-
the following:
22-
23-
#. Push the package files to PyPI
24-
#. Call the ``remorses/bump-version@js`` action to auto-increment
25-
Arcade's version on the development branch
26-
27-
This is where an edge case arises:
28-
29-
#. Our CI expects ``3.1.0-dev.1`` for dev preview builds
30-
#. Python expects ``3.1.0.dev1`` for dev preview builds
31-
32-
The ``VERSION`` file in this file's directory stores the version
33-
in the form the GH Action prefers. This allows it to auto-increment
34-
the version number on the ``development`` branch after we make an
35-
Arcade release to PyPI.
36-
37-
The auto-bump action is configured by the following file:
38-
https://github.com/pythonarcade/arcade/blob/development/.github/workflows/bump_version.yml
39-
40-
As an example, the GH action would auto-increment a dev preview's
41-
version after releasing the 5th dev preview of ``3.1.0`` by updating
42-
the ``VERSION`` file from this:
43-
44-
.. code-block::
45-
46-
3.1.0-dev.5
47-
48-
...to this:
49-
50-
.. code-block::
51-
52-
3.1.0-dev.6
24+
* .dev{DEV_PREVIEW_NUMBER}
25+
* rc{RC_NUMBER}
5326
5427
"""
5528

@@ -63,72 +36,83 @@
6336
_HERE = Path(__file__).parent
6437

6538
# Grab version numbers + optional dev point preview
66-
# Assumes $MAJOR.$MINOR.$POINT format with optional -dev$DEV_PREVIEW
39+
# Assumes:
40+
# {MAJOR}.{MINOR}.{POINT} format
41+
# optional: one and ONLY one of:
42+
# 1. dev{DEV_PREVIEW}
43+
# 2. rc{RC_NUMBER}
6744
# Q: Why did you use regex?!
6845
# A: If the dev_preview field is invalid, the whole match fails instantly
69-
_VERSION_REGEX = re.compile(
46+
_VERSION_REGEX: Final[re.Pattern] = re.compile(
7047
r"""
7148
# First three version number fields
7249
(?P<major>[0-9]+)
73-
\.(?P<minor>[0-9]+)
74-
\.(?P<point>[0-9]+)
75-
# Optional dev preview suffix
50+
\.(?P<minor>0|[1-9][0-9]*)
51+
\.(?P<point>0|[1-9][0-9]*)
52+
# Optional and mutually exclusive: dev preview or rc number
7653
(?:
77-
-dev # Dev prefix as a literal
78-
\. # Point
79-
(?P<dev_preview>[0-9]+) # Dev preview number
54+
\.(?P<dev_preview>dev(?:0|[1-9][0-9]*))
55+
| # XOR: can't be both a preview and an rc
56+
(?P<rc_number>rc(?:0|[1-9][0-9]*))
8057
)?
8158
""",
8259
re.X,
8360
)
8461

8562

86-
def _parse_python_friendly_version(version_for_github_actions: str) -> str:
87-
"""Convert a GitHub CI version string to a Python-friendly one.
63+
def _parse_python_friendly_version(
64+
raw_version: str, pattern: re.Pattern[str] = _VERSION_REGEX
65+
) -> str:
66+
"""Read a GitHub CI version string to a Python-friendly one.
8867
8968
For example, ``3.1.0-dev.1`` would become ``3.1.0.dev1``.
9069
9170
Args:
92-
version_for_github_actions:
71+
raw_version:
9372
A raw GitHub CI version string, as read from a file.
9473
Returns:
9574
A Python-friendly version string.
9675
"""
9776
# Quick preflight check: we don't support tuple format here!
98-
if not isinstance(version_for_github_actions, str):
99-
raise TypeError(
100-
f"Expected a string of the format MAJOR.MINOR.POINT"
101-
f"or MAJOR.MINOR.POINT-dev.DEV_PREVIEW,"
102-
f"not {version_for_github_actions!r}"
103-
)
104-
105-
# Attempt to extract our raw data
106-
match = _VERSION_REGEX.fullmatch(version_for_github_actions.strip())
107-
if match is None:
108-
raise ValueError(
109-
f"String does not appear to be a version number: {version_for_github_actions!r}"
77+
problem = None
78+
if not isinstance(raw_version, str):
79+
problem = TypeError
80+
elif (match := pattern.fullmatch(raw_version)) is None:
81+
problem = ValueError
82+
if problem:
83+
raise problem(
84+
f"{raw_version=!r} not a str of the format MAJOR.MINOR"
85+
f"POINT with at most one of dev{{DEV_PREVIEW}} or"
86+
f"rc{{RC_NUMBER}},"
11087
)
11188

11289
# Build final output, including a dev preview version if present
113-
group_dict = match.groupdict()
114-
major, minor, point, dev_preview = group_dict.values()
115-
parts = [major, minor, point]
116-
if dev_preview is not None:
117-
parts.append(f"dev{dev_preview}")
90+
group_dict: dict[str, str | None] = match.groupdict() # type: ignore
91+
parts: list[str] = [group_dict[k] for k in ("major", "minor", "point")] # type: ignore
92+
dev_preview, rc_number = (group_dict[k] for k in ("dev_preview", "rc_number"))
93+
94+
if dev_preview and rc_number:
95+
raise ValueError(f"Can't have both {dev_preview=!r} and {rc_number=!r}")
96+
elif dev_preview:
97+
parts.append(dev_preview)
98+
11899
joined = ".".join(parts)
100+
if rc_number:
101+
joined += rc_number
119102

120103
return joined
121104

122105

123-
def _parse_py_version_from_github_ci_file(
106+
def _parse_py_version_from_file(
124107
version_path: str | Path = _HERE / "VERSION", write_errors_to=sys.stderr
125108
) -> str:
126-
"""Parse a Python-friendly version from a ``bump-version``-compatible file.
109+
"""Read & validate the VERSION file as from a limited subset.
127110
128111
On failure, it will:
129112
130113
#. Print an error to stderr
131114
#. Return "0.0.0"
115+
#. (Indirectly) cause any PyPI uploads to fail
132116
133117
Args:
134118
version_path:
@@ -152,7 +136,7 @@ def _parse_py_version_from_github_ci_file(
152136
return data
153137

154138

155-
VERSION: Final[str] = _parse_py_version_from_github_ci_file()
139+
VERSION: Final[str] = _parse_py_version_from_file()
156140
"""A Python-friendly version string.
157141
158142
This value is converted from the GitHub-style ``VERSION`` file at the

tests/unit/test_version.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,33 @@
55
import pytest
66
from arcade.version import (
77
_parse_python_friendly_version,
8-
_parse_py_version_from_github_ci_file
8+
_parse_py_version_from_file
99
)
1010

1111

1212
@pytest.mark.parametrize("value, expected", [
13-
("3.0.0-dev.1", "3.0.0.dev1"),
13+
("3.0.0.dev1", "3.0.0.dev1"),
1414
("3.0.0", "3.0.0"),
1515
# Edge cases
16-
("11.22.333-dev.4444", "11.22.333.dev4444"),
16+
("11.22.333.dev4444", "11.22.333.dev4444"),
1717
("11.22.333", "11.22.333"),
18+
("111.2222.3333rc0", "111.2222.3333rc0")
1819
])
1920
class TestParsingWellFormedData:
2021
def test_parse_python_friendly_version(
2122
self, value, expected
2223
):
2324
assert _parse_python_friendly_version(value) == expected
2425

25-
def test_parse_py_version_from_github_ci_file(
26+
def test_parse_py_version_from_file(
2627
self, value, expected
2728
):
2829

2930
with tempfile.NamedTemporaryFile("w", delete=False) as f:
3031
f.write(value)
3132
f.close()
3233

33-
assert _parse_py_version_from_github_ci_file(
34+
assert _parse_py_version_from_file(
3435
f.name
3536
) == expected
3637

@@ -47,12 +48,15 @@ def test_parse_py_version_from_github_ci_file(
4748
"3.1.2.",
4849
"3.1.0.dev",
4950
"3.1.0-dev."
51+
"3.1.0-dev.4" # No longer valid input
5052
# Hex is not valid in version numbers
5153
"A",
5254
"3.A.",
5355
"3.1.A",
5456
"3.1.0.A",
55-
"3.1.0-dev.A"
57+
"3.1.0-dev.A",
58+
# Can't be both a release candidate and a dev preview
59+
"3.1.0.dev4rc1"
5660
)
5761
)
5862
def test_parse_python_friendly_version_raises_value_errors(bad_value):
@@ -72,8 +76,8 @@ def test_parse_python_friendly_version_raises_typeerror_on_bad_values(bad_type):
7276
_parse_python_friendly_version(bad_type) # type: ignore # Type mistmatch is the point
7377

7478

75-
def test_parse_py_version_from_github_ci_file_returns_zeroes_on_errors():
79+
def test_parse_py_version_from_file_returns_zeroes_on_errors():
7680
fake_stderr = mock.MagicMock(sys.stderr)
77-
assert _parse_py_version_from_github_ci_file(
81+
assert _parse_py_version_from_file(
7882
"FILEDOESNOTEXIST", write_errors_to=fake_stderr
7983
) == "0.0.0"

0 commit comments

Comments
 (0)