Skip to content

Commit 7c30674

Browse files
committed
Extend pyversions to include dependencies
Relenv has been tracking what python verions are available for some time now. It'll be great to track dependency versions too, this will reduce the burdon of maintaining things movnig forward.
1 parent 9678e58 commit 7c30674

File tree

8 files changed

+1666
-253
lines changed

8 files changed

+1666
-253
lines changed

relenv/build/common.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,14 @@ def print_ui(
204204

205205
def verify_checksum(file: PathLike, checksum: Optional[str]) -> bool:
206206
"""
207-
Verify the checksum of a files.
207+
Verify the checksum of a file.
208+
209+
Supports both SHA-1 (40 hex chars) and SHA-256 (64 hex chars) checksums.
210+
The hash algorithm is auto-detected based on checksum length.
208211
209212
:param file: The path to the file to check.
210213
:type file: str
211-
:param checksum: The checksum to verify against
214+
:param checksum: The checksum to verify against (SHA-1 or SHA-256)
212215
:type checksum: str
213216
214217
:raises RelenvException: If the checksum verification failed
@@ -219,11 +222,26 @@ def verify_checksum(file: PathLike, checksum: Optional[str]) -> bool:
219222
if checksum is None:
220223
log.error("Can't verify checksum because none was given")
221224
return False
225+
226+
# Auto-detect hash type based on length
227+
# SHA-1: 40 hex chars, SHA-256: 64 hex chars
228+
if len(checksum) == 64:
229+
hash_algo = hashlib.sha256()
230+
hash_name = "sha256"
231+
elif len(checksum) == 40:
232+
hash_algo = hashlib.sha1()
233+
hash_name = "sha1"
234+
else:
235+
raise RelenvException(
236+
f"Invalid checksum length {len(checksum)}. Expected 40 (SHA-1) or 64 (SHA-256)"
237+
)
238+
222239
with open(file, "rb") as fp:
223-
file_checksum = hashlib.sha1(fp.read()).hexdigest()
240+
hash_algo.update(fp.read())
241+
file_checksum = hash_algo.hexdigest()
224242
if checksum != file_checksum:
225243
raise RelenvException(
226-
f"sha1 checksum verification failed. expected={checksum} found={file_checksum}"
244+
f"{hash_name} checksum verification failed. expected={checksum} found={file_checksum}"
227245
)
228246
return True
229247

@@ -541,6 +559,52 @@ def uuid_version(href: str) -> Optional[str]:
541559
return None
542560

543561

562+
def get_dependency_version(name: str, platform: str) -> Optional[Dict[str, str]]:
563+
"""
564+
Get dependency version and metadata from python-versions.json.
565+
566+
Returns dict with keys: version, url, sha256, and any extra fields (e.g., sqliteversion)
567+
Returns None if dependency not found.
568+
569+
:param name: Dependency name (openssl, sqlite, xz)
570+
:param platform: Platform name (linux, darwin, win32)
571+
:return: Dict with version, url, sha256, and extra fields, or None
572+
"""
573+
versions_file = MODULE_DIR / "python-versions.json"
574+
if not versions_file.exists():
575+
return None
576+
577+
import json
578+
579+
data = json.loads(versions_file.read_text())
580+
dependencies = data.get("dependencies", {})
581+
582+
if name not in dependencies:
583+
return None
584+
585+
# Get the latest version for this dependency that supports the platform
586+
dep_versions = dependencies[name]
587+
for version, info in sorted(
588+
dep_versions.items(),
589+
key=lambda x: [int(n) for n in x[0].split(".")],
590+
reverse=True,
591+
):
592+
if platform in info.get("platforms", []):
593+
# Build result dict with version, url, sha256, and any extra fields
594+
result = {
595+
"version": version,
596+
"url": info["url"],
597+
"sha256": info.get("sha256", ""),
598+
}
599+
# Add any extra fields (like sqliteversion for SQLite)
600+
for key, value in info.items():
601+
if key not in ["url", "sha256", "platforms"]:
602+
result[key] = value
603+
return result
604+
605+
return None
606+
607+
544608
def parse_links(text: str) -> List[str]:
545609
class HrefParser(HTMLParser):
546610
def __init__(self) -> None:

relenv/build/darwin.py

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@
1010
from typing import IO, MutableMapping
1111

1212
from ..common import DARWIN, MACOS_DEVELOPMENT_TARGET, arches
13-
from .common import Dirs, build_openssl, build_sqlite, builds, finalize, runcmd
13+
from .common import (
14+
Dirs,
15+
build_openssl,
16+
build_sqlite,
17+
builds,
18+
finalize,
19+
get_dependency_version,
20+
runcmd,
21+
)
1422

1523
ARCHES = arches[DARWIN]
1624

@@ -38,6 +46,54 @@ def populate_env(env: MutableMapping[str, str], dirs: Dirs) -> None:
3846
env["CFLAGS"] = " ".join(cflags).format(prefix=dirs.prefix)
3947

4048

49+
def update_expat(dirs: Dirs, env: MutableMapping[str, str]) -> None:
50+
"""
51+
Update the bundled expat library to the latest version.
52+
53+
Python ships with an older bundled expat. This function updates it
54+
to the latest version for security and bug fixes.
55+
"""
56+
import pathlib
57+
import shutil
58+
import glob
59+
import urllib.request
60+
import tarfile
61+
62+
# Get version from JSON
63+
expat_info = get_dependency_version("expat", "darwin")
64+
if not expat_info:
65+
# No update needed, use bundled version
66+
return
67+
68+
version = expat_info["version"]
69+
version_tag = version.replace(".", "_")
70+
url = f"https://github.com/libexpat/libexpat/releases/download/R_{version_tag}/expat-{version}.tar.xz"
71+
72+
expat_dir = pathlib.Path(dirs.source) / "Modules" / "expat"
73+
if not expat_dir.exists():
74+
# No expat directory, skip
75+
return
76+
77+
# Download expat tarball
78+
tmpbuild = pathlib.Path(dirs.tmpbuild)
79+
tarball_path = tmpbuild / f"expat-{version}.tar.xz"
80+
urllib.request.urlretrieve(url, str(tarball_path))
81+
82+
# Extract tarball
83+
with tarfile.open(tarball_path) as tar:
84+
tar.extractall(path=str(tmpbuild))
85+
86+
# Copy source files to Modules/expat/
87+
expat_source_dir = tmpbuild / f"expat-{version}" / "lib"
88+
for source_file in ["*.h", "*.c"]:
89+
for file_path in glob.glob(str(expat_source_dir / source_file)):
90+
target_file = expat_dir / pathlib.Path(file_path).name
91+
# Remove old file if it exists
92+
if target_file.exists():
93+
target_file.unlink()
94+
shutil.copy2(file_path, str(expat_dir))
95+
96+
4197
def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> None:
4298
"""
4399
Run the commands to build Python.
@@ -49,6 +105,9 @@ def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> N
49105
:param logfp: A handle for the log file
50106
:type logfp: file
51107
"""
108+
# Update bundled expat to latest version
109+
update_expat(dirs, env)
110+
52111
env["LDFLAGS"] = "-Wl,-rpath,{prefix}/lib {ldflags}".format(
53112
prefix=dirs.prefix, ldflags=env["LDFLAGS"]
54113
)
@@ -77,33 +136,66 @@ def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> N
77136

78137
build = builds.add("darwin", populate_env=populate_env)
79138

139+
# Get dependency versions from JSON (with fallback to hardcoded values)
140+
openssl_info = get_dependency_version("openssl", "darwin")
141+
if openssl_info:
142+
openssl_version = openssl_info["version"]
143+
openssl_url = openssl_info["url"]
144+
openssl_checksum = openssl_info["sha256"]
145+
else:
146+
openssl_version = "3.5.4"
147+
openssl_url = "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz"
148+
openssl_checksum = "b75daac8e10f189abe28a076ba5905d363e4801f"
149+
80150
build.add(
81151
"openssl",
82152
build_func=build_openssl,
83153
download={
84-
"url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz",
85-
"version": "3.5.4",
86-
"checksum": "b75daac8e10f189abe28a076ba5905d363e4801f",
154+
"url": openssl_url,
155+
"version": openssl_version,
156+
"checksum": openssl_checksum,
87157
},
88158
)
89159

160+
# Get XZ version from JSON
161+
xz_info = get_dependency_version("xz", "darwin")
162+
if xz_info:
163+
xz_version = xz_info["version"]
164+
xz_url = xz_info["url"]
165+
xz_checksum = xz_info["sha256"]
166+
else:
167+
xz_version = "5.8.1"
168+
xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz"
169+
xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931"
170+
90171
build.add(
91172
"XZ",
92173
download={
93-
"url": "http://tukaani.org/xz/xz-{version}.tar.gz",
94-
"version": "5.8.1",
95-
"checksum": "ed4d5589c4cfe84e1697bd02a9954b76af336931",
174+
"url": xz_url,
175+
"version": xz_version,
176+
"checksum": xz_checksum,
96177
},
97178
)
98179

180+
# Get SQLite version from JSON
181+
sqlite_info = get_dependency_version("sqlite", "darwin")
182+
if sqlite_info:
183+
sqlite_url = sqlite_info["url"]
184+
sqlite_checksum = sqlite_info["sha256"]
185+
sqlite_version_num = sqlite_info.get("sqliteversion", "3500400")
186+
else:
187+
sqlite_version_num = "3500400"
188+
sqlite_url = "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz"
189+
sqlite_checksum = "145048005c777796dd8494aa1cfed304e8c34283"
190+
99191
build.add(
100192
name="SQLite",
101193
build_func=build_sqlite,
102194
download={
103-
"url": "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz",
195+
"url": sqlite_url,
104196
"fallback_url": "https://woz.io/relenv/dependencies/sqlite-autoconf-{version}.tar.gz",
105-
"version": "3500400",
106-
"checksum": "145048005c777796dd8494aa1cfed304e8c34283",
197+
"version": sqlite_version_num,
198+
"checksum": sqlite_checksum,
107199
},
108200
)
109201

0 commit comments

Comments
 (0)