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/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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
193
|
-
|
|
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
|
|
197
|
-
log.info("Skipping dynamic load: %s",
|
|
226
|
+
if x.startswith("@"):
|
|
227
|
+
log.info("Skipping dynamic load: %s", x)
|
|
198
228
|
continue
|
|
199
229
|
if os.path.exists(x):
|
|
200
|
-
|
|
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,
|
|
240
|
+
log.info("Use %s to %s", y, path_str)
|
|
210
241
|
z = pathlib.Path("@loader_path") / os.path.relpath(
|
|
211
|
-
y,
|
|
242
|
+
y, path_obj.resolve().parent
|
|
212
243
|
)
|
|
213
|
-
cmd = ["install_name_tool", "-change", x, z,
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
"
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
367
|
-
processed = {}
|
|
413
|
+
processed: dict[str, dict[str, list[str]] | None] = {}
|
|
368
414
|
found = True
|
|
369
415
|
while found:
|
|
370
416
|
found = False
|