|
1 | 1 | """
|
2 |
| -Version |
| 2 | +Loads the Arcade version into a Python-readable ``VERSION`` string. |
3 | 3 |
|
4 |
| -We are using a github action to bump the VERSION file versions. |
| 4 | +Everyday Arcade users may prefer accessing the ``VERSION`` string |
| 5 | +from Arcade's top-level alias: |
5 | 6 |
|
6 |
| -2.7.3-dev.5 |
7 |
| -will go to: |
8 |
| -2.7.3-dev.6 |
| 7 | +.. code-block:: python |
9 | 8 |
|
10 |
| -Problem is, python doesn't like that last period: |
11 |
| -2.7.3-dev.5 |
12 |
| -should be |
13 |
| -2.7.3.dev5 |
14 |
| -...and our github action doesn't like that pattern. |
15 |
| -So this will delete that last period and flip around the dash. |
| 9 | + import sys |
| 10 | + import arcade |
| 11 | +
|
| 12 | + if arcade.version < "3.0.0": |
| 13 | + # Using file=sys.stderr prints to the error stream (usually prints red) |
| 14 | + print("This game requires Arcade 3.0.0+ to run!", file=sys.stderr) |
| 15 | +
|
| 16 | +
|
| 17 | +Arcade contributors will benefit from understanding how and why |
| 18 | +this file loads and converts the contents of the ``VERSION`` file. |
| 19 | +
|
| 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 |
16 | 53 |
|
17 |
| -ALSO note that this bumps the version AFTER the deploy. |
18 |
| -So if we are at version 2.7.3.dev5 that's the version deploy. Bump will bump it to dev6. |
19 | 54 | """
|
20 | 55 |
|
21 | 56 | from __future__ import annotations
|
22 | 57 |
|
23 |
| -import os |
| 58 | +import re |
| 59 | +import sys |
| 60 | +from pathlib import Path |
| 61 | +from typing import Final |
| 62 | + |
| 63 | +_HERE = Path(__file__).parent |
| 64 | + |
| 65 | +# Grab version numbers + optional dev point preview |
| 66 | +# Assumes $MAJOR.$MINOR.$POINT format with optional -dev$DEV_PREVIEW |
| 67 | +# Q: Why did you use regex?! |
| 68 | +# A: If the dev_preview field is invalid, the whole match fails instantly |
| 69 | +_VERSION_REGEX = re.compile( |
| 70 | + r""" |
| 71 | + # First three version number fields |
| 72 | + (?P<major>[0-9]+) |
| 73 | + \.(?P<minor>[0-9]+) |
| 74 | + \.(?P<point>[0-9]+) |
| 75 | + # Optional dev preview suffix |
| 76 | + (?: |
| 77 | + -dev # Dev prefix as a literal |
| 78 | + \. # Point |
| 79 | + (?P<dev_preview>[0-9]+) # Dev preview number |
| 80 | + )? |
| 81 | + """, |
| 82 | + re.X, |
| 83 | +) |
| 84 | + |
| 85 | + |
| 86 | +def _parse_python_friendly_version(version_for_github_actions: str) -> str: |
| 87 | + """Convert a GitHub CI version string to a Python-friendly one. |
| 88 | +
|
| 89 | + For example, ``3.1.0-dev.1`` would become ``3.1.0.dev1``. |
24 | 90 |
|
| 91 | + Args: |
| 92 | + version_for_github_actions: |
| 93 | + A raw GitHub CI version string, as read from a file. |
| 94 | + Returns: |
| 95 | + A Python-friendly version string. |
| 96 | + """ |
| 97 | + # 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 | + ) |
25 | 104 |
|
26 |
| -def _rreplace(s, old, new, occurrence): |
27 |
| - li = s.rsplit(old, occurrence) |
28 |
| - return new.join(li) |
| 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}" |
| 110 | + ) |
29 | 111 |
|
| 112 | + # 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}") |
| 118 | + joined = ".".join(parts) |
30 | 119 |
|
31 |
| -def _get_version(): |
32 |
| - dirname = os.path.dirname(__file__) or "." |
33 |
| - my_path = f"{dirname}/VERSION" |
| 120 | + return joined |
34 | 121 |
|
| 122 | + |
| 123 | +def _parse_py_version_from_github_ci_file( |
| 124 | + version_path: str | Path = _HERE / "VERSION", write_errors_to=sys.stderr |
| 125 | +) -> str: |
| 126 | + """Parse a Python-friendly version from a ``bump-version``-compatible file. |
| 127 | +
|
| 128 | + On failure, it will: |
| 129 | +
|
| 130 | + #. Print an error to stderr |
| 131 | + #. Return "0.0.0" |
| 132 | +
|
| 133 | + Args: |
| 134 | + version_path: |
| 135 | + The VERSION file's path, defaulting to the same directory as |
| 136 | + this file. |
| 137 | + write_errors_to: |
| 138 | + Makes CI simpler by allowing a stream mock to be passed easily. |
| 139 | + Returns: |
| 140 | + Either a converted version or "0.0.0" on failure. |
| 141 | + """ |
| 142 | + data = "0.0.0" |
35 | 143 | try:
|
36 |
| - text_file = open(my_path, "r") |
37 |
| - data = text_file.read().strip() |
38 |
| - text_file.close() |
39 |
| - data = _rreplace(data, ".", "", 1) |
40 |
| - data = _rreplace(data, "-", ".", 1) |
41 |
| - except Exception: |
42 |
| - print(f"ERROR: Unable to load version number via '{my_path}'.") |
43 |
| - data = "0.0.0" |
| 144 | + raw = Path(version_path).resolve().read_text().strip() |
| 145 | + data = _parse_python_friendly_version(raw) |
| 146 | + except Exception as e: |
| 147 | + print( |
| 148 | + f"ERROR: Unable to load version number via '{str(version_path)}': {e}", |
| 149 | + file=write_errors_to, |
| 150 | + ) |
44 | 151 |
|
45 | 152 | return data
|
46 | 153 |
|
47 | 154 |
|
48 |
| -VERSION = _get_version() |
| 155 | +VERSION: Final[str] = _parse_py_version_from_github_ci_file() |
| 156 | +"""A Python-friendly version string. |
| 157 | +
|
| 158 | +This value is converted from the GitHub-style ``VERSION`` file at the |
| 159 | +top-level of the arcade module. |
| 160 | +""" |
0 commit comments