relenv 0.22.1__tar.gz → 0.22.2__tar.gz

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.
Files changed (55) hide show
  1. {relenv-0.22.1/relenv.egg-info → relenv-0.22.2}/PKG-INFO +1 -1
  2. {relenv-0.22.1 → relenv-0.22.2}/relenv/__init__.py +1 -1
  3. {relenv-0.22.1 → relenv-0.22.2}/relenv/__main__.py +1 -1
  4. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/__init__.py +21 -25
  5. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/__init__.py +1 -1
  6. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/_sysconfigdata_template.py +1 -1
  7. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/builder.py +1 -1
  8. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/builders.py +1 -1
  9. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/download.py +1 -1
  10. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/install.py +6 -2
  11. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/common/ui.py +1 -1
  12. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/darwin.py +1 -1
  13. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/linux.py +1 -1
  14. {relenv-0.22.1 → relenv-0.22.2}/relenv/build/windows.py +1 -1
  15. {relenv-0.22.1 → relenv-0.22.2}/relenv/buildenv.py +1 -1
  16. {relenv-0.22.1 → relenv-0.22.2}/relenv/check.py +1 -1
  17. {relenv-0.22.1 → relenv-0.22.2}/relenv/common.py +2 -4
  18. {relenv-0.22.1 → relenv-0.22.2}/relenv/create.py +19 -27
  19. {relenv-0.22.1 → relenv-0.22.2}/relenv/fetch.py +7 -5
  20. {relenv-0.22.1 → relenv-0.22.2}/relenv/manifest.py +1 -1
  21. {relenv-0.22.1 → relenv-0.22.2}/relenv/pyversions.py +49 -1
  22. {relenv-0.22.1 → relenv-0.22.2}/relenv/relocate.py +34 -2
  23. {relenv-0.22.1 → relenv-0.22.2}/relenv/runtime.py +1 -1
  24. {relenv-0.22.1 → relenv-0.22.2}/relenv/toolchain.py +1 -1
  25. {relenv-0.22.1 → relenv-0.22.2/relenv.egg-info}/PKG-INFO +1 -1
  26. {relenv-0.22.1 → relenv-0.22.2}/tests/__init__.py +1 -1
  27. {relenv-0.22.1 → relenv-0.22.2}/tests/_pytest_typing.py +1 -1
  28. {relenv-0.22.1 → relenv-0.22.2}/tests/conftest.py +1 -1
  29. {relenv-0.22.1 → relenv-0.22.2}/tests/test_build.py +1 -1
  30. {relenv-0.22.1 → relenv-0.22.2}/tests/test_common.py +2 -2
  31. {relenv-0.22.1 → relenv-0.22.2}/tests/test_create.py +1 -1
  32. {relenv-0.22.1 → relenv-0.22.2}/tests/test_downloads.py +1 -1
  33. {relenv-0.22.1 → relenv-0.22.2}/tests/test_fips_photon.py +1 -1
  34. {relenv-0.22.1 → relenv-0.22.2}/tests/test_module_imports.py +1 -1
  35. {relenv-0.22.1 → relenv-0.22.2}/tests/test_pyversions_runtime.py +78 -1
  36. {relenv-0.22.1 → relenv-0.22.2}/tests/test_relocate.py +86 -1
  37. {relenv-0.22.1 → relenv-0.22.2}/tests/test_relocate_module.py +1 -1
  38. {relenv-0.22.1 → relenv-0.22.2}/tests/test_runtime.py +1 -1
  39. {relenv-0.22.1 → relenv-0.22.2}/tests/test_verify_build.py +7 -5
  40. {relenv-0.22.1 → relenv-0.22.2}/LICENSE.md +0 -0
  41. {relenv-0.22.1 → relenv-0.22.2}/MANIFEST.in +0 -0
  42. {relenv-0.22.1 → relenv-0.22.2}/NOTICE +0 -0
  43. {relenv-0.22.1 → relenv-0.22.2}/README.md +0 -0
  44. {relenv-0.22.1 → relenv-0.22.2}/pyproject.toml +0 -0
  45. {relenv-0.22.1 → relenv-0.22.2}/relenv/_resources/xz/config.h +0 -0
  46. {relenv-0.22.1 → relenv-0.22.2}/relenv/_resources/xz/readme.md +0 -0
  47. {relenv-0.22.1 → relenv-0.22.2}/relenv/_scripts/install_vc_build.ps1 +0 -0
  48. {relenv-0.22.1 → relenv-0.22.2}/relenv/python-versions.json +0 -0
  49. {relenv-0.22.1 → relenv-0.22.2}/relenv.egg-info/SOURCES.txt +0 -0
  50. {relenv-0.22.1 → relenv-0.22.2}/relenv.egg-info/dependency_links.txt +0 -0
  51. {relenv-0.22.1 → relenv-0.22.2}/relenv.egg-info/entry_points.txt +0 -0
  52. {relenv-0.22.1 → relenv-0.22.2}/relenv.egg-info/requires.txt +0 -0
  53. {relenv-0.22.1 → relenv-0.22.2}/relenv.egg-info/top_level.txt +0 -0
  54. {relenv-0.22.1 → relenv-0.22.2}/setup.cfg +0 -0
  55. {relenv-0.22.1 → relenv-0.22.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: relenv
3
- Version: 0.22.1
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
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  The entrypoint into relenv.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  # mypy: ignore-errors
4
4
  """
@@ -15,8 +15,13 @@ from types import FrameType, ModuleType
15
15
 
16
16
  from . import darwin, linux, windows
17
17
  from .common import builds
18
- from ..common import DEFAULT_PYTHON, build_arch
19
- from ..pyversions import Version, python_versions
18
+ from ..common import build_arch
19
+ from ..pyversions import (
20
+ Version,
21
+ get_default_python_version,
22
+ python_versions,
23
+ resolve_python_version,
24
+ )
20
25
 
21
26
 
22
27
  def platform_module() -> ModuleType:
@@ -62,11 +67,12 @@ def setup_parser(
62
67
  "logs, src, build, and previous tarball."
63
68
  ),
64
69
  )
70
+ default_version = get_default_python_version()
65
71
  build_subparser.add_argument(
66
72
  "--python",
67
- default=DEFAULT_PYTHON,
73
+ default=default_version,
68
74
  type=str,
69
- help="The python version [default: %(default)s]",
75
+ help="The python version (e.g., 3.10, 3.13.7) [default: %(default)s]",
70
76
  )
71
77
  build_subparser.add_argument(
72
78
  "--no-cleanup",
@@ -146,27 +152,17 @@ def main(args: argparse.Namespace) -> None:
146
152
  print(f"Unsupported platform: {sys.platform}")
147
153
  sys.exit(1)
148
154
 
149
- requested = Version(args.python)
150
-
151
- if requested.micro:
155
+ try:
156
+ build_version_str = resolve_python_version(args.python)
157
+ except RuntimeError as e:
158
+ print(f"Error: {e}")
152
159
  pyversions = python_versions()
153
- if requested not in pyversions:
154
- print(f"Unknown version {requested}")
155
- strversions = "\n".join([str(_) for _ in pyversions])
156
- print(f"Known versions are:\n{strversions}")
157
- sys.exit(1)
158
- build_version = requested
159
- else:
160
- pyversions = python_versions(args.python)
161
- build_version = sorted(list(pyversions.keys()))[-1]
162
-
163
- # print(pyversions)
164
- # print(pyversions[0].major)
165
- # print(pyversions[0].minor)
166
- # print(pyversions[0].micro)
167
- # print(pyversions[0].pre)
168
- # print(pyversions[0].post)
169
- # print(pyversions)
160
+ strversions = "\n".join([str(_) for _ in pyversions])
161
+ print(f"Known versions are:\n{strversions}")
162
+ sys.exit(1)
163
+
164
+ build_version = Version(build_version_str)
165
+ pyversions = python_versions()
170
166
  print(f"Build Python {build_version}")
171
167
 
172
168
  # XXX
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Build process common methods.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  # mypy: ignore-errors
4
4
  # flake8: noqa
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Builder and Builds classes for managing the build process.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Build functions for specific dependencies.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Download utility class for fetching build dependencies.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Installation and finalization functions for the build process.
@@ -427,7 +427,11 @@ def finalize(
427
427
  :type logfp: file
428
428
  """
429
429
  # Run relok8 to make sure the rpaths are relocatable.
430
- relenv.relocate.main(dirs.prefix, log_file_name=str(dirs.logs / "relocate.py.log"))
430
+ # Modules that don't link to relenv libs will have their RPATH removed
431
+ relenv.relocate.main(
432
+ dirs.prefix,
433
+ log_file_name=str(dirs.logs / "relocate.py.log"),
434
+ )
431
435
  # Install relenv-sysconfigdata module
432
436
  libdir = pathlib.Path(dirs.prefix) / "lib"
433
437
 
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  UI and build statistics utilities.
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Broadcom.
1
+ # Copyright 2025-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2
3
3
  # mypy: ignore-errors
4
4
  """
@@ -1,4 +1,4 @@
1
- # Copyright 2025 Broadcom.
1
+ # Copyright 2025-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2
3
3
  # mypy: ignore-errors
4
4
  """
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2
3
3
  # mypy: ignore-errors
4
4
  """
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Helper for building libraries to install into a relenv environment.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Check the integrety of a relenv environment.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Common classes and values used around relenv.
@@ -34,14 +34,12 @@ from typing import (
34
34
  )
35
35
 
36
36
  # relenv package version
37
- __version__ = "0.22.1"
37
+ __version__ = "0.22.2"
38
38
 
39
39
  log = logging.getLogger(__name__)
40
40
 
41
41
  MODULE_DIR = pathlib.Path(__file__).resolve().parent
42
42
 
43
- DEFAULT_PYTHON = "3.10.18"
44
-
45
43
  LINUX = "linux"
46
44
  WIN32 = "win32"
47
45
  DARWIN = "darwin"
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  The ``relenv create`` command.
@@ -23,7 +23,11 @@ from .common import (
23
23
  format_shebang,
24
24
  relative_interpreter,
25
25
  )
26
- from .pyversions import Version, python_versions
26
+ from .pyversions import (
27
+ get_default_python_version,
28
+ python_versions,
29
+ resolve_python_version,
30
+ )
27
31
 
28
32
 
29
33
  @contextlib.contextmanager
@@ -73,11 +77,12 @@ def setup_parser(
73
77
  type=str,
74
78
  help="The host architecture [default: %(default)s]",
75
79
  )
80
+ default_version = get_default_python_version()
76
81
  subparser.add_argument(
77
82
  "--python",
78
- default="3.10.17",
83
+ default=default_version,
79
84
  type=str,
80
- help="The python version [default: %(default)s]",
85
+ help="The python version (e.g., 3.10, 3.13.7) [default: %(default)s]",
81
86
  )
82
87
 
83
88
 
@@ -106,8 +111,9 @@ def create(
106
111
  else:
107
112
  writeto = pathlib.Path(name).resolve()
108
113
 
114
+ # Version should be provided by main(), but handle None just in case
109
115
  if version is None:
110
- version = "3.10.17"
116
+ version = get_default_python_version()
111
117
 
112
118
  if pathlib.Path(writeto).exists():
113
119
  raise CreateException("The requested path already exists.")
@@ -253,31 +259,17 @@ def main(args: argparse.Namespace) -> None:
253
259
  "Warning: Cross compilation support is experimental and is not fully tested or working!"
254
260
  )
255
261
 
256
- # Resolve version (support minor version like "3.12" or full version like "3.12.7")
257
- requested = Version(args.python)
258
-
259
- if requested.micro:
260
- # Full version specified (e.g., "3.12.7")
262
+ try:
263
+ create_version = resolve_python_version(args.python)
264
+ except RuntimeError as e:
265
+ print(f"Error: {e}")
261
266
  pyversions = python_versions()
262
- if requested not in pyversions:
263
- print(f"Unknown version {requested}")
264
- strversions = "\n".join([str(_) for _ in pyversions])
265
- print(f"Known versions are:\n{strversions}")
266
- sys.exit(1)
267
- create_version = requested
268
- else:
269
- # Minor version specified (e.g., "3.12"), resolve to latest
270
- pyversions = python_versions(args.python)
271
- if not pyversions:
272
- print(f"Unknown minor version {requested}")
273
- all_versions = python_versions()
274
- strversions = "\n".join([str(_) for _ in all_versions])
275
- print(f"Known versions are:\n{strversions}")
276
- sys.exit(1)
277
- create_version = sorted(list(pyversions.keys()))[-1]
267
+ strversions = "\n".join([str(_) for _ in pyversions])
268
+ print(f"Known versions are:\n{strversions}")
269
+ sys.exit(1)
278
270
 
279
271
  try:
280
- create(name, arch=args.arch, version=str(create_version))
272
+ create(name, arch=args.arch, version=create_version)
281
273
  except CreateException as exc:
282
274
  print(exc)
283
275
  sys.exit(1)
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  # mypy: ignore-errors
4
4
  """
@@ -16,7 +16,6 @@ from .build import platform_module
16
16
  from .common import (
17
17
  CHECK_HOSTS,
18
18
  DATA_DIR,
19
- DEFAULT_PYTHON,
20
19
  __version__,
21
20
  build_arch,
22
21
  check_url,
@@ -24,6 +23,7 @@ from .common import (
24
23
  get_triplet,
25
24
  work_dir,
26
25
  )
26
+ from .pyversions import get_default_python_version, resolve_python_version
27
27
 
28
28
 
29
29
  def setup_parser(
@@ -45,11 +45,12 @@ def setup_parser(
45
45
  type=str,
46
46
  help="Architecture to download. [default: %(default)s]",
47
47
  )
48
+ default_version = get_default_python_version()
48
49
  subparser.add_argument(
49
50
  "--python",
50
- default=DEFAULT_PYTHON,
51
+ default=default_version,
51
52
  type=str,
52
- help="The python version [default: %(default)s]",
53
+ help="The python version (e.g., 3.10, 3.13.7) [default: %(default)s]",
53
54
  )
54
55
 
55
56
 
@@ -87,7 +88,8 @@ def main(args: argparse.Namespace) -> None:
87
88
  """
88
89
  version = os.environ.get("RELENV_FETCH_VERSION", __version__)
89
90
  triplet = get_triplet(machine=args.arch)
90
- python = args.python
91
+ # args.python will be the default version or user-specified version
92
+ python = resolve_python_version(args.python)
91
93
  check_hosts = CHECK_HOSTS
92
94
  if os.environ.get("RELENV_FETCH_HOST", ""):
93
95
  check_hosts = [os.environ["RELENV_FETCH_HOST"]]
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  """
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
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:
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
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
- log.info("Do not adjust rpath of %s", path)
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)
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
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.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  The ``relenv toolchain`` command.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: relenv
3
- Version: 0.22.1
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
@@ -1,2 +1,2 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """
4
4
  Typed helper wrappers for common pytest decorators so mypy understands them.
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  import logging
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  import hashlib
4
4
  import logging
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  from __future__ import annotations
4
4
 
@@ -529,7 +529,7 @@ def test_toolchain_uses_cache_without_relenv_data(
529
529
  def test_copyright_headers() -> None:
530
530
  """Verify all Python source files have the correct copyright header."""
531
531
  expected_header = (
532
- "# Copyright 2022-2025 Broadcom.\n" "# SPDX-License-Identifier: Apache-2.0\n"
532
+ "# Copyright 2022-2026 Broadcom.\n" "# SPDX-License-Identifier: Apache-2.0\n"
533
533
  )
534
534
 
535
535
  # Find all Python files in relenv/ and tests/
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  import os
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  import pathlib
4
4
  import subprocess
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  import os
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  from __future__ import annotations
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
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
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
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/../..")
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  from __future__ import annotations
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  from __future__ import annotations
@@ -1,4 +1,4 @@
1
- # Copyright 2022-2025 Broadcom.
1
+ # Copyright 2022-2026 Broadcom.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  # mypy: ignore-errors
4
4
  """
@@ -180,13 +180,15 @@ def test_imports(pyexec):
180
180
 
181
181
 
182
182
  def test_pip_install_salt_git(pipexec, build, build_dir, pyexec, build_version):
183
- if (
184
- sys.platform == "win32"
185
- and "3.11" in build_version
183
+ if sys.platform == "win32" and (
184
+ "3.10" in build_version
185
+ or "3.11" in build_version
186
186
  or "3.12" in build_version
187
187
  or "3.13" in build_version
188
188
  ):
189
- pytest.xfail("Salt does not work with 3.11 or 3.12 on windows yet")
189
+ pytest.xfail(
190
+ "Salt git install fails on Windows (setup.py tries to install missing man pages)"
191
+ )
190
192
  if sys.platform == "darwin" and "3.12" in build_version:
191
193
  pytest.xfail("Salt does not work with 3.12 on macos yet")
192
194
  if sys.platform == "darwin" and "3.13" in build_version:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes