relenv 0.22.0__py3-none-any.whl → 0.22.2__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 +1 -1
- relenv/__main__.py +1 -1
- relenv/build/__init__.py +21 -25
- relenv/build/common/__init__.py +1 -1
- relenv/build/common/_sysconfigdata_template.py +1 -1
- relenv/build/common/builder.py +1 -1
- relenv/build/common/builders.py +1 -1
- relenv/build/common/download.py +1 -1
- relenv/build/common/install.py +6 -2
- relenv/build/common/ui.py +1 -1
- relenv/build/darwin.py +1 -1
- relenv/build/linux.py +1 -1
- relenv/build/windows.py +1 -1
- relenv/buildenv.py +1 -1
- relenv/check.py +1 -1
- relenv/common.py +5 -4
- relenv/create.py +19 -27
- relenv/fetch.py +7 -5
- relenv/manifest.py +1 -1
- relenv/python-versions.json +347 -326
- relenv/pyversions.py +49 -1
- relenv/relocate.py +34 -2
- relenv/runtime.py +6 -1
- relenv/toolchain.py +1 -1
- {relenv-0.22.0.dist-info → relenv-0.22.2.dist-info}/METADATA +1 -1
- relenv-0.22.2.dist-info/RECORD +48 -0
- tests/__init__.py +1 -1
- tests/_pytest_typing.py +1 -1
- tests/conftest.py +1 -1
- tests/test_build.py +1 -1
- tests/test_common.py +64 -2
- tests/test_create.py +1 -1
- tests/test_downloads.py +1 -1
- tests/test_fips_photon.py +1 -1
- tests/test_module_imports.py +1 -1
- tests/test_pyversions_runtime.py +78 -1
- tests/test_relocate.py +86 -1
- tests/test_relocate_module.py +1 -1
- tests/test_runtime.py +167 -1
- tests/test_verify_build.py +7 -5
- relenv-0.22.0.dist-info/RECORD +0 -48
- {relenv-0.22.0.dist-info → relenv-0.22.2.dist-info}/WHEEL +0 -0
- {relenv-0.22.0.dist-info → relenv-0.22.2.dist-info}/entry_points.txt +0 -0
- {relenv-0.22.0.dist-info → relenv-0.22.2.dist-info}/licenses/LICENSE.md +0 -0
- {relenv-0.22.0.dist-info → relenv-0.22.2.dist-info}/licenses/NOTICE +0 -0
- {relenv-0.22.0.dist-info → relenv-0.22.2.dist-info}/top_level.txt +0 -0
relenv/pyversions.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
"""
|
|
4
4
|
Versions utility.
|
|
@@ -947,6 +947,54 @@ def python_versions(
|
|
|
947
947
|
return {version: pyversions[str(version)] for version in versions}
|
|
948
948
|
|
|
949
949
|
|
|
950
|
+
def get_default_python_version() -> str:
|
|
951
|
+
"""
|
|
952
|
+
Get the default Python version to use when none is specified.
|
|
953
|
+
|
|
954
|
+
:return: The default Python version string (e.g., "3.10.19")
|
|
955
|
+
"""
|
|
956
|
+
# Default to latest 3.10 version
|
|
957
|
+
pyversions = python_versions("3.10")
|
|
958
|
+
if not pyversions:
|
|
959
|
+
raise RuntimeError("No 3.10 versions found")
|
|
960
|
+
latest = sorted(list(pyversions.keys()))[-1]
|
|
961
|
+
return str(latest)
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def resolve_python_version(version_spec: str | None = None) -> str:
|
|
965
|
+
"""
|
|
966
|
+
Resolve a Python version specification to a full version string.
|
|
967
|
+
|
|
968
|
+
If version_spec is None, returns the latest Python 3.10 version.
|
|
969
|
+
If version_spec is partial (e.g., "3.10"), returns the latest micro version.
|
|
970
|
+
If version_spec is full (e.g., "3.10.19"), returns it as-is after validation.
|
|
971
|
+
|
|
972
|
+
:param version_spec: Version specification (None, "3.10", or "3.10.19")
|
|
973
|
+
:return: Full version string (e.g., "3.10.19")
|
|
974
|
+
:raises RuntimeError: If the version is not found
|
|
975
|
+
"""
|
|
976
|
+
if version_spec is None:
|
|
977
|
+
# Default to latest 3.10 version
|
|
978
|
+
return get_default_python_version()
|
|
979
|
+
|
|
980
|
+
requested = Version(version_spec)
|
|
981
|
+
|
|
982
|
+
if requested.micro is not None:
|
|
983
|
+
# Full version specified - validate it exists
|
|
984
|
+
pyversions = python_versions()
|
|
985
|
+
if requested not in pyversions:
|
|
986
|
+
raise RuntimeError(f"Unknown version {requested}")
|
|
987
|
+
return str(requested)
|
|
988
|
+
else:
|
|
989
|
+
# Partial version (major.minor) - get latest micro
|
|
990
|
+
pyversions = python_versions(version_spec)
|
|
991
|
+
if not pyversions:
|
|
992
|
+
raise RuntimeError(f"Unknown minor version {requested}")
|
|
993
|
+
# Return the latest version for this major.minor
|
|
994
|
+
latest = sorted(list(pyversions.keys()))[-1]
|
|
995
|
+
return str(latest)
|
|
996
|
+
|
|
997
|
+
|
|
950
998
|
def setup_parser(
|
|
951
999
|
subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
952
1000
|
) -> None:
|
relenv/relocate.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
"""
|
|
4
4
|
A script to ensure the proper rpaths are in place for the relenv environment.
|
|
@@ -264,6 +264,34 @@ def is_in_dir(
|
|
|
264
264
|
return os.path.realpath(filepath).startswith(os.path.realpath(directory) + os.sep)
|
|
265
265
|
|
|
266
266
|
|
|
267
|
+
def remove_rpath(path: str | os.PathLike[str]) -> bool:
|
|
268
|
+
"""
|
|
269
|
+
Remove the rpath from a given ELF file.
|
|
270
|
+
|
|
271
|
+
:param path: The path to an ELF file
|
|
272
|
+
:type path: str
|
|
273
|
+
|
|
274
|
+
:return: True if successful, False otherwise
|
|
275
|
+
:rtype: bool
|
|
276
|
+
"""
|
|
277
|
+
old_rpath = parse_rpath(path)
|
|
278
|
+
if not old_rpath:
|
|
279
|
+
# No RPATH to remove
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
log.info("Remove RPATH from %s (was: %s)", path, old_rpath)
|
|
283
|
+
proc = subprocess.run(
|
|
284
|
+
["patchelf", "--remove-rpath", path],
|
|
285
|
+
stderr=subprocess.PIPE,
|
|
286
|
+
stdout=subprocess.PIPE,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if proc.returncode:
|
|
290
|
+
log.error("Failed to remove RPATH from %s: %s", path, proc.stderr.decode())
|
|
291
|
+
return False
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
|
|
267
295
|
def patch_rpath(
|
|
268
296
|
path: str | os.PathLike[str],
|
|
269
297
|
new_rpath: str,
|
|
@@ -325,6 +353,7 @@ def handle_elf(
|
|
|
325
353
|
root = libs
|
|
326
354
|
proc = subprocess.run(["ldd", path], stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
327
355
|
needs_rpath = False
|
|
356
|
+
|
|
328
357
|
for line in proc.stdout.decode().splitlines():
|
|
329
358
|
if line.find("=>") == -1:
|
|
330
359
|
log.debug("Skip ldd output line: %s", line)
|
|
@@ -371,7 +400,9 @@ def handle_elf(
|
|
|
371
400
|
log.info("Adjust rpath of %s to %s", path, relpath)
|
|
372
401
|
patch_rpath(path, relpath)
|
|
373
402
|
else:
|
|
374
|
-
|
|
403
|
+
# No relenv libraries are linked, so RPATH is not needed
|
|
404
|
+
# Remove any existing RPATH to avoid security/correctness issues
|
|
405
|
+
remove_rpath(path)
|
|
375
406
|
|
|
376
407
|
|
|
377
408
|
def main(
|
|
@@ -420,6 +451,7 @@ def main(
|
|
|
420
451
|
if path in processed:
|
|
421
452
|
continue
|
|
422
453
|
log.debug("Checking %s", path)
|
|
454
|
+
|
|
423
455
|
if is_macho(path):
|
|
424
456
|
log.info("Found Mach-O %s", path)
|
|
425
457
|
_ = handle_macho(path, libs_dir, rpath_only)
|
relenv/runtime.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
"""
|
|
4
4
|
This code is run when initializing the python interperter in a Relenv environment.
|
|
@@ -1270,3 +1270,8 @@ def bootstrap() -> None:
|
|
|
1270
1270
|
setup_crossroot()
|
|
1271
1271
|
install_cargo_config()
|
|
1272
1272
|
sys.meta_path = [importer] + sys.meta_path
|
|
1273
|
+
|
|
1274
|
+
# For Python 3.13+, sysconfig became a package so the importer doesn't
|
|
1275
|
+
# intercept it. Manually wrap it here.
|
|
1276
|
+
if sys.version_info >= (3, 13):
|
|
1277
|
+
wrap_sysconfig("sysconfig")
|
relenv/toolchain.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: relenv
|
|
3
|
-
Version: 0.22.
|
|
3
|
+
Version: 0.22.2
|
|
4
4
|
Project-URL: Source Code, https://github.com/saltstack/relative-environment-for-python
|
|
5
5
|
Project-URL: Documentation, https://relenv.readthedocs.io/en/latest/
|
|
6
6
|
Project-URL: Changelog, https://relenv.readthedocs.io/en/latest/changelog.html
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
relenv/__init__.py,sha256=LmJZKtCS7hgyl9VIIT4qiIXj8YX1fJKY0qJUi2UtwgA,325
|
|
2
|
+
relenv/__main__.py,sha256=gLR88_GYmdpyuppoaCq-W0SPetEOQFisQglcQZSGROg,1538
|
|
3
|
+
relenv/buildenv.py,sha256=I7ZyWxO6RZveaCQtqwBPpUW1XdMLwmuPfww5dfzRgwM,4120
|
|
4
|
+
relenv/check.py,sha256=m3KkTS6LQkvRwexVqqGQ4_A7GQU-dAmcF8EQDu_K67c,1133
|
|
5
|
+
relenv/common.py,sha256=ZAOmSh74HYx9CAYA5mnz1EHqsDEa_pwjfctiyt44x00,35826
|
|
6
|
+
relenv/create.py,sha256=6Q3m1MkeYyeH48UM6wYhvTlhHRRA73ZA34LfvNe0Ylk,7934
|
|
7
|
+
relenv/fetch.py,sha256=cnkPJiPlttiWOm7Olt4R23RAu_IzzcUsq1H-yHoPbow,2791
|
|
8
|
+
relenv/manifest.py,sha256=xyMHWao_I3j7gCyweES3I50830efacxZI3KumuPfyvU,1057
|
|
9
|
+
relenv/python-versions.json,sha256=axX8aDQhLwdQn55WRYc5JbQd9_qV9yw6Syue-7ieWh8,14571
|
|
10
|
+
relenv/pyversions.py,sha256=euMZcELZsM612c4D3PD9dad4ScFbh0S54dRnbf7BHJg,43032
|
|
11
|
+
relenv/relocate.py,sha256=gsLMbgvCYqHXGZ5ZDBUsvVEYrefken0D5v9bJ93j-qA,14003
|
|
12
|
+
relenv/runtime.py,sha256=-3CE8v_g6flBHqldMmYM4XCTGAYbDuui3JX-o6dI93M,40035
|
|
13
|
+
relenv/toolchain.py,sha256=vbL79grOsThEsstYpx1fZxo3EuliDj5UHcsTpEgdWXo,929
|
|
14
|
+
relenv/_resources/xz/config.h,sha256=2IofvMqyAa8xBaQb6QGStiUugMmjyUCq_Of5drh7fro,4117
|
|
15
|
+
relenv/_resources/xz/readme.md,sha256=aOs7hz9RhTnTwHUt6gbO-2Sl0xLKIJp8-w6Du4VIFgY,258
|
|
16
|
+
relenv/_scripts/install_vc_build.ps1,sha256=ir1bcz7rNOuFw4E5_AqBidiKS0SpPViXW7zaanRPCoM,7629
|
|
17
|
+
relenv/build/__init__.py,sha256=0Gre5oainAdkw4Bfc54V4Dwrjz4a5_hiICeEil9D-Cw,6157
|
|
18
|
+
relenv/build/darwin.py,sha256=n1ff-NLFlcN3_fqeJUOPnBC6HODT5Y-gvWjQ3UpdXsE,7136
|
|
19
|
+
relenv/build/linux.py,sha256=UD1b-bTri9lF3iaoibOFO2vzF-6GpubO38_65hvmqtY,26161
|
|
20
|
+
relenv/build/windows.py,sha256=-HO2MIxI3P8u3lde92KRBOM-zP4y9zyy8Xp7pULd3Es,16102
|
|
21
|
+
relenv/build/common/__init__.py,sha256=PUAaLAv_1zIGJcGb77jGhSZ1PdawJF6DH4RMRKamh7U,1004
|
|
22
|
+
relenv/build/common/_sysconfigdata_template.py,sha256=NaJkmEKIj8Uhf7Cs82FyrMd2CyZVh-Ri8mE3Pm8jxo0,1955
|
|
23
|
+
relenv/build/common/builder.py,sha256=vFujUxp_cq-miBZD6RLS3yta7158DbiQM0zZg593IDQ,31000
|
|
24
|
+
relenv/build/common/builders.py,sha256=2ajiN3gjjIsQ6TFuEvUCzizL_XKnvx0F9qeuak658ho,4772
|
|
25
|
+
relenv/build/common/download.py,sha256=oFmwYhdRlWvQMPju5ByjCTEsbxmH8ENFXilnZKY7c6U,10479
|
|
26
|
+
relenv/build/common/install.py,sha256=nXFxncnJQKlaell3Ps3LnjmVyGpS8KVuRWSMWrdjJHA,19299
|
|
27
|
+
relenv/build/common/ui.py,sha256=FebuZVSioSHGzTUyOSepFRkZzKN-SxchjXsM5tTLApI,14192
|
|
28
|
+
relenv-0.22.2.dist-info/licenses/LICENSE.md,sha256=T0SRk3vJM1YcAJjDz9vsX9gsCRatAVSBS7LeU0tklRM,9919
|
|
29
|
+
relenv-0.22.2.dist-info/licenses/NOTICE,sha256=Ns0AybPHBsgJKJJfjE6YnGgWEQQ9F7lQ6QNlYLlQT3E,548
|
|
30
|
+
tests/__init__.py,sha256=FdZLbBoaMwwgwYwDI9ozQnuT7FHQ7ae8MhQrsP3tVyQ,70
|
|
31
|
+
tests/_pytest_typing.py,sha256=cOGrU7it8H-2F198AT3bHf1Un8K3PzYvQgDs2PCmFb8,1204
|
|
32
|
+
tests/conftest.py,sha256=tccQCx1DLwftaJCnNLrBkyOmj-a_13nvayUG0KEkSzg,2628
|
|
33
|
+
tests/test_build.py,sha256=KR1jci5fWyA3yWAavnh3QowdPyYF_b0IURkhoe4YA0E,14992
|
|
34
|
+
tests/test_common.py,sha256=CJPpgP0euIOTFatZ2GmrR-TrJ1d2Hv_W8iaVpkNcuAc,18008
|
|
35
|
+
tests/test_create.py,sha256=iW26qyApMHTfOVa_X2LFsWeBIaaGo1pk8m1xaSOhGdA,6849
|
|
36
|
+
tests/test_downloads.py,sha256=A4caZk4HUdMPPS83toxx7FM6sXy4etxMqeKcDDx-gNc,3528
|
|
37
|
+
tests/test_fips_photon.py,sha256=Y39E1LUndDn3_w4Ir9gkYwxAIHlH8fWPvDI1GRNFh_g,1397
|
|
38
|
+
tests/test_module_imports.py,sha256=erwMhcRK-HMFjmVQoewQSLBtjes2KpZfiFAXgvK_Nt4,1354
|
|
39
|
+
tests/test_pyversions_runtime.py,sha256=CTlGySCCLTuk1I4KFftr2S37gnPvpYubNun8oIRv5BU,8978
|
|
40
|
+
tests/test_relocate.py,sha256=eWgocPhQoU9zAItdLN9yp45j0ura6IPsCmmkuLOOCGU,13402
|
|
41
|
+
tests/test_relocate_module.py,sha256=LslZSILgSAgLZ_wSepIlTFqb7AIkXTNGLWiPiNh5ffE,7802
|
|
42
|
+
tests/test_runtime.py,sha256=5anHdx92Je38XBb-_rDxwtAatgLk7U843s_ZFQ2YzTA,71990
|
|
43
|
+
tests/test_verify_build.py,sha256=2PyWNedqgc4RcvmicLhSnWr1GoFbVKh4W9tWUmPw_iQ,67827
|
|
44
|
+
relenv-0.22.2.dist-info/METADATA,sha256=WyqgcOFBqd_9VISxlNR-1Ga2gqyvM41fl0JYkHb6yvg,1360
|
|
45
|
+
relenv-0.22.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
46
|
+
relenv-0.22.2.dist-info/entry_points.txt,sha256=dO66nWPPWl8ALWWnZFlHKAo6mfPFuQid7purYWL2ddc,48
|
|
47
|
+
relenv-0.22.2.dist-info/top_level.txt,sha256=P4Ro6JLZE53ZdsQ76o2OzBcpb0MaVJmbfr0HAn9WF8M,13
|
|
48
|
+
relenv-0.22.2.dist-info/RECORD,,
|
tests/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
tests/_pytest_typing.py
CHANGED
tests/conftest.py
CHANGED
tests/test_build.py
CHANGED
tests/test_common.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -464,10 +464,72 @@ def test_makepath_oserror() -> None:
|
|
|
464
464
|
assert case == os.path.normcase(expected)
|
|
465
465
|
|
|
466
466
|
|
|
467
|
+
def test_toolchain_respects_relenv_data(
|
|
468
|
+
tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
|
|
469
|
+
) -> None:
|
|
470
|
+
"""
|
|
471
|
+
Test that RELENV_DATA environment variable controls toolchain location.
|
|
472
|
+
|
|
473
|
+
This is a regression test for issue #XXX where RELENV_DATA was ignored
|
|
474
|
+
in version 0.20.2+ after toolchains were moved to a cache directory.
|
|
475
|
+
When RELENV_DATA is set (as in saltstack CI pipelines), the toolchain
|
|
476
|
+
should be found in $RELENV_DATA/toolchain, not in the cache directory.
|
|
477
|
+
"""
|
|
478
|
+
data_dir = tmp_path / "custom_data"
|
|
479
|
+
triplet = "x86_64-linux-gnu"
|
|
480
|
+
toolchain_path = data_dir / "toolchain" / triplet
|
|
481
|
+
toolchain_path.mkdir(parents=True)
|
|
482
|
+
|
|
483
|
+
# Patch sys.platform to simulate Linux
|
|
484
|
+
monkeypatch.setattr(sys, "platform", "linux")
|
|
485
|
+
monkeypatch.setattr(
|
|
486
|
+
relenv.common, "get_triplet", lambda machine=None, plat=None: triplet
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Set RELENV_DATA environment variable
|
|
490
|
+
monkeypatch.setenv("RELENV_DATA", str(data_dir))
|
|
491
|
+
|
|
492
|
+
# Patch DATA_DIR to reflect the new RELENV_DATA value
|
|
493
|
+
monkeypatch.setattr(relenv.common, "DATA_DIR", data_dir)
|
|
494
|
+
|
|
495
|
+
# Verify toolchain_root_dir returns DATA_DIR/toolchain
|
|
496
|
+
from relenv.common import toolchain_root_dir
|
|
497
|
+
|
|
498
|
+
result = toolchain_root_dir()
|
|
499
|
+
assert result == data_dir / "toolchain"
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def test_toolchain_uses_cache_without_relenv_data(
|
|
503
|
+
tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch
|
|
504
|
+
) -> None:
|
|
505
|
+
"""
|
|
506
|
+
Test that toolchain uses cache directory when RELENV_DATA is not set.
|
|
507
|
+
|
|
508
|
+
When RELENV_DATA is not set, the toolchain should be stored in the
|
|
509
|
+
cache directory (~/.cache/relenv/toolchains or $XDG_CACHE_HOME/relenv/toolchains)
|
|
510
|
+
to allow reuse across different relenv environments.
|
|
511
|
+
"""
|
|
512
|
+
cache_dir = tmp_path / ".cache" / "relenv" / "toolchains"
|
|
513
|
+
|
|
514
|
+
# Patch sys.platform to simulate Linux
|
|
515
|
+
monkeypatch.setattr(sys, "platform", "linux")
|
|
516
|
+
|
|
517
|
+
# Remove RELENV_DATA if set
|
|
518
|
+
monkeypatch.delenv("RELENV_DATA", raising=False)
|
|
519
|
+
|
|
520
|
+
# Set XDG_CACHE_HOME to control cache location
|
|
521
|
+
monkeypatch.setenv("XDG_CACHE_HOME", str(tmp_path / ".cache"))
|
|
522
|
+
|
|
523
|
+
from relenv.common import toolchain_root_dir
|
|
524
|
+
|
|
525
|
+
result = toolchain_root_dir()
|
|
526
|
+
assert result == cache_dir
|
|
527
|
+
|
|
528
|
+
|
|
467
529
|
def test_copyright_headers() -> None:
|
|
468
530
|
"""Verify all Python source files have the correct copyright header."""
|
|
469
531
|
expected_header = (
|
|
470
|
-
"# Copyright 2022-
|
|
532
|
+
"# Copyright 2022-2026 Broadcom.\n" "# SPDX-License-Identifier: Apache-2.0\n"
|
|
471
533
|
)
|
|
472
534
|
|
|
473
535
|
# Find all Python files in relenv/ and tests/
|
tests/test_create.py
CHANGED
tests/test_downloads.py
CHANGED
tests/test_fips_photon.py
CHANGED
tests/test_module_imports.py
CHANGED
tests/test_pyversions_runtime.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
#
|
|
4
4
|
from __future__ import annotations
|
|
@@ -175,3 +175,80 @@ def test_detect_xz_versions(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
175
175
|
assert "5.6.3" in versions
|
|
176
176
|
# Verify sorting (latest first)
|
|
177
177
|
assert versions[0] == "5.8.1"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_resolve_python_version_none_defaults_to_latest_310() -> None:
|
|
181
|
+
"""Test that None resolves to the latest 3.10 version."""
|
|
182
|
+
result = pyversions.resolve_python_version(None)
|
|
183
|
+
assert result.startswith("3.10.")
|
|
184
|
+
# Verify it's a valid version in the registry
|
|
185
|
+
versions = pyversions.python_versions("3.10")
|
|
186
|
+
assert pyversions.Version(result) in versions
|
|
187
|
+
# Verify it's the latest 3.10 version
|
|
188
|
+
latest = sorted(list(versions.keys()))[-1]
|
|
189
|
+
assert result == str(latest)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_resolve_python_version_partial_minor() -> None:
|
|
193
|
+
"""Test that partial versions (3.10) resolve to latest micro."""
|
|
194
|
+
result = pyversions.resolve_python_version("3.10")
|
|
195
|
+
assert result.startswith("3.10.")
|
|
196
|
+
# Verify it resolves to the latest micro version
|
|
197
|
+
versions = pyversions.python_versions("3.10")
|
|
198
|
+
latest = sorted(list(versions.keys()))[-1]
|
|
199
|
+
assert result == str(latest)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_resolve_python_version_different_minors() -> None:
|
|
203
|
+
"""Test resolution works for different minor versions."""
|
|
204
|
+
result_311 = pyversions.resolve_python_version("3.11")
|
|
205
|
+
assert result_311.startswith("3.11.")
|
|
206
|
+
|
|
207
|
+
result_313 = pyversions.resolve_python_version("3.13")
|
|
208
|
+
assert result_313.startswith("3.13.")
|
|
209
|
+
|
|
210
|
+
# Verify they're different
|
|
211
|
+
assert result_311 != result_313
|
|
212
|
+
|
|
213
|
+
# Verify each is the latest for its minor version
|
|
214
|
+
versions_311 = pyversions.python_versions("3.11")
|
|
215
|
+
latest_311 = sorted(list(versions_311.keys()))[-1]
|
|
216
|
+
assert result_311 == str(latest_311)
|
|
217
|
+
|
|
218
|
+
versions_313 = pyversions.python_versions("3.13")
|
|
219
|
+
latest_313 = sorted(list(versions_313.keys()))[-1]
|
|
220
|
+
assert result_313 == str(latest_313)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_resolve_python_version_full_version() -> None:
|
|
224
|
+
"""Test that full versions are validated and returned as-is."""
|
|
225
|
+
# Get any valid version from the registry
|
|
226
|
+
all_versions = pyversions.python_versions()
|
|
227
|
+
some_version = str(next(iter(all_versions)))
|
|
228
|
+
|
|
229
|
+
result = pyversions.resolve_python_version(some_version)
|
|
230
|
+
assert result == some_version
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_resolve_python_version_invalid_full_version() -> None:
|
|
234
|
+
"""Test that invalid full versions raise RuntimeError."""
|
|
235
|
+
with pytest.raises(RuntimeError, match="Unknown version"):
|
|
236
|
+
pyversions.resolve_python_version("3.10.999")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_resolve_python_version_invalid_minor_version() -> None:
|
|
240
|
+
"""Test that invalid minor versions raise RuntimeError."""
|
|
241
|
+
with pytest.raises(RuntimeError, match="Unknown minor version"):
|
|
242
|
+
pyversions.resolve_python_version("3.99")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_resolve_python_version_consistency() -> None:
|
|
246
|
+
"""Test that resolve_python_version is idempotent for full versions."""
|
|
247
|
+
# Get a valid full version from the registry
|
|
248
|
+
all_versions = pyversions.python_versions()
|
|
249
|
+
some_version = str(next(iter(all_versions)))
|
|
250
|
+
|
|
251
|
+
# Resolving a full version twice should give the same result
|
|
252
|
+
first = pyversions.resolve_python_version(some_version)
|
|
253
|
+
second = pyversions.resolve_python_version(first)
|
|
254
|
+
assert first == second
|
tests/test_relocate.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2022-
|
|
1
|
+
# Copyright 2022-2026 Broadcom.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import pathlib
|
|
4
4
|
import shutil
|
|
@@ -16,6 +16,7 @@ from relenv.relocate import (
|
|
|
16
16
|
main,
|
|
17
17
|
parse_readelf_d,
|
|
18
18
|
patch_rpath,
|
|
19
|
+
remove_rpath,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
pytestmark = [
|
|
@@ -276,3 +277,87 @@ def test_handle_elf_rpath_only(tmp_path: pathlib.Path) -> None:
|
|
|
276
277
|
assert not (proj.libs_dir / "fake.so.2").exists()
|
|
277
278
|
assert patch_rpath_mock.call_count == 1
|
|
278
279
|
patch_rpath_mock.assert_called_with(str(pybin), "$ORIGIN/../lib")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_remove_rpath_with_existing_rpath(tmp_path: pathlib.Path) -> None:
|
|
283
|
+
"""Test that remove_rpath removes an existing RPATH."""
|
|
284
|
+
path = str(tmp_path / "test.so")
|
|
285
|
+
with patch("subprocess.run", return_value=MagicMock(returncode=0)):
|
|
286
|
+
with patch(
|
|
287
|
+
"relenv.relocate.parse_rpath",
|
|
288
|
+
return_value=["/some/absolute/path"],
|
|
289
|
+
):
|
|
290
|
+
assert remove_rpath(path) is True
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_remove_rpath_no_existing_rpath(tmp_path: pathlib.Path) -> None:
|
|
294
|
+
"""Test that remove_rpath succeeds when there's no RPATH to remove."""
|
|
295
|
+
path = str(tmp_path / "test.so")
|
|
296
|
+
with patch("relenv.relocate.parse_rpath", return_value=[]):
|
|
297
|
+
assert remove_rpath(path) is True
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_remove_rpath_failed(tmp_path: pathlib.Path) -> None:
|
|
301
|
+
"""Test that remove_rpath returns False when patchelf fails."""
|
|
302
|
+
path = str(tmp_path / "test.so")
|
|
303
|
+
with patch("subprocess.run", return_value=MagicMock(returncode=1)):
|
|
304
|
+
with patch(
|
|
305
|
+
"relenv.relocate.parse_rpath",
|
|
306
|
+
return_value=["/some/absolute/path"],
|
|
307
|
+
):
|
|
308
|
+
assert remove_rpath(path) is False
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def test_handle_elf_removes_rpath_when_no_relenv_libs(tmp_path: pathlib.Path) -> None:
|
|
312
|
+
"""Test that handle_elf removes RPATH for binaries linking only to system libs."""
|
|
313
|
+
proj = LinuxProject(tmp_path / "proj")
|
|
314
|
+
module = proj.add_simple_elf("array.so", "lib", "python3.10", "lib-dynload")
|
|
315
|
+
|
|
316
|
+
# ldd output showing only system libraries
|
|
317
|
+
ldd_ret = """
|
|
318
|
+
linux-vdso.so.1 => linux-vdso.so.1 (0x0123456789)
|
|
319
|
+
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x0123456789)
|
|
320
|
+
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0123456789)
|
|
321
|
+
""".encode()
|
|
322
|
+
|
|
323
|
+
with proj:
|
|
324
|
+
with patch("subprocess.run", return_value=MagicMock(stdout=ldd_ret)):
|
|
325
|
+
with patch("relenv.relocate.remove_rpath") as remove_rpath_mock:
|
|
326
|
+
with patch("relenv.relocate.patch_rpath") as patch_rpath_mock:
|
|
327
|
+
handle_elf(
|
|
328
|
+
str(module), str(proj.libs_dir), True, str(proj.root_dir)
|
|
329
|
+
)
|
|
330
|
+
# Should remove RPATH, not patch it
|
|
331
|
+
assert remove_rpath_mock.call_count == 1
|
|
332
|
+
assert patch_rpath_mock.call_count == 0
|
|
333
|
+
remove_rpath_mock.assert_called_with(str(module))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def test_handle_elf_sets_rpath_when_relenv_libs_present(tmp_path: pathlib.Path) -> None:
|
|
337
|
+
"""Test that handle_elf sets RPATH for binaries linking to relenv libs."""
|
|
338
|
+
proj = LinuxProject(tmp_path / "proj")
|
|
339
|
+
module = proj.add_simple_elf("_ssl.so", "lib", "python3.10", "lib-dynload")
|
|
340
|
+
libssl = proj.libs_dir / "libssl.so.3"
|
|
341
|
+
libssl.touch()
|
|
342
|
+
|
|
343
|
+
# ldd output showing relenv-built library
|
|
344
|
+
ldd_ret = """
|
|
345
|
+
linux-vdso.so.1 => linux-vdso.so.1 (0x0123456789)
|
|
346
|
+
libssl.so.3 => {libssl} (0x0123456789)
|
|
347
|
+
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x0123456789)
|
|
348
|
+
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0123456789)
|
|
349
|
+
""".format(
|
|
350
|
+
libssl=libssl
|
|
351
|
+
).encode()
|
|
352
|
+
|
|
353
|
+
with proj:
|
|
354
|
+
with patch("subprocess.run", return_value=MagicMock(stdout=ldd_ret)):
|
|
355
|
+
with patch("relenv.relocate.remove_rpath") as remove_rpath_mock:
|
|
356
|
+
with patch("relenv.relocate.patch_rpath") as patch_rpath_mock:
|
|
357
|
+
handle_elf(
|
|
358
|
+
str(module), str(proj.libs_dir), True, str(proj.root_dir)
|
|
359
|
+
)
|
|
360
|
+
# Should patch RPATH, not remove it
|
|
361
|
+
assert patch_rpath_mock.call_count == 1
|
|
362
|
+
assert remove_rpath_mock.call_count == 0
|
|
363
|
+
patch_rpath_mock.assert_called_with(str(module), "$ORIGIN/../..")
|
tests/test_relocate_module.py
CHANGED