Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,46 +88,46 @@ workflows:
jobs:
# 3.12.10
- build-test-python:
name: python-3.12.10-x64
python_version: 3.12.10
name: python-3.12.11-x64
python_version: 3.12.11
python_arch: x64

- build-test-python:
name: python-3.12.10-x86
python_version: 3.12.10
name: python-3.12.11-x86
python_version: 3.12.11
python_arch: x86

# 3.11.12
- build-test-python:
name: python-3.11.12-x64
python_version: 3.11.12
name: python-3.11.13-x64
python_version: 3.11.13
python_arch: x64

- build-test-python:
name: python-3.11.12-x86
python_version: 3.11.12
name: python-3.11.13-x86
python_version: 3.11.13
python_arch: x86

# 3.10.17
- build-test-python:
name: python-3.10.17-x64
python_version: 3.10.17
name: python-3.10.18-x64
python_version: 3.10.18
python_arch: x64

- build-test-python:
name: python-3.10.17-x86
python_version: 3.10.17
name: python-3.10.18-x86
python_version: 3.10.18
python_arch: x86

# 3.9.22
- build-test-python:
name: python-3.9.22-x64
python_version: 3.9.22
name: python-3.9.23-x64
python_version: 3.9.23
python_arch: x64

- build-test-python:
name: python-3.9.22-x86
python_version: 3.9.22
name: python-3.9.23-x86
python_version: 3.9.23
python_arch: x86

# 3.8.20
Expand Down Expand Up @@ -168,29 +168,29 @@ workflows:
jobs:
# 3.12.10
- build-test-python-win:
name: python-3.12.10-win-x64
python_version: 3.12.10
name: python-3.12.11-win-x64
python_version: 3.12.11
python_arch: x64
generator: "Visual Studio 16 2019"

# 3.11.12
- build-test-python-win:
name: python-3.11.12-win-x64
python_version: 3.11.12
name: python-3.11.13-win-x64
python_version: 3.11.13
python_arch: x64
generator: "Visual Studio 16 2019"

# 3.10.17
- build-test-python-win:
name: python-3.10.17-win-x64
python_version: 3.10.17
name: python-3.10.18-win-x64
python_version: 3.10.18
python_arch: x64
generator: "Visual Studio 16 2019"

# 3.9.22
- build-test-python-win:
name: python-3.9.22-win-x64
python_version: 3.9.22
name: python-3.9.23-win-x64
python_version: 3.9.23
python_arch: x64
generator: "Visual Studio 16 2019"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
runs-on: [macos-latest]
python-version: [3.7.17, 3.8.20, 3.9.22, 3.10.17, 3.11.12, 3.12.10]
python-version: [3.7.17, 3.8.20, 3.9.23, 3.10.18, 3.11.13, 3.12.11]
include:
- runs-on: macos-latest
c-compiler: "clang"
Expand Down
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.20.6)

set(PYTHON_VERSION "3.12.10" CACHE STRING "The version of Python to build.")
set(PYTHON_VERSION "3.12.11" CACHE STRING "The version of Python to build.")

string(REPLACE "." ";" VERSION_LIST ${PYTHON_VERSION})
list(GET VERSION_LIST 0 PY_VERSION_MAJOR)
Expand Down Expand Up @@ -278,6 +278,7 @@ set(_download_3.9.19_md5 "b4d723903d0a8266b110c3da2f992416")
set(_download_3.9.20_md5 "896c19e5815ba990a3d1261502ea9f83")
set(_download_3.9.21_md5 "e61b3568082b57d55fd74cfc7ca020b4")
set(_download_3.9.22_md5 "8fe76e248a0e149ac23e8e4886397475")
set(_download_3.9.23_md5 "e6c3c5ba679cc6a1e2932b2fdcafbc3d")
# 3.10.x
set(_download_3.10.0_md5 "729e36388ae9a832b01cf9138921b383")
set(_download_3.10.1_md5 "91822157a97da16203877400c810d93e")
Expand All @@ -297,6 +298,7 @@ set(_download_3.10.14_md5 "f67d78c8323a18fe6b945914c51a7aa6")
set(_download_3.10.15_md5 "b6a2b570ea75ef55f50bfe79d778eb01")
set(_download_3.10.16_md5 "2515d8571c6fdd7fc620aa9e1cc6d202")
set(_download_3.10.17_md5 "763324aa2b396ee10a51bfa6c645d8e9")
set(_download_3.10.18_md5 "035ff701ad6c9183dc4c6de817924892")
# 3.11.x
set(_download_3.11.0_md5 "c5f77f1ea256dc5bdb0897eeb4d35bb0")
set(_download_3.11.1_md5 "5c986b2865979b393aa50a31c65b64e8")
Expand All @@ -311,6 +313,7 @@ set(_download_3.11.9_md5 "bfd4d3bfeac4216ce35d7a503bf02d5c")
set(_download_3.11.10_md5 "35c36069a43dd57a7e9915deba0f864e")
set(_download_3.11.11_md5 "9a5b43fcc06810b8ae924b0a080e6569")
set(_download_3.11.12_md5 "b8bb496014f05f5be180fab74810f40b")
set(_download_3.11.13_md5 "8abf1b1a9237f01b54572e2ecb246262")
# 3.12.x
set(_download_3.12.0_md5 "d6eda3e1399cef5dfde7c4f319b0596c")
set(_download_3.12.1_md5 "51c5c22dcbc698483734dff5c8028606")
Expand All @@ -323,6 +326,7 @@ set(_download_3.12.7_md5 "5d0c0e4c6a022a87165a9addcd869109")
set(_download_3.12.8_md5 "304473cf367fa65e450edf4b06b55fcc")
set(_download_3.12.9_md5 "ce613c72fa9b32fb4f109762d61b249b")
set(_download_3.12.10_md5 "35c03f014408e26e2b06d576c19cac54")
set(_download_3.12.11_md5 "45bda920329568dd6650b0ac556d17db")

