relenv 0.21.2__py3-none-any.whl → 0.22.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- relenv/__init__.py +14 -2
- relenv/__main__.py +12 -6
- relenv/_resources/xz/config.h +148 -0
- relenv/_resources/xz/readme.md +4 -0
- relenv/build/__init__.py +28 -30
- relenv/build/common/__init__.py +50 -0
- relenv/build/common/_sysconfigdata_template.py +72 -0
- relenv/build/common/builder.py +907 -0
- relenv/build/common/builders.py +163 -0
- relenv/build/common/download.py +324 -0
- relenv/build/common/install.py +609 -0
- relenv/build/common/ui.py +432 -0
- relenv/build/darwin.py +128 -14
- relenv/build/linux.py +292 -74
- relenv/build/windows.py +123 -169
- relenv/buildenv.py +48 -17
- relenv/check.py +10 -5
- relenv/common.py +492 -165
- relenv/create.py +147 -7
- relenv/fetch.py +16 -4
- relenv/manifest.py +15 -7
- relenv/python-versions.json +350 -0
- relenv/pyversions.py +817 -30
- relenv/relocate.py +101 -55
- relenv/runtime.py +457 -282
- relenv/toolchain.py +9 -3
- {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/METADATA +1 -1
- relenv-0.22.1.dist-info/RECORD +48 -0
- tests/__init__.py +2 -0
- tests/_pytest_typing.py +45 -0
- tests/conftest.py +42 -36
- tests/test_build.py +426 -9
- tests/test_common.py +373 -48
- tests/test_create.py +149 -6
- tests/test_downloads.py +19 -15
- tests/test_fips_photon.py +6 -3
- tests/test_module_imports.py +44 -0
- tests/test_pyversions_runtime.py +177 -0
- tests/test_relocate.py +45 -39
- tests/test_relocate_module.py +257 -0
- tests/test_runtime.py +1968 -6
- tests/test_verify_build.py +477 -34
- relenv/build/common.py +0 -1707
- relenv-0.21.2.dist-info/RECORD +0 -35
- {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/WHEEL +0 -0
- {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/entry_points.txt +0 -0
- {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/licenses/LICENSE.md +0 -0
- {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/licenses/NOTICE +0 -0
- {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/top_level.txt +0 -0
relenv/pyversions.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2025 Broadcom.
|
|
1
|
+
# Copyright 2022-2025 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
"""
|
|
4
4
|
Versions utility.
|
|
@@ -11,20 +11,35 @@ Versions utility.
|
|
|
11
11
|
# )
|
|
12
12
|
#
|
|
13
13
|
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
14
17
|
import hashlib
|
|
15
18
|
import json
|
|
16
19
|
import logging
|
|
17
|
-
import os
|
|
20
|
+
import os as _os
|
|
18
21
|
import pathlib
|
|
19
22
|
import re
|
|
20
|
-
import subprocess
|
|
21
|
-
import sys
|
|
23
|
+
import subprocess as _subprocess
|
|
24
|
+
import sys as _sys
|
|
22
25
|
import time
|
|
26
|
+
from typing import Any
|
|
23
27
|
|
|
24
28
|
from relenv.common import Version, check_url, download_url, fetch_url_content
|
|
25
29
|
|
|
26
30
|
log = logging.getLogger(__name__)
|
|
27
31
|
|
|
32
|
+
os = _os
|
|
33
|
+
subprocess = _subprocess
|
|
34
|
+
sys = _sys
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Version",
|
|
38
|
+
"os",
|
|
39
|
+
"subprocess",
|
|
40
|
+
"sys",
|
|
41
|
+
]
|
|
42
|
+
|
|
28
43
|
KEYSERVERS = [
|
|
29
44
|
"keyserver.ubuntu.com",
|
|
30
45
|
"keys.openpgp.org",
|
|
@@ -34,16 +49,16 @@ KEYSERVERS = [
|
|
|
34
49
|
ARCHIVE = "https://www.python.org/ftp/python/{version}/Python-{version}.{ext}"
|
|
35
50
|
|
|
36
51
|
|
|
37
|
-
def _ref_version(x):
|
|
52
|
+
def _ref_version(x: str) -> Version:
|
|
38
53
|
_ = x.split("Python ", 1)[1].split("<", 1)[0]
|
|
39
54
|
return Version(_)
|
|
40
55
|
|
|
41
56
|
|
|
42
|
-
def _ref_path(x):
|
|
57
|
+
def _ref_path(x: str) -> str:
|
|
43
58
|
return x.split('href="')[1].split('"')[0]
|
|
44
59
|
|
|
45
60
|
|
|
46
|
-
def _release_urls(version, gzip=False):
|
|
61
|
+
def _release_urls(version: Version, gzip: bool = False) -> tuple[str, str | None]:
|
|
47
62
|
if gzip:
|
|
48
63
|
tarball = f"https://www.python.org/ftp/python/{version}/Python-{version}.tgz"
|
|
49
64
|
else:
|
|
@@ -54,7 +69,7 @@ def _release_urls(version, gzip=False):
|
|
|
54
69
|
return tarball, f"{tarball}.asc"
|
|
55
70
|
|
|
56
71
|
|
|
57
|
-
def _receive_key(keyid, server):
|
|
72
|
+
def _receive_key(keyid: str, server: str) -> bool:
|
|
58
73
|
proc = subprocess.run(
|
|
59
74
|
["gpg", "--keyserver", server, "--recv-keys", keyid], capture_output=True
|
|
60
75
|
)
|
|
@@ -63,25 +78,28 @@ def _receive_key(keyid, server):
|
|
|
63
78
|
return False
|
|
64
79
|
|
|
65
80
|
|
|
66
|
-
def _get_keyid(proc):
|
|
81
|
+
def _get_keyid(proc: subprocess.CompletedProcess[bytes]) -> str | None:
|
|
67
82
|
try:
|
|
68
83
|
err = proc.stderr.decode()
|
|
69
84
|
return err.splitlines()[1].rsplit(" ", 1)[-1]
|
|
70
85
|
except (AttributeError, IndexError):
|
|
71
|
-
return
|
|
86
|
+
return None
|
|
72
87
|
|
|
73
88
|
|
|
74
|
-
def verify_signature(
|
|
89
|
+
def verify_signature(
|
|
90
|
+
path: str | os.PathLike[str],
|
|
91
|
+
signature: str | os.PathLike[str],
|
|
92
|
+
) -> bool:
|
|
75
93
|
"""
|
|
76
94
|
Verify gpg signature.
|
|
77
95
|
"""
|
|
78
96
|
proc = subprocess.run(["gpg", "--verify", signature, path], capture_output=True)
|
|
79
97
|
keyid = _get_keyid(proc)
|
|
80
98
|
if proc.returncode == 0:
|
|
81
|
-
print(f"Valid signature {path} {keyid}")
|
|
99
|
+
print(f"Valid signature {path} {keyid or ''}")
|
|
82
100
|
return True
|
|
83
101
|
err = proc.stderr.decode()
|
|
84
|
-
if "No public key" in err:
|
|
102
|
+
if keyid and "No public key" in err:
|
|
85
103
|
for server in KEYSERVERS:
|
|
86
104
|
if _receive_key(keyid, server):
|
|
87
105
|
print(f"found public key {keyid} on {server}")
|
|
@@ -106,7 +124,7 @@ VERSION = None # '3.13.2'
|
|
|
106
124
|
UPDATE = False
|
|
107
125
|
|
|
108
126
|
|
|
109
|
-
def digest(file):
|
|
127
|
+
def digest(file: str | os.PathLike[str]) -> str:
|
|
110
128
|
"""
|
|
111
129
|
SHA-256 digest of file.
|
|
112
130
|
"""
|
|
@@ -116,9 +134,9 @@ def digest(file):
|
|
|
116
134
|
return hsh.hexdigest()
|
|
117
135
|
|
|
118
136
|
|
|
119
|
-
def _main():
|
|
137
|
+
def _main() -> None:
|
|
120
138
|
|
|
121
|
-
pyversions = {"versions": []}
|
|
139
|
+
pyversions: dict[str, Any] = {"versions": []}
|
|
122
140
|
|
|
123
141
|
vfile = pathlib.Path(".pyversions")
|
|
124
142
|
cfile = pathlib.Path(".content")
|
|
@@ -130,6 +148,8 @@ def _main():
|
|
|
130
148
|
content = fetch_url_content(url)
|
|
131
149
|
cfile.write_text(content)
|
|
132
150
|
tsfile.write_text(str(ts))
|
|
151
|
+
pyversions = {"versions": []}
|
|
152
|
+
vfile.write_text(json.dumps(pyversions, indent=1))
|
|
133
153
|
elif CHECK:
|
|
134
154
|
ts = int(tsfile.read_text())
|
|
135
155
|
if check_url(url, timestamp=ts):
|
|
@@ -152,7 +172,7 @@ def _main():
|
|
|
152
172
|
versions = [_ for _ in parsed_versions if _.major >= 3]
|
|
153
173
|
cwd = os.getcwd()
|
|
154
174
|
|
|
155
|
-
out = {}
|
|
175
|
+
out: dict[str, dict[str, str]] = {}
|
|
156
176
|
|
|
157
177
|
for version in versions:
|
|
158
178
|
if VERSION and Version(VERSION) != version:
|
|
@@ -210,7 +230,626 @@ def _main():
|
|
|
210
230
|
vfile.write_text(json.dumps(out, indent=1))
|
|
211
231
|
|
|
212
232
|
|
|
213
|
-
def
|
|
233
|
+
def sha256_digest(file: str | os.PathLike[str]) -> str:
|
|
234
|
+
"""
|
|
235
|
+
SHA-256 digest of file.
|
|
236
|
+
"""
|
|
237
|
+
hsh = hashlib.sha256()
|
|
238
|
+
with open(file, "rb") as fp:
|
|
239
|
+
hsh.update(fp.read())
|
|
240
|
+
return hsh.hexdigest()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def detect_openssl_versions() -> list[str]:
|
|
244
|
+
"""
|
|
245
|
+
Detect available OpenSSL versions from GitHub releases.
|
|
246
|
+
"""
|
|
247
|
+
url = "https://github.com/openssl/openssl/tags"
|
|
248
|
+
content = fetch_url_content(url)
|
|
249
|
+
# Find tags like openssl-3.5.4
|
|
250
|
+
pattern = r'openssl-(\d+\.\d+\.\d+)"'
|
|
251
|
+
matches = re.findall(pattern, content)
|
|
252
|
+
# Deduplicate and sort
|
|
253
|
+
versions = sorted(
|
|
254
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
255
|
+
)
|
|
256
|
+
return versions
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def detect_sqlite_versions() -> list[tuple[str, str]]:
|
|
260
|
+
"""
|
|
261
|
+
Detect available SQLite versions from sqlite.org.
|
|
262
|
+
|
|
263
|
+
Returns list of (version, sqliteversion) tuples.
|
|
264
|
+
"""
|
|
265
|
+
url = "https://sqlite.org/download.html"
|
|
266
|
+
content = fetch_url_content(url)
|
|
267
|
+
# Find sqlite-autoconf-NNNNNNN.tar.gz
|
|
268
|
+
pattern = r"sqlite-autoconf-(\d{7})\.tar\.gz"
|
|
269
|
+
matches = re.findall(pattern, content)
|
|
270
|
+
# Convert to version format
|
|
271
|
+
versions = []
|
|
272
|
+
for sqlite_ver in set(matches):
|
|
273
|
+
# SQLite version format: 3XXYYZZ where XX=minor, YY=patch, ZZ=subpatch
|
|
274
|
+
if len(sqlite_ver) == 7 and sqlite_ver[0] == "3":
|
|
275
|
+
major = 3
|
|
276
|
+
minor = int(sqlite_ver[1:3])
|
|
277
|
+
patch = int(sqlite_ver[3:5])
|
|
278
|
+
subpatch = int(sqlite_ver[5:7])
|
|
279
|
+
version = f"{major}.{minor}.{patch}.{subpatch}"
|
|
280
|
+
versions.append((version, sqlite_ver))
|
|
281
|
+
return sorted(
|
|
282
|
+
versions, key=lambda x: [int(n) for n in x[0].split(".")], reverse=True
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def detect_xz_versions() -> list[str]:
|
|
287
|
+
"""
|
|
288
|
+
Detect available XZ versions from tukaani.org.
|
|
289
|
+
"""
|
|
290
|
+
url = "https://tukaani.org/xz/"
|
|
291
|
+
content = fetch_url_content(url)
|
|
292
|
+
# Find xz-X.Y.Z.tar.gz
|
|
293
|
+
pattern = r"xz-(\d+\.\d+\.\d+)\.tar\.gz"
|
|
294
|
+
matches = re.findall(pattern, content)
|
|
295
|
+
# Deduplicate and sort
|
|
296
|
+
versions = sorted(
|
|
297
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
298
|
+
)
|
|
299
|
+
return versions
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def detect_libffi_versions() -> list[str]:
|
|
303
|
+
"""Detect available libffi versions from GitHub releases."""
|
|
304
|
+
url = "https://github.com/libffi/libffi/tags"
|
|
305
|
+
content = fetch_url_content(url)
|
|
306
|
+
pattern = r'v(\d+\.\d+\.\d+)"'
|
|
307
|
+
matches = re.findall(pattern, content)
|
|
308
|
+
return sorted(
|
|
309
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def detect_zlib_versions() -> list[str]:
|
|
314
|
+
"""Detect available zlib versions from zlib.net."""
|
|
315
|
+
url = "https://zlib.net/"
|
|
316
|
+
content = fetch_url_content(url)
|
|
317
|
+
pattern = r"zlib-(\d+\.\d+\.\d+)\.tar\.gz"
|
|
318
|
+
matches = re.findall(pattern, content)
|
|
319
|
+
return sorted(
|
|
320
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def detect_bzip2_versions() -> list[str]:
|
|
325
|
+
"""Detect available bzip2 versions from sourceware.org."""
|
|
326
|
+
url = "https://sourceware.org/pub/bzip2/"
|
|
327
|
+
content = fetch_url_content(url)
|
|
328
|
+
pattern = r"bzip2-(\d+\.\d+\.\d+)\.tar\.gz"
|
|
329
|
+
matches = re.findall(pattern, content)
|
|
330
|
+
return sorted(
|
|
331
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def detect_ncurses_versions() -> list[str]:
|
|
336
|
+
"""Detect available ncurses versions from GNU mirrors."""
|
|
337
|
+
url = "https://mirrors.ocf.berkeley.edu/gnu/ncurses/"
|
|
338
|
+
content = fetch_url_content(url)
|
|
339
|
+
pattern = r"ncurses-(\d+\.\d+)\.tar\.gz"
|
|
340
|
+
matches = re.findall(pattern, content)
|
|
341
|
+
return sorted(
|
|
342
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def detect_readline_versions() -> list[str]:
|
|
347
|
+
"""Detect available readline versions from GNU mirrors."""
|
|
348
|
+
url = "https://mirrors.ocf.berkeley.edu/gnu/readline/"
|
|
349
|
+
content = fetch_url_content(url)
|
|
350
|
+
pattern = r"readline-(\d+\.\d+)\.tar\.gz"
|
|
351
|
+
matches = re.findall(pattern, content)
|
|
352
|
+
return sorted(
|
|
353
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def detect_gdbm_versions() -> list[str]:
|
|
358
|
+
"""Detect available gdbm versions from GNU mirrors."""
|
|
359
|
+
url = "https://mirrors.ocf.berkeley.edu/gnu/gdbm/"
|
|
360
|
+
content = fetch_url_content(url)
|
|
361
|
+
pattern = r"gdbm-(\d+\.\d+)\.tar\.gz"
|
|
362
|
+
matches = re.findall(pattern, content)
|
|
363
|
+
return sorted(
|
|
364
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def detect_libxcrypt_versions() -> list[str]:
|
|
369
|
+
"""Detect available libxcrypt versions from GitHub releases."""
|
|
370
|
+
url = "https://github.com/besser82/libxcrypt/tags"
|
|
371
|
+
content = fetch_url_content(url)
|
|
372
|
+
pattern = r'v(\d+\.\d+\.\d+)"'
|
|
373
|
+
matches = re.findall(pattern, content)
|
|
374
|
+
return sorted(
|
|
375
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def detect_krb5_versions() -> list[str]:
|
|
380
|
+
"""Detect available krb5 versions from kerberos.org."""
|
|
381
|
+
url = "https://kerberos.org/dist/krb5/"
|
|
382
|
+
content = fetch_url_content(url)
|
|
383
|
+
# krb5 versions are like 1.22/
|
|
384
|
+
pattern = r"(\d+\.\d+)/"
|
|
385
|
+
matches = re.findall(pattern, content)
|
|
386
|
+
return sorted(
|
|
387
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def detect_uuid_versions() -> list[str]:
|
|
392
|
+
"""Detect available libuuid versions from SourceForge."""
|
|
393
|
+
url = "https://sourceforge.net/projects/libuuid/files/"
|
|
394
|
+
content = fetch_url_content(url)
|
|
395
|
+
pattern = r"libuuid-(\d+\.\d+\.\d+)\.tar\.gz"
|
|
396
|
+
matches = re.findall(pattern, content)
|
|
397
|
+
return sorted(
|
|
398
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def detect_tirpc_versions() -> list[str]:
|
|
403
|
+
"""Detect available libtirpc versions from SourceForge."""
|
|
404
|
+
url = "https://sourceforge.net/projects/libtirpc/files/libtirpc/"
|
|
405
|
+
content = fetch_url_content(url)
|
|
406
|
+
pattern = r"(\d+\.\d+\.\d+)/"
|
|
407
|
+
matches = re.findall(pattern, content)
|
|
408
|
+
return sorted(
|
|
409
|
+
set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def detect_expat_versions() -> list[str]:
|
|
414
|
+
"""Detect available expat versions from GitHub releases."""
|
|
415
|
+
url = "https://github.com/libexpat/libexpat/tags"
|
|
416
|
+
content = fetch_url_content(url)
|
|
417
|
+
# Expat versions are tagged like R_2_7_3
|
|
418
|
+
pattern = r'R_(\d+)_(\d+)_(\d+)"'
|
|
419
|
+
matches = re.findall(pattern, content)
|
|
420
|
+
# Convert R_2_7_3 to 2.7.3
|
|
421
|
+
versions = [f"{m[0]}.{m[1]}.{m[2]}" for m in matches]
|
|
422
|
+
return sorted(
|
|
423
|
+
set(versions), key=lambda v: [int(x) for x in v.split(".")], reverse=True
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def update_dependency_versions(
|
|
428
|
+
path: pathlib.Path, deps_to_update: list[str] | None = None
|
|
429
|
+
) -> None:
|
|
430
|
+
"""
|
|
431
|
+
Update dependency versions in python-versions.json.
|
|
432
|
+
|
|
433
|
+
Downloads tarballs, computes SHA-256, and updates the JSON file.
|
|
434
|
+
|
|
435
|
+
:param path: Path to python-versions.json
|
|
436
|
+
:param deps_to_update: List of dependencies to update (openssl, sqlite, xz), or None for all
|
|
437
|
+
"""
|
|
438
|
+
cwd = os.getcwd()
|
|
439
|
+
|
|
440
|
+
# Read existing data
|
|
441
|
+
if path.exists():
|
|
442
|
+
all_data = json.loads(path.read_text())
|
|
443
|
+
if "python" in all_data:
|
|
444
|
+
pydata = all_data["python"]
|
|
445
|
+
dependencies = all_data.get("dependencies", {})
|
|
446
|
+
else:
|
|
447
|
+
# Old format
|
|
448
|
+
pydata = all_data
|
|
449
|
+
dependencies = {}
|
|
450
|
+
else:
|
|
451
|
+
pydata = {}
|
|
452
|
+
dependencies = {}
|
|
453
|
+
|
|
454
|
+
# Determine which dependencies to update
|
|
455
|
+
if deps_to_update is None:
|
|
456
|
+
# By default, update commonly-changed dependencies
|
|
457
|
+
# Full list: openssl, sqlite, xz, libffi, zlib, bzip2, ncurses,
|
|
458
|
+
# readline, gdbm, libxcrypt, krb5, uuid, tirpc, expat
|
|
459
|
+
deps_to_update = [
|
|
460
|
+
"openssl",
|
|
461
|
+
"sqlite",
|
|
462
|
+
"xz",
|
|
463
|
+
"libffi",
|
|
464
|
+
"zlib",
|
|
465
|
+
"ncurses",
|
|
466
|
+
"readline",
|
|
467
|
+
"gdbm",
|
|
468
|
+
"libxcrypt",
|
|
469
|
+
"krb5",
|
|
470
|
+
"bzip2",
|
|
471
|
+
"uuid",
|
|
472
|
+
"tirpc",
|
|
473
|
+
"expat",
|
|
474
|
+
]
|
|
475
|
+
|
|
476
|
+
# Update OpenSSL
|
|
477
|
+
if "openssl" in deps_to_update:
|
|
478
|
+
print("Checking OpenSSL versions...")
|
|
479
|
+
openssl_versions = detect_openssl_versions()
|
|
480
|
+
if openssl_versions:
|
|
481
|
+
latest = openssl_versions[0]
|
|
482
|
+
print(f"Latest OpenSSL: {latest}")
|
|
483
|
+
if "openssl" not in dependencies:
|
|
484
|
+
dependencies["openssl"] = {}
|
|
485
|
+
if latest not in dependencies["openssl"]:
|
|
486
|
+
url = f"https://github.com/openssl/openssl/releases/download/openssl-{latest}/openssl-{latest}.tar.gz"
|
|
487
|
+
print(f"Downloading {url}...")
|
|
488
|
+
download_path = download_url(url, cwd)
|
|
489
|
+
checksum = sha256_digest(download_path)
|
|
490
|
+
print(f"SHA-256: {checksum}")
|
|
491
|
+
url_template = (
|
|
492
|
+
"https://github.com/openssl/openssl/releases/download/"
|
|
493
|
+
"openssl-{version}/openssl-{version}.tar.gz"
|
|
494
|
+
)
|
|
495
|
+
dependencies["openssl"][latest] = {
|
|
496
|
+
"url": url_template,
|
|
497
|
+
"sha256": checksum,
|
|
498
|
+
"platforms": ["linux", "darwin"],
|
|
499
|
+
}
|
|
500
|
+
# Clean up download
|
|
501
|
+
os.remove(download_path)
|
|
502
|
+
|
|
503
|
+
# Update SQLite
|
|
504
|
+
if "sqlite" in deps_to_update:
|
|
505
|
+
print("Checking SQLite versions...")
|
|
506
|
+
sqlite_versions = detect_sqlite_versions()
|
|
507
|
+
if sqlite_versions:
|
|
508
|
+
latest_version, latest_sqliteversion = sqlite_versions[0]
|
|
509
|
+
print(
|
|
510
|
+
f"Latest SQLite: {latest_version} (sqlite version {latest_sqliteversion})"
|
|
511
|
+
)
|
|
512
|
+
if "sqlite" not in dependencies:
|
|
513
|
+
dependencies["sqlite"] = {}
|
|
514
|
+
if latest_version not in dependencies["sqlite"]:
|
|
515
|
+
# SQLite URLs include year, try current year
|
|
516
|
+
import datetime
|
|
517
|
+
|
|
518
|
+
year = datetime.datetime.now().year
|
|
519
|
+
url = f"https://sqlite.org/{year}/sqlite-autoconf-{latest_sqliteversion}.tar.gz"
|
|
520
|
+
print(f"Downloading {url}...")
|
|
521
|
+
try:
|
|
522
|
+
download_path = download_url(url, cwd)
|
|
523
|
+
checksum = sha256_digest(download_path)
|
|
524
|
+
print(f"SHA-256: {checksum}")
|
|
525
|
+
# Store URL with actual year and {version} placeholder (not {sqliteversion})
|
|
526
|
+
# The build scripts pass sqliteversion value as "version" parameter
|
|
527
|
+
dependencies["sqlite"][latest_version] = {
|
|
528
|
+
"url": f"https://sqlite.org/{year}/sqlite-autoconf-{{version}}.tar.gz",
|
|
529
|
+
"sha256": checksum,
|
|
530
|
+
"sqliteversion": latest_sqliteversion,
|
|
531
|
+
"platforms": ["linux", "darwin", "win32"],
|
|
532
|
+
}
|
|
533
|
+
# Clean up download
|
|
534
|
+
os.remove(download_path)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
print(f"Failed to download SQLite: {e}")
|
|
537
|
+
|
|
538
|
+
# Update XZ
|
|
539
|
+
if "xz" in deps_to_update:
|
|
540
|
+
print("Checking XZ versions...")
|
|
541
|
+
xz_versions = detect_xz_versions()
|
|
542
|
+
if xz_versions:
|
|
543
|
+
latest = xz_versions[0]
|
|
544
|
+
print(f"Latest XZ: {latest}")
|
|
545
|
+
if "xz" not in dependencies:
|
|
546
|
+
dependencies["xz"] = {}
|
|
547
|
+
if latest not in dependencies["xz"]:
|
|
548
|
+
url = f"http://tukaani.org/xz/xz-{latest}.tar.gz"
|
|
549
|
+
print(f"Downloading {url}...")
|
|
550
|
+
download_path = download_url(url, cwd)
|
|
551
|
+
checksum = sha256_digest(download_path)
|
|
552
|
+
print(f"SHA-256: {checksum}")
|
|
553
|
+
dependencies["xz"][latest] = {
|
|
554
|
+
"url": "http://tukaani.org/xz/xz-{version}.tar.gz",
|
|
555
|
+
"sha256": checksum,
|
|
556
|
+
"platforms": ["linux", "darwin", "win32"],
|
|
557
|
+
}
|
|
558
|
+
# Clean up download
|
|
559
|
+
os.remove(download_path)
|
|
560
|
+
|
|
561
|
+
# Update libffi
|
|
562
|
+
if "libffi" in deps_to_update:
|
|
563
|
+
print("Checking libffi versions...")
|
|
564
|
+
libffi_versions = detect_libffi_versions()
|
|
565
|
+
if libffi_versions:
|
|
566
|
+
latest = libffi_versions[0]
|
|
567
|
+
print(f"Latest libffi: {latest}")
|
|
568
|
+
if "libffi" not in dependencies:
|
|
569
|
+
dependencies["libffi"] = {}
|
|
570
|
+
if latest not in dependencies["libffi"]:
|
|
571
|
+
url = f"https://github.com/libffi/libffi/releases/download/v{latest}/libffi-{latest}.tar.gz"
|
|
572
|
+
print(f"Downloading {url}...")
|
|
573
|
+
try:
|
|
574
|
+
download_path = download_url(url, cwd)
|
|
575
|
+
checksum = sha256_digest(download_path)
|
|
576
|
+
print(f"SHA-256: {checksum}")
|
|
577
|
+
dependencies["libffi"][latest] = {
|
|
578
|
+
"url": "https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz",
|
|
579
|
+
"sha256": checksum,
|
|
580
|
+
"platforms": ["linux"],
|
|
581
|
+
}
|
|
582
|
+
os.remove(download_path)
|
|
583
|
+
except Exception as e:
|
|
584
|
+
print(f"Failed to download libffi: {e}")
|
|
585
|
+
|
|
586
|
+
# Update zlib
|
|
587
|
+
if "zlib" in deps_to_update:
|
|
588
|
+
print("Checking zlib versions...")
|
|
589
|
+
zlib_versions = detect_zlib_versions()
|
|
590
|
+
if zlib_versions:
|
|
591
|
+
latest = zlib_versions[0]
|
|
592
|
+
print(f"Latest zlib: {latest}")
|
|
593
|
+
if "zlib" not in dependencies:
|
|
594
|
+
dependencies["zlib"] = {}
|
|
595
|
+
if latest not in dependencies["zlib"]:
|
|
596
|
+
url = f"https://zlib.net/fossils/zlib-{latest}.tar.gz"
|
|
597
|
+
print(f"Downloading {url}...")
|
|
598
|
+
try:
|
|
599
|
+
download_path = download_url(url, cwd)
|
|
600
|
+
checksum = sha256_digest(download_path)
|
|
601
|
+
print(f"SHA-256: {checksum}")
|
|
602
|
+
dependencies["zlib"][latest] = {
|
|
603
|
+
"url": "https://zlib.net/fossils/zlib-{version}.tar.gz",
|
|
604
|
+
"sha256": checksum,
|
|
605
|
+
"platforms": ["linux"],
|
|
606
|
+
}
|
|
607
|
+
os.remove(download_path)
|
|
608
|
+
except Exception as e:
|
|
609
|
+
print(f"Failed to download zlib: {e}")
|
|
610
|
+
|
|
611
|
+
# Update ncurses
|
|
612
|
+
if "ncurses" in deps_to_update:
|
|
613
|
+
print("Checking ncurses versions...")
|
|
614
|
+
ncurses_versions = detect_ncurses_versions()
|
|
615
|
+
if ncurses_versions:
|
|
616
|
+
latest = ncurses_versions[0]
|
|
617
|
+
print(f"Latest ncurses: {latest}")
|
|
618
|
+
if "ncurses" not in dependencies:
|
|
619
|
+
dependencies["ncurses"] = {}
|
|
620
|
+
if latest not in dependencies["ncurses"]:
|
|
621
|
+
url = f"https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{latest}.tar.gz"
|
|
622
|
+
print(f"Downloading {url}...")
|
|
623
|
+
try:
|
|
624
|
+
download_path = download_url(url, cwd)
|
|
625
|
+
checksum = sha256_digest(download_path)
|
|
626
|
+
print(f"SHA-256: {checksum}")
|
|
627
|
+
dependencies["ncurses"][latest] = {
|
|
628
|
+
"url": "https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{version}.tar.gz",
|
|
629
|
+
"sha256": checksum,
|
|
630
|
+
"platforms": ["linux"],
|
|
631
|
+
}
|
|
632
|
+
os.remove(download_path)
|
|
633
|
+
except Exception as e:
|
|
634
|
+
print(f"Failed to download ncurses: {e}")
|
|
635
|
+
|
|
636
|
+
# Update readline
|
|
637
|
+
if "readline" in deps_to_update:
|
|
638
|
+
print("Checking readline versions...")
|
|
639
|
+
readline_versions = detect_readline_versions()
|
|
640
|
+
if readline_versions:
|
|
641
|
+
latest = readline_versions[0]
|
|
642
|
+
print(f"Latest readline: {latest}")
|
|
643
|
+
if "readline" not in dependencies:
|
|
644
|
+
dependencies["readline"] = {}
|
|
645
|
+
if latest not in dependencies["readline"]:
|
|
646
|
+
url = f"https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{latest}.tar.gz"
|
|
647
|
+
print(f"Downloading {url}...")
|
|
648
|
+
try:
|
|
649
|
+
download_path = download_url(url, cwd)
|
|
650
|
+
checksum = sha256_digest(download_path)
|
|
651
|
+
print(f"SHA-256: {checksum}")
|
|
652
|
+
dependencies["readline"][latest] = {
|
|
653
|
+
"url": "https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{version}.tar.gz",
|
|
654
|
+
"sha256": checksum,
|
|
655
|
+
"platforms": ["linux"],
|
|
656
|
+
}
|
|
657
|
+
os.remove(download_path)
|
|
658
|
+
except Exception as e:
|
|
659
|
+
print(f"Failed to download readline: {e}")
|
|
660
|
+
|
|
661
|
+
# Update gdbm
|
|
662
|
+
if "gdbm" in deps_to_update:
|
|
663
|
+
print("Checking gdbm versions...")
|
|
664
|
+
gdbm_versions = detect_gdbm_versions()
|
|
665
|
+
if gdbm_versions:
|
|
666
|
+
latest = gdbm_versions[0]
|
|
667
|
+
print(f"Latest gdbm: {latest}")
|
|
668
|
+
if "gdbm" not in dependencies:
|
|
669
|
+
dependencies["gdbm"] = {}
|
|
670
|
+
if latest not in dependencies["gdbm"]:
|
|
671
|
+
url = f"https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{latest}.tar.gz"
|
|
672
|
+
print(f"Downloading {url}...")
|
|
673
|
+
try:
|
|
674
|
+
download_path = download_url(url, cwd)
|
|
675
|
+
checksum = sha256_digest(download_path)
|
|
676
|
+
print(f"SHA-256: {checksum}")
|
|
677
|
+
dependencies["gdbm"][latest] = {
|
|
678
|
+
"url": "https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{version}.tar.gz",
|
|
679
|
+
"sha256": checksum,
|
|
680
|
+
"platforms": ["linux"],
|
|
681
|
+
}
|
|
682
|
+
os.remove(download_path)
|
|
683
|
+
except Exception as e:
|
|
684
|
+
print(f"Failed to download gdbm: {e}")
|
|
685
|
+
|
|
686
|
+
# Update libxcrypt
|
|
687
|
+
if "libxcrypt" in deps_to_update:
|
|
688
|
+
print("Checking libxcrypt versions...")
|
|
689
|
+
libxcrypt_versions = detect_libxcrypt_versions()
|
|
690
|
+
if libxcrypt_versions:
|
|
691
|
+
latest = libxcrypt_versions[0]
|
|
692
|
+
print(f"Latest libxcrypt: {latest}")
|
|
693
|
+
if "libxcrypt" not in dependencies:
|
|
694
|
+
dependencies["libxcrypt"] = {}
|
|
695
|
+
if latest not in dependencies["libxcrypt"]:
|
|
696
|
+
url = f"https://github.com/besser82/libxcrypt/releases/download/v{latest}/libxcrypt-{latest}.tar.xz"
|
|
697
|
+
print(f"Downloading {url}...")
|
|
698
|
+
try:
|
|
699
|
+
download_path = download_url(url, cwd)
|
|
700
|
+
checksum = sha256_digest(download_path)
|
|
701
|
+
print(f"SHA-256: {checksum}")
|
|
702
|
+
dependencies["libxcrypt"][latest] = {
|
|
703
|
+
"url": (
|
|
704
|
+
"https://github.com/besser82/libxcrypt/releases/"
|
|
705
|
+
"download/v{version}/libxcrypt-{version}.tar.xz"
|
|
706
|
+
),
|
|
707
|
+
"sha256": checksum,
|
|
708
|
+
"platforms": ["linux"],
|
|
709
|
+
}
|
|
710
|
+
os.remove(download_path)
|
|
711
|
+
except Exception as e:
|
|
712
|
+
print(f"Failed to download libxcrypt: {e}")
|
|
713
|
+
|
|
714
|
+
# Update krb5
|
|
715
|
+
if "krb5" in deps_to_update:
|
|
716
|
+
print("Checking krb5 versions...")
|
|
717
|
+
krb5_versions = detect_krb5_versions()
|
|
718
|
+
if krb5_versions:
|
|
719
|
+
latest = krb5_versions[0]
|
|
720
|
+
print(f"Latest krb5: {latest}")
|
|
721
|
+
if "krb5" not in dependencies:
|
|
722
|
+
dependencies["krb5"] = {}
|
|
723
|
+
if latest not in dependencies["krb5"]:
|
|
724
|
+
url = f"https://kerberos.org/dist/krb5/{latest}/krb5-{latest}.tar.gz"
|
|
725
|
+
print(f"Downloading {url}...")
|
|
726
|
+
try:
|
|
727
|
+
download_path = download_url(url, cwd)
|
|
728
|
+
checksum = sha256_digest(download_path)
|
|
729
|
+
print(f"SHA-256: {checksum}")
|
|
730
|
+
dependencies["krb5"][latest] = {
|
|
731
|
+
"url": "https://kerberos.org/dist/krb5/{version}/krb5-{version}.tar.gz",
|
|
732
|
+
"sha256": checksum,
|
|
733
|
+
"platforms": ["linux"],
|
|
734
|
+
}
|
|
735
|
+
os.remove(download_path)
|
|
736
|
+
except Exception as e:
|
|
737
|
+
print(f"Failed to download krb5: {e}")
|
|
738
|
+
|
|
739
|
+
# Update bzip2
|
|
740
|
+
if "bzip2" in deps_to_update:
|
|
741
|
+
print("Checking bzip2 versions...")
|
|
742
|
+
bzip2_versions = detect_bzip2_versions()
|
|
743
|
+
if bzip2_versions:
|
|
744
|
+
latest = bzip2_versions[0]
|
|
745
|
+
print(f"Latest bzip2: {latest}")
|
|
746
|
+
if "bzip2" not in dependencies:
|
|
747
|
+
dependencies["bzip2"] = {}
|
|
748
|
+
if latest not in dependencies["bzip2"]:
|
|
749
|
+
url = f"https://sourceware.org/pub/bzip2/bzip2-{latest}.tar.gz"
|
|
750
|
+
print(f"Downloading {url}...")
|
|
751
|
+
try:
|
|
752
|
+
download_path = download_url(url, cwd)
|
|
753
|
+
checksum = sha256_digest(download_path)
|
|
754
|
+
print(f"SHA-256: {checksum}")
|
|
755
|
+
dependencies["bzip2"][latest] = {
|
|
756
|
+
"url": "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz",
|
|
757
|
+
"sha256": checksum,
|
|
758
|
+
"platforms": ["linux", "darwin"],
|
|
759
|
+
}
|
|
760
|
+
os.remove(download_path)
|
|
761
|
+
except Exception as e:
|
|
762
|
+
print(f"Failed to download bzip2: {e}")
|
|
763
|
+
|
|
764
|
+
# Update uuid
|
|
765
|
+
if "uuid" in deps_to_update:
|
|
766
|
+
print("Checking uuid versions...")
|
|
767
|
+
uuid_versions = detect_uuid_versions()
|
|
768
|
+
if uuid_versions:
|
|
769
|
+
latest = uuid_versions[0]
|
|
770
|
+
print(f"Latest uuid: {latest}")
|
|
771
|
+
if "uuid" not in dependencies:
|
|
772
|
+
dependencies["uuid"] = {}
|
|
773
|
+
if latest not in dependencies["uuid"]:
|
|
774
|
+
url = f"https://sourceforge.net/projects/libuuid/files/libuuid-{latest}.tar.gz"
|
|
775
|
+
print(f"Downloading {url}...")
|
|
776
|
+
try:
|
|
777
|
+
download_path = download_url(url, cwd)
|
|
778
|
+
checksum = sha256_digest(download_path)
|
|
779
|
+
print(f"SHA-256: {checksum}")
|
|
780
|
+
dependencies["uuid"][latest] = {
|
|
781
|
+
"url": "https://sourceforge.net/projects/libuuid/files/libuuid-{version}.tar.gz",
|
|
782
|
+
"sha256": checksum,
|
|
783
|
+
"platforms": ["linux"],
|
|
784
|
+
}
|
|
785
|
+
os.remove(download_path)
|
|
786
|
+
except Exception as e:
|
|
787
|
+
print(f"Failed to download uuid: {e}")
|
|
788
|
+
|
|
789
|
+
# Update tirpc
|
|
790
|
+
if "tirpc" in deps_to_update:
|
|
791
|
+
print("Checking tirpc versions...")
|
|
792
|
+
tirpc_versions = detect_tirpc_versions()
|
|
793
|
+
if tirpc_versions:
|
|
794
|
+
latest = tirpc_versions[0]
|
|
795
|
+
print(f"Latest tirpc: {latest}")
|
|
796
|
+
if "tirpc" not in dependencies:
|
|
797
|
+
dependencies["tirpc"] = {}
|
|
798
|
+
if latest not in dependencies["tirpc"]:
|
|
799
|
+
url = f"https://sourceforge.net/projects/libtirpc/files/libtirpc-{latest}.tar.bz2"
|
|
800
|
+
print(f"Downloading {url}...")
|
|
801
|
+
try:
|
|
802
|
+
download_path = download_url(url, cwd)
|
|
803
|
+
checksum = sha256_digest(download_path)
|
|
804
|
+
print(f"SHA-256: {checksum}")
|
|
805
|
+
dependencies["tirpc"][latest] = {
|
|
806
|
+
"url": "https://sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2",
|
|
807
|
+
"sha256": checksum,
|
|
808
|
+
"platforms": ["linux"],
|
|
809
|
+
}
|
|
810
|
+
os.remove(download_path)
|
|
811
|
+
except Exception as e:
|
|
812
|
+
print(f"Failed to download tirpc: {e}")
|
|
813
|
+
|
|
814
|
+
# Update expat
|
|
815
|
+
if "expat" in deps_to_update:
|
|
816
|
+
print("Checking expat versions...")
|
|
817
|
+
expat_versions = detect_expat_versions()
|
|
818
|
+
if expat_versions:
|
|
819
|
+
latest = expat_versions[0]
|
|
820
|
+
print(f"Latest expat: {latest}")
|
|
821
|
+
if "expat" not in dependencies:
|
|
822
|
+
dependencies["expat"] = {}
|
|
823
|
+
if latest not in dependencies["expat"]:
|
|
824
|
+
# Expat uses R_X_Y_Z format for releases
|
|
825
|
+
version_tag = latest.replace(".", "_")
|
|
826
|
+
url = f"https://github.com/libexpat/libexpat/releases/download/R_{version_tag}/expat-{latest}.tar.xz"
|
|
827
|
+
print(f"Downloading {url}...")
|
|
828
|
+
try:
|
|
829
|
+
download_path = download_url(url, cwd)
|
|
830
|
+
checksum = sha256_digest(download_path)
|
|
831
|
+
print(f"SHA-256: {checksum}")
|
|
832
|
+
# Store URL template with placeholder for version
|
|
833
|
+
# Build scripts will construct actual URL dynamically from version
|
|
834
|
+
dependencies["expat"][latest] = {
|
|
835
|
+
"url": (
|
|
836
|
+
f"https://github.com/libexpat/libexpat/releases/"
|
|
837
|
+
f"download/R_{version_tag}/expat-{{version}}.tar.xz"
|
|
838
|
+
),
|
|
839
|
+
"sha256": checksum,
|
|
840
|
+
"platforms": ["linux", "darwin", "win32"],
|
|
841
|
+
}
|
|
842
|
+
os.remove(download_path)
|
|
843
|
+
except Exception as e:
|
|
844
|
+
print(f"Failed to download expat: {e}")
|
|
845
|
+
|
|
846
|
+
# Write updated data
|
|
847
|
+
all_data = {"python": pydata, "dependencies": dependencies}
|
|
848
|
+
path.write_text(json.dumps(all_data, indent=1))
|
|
849
|
+
print(f"Updated {path}")
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def create_pyversions(path: pathlib.Path) -> None:
|
|
214
853
|
"""
|
|
215
854
|
Create python-versions.json file.
|
|
216
855
|
"""
|
|
@@ -222,15 +861,24 @@ def create_pyversions(path):
|
|
|
222
861
|
versions = [_ for _ in parsed_versions if _.major >= 3]
|
|
223
862
|
|
|
224
863
|
if path.exists():
|
|
225
|
-
|
|
864
|
+
all_data = json.loads(path.read_text())
|
|
865
|
+
# Handle both old format (flat dict) and new format (nested)
|
|
866
|
+
if "python" in all_data:
|
|
867
|
+
pydata = all_data["python"]
|
|
868
|
+
dependencies = all_data.get("dependencies", {})
|
|
869
|
+
else:
|
|
870
|
+
# Old format - convert to new
|
|
871
|
+
pydata = all_data
|
|
872
|
+
dependencies = {}
|
|
226
873
|
else:
|
|
227
|
-
|
|
874
|
+
pydata = {}
|
|
875
|
+
dependencies = {}
|
|
228
876
|
|
|
229
877
|
for version in versions:
|
|
230
878
|
if version >= Version("3.14"):
|
|
231
879
|
continue
|
|
232
880
|
|
|
233
|
-
if str(version) in
|
|
881
|
+
if str(version) in pydata:
|
|
234
882
|
continue
|
|
235
883
|
|
|
236
884
|
if version <= Version("3.2") and version.micro == 0:
|
|
@@ -246,23 +894,34 @@ def create_pyversions(path):
|
|
|
246
894
|
verified = verify_signature(download_path, sig_path)
|
|
247
895
|
if verified:
|
|
248
896
|
print(f"Version {version} has digest {digest(download_path)}")
|
|
249
|
-
|
|
897
|
+
pydata[str(version)] = digest(download_path)
|
|
250
898
|
else:
|
|
251
899
|
raise Exception("Signature failed to verify: {url}")
|
|
252
900
|
|
|
253
|
-
|
|
901
|
+
# Write in new structured format
|
|
902
|
+
all_data = {"python": pydata, "dependencies": dependencies}
|
|
903
|
+
path.write_text(json.dumps(all_data, indent=1))
|
|
254
904
|
|
|
255
|
-
#
|
|
256
|
-
|
|
905
|
+
# Final write in new structured format
|
|
906
|
+
all_data = {"python": pydata, "dependencies": dependencies}
|
|
907
|
+
path.write_text(json.dumps(all_data, indent=1))
|
|
257
908
|
|
|
258
909
|
|
|
259
|
-
def python_versions(
|
|
910
|
+
def python_versions(
|
|
911
|
+
minor: str | None = None,
|
|
912
|
+
*,
|
|
913
|
+
create: bool = False,
|
|
914
|
+
update: bool = False,
|
|
915
|
+
) -> dict[Version, str]:
|
|
260
916
|
"""
|
|
261
917
|
List python versions.
|
|
262
918
|
"""
|
|
263
919
|
packaged = pathlib.Path(__file__).parent / "python-versions.json"
|
|
264
920
|
local = pathlib.Path("~/.local/relenv/python-versions.json")
|
|
265
921
|
|
|
922
|
+
if update:
|
|
923
|
+
create = True
|
|
924
|
+
|
|
266
925
|
if create:
|
|
267
926
|
create_pyversions(packaged)
|
|
268
927
|
|
|
@@ -274,15 +933,23 @@ def python_versions(minor=None, create=False, update=False):
|
|
|
274
933
|
readfrom = packaged
|
|
275
934
|
else:
|
|
276
935
|
raise RuntimeError("No versions file found")
|
|
277
|
-
|
|
936
|
+
data = json.loads(readfrom.read_text())
|
|
937
|
+
# Handle both old format (flat dict) and new format (nested with "python" key)
|
|
938
|
+
pyversions = (
|
|
939
|
+
data.get("python", data)
|
|
940
|
+
if isinstance(data, dict) and "python" in data
|
|
941
|
+
else data
|
|
942
|
+
)
|
|
278
943
|
versions = [Version(_) for _ in pyversions]
|
|
279
944
|
if minor:
|
|
280
945
|
mv = Version(minor)
|
|
281
946
|
versions = [_ for _ in versions if _.major == mv.major and _.minor == mv.minor]
|
|
282
|
-
return {
|
|
947
|
+
return {version: pyversions[str(version)] for version in versions}
|
|
283
948
|
|
|
284
949
|
|
|
285
|
-
def setup_parser(
|
|
950
|
+
def setup_parser(
|
|
951
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
952
|
+
) -> None:
|
|
286
953
|
"""
|
|
287
954
|
Setup the subparser for the ``versions`` command.
|
|
288
955
|
|
|
@@ -314,12 +981,132 @@ def setup_parser(subparsers):
|
|
|
314
981
|
type=str,
|
|
315
982
|
help="The python version [default: %(default)s]",
|
|
316
983
|
)
|
|
984
|
+
subparser.add_argument(
|
|
985
|
+
"--check-deps",
|
|
986
|
+
default=False,
|
|
987
|
+
action="store_true",
|
|
988
|
+
help="Check for new dependency versions",
|
|
989
|
+
)
|
|
990
|
+
subparser.add_argument(
|
|
991
|
+
"--update-deps",
|
|
992
|
+
default=False,
|
|
993
|
+
action="store_true",
|
|
994
|
+
help="Update dependency versions (downloads and computes checksums)",
|
|
995
|
+
)
|
|
317
996
|
|
|
318
997
|
|
|
319
|
-
def main(args):
|
|
998
|
+
def main(args: argparse.Namespace) -> None:
|
|
320
999
|
"""
|
|
321
1000
|
Versions utility main method.
|
|
322
1001
|
"""
|
|
1002
|
+
packaged = pathlib.Path(__file__).parent / "python-versions.json"
|
|
1003
|
+
|
|
1004
|
+
# Handle dependency operations
|
|
1005
|
+
if args.check_deps:
|
|
1006
|
+
print("Checking for new dependency versions...\n")
|
|
1007
|
+
|
|
1008
|
+
# Load current versions from JSON
|
|
1009
|
+
with open(packaged) as f:
|
|
1010
|
+
data = json.load(f)
|
|
1011
|
+
|
|
1012
|
+
current_deps = data.get("dependencies", {})
|
|
1013
|
+
updates_available = []
|
|
1014
|
+
up_to_date = []
|
|
1015
|
+
|
|
1016
|
+
# Detect terminal capabilities for fancy vs ASCII output
|
|
1017
|
+
use_unicode = True
|
|
1018
|
+
if sys.platform == "win32":
|
|
1019
|
+
# Check if we're in a modern terminal that supports Unicode
|
|
1020
|
+
import os
|
|
1021
|
+
|
|
1022
|
+
# Windows Terminal and modern PowerShell support Unicode
|
|
1023
|
+
wt_session = os.environ.get("WT_SESSION")
|
|
1024
|
+
term_program = os.environ.get("TERM_PROGRAM")
|
|
1025
|
+
if not wt_session and not term_program:
|
|
1026
|
+
# Likely cmd.exe or old PowerShell, use ASCII
|
|
1027
|
+
use_unicode = False
|
|
1028
|
+
|
|
1029
|
+
if use_unicode:
|
|
1030
|
+
ok_symbol = "✓"
|
|
1031
|
+
update_symbol = "⚠"
|
|
1032
|
+
new_symbol = "✗"
|
|
1033
|
+
arrow = "→"
|
|
1034
|
+
else:
|
|
1035
|
+
ok_symbol = "[OK] "
|
|
1036
|
+
update_symbol = "[UPDATE]"
|
|
1037
|
+
new_symbol = "[NEW] "
|
|
1038
|
+
arrow = "->"
|
|
1039
|
+
|
|
1040
|
+
# Check each dependency
|
|
1041
|
+
checks = [
|
|
1042
|
+
("openssl", "OpenSSL", detect_openssl_versions),
|
|
1043
|
+
("sqlite", "SQLite", detect_sqlite_versions),
|
|
1044
|
+
("xz", "XZ", detect_xz_versions),
|
|
1045
|
+
("libffi", "libffi", detect_libffi_versions),
|
|
1046
|
+
("zlib", "zlib", detect_zlib_versions),
|
|
1047
|
+
("ncurses", "ncurses", detect_ncurses_versions),
|
|
1048
|
+
("readline", "readline", detect_readline_versions),
|
|
1049
|
+
("gdbm", "gdbm", detect_gdbm_versions),
|
|
1050
|
+
("libxcrypt", "libxcrypt", detect_libxcrypt_versions),
|
|
1051
|
+
("krb5", "krb5", detect_krb5_versions),
|
|
1052
|
+
("bzip2", "bzip2", detect_bzip2_versions),
|
|
1053
|
+
("uuid", "uuid", detect_uuid_versions),
|
|
1054
|
+
("tirpc", "tirpc", detect_tirpc_versions),
|
|
1055
|
+
("expat", "expat", detect_expat_versions),
|
|
1056
|
+
]
|
|
1057
|
+
|
|
1058
|
+
for dep_key, dep_name, detect_func in checks:
|
|
1059
|
+
detected = detect_func()
|
|
1060
|
+
if not detected:
|
|
1061
|
+
continue
|
|
1062
|
+
|
|
1063
|
+
# Handle SQLite's tuple return
|
|
1064
|
+
if dep_key == "sqlite":
|
|
1065
|
+
latest_version = detected[0][0] # type: ignore[index]
|
|
1066
|
+
else:
|
|
1067
|
+
latest_version = detected[0] # type: ignore[index]
|
|
1068
|
+
|
|
1069
|
+
# Get current version from JSON
|
|
1070
|
+
current_version = None
|
|
1071
|
+
if dep_key in current_deps:
|
|
1072
|
+
versions = sorted(current_deps[dep_key].keys(), reverse=True)
|
|
1073
|
+
if versions:
|
|
1074
|
+
current_version = versions[0]
|
|
1075
|
+
|
|
1076
|
+
# Compare versions
|
|
1077
|
+
if current_version == latest_version:
|
|
1078
|
+
print(
|
|
1079
|
+
f"{ok_symbol} {dep_name:12} {current_version:15} " f"(up-to-date)"
|
|
1080
|
+
)
|
|
1081
|
+
up_to_date.append(dep_name)
|
|
1082
|
+
elif current_version:
|
|
1083
|
+
print(
|
|
1084
|
+
f"{update_symbol} {dep_name:12} {current_version:15} "
|
|
1085
|
+
f"{arrow} {latest_version} (update available)"
|
|
1086
|
+
)
|
|
1087
|
+
updates_available.append((dep_name, current_version, latest_version))
|
|
1088
|
+
else:
|
|
1089
|
+
print(
|
|
1090
|
+
f"{new_symbol} {dep_name:12} {'(not tracked)':15} "
|
|
1091
|
+
f"{arrow} {latest_version}"
|
|
1092
|
+
)
|
|
1093
|
+
updates_available.append((dep_name, None, latest_version))
|
|
1094
|
+
|
|
1095
|
+
# Summary
|
|
1096
|
+
print(f"\n{'=' * 60}")
|
|
1097
|
+
print(f"Summary: {len(up_to_date)} up-to-date, ", end="")
|
|
1098
|
+
print(f"{len(updates_available)} updates available")
|
|
1099
|
+
|
|
1100
|
+
if updates_available:
|
|
1101
|
+
print("\nTo update dependencies, run:")
|
|
1102
|
+
print(" python3 -m relenv versions --update-deps")
|
|
1103
|
+
|
|
1104
|
+
sys.exit(0)
|
|
1105
|
+
|
|
1106
|
+
if args.update_deps:
|
|
1107
|
+
update_dependency_versions(packaged)
|
|
1108
|
+
sys.exit(0)
|
|
1109
|
+
|
|
323
1110
|
if args.update:
|
|
324
1111
|
python_versions(create=True)
|
|
325
1112
|
if args.list:
|