portable-python 1.8.4__tar.gz → 1.8.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {portable-python-1.8.4/src/portable_python.egg-info → portable-python-1.8.5}/PKG-INFO +1 -1
- portable-python-1.8.5/pyproject.toml +76 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/setup.py +0 -1
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/__init__.py +51 -36
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/cli.py +7 -7
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/config.py +68 -42
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/cpython.py +41 -31
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/external/_inspect.py +10 -11
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/external/tkinter.py +3 -2
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/external/xcpython.py +29 -12
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/inspector.py +21 -17
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/tracking.py +0 -3
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/versions.py +31 -9
- {portable-python-1.8.4 → portable-python-1.8.5/src/portable_python.egg-info}/PKG-INFO +1 -1
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_cleanup.py +1 -1
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_inspector.py +1 -1
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_list.py +6 -5
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_setup.py +7 -6
- portable-python-1.8.4/pyproject.toml +0 -2
- {portable-python-1.8.4 → portable-python-1.8.5}/DEVELOP.md +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/LICENSE +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/MANIFEST.in +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/README.rst +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/SECURITY.md +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/requirements.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/setup.cfg +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/__main__.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python/external/__init__.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/SOURCES.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/dependency_links.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/entry_points.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/requires.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/top_level.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_build.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_failed.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_invoker.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_prefix.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_recompress.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.5}/tests/test_report.py +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
|
|
4
|
+
[tool.ruff]
|
|
5
|
+
cache-dir = ".tox/.ruff_cache"
|
|
6
|
+
line-length = 140
|
|
7
|
+
src = ["src", "tests"]
|
|
8
|
+
|
|
9
|
+
[tool.ruff.lint]
|
|
10
|
+
extend-select = [
|
|
11
|
+
"A", # flake8-builtins
|
|
12
|
+
# "ARG", # flake8-unused-arguments
|
|
13
|
+
"B", # flake8-bugbear
|
|
14
|
+
"C4", # flake8-comprehensions
|
|
15
|
+
"C90", # mccabe
|
|
16
|
+
"D", # pydocstyle
|
|
17
|
+
"DTZ", # flake8-datetimez
|
|
18
|
+
"E", # pycodestyle errors
|
|
19
|
+
"EM", # flake8-errmsg
|
|
20
|
+
"ERA", # eradicate
|
|
21
|
+
"EXE", # flake8-executable
|
|
22
|
+
"F", # pyflakes
|
|
23
|
+
"FLY", # flynt
|
|
24
|
+
"G", # flake8-logging-format
|
|
25
|
+
"I", # isort
|
|
26
|
+
"INT", # flake8-gettext
|
|
27
|
+
"PGH", # pygrep-hooks
|
|
28
|
+
"PIE", # flake8-pie
|
|
29
|
+
"PT", # flake8-pytest
|
|
30
|
+
"PYI", # flake8-pyi
|
|
31
|
+
"Q", # flake8-quotes
|
|
32
|
+
"RSE", # flake8-raise
|
|
33
|
+
"RET", # flake8-return
|
|
34
|
+
"RUF", # ruff-specific
|
|
35
|
+
"S", # flake8-bandit
|
|
36
|
+
# "SIM", # flake8-simplify
|
|
37
|
+
"SLF", # flake8-self
|
|
38
|
+
"SLOT", # flake8-slots
|
|
39
|
+
"T10", # flake8-debugger
|
|
40
|
+
"TID", # flake8-tidy-imports
|
|
41
|
+
"TCH", # flake8-type-checking
|
|
42
|
+
# "TD", # flake8-todos
|
|
43
|
+
"TRY", # tryceratops
|
|
44
|
+
"W", # pycodestyle warnings
|
|
45
|
+
]
|
|
46
|
+
ignore = [
|
|
47
|
+
# TODO: gradually remove these (document all the things)
|
|
48
|
+
"D100", # Missing docstring in public module
|
|
49
|
+
"D101", # Missing docstring in public class
|
|
50
|
+
"D102", # Missing docstring in public method
|
|
51
|
+
"D103", # Missing docstring in public function
|
|
52
|
+
"D104", # Missing docstring in public package
|
|
53
|
+
"D105", # Missing docstring in magic method
|
|
54
|
+
"D200", # One-line docstring should fit on one line with quotes
|
|
55
|
+
"D205", # 1 blank line required between summary line and description
|
|
56
|
+
"D400", # First line should end with a period
|
|
57
|
+
# Not useful:
|
|
58
|
+
"RET503", # Missing explicit `return` at the end of function able to return non-`None` value
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.ruff.lint.flake8-builtins]
|
|
62
|
+
builtins-ignorelist = ["bin", "compile"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint.isort]
|
|
65
|
+
order-by-type = false
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.mccabe]
|
|
68
|
+
max-complexity = 14
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint.pydocstyle]
|
|
71
|
+
convention = "numpy"
|
|
72
|
+
|
|
73
|
+
[tool.ruff.per-file-ignores]
|
|
74
|
+
"tests/*" = [
|
|
75
|
+
"S", # No security checks for tests
|
|
76
|
+
]
|
|
@@ -15,7 +15,7 @@ import multiprocessing
|
|
|
15
15
|
import os
|
|
16
16
|
import pathlib
|
|
17
17
|
import re
|
|
18
|
-
from typing import List
|
|
18
|
+
from typing import ClassVar, List
|
|
19
19
|
|
|
20
20
|
import runez
|
|
21
21
|
from runez.http import RestClient
|
|
@@ -24,7 +24,6 @@ from runez.render import Header, PrettyTable
|
|
|
24
24
|
|
|
25
25
|
from portable_python.versions import PPG
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
LOG = logging.getLogger(__name__)
|
|
29
28
|
RX_BINARY = re.compile(r"^.*\.(dylib|gmo|icns|ico|nib|prof.*|tar)$")
|
|
30
29
|
|
|
@@ -36,11 +35,16 @@ def is_binary_file(path):
|
|
|
36
35
|
def patch_folder(folder, regex, replacement, ignore=None):
|
|
37
36
|
"""Replace all occurrences of 'old_text' by 'new_text' in all files in 'folder'
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
folder : pathlib.Path
|
|
41
|
+
Folder to scan
|
|
42
|
+
regex : str
|
|
43
|
+
Regex to replace
|
|
44
|
+
replacement : str
|
|
45
|
+
Replacement text
|
|
46
|
+
ignore : re.Pattern | None
|
|
47
|
+
Regex stating what to ignore
|
|
44
48
|
"""
|
|
45
49
|
for path in runez.ls_dir(folder):
|
|
46
50
|
if not path.is_symlink() and (not ignore or not ignore.match(path.name)):
|
|
@@ -53,21 +57,21 @@ def patch_folder(folder, regex, replacement, ignore=None):
|
|
|
53
57
|
|
|
54
58
|
def patch_file(path, regex, replacement):
|
|
55
59
|
try:
|
|
56
|
-
with open(path
|
|
60
|
+
with open(path) as fh:
|
|
57
61
|
text = fh.read()
|
|
58
62
|
|
|
59
63
|
new_text = re.sub(regex, replacement, text, flags=re.MULTILINE)
|
|
60
64
|
if text != new_text:
|
|
61
|
-
with open(path, "
|
|
65
|
+
with open(path, "w") as fh:
|
|
62
66
|
fh.write(new_text)
|
|
63
67
|
|
|
64
|
-
LOG.info("Patched '%s' in %s"
|
|
68
|
+
LOG.info("Patched '%s' in %s", regex, runez.short(path))
|
|
65
69
|
|
|
66
70
|
except Exception as e:
|
|
67
|
-
with open(path,
|
|
71
|
+
with open(path, errors="ignore") as fh:
|
|
68
72
|
text = fh.read()
|
|
69
73
|
if re.search(regex, text):
|
|
70
|
-
LOG.warning("Can't patch '%s': %s"
|
|
74
|
+
LOG.warning("Can't patch '%s': %s", runez.short(path), e)
|
|
71
75
|
|
|
72
76
|
|
|
73
77
|
class FolderMask:
|
|
@@ -81,7 +85,7 @@ class FolderMask:
|
|
|
81
85
|
"""
|
|
82
86
|
|
|
83
87
|
def __init__(self, target_folder):
|
|
84
|
-
LOG.info("Applying isolation hack/mask to %s"
|
|
88
|
+
LOG.info("Applying isolation hack/mask to %s", target_folder)
|
|
85
89
|
self.target_folder = target_folder
|
|
86
90
|
r = runez.run("hdiutil", "attach", "-nomount", "ram://2048", fatal=Exception)
|
|
87
91
|
self.ram_disk = r.output.strip()
|
|
@@ -93,7 +97,7 @@ class FolderMask:
|
|
|
93
97
|
self.mounted = True
|
|
94
98
|
|
|
95
99
|
def cleanup(self):
|
|
96
|
-
LOG.info("Cleaning up isolation hack/mask for %s"
|
|
100
|
+
LOG.info("Cleaning up isolation hack/mask for %s", self.target_folder)
|
|
97
101
|
if self.mounted:
|
|
98
102
|
runez.run("umount", self.target_folder, fatal=False)
|
|
99
103
|
|
|
@@ -118,8 +122,12 @@ class BuildContext:
|
|
|
118
122
|
|
|
119
123
|
def _resolved_isolation(self):
|
|
120
124
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
Isolation setting currently configured.
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
str | None
|
|
130
|
+
What strategy to use to work around the fact that python's ./configure script looks at /usr/local
|
|
123
131
|
"""
|
|
124
132
|
v = PPG.config.get_value("isolate-usr-local")
|
|
125
133
|
if v == "auto":
|
|
@@ -182,7 +190,7 @@ class BuildContext:
|
|
|
182
190
|
|
|
183
191
|
class BuildSetup:
|
|
184
192
|
"""
|
|
185
|
-
|
|
193
|
+
Drives the compilation, external modules first, then the target python itself.
|
|
186
194
|
All modules are compiled in the same manner, follow the same conventional build layout.
|
|
187
195
|
"""
|
|
188
196
|
|
|
@@ -191,10 +199,14 @@ class BuildSetup:
|
|
|
191
199
|
|
|
192
200
|
def __init__(self, python_spec=None, modules=None, prefix=None):
|
|
193
201
|
"""
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
python_spec : str | PythonSpec | None
|
|
205
|
+
Python to build (family and version)
|
|
206
|
+
modules : str | None
|
|
207
|
+
Modules to build (default: from config)
|
|
208
|
+
prefix : str | None
|
|
209
|
+
--prefix to use
|
|
198
210
|
"""
|
|
199
211
|
if not python_spec or python_spec == "latest":
|
|
200
212
|
python_spec = PPG.cpython.latest
|
|
@@ -267,11 +279,11 @@ class BuildSetup:
|
|
|
267
279
|
with BuildContext(self) as build_context:
|
|
268
280
|
self.build_context = build_context
|
|
269
281
|
modules = self.python_builder.modules
|
|
270
|
-
LOG.info("portable-python v%s, current folder: %s"
|
|
282
|
+
LOG.info("portable-python v%s, current folder: %s", runez.get_version(__name__), os.getcwd())
|
|
271
283
|
LOG.info(runez.joined(modules, list(modules)))
|
|
272
284
|
LOG.info(PPG.config.config_files_report())
|
|
273
|
-
LOG.info("Platform: %s"
|
|
274
|
-
LOG.info("Build report:\n%s"
|
|
285
|
+
LOG.info("Platform: %s", PPG.target)
|
|
286
|
+
LOG.info("Build report:\n%s", self.python_builder.modules.report())
|
|
275
287
|
self.validate_module_selection(fatal=not runez.DRYRUN and not self.x_debug)
|
|
276
288
|
self.ensure_clean_folder(self.folders.components)
|
|
277
289
|
self.ensure_clean_folder(self.folders.deps)
|
|
@@ -375,7 +387,6 @@ class ModuleCollection:
|
|
|
375
387
|
|
|
376
388
|
|
|
377
389
|
class LinkerOutcome(enum.Enum):
|
|
378
|
-
|
|
379
390
|
absent = "orange"
|
|
380
391
|
failed = "red"
|
|
381
392
|
shared = "blue"
|
|
@@ -389,6 +400,7 @@ class ModuleBuilder:
|
|
|
389
400
|
m_build_cwd: str = None # Optional: relative (to unpacked source) folder where to run configure/make from
|
|
390
401
|
m_debian = None
|
|
391
402
|
m_include: str = None # Optional: subfolder to automatically list in CPATH when this module is active
|
|
403
|
+
m_telltale: ClassVar[list] # Optional: list of files that, if present, indicate this module is installed
|
|
392
404
|
|
|
393
405
|
setup: BuildSetup
|
|
394
406
|
parent_module: "ModuleBuilder" = None
|
|
@@ -396,8 +408,10 @@ class ModuleBuilder:
|
|
|
396
408
|
|
|
397
409
|
def __init__(self, parent_module):
|
|
398
410
|
"""
|
|
399
|
-
|
|
400
|
-
|
|
411
|
+
Parameters
|
|
412
|
+
----------
|
|
413
|
+
parent_module : BuildSetup | ModuleBuilder
|
|
414
|
+
Associated parent
|
|
401
415
|
"""
|
|
402
416
|
self.m_name = ModuleCollection.get_module_name(self.__class__)
|
|
403
417
|
if isinstance(parent_module, BuildSetup):
|
|
@@ -458,7 +472,7 @@ class ModuleBuilder:
|
|
|
458
472
|
if self.resolved_telltale:
|
|
459
473
|
return "has %s" % self.resolved_telltale
|
|
460
474
|
|
|
461
|
-
return "no %s" %
|
|
475
|
+
return "no %s" % self.m_telltale
|
|
462
476
|
|
|
463
477
|
def _find_telltale(self):
|
|
464
478
|
telltales = getattr(self, "m_telltale", runez.UNSET)
|
|
@@ -525,8 +539,7 @@ class ModuleBuilder:
|
|
|
525
539
|
|
|
526
540
|
def run_configure(self, program, *args, prefix=None):
|
|
527
541
|
"""
|
|
528
|
-
|
|
529
|
-
This allows to have descendants customize each part relatively elegantly
|
|
542
|
+
Run ./configure script for this module.
|
|
530
543
|
"""
|
|
531
544
|
if prefix is None:
|
|
532
545
|
prefix = self.deps
|
|
@@ -564,7 +577,8 @@ class ModuleBuilder:
|
|
|
564
577
|
yield
|
|
565
578
|
|
|
566
579
|
except Exception as e:
|
|
567
|
-
|
|
580
|
+
overview = repr(e)
|
|
581
|
+
LOG.exception("Error while compiling %s: %s", self, overview)
|
|
568
582
|
raise
|
|
569
583
|
|
|
570
584
|
finally:
|
|
@@ -598,7 +612,7 @@ class ModuleBuilder:
|
|
|
598
612
|
env_vars = self._get_env_vars()
|
|
599
613
|
prev_env_vars = {}
|
|
600
614
|
for var_name, value in env_vars.items():
|
|
601
|
-
LOG.info("env %s=%s"
|
|
615
|
+
LOG.info("env %s=%s", var_name, runez.short(value, size=2048))
|
|
602
616
|
prev_env_vars[var_name] = os.environ.get(var_name)
|
|
603
617
|
os.environ[var_name] = value
|
|
604
618
|
|
|
@@ -672,7 +686,6 @@ class ModuleBuilder:
|
|
|
672
686
|
|
|
673
687
|
|
|
674
688
|
class PythonBuilder(ModuleBuilder):
|
|
675
|
-
|
|
676
689
|
_bin_python: pathlib.Path = None
|
|
677
690
|
|
|
678
691
|
def __init__(self, parent_module):
|
|
@@ -692,8 +705,10 @@ class PythonBuilder(ModuleBuilder):
|
|
|
692
705
|
@property
|
|
693
706
|
def bin_python(self):
|
|
694
707
|
"""
|
|
695
|
-
Returns
|
|
696
|
-
|
|
708
|
+
Returns
|
|
709
|
+
-------
|
|
710
|
+
pathlib.Path | None
|
|
711
|
+
Path to freshly compiled bin/python, real file (not symlink)
|
|
697
712
|
"""
|
|
698
713
|
if self._bin_python is None:
|
|
699
714
|
self._bin_python = PPG.config.find_main_file(self.bin_folder / "python", self.version)
|
|
@@ -724,5 +739,5 @@ class PythonBuilder(ModuleBuilder):
|
|
|
724
739
|
expected = 0o755 if path.is_dir() else 0o644
|
|
725
740
|
current = path.stat().st_mode & 0o777
|
|
726
741
|
if current != expected:
|
|
727
|
-
LOG.info("Corrected permissions for %s (was %s)"
|
|
742
|
+
LOG.info("Corrected permissions for %s (was %s)", runez.short(path), oct(current))
|
|
728
743
|
path.chmod(expected)
|
|
@@ -8,7 +8,6 @@ from runez.render import PrettyTable
|
|
|
8
8
|
from portable_python import BuildSetup, PPG
|
|
9
9
|
from portable_python.inspector import LibAutoCorrect, PythonInspector
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
LOG = logging.getLogger(__name__)
|
|
13
12
|
|
|
14
13
|
|
|
@@ -60,10 +59,6 @@ def build_report(modules, python_spec):
|
|
|
60
59
|
def diagnostics():
|
|
61
60
|
"""Show diagnostics info"""
|
|
62
61
|
with runez.Anchored("."):
|
|
63
|
-
def _diagnostics():
|
|
64
|
-
yield "invoker python", runez.SYS_INFO.invoker_python
|
|
65
|
-
yield from runez.SYS_INFO.diagnostics()
|
|
66
|
-
|
|
67
62
|
config = PPG.config.represented()
|
|
68
63
|
print(PrettyTable.two_column_diagnostics(_diagnostics(), config))
|
|
69
64
|
|
|
@@ -106,6 +101,11 @@ def list_cmd(json, family):
|
|
|
106
101
|
print(" %s: %s" % (runez.bold(mm), v))
|
|
107
102
|
|
|
108
103
|
|
|
104
|
+
def _diagnostics():
|
|
105
|
+
yield "invoker python", runez.SYS_INFO.invoker_python
|
|
106
|
+
yield from runez.SYS_INFO.diagnostics()
|
|
107
|
+
|
|
108
|
+
|
|
109
109
|
def _find_recompress_source(folders, path):
|
|
110
110
|
candidate = runez.to_path(path)
|
|
111
111
|
if candidate.exists() or candidate.is_absolute():
|
|
@@ -158,7 +158,7 @@ def recompress(path, ext):
|
|
|
158
158
|
|
|
159
159
|
\b
|
|
160
160
|
Mildly useful for comparing sizes from different compressions
|
|
161
|
-
"""
|
|
161
|
+
""" # noqa: D301
|
|
162
162
|
extension = runez.SYS_INFO.platform_id.canonical_compress_extension(ext)
|
|
163
163
|
pspec = PythonSpec.from_text(path)
|
|
164
164
|
folders = PPG.get_folders(base=".", family=pspec and pspec.family, version=pspec and pspec.version)
|
|
@@ -203,6 +203,6 @@ def lib_auto_correct(commit, prefix, path):
|
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
if __name__ == "__main__":
|
|
206
|
-
from portable_python.cli import main
|
|
206
|
+
from portable_python.cli import main
|
|
207
207
|
|
|
208
208
|
main()
|
|
@@ -9,7 +9,6 @@ import runez
|
|
|
9
9
|
import yaml
|
|
10
10
|
from runez.pyenv import Version
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
LOG = logging.getLogger(__name__)
|
|
14
13
|
|
|
15
14
|
DEFAULT_CONFIG = """
|
|
@@ -79,9 +78,12 @@ class Config:
|
|
|
79
78
|
|
|
80
79
|
def __init__(self, paths=None, target=None):
|
|
81
80
|
"""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
paths : str | list | None
|
|
84
|
+
Path(s) to config file(s)
|
|
85
|
+
target : str | runez.system.PlatformId | None
|
|
86
|
+
Target platform (for testing, defaults to current platform)
|
|
85
87
|
"""
|
|
86
88
|
self.paths = runez.flattened(paths, split=",")
|
|
87
89
|
if not isinstance(target, runez.system.PlatformId):
|
|
@@ -98,17 +100,21 @@ class Config:
|
|
|
98
100
|
return "%s [%s]" % (runez.plural(self._sources, "config source"), self.target)
|
|
99
101
|
|
|
100
102
|
def completions(self, **given):
|
|
101
|
-
res =
|
|
103
|
+
res = {"arch": self.target.arch, "platform": self.target.platform, "subsystem": self.target.subsystem, "target": str(self.target)}
|
|
102
104
|
res.update(given)
|
|
103
105
|
return res
|
|
104
106
|
|
|
105
107
|
def get_value(self, *key, by_platform=True):
|
|
106
108
|
"""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
key : strzz | tuple
|
|
112
|
+
Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
|
|
113
|
+
by_platform : bool
|
|
114
|
+
If True, value can be configured by platform
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
112
118
|
Associated value, if any
|
|
113
119
|
"""
|
|
114
120
|
value, _ = self.get_entry(*key, by_platform=by_platform)
|
|
@@ -116,18 +122,23 @@ class Config:
|
|
|
116
122
|
|
|
117
123
|
def get_entry(self, *key, by_platform=True):
|
|
118
124
|
"""
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
key : str | tuple
|
|
128
|
+
Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
|
|
129
|
+
by_platform : bool
|
|
130
|
+
If True, value can be configured by platform
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
(str | int | float | bool | dict | list | None, ConfigSource | None)
|
|
135
|
+
Associated value (if any), together with the source that defined it
|
|
125
136
|
"""
|
|
126
137
|
if by_platform:
|
|
127
138
|
keys = (self.target.platform, self.target.arch, *key), (self.target.platform, *key), key
|
|
128
139
|
|
|
129
140
|
else:
|
|
130
|
-
keys = (key,
|
|
141
|
+
keys = (key,)
|
|
131
142
|
|
|
132
143
|
for k in keys:
|
|
133
144
|
for source in self._sources:
|
|
@@ -150,7 +161,7 @@ class Config:
|
|
|
150
161
|
return value
|
|
151
162
|
|
|
152
163
|
def config_files_report(self):
|
|
153
|
-
"""One
|
|
164
|
+
"""One-liner describing which config files are used, if any"""
|
|
154
165
|
if len(self._sources) > 1:
|
|
155
166
|
return "Config files: %s" % runez.joined(self._sources[:-1], delimiter=", ")
|
|
156
167
|
|
|
@@ -174,7 +185,7 @@ class Config:
|
|
|
174
185
|
def delete(path):
|
|
175
186
|
size = runez.filesize(path)
|
|
176
187
|
runez.delete(path, logger=None)
|
|
177
|
-
LOG.info("Deleted %s (%s)"
|
|
188
|
+
LOG.info("Deleted %s (%s)", runez.short(path), runez.represented_bytesize(size))
|
|
178
189
|
return size
|
|
179
190
|
|
|
180
191
|
@staticmethod
|
|
@@ -187,10 +198,14 @@ class Config:
|
|
|
187
198
|
|
|
188
199
|
def cleanup_configured_globs(self, title, module, *keys):
|
|
189
200
|
"""
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
title : str
|
|
204
|
+
Title to use in log messages
|
|
205
|
+
module : portable_python.PythonBuilder
|
|
206
|
+
Associated python builder module
|
|
207
|
+
*keys : str
|
|
208
|
+
Config keys to lookup
|
|
194
209
|
"""
|
|
195
210
|
globs = [(x, f"{x}-{self.target.platform}") for x in keys]
|
|
196
211
|
globs = runez.flattened(globs, transform=self.get_value)
|
|
@@ -199,16 +214,20 @@ class Config:
|
|
|
199
214
|
|
|
200
215
|
def cleanup_globs(self, title, module, *globs):
|
|
201
216
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
title : str
|
|
220
|
+
Title to use in log messages
|
|
221
|
+
module : portable_python.PythonBuilder
|
|
222
|
+
Associated python builder module
|
|
223
|
+
*globs : str
|
|
224
|
+
Glob patterns to clean up
|
|
206
225
|
"""
|
|
207
226
|
if globs:
|
|
208
227
|
spec = [module.setup.folders.formatted(x) for x in globs]
|
|
209
228
|
deleted_size = 0
|
|
210
229
|
matcher = FileMatcher(spec)
|
|
211
|
-
LOG.info("Applying clean-up spec: %s"
|
|
230
|
+
LOG.info("Applying clean-up spec: %s", matcher)
|
|
212
231
|
cleaned = []
|
|
213
232
|
for dirpath, dirnames, filenames in os.walk(module.install_folder):
|
|
214
233
|
removed = []
|
|
@@ -241,7 +260,7 @@ class Config:
|
|
|
241
260
|
_find_file_duplicates(seen, folder)
|
|
242
261
|
duplicates = {k: v for k, v in seen.items() if len(v) > 1}
|
|
243
262
|
for dupes in duplicates.values():
|
|
244
|
-
LOG.info("Found duplicates: %s"
|
|
263
|
+
LOG.info("Found duplicates: %s", runez.joined(dupes, delimiter=", "))
|
|
245
264
|
dupes = sorted(dupes, key=lambda x: len(str(x)))
|
|
246
265
|
if len(dupes) == 2:
|
|
247
266
|
shorter, longer = dupes
|
|
@@ -261,14 +280,19 @@ class Config:
|
|
|
261
280
|
return basename, "%s%s" % (basename, version.major), "%s%s" % (basename, version.mm)
|
|
262
281
|
|
|
263
282
|
@staticmethod
|
|
264
|
-
def find_main_file(desired
|
|
283
|
+
def find_main_file(desired, version):
|
|
265
284
|
"""
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
desired : pathlib.Path
|
|
288
|
+
Desired path (base name considered if path not directly available)
|
|
289
|
+
version : Version
|
|
290
|
+
Associated version
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
pathlib.Path | None
|
|
295
|
+
Path to associated real file (not symlink)
|
|
272
296
|
"""
|
|
273
297
|
p = Config.real_path(desired)
|
|
274
298
|
if p:
|
|
@@ -335,10 +359,14 @@ class ConfigSource:
|
|
|
335
359
|
|
|
336
360
|
def get_value(self, key):
|
|
337
361
|
"""
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
362
|
+
Parameters
|
|
363
|
+
----------
|
|
364
|
+
key : str | tuple
|
|
365
|
+
Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
|
|
366
|
+
|
|
367
|
+
Returns
|
|
368
|
+
-------
|
|
369
|
+
str | int | float | bool | dict | list | None
|
|
342
370
|
Associated value, if any
|
|
343
371
|
"""
|
|
344
372
|
return self._deep_get(self.data, key)
|
|
@@ -360,7 +388,6 @@ class ConfigSource:
|
|
|
360
388
|
|
|
361
389
|
|
|
362
390
|
class FileMatcher:
|
|
363
|
-
|
|
364
391
|
def __init__(self, clean_spec):
|
|
365
392
|
self.matches = []
|
|
366
393
|
for spec in clean_spec:
|
|
@@ -376,7 +403,6 @@ class FileMatcher:
|
|
|
376
403
|
|
|
377
404
|
|
|
378
405
|
class SingleFileMatch:
|
|
379
|
-
|
|
380
406
|
_on_folder = False
|
|
381
407
|
_rx_basename = None
|
|
382
408
|
_rx_path = None
|
|
@@ -12,7 +12,6 @@ from portable_python.external.tkinter import TkInter
|
|
|
12
12
|
from portable_python.external.xcpython import Bdb, Bzip2, Gdbm, LibFFI, Openssl, Readline, Sqlite, Uuid, Xz, Zlib
|
|
13
13
|
from portable_python.inspector import LibAutoCorrect, PythonInspector
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
# https://github.com/docker-library/python/issues/160
|
|
17
16
|
PGO_TESTS = """
|
|
18
17
|
-m test.regrtest --pgo test_array test_base64 test_binascii test_binhex test_binop test_bytes test_c_locale_coercion
|
|
@@ -24,12 +23,17 @@ test_struct test_threading test_time test_traceback test_unicode
|
|
|
24
23
|
|
|
25
24
|
def represented_yaml(key_value_pairs):
|
|
26
25
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
Yaml representation for given key/value pairs
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
key_value_pairs : iterable
|
|
31
|
+
Key/value pairs to represent
|
|
30
32
|
|
|
31
|
-
Returns
|
|
32
|
-
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
str
|
|
36
|
+
Yaml representation
|
|
33
37
|
"""
|
|
34
38
|
content = [yaml.safe_dump(runez.serialize.json_sanitized({x: y}), width=140) for x, y in key_value_pairs]
|
|
35
39
|
return runez.joined(content, delimiter="\n")
|
|
@@ -46,28 +50,38 @@ class Cpython(PythonBuilder):
|
|
|
46
50
|
|
|
47
51
|
def build_information(self):
|
|
48
52
|
"""
|
|
49
|
-
|
|
53
|
+
Build information to store in manifest.
|
|
50
54
|
`None` or empty values are "naturally" omitted by the fact we use `runez.joined()` and `runez.flattened()`,
|
|
51
|
-
which have a default parameter `keep_empty=False
|
|
55
|
+
which have a default parameter `keep_empty=False`.
|
|
56
|
+
|
|
57
|
+
Yields
|
|
58
|
+
------
|
|
59
|
+
(str, dict)
|
|
52
60
|
"""
|
|
53
|
-
yield
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
yield (
|
|
62
|
+
"cpython",
|
|
63
|
+
{
|
|
64
|
+
"prefix": self.setup.prefix,
|
|
65
|
+
"source": self.url,
|
|
66
|
+
"static": runez.joined(self.modules.selected) or None,
|
|
67
|
+
"target": PPG.target,
|
|
68
|
+
"version": self.version,
|
|
69
|
+
},
|
|
70
|
+
)
|
|
60
71
|
yield "configure-args", runez.joined(runez.short(x) for x in self.c_configure_args())
|
|
61
72
|
compiled_by = os.environ.get("PP_ORIGIN") or PPG.config.get_value("compiled-by")
|
|
62
73
|
bc = self.setup.build_context
|
|
63
|
-
yield
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
yield (
|
|
75
|
+
"compilation-info",
|
|
76
|
+
{
|
|
77
|
+
"compiled-by": compiled_by or "https://pypi.org/project/portable-python/",
|
|
78
|
+
"date": datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z"),
|
|
79
|
+
"host-platform": runez.SYS_INFO.platform_info,
|
|
80
|
+
"ldd-version": PythonInspector.tool_version("ldd"),
|
|
81
|
+
"portable-python-version": runez.get_version(__package__),
|
|
82
|
+
"special-context": bc.isolate_usr_local and bc,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
71
85
|
additional = PPG.config.get_value("manifest", "additional-info")
|
|
72
86
|
if additional:
|
|
73
87
|
res = {}
|
|
@@ -174,7 +188,7 @@ class Cpython(PythonBuilder):
|
|
|
174
188
|
# Special edge case in macosx_sdk_specified() where /usr/local is fine...
|
|
175
189
|
x = "startswith({q}/usr/{q}) and not path.startswith({q}{p}{q})"
|
|
176
190
|
special_case = x.replace("(", r"\(").replace(")", r"\)").format(q="['\"]", p=self.deps)
|
|
177
|
-
restored = x.format(q="'", p=
|
|
191
|
+
restored = x.format(q="'", p="/usr/local")
|
|
178
192
|
patch_file(setup_py, special_case, restored)
|
|
179
193
|
|
|
180
194
|
# Only doable on macOS: patch -install_name so produced exes/libs use a relative path
|
|
@@ -286,11 +300,7 @@ class Cpython(PythonBuilder):
|
|
|
286
300
|
Autocorrect pkgconfig and sysconfig to use relative paths
|
|
287
301
|
See https://manpages.debian.org/stretch/pkg-config/pkg-config.1.en.html#PKG-CONFIG_DERIVED_VARIABLES
|
|
288
302
|
"""
|
|
289
|
-
patch_folder(
|
|
290
|
-
self.install_folder / "lib/pkgconfig",
|
|
291
|
-
f"prefix={self.c_configure_prefix}",
|
|
292
|
-
"prefix=${pcfiledir}/../.."
|
|
293
|
-
)
|
|
303
|
+
patch_folder(self.install_folder / "lib/pkgconfig", f"prefix={self.c_configure_prefix}", "prefix=${pcfiledir}/../..")
|
|
294
304
|
sys_cfg = self._find_sys_cfg()
|
|
295
305
|
if sys_cfg:
|
|
296
306
|
rs = RelSysConf(sys_cfg, self.c_configure_prefix)
|
|
@@ -347,7 +357,7 @@ class Cpython(PythonBuilder):
|
|
|
347
357
|
|
|
348
358
|
if lines:
|
|
349
359
|
LOG.info("Auto-corrected shebang for %s" % runez.short(path))
|
|
350
|
-
with open(path, "
|
|
360
|
+
with open(path, "w") as fh:
|
|
351
361
|
for line in lines:
|
|
352
362
|
fh.write(line)
|
|
353
363
|
|
|
@@ -375,7 +385,7 @@ class RelSysConf:
|
|
|
375
385
|
def _relativize(self, line):
|
|
376
386
|
start = 0
|
|
377
387
|
for m in self.rx_strings.finditer(line):
|
|
378
|
-
yield line[start:m.start(0)]
|
|
388
|
+
yield line[start : m.start(0)]
|
|
379
389
|
start = m.end(0)
|
|
380
390
|
content = m.group(2)
|
|
381
391
|
if self.prefix in content:
|
|
@@ -4,7 +4,6 @@ import re
|
|
|
4
4
|
import sys
|
|
5
5
|
import sysconfig
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
RX_VERSION = re.compile(r"\d\.\d")
|
|
9
8
|
INSIGHTS = {
|
|
10
9
|
"_gdbm": "_GDBM_VERSION",
|
|
@@ -38,7 +37,7 @@ def get_version(text):
|
|
|
38
37
|
def pymodule_version_info(key, value, pymodule):
|
|
39
38
|
version = get_version(value)
|
|
40
39
|
if version:
|
|
41
|
-
result =
|
|
40
|
+
result = {"version_field": key, "version": version}
|
|
42
41
|
if hasattr(pymodule, "__file__"):
|
|
43
42
|
result["path"] = pymodule.__file__
|
|
44
43
|
|
|
@@ -54,14 +53,14 @@ def pymodule_info(module_name, pymodule):
|
|
|
54
53
|
return v
|
|
55
54
|
|
|
56
55
|
if hasattr(pymodule, "__file__"):
|
|
57
|
-
return
|
|
56
|
+
return {"path": pymodule.__file__}
|
|
58
57
|
|
|
59
58
|
if hasattr(pymodule, "__spec__"):
|
|
60
|
-
v = getattr(pymodule.__spec__, "origin")
|
|
59
|
+
v = getattr(pymodule.__spec__, "origin", None)
|
|
61
60
|
if v == "built-in":
|
|
62
|
-
return
|
|
61
|
+
return {"version": v}
|
|
63
62
|
|
|
64
|
-
return
|
|
63
|
+
return {"note": str(dir(pymodule))}
|
|
65
64
|
|
|
66
65
|
|
|
67
66
|
def module_report(module_name):
|
|
@@ -71,9 +70,9 @@ def module_report(module_name):
|
|
|
71
70
|
except Exception as e:
|
|
72
71
|
note = str(e)
|
|
73
72
|
if "No module named" in note:
|
|
74
|
-
return
|
|
73
|
+
return {"version": "*absent*"}
|
|
75
74
|
|
|
76
|
-
return
|
|
75
|
+
return {"version": "*absent*", "note": note}
|
|
77
76
|
|
|
78
77
|
|
|
79
78
|
def get_srcdir():
|
|
@@ -92,7 +91,7 @@ def get_simplified_dirs(path):
|
|
|
92
91
|
if path.startswith("/private"):
|
|
93
92
|
result.append(path[8:]) # whoever compiled didn't use realpath(tmp)
|
|
94
93
|
|
|
95
|
-
elif not path.startswith("/tmp"): #
|
|
94
|
+
elif not path.startswith("/tmp"): # noqa: S108, just simplifying path representation
|
|
96
95
|
result.append(os.path.dirname(result[0]))
|
|
97
96
|
|
|
98
97
|
return result
|
|
@@ -121,8 +120,8 @@ def main(arg):
|
|
|
121
120
|
names.remove("pip")
|
|
122
121
|
names.append("pip")
|
|
123
122
|
|
|
124
|
-
report = dict((k, module_report(k)) for k in names)
|
|
125
|
-
report =
|
|
123
|
+
report = dict((k, module_report(k)) for k in names) # noqa: C402, works on py2 as well (can inspect py2)
|
|
124
|
+
report = {"report": report, "srcdir": get_srcdir(), "prefix": sysconfig.get_config_var("prefix")}
|
|
126
125
|
print(json.dumps(report, indent=2, sort_keys=True))
|
|
127
126
|
|
|
128
127
|
|
|
@@ -5,6 +5,8 @@ It is also unclear what version to use, which build docs to follow etc.
|
|
|
5
5
|
Contributions are welcome!
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from typing import ClassVar
|
|
9
|
+
|
|
8
10
|
import runez
|
|
9
11
|
|
|
10
12
|
from portable_python import ModuleBuilder, PPG
|
|
@@ -37,7 +39,6 @@ class Tcl(ModuleBuilder):
|
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
class Tk(ModuleBuilder):
|
|
40
|
-
|
|
41
42
|
m_build_cwd = "unix"
|
|
42
43
|
|
|
43
44
|
@property
|
|
@@ -107,7 +108,7 @@ class TkInter(ModuleBuilder):
|
|
|
107
108
|
"""
|
|
108
109
|
|
|
109
110
|
m_debian = "-tk-dev"
|
|
110
|
-
m_telltale = ["{include}/tk", "{include}/tk.h"]
|
|
111
|
+
m_telltale: ClassVar[list] = ["{include}/tk", "{include}/tk.h"]
|
|
111
112
|
|
|
112
113
|
@classmethod
|
|
113
114
|
def candidate_modules(cls):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import ClassVar
|
|
2
|
+
|
|
1
3
|
import runez
|
|
2
4
|
|
|
3
5
|
from portable_python import LinkerOutcome, ModuleBuilder, PPG
|
|
@@ -64,7 +66,7 @@ class Gdbm(ModuleBuilder):
|
|
|
64
66
|
"""
|
|
65
67
|
|
|
66
68
|
m_debian = "libgdbm-dev"
|
|
67
|
-
m_telltale = ["{include}/gdbm.h"]
|
|
69
|
+
m_telltale: ClassVar[list] = ["{include}/gdbm.h"]
|
|
68
70
|
|
|
69
71
|
@property
|
|
70
72
|
def url(self):
|
|
@@ -102,7 +104,7 @@ class LibFFI(ModuleBuilder):
|
|
|
102
104
|
"""
|
|
103
105
|
|
|
104
106
|
m_debian = "!libffi-dev"
|
|
105
|
-
m_telltale = ["{include}/ffi.h", "{include}/ffi/ffi.h"]
|
|
107
|
+
m_telltale: ClassVar[list] = ["{include}/ffi.h", "{include}/ffi/ffi.h"]
|
|
106
108
|
|
|
107
109
|
xenv_CFLAGS = "-fPIC"
|
|
108
110
|
|
|
@@ -163,7 +165,6 @@ class Openssl(ModuleBuilder):
|
|
|
163
165
|
|
|
164
166
|
|
|
165
167
|
class Ncurses(ModuleBuilder):
|
|
166
|
-
|
|
167
168
|
m_include = "ncursesw"
|
|
168
169
|
|
|
169
170
|
xenv_CFLAGS = "-fPIC"
|
|
@@ -231,8 +232,14 @@ class Readline(ModuleBuilder):
|
|
|
231
232
|
|
|
232
233
|
def _do_linux_compile(self):
|
|
233
234
|
self.run_configure(
|
|
234
|
-
"./configure",
|
|
235
|
-
"--disable-
|
|
235
|
+
"./configure",
|
|
236
|
+
"--disable-shared",
|
|
237
|
+
"--enable-static",
|
|
238
|
+
"--with-curses",
|
|
239
|
+
"--enable-multibyte",
|
|
240
|
+
"--disable-install-examples",
|
|
241
|
+
"--disable-docs",
|
|
242
|
+
"--enable-portable-binary",
|
|
236
243
|
)
|
|
237
244
|
self.run_make(cpu_count=0)
|
|
238
245
|
self.run_make("install", cpu_count=0)
|
|
@@ -245,7 +252,7 @@ class Sqlite(ModuleBuilder):
|
|
|
245
252
|
"""
|
|
246
253
|
|
|
247
254
|
m_debian = "+libsqlite3-dev"
|
|
248
|
-
m_telltale = ["{include}/sqlite3.h"]
|
|
255
|
+
m_telltale: ClassVar[list] = ["{include}/sqlite3.h"]
|
|
249
256
|
|
|
250
257
|
xenv_CFLAGS = "-fPIC"
|
|
251
258
|
|
|
@@ -265,7 +272,12 @@ class Sqlite(ModuleBuilder):
|
|
|
265
272
|
|
|
266
273
|
def _do_linux_compile(self):
|
|
267
274
|
self.run_configure(
|
|
268
|
-
"./configure",
|
|
275
|
+
"./configure",
|
|
276
|
+
"--enable-shared=no",
|
|
277
|
+
"--enable-static=yes",
|
|
278
|
+
"--disable-tcl",
|
|
279
|
+
"--disable-readline",
|
|
280
|
+
"--with-pic=yes",
|
|
269
281
|
)
|
|
270
282
|
self.run_make()
|
|
271
283
|
self.run_make("install")
|
|
@@ -279,7 +291,7 @@ class Uuid(ModuleBuilder):
|
|
|
279
291
|
|
|
280
292
|
m_debian = "+uuid-dev"
|
|
281
293
|
m_include = "uuid"
|
|
282
|
-
m_telltale = ["{include}/uuid/uuid.h"]
|
|
294
|
+
m_telltale: ClassVar[list] = ["{include}/uuid/uuid.h"]
|
|
283
295
|
|
|
284
296
|
xenv_CFLAGS = "-fPIC"
|
|
285
297
|
|
|
@@ -298,7 +310,6 @@ class Uuid(ModuleBuilder):
|
|
|
298
310
|
|
|
299
311
|
|
|
300
312
|
class Xz(ModuleBuilder):
|
|
301
|
-
|
|
302
313
|
m_telltale = "{include}/lzma.h"
|
|
303
314
|
|
|
304
315
|
def auto_select_reason(self):
|
|
@@ -316,8 +327,14 @@ class Xz(ModuleBuilder):
|
|
|
316
327
|
def _do_linux_compile(self):
|
|
317
328
|
self.run_configure(
|
|
318
329
|
"./configure",
|
|
319
|
-
"--enable-shared=no",
|
|
320
|
-
"--
|
|
330
|
+
"--enable-shared=no",
|
|
331
|
+
"--enable-static=yes",
|
|
332
|
+
"--with-pic=yes",
|
|
333
|
+
"--disable-rpath",
|
|
334
|
+
"--disable-dependency-tracking",
|
|
335
|
+
"--disable-doc",
|
|
336
|
+
"--disable-nls",
|
|
337
|
+
"--without-libintl-prefix",
|
|
321
338
|
)
|
|
322
339
|
self.run_make()
|
|
323
340
|
self.run_make("install")
|
|
@@ -340,7 +357,7 @@ class Zlib(ModuleBuilder):
|
|
|
340
357
|
|
|
341
358
|
@property
|
|
342
359
|
def url(self):
|
|
343
|
-
return f"https://zlib.net/zlib-{self.version}.tar.gz"
|
|
360
|
+
return f"https://zlib.net/fossils/zlib-{self.version}.tar.gz"
|
|
344
361
|
|
|
345
362
|
@property
|
|
346
363
|
def version(self):
|
|
@@ -11,7 +11,6 @@ from runez.render import PrettyTable
|
|
|
11
11
|
from portable_python.tracking import Trackable, Tracker
|
|
12
12
|
from portable_python.versions import PPG
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
LOG = logging.getLogger(__name__)
|
|
16
15
|
RX_DYNLIB = re.compile(r"^.*\.(so(\.[0-9.]+)?|dylib)$")
|
|
17
16
|
|
|
@@ -57,10 +56,14 @@ class LibAutoCorrect:
|
|
|
57
56
|
|
|
58
57
|
def __init__(self, prefix, install_folder, ppp_marker=None):
|
|
59
58
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
prefix: str
|
|
62
|
+
Prefix used in ./configure
|
|
63
|
+
install_folder : pathlib.Path
|
|
64
|
+
Installation folder to scan (all paths will be relative to this)
|
|
65
|
+
ppp_marker : str | None
|
|
66
|
+
Path to ppp-marker, if any
|
|
64
67
|
"""
|
|
65
68
|
self.prefix = prefix
|
|
66
69
|
self.install_folder = install_folder
|
|
@@ -103,6 +106,7 @@ class LibAutoCorrect:
|
|
|
103
106
|
def _auto_correct_macos(self, path):
|
|
104
107
|
"""
|
|
105
108
|
On macos, we use install_name_tool, example:
|
|
109
|
+
|
|
106
110
|
install_name_tool -add_rpath @executable_path/../lib .../bin/python
|
|
107
111
|
install_name_tool -change /<prefix>/lib/libpython3.9.dylib @rpath/libpython3.9.dylib .../bin/python
|
|
108
112
|
|
|
@@ -146,7 +150,6 @@ class LibAutoCorrect:
|
|
|
146
150
|
|
|
147
151
|
|
|
148
152
|
class ModuleInfo:
|
|
149
|
-
|
|
150
153
|
_regex = re.compile(r"^(.*?)\s*(\S+/(lib(64)?/.*))$")
|
|
151
154
|
|
|
152
155
|
def __init__(self, inspector: "PythonInspector", name: str, payload: dict):
|
|
@@ -166,8 +169,7 @@ class ModuleInfo:
|
|
|
166
169
|
path = self.filepath
|
|
167
170
|
if path:
|
|
168
171
|
if is_dyn_lib(path):
|
|
169
|
-
|
|
170
|
-
return info
|
|
172
|
+
return SoInfo(self.inspector, path)
|
|
171
173
|
|
|
172
174
|
if path.name.startswith("__init__."):
|
|
173
175
|
path = path.parent
|
|
@@ -200,7 +202,6 @@ class ModuleInfo:
|
|
|
200
202
|
|
|
201
203
|
|
|
202
204
|
class CLibInfo(Trackable):
|
|
203
|
-
|
|
204
205
|
def __init__(self, inspector: "PythonInspector", path: str, version: str, basename: str):
|
|
205
206
|
self.inspector = inspector
|
|
206
207
|
if not basename:
|
|
@@ -249,7 +250,6 @@ class CLibInfo(Trackable):
|
|
|
249
250
|
|
|
250
251
|
|
|
251
252
|
class SoInfo(Trackable):
|
|
252
|
-
|
|
253
253
|
def __init__(self, inspector: "PythonInspector", path):
|
|
254
254
|
self.inspector = inspector
|
|
255
255
|
self.path = runez.to_path(path)
|
|
@@ -286,7 +286,7 @@ class SoInfo(Trackable):
|
|
|
286
286
|
if runez.which(program):
|
|
287
287
|
r = runez.run(*cmd, path, fatal=False, logger=None)
|
|
288
288
|
if not r.succeeded:
|
|
289
|
-
logging.warning("%s exited with code %s for %s: %s"
|
|
289
|
+
logging.warning("%s exited with code %s for %s: %s", program, r.exit_code, path, r.full_output)
|
|
290
290
|
return program, None
|
|
291
291
|
|
|
292
292
|
return program, r.output
|
|
@@ -397,7 +397,6 @@ def get_lib_type(install_folder, path, basename):
|
|
|
397
397
|
|
|
398
398
|
|
|
399
399
|
class PythonInspector:
|
|
400
|
-
|
|
401
400
|
default = "_bz2,_ctypes,_curses,_decimal,_dbm,_gdbm,_lzma,_tkinter,_sqlite3,_ssl,_uuid,pip,readline,pyexpat,setuptools,zlib"
|
|
402
401
|
additional = "_asyncio,_functools,_tracemalloc,dbm.gnu,ensurepip,ossaudiodev,spwd,sys,tkinter,venv,wheel"
|
|
403
402
|
|
|
@@ -500,11 +499,17 @@ class PythonInspector:
|
|
|
500
499
|
@staticmethod
|
|
501
500
|
def parsed_version(output):
|
|
502
501
|
"""
|
|
503
|
-
|
|
504
|
-
output (str | None): Output to parse
|
|
502
|
+
Version mentioned in `output`
|
|
505
503
|
|
|
506
|
-
|
|
507
|
-
|
|
504
|
+
Parameters
|
|
505
|
+
----------
|
|
506
|
+
output : str | None
|
|
507
|
+
Output to parse
|
|
508
|
+
|
|
509
|
+
Returns
|
|
510
|
+
-------
|
|
511
|
+
str | None
|
|
512
|
+
Simplified version number reported
|
|
508
513
|
"""
|
|
509
514
|
if output:
|
|
510
515
|
for line in output.splitlines():
|
|
@@ -535,7 +540,6 @@ def _find_parent_subfolder(folder, basename):
|
|
|
535
540
|
|
|
536
541
|
|
|
537
542
|
class FullSoReport:
|
|
538
|
-
|
|
539
543
|
def __init__(self, inspector: PythonInspector):
|
|
540
544
|
self.inspector = inspector
|
|
541
545
|
self.size = 0
|
|
@@ -2,7 +2,6 @@ import runez
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class Trackable:
|
|
5
|
-
|
|
6
5
|
tracked_category = None
|
|
7
6
|
|
|
8
7
|
def __eq__(self, other):
|
|
@@ -16,7 +15,6 @@ class Trackable:
|
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class TrackedCollection:
|
|
19
|
-
|
|
20
18
|
def __init__(self, name):
|
|
21
19
|
self.name = name
|
|
22
20
|
self.items = []
|
|
@@ -38,7 +36,6 @@ class TrackedCollection:
|
|
|
38
36
|
|
|
39
37
|
|
|
40
38
|
class Tracker(TrackedCollection):
|
|
41
|
-
|
|
42
39
|
def __init__(self, enum, name=None):
|
|
43
40
|
self.kind = enum.__name__.replace("Type", "").lower()
|
|
44
41
|
super().__init__(name or self.kind)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Tracking only a handful of most recent (and non-EOL) versions by design
|
|
3
|
-
|
|
2
|
+
Tracking only a handful of most recent (and non-EOL) versions by design.
|
|
3
|
+
|
|
4
|
+
Not trying to do historical stuff here, older (or EOL-ed) versions will be removed from the list without notice.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
8
9
|
import re
|
|
10
|
+
from typing import ClassVar
|
|
9
11
|
|
|
10
12
|
import runez
|
|
11
13
|
from runez.http import RestClient
|
|
@@ -49,13 +51,23 @@ class VersionFamily:
|
|
|
49
51
|
self._fetch_versions()
|
|
50
52
|
return self._versions
|
|
51
53
|
|
|
52
|
-
def get_available_versions(self)
|
|
53
|
-
"""
|
|
54
|
+
def get_available_versions(self):
|
|
55
|
+
"""
|
|
56
|
+
Available versions.
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
list[Version]
|
|
61
|
+
"""
|
|
54
62
|
|
|
55
63
|
def get_builder(self):
|
|
56
64
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
Builder implementation for this family.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
portable_python.PythonBuilder
|
|
70
|
+
Builder implementation for this family
|
|
59
71
|
"""
|
|
60
72
|
|
|
61
73
|
|
|
@@ -105,7 +117,6 @@ class CPythonFamily(VersionFamily):
|
|
|
105
117
|
|
|
106
118
|
|
|
107
119
|
class Folders:
|
|
108
|
-
|
|
109
120
|
def __init__(self, config: Config, base=None, family=None, version=None):
|
|
110
121
|
self.config = config
|
|
111
122
|
self.base_folder = runez.resolved_path(base)
|
|
@@ -160,10 +171,21 @@ class Folders:
|
|
|
160
171
|
|
|
161
172
|
|
|
162
173
|
class PPG:
|
|
163
|
-
"""
|
|
174
|
+
"""
|
|
175
|
+
Global settings for portable-python
|
|
176
|
+
|
|
177
|
+
Attributes
|
|
178
|
+
----------
|
|
179
|
+
families : dict[str, VersionFamily]
|
|
180
|
+
Mapping of family names to implementations
|
|
181
|
+
config : portable_python.config.Config
|
|
182
|
+
Global configuration
|
|
183
|
+
target : runez.system.PlatformId
|
|
184
|
+
Target platform
|
|
185
|
+
"""
|
|
164
186
|
|
|
165
187
|
cpython = CPythonFamily()
|
|
166
|
-
families =
|
|
188
|
+
families: ClassVar[dict] = {"cpython": cpython}
|
|
167
189
|
config = Config()
|
|
168
190
|
target = config.target
|
|
169
191
|
|
|
@@ -48,7 +48,7 @@ def test_cleanup(cli, monkeypatch):
|
|
|
48
48
|
assert "MACOSX_DEPLOYMENT_TARGET" not in cli.logged
|
|
49
49
|
assert "selected: all" in cli.logged
|
|
50
50
|
assert f"Would symlink {install_dir}/bin/pip{f.mm} <- {install_dir}/bin/pip" in cli.logged
|
|
51
|
-
assert f"Would symlink {lib}/python{f.mm}/config-{f.mm}-darwin/libpython{f.mm}.a <- {lib}/libpython{f.mm}.a"
|
|
51
|
+
assert f"Would symlink {lib}/python{f.mm}/config-{f.mm}-darwin/libpython{f.mm}.a <- {lib}/libpython{f.mm}.a" in cli.logged
|
|
52
52
|
assert f"Would tar {install_dir} -> dist/cpython-{f.version}-linux-x86_64.tar.gz" in cli.logged
|
|
53
53
|
|
|
54
54
|
runez.touch("bin/brew", logger=None)
|
|
@@ -133,7 +133,7 @@ class MockSharedExeRun:
|
|
|
133
133
|
if args[1] == "--print-rpath":
|
|
134
134
|
return self.prefix
|
|
135
135
|
|
|
136
|
-
def _macos_run(self, program, *
|
|
136
|
+
def _macos_run(self, program, *_):
|
|
137
137
|
if program == "otool":
|
|
138
138
|
return f"foo/bin/python:\n {self.prefix}/lib/libpython3.9.dylib (...)\n /usr/lib/... (...)"
|
|
139
139
|
|
|
@@ -3,7 +3,6 @@ from runez.http import RestClient
|
|
|
3
3
|
from portable_python import BuildSetup
|
|
4
4
|
from portable_python.versions import CPythonFamily, PPG
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
REST_CLIENT = RestClient()
|
|
8
7
|
GH_CPYTHON_SAMPLE = """
|
|
9
8
|
[
|
|
@@ -21,10 +20,12 @@ PYTHON_ORG_SAMPLE = """
|
|
|
21
20
|
"""
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
@REST_CLIENT.mock(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
@REST_CLIENT.mock(
|
|
24
|
+
{
|
|
25
|
+
"https://www.python.org/ftp/python/": PYTHON_ORG_SAMPLE,
|
|
26
|
+
"https://api.github.com/repos/python/cpython/git/matching-refs/tags/v3.": GH_CPYTHON_SAMPLE,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
28
29
|
def test_list(cli, monkeypatch):
|
|
29
30
|
# Edge cases
|
|
30
31
|
monkeypatch.setattr(PPG, "config", None)
|
|
@@ -5,8 +5,8 @@ from portable_python import BuildSetup, ModuleBuilder
|
|
|
5
5
|
from portable_python.versions import PPG
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def test_config(cli
|
|
9
|
-
with pytest.raises(
|
|
8
|
+
def test_config(cli):
|
|
9
|
+
with pytest.raises(runez.system.AbortException):
|
|
10
10
|
PPG.config.parsed_yaml("a: b\ninvalid line", "testing")
|
|
11
11
|
|
|
12
12
|
cli.run("-ntmacos-arm64", "-c", cli.tests_path("sample-config1.yml"), "build", "3.9.7", "-mnone")
|
|
@@ -34,8 +34,8 @@ def test_diagnostics(cli):
|
|
|
34
34
|
|
|
35
35
|
def test_edge_cases(temp_folder, monkeypatch, logged):
|
|
36
36
|
monkeypatch.setattr(PPG, "config", None)
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
PPG.grab_config(runez.DEV.tests_path("sample-incomplete.yml"))
|
|
38
|
+
with pytest.raises(runez.system.AbortException):
|
|
39
39
|
PPG.get_folders()
|
|
40
40
|
assert "Folder 'destdir' must be configured" in logged.pop()
|
|
41
41
|
|
|
@@ -56,9 +56,10 @@ def test_edge_cases(temp_folder, monkeypatch, logged):
|
|
|
56
56
|
assert outcome.name == "failed"
|
|
57
57
|
assert reason == "broken, can't compile statically with foo present"
|
|
58
58
|
|
|
59
|
-
PPG.config
|
|
59
|
+
sources = getattr(PPG.config, "_sources", None)
|
|
60
|
+
monkeypatch.setattr(sources[0], "data", {"ext": "foo"})
|
|
60
61
|
assert not logged
|
|
61
|
-
with pytest.raises(
|
|
62
|
+
with pytest.raises(runez.system.AbortException):
|
|
62
63
|
_ = BuildSetup("3.9.6")
|
|
63
64
|
assert "Invalid extension 'foo'" in logged.pop()
|
|
64
65
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{portable-python-1.8.4 → portable-python-1.8.5}/src/portable_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|