|
18 | 18 |
|
19 | 19 | # Python.org URLs |
20 | 20 | PYTHON_FTP_URL = "https://www.python.org/ftp/python/" |
21 | | -PYTHON_MACOS_PKG_URL_TEMPLATE = "https://www.python.org/ftp/python/{version}/python-{version}-macos11.pkg" |
22 | 21 | PYTHON_SBOM_LINUX_URL_TEMPLATE = "https://www.python.org/ftp/python/{version}/Python-{version}.tgz.spdx.json" |
23 | 22 | PYTHON_SBOM_WINDOWS_URL_TEMPLATE = "https://www.python.org/ftp/python/{version}/python-{version}-amd64.exe.spdx.json" |
24 | 23 |
|
| 24 | +# Python Build Standalone (PBS) - used for macOS |
| 25 | +# https://github.com/astral-sh/python-build-standalone |
| 26 | +PBS_LATEST_RELEASE_URL = "https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest" |
| 27 | +PBS_SHA256SUMS_URL_TEMPLATE = ( |
| 28 | + "https://github.com/astral-sh/python-build-standalone/releases/download/{release}/SHA256SUMS" |
| 29 | +) |
| 30 | + |
25 | 31 | # Regex patterns for Dockerfile updates |
26 | 32 | # Linux: ENV PYTHON3_VERSION=3.13.7 (no quotes, matches version at end of line) |
27 | 33 | LINUX_VERSION_PATTERN = re.compile(r'(ENV PYTHON3_VERSION=)(\d+\.\d+\.\d+)$', re.MULTILINE) |
@@ -248,28 +254,44 @@ def apply_substitution(pattern: re.Pattern, replace_func, error_msg: str, _docke |
248 | 254 |
|
249 | 255 |
|
250 | 256 | def upgrade_macos_python_version(app: Application, new_version: str, tracker: ValidationTracker): |
251 | | - macos_python_file = app.repo.path / '.github' / 'workflows' / 'resolve-build-deps.yaml' |
| 257 | + """ |
| 258 | + Update macOS Python version in the workflow file. |
252 | 259 |
|
253 | | - macos_content = read_file_safely(macos_python_file, 'macOS workflow', tracker) |
254 | | - if macos_content is None: |
| 260 | + The workflow uses Python Build Standalone (PBS) from Astral. Updates PYTHON_PATCH, |
| 261 | + PBS_RELEASE, and the SHA256 hashes for both macOS architectures. |
| 262 | + """ |
| 263 | + workflow_file = app.repo.path / '.github' / 'workflows' / 'resolve-build-deps.yaml' |
| 264 | + content = read_file_safely(workflow_file, 'macOS workflow', tracker) |
| 265 | + if content is None: |
255 | 266 | return |
256 | 267 |
|
257 | | - target_line = next((line for line in macos_content.splitlines() if 'PYTHON3_DOWNLOAD_URL' in line), None) |
| 268 | + new_patch = new_version.split('.')[-1] |
258 | 269 |
|
259 | | - if target_line is None: |
260 | | - tracker.error(('macOS workflow',), message='Could not find PYTHON3_DOWNLOAD_URL') |
| 270 | + # Get the latest PBS release and SHA256 hashes |
| 271 | + pbs_info = get_pbs_release_info(app, new_version) |
| 272 | + if pbs_info is None: |
| 273 | + tracker.error( |
| 274 | + ('macOS workflow',), |
| 275 | + message=f'Could not find PBS release with Python {new_version}. ' |
| 276 | + 'A new PBS release may not be available yet.', |
| 277 | + ) |
261 | 278 | return |
262 | 279 |
|
263 | | - new_url = PYTHON_MACOS_PKG_URL_TEMPLATE.format(version=new_version) |
264 | | - indent = target_line[: target_line.index('PYTHON3_DOWNLOAD_URL')] |
265 | | - new_line = f'{indent}PYTHON3_DOWNLOAD_URL: "{new_url}"' |
| 280 | + # Define replacements: (pattern, new_value) |
| 281 | + replacements = [ |
| 282 | + (r'^(\s*PYTHON_PATCH:\s*)\d+\s*$', rf'\g<1>{new_patch}'), |
| 283 | + (r'^(\s*PBS_RELEASE:\s*)\d+\s*$', rf"\g<1>{pbs_info['release']}"), |
| 284 | + (r'^(\s*PBS_SHA256__aarch64:\s*)[0-9a-f]+\s*$', rf"\g<1>{pbs_info['aarch64']}"), |
| 285 | + (r'^(\s*PBS_SHA256__x86_64:\s*)[0-9a-f]+\s*$', rf"\g<1>{pbs_info['x86_64']}"), |
| 286 | + ] |
266 | 287 |
|
267 | | - if target_line == new_line: |
268 | | - app.display_info(f"Python version in macOS workflow is already at {new_version}") |
269 | | - return |
| 288 | + for pattern, replacement in replacements: |
| 289 | + content, count = re.subn(pattern, replacement, content, count=1, flags=re.MULTILINE) |
| 290 | + if count == 0: |
| 291 | + tracker.error(('macOS workflow',), message=f'Could not find pattern: {pattern}') |
| 292 | + return |
270 | 293 |
|
271 | | - updated_content = macos_content.replace(target_line, new_line, 1) |
272 | | - write_file_safely(macos_python_file, updated_content, 'macOS workflow', tracker) |
| 294 | + write_file_safely(workflow_file, content, 'macOS workflow', tracker) |
273 | 295 |
|
274 | 296 |
|
275 | 297 | def upgrade_python_version_full_constant(app: Application, new_version: str, tracker: ValidationTracker): |
@@ -336,6 +358,46 @@ def get_latest_python_version(app: Application, major_minor: str) -> str | None: |
336 | 358 | return str(versions[-1]) |
337 | 359 |
|
338 | 360 |
|
| 361 | +def get_pbs_release_info(app: Application, python_version: str) -> dict[str, str] | None: |
| 362 | + """ |
| 363 | + Get the latest PBS release info with SHA256 hashes for the specified Python version. |
| 364 | +
|
| 365 | + Returns dict with 'release', 'aarch64', 'x86_64' keys, or None if not found. |
| 366 | + """ |
| 367 | + try: |
| 368 | + # Get latest PBS release tag |
| 369 | + response = httpx.get(PBS_LATEST_RELEASE_URL, timeout=30) |
| 370 | + response.raise_for_status() |
| 371 | + release = orjson.loads(response.text).get('tag_name') |
| 372 | + if not release: |
| 373 | + return None |
| 374 | + |
| 375 | + # Fetch SHA256SUMS file and extract hashes for our target files |
| 376 | + sha_response = httpx.get(PBS_SHA256SUMS_URL_TEMPLATE.format(release=release), timeout=30, follow_redirects=True) |
| 377 | + if sha_response.status_code != 200: |
| 378 | + return None |
| 379 | + |
| 380 | + hashes = {'release': release} |
| 381 | + for arch in ('aarch64', 'x86_64'): |
| 382 | + filename = f'cpython-{python_version}+{release}-{arch}-apple-darwin-install_only_stripped.tar.gz' |
| 383 | + for line in sha_response.text.splitlines(): |
| 384 | + if filename in line: |
| 385 | + sha_hash = line.split()[0] |
| 386 | + if validate_sha256(sha_hash): |
| 387 | + hashes[arch] = sha_hash |
| 388 | + break |
| 389 | + |
| 390 | + # Verify we found both architectures |
| 391 | + if 'aarch64' not in hashes or 'x86_64' not in hashes: |
| 392 | + return None |
| 393 | + |
| 394 | + return hashes |
| 395 | + |
| 396 | + except httpx.RequestException as e: |
| 397 | + app.display_warning(f"Error fetching PBS release info: {e}") |
| 398 | + return None |
| 399 | + |
| 400 | + |
339 | 401 | def get_python_sha256_hashes(app: Application, version: str) -> dict[str, str]: |
340 | 402 | """ |
341 | 403 | Fetch SHA256 hashes for Python release artifacts using SBOM files. |
|
0 commit comments