import argparse
from collections import defaultdict
import requests
import pathlib
import yaml
import re
_VERSIONS_URL = "https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json"
_OSX_PYTHON_EXCLUSIONS = []
def parse_version(v):
return tuple(int(part) for part in re.split(r"\W", v)[:3])
def get_github_python_versions():
versions_json = requests.get(_VERSIONS_URL).json()
platforms = {}
for v in versions_json:
version_platforms = set()
for f in v["files"]:
platform, arch = f["platform"], f["arch"]
if platform == "linux" and f.get("platform_version") != "22.04":
continue
version_platforms.add((platform, arch))
platforms[v["version"]] = version_platforms
raw_versions = [v["version"] for v in versions_json]
minor_versions = defaultdict(list)
for version_str in raw_versions:
if "-" in version_str:
continue
major, minor, patch = parse_version(version_str)
if major == 3 and minor < 6:
continue
elif major == 2 and minor < 7:
continue
minor_versions[(major, minor)].append(patch)
versions = []
for (major, minor), patches in minor_versions.items():
patches.sort()
if major == 2 or minor <= 12:
patches = [patches[0], patches[-1]]
if major == 3 and minor > 14:
continue
versions.extend(f"{major}.{minor}.{patch}" for patch in patches)
return versions, platforms
def update_python_test_versions(force=False):
versions, platforms = get_github_python_versions()
versions = sorted(versions, key=parse_version)
build_yml_path = (
pathlib.Path(__file__).parent.parent / ".github" / "workflows" / "build.yml"
)
build_yml = yaml.safe_load(open(build_yml_path))
test_matrix = build_yml["jobs"]["test-wheels"]["strategy"]["matrix"]
existing_python_versions = test_matrix["python-version"]
if not force and versions == existing_python_versions:
print("No new python versions found - not updating github actions")
return
print("Adding new versions")
print("Old:", existing_python_versions)
print("New:", versions)
lines = list(open(build_yml_path))
first_line = lines.index(
" # automatically generated by ci/update_python_test_versions.py\n"
)
first_version_line = lines.index(" [\n", first_line)
last_version_line = lines.index(" ]\n", first_version_line)
new_versions = [f" {v},\n" for v in versions]
lines = lines[: first_version_line + 1] + new_versions + lines[last_version_line:]
exclusions = []
for v in versions:
if ("darwin", "arm64") not in platforms[v] or any(
v.startswith(pattern) for pattern in _OSX_PYTHON_EXCLUSIONS
):
exclusions.append(" - os: macos-latest\n")
exclusions.append(f" python-version: {v}\n")
if ("win32", "x64") not in platforms[v]:
exclusions.append(" - os: windows-latest\n")
exclusions.append(f" python-version: {v}\n")
if ("linux", "x64") not in platforms[v]:
exclusions.append(" - os: ubuntu-22.04\n")
exclusions.append(f" python-version: {v}\n")
if ("linux", "arm64") not in platforms[v]:
exclusions.append(" - os: ubuntu-22.04-arm\n")
exclusions.append(f" python-version: {v}\n")
first_exclude_line = lines.index(" exclude:\n", first_line)
last_exclude_line = lines.index("\n", first_exclude_line)
lines = lines[: first_exclude_line + 1] + exclusions + lines[last_exclude_line:]
with open(build_yml_path, "w") as o:
o.write("".join(lines))
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Updates github actions with new python versions",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"--force",
help="Run script even if there are no new python versions",
action="store_true",
)
args = parser.parse_args()
update_python_test_versions(force=args.force)