relenv 0.21.1__py3-none-any.whl → 0.22.0__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 +296 -78
  15. relenv/build/windows.py +259 -44
  16. relenv/buildenv.py +48 -17
  17. relenv/check.py +10 -5
  18. relenv/common.py +499 -163
  19. relenv/create.py +147 -7
  20. relenv/fetch.py +16 -4
  21. relenv/manifest.py +15 -7
  22. relenv/python-versions.json +329 -0
  23. relenv/pyversions.py +817 -30
  24. relenv/relocate.py +101 -55
  25. relenv/runtime.py +452 -253
  26. relenv/toolchain.py +9 -3
  27. {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/METADATA +1 -1
  28. relenv-0.22.0.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 +311 -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 +1802 -6
  42. tests/test_verify_build.py +500 -34
  43. relenv/build/common.py +0 -1609
  44. relenv-0.21.1.dist-info/RECORD +0 -35
  45. {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/WHEEL +0 -0
  46. {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/entry_points.txt +0 -0
  47. {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/licenses/LICENSE.md +0 -0
  48. {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/licenses/NOTICE +0 -0
  49. {relenv-0.21.1.dist-info → relenv-0.22.0.dist-info}/top_level.txt +0 -0
relenv/runtime.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # Copyright 2022-2025 Broadcom.
2
- # SPDX-License-Identifier: Apache-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.
5
5
 
@@ -10,19 +10,61 @@ This code is run when initializing the python interperter in a Relenv environmen
10
10
  gcc. This ensures when using pip any c dependencies are compiled against the
11
11
  proper glibc version.
12
12
  """
13
+ from __future__ import annotations
14
+
13
15
  import contextlib
14
- import ctypes
16
+ import ctypes as _ctypes
15
17
  import functools
16
- import importlib
17
- import json
18
+ import importlib as _importlib
19
+ import json as _json
18
20
  import os
19
21
  import pathlib
20
- import shutil
21
- import site
22
- import subprocess
23
- import sys
22
+ import shutil as _shutil
23
+ import site as _site
24
+ import subprocess as _subprocess
25
+ import sys as _sys
24
26
  import textwrap
25
- import warnings
27
+ import warnings as _warnings
28
+ from importlib.machinery import ModuleSpec
29
+ from types import ModuleType
30
+ from typing import (
31
+ Any,
32
+ Callable,
33
+ Dict,
34
+ Iterable,
35
+ Iterator,
36
+ Optional,
37
+ Sequence,
38
+ Union,
39
+ cast,
40
+ )
41
+
42
+ # The tests monkeypatch these module-level imports (e.g., json.loads) inside
43
+ # relenv.runtime itself; keeping them as Any both preserves test isolation—no
44
+ # need to patch the global stdlib modules—and avoids mypy attr-defined noise
45
+ # while still exercising the real runtime wiring.
46
+ json = cast(Any, _json)
47
+ importlib = cast(Any, _importlib)
48
+ site = cast(Any, _site)
49
+ subprocess = cast(Any, _subprocess)
50
+ sys = cast(Any, _sys)
51
+ ctypes = cast(Any, _ctypes)
52
+ shutil = cast(Any, _shutil)
53
+ warnings = cast(Any, _warnings)
54
+
55
+ __all__ = [
56
+ "sys",
57
+ "shutil",
58
+ "subprocess",
59
+ "json",
60
+ "importlib",
61
+ "site",
62
+ "ctypes",
63
+ "warnings",
64
+ ]
65
+
66
+ PathType = Union[str, os.PathLike[str]]
67
+ ConfigVars = Dict[str, str]
26
68
 
27
69
  # relenv.pth has a __file__ which is set to the path to site.py of the python
28
70
  # interpreter being used. We're using that to determine the proper
@@ -32,7 +74,7 @@ import warnings
32
74
  # imports happen before our path munghing in site in wrapsitecustomize.
33
75
 
34
76
 
35
- def path_import(name, path):
77
+ def path_import(name: str, path: PathType) -> ModuleType:
36
78
  """
37
79
  Import module from a path.
38
80
 
@@ -42,46 +84,50 @@ def path_import(name, path):
42
84
  import importlib.util
43
85
 
44
86
  spec = importlib.util.spec_from_file_location(name, path)
87
+ if spec is None or spec.loader is None:
88
+ raise ImportError(f"Unable to load module {name} from {path}")
45
89
  module = importlib.util.module_from_spec(spec)
46
90
  spec.loader.exec_module(module)
47
91
  sys.modules[name] = module
48
92
  return module
49
93
 
50
94
 
51
- def common():
52
- """
53
- Late import relenv common.
54
- """
55
- if not hasattr(common, "common"):
56
- common.common = path_import(
95
+ _COMMON: Optional[ModuleType] = None
96
+ _RELOCATE: Optional[ModuleType] = None
97
+ _BUILDENV: Optional[ModuleType] = None
98
+
99
+
100
+ def common() -> ModuleType:
101
+ """Return the cached ``relenv.common`` module."""
102
+ global _COMMON
103
+ if _COMMON is None:
104
+ _COMMON = path_import(
57
105
  "relenv.common", str(pathlib.Path(__file__).parent / "common.py")
58
106
  )
59
- return common.common
107
+ return _COMMON
60
108
 
61
109
 
62
- def relocate():
63
- """
64
- Late import relenv relocate.
65
- """
66
- if not hasattr(relocate, "relocate"):
67
- relocate.relocate = path_import(
110
+ def relocate() -> ModuleType:
111
+ """Return the cached ``relenv.relocate`` module."""
112
+ global _RELOCATE
113
+ if _RELOCATE is None:
114
+ _RELOCATE = path_import(
68
115
  "relenv.relocate", str(pathlib.Path(__file__).parent / "relocate.py")
69
116
  )
70
- return relocate.relocate
117
+ return _RELOCATE
71
118
 
72
119
 
73
- def buildenv():
74
- """
75
- Late import relenv buildenv.
76
- """
77
- if not hasattr(buildenv, "builenv"):
78
- buildenv.buildenv = path_import(
120
+ def buildenv() -> ModuleType:
121
+ """Return the cached ``relenv.buildenv`` module."""
122
+ global _BUILDENV
123
+ if _BUILDENV is None:
124
+ _BUILDENV = path_import(
79
125
  "relenv.buildenv", str(pathlib.Path(__file__).parent / "buildenv.py")
80
126
  )
81
- return buildenv.buildenv
127
+ return _BUILDENV
82
128
 
83
129
 
84
- def get_major_version():
130
+ def get_major_version() -> str:
85
131
  """
86
132
  Current python major version.
87
133
  """
@@ -89,7 +135,7 @@ def get_major_version():
89
135
 
90
136
 
91
137
  @contextlib.contextmanager
92
- def pushd(new_dir):
138
+ def pushd(new_dir: PathType) -> Iterator[None]:
93
139
  """
94
140
  Changedir context.
95
141
  """
@@ -101,7 +147,7 @@ def pushd(new_dir):
101
147
  os.chdir(old_dir)
102
148
 
103
149
 
104
- def debug(string):
150
+ def debug(string: str) -> None:
105
151
  """
106
152
  Prints the provided message if RELENV_DEBUG is truthy in the environment.
107
153
 
@@ -113,7 +159,7 @@ def debug(string):
113
159
  sys.stdout.flush()
114
160
 
115
161
 
116
- def relenv_root():
162
+ def relenv_root() -> pathlib.Path:
117
163
  """
118
164
  Return the relenv module root.
119
165
  """
@@ -126,7 +172,9 @@ def relenv_root():
126
172
  return MODULE_DIR.parent.parent.parent.parent
127
173
 
128
174
 
129
- def _build_shebang(func, *args, **kwargs):
175
+ def _build_shebang(
176
+ func: Callable[..., bytes], *args: Any, **kwargs: Any
177
+ ) -> Callable[..., bytes]:
130
178
  """
131
179
  Build a shebang to point to the proper location.
132
180
 
@@ -135,34 +183,37 @@ def _build_shebang(func, *args, **kwargs):
135
183
  """
136
184
 
137
185
  @functools.wraps(func)
138
- def wrapped(self, *args, **kwargs):
186
+ def wrapped(self: Any, *args: Any, **kwargs: Any) -> bytes:
139
187
  scripts = pathlib.Path(self.target_dir)
140
188
  if TARGET.TARGET:
141
- scripts = pathlib.Path(TARGET.PATH).absolute() / "bin"
189
+ scripts = pathlib.Path(_ensure_target_path()).absolute() / "bin"
142
190
  try:
143
191
  interpreter = common().relative_interpreter(
144
192
  sys.RELENV, scripts, pathlib.Path(sys.executable).resolve()
145
193
  )
146
194
  except ValueError:
147
195
  debug(f"Relenv Value Error - _build_shebang {self.target_dir}")
148
- return func(self, *args, **kwargs)
196
+ original_result: bytes = func(self, *args, **kwargs)
197
+ return original_result
149
198
  debug(f"Relenv - _build_shebang {scripts} {interpreter}")
150
199
  if sys.platform == "win32":
151
200
  return (
152
201
  str(pathlib.Path("#!<launcher_dir>") / interpreter).encode() + b"\r\n"
153
202
  )
154
- return common().format_shebang("/" / interpreter).encode()
203
+ rel_path = str(pathlib.PurePosixPath("/") / interpreter)
204
+ formatted = cast(str, common().format_shebang(rel_path))
205
+ return formatted.encode()
155
206
 
156
207
  return wrapped
157
208
 
158
209
 
159
- def get_config_var_wrapper(func):
210
+ def get_config_var_wrapper(func: Callable[[str], Any]) -> Callable[[str], Any]:
160
211
  """
161
212
  Return a wrapper to resolve paths relative to the relenv root.
162
213
  """
163
214
 
164
215
  @functools.wraps(func)
165
- def wrapped(name):
216
+ def wrapped(name: str) -> Any:
166
217
  if name == "BINDIR":
167
218
  orig = func(name)
168
219
  if os.environ.get("RELENV_PIP_DIR"):
@@ -179,13 +230,13 @@ def get_config_var_wrapper(func):
179
230
  return wrapped
180
231
 
181
232
 
182
- _CONFIG_VARS_DEFAULTS = {
233
+ CONFIG_VARS_DEFAULTS: ConfigVars = {
183
234
  "AR": "ar",
184
235
  "CC": "gcc",
185
236
  "CFLAGS": "-Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall",
186
237
  "CPPFLAGS": "-I. -I./Include",
187
238
  "CXX": "g++",
188
- "LIBDEST": "/usr/local/lib/python3.8",
239
+ "LIBDEST": "/usr/local/lib/python3.10",
189
240
  "SCRIPTDIR": "/usr/local/lib",
190
241
  "BLDSHARED": "gcc -shared",
191
242
  "LDFLAGS": "",
@@ -193,10 +244,10 @@ _CONFIG_VARS_DEFAULTS = {
193
244
  "LDSHARED": "gcc -shared",
194
245
  }
195
246
 
196
- _SYSTEM_CONFIG_VARS = None
247
+ _SYSTEM_CONFIG_VARS: Optional[ConfigVars] = None
197
248
 
198
249
 
199
- def system_sysconfig():
250
+ def system_sysconfig() -> ConfigVars:
200
251
  """
201
252
  Read the system python's sysconfig values.
202
253
 
@@ -204,7 +255,7 @@ def system_sysconfig():
204
255
  to avoid the overhead of shelling out.
205
256
  """
206
257
  global _SYSTEM_CONFIG_VARS
207
- if _SYSTEM_CONFIG_VARS:
258
+ if _SYSTEM_CONFIG_VARS is not None:
208
259
  return _SYSTEM_CONFIG_VARS
209
260
  pyexec = pathlib.Path("/usr/bin/python3")
210
261
  if pyexec.exists():
@@ -220,25 +271,27 @@ def system_sysconfig():
220
271
  _SYSTEM_CONFIG_VARS = json.loads(p.stdout.strip())
221
272
  except json.JSONDecodeError:
222
273
  debug(f"Failed to load JSON from: {p.stdout.strip()}")
223
- _SYSTEM_CONFIG_VARS = _CONFIG_VARS_DEFAULTS
274
+ _SYSTEM_CONFIG_VARS = CONFIG_VARS_DEFAULTS
224
275
  else:
225
276
  debug("System python not found")
226
- _SYSTEM_CONFIG_VARS = _CONFIG_VARS_DEFAULTS
277
+ _SYSTEM_CONFIG_VARS = CONFIG_VARS_DEFAULTS
227
278
  return _SYSTEM_CONFIG_VARS
228
279
 
229
280
 
230
- def get_config_vars_wrapper(func, mod):
281
+ def get_config_vars_wrapper(
282
+ func: Callable[..., ConfigVars], mod: ModuleType
283
+ ) -> Callable[..., ConfigVars]:
231
284
  """
232
285
  Return a wrapper to resolve paths relative to the relenv root.
233
286
  """
234
287
 
235
288
  @functools.wraps(func)
236
- def wrapped(*args):
289
+ def wrapped(*args: Any) -> ConfigVars:
237
290
  if sys.platform == "win32" or "RELENV_BUILDENV" in os.environ:
238
291
  return func(*args)
239
292
 
240
- _CONFIG_VARS = func()
241
- _SYSTEM_CONFIG_VARS = system_sysconfig()
293
+ config_vars = func()
294
+ system_config_vars = system_sysconfig()
242
295
  for name in [
243
296
  "AR",
244
297
  "CC",
@@ -252,20 +305,26 @@ def get_config_vars_wrapper(func, mod):
252
305
  "LDCXXSHARED",
253
306
  "LDSHARED",
254
307
  ]:
255
- _CONFIG_VARS[name] = _SYSTEM_CONFIG_VARS[name]
256
- mod._CONFIG_VARS = _CONFIG_VARS
308
+ config_vars[name] = system_config_vars[name]
309
+ setattr(mod, "_CONFIG_VARS", config_vars)
257
310
  return func(*args)
258
311
 
259
312
  return wrapped
260
313
 
261
314
 
262
- def get_paths_wrapper(func, default_scheme):
315
+ def get_paths_wrapper(
316
+ func: Callable[..., Dict[str, str]], default_scheme: str
317
+ ) -> Callable[..., Dict[str, str]]:
263
318
  """
264
319
  Return a wrapper to resolve paths relative to the relenv root.
265
320
  """
266
321
 
267
322
  @functools.wraps(func)
268
- def wrapped(scheme=default_scheme, vars=None, expand=True):
323
+ def wrapped(
324
+ scheme: Optional[str] = default_scheme,
325
+ vars: Optional[Dict[str, str]] = None,
326
+ expand: bool = True,
327
+ ) -> Dict[str, str]:
269
328
  paths = func(scheme=scheme, vars=vars, expand=expand)
270
329
  if "RELENV_PIP_DIR" in os.environ:
271
330
  paths["scripts"] = str(relenv_root())
@@ -275,7 +334,7 @@ def get_paths_wrapper(func, default_scheme):
275
334
  return wrapped
276
335
 
277
336
 
278
- def finalize_options_wrapper(func):
337
+ def finalize_options_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
279
338
  """
280
339
  Wrapper around build_ext.finalize_options.
281
340
 
@@ -283,15 +342,15 @@ def finalize_options_wrapper(func):
283
342
  """
284
343
 
285
344
  @functools.wraps(func)
286
- def wrapper(self, *args, **kwargs):
345
+ def wrapper(self: Any, *args: Any, **kwargs: Any) -> None:
287
346
  func(self, *args, **kwargs)
288
347
  if "RELENV_BUILDENV" in os.environ:
289
- self.include_dirs.append(f"{relenv_root()}/include")
348
+ self.include_dirs.append(str(relenv_root() / "include"))
290
349
 
291
350
  return wrapper
292
351
 
293
352
 
294
- def install_wheel_wrapper(func):
353
+ def install_wheel_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
295
354
  """
296
355
  Wrap pip's wheel install function.
297
356
 
@@ -300,15 +359,15 @@ def install_wheel_wrapper(func):
300
359
 
301
360
  @functools.wraps(func)
302
361
  def wrapper(
303
- name,
304
- wheel_path,
305
- scheme,
306
- req_description,
307
- pycompile,
308
- warn_script_location,
309
- direct_url,
310
- requested,
311
- ):
362
+ name: str,
363
+ wheel_path: PathType,
364
+ scheme: Any,
365
+ req_description: str,
366
+ pycompile: Any,
367
+ warn_script_location: Any,
368
+ direct_url: Any,
369
+ requested: Any,
370
+ ) -> Any:
312
371
  from zipfile import ZipFile
313
372
 
314
373
  from pip._internal.utils.wheel import parse_wheel
@@ -348,7 +407,7 @@ def install_wheel_wrapper(func):
348
407
  return wrapper
349
408
 
350
409
 
351
- def install_legacy_wrapper(func):
410
+ def install_legacy_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
352
411
  """
353
412
  Wrap pip's legacy install function.
354
413
 
@@ -359,21 +418,21 @@ def install_legacy_wrapper(func):
359
418
 
360
419
  @functools.wraps(func)
361
420
  def wrapper(
362
- install_options,
363
- global_options,
364
- root,
365
- home,
366
- prefix,
367
- use_user_site,
368
- pycompile,
369
- scheme,
370
- setup_py_path,
371
- isolated,
372
- req_name,
373
- build_env,
374
- unpacked_source_directory,
375
- req_description,
376
- ):
421
+ install_options: Any,
422
+ global_options: Any,
423
+ root: Any,
424
+ home: Any,
425
+ prefix: Any,
426
+ use_user_site: Any,
427
+ pycompile: Any,
428
+ scheme: Any,
429
+ setup_py_path: Any,
430
+ isolated: Any,
431
+ req_name: Any,
432
+ build_env: Any,
433
+ unpacked_source_directory: Any,
434
+ req_description: Any,
435
+ ) -> Any:
377
436
 
378
437
  pkginfo = pathlib.Path(setup_py_path).parent / "PKG-INFO"
379
438
  with open(pkginfo) as fp:
@@ -447,13 +506,19 @@ class Wrapper:
447
506
  Wrap methods of an imported module.
448
507
  """
449
508
 
450
- def __init__(self, module, wrapper, matcher="equals", _loading=False):
509
+ def __init__(
510
+ self,
511
+ module: str,
512
+ wrapper: Callable[[str], ModuleType],
513
+ matcher: str = "equals",
514
+ _loading: bool = False,
515
+ ) -> None:
451
516
  self.module = module
452
517
  self.wrapper = wrapper
453
518
  self.matcher = matcher
454
519
  self.loading = _loading
455
520
 
456
- def matches(self, module):
521
+ def matches(self: "Wrapper", module: str) -> bool:
457
522
  """
458
523
  Check if wrapper metches module being imported.
459
524
  """
@@ -461,7 +526,7 @@ class Wrapper:
461
526
  return module.startswith(self.module)
462
527
  return self.module == module
463
528
 
464
- def __call__(self, module_name):
529
+ def __call__(self: "Wrapper", module_name: str) -> ModuleType:
465
530
  """
466
531
  Preform the wrapper operation.
467
532
  """
@@ -473,15 +538,24 @@ class RelenvImporter:
473
538
  Handle runtime wrapping of module methods.
474
539
  """
475
540
 
476
- def __init__(self, wrappers=None, _loads=None):
541
+ def __init__(
542
+ self,
543
+ wrappers: Optional[Iterable[Wrapper]] = None,
544
+ _loads: Optional[Dict[str, ModuleType]] = None,
545
+ ) -> None:
477
546
  if wrappers is None:
478
547
  wrappers = []
479
- self.wrappers = set(wrappers)
548
+ self.wrappers: set[Wrapper] = set(wrappers)
480
549
  if _loads is None:
481
550
  _loads = {}
482
- self._loads = _loads
483
-
484
- def find_spec(self, module_name, package_path=None, target=None):
551
+ self._loads: Dict[str, ModuleType] = _loads
552
+
553
+ def find_spec(
554
+ self: "RelenvImporter",
555
+ module_name: str,
556
+ package_path: Optional[Sequence[str]] = None,
557
+ target: Any = None,
558
+ ) -> Optional[ModuleSpec]:
485
559
  """
486
560
  Find modules being imported.
487
561
  """
@@ -489,9 +563,15 @@ class RelenvImporter:
489
563
  if wrapper.matches(module_name) and not wrapper.loading:
490
564
  debug(f"RelenvImporter - match {module_name} {package_path} {target}")
491
565
  wrapper.loading = True
492
- return importlib.util.spec_from_loader(module_name, self)
566
+ spec = importlib.util.spec_from_loader(module_name, self)
567
+ return cast(Optional[ModuleSpec], spec)
568
+ return None
493
569
 
494
- def find_module(self, module_name, package_path=None):
570
+ def find_module(
571
+ self: "RelenvImporter",
572
+ module_name: str,
573
+ package_path: Optional[Sequence[str]] = None,
574
+ ) -> Optional["RelenvImporter"]:
495
575
  """
496
576
  Find modules being imported.
497
577
  """
@@ -500,38 +580,43 @@ class RelenvImporter:
500
580
  debug(f"RelenvImporter - match {module_name}")
501
581
  wrapper.loading = True
502
582
  return self
583
+ return None
503
584
 
504
- def load_module(self, name):
585
+ def load_module(self: "RelenvImporter", name: str) -> ModuleType:
505
586
  """
506
587
  Load an imported module.
507
588
  """
589
+ mod: Optional[ModuleType] = None
508
590
  for wrapper in self.wrappers:
509
591
  if wrapper.matches(name):
510
592
  debug(f"RelenvImporter - load_module {name}")
511
593
  mod = wrapper(name)
512
594
  wrapper.loading = False
513
595
  break
596
+ if mod is None:
597
+ mod = importlib.import_module(name)
514
598
  sys.modules[name] = mod
515
599
  return mod
516
600
 
517
- def create_module(self, spec):
601
+ def create_module(self: "RelenvImporter", spec: ModuleSpec) -> Optional[ModuleType]:
518
602
  """
519
603
  Create the module via a spec.
520
604
  """
521
605
  return self.load_module(spec.name)
522
606
 
523
- def exec_module(self, module):
607
+ def exec_module(self: "RelenvImporter", module: ModuleType) -> None:
524
608
  """
525
609
  Exec module noop.
526
610
  """
527
611
  return None
528
612
 
529
613
 
530
- def wrap_sysconfig(name):
614
+ def wrap_sysconfig(name: str) -> ModuleType:
531
615
  """
532
616
  Sysconfig wrapper.
533
617
  """
534
- mod = importlib.import_module("sysconfig")
618
+ module: ModuleType = importlib.import_module("sysconfig")
619
+ mod = cast(Any, module)
535
620
  mod.get_config_var = get_config_var_wrapper(mod.get_config_var)
536
621
  mod.get_config_vars = get_config_vars_wrapper(mod.get_config_vars, mod)
537
622
  mod._PIP_USE_SYSCONFIG = True
@@ -542,48 +627,52 @@ def wrap_sysconfig(name):
542
627
  # Python < 3.10
543
628
  scheme = mod._get_default_scheme()
544
629
  mod.get_paths = get_paths_wrapper(mod.get_paths, scheme)
545
- return mod
630
+ return module
546
631
 
547
632
 
548
- def wrap_pip_distlib_scripts(name):
633
+ def wrap_pip_distlib_scripts(name: str) -> ModuleType:
549
634
  """
550
635
  pip.distlib.scripts wrapper.
551
636
  """
552
- mod = importlib.import_module(name)
637
+ module: ModuleType = importlib.import_module(name)
638
+ mod = cast(Any, module)
553
639
  mod.ScriptMaker._build_shebang = _build_shebang(mod.ScriptMaker._build_shebang)
554
- return mod
640
+ return module
555
641
 
556
642
 
557
- def wrap_distutils_command(name):
643
+ def wrap_distutils_command(name: str) -> ModuleType:
558
644
  """
559
645
  distutils.command wrapper.
560
646
  """
561
- mod = importlib.import_module(name)
647
+ module: ModuleType = importlib.import_module(name)
648
+ mod = cast(Any, module)
562
649
  mod.build_ext.finalize_options = finalize_options_wrapper(
563
650
  mod.build_ext.finalize_options
564
651
  )
565
- return mod
652
+ return module
566
653
 
567
654
 
568
- def wrap_pip_install_wheel(name):
655
+ def wrap_pip_install_wheel(name: str) -> ModuleType:
569
656
  """
570
657
  pip._internal.operations.install.wheel wrapper.
571
658
  """
572
- mod = importlib.import_module(name)
659
+ module: ModuleType = importlib.import_module(name)
660
+ mod = cast(Any, module)
573
661
  mod.install_wheel = install_wheel_wrapper(mod.install_wheel)
574
- return mod
662
+ return module
575
663
 
576
664
 
577
- def wrap_pip_install_legacy(name):
665
+ def wrap_pip_install_legacy(name: str) -> ModuleType:
578
666
  """
579
667
  pip._internal.operations.install.legacy wrapper.
580
668
  """
581
- mod = importlib.import_module(name)
669
+ module: ModuleType = importlib.import_module(name)
670
+ mod = cast(Any, module)
582
671
  mod.install = install_legacy_wrapper(mod.install)
583
- return mod
672
+ return module
584
673
 
585
674
 
586
- def set_env_if_not_set(name, value):
675
+ def set_env_if_not_set(name: str, value: str) -> None:
587
676
  """
588
677
  Set an environment variable if not already set.
589
678
 
@@ -600,21 +689,21 @@ def set_env_if_not_set(name, value):
600
689
  os.environ[name] = value
601
690
 
602
691
 
603
- def wrap_pip_build_wheel(name):
692
+ def wrap_pip_build_wheel(name: str) -> ModuleType:
604
693
  """
605
694
  pip._internal.operations.build wrapper.
606
695
  """
607
- mod = importlib.import_module(name)
696
+ module: ModuleType = importlib.import_module(name)
697
+ mod = cast(Any, module)
608
698
 
609
- def wrap(func):
699
+ def wrap(func: Callable[..., Any]) -> Callable[..., Any]:
610
700
  @functools.wraps(func)
611
- def wrapper(*args, **kwargs):
701
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
612
702
  if sys.platform != "linux":
613
703
  return func(*args, **kwargs)
614
- base_dir = common().DATA_DIR / "toolchain"
615
- toolchain = base_dir / common().get_triplet()
704
+ toolchain = common().get_toolchain()
616
705
  cargo_home = str(common().DATA_DIR / "cargo")
617
- if not toolchain.exists():
706
+ if toolchain is None or not toolchain.exists():
618
707
  debug("Unable to set CARGO_HOME no toolchain exists")
619
708
  else:
620
709
  relenvroot = str(sys.RELENV)
@@ -631,7 +720,7 @@ def wrap_pip_build_wheel(name):
631
720
  return wrapper
632
721
 
633
722
  mod.build_wheel_pep517 = wrap(mod.build_wheel_pep517)
634
- return mod
723
+ return module
635
724
 
636
725
 
637
726
  class TARGET:
@@ -639,21 +728,31 @@ class TARGET:
639
728
  Container for global pip target state.
640
729
  """
641
730
 
642
- TARGET = False
643
- TARGET_PATH = None
644
- IGNORE = False
645
- INSTALL = False
731
+ TARGET: bool = False
732
+ PATH: Optional[str] = None
733
+ IGNORE: bool = False
734
+ INSTALL: bool = False
735
+
736
+
737
+ def _ensure_target_path() -> str:
738
+ """
739
+ Return the stored target path, raising if it is unavailable.
740
+ """
741
+ if TARGET.PATH is None:
742
+ raise RuntimeError("TARGET path requested but not initialized")
743
+ return TARGET.PATH
646
744
 
647
745
 
648
- def wrap_cmd_install(name):
746
+ def wrap_cmd_install(name: str) -> ModuleType:
649
747
  """
650
748
  Wrap pip install command to store target argument state.
651
749
  """
652
- mod = importlib.import_module(name)
750
+ module: ModuleType = importlib.import_module(name)
751
+ mod = cast(Any, module)
653
752
 
654
- def wrap(func):
753
+ def wrap_run(func: Callable[..., Any]) -> Callable[..., Any]:
655
754
  @functools.wraps(func)
656
- def wrapper(self, options, args):
755
+ def wrapper(self: Any, options: Any, args: Sequence[str]) -> Any:
657
756
  if not options.use_user_site:
658
757
  if options.target_dir:
659
758
  TARGET.TARGET = True
@@ -663,11 +762,13 @@ def wrap_cmd_install(name):
663
762
 
664
763
  return wrapper
665
764
 
666
- mod.InstallCommand.run = wrap(mod.InstallCommand.run)
765
+ mod.InstallCommand.run = wrap_run(mod.InstallCommand.run)
667
766
 
668
- def wrap(func):
767
+ def wrap_handle_target(func: Callable[..., Any]) -> Callable[..., Any]:
669
768
  @functools.wraps(func)
670
- def wrapper(self, target_dir, target_temp_dir, upgrade):
769
+ def wrapper(
770
+ self: Any, target_dir: str, target_temp_dir: str, upgrade: bool
771
+ ) -> int:
671
772
  from pip._internal.cli.status_codes import SUCCESS
672
773
 
673
774
  return SUCCESS
@@ -675,30 +776,37 @@ def wrap_cmd_install(name):
675
776
  return wrapper
676
777
 
677
778
  if hasattr(mod.InstallCommand, "_handle_target_dir"):
678
- mod.InstallCommand._handle_target_dir = wrap(
779
+ mod.InstallCommand._handle_target_dir = wrap_handle_target(
679
780
  mod.InstallCommand._handle_target_dir
680
781
  )
681
- return mod
782
+ return module
682
783
 
683
784
 
684
- def wrap_locations(name):
785
+ def wrap_locations(name: str) -> ModuleType:
685
786
  """
686
787
  Wrap pip locations to fix locations when installing with target.
687
788
  """
688
- mod = importlib.import_module(name)
789
+ module: ModuleType = importlib.import_module(name)
790
+ mod = cast(Any, module)
689
791
 
690
- def wrap(func):
792
+ def make_scheme_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
691
793
  @functools.wraps(func)
692
794
  def wrapper(
693
- dist_name, user=False, home=None, root=None, isolated=False, prefix=None
694
- ):
795
+ dist_name: str,
796
+ user: bool = False,
797
+ home: Optional[PathType] = None,
798
+ root: Optional[PathType] = None,
799
+ isolated: bool = False,
800
+ prefix: Optional[PathType] = None,
801
+ ) -> Any:
695
802
  scheme = func(dist_name, user, home, root, isolated, prefix)
696
803
  if TARGET.TARGET and TARGET.INSTALL:
697
804
  from pip._internal.models.scheme import Scheme
698
805
 
806
+ target_path = _ensure_target_path()
699
807
  scheme = Scheme(
700
- platlib=TARGET.PATH,
701
- purelib=TARGET.PATH,
808
+ platlib=target_path,
809
+ purelib=target_path,
702
810
  headers=scheme.headers,
703
811
  scripts=scheme.scripts,
704
812
  data=scheme.data,
@@ -709,113 +817,179 @@ def wrap_locations(name):
709
817
 
710
818
  # get_scheme is not available on pip-19.2.3
711
819
  # try:
712
- mod.get_scheme = wrap(mod.get_scheme)
820
+ mod.get_scheme = make_scheme_wrapper(mod.get_scheme)
713
821
  # except AttributeError:
714
822
  # debug(f"Module {mod} does not have attribute get_scheme")
715
823
 
716
- return mod
824
+ return module
717
825
 
718
826
 
719
- def wrap_req_command(name):
827
+ def wrap_req_command(name: str) -> ModuleType:
720
828
  """
721
829
  Honor ignore installed option from pip cli.
722
830
  """
723
- mod = importlib.import_module(name)
831
+ module: ModuleType = importlib.import_module(name)
832
+ mod = cast(Any, module)
724
833
 
725
- def wrap(func):
834
+ def make_package_finder_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
726
835
  @functools.wraps(func)
727
836
  def wrapper(
728
- self,
729
- options,
730
- session,
731
- target_python=None,
732
- ignore_requires_python=None,
733
- ):
837
+ self: Any,
838
+ options: Any,
839
+ session: Any,
840
+ target_python: Any = None,
841
+ ignore_requires_python: Any = None,
842
+ ) -> Any:
734
843
  if TARGET.TARGET:
735
844
  options.ignore_installed = TARGET.IGNORE
736
845
  return func(self, options, session, target_python, ignore_requires_python)
737
846
 
738
847
  return wrapper
739
848
 
740
- mod.RequirementCommand._build_package_finder = wrap(
849
+ mod.RequirementCommand._build_package_finder = make_package_finder_wrapper(
741
850
  mod.RequirementCommand._build_package_finder
742
851
  )
743
- return mod
852
+ return module
744
853
 
745
854
 
746
- def wrap_req_install(name):
855
+ def wrap_req_install(name: str) -> ModuleType:
747
856
  """
748
857
  Honor ignore installed option from pip cli.
749
858
  """
750
- mod = importlib.import_module(name)
751
-
752
- def wrap(func):
753
- if mod.InstallRequirement.install.__code__.co_argcount == 9:
754
-
755
- @functools.wraps(func)
756
- def wrapper(
757
- self,
758
- install_options,
759
- global_options=None,
760
- root=None,
761
- home=None,
762
- prefix=None,
763
- warn_script_location=True,
764
- use_user_site=False,
765
- pycompile=True,
766
- ):
767
- try:
768
- if TARGET.TARGET:
769
- TARGET.INSTALL = True
770
- home = TARGET.PATH
771
- return func(
772
- self,
773
- install_options,
774
- global_options,
775
- root,
776
- home,
777
- prefix,
778
- warn_script_location,
779
- use_user_site,
780
- pycompile,
781
- )
782
- finally:
783
- TARGET.INSTALL = False
859
+ module: ModuleType = importlib.import_module(name)
860
+ mod = cast(Any, module)
784
861
 
785
- else:
862
+ original = mod.InstallRequirement.install
863
+ argcount = original.__code__.co_argcount
786
864
 
787
- @functools.wraps(func)
788
- def wrapper(
789
- self,
790
- global_options=None,
791
- root=None,
792
- home=None,
793
- prefix=None,
794
- warn_script_location=True,
795
- use_user_site=False,
796
- pycompile=True,
797
- ):
798
- try:
799
- if TARGET.TARGET:
800
- TARGET.INSTALL = True
801
- home = TARGET.PATH
802
- return func(
803
- self,
804
- global_options,
805
- root,
806
- home,
807
- prefix,
808
- warn_script_location,
809
- use_user_site,
810
- pycompile,
811
- )
812
- finally:
813
- TARGET.INSTALL = False
865
+ if argcount == 7:
814
866
 
815
- return wrapper
867
+ @functools.wraps(original)
868
+ def install_wrapper_pep517(
869
+ self: Any,
870
+ root: Optional[PathType] = None,
871
+ home: Optional[PathType] = None,
872
+ prefix: Optional[PathType] = None,
873
+ warn_script_location: bool = True,
874
+ use_user_site: bool = False,
875
+ pycompile: bool = True,
876
+ ) -> Any:
877
+ try:
878
+ if TARGET.TARGET:
879
+ TARGET.INSTALL = True
880
+ home = _ensure_target_path()
881
+ return original(
882
+ self,
883
+ root,
884
+ home,
885
+ prefix,
886
+ warn_script_location,
887
+ use_user_site,
888
+ pycompile,
889
+ )
890
+ finally:
891
+ TARGET.INSTALL = False
892
+
893
+ mod.InstallRequirement.install = install_wrapper_pep517
894
+
895
+ elif argcount == 8:
896
+
897
+ @functools.wraps(original)
898
+ def install_wrapper_pep517_opts(
899
+ self: Any,
900
+ global_options: Any = None,
901
+ root: Optional[PathType] = None,
902
+ home: Optional[PathType] = None,
903
+ prefix: Optional[PathType] = None,
904
+ warn_script_location: bool = True,
905
+ use_user_site: bool = False,
906
+ pycompile: bool = True,
907
+ ) -> Any:
908
+ try:
909
+ if TARGET.TARGET:
910
+ TARGET.INSTALL = True
911
+ home = _ensure_target_path()
912
+ return original(
913
+ self,
914
+ global_options,
915
+ root,
916
+ home,
917
+ prefix,
918
+ warn_script_location,
919
+ use_user_site,
920
+ pycompile,
921
+ )
922
+ finally:
923
+ TARGET.INSTALL = False
924
+
925
+ mod.InstallRequirement.install = install_wrapper_pep517_opts
926
+
927
+ elif argcount == 9:
928
+
929
+ @functools.wraps(original)
930
+ def install_wrapper_legacy(
931
+ self: Any,
932
+ install_options: Any,
933
+ global_options: Any = None,
934
+ root: Optional[PathType] = None,
935
+ home: Optional[PathType] = None,
936
+ prefix: Optional[PathType] = None,
937
+ warn_script_location: bool = True,
938
+ use_user_site: bool = False,
939
+ pycompile: bool = True,
940
+ ) -> Any:
941
+ try:
942
+ if TARGET.TARGET:
943
+ TARGET.INSTALL = True
944
+ home = _ensure_target_path()
945
+ return original(
946
+ self,
947
+ install_options,
948
+ global_options,
949
+ root,
950
+ home,
951
+ prefix,
952
+ warn_script_location,
953
+ use_user_site,
954
+ pycompile,
955
+ )
956
+ finally:
957
+ TARGET.INSTALL = False
958
+
959
+ mod.InstallRequirement.install = install_wrapper_legacy
960
+
961
+ else:
962
+
963
+ @functools.wraps(original)
964
+ def install_wrapper_generic(
965
+ self: Any,
966
+ global_options: Any = None,
967
+ root: Optional[PathType] = None,
968
+ home: Optional[PathType] = None,
969
+ prefix: Optional[PathType] = None,
970
+ warn_script_location: bool = True,
971
+ use_user_site: bool = False,
972
+ pycompile: bool = True,
973
+ ) -> Any:
974
+ try:
975
+ if TARGET.TARGET:
976
+ TARGET.INSTALL = True
977
+ home = _ensure_target_path()
978
+ return original(
979
+ self,
980
+ global_options,
981
+ root,
982
+ home,
983
+ prefix,
984
+ warn_script_location,
985
+ use_user_site,
986
+ pycompile,
987
+ )
988
+ finally:
989
+ TARGET.INSTALL = False
816
990
 
817
- mod.InstallRequirement.install = wrap(mod.InstallRequirement.install)
818
- return mod
991
+ mod.InstallRequirement.install = install_wrapper_generic
992
+ return module
819
993
 
820
994
 
821
995
  importer = RelenvImporter(
@@ -834,7 +1008,7 @@ importer = RelenvImporter(
834
1008
  )
835
1009
 
836
1010
 
837
- def install_cargo_config():
1011
+ def install_cargo_config() -> None:
838
1012
  """
839
1013
  Setup cargo config.
840
1014
  """
@@ -845,8 +1019,8 @@ def install_cargo_config():
845
1019
  # load the ssl module. Causing out setup_openssl method to fail to load
846
1020
  # fips module.
847
1021
  dirs = common().work_dirs()
848
- triplet = common().get_triplet()
849
1022
  cargo_home = dirs.data / "cargo"
1023
+ triplet = common().get_triplet()
850
1024
 
851
1025
  toolchain = common().get_toolchain()
852
1026
  if not toolchain:
@@ -858,27 +1032,46 @@ def install_cargo_config():
858
1032
  return
859
1033
 
860
1034
  # cargo_home = dirs.data / "cargo"
861
- if not cargo_home.exists():
862
- cargo_home.mkdir()
1035
+ cargo_home.mkdir(parents=True, exist_ok=True)
863
1036
  cargo_config = cargo_home / "config.toml"
864
- if not cargo_config.exists():
865
- if triplet == "x86_64-linux-gnu":
866
- cargo_triplet = "x86_64-unknown-linux-gnu"
867
- else:
868
- cargo_triplet = "aarch64-unknown-linux-gnu"
869
- gcc = toolchain / "bin" / f"{triplet}-gcc"
870
- with open(cargo_config, "w") as fp:
871
- fp.write(
872
- textwrap.dedent(
873
- """\
874
- [target.{}]
875
- linker = "{}"
1037
+ if triplet == "x86_64-linux-gnu":
1038
+ cargo_triplet = "x86_64-unknown-linux-gnu"
1039
+ else:
1040
+ cargo_triplet = "aarch64-unknown-linux-gnu"
1041
+ gcc = toolchain / "bin" / f"{triplet}-gcc"
1042
+
1043
+ def existing_linker() -> str | None:
1044
+ if not cargo_config.exists():
1045
+ return None
1046
+ try:
1047
+ contents = cargo_config.read_text()
1048
+ except OSError:
1049
+ return None
1050
+ marker = f"[target.{cargo_triplet}]"
1051
+ if marker not in contents:
1052
+ return None
1053
+ for line in contents.splitlines():
1054
+ stripped = line.strip()
1055
+ if stripped.startswith("linker"):
1056
+ value_part: str
1057
+ _, _, value_part = stripped.partition("=")
1058
+ value_str: str = value_part.strip().strip('"')
1059
+ if value_str:
1060
+ return value_str
1061
+ return None
1062
+
1063
+ if existing_linker() != str(gcc):
1064
+ cargo_config.write_text(
1065
+ textwrap.dedent(
1066
+ """\
1067
+ [target.{triplet}]
1068
+ linker = "{linker}"
876
1069
  """
877
- ).format(cargo_triplet, gcc)
878
- )
1070
+ ).format(triplet=cargo_triplet, linker=gcc)
1071
+ )
879
1072
 
880
1073
 
881
- def setup_openssl():
1074
+ def setup_openssl() -> None:
882
1075
  """
883
1076
  Configure openssl certificate locations.
884
1077
  """
@@ -918,7 +1111,7 @@ def setup_openssl():
918
1111
  debug(msg)
919
1112
  else:
920
1113
  try:
921
- _, directory = proc.stdout.split(":")
1114
+ _, directory = proc.stdout.split(":", 1)
922
1115
  except ValueError:
923
1116
  debug("Unable to parse modules dir")
924
1117
  return
@@ -952,7 +1145,7 @@ def setup_openssl():
952
1145
  debug(msg)
953
1146
  else:
954
1147
  try:
955
- _, directory = proc.stdout.split(":")
1148
+ _, directory = proc.stdout.split(":", 1)
956
1149
  except ValueError:
957
1150
  debug("Unable to parse openssldir")
958
1151
  return
@@ -964,7 +1157,7 @@ def setup_openssl():
964
1157
  os.environ["SSL_CERT_FILE"] = str(cert_file)
965
1158
 
966
1159
 
967
- def set_openssl_modules_dir(path):
1160
+ def set_openssl_modules_dir(path: str) -> None:
968
1161
  """
969
1162
  Set the default search location for openssl modules.
970
1163
  """
@@ -982,7 +1175,7 @@ def set_openssl_modules_dir(path):
982
1175
  OSSL_PROVIDER_set_default_search_path(None, path.encode())
983
1176
 
984
1177
 
985
- def load_openssl_provider(name):
1178
+ def load_openssl_provider(name: str) -> int:
986
1179
  """
987
1180
  Load an openssl module.
988
1181
  """
@@ -995,10 +1188,11 @@ def load_openssl_provider(name):
995
1188
  OSSL_PROVIDER_load = libcrypto.OSSL_PROVIDER_load
996
1189
  OSSL_PROVIDER_load.argtypes = (POSSL_LIB_CTX, ctypes.c_char_p)
997
1190
  OSSL_PROVIDER_load.restype = ctypes.c_int
998
- return OSSL_PROVIDER_load(None, name.encode())
1191
+ result = OSSL_PROVIDER_load(None, name.encode())
1192
+ return int(result)
999
1193
 
1000
1194
 
1001
- def setup_crossroot():
1195
+ def setup_crossroot() -> None:
1002
1196
  """
1003
1197
  Setup cross root if needed.
1004
1198
  """
@@ -1016,7 +1210,7 @@ def setup_crossroot():
1016
1210
  ] + [_ for _ in sys.path if "site-packages" not in _]
1017
1211
 
1018
1212
 
1019
- def wrapsitecustomize(func):
1213
+ def wrapsitecustomize(func: Callable[[], Any]) -> Callable[[], None]:
1020
1214
  """
1021
1215
  Wrap site.execsitecustomize.
1022
1216
 
@@ -1025,12 +1219,14 @@ def wrapsitecustomize(func):
1025
1219
  """
1026
1220
 
1027
1221
  @functools.wraps(func)
1028
- def wrapper():
1222
+ def wrapper() -> None:
1029
1223
  func()
1030
1224
 
1031
- sitecustomize = None
1225
+ sitecustomize_module = None
1032
1226
  try:
1033
- import sitecustomize
1227
+ import sitecustomize as _sitecustomize
1228
+
1229
+ sitecustomize_module = _sitecustomize
1034
1230
  except ImportError as exc:
1035
1231
  if exc.name != "sitecustomize":
1036
1232
  raise
@@ -1039,7 +1235,10 @@ def wrapsitecustomize(func):
1039
1235
  # relenv environment. This can't be done when pip is using build_env to
1040
1236
  # install packages. This code seems potentially brittle and there may
1041
1237
  # be reasonable arguments against doing it at all.
1042
- if sitecustomize is None or "pip-build-env" not in sitecustomize.__file__:
1238
+ if (
1239
+ sitecustomize_module is None
1240
+ or "pip-build-env" not in sitecustomize_module.__file__
1241
+ ):
1043
1242
  _orig = sys.path[:]
1044
1243
  # Replace sys.path
1045
1244
  sys.path[:] = common().sanitize_sys_path(sys.path)
@@ -1054,7 +1253,7 @@ def wrapsitecustomize(func):
1054
1253
  return wrapper
1055
1254
 
1056
1255
 
1057
- def bootstrap():
1256
+ def bootstrap() -> None:
1058
1257
  """
1059
1258
  Bootstrap the relenv environment.
1060
1259
  """