set(_extracted_dir "Python-${PY_VERSION}")

Expand Down
212 changes: 212 additions & 0 deletions Utilities/check_python_patches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import os
import re
import requests
from packaging.version import Version, parse

PATCHES_DIR = os.path.join(os.path.dirname(__file__), '..', 'patches')
PYTHON_RELEASES_URL = 'https://www.python.org/api/v2/downloads/release/'

# Helper: get supported versions from CMakeLists.txt md5 variables
def get_supported_versions():
cmake_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'CMakeLists.txt'))
versions = set()
with open(cmake_path, 'r', encoding='utf-8') as f:
for line in f:
m = re.match(r'set\(_download_(\d+)\.(\d+)\.(\d+)_md5', line)
if m:
minor = f"{m.group(1)}.{m.group(2)}"
versions.add(minor)
def version_key(v):
major, minor = v.split('.')
return (int(major), int(minor))
return sorted(versions, key=version_key)

# Helper: get latest patch for a given minor version
def get_latest_patch_version(minor_version):
# Query Python.org for all releases
resp = requests.get(PYTHON_RELEASES_URL)
resp.raise_for_status()
data = resp.json()
candidates = []
# The API returns a list of releases, not a dict
for rel in data:
# Each rel should be a dict with 'name' key
v = rel.get('name', '').lstrip('Python ').strip()
if v.startswith(minor_version + '.'):
try:
candidates.append(parse(v))
except Exception:
continue
if not candidates:
return None
return str(max(candidates))

# Helper: get all patch folders for a minor version
def get_patch_folders(minor_version):
return [d for d in os.listdir(PATCHES_DIR) if d.startswith(minor_version + '.')]

def main():

supported = get_supported_versions()
# --- Update CI.yml and config.yml to use latest patch releases ---
# Map: minor_version -> latest_patch_version
latest_patches = {}
for minor in supported:
latest = get_latest_patch_version(minor)
if latest:
latest_patches[minor] = latest

# Update .github/workflows/CI.yml
ci_yml_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '.github', 'workflows', 'CI.yml'))
if os.path.exists(ci_yml_path):
with open(ci_yml_path, 'r', encoding='utf-8') as f:
ci_lines = f.readlines()
# Update python-version matrix
for idx, line in enumerate(ci_lines):
m = re.match(r'(\s*python-version:\s*)\[(.*?)\]', line)
if m:
orig_versions = re.findall(r'(\d+\.\d+\.\d+)', m.group(2))
new_versions = []
for v in orig_versions:
minor = '.'.join(v.split('.')[:2])
if minor in latest_patches:
new_versions.append(latest_patches[minor])
else:
new_versions.append(v)
new_versions_str = ', '.join(new_versions)
ci_lines[idx] = f'{m.group(1)}[{new_versions_str}]\n'
break
# Update job names containing python version
version_pat = re.compile(r'(python-)(\d+\.\d+\.\d+)([-\w]*)')
for idx, line in enumerate(ci_lines):
def repl(m):
minor = '.'.join(m.group(2).split('.')[:2])
new_version = latest_patches.get(minor, m.group(2))
return f'{m.group(1)}{new_version}{m.group(3)}'
new_line = version_pat.sub(repl, line)
if new_line != line:
ci_lines[idx] = new_line
with open(ci_yml_path, 'w', encoding='utf-8') as f:
f.writelines(ci_lines)
print(f"Updated python-version matrix and job names in {ci_yml_path}")

