|
| 1 | +"""Script that updates the Python versions used in the project.""" |
| 2 | + |
| 3 | +import os |
| 4 | +import re |
| 5 | + |
| 6 | +from packaging.version import Version |
| 7 | +import requests |
| 8 | + |
| 9 | + |
| 10 | +def is_version_string(s: str) -> bool: |
| 11 | + """Check if the string is in accepted version format. |
| 12 | +
|
| 13 | + Parameters |
| 14 | + ---------- |
| 15 | + s : str |
| 16 | + String to check. |
| 17 | +
|
| 18 | + Returns |
| 19 | + ------- |
| 20 | + bool |
| 21 | + True if the string is in the accepted version format, False otherwise. |
| 22 | + """ |
| 23 | + pattern = r"^\d+\.\d+\.\d+$" |
| 24 | + return bool(re.match(pattern, s)) |
| 25 | + |
| 26 | + |
| 27 | +def get_latest_github_release(user, repo) -> dict: |
| 28 | + """Get the latest release of a GitHub repository. |
| 29 | +
|
| 30 | + Parameters |
| 31 | + ---------- |
| 32 | + user : str |
| 33 | + GitHub username. |
| 34 | + repo : str |
| 35 | + Repository name. |
| 36 | +
|
| 37 | + Returns |
| 38 | + ------- |
| 39 | + dict |
| 40 | + JSON response of the latest release. |
| 41 | + """ |
| 42 | + url = f"https://api.github.com/repos/{user}/{repo}/releases/latest" |
| 43 | + response = requests.get(url) |
| 44 | + if response.status_code == 200: |
| 45 | + return response.json() |
| 46 | + else: |
| 47 | + print(f"Failed to get releases: {response.content}") |
| 48 | + return None |
| 49 | + |
| 50 | + |
| 51 | +def get_minor_version_sublist_with_greater_patch(list: list[str], current_version: str): |
| 52 | + """Get the sublist of versions with greater patch than the current version.""" |
| 53 | + major, minor, patch = current_version.split(".") |
| 54 | + major, minor, patch = int(major), int(minor), int(patch) |
| 55 | + sublist = [version for version in list if version.startswith(f"{major}.{minor}.")] |
| 56 | + sublist = [version for version in sublist if int(version.split(".")[2]) > patch] |
| 57 | + sublist = sorted(sublist, key=Version, reverse=True) |
| 58 | + |
| 59 | + return sublist |
| 60 | + |
| 61 | + |
| 62 | +# Get path to the root of the project |
| 63 | +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 64 | + |
| 65 | +# Path to the constants file |
| 66 | +CONSTANTS_FILE = os.path.join( |
| 67 | + ROOT_DIR, "src", "ansys", "tools", "installer", "constants.py" |
| 68 | +) |
| 69 | + |
| 70 | +# Parse the constants file to find the current Python versions |
| 71 | +# used in the project. The versions are stored in a tuple |
| 72 | +with open(CONSTANTS_FILE, "r") as f: |
| 73 | + lines = f.readlines() |
| 74 | + |
| 75 | +# Import the following constants inside the constants file |
| 76 | +# |
| 77 | +# Example: |
| 78 | +# |
| 79 | +# VANILLA_PYTHON_VERSIONS = { |
| 80 | +# "Python 3.8": "3.8.10", |
| 81 | +# "Python 3.9": "3.9.13", |
| 82 | +# "Python 3.10": "3.10.11", |
| 83 | +# "Python 3.11": "3.11.6", |
| 84 | +# "Python 3.12": "3.12.0", |
| 85 | +# } |
| 86 | +# |
| 87 | +# CONDA_PYTHON_VERSION = "23.1.0-4" |
| 88 | +# |
| 89 | + |
| 90 | +vanilla_python_versions: dict[str, str] = {} |
| 91 | +conda_python_version: str = "" |
| 92 | + |
| 93 | +for line in lines: |
| 94 | + if "VANILLA_PYTHON_VERSIONS" in line: |
| 95 | + # Store the index of the line where the dictionary starts |
| 96 | + start_index = lines.index(line) |
| 97 | + break |
| 98 | + |
| 99 | +# Get the dictionary that contains the Python versions |
| 100 | +for line in lines[start_index:]: |
| 101 | + if "}" in line: |
| 102 | + # Store the index of the line where the dictionary ends |
| 103 | + end_index = lines.index(line, start_index) |
| 104 | + break |
| 105 | + |
| 106 | +# Parse the dictionary to get the Python versions |
| 107 | +for line in lines[start_index : end_index + 1]: |
| 108 | + if "Python" in line: |
| 109 | + # Extract the Python version and the version number |
| 110 | + python_version = line.split(":")[0].strip().replace('"', "") |
| 111 | + version_number = line.split(":")[1].strip().replace('"', "").replace(",", "") |
| 112 | + |
| 113 | + # Store the Python version and the version number |
| 114 | + vanilla_python_versions[python_version] = version_number |
| 115 | + |
| 116 | +# Get the Conda Python version |
| 117 | +for line in lines: |
| 118 | + if "CONDA_PYTHON_VERSION" in line: |
| 119 | + conda_python_version = line.split("=")[1].strip().replace('"', "") |
| 120 | + |
| 121 | +# LOG - Print the current Python versions |
| 122 | +print("Current Vanilla Python versions:") |
| 123 | +for version in vanilla_python_versions.values(): |
| 124 | + print(f">>> '{version}'") |
| 125 | + |
| 126 | +print("Current Conda Python version") |
| 127 | +print(f">>> '{conda_python_version}'") |
| 128 | + |
| 129 | +# -------------------------------------------------------------------------------------------- |
| 130 | + |
| 131 | +print("--- \nUpdating Python versions...\n") |
| 132 | + |
| 133 | +# Check remote Python versions available |
| 134 | +PYTHON_FTP = "https://www.python.org/ftp/python" |
| 135 | + |
| 136 | +# List all folders in the Python FTP |
| 137 | +response = requests.get(PYTHON_FTP) |
| 138 | +text = response.text.split("\n") |
| 139 | +ftp_versions = [] |
| 140 | +for line in text: |
| 141 | + tmp = line.strip('<a href="').split('/">')[0] |
| 142 | + # Check if the folder is a Python version |
| 143 | + if is_version_string(tmp): |
| 144 | + ftp_versions.append(tmp) |
| 145 | + |
| 146 | +# For each minor version, get the patch versions available |
| 147 | +# greter than the current patch version |
| 148 | +for python_version_key, python_version_value in vanilla_python_versions.items(): |
| 149 | + # Get the minor version of the current Python version |
| 150 | + minor_version = ".".join(python_version_value.split(".")[:2]) |
| 151 | + |
| 152 | + # Get the patch versions available |
| 153 | + patch_versions = get_minor_version_sublist_with_greater_patch( |
| 154 | + ftp_versions, python_version_value |
| 155 | + ) |
| 156 | + |
| 157 | + # Check if the patch versions contain the executable |
| 158 | + new_patch_version = None |
| 159 | + for patch_version in patch_versions: |
| 160 | + # Check if the executable exists |
| 161 | + response_1 = requests.get( |
| 162 | + f"{PYTHON_FTP}/{patch_version}/Python-{patch_version}.tar.xz" |
| 163 | + ) |
| 164 | + response_2 = requests.get( |
| 165 | + f"{PYTHON_FTP}/{patch_version}/python-{patch_version}-amd64.exe" |
| 166 | + ) |
| 167 | + if response_1.status_code == 200 and response_2.status_code == 200: |
| 168 | + print(f"Python {patch_version} is available for download") |
| 169 | + new_patch_version = patch_version |
| 170 | + break |
| 171 | + |
| 172 | + # Update the Python version |
| 173 | + if new_patch_version: |
| 174 | + vanilla_python_versions[python_version_key] = new_patch_version |
| 175 | + else: |
| 176 | + print(f"Python {python_version_value} is already the latest version available") |
| 177 | + |
| 178 | +# Get the latest Conda Python version |
| 179 | +latest_conda_release = get_latest_github_release("conda-forge", "miniforge") |
| 180 | + |
| 181 | +# Verify that the assets are available |
| 182 | +assets = latest_conda_release["assets"] |
| 183 | +new_conda_version = None |
| 184 | +count = 0 |
| 185 | +for asset in assets: |
| 186 | + if f"Miniforge3-{latest_conda_release['name']}-Linux-x86_64.sh" in asset["name"]: |
| 187 | + count += 1 |
| 188 | + if f"Miniforge3-{latest_conda_release['name']}-Windows-x86_64.exe" in asset["name"]: |
| 189 | + count += 1 |
| 190 | + if count == 2: |
| 191 | + new_conda_version = latest_conda_release["name"] |
| 192 | + break |
| 193 | + |
| 194 | +# Update the Conda Python version |
| 195 | +if new_conda_version: |
| 196 | + conda_python_version = new_conda_version |
| 197 | + print(f"Conda Python version updated to {conda_python_version}") |
| 198 | +else: |
| 199 | + print(f"Conda Python version is already the latest version available") |
| 200 | + |
| 201 | +print("\nPython versions updated successfully\n ---") |
| 202 | + |
| 203 | +# -------------------------------------------------------------------------------------------- |
| 204 | + |
| 205 | +# LOG - Print the new Python versions |
| 206 | +print("New Vanilla Python versions:") |
| 207 | +for version in vanilla_python_versions.values(): |
| 208 | + print(f">>> '{version}'") |
| 209 | + |
| 210 | +print("New Conda Python version:") |
| 211 | +print(f">>> '{conda_python_version}'") |
| 212 | + |
| 213 | +# Update the constants file with the new Python versions |
| 214 | +# Write the new Python versions to the constants file |
| 215 | +with open(CONSTANTS_FILE, "w") as f: |
| 216 | + for line in lines[:start_index]: |
| 217 | + f.write(line) |
| 218 | + |
| 219 | + f.write("VANILLA_PYTHON_VERSIONS = {\n") |
| 220 | + for python_version, version_number in vanilla_python_versions.items(): |
| 221 | + f.write(f' "{python_version}": "{version_number}",\n') |
| 222 | + f.write("}\n\n") |
| 223 | + |
| 224 | + f.write(f'CONDA_PYTHON_VERSION = "{conda_python_version}"\n') |
0 commit comments