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/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,142 +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 == 7:
754
-
755
- @functools.wraps(func)
756
- def wrapper(
757
- self,
758
- root=None,
759
- home=None,
760
- prefix=None,
761
- warn_script_location=True,
762
- use_user_site=False,
763
- pycompile=True,
764
- ):
765
- try:
766
- if TARGET.TARGET:
767
- TARGET.INSTALL = True
768
- home = TARGET.PATH
769
- return func(
770
- self,
771
- root,
772
- home,
773
- prefix,
774
- warn_script_location,
775
- use_user_site,
776
- pycompile,
777
- )
778
- finally:
779
- TARGET.INSTALL = False
780
-
781
- elif mod.InstallRequirement.install.__code__.co_argcount == 8:
782
-
783
- @functools.wraps(func)
784
- def wrapper(
785
- self,
786
- global_options=None,
787
- root=None,
788
- home=None,
789
- prefix=None,
790
- warn_script_location=True,
791
- use_user_site=False,
792
- pycompile=True,
793
- ):
794
- try:
795
- if TARGET.TARGET:
796
- TARGET.INSTALL = True
797
- home = TARGET.PATH
798
- return func(
799
- self,
800
- global_options,
801
- root,
802
- home,
803
- prefix,
804
- warn_script_location,
805
- use_user_site,
806
- pycompile,
807
- )
808
- finally:
809
- TARGET.INSTALL = False
859
+ module: ModuleType = importlib.import_module(name)
860
+ mod = cast(Any, module)
810
861
 
811
- else:
812
- # Oldest version of this method sigature with 9 arguments.
813
-
814
- @functools.wraps(func)
815
- def wrapper(
816
- self,
817
- install_options,
818
- global_options=None,
819
- root=None,
820
- home=None,
821
- prefix=None,
822
- warn_script_location=True,
823
- use_user_site=False,
824
- pycompile=True,
825
- ):
826
- try:
827
- if TARGET.TARGET:
828
- TARGET.INSTALL = True
829
- home = TARGET.PATH
830
- return func(
831
- self,
832
- install_options,
833
- global_options,
834
- root,
835
- home,
836
- prefix,
837
- warn_script_location,
838
- use_user_site,
839
- pycompile,
840
- )
841
- finally:
842
- TARGET.INSTALL = False
862
+ original = mod.InstallRequirement.install
863
+ argcount = original.__code__.co_argcount
843
864
 
844
- return wrapper
865
+ if argcount == 7:
866
+
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
845
990
 
846
- mod.InstallRequirement.install = wrap(mod.InstallRequirement.install)
847
- return mod
991
+ mod.InstallRequirement.install = install_wrapper_generic
992
+ return module
848
993
 
849
994
 
850
995
  importer = RelenvImporter(
@@ -863,7 +1008,7 @@ importer = RelenvImporter(
863
1008
  )
864
1009
 
865
1010
 
866
- def install_cargo_config():
1011
+ def install_cargo_config() -> None:
867
1012
  """
868
1013
  Setup cargo config.
869
1014
  """
@@ -887,27 +1032,46 @@ def install_cargo_config():
887
1032
  return
888
1033
 
889
1034
  # cargo_home = dirs.data / "cargo"
890
- if not cargo_home.exists():
891
- cargo_home.mkdir()
1035
+ cargo_home.mkdir(parents=True, exist_ok=True)
892
1036
  cargo_config = cargo_home / "config.toml"
893
- if not cargo_config.exists():
894
- if triplet == "x86_64-linux-gnu":
895
- cargo_triplet = "x86_64-unknown-linux-gnu"
896
- else:
897
- cargo_triplet = "aarch64-unknown-linux-gnu"
898
- gcc = toolchain / "bin" / f"{triplet}-gcc"
899
- with open(cargo_config, "w") as fp:
900
- fp.write(
901
- textwrap.dedent(
902
- """\
903
- [target.{}]
904
- 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}"
905
1069
  """
906
- ).format(cargo_triplet, gcc)
907
- )
1070
+ ).format(triplet=cargo_triplet, linker=gcc)
1071
+ )
908
1072
 
909
1073
 
910
- def setup_openssl():
1074
+ def setup_openssl() -> None:
911
1075
  """
912
1076
  Configure openssl certificate locations.
913
1077
  """
@@ -947,7 +1111,7 @@ def setup_openssl():
947
1111
  debug(msg)
948
1112
  else:
949
1113
  try:
