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.
Files changed (49) hide show
  1. relenv/__init__.py +14 -2
  2. relenv/__main__.py +12 -6
  3. relenv/_resources/xz/config.h +148 -0
  4. relenv/_resources/xz/readme.md +4 -0
  5. relenv/build/__init__.py +28 -30
  6. relenv/build/common/__init__.py +50 -0
  7. relenv/build/common/_sysconfigdata_template.py +72 -0
  8. relenv/build/common/builder.py +907 -0
  9. relenv/build/common/builders.py +163 -0
  10. relenv/build/common/download.py +324 -0
  11. relenv/build/common/install.py +609 -0
  12. relenv/build/common/ui.py +432 -0
  13. relenv/build/darwin.py +128 -14
  14. relenv/build/linux.py +292 -74
  15. relenv/build/windows.py +123 -169
  16. relenv/buildenv.py +48 -17
  17. relenv/check.py +10 -5
  18. relenv/common.py +492 -165
  19. relenv/create.py +147 -7
  20. relenv/fetch.py +16 -4
  21. relenv/manifest.py +15 -7
  22. relenv/python-versions.json +350 -0
  23. relenv/pyversions.py +817 -30
  24. relenv/relocate.py +101 -55
  25. relenv/runtime.py +457 -282
  26. relenv/toolchain.py +9 -3
  27. {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/METADATA +1 -1
  28. relenv-0.22.1.dist-info/RECORD +48 -0
  29. tests/__init__.py +2 -0
  30. tests/_pytest_typing.py +45 -0
  31. tests/conftest.py +42 -36
  32. tests/test_build.py +426 -9
  33. tests/test_common.py +373 -48
  34. tests/test_create.py +149 -6
  35. tests/test_downloads.py +19 -15
  36. tests/test_fips_photon.py +6 -3
  37. tests/test_module_imports.py +44 -0
  38. tests/test_pyversions_runtime.py +177 -0
  39. tests/test_relocate.py +45 -39
  40. tests/test_relocate_module.py +257 -0
  41. tests/test_runtime.py +1968 -6
  42. tests/test_verify_build.py +477 -34
  43. relenv/build/common.py +0 -1707
  44. relenv-0.21.2.dist-info/RECORD +0 -35
  45. {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/WHEEL +0 -0
  46. {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/entry_points.txt +0 -0
  47. {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/licenses/LICENSE.md +0 -0
  48. {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/licenses/NOTICE +0 -0
  49. {relenv-0.21.2.dist-info → relenv-0.22.1.dist-info}/top_level.txt +0 -0
relenv/relocate.py CHANGED
@@ -1,17 +1,41 @@
1
1
  # Copyright 2022-2025 Broadcom.
2
- # SPDX-License-Identifier: Apache-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.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import logging
8
- import os
10
+ import os as _os
9
11
  import pathlib
10
- import shutil
11
- import subprocess
12
+ import shutil as _shutil
13
+ import subprocess as _subprocess
14
+ from typing import Optional
12
15
 
13
16
  log = logging.getLogger(__name__)
14
17
 
18
+ os = _os
19
+ shutil = _shutil
20
+ subprocess = _subprocess
21
+
22
+ __all__ = [
23
+ "is_macho",
24
+ "is_elf",
25
+ "parse_otool_l",
26
+ "parse_readelf_d",
27
+ "parse_macho",
28
+ "parse_rpath",
29
+ "handle_macho",
30
+ "is_in_dir",
31
+ "handle_elf",
32
+ "patch_rpath",
33
+ "main",
34
+ "os",
35
+ "shutil",
36
+ "subprocess",
37
+ ]
38
+
15
39
 
16
40
  LIBCLIBS = [
17
41
  "linux-vdso.so.1",
@@ -47,7 +71,7 @@ LC_LOAD_DYLIB = "LC_LOAD_DYLIB"
47
71
  LC_RPATH = "LC_RPATH"
48
72
 
49
73
 
50
- def is_macho(path):
74
+ def is_macho(path: str | os.PathLike[str]) -> bool:
51
75
  """
52
76
  Determines whether the given file is a macho file.
53
77
 
@@ -63,7 +87,7 @@ def is_macho(path):
63
87
  return magic in [b"\xcf\xfa\xed\xfe"]
64
88
 
65
89
 
66
- def is_elf(path):
90
+ def is_elf(path: str | os.PathLike[str]) -> bool:
67
91
  """
68
92
  Determines whether the given file is an ELF file.
69
93
 
@@ -78,7 +102,7 @@ def is_elf(path):
78
102
  return magic == b"\x7f\x45\x4c\x46"
79
103
 
80
104
 
81
- def parse_otool_l(stdout):
105
+ def parse_otool_l(stdout: str) -> dict[str, list[str]]:
82
106
  """
83
107
  Parse the output of ``otool -l <path>``.
84
108
 
@@ -89,9 +113,9 @@ def parse_otool_l(stdout):
89
113
  :rtype: dict
90
114
  """
91
115
  in_cmd = False
92
- cmd = None
93
- name = None
94
- data = {}
116
+ cmd: Optional[str] = None
117
+ name: Optional[str] = None
118
+ data: dict[str, list[str]] = {}
95
119
  for line in [x.strip() for x in stdout.split("\n")]:
96
120
 
97
121
  if not line:
@@ -99,31 +123,29 @@ def parse_otool_l(stdout):
99
123
 
100
124
  if line.split()[0] == "cmd":
101
125
  in_cmd = False
102
- if cmd:
103
- if cmd not in data:
104
- data[cmd] = []
105
- data[cmd].append(name)
126
+ if cmd is not None and name is not None:
127
+ data.setdefault(cmd, []).append(name)
106
128
  cmd = None
107
129
  name = None
108
- if line.split()[-1] in (LC_ID_DYLIB, LC_LOAD_DYLIB, "LC_RPATH"):
109
- cmd = line.split()[-1]
130
+ command = line.split()[-1]
131
+ if command in (LC_ID_DYLIB, LC_LOAD_DYLIB, "LC_RPATH"):
132
+ cmd = command
110
133
  in_cmd = True
111
134
 
112
135
  if in_cmd:
113
- if line.split()[0] == "name":
114
- name = line.split()[1]
115
- if line.split()[0] == "path":
116
- name = line.split()[1]
136
+ parts = line.split()
137
+ if parts[0] == "name" and len(parts) > 1:
138
+ name = parts[1]
139
+ if parts[0] == "path" and len(parts) > 1:
140
+ name = parts[1]
117
141
 
118
- if in_cmd:
119
- if cmd not in data:
120
- data[cmd] = []
121
- data[cmd].append(name)
142
+ if in_cmd and cmd is not None and name is not None:
143
+ data.setdefault(cmd, []).append(name)
122
144
 
123
145
  return data
124
146
 
125
147
 
126
- def parse_readelf_d(stdout):
148
+ def parse_readelf_d(stdout: str) -> list[str]:
127
149
  """
128
150
  Parse the output of ``readelf -d <path>``.
129
151
 
@@ -141,7 +163,7 @@ def parse_readelf_d(stdout):
141
163
  return []
142
164
 
143
165
 
144
- def parse_macho(path):
166
+ def parse_macho(path: str | os.PathLike[str]) -> dict[str, list[str]] | None:
145
167
  """
146
168
  Run ``otool -l <path>`` and return its parsed output.
147
169
 
@@ -156,11 +178,11 @@ def parse_macho(path):
156
178
  )
157
179
  stdout = proc.stdout.decode()
158
180
  if stdout.find("is not an object file") != -1:
159
- return
181
+ return None
160
182
  return parse_otool_l(stdout)
161
183
 
162
184
 
163
- def parse_rpath(path):
185
+ def parse_rpath(path: str | os.PathLike[str]) -> list[str]:
164
186
  """
165
187
  Run ``readelf -d <path>`` and return its parsed output.
166
188
 
@@ -176,7 +198,11 @@ def parse_rpath(path):
176
198
  return parse_readelf_d(proc.stdout.decode())
177
199
 
178
200
 
179
- def handle_macho(path, root_dir, rpath_only):
201
+ def handle_macho(
202
+ path: str | os.PathLike[str],
203
+ root_dir: str | os.PathLike[str],
204
+ rpath_only: bool,
205
+ ) -> dict[str, list[str]] | None:
180
206
  """
181
207
  Ensure the given macho file has the correct rpath and is in th correct location.
182
208
 
@@ -189,15 +215,20 @@ def handle_macho(path, root_dir, rpath_only):
189
215
 
190
216
  :return: The information from ``parse_macho`` on the macho file.
191
217
  """
192
- obj = parse_macho(path)
193
- log.info("Processing file %s %r", path, obj)
218
+ path_obj = pathlib.Path(path)
219
+ obj = parse_macho(path_obj)
220
+ path_str = str(path_obj)
221
+ log.info("Processing file %s %r", path_str, obj)
222
+ if not obj:
223
+ return None
194
224
  if LC_LOAD_DYLIB in obj:
195
225
  for x in obj[LC_LOAD_DYLIB]:
196
- if path.startswith("@"):
197
- log.info("Skipping dynamic load: %s", path)
226
+ if x.startswith("@"):
227
+ log.info("Skipping dynamic load: %s", x)
198
228
  continue
199
229
  if os.path.exists(x):
200
- y = pathlib.Path(root_dir).resolve() / os.path.basename(x)
230
+ target_dir = pathlib.Path(root_dir).resolve()
231
+ y = target_dir / os.path.basename(x)
201
232
  if not os.path.exists(y):
202
233
  if rpath_only:
203
234
  log.warning("In `rpath_only mode` but %s is not in %s", x, y)
@@ -206,17 +237,19 @@ def handle_macho(path, root_dir, rpath_only):
206
237
  shutil.copy(x, y)
207
238
  shutil.copymode(x, y)
208
239
  log.info("Copied %s to %s", x, y)
209
- log.info("Use %s to %s", y, path)
240
+ log.info("Use %s to %s", y, path_str)
210
241
  z = pathlib.Path("@loader_path") / os.path.relpath(
211
- y, pathlib.Path(path).resolve().parent
242
+ y, path_obj.resolve().parent
212
243
  )
213
- cmd = ["install_name_tool", "-change", x, z, path]
244
+ cmd = ["install_name_tool", "-change", x, str(z), path_str]
214
245
  subprocess.run(cmd)
215
- log.info("Changed %s to %s in %s", x, z, path)
246
+ log.info("Changed %s to %s in %s", x, z, path_str)
216
247
  return obj
217
248
 
218
249
 
219
- def is_in_dir(filepath, directory):
250
+ def is_in_dir(
251
+ filepath: str | os.PathLike[str], directory: str | os.PathLike[str]
252
+ ) -> bool:
220
253
  """
221
254
  Determines whether a file is contained within a directory.
222
255
 
@@ -231,7 +264,11 @@ def is_in_dir(filepath, directory):
231
264
  return os.path.realpath(filepath).startswith(os.path.realpath(directory) + os.sep)
232
265
 
233
266
 
234
- def patch_rpath(path, new_rpath, only_relative=True):
267
+ def patch_rpath(
268
+ path: str | os.PathLike[str],
269
+ new_rpath: str,
270
+ only_relative: bool = True,
271
+ ) -> str | bool:
235
272
  """
236
273
  Patch the rpath of a given ELF file.
237
274
 
@@ -266,7 +303,12 @@ def patch_rpath(path, new_rpath, only_relative=True):
266
303
  return ":".join(old_rpath)
267
304
 
268
305
 
269
- def handle_elf(path, libs, rpath_only, root=None):
306
+ def handle_elf(
307
+ path: str | os.PathLike[str],
308
+ libs: str | os.PathLike[str],
309
+ rpath_only: bool,
310
+ root: str | os.PathLike[str] | None = None,
311
+ ) -> None:
270
312
  """
271
313
  Handle the parsing and pathcing of an ELF file.
272
314
 
@@ -333,8 +375,12 @@ def handle_elf(path, libs, rpath_only, root=None):
333
375
 
334
376
 
335
377
  def main(
336
- root, libs_dir=None, rpath_only=True, log_level="DEBUG", log_file_name="<stdout>"
337
- ):
378
+ root: str | os.PathLike[str],
379
+ libs_dir: str | os.PathLike[str] | None = None,
380
+ rpath_only: bool = True,
381
+ log_level: str = "DEBUG",
382
+ log_file_name: str = "<stdout>",
383
+ ) -> None:
338
384
  """
339
385
  The entrypoint into the relocate script.
340
386
 
@@ -347,24 +393,24 @@ def main(
347
393
  :param log_level: The level to log at, defaults to "INFO"
348
394
  :type log_level: str, optional
349
395
  """
396
+ level = logging.getLevelName(log_level.upper())
350
397
  if log_file_name != "<stdout>":
351
- kwargs = {
352
- "filename": log_file_name,
353
- "filemode": "w",
354
- }
398
+ logging.basicConfig(
399
+ level=level,
400
+ format="%(asctime)s %(message)s",
401
+ filename=log_file_name,
402
+ filemode="w",
403
+ )
355
404
  else:
356
- kwargs = {}
357
- logging.basicConfig(
358
- level=logging.getLevelName(log_level.upper()),
359
- format="%(asctime)s %(message)s",
360
- **kwargs,
361
- )
405
+ logging.basicConfig(
406
+ level=level,
407
+ format="%(asctime)s %(message)s",
408
+ )
362
409
  root_dir = str(pathlib.Path(root).resolve())
363
410
  if libs_dir is None:
364
411
  libs_dir = pathlib.Path(root_dir, "lib")
365
412
  libs_dir = str(pathlib.Path(libs_dir).resolve())
366
- rpath_only = rpath_only
367
- processed = {}
413
+ processed: dict[str, dict[str, list[str]] | None] = {}
368
414
  found = True
369
415
  while found:
370
416
  found = False