# Update .circleci/config.yml
circleci_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '.circleci', 'config.yml'))
if os.path.exists(circleci_path):
with open(circleci_path, 'r', encoding='utf-8') as f:
circle_lines = f.readlines()
# Replace all python_version: lines in workflows with latest patch
for idx, line in enumerate(circle_lines):
m = re.match(r'(\s*python_version:\s*)(\d+\.\d+\.\d+)', line)
if m:
minor = '.'.join(m.group(2).split('.')[:2])
if minor in latest_patches:
new_line = f'{m.group(1)}{latest_patches[minor]}\n'
if circle_lines[idx] != new_line:
circle_lines[idx] = new_line
# Update job names containing python version
version_pat = re.compile(r'(python-)(\d+\.\d+\.\d+)([-\w]*)')
for idx, line in enumerate(circle_lines):
def repl(m):
minor = '.'.join(m.group(2).split('.')[:2])
new_version = latest_patches.get(minor, m.group(2))
return f'{m.group(1)}{new_version}{m.group(3)}'
new_line = version_pat.sub(repl, line)
if new_line != line:
circle_lines[idx] = new_line
with open(circleci_path, 'w', encoding='utf-8') as f:
f.writelines(circle_lines)
print(f"Updated python_version fields and job names in {circleci_path}")
updated = False
supported = get_supported_versions()
cmake_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'CMakeLists.txt'))
with open(cmake_path, 'r', encoding='utf-8') as f:
cmake_lines = f.readlines()

for minor in supported:
latest = get_latest_patch_version(minor)
if not latest:
continue
# Check if CMakeLists.txt already has this version
cmake_var = f'_download_{latest}_md5'
if any(cmake_var in line for line in cmake_lines):
continue

# Get MD5 hash from the Python release page
release_url = f'https://www.python.org/downloads/release/python-{latest.replace(".", "")}/'
try:
resp = requests.get(release_url)
resp.raise_for_status()
html = resp.text
# Try to find the MD5 for the .tgz file in the files table
# First, try a robust regex for the .tgz row and MD5
pattern = rf'<tr[^>]*>.*?Python-{latest}\.tgz.*?<td[^>]*>.*?([a-fA-F0-9]{{32}}).*?</td>.*?</tr>'
match = re.search(pattern, html, re.DOTALL)
if not match:
# Fallback: try to find the MD5 in a <tt> tag near the .tgz link
pattern2 = rf'Python-{latest}\.tgz.*?([a-fA-F0-9]{{32}})'
match2 = re.search(pattern2, html, re.DOTALL)
if match2:
md5 = match2.group(1)
else:
print(f"MD5 not found on release page for {latest}")
continue
else:
md5 = match.group(1)
except Exception as e:
print(f"Failed to fetch MD5 for {latest} from release page: {e}")
continue

# Find where to insert: after the last md5 line for the same minor version
all_md5_lines = []
md5_pattern = re.compile(r'set\(_download_(\d+)\.(\d+)\.(\d+)_md5')
for idx, line in enumerate(cmake_lines):
m = md5_pattern.match(line.strip())
if m:
all_md5_lines.append((int(m.group(1)), int(m.group(2)), int(m.group(3)), idx))
major, minor_num = map(int, minor.split('.'))
this_minor_lines = [t for t in all_md5_lines if (t[0], t[1]) == (major, minor_num)]
if this_minor_lines:
# Insert after the highest patch for this minor version
insert_idx = max(this_minor_lines, key=lambda t: t[2])[3] + 1
else:
# If no md5 for this minor, insert after the last md5 for any earlier version
earlier_lines = [t for t in all_md5_lines if (t[0], t[1]) < (major, minor_num)]
if earlier_lines:
insert_idx = max(earlier_lines, key=lambda t: (t[0], t[1], t[2]))[3] + 1
else:
# Otherwise, insert at the top after all comments
insert_idx = 0
for idx, line in enumerate(cmake_lines):
if not line.strip().startswith('#'):
insert_idx = idx
break
new_line = f'set(_download_{latest}_md5 "{md5}")\n'
cmake_lines.insert(insert_idx, new_line)
print(f"Added {new_line.strip()} to CMakeLists.txt after line {insert_idx}")
updated = True

# --- Update default PYTHON_VERSION to latest with md5 variable ---
md5_versions = []
for line in cmake_lines:
m = re.match(r'set\(_download_(\d+\.\d+\.\d+)_md5', line)
if m:
md5_versions.append(m.group(1))
if md5_versions:
latest_md5_version = str(max(md5_versions, key=lambda v: tuple(map(int, v.split('.')))))
# Update set(PYTHON_VERSION ...) line
for idx, line in enumerate(cmake_lines):
if line.strip().startswith('set(PYTHON_VERSION '):
old = cmake_lines[idx]
cmake_lines[idx] = f'set(PYTHON_VERSION "{latest_md5_version}" CACHE STRING "The version of Python to build.")\n'
print(f"Updated default PYTHON_VERSION in CMakeLists.txt: {old.strip()} -> {cmake_lines[idx].strip()}")
updated = True
break
if updated:
with open(cmake_path, 'w', encoding='utf-8') as f:
f.writelines(cmake_lines)
else:
print("No new patch releases found.")

if __name__ == '__main__':
main()