950
- _, directory = proc.stdout.split(":")
1114
+ _, directory = proc.stdout.split(":", 1)
951
1115
  except ValueError:
952
1116
  debug("Unable to parse modules dir")
953
1117
  return
@@ -981,7 +1145,7 @@ def setup_openssl():
981
1145
  debug(msg)
982
1146
  else:
983
1147
  try:
984
- _, directory = proc.stdout.split(":")
1148
+ _, directory = proc.stdout.split(":", 1)
985
1149
  except ValueError:
986
1150
  debug("Unable to parse openssldir")
987
1151
  return
@@ -993,7 +1157,7 @@ def setup_openssl():
993
1157
  os.environ["SSL_CERT_FILE"] = str(cert_file)
994
1158
 
995
1159
 
996
- def set_openssl_modules_dir(path):
1160
+ def set_openssl_modules_dir(path: str) -> None:
997
1161
  """
998
1162
  Set the default search location for openssl modules.
999
1163
  """
@@ -1011,7 +1175,7 @@ def set_openssl_modules_dir(path):
1011
1175
  OSSL_PROVIDER_set_default_search_path(None, path.encode())
1012
1176
 
1013
1177
 
1014
- def load_openssl_provider(name):
1178
+ def load_openssl_provider(name: str) -> int:
1015
1179
  """
1016
1180
  Load an openssl module.
1017
1181
  """
@@ -1024,10 +1188,11 @@ def load_openssl_provider(name):
1024
1188
  OSSL_PROVIDER_load = libcrypto.OSSL_PROVIDER_load
1025
1189
  OSSL_PROVIDER_load.argtypes = (POSSL_LIB_CTX, ctypes.c_char_p)
1026
1190
  OSSL_PROVIDER_load.restype = ctypes.c_int
1027
- return OSSL_PROVIDER_load(None, name.encode())
1191
+ result = OSSL_PROVIDER_load(None, name.encode())
1192
+ return int(result)
1028
1193
 
1029
1194
 
1030
- def setup_crossroot():
1195
+ def setup_crossroot() -> None:
1031
1196
  """
1032
1197
  Setup cross root if needed.
1033
1198
  """
@@ -1045,7 +1210,7 @@ def setup_crossroot():
1045
1210
  ] + [_ for _ in sys.path if "site-packages" not in _]
1046
1211
 
1047
1212
 
1048
- def wrapsitecustomize(func):
1213
+ def wrapsitecustomize(func: Callable[[], Any]) -> Callable[[], None]:
1049
1214
  """
1050
1215
  Wrap site.execsitecustomize.
1051
1216
 
@@ -1054,12 +1219,14 @@ def wrapsitecustomize(func):
1054
1219
  """
1055
1220
 
1056
1221
  @functools.wraps(func)
1057
- def wrapper():
1222
+ def wrapper() -> None:
1058
1223
  func()
1059
1224
 
1060
- sitecustomize = None
1225
+ sitecustomize_module = None
1061
1226
  try:
1062
- import sitecustomize
1227
+ import sitecustomize as _sitecustomize
1228
+
1229
+ sitecustomize_module = _sitecustomize
1063
1230
  except ImportError as exc:
1064
1231
  if exc.name != "sitecustomize":
1065
1232
  raise
@@ -1068,7 +1235,10 @@ def wrapsitecustomize(func):
1068
1235
  # relenv environment. This can't be done when pip is using build_env to
1069
1236
  # install packages. This code seems potentially brittle and there may
1070
1237
  # be reasonable arguments against doing it at all.
1071
- 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
+ ):
1072
1242
  _orig = sys.path[:]
1073
1243
  # Replace sys.path
1074
1244
  sys.path[:] = common().sanitize_sys_path(sys.path)
@@ -1083,7 +1253,7 @@ def wrapsitecustomize(func):
1083
1253
  return wrapper
1084
1254
 
1085
1255
 
1086
- def bootstrap():
1256
+ def bootstrap() -> None:
1087
1257
  """
1088
1258
  Bootstrap the relenv environment.
1089
1259
  """
@@ -1100,3 +1270,8 @@ def bootstrap():
1100
1270
  setup_crossroot()
1101
1271
  install_cargo_config()
1102
1272
  sys.meta_path = [importer] + sys.meta_path
1273
+
1274
+ # For Python 3.13+, sysconfig became a package so the importer doesn't
1275
+ # intercept it. Manually wrap it here.
1276
+ if sys.version_info >= (3, 13):
1277
+ wrap_sysconfig("sysconfig")