portable-python 1.8.4__tar.gz → 1.8.6__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.6}/PKG-INFO +1 -1
- portable-python-1.8.6/pyproject.toml +76 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/setup.py +0 -1
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/__init__.py +51 -36
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/cli.py +7 -7
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/config.py +68 -42
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/cpython.py +41 -31
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/_inspect.py +10 -11
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/tkinter.py +3 -2
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/xcpython.py +33 -16
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/inspector.py +21 -17
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/tracking.py +0 -3
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/versions.py +31 -9
- {portable-python-1.8.4 → portable-python-1.8.6/src/portable_python.egg-info}/PKG-INFO +1 -1
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_cleanup.py +1 -1
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_failed.py +1 -1
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_inspector.py +1 -1
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_list.py +6 -5
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_setup.py +7 -6
- portable-python-1.8.4/pyproject.toml +0 -2
- {portable-python-1.8.4 → portable-python-1.8.6}/DEVELOP.md +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/LICENSE +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/MANIFEST.in +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/README.rst +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/SECURITY.md +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/requirements.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/setup.cfg +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/__main__.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/__init__.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/SOURCES.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/dependency_links.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/entry_points.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/requires.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/top_level.txt +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_build.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_invoker.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_prefix.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_recompress.py +0 -0
- {portable-python-1.8.4 → portable-python-1.8.6}/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
|