kivyforge 3.0.0.dev0__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.
- kivyforge/__init__.py +9 -0
- kivyforge/artifacts/__init__.py +41 -0
- kivyforge/artifacts/cache.py +81 -0
- kivyforge/artifacts/collect.py +354 -0
- kivyforge/artifacts/download.py +125 -0
- kivyforge/artifacts/frameworks.py +240 -0
- kivyforge/artifacts/native_stage_util.py +346 -0
- kivyforge/artifacts/runtime.py +65 -0
- kivyforge/artifacts/verify.py +45 -0
- kivyforge/artifacts/wheels.py +89 -0
- kivyforge/bundle/__init__.py +1 -0
- kivyforge/bundle/pycompile.py +161 -0
- kivyforge/cli/__init__.py +52 -0
- kivyforge/cli/_common.py +62 -0
- kivyforge/cli/_platform.py +84 -0
- kivyforge/cli/build.py +111 -0
- kivyforge/cli/clean.py +135 -0
- kivyforge/cli/doctor.py +52 -0
- kivyforge/cli/init.py +612 -0
- kivyforge/cli/init_writer.py +716 -0
- kivyforge/cli/lock.py +261 -0
- kivyforge/cli/open_cmd.py +15 -0
- kivyforge/cli/package.py +134 -0
- kivyforge/cli/run.py +162 -0
- kivyforge/cli/status.py +20 -0
- kivyforge/cli/upgrade.py +314 -0
- kivyforge/config/__init__.py +46 -0
- kivyforge/config/errors.py +43 -0
- kivyforge/config/icons.py +60 -0
- kivyforge/config/loader.py +3079 -0
- kivyforge/config/model.py +817 -0
- kivyforge/doctor/__init__.py +19 -0
- kivyforge/doctor/checks_common.py +95 -0
- kivyforge/doctor/probe.py +357 -0
- kivyforge/doctor/result.py +41 -0
- kivyforge/lock/__init__.py +42 -0
- kivyforge/lock/_pip_shim.py +63 -0
- kivyforge/lock/find_links.py +182 -0
- kivyforge/lock/model.py +69 -0
- kivyforge/lock/pep751.py +111 -0
- kivyforge/lock/reader.py +30 -0
- kivyforge/lock/resolver.py +124 -0
- kivyforge/lock/wheelruntime/__init__.py +70 -0
- kivyforge/lock/wheelruntime/builder.py +280 -0
- kivyforge/lock/wheelruntime/model.py +94 -0
- kivyforge/lock/wheelruntime/native_binaries.py +127 -0
- kivyforge/lock/wheelruntime/pbs_github.py +133 -0
- kivyforge/lock/wheelruntime/profile.py +104 -0
- kivyforge/lock/wheelruntime/resolver.py +248 -0
- kivyforge/lock/wheelruntime/runtime.py +161 -0
- kivyforge/lock/wheelruntime/serialize.py +245 -0
- kivyforge/platforms/__init__.py +114 -0
- kivyforge/platforms/android/__init__.py +140 -0
- kivyforge/platforms/android/adb.py +215 -0
- kivyforge/platforms/android/bootstrap/__init__.py +7 -0
- kivyforge/platforms/android/bootstrap/contract.py +166 -0
- kivyforge/platforms/android/bootstrap/render.py +244 -0
- kivyforge/platforms/android/bootstrap/templates/androidtest/KivyforgeContractTest.java +76 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivy_bootstrap.py +50 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_bootstrap.py +54 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_selftest.py +97 -0
- kivyforge/platforms/android/bootstrap/templates/cpp/CMakeLists.txt +20 -0
- kivyforge/platforms/android/bootstrap/templates/cpp/main.c +150 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/jnius/NativeInvocationHandler.java +48 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonActivity.java +75 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonBundle.java +132 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonService.java +189 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/renpy/android/Hardware.java +276 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/LICENSE-SDL.txt +18 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/SDL_REVISION.txt +3 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDevice.java +22 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceBLESteamController.java +650 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceManager.java +698 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceUSB.java +309 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDL.java +90 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLActivity.java +2120 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLAudioManager.java +514 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLControllerManager.java +856 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLSurface.java +405 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/LICENSE-SDL.txt +18 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/SDL_REVISION.txt +3 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDevice.java +21 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceBLESteamController.java +829 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceManager.java +698 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceUSB.java +354 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDL.java +90 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLActivity.java +2240 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLAudioManager.java +126 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLControllerManager.java +1010 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLDummyEdit.java +66 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLInputConnection.java +136 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLSensorManager.java +73 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLSurface.java +464 -0
- kivyforge/platforms/android/cli.py +1023 -0
- kivyforge/platforms/android/doctor.py +899 -0
- kivyforge/platforms/android/elf.py +148 -0
- kivyforge/platforms/android/generate/__init__.py +1 -0
- kivyforge/platforms/android/generate/manifest.py +261 -0
- kivyforge/platforms/android/generate/project.py +462 -0
- kivyforge/platforms/android/generate/services.py +187 -0
- kivyforge/platforms/android/gradle_wrapper/NOTICE +3 -0
- kivyforge/platforms/android/gradle_wrapper/SHA256SUMS +1 -0
- kivyforge/platforms/android/gradle_wrapper/gradle-wrapper.jar +0 -0
- kivyforge/platforms/android/gradle_wrapper/gradlew +164 -0
- kivyforge/platforms/android/gradle_wrapper/gradlew.bat +90 -0
- kivyforge/platforms/android/gradlew.py +60 -0
- kivyforge/platforms/android/icons.py +237 -0
- kivyforge/platforms/android/lock/__init__.py +1 -0
- kivyforge/platforms/android/lock/archives.py +109 -0
- kivyforge/platforms/android/lock/builder.py +387 -0
- kivyforge/platforms/android/lock/markers.py +71 -0
- kivyforge/platforms/android/lock/maven.py +252 -0
- kivyforge/platforms/android/lock/model.py +113 -0
- kivyforge/platforms/android/lock/python_meta.py +199 -0
- kivyforge/platforms/android/lock/reader.py +206 -0
- kivyforge/platforms/android/lock/resolver.py +276 -0
- kivyforge/platforms/android/lock/writer.py +121 -0
- kivyforge/platforms/android/policy.py +214 -0
- kivyforge/platforms/android/signing.py +145 -0
- kivyforge/platforms/android/smoke.py +43 -0
- kivyforge/platforms/android/splash.py +293 -0
- kivyforge/platforms/android/stage/__init__.py +1 -0
- kivyforge/platforms/android/stage/bundle.py +295 -0
- kivyforge/platforms/android/stage/jnilibs.py +186 -0
- kivyforge/platforms/android/stage/runtime.py +61 -0
- kivyforge/platforms/android/stage/wheels.py +106 -0
- kivyforge/platforms/android/toolchain.py +29 -0
- kivyforge/platforms/base.py +157 -0
- kivyforge/platforms/ios/__init__.py +129 -0
- kivyforge/platforms/ios/assets.py +193 -0
- kivyforge/platforms/ios/buildsettings.py +128 -0
- kivyforge/platforms/ios/cli.py +543 -0
- kivyforge/platforms/ios/doctor.py +410 -0
- kivyforge/platforms/ios/entitlements.py +34 -0
- kivyforge/platforms/ios/generator.py +318 -0
- kivyforge/platforms/ios/lock/__init__.py +71 -0
- kivyforge/platforms/ios/lock/builder.py +393 -0
- kivyforge/platforms/ios/lock/markers.py +69 -0
- kivyforge/platforms/ios/lock/model.py +95 -0
- kivyforge/platforms/ios/lock/python_meta.py +187 -0
- kivyforge/platforms/ios/lock/reader.py +191 -0
- kivyforge/platforms/ios/lock/resolver.py +304 -0
- kivyforge/platforms/ios/lock/spm.py +217 -0
- kivyforge/platforms/ios/lock/writer.py +114 -0
- kivyforge/platforms/ios/lock/xcframework.py +188 -0
- kivyforge/platforms/ios/materialize.py +59 -0
- kivyforge/platforms/ios/plist.py +91 -0
- kivyforge/platforms/ios/privacy.py +39 -0
- kivyforge/platforms/ios/skeleton.py +165 -0
- kivyforge/platforms/ios/sources.py +63 -0
- kivyforge/platforms/ios/staging.py +225 -0
- kivyforge/platforms/ios/swift_packages.py +324 -0
- kivyforge/platforms/ios/templates/kivyforge_bootstrap.h +34 -0
- kivyforge/platforms/ios/templates/kivyforge_bootstrap.m +146 -0
- kivyforge/platforms/ios/templates/main.m +15 -0
- kivyforge/platforms/ios/templates/main_config.h.in +12 -0
- kivyforge/platforms/ios/xcframeworks.py +194 -0
- kivyforge/platforms/ios/xcode/__init__.py +76 -0
- kivyforge/platforms/ios/xcode/commands.py +296 -0
- kivyforge/platforms/ios/xcode/runner.py +316 -0
- kivyforge/platforms/linux/__init__.py +120 -0
- kivyforge/platforms/linux/appimage.py +169 -0
- kivyforge/platforms/linux/bundle.py +235 -0
- kivyforge/platforms/linux/cli.py +232 -0
- kivyforge/platforms/linux/desktop.py +71 -0
- kivyforge/platforms/linux/doctor.py +438 -0
- kivyforge/platforms/linux/elftools.py +95 -0
- kivyforge/platforms/linux/icons.py +129 -0
- kivyforge/platforms/linux/launcher.py +107 -0
- kivyforge/platforms/linux/lock/__init__.py +126 -0
- kivyforge/platforms/linux/lock/profile.py +254 -0
- kivyforge/platforms/linux/lock/runtime.py +75 -0
- kivyforge/platforms/linux/native_stage.py +47 -0
- kivyforge/platforms/linux/runtime_stage.py +107 -0
- kivyforge/platforms/linux/wheels_stage.py +103 -0
- kivyforge/platforms/macos/__init__.py +121 -0
- kivyforge/platforms/macos/bundle.py +255 -0
- kivyforge/platforms/macos/cli.py +269 -0
- kivyforge/platforms/macos/doctor.py +480 -0
- kivyforge/platforms/macos/icns.py +75 -0
- kivyforge/platforms/macos/launcher.py +118 -0
- kivyforge/platforms/macos/lock/__init__.py +112 -0
- kivyforge/platforms/macos/lock/profile.py +119 -0
- kivyforge/platforms/macos/lock/runtime.py +58 -0
- kivyforge/platforms/macos/machotools.py +138 -0
- kivyforge/platforms/macos/native_stage.py +47 -0
- kivyforge/platforms/macos/notarize.py +143 -0
- kivyforge/platforms/macos/plist.py +37 -0
- kivyforge/platforms/macos/runtime_stage.py +110 -0
- kivyforge/platforms/macos/signing.py +78 -0
- kivyforge/platforms/macos/wheels_stage.py +108 -0
- kivyforge/platforms/windows/__init__.py +133 -0
- kivyforge/platforms/windows/assets.py +76 -0
- kivyforge/platforms/windows/bundle.py +298 -0
- kivyforge/platforms/windows/cli.py +314 -0
- kivyforge/platforms/windows/doctor.py +563 -0
- kivyforge/platforms/windows/fsswap.py +87 -0
- kivyforge/platforms/windows/icons.py +64 -0
- kivyforge/platforms/windows/launcher/__init__.py +193 -0
- kivyforge/platforms/windows/launcher/build_launcher.py +507 -0
- kivyforge/platforms/windows/launcher/launcher.c +379 -0
- kivyforge/platforms/windows/lock/__init__.py +112 -0
- kivyforge/platforms/windows/lock/profile.py +101 -0
- kivyforge/platforms/windows/lock/runtime.py +64 -0
- kivyforge/platforms/windows/naming.py +52 -0
- kivyforge/platforms/windows/native_stage.py +66 -0
- kivyforge/platforms/windows/petools.py +98 -0
- kivyforge/platforms/windows/rcedit.py +116 -0
- kivyforge/platforms/windows/runtime_stage.py +221 -0
- kivyforge/platforms/windows/signing.py +168 -0
- kivyforge/platforms/windows/vendor/LICENSE.rcedit +29 -0
- kivyforge/platforms/windows/vendor/NOTICE +17 -0
- kivyforge/platforms/windows/vendor/SHA256SUMS +4 -0
- kivyforge/platforms/windows/vendor/TOOLSET.txt +4 -0
- kivyforge/platforms/windows/vendor/__init__.py +12 -0
- kivyforge/platforms/windows/vendor/fetch_rcedit.py +97 -0
- kivyforge/platforms/windows/vendor/launcher-amd64.exe +0 -0
- kivyforge/platforms/windows/vendor/rcedit-x64.exe +0 -0
- kivyforge/platforms/windows/wheels_stage.py +139 -0
- kivyforge-3.0.0.dev0.dist-info/METADATA +609 -0
- kivyforge-3.0.0.dev0.dist-info/RECORD +225 -0
- kivyforge-3.0.0.dev0.dist-info/WHEEL +5 -0
- kivyforge-3.0.0.dev0.dist-info/entry_points.txt +3 -0
- kivyforge-3.0.0.dev0.dist-info/licenses/LICENSE +21 -0
- kivyforge-3.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Validate ``[tool.kivy.<platform>].find_links`` before lock resolution.
|
|
2
|
+
|
|
3
|
+
``find_links`` is a shared surface across iOS/macOS/Linux, so every diagnostic
|
|
4
|
+
here is parameterized by ``platform`` (the ``[tool.kivy.<platform>]`` key path
|
|
5
|
+
and platform-appropriate wording). ``platform`` defaults to ``"ios"`` for
|
|
6
|
+
backward compatibility with the original iOS-only call sites.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FindLinksError(Exception):
|
|
16
|
+
"""A find_links directory is missing or has no usable wheels."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def resolve_find_links(project_root: Path, entries: tuple[str, ...]) -> list[str]:
|
|
20
|
+
"""Resolve repo-relative ``find_links`` entries to absolute paths."""
|
|
21
|
+
root = project_root.resolve()
|
|
22
|
+
return [str((root / entry).resolve()) for entry in entries]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_find_links(
|
|
26
|
+
project_root: Path, entries: tuple[str, ...], *, platform: str = "ios"
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Raise ``FindLinksError`` when a find_links directory is missing or unusable."""
|
|
29
|
+
if not entries:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
key = f"[tool.kivy.{platform}].find_links"
|
|
33
|
+
root = project_root.resolve()
|
|
34
|
+
lines: list[str] = []
|
|
35
|
+
for entry in entries:
|
|
36
|
+
path = (root / entry).resolve()
|
|
37
|
+
rel = _display_path(root, path, entry)
|
|
38
|
+
if not path.exists():
|
|
39
|
+
lines.append(f"{key} entry {entry!r} does not exist ({rel}).")
|
|
40
|
+
continue
|
|
41
|
+
if not path.is_dir():
|
|
42
|
+
lines.append(f"{key} entry {entry!r} is not a directory ({rel}).")
|
|
43
|
+
continue
|
|
44
|
+
if not any(path.glob("*.whl")):
|
|
45
|
+
lines.append(f"{key} entry {entry!r} contains no .whl files ({rel}).")
|
|
46
|
+
|
|
47
|
+
if not lines:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
hint = _find_links_hint(root, entries, platform)
|
|
51
|
+
message = "\n".join(lines)
|
|
52
|
+
if hint:
|
|
53
|
+
message = f"{message}\n{hint}"
|
|
54
|
+
raise FindLinksError(message)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def wheel_path_from_project_root(project_root: Path, resolved: Path) -> str:
|
|
58
|
+
"""Return a project-root-relative wheel path (may contain ``..``)."""
|
|
59
|
+
root = project_root.resolve()
|
|
60
|
+
resolved = resolved.resolve()
|
|
61
|
+
_ensure_find_link_scope(root, resolved)
|
|
62
|
+
return Path(os.path.relpath(resolved, root)).as_posix()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def find_links_resolution_hint(
|
|
66
|
+
project_root: Path,
|
|
67
|
+
entries: tuple[str, ...],
|
|
68
|
+
*,
|
|
69
|
+
pip_stderr: str = "",
|
|
70
|
+
platform: str = "ios",
|
|
71
|
+
) -> str | None:
|
|
72
|
+
"""Return extra guidance when pip resolution likely failed for find_links reasons."""
|
|
73
|
+
if not entries:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
root = project_root.resolve()
|
|
77
|
+
pip_text = pip_stderr.lower()
|
|
78
|
+
if (
|
|
79
|
+
"no matching distribution" not in pip_text
|
|
80
|
+
and "could not find a version" not in pip_text
|
|
81
|
+
):
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
lines: list[str] = []
|
|
85
|
+
for entry in entries:
|
|
86
|
+
path = (root / entry).resolve()
|
|
87
|
+
rel = _display_path(root, path, entry)
|
|
88
|
+
if not path.exists():
|
|
89
|
+
lines.append(f" • {entry!r} → missing ({rel})")
|
|
90
|
+
elif not path.is_dir():
|
|
91
|
+
lines.append(f" • {entry!r} → not a directory ({rel})")
|
|
92
|
+
elif not any(path.glob("*.whl")):
|
|
93
|
+
lines.append(
|
|
94
|
+
f" • {entry!r} → directory exists but has no .whl files ({rel})"
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
count = len(list(path.glob("*.whl")))
|
|
98
|
+
lines.append(
|
|
99
|
+
f" • {entry!r} → {count} wheel(s) present ({rel}); "
|
|
100
|
+
"none matched this target's platform tags or a dependency."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if not lines:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
"find_links check:\n"
|
|
108
|
+
+ "\n".join(lines)
|
|
109
|
+
+ "\n"
|
|
110
|
+
+ _find_links_hint(root, entries, platform)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def find_links_doctor_detail(
|
|
115
|
+
project_root: Path, entry: str, path: Path, *, platform: str = "ios"
|
|
116
|
+
) -> tuple[str, str | None]:
|
|
117
|
+
"""Return (detail, hint) for a single find_links entry."""
|
|
118
|
+
rel = _display_path(project_root.resolve(), path, entry)
|
|
119
|
+
if not path.exists():
|
|
120
|
+
return f"{entry!r} missing ({rel})", _find_links_hint(
|
|
121
|
+
project_root, (entry,), platform
|
|
122
|
+
)
|
|
123
|
+
if not path.is_dir():
|
|
124
|
+
return f"{entry!r} is not a directory ({rel})", None
|
|
125
|
+
wheels = list(path.glob("*.whl"))
|
|
126
|
+
if not wheels:
|
|
127
|
+
return f"{entry!r} has no .whl files ({rel})", _find_links_hint(
|
|
128
|
+
project_root, (entry,), platform
|
|
129
|
+
)
|
|
130
|
+
return f"{entry!r} ({len(wheels)} wheel(s) in {rel})", None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _find_links_hint(
|
|
134
|
+
project_root: Path, entries: tuple[str, ...], platform: str = "ios"
|
|
135
|
+
) -> str:
|
|
136
|
+
del entries # reserved for future per-entry hints
|
|
137
|
+
shared_note = ""
|
|
138
|
+
if "examples" in project_root.parts and project_root.name != "wheels":
|
|
139
|
+
if platform == "ios":
|
|
140
|
+
# iOS ships a helper script to build its examples wheelhouse.
|
|
141
|
+
shared_note = (
|
|
142
|
+
" Shared example wheels live under examples/wheels/ios/ — run "
|
|
143
|
+
"`scripts/build_ios_wheels.sh` from the repo root.\n"
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
shared_note = (
|
|
147
|
+
f" Shared example wheels live under examples/wheels/{platform}/.\n"
|
|
148
|
+
)
|
|
149
|
+
return (
|
|
150
|
+
f"{shared_note}"
|
|
151
|
+
f" Add locally built {platform} wheels to the directory, then re-run "
|
|
152
|
+
"`kivyforge lock`."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _display_path(project_root: Path, path: Path, entry: str) -> str:
|
|
157
|
+
try:
|
|
158
|
+
return path.relative_to(project_root).as_posix()
|
|
159
|
+
except ValueError:
|
|
160
|
+
return entry
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _repo_root(start: Path) -> Path | None:
|
|
164
|
+
"""Nearest ancestor (inclusive) containing a ``.git`` marker, else None."""
|
|
165
|
+
for candidate in (start, *start.parents):
|
|
166
|
+
if (candidate / ".git").exists():
|
|
167
|
+
return candidate
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _ensure_find_link_scope(project_root: Path, resolved: Path) -> None:
|
|
172
|
+
allowed = [project_root, project_root.parent]
|
|
173
|
+
repo = _repo_root(project_root)
|
|
174
|
+
if repo is not None:
|
|
175
|
+
allowed.append(repo)
|
|
176
|
+
for base in allowed:
|
|
177
|
+
if resolved == base or base in resolved.parents:
|
|
178
|
+
return
|
|
179
|
+
raise FindLinksError(
|
|
180
|
+
f"find_links path {resolved} is outside the project directory, its "
|
|
181
|
+
"parent, and the enclosing repository"
|
|
182
|
+
)
|
kivyforge/lock/model.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Shared, platform-neutral ``[[packages]]`` model (PEP 751).
|
|
2
|
+
|
|
3
|
+
The per-slice wheel + package dataclasses here are consumed by every backend
|
|
4
|
+
(iOS, macOS, Linux) and the generic wheel+runtime engine. Platform-specific
|
|
5
|
+
lockfile models (the iOS ``Lockfile`` + xcframework/swift-package entries, the
|
|
6
|
+
macOS/Linux ``WheelRuntimeLock``) build on top of these.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
LOCK_VERSION = "1.0"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class LockedWheel:
|
|
19
|
+
"""One ``[[packages.wheels]]`` entry (a single platform slice)."""
|
|
20
|
+
|
|
21
|
+
name: str # wheel filename; platform tag is parsed back out of this
|
|
22
|
+
sha256: str
|
|
23
|
+
url: str | None = None
|
|
24
|
+
path: str | None = None
|
|
25
|
+
upload_time: str | None = None
|
|
26
|
+
size: int | None = None
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
if bool(self.url) == bool(self.path):
|
|
30
|
+
raise ValueError(f"wheel {self.name!r} must have exactly one of url/path")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def platform_tag(self) -> str:
|
|
34
|
+
"""The platform tag parsed from the wheel filename (last tag segment)."""
|
|
35
|
+
stem = self.name[:-4] if self.name.endswith(".whl") else self.name
|
|
36
|
+
return stem.rsplit("-", 1)[-1]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def is_pure_python(self) -> bool:
|
|
40
|
+
return "none-any" in self.name
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class PackageDep:
|
|
45
|
+
name: str
|
|
46
|
+
marker: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class LockedPackage:
|
|
51
|
+
"""One ``[[packages]]`` entry."""
|
|
52
|
+
|
|
53
|
+
name: str
|
|
54
|
+
version: str
|
|
55
|
+
wheels: tuple[LockedWheel, ...]
|
|
56
|
+
requires_python: str | None = None
|
|
57
|
+
dependencies: tuple[PackageDep, ...] = ()
|
|
58
|
+
marker: str | None = None
|
|
59
|
+
direct_requirement: bool = False
|
|
60
|
+
source_index: str | None = None
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def sort_key(self) -> tuple[str, str]:
|
|
64
|
+
return (canonical_name(self.name), self.version)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def canonical_name(name: str) -> str:
|
|
68
|
+
"""PEP 503 normalization for package-name comparison."""
|
|
69
|
+
return re.sub(r"[-_.]+", "-", name).lower()
|
kivyforge/lock/pep751.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Shared, platform-neutral PEP 751 serialization helpers.
|
|
2
|
+
|
|
3
|
+
Both the iOS and macOS lock writers/readers emit and parse the ``[[packages]]``
|
|
4
|
+
wheels section identically; only the per-platform ``[tool.kivyforge]`` extension
|
|
5
|
+
(the iOS ``python_xcframework``/xcframeworks/swift_packages vs. the macOS python
|
|
6
|
+
runtime) differs. Keeping the ``[[packages]]`` machinery here means a single
|
|
7
|
+
source of truth for the PEP 751 layout and string escaping.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .model import LockedPackage, LockedWheel, PackageDep
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def s(value: str) -> str:
|
|
16
|
+
"""Emit a TOML basic string with minimal escaping."""
|
|
17
|
+
escaped = (
|
|
18
|
+
value.replace("\\", "\\\\")
|
|
19
|
+
.replace('"', '\\"')
|
|
20
|
+
.replace("\n", "\\n")
|
|
21
|
+
.replace("\t", "\\t")
|
|
22
|
+
)
|
|
23
|
+
return f'"{escaped}"'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def arr(values) -> str:
|
|
27
|
+
if not values:
|
|
28
|
+
return "[]"
|
|
29
|
+
return "[" + ", ".join(s(v) for v in values) + "]"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def b(value: bool) -> str:
|
|
33
|
+
return "true" if value else "false"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def emit_package(lines: list[str], pkg: LockedPackage) -> None:
|
|
37
|
+
lines.append("[[packages]]")
|
|
38
|
+
lines.append(f"name = {s(pkg.name)}")
|
|
39
|
+
lines.append(f"version = {s(pkg.version)}")
|
|
40
|
+
if pkg.requires_python:
|
|
41
|
+
lines.append(f"requires-python = {s(pkg.requires_python)}")
|
|
42
|
+
if pkg.marker:
|
|
43
|
+
lines.append(f"marker = {s(pkg.marker)}")
|
|
44
|
+
if pkg.dependencies:
|
|
45
|
+
deps = ", ".join(_dep_inline(d) for d in pkg.dependencies)
|
|
46
|
+
lines.append(f"dependencies = [{deps}]")
|
|
47
|
+
|
|
48
|
+
if pkg.direct_requirement or pkg.source_index:
|
|
49
|
+
lines.append("")
|
|
50
|
+
lines.append("[packages.tool.kivyforge]")
|
|
51
|
+
if pkg.direct_requirement:
|
|
52
|
+
lines.append("direct_requirement = true")
|
|
53
|
+
if pkg.source_index:
|
|
54
|
+
lines.append(f"source_index = {s(pkg.source_index)}")
|
|
55
|
+
|
|
56
|
+
for wheel in sorted(pkg.wheels, key=lambda w: w.name):
|
|
57
|
+
lines.append("")
|
|
58
|
+
emit_wheel(lines, wheel)
|
|
59
|
+
lines.append("")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def emit_wheel(lines: list[str], wheel: LockedWheel) -> None:
|
|
63
|
+
lines.append("[[packages.wheels]]")
|
|
64
|
+
lines.append(f"name = {s(wheel.name)}")
|
|
65
|
+
if wheel.upload_time:
|
|
66
|
+
lines.append(f"upload-time = {s(wheel.upload_time)}")
|
|
67
|
+
if wheel.url:
|
|
68
|
+
lines.append(f"url = {s(wheel.url)}")
|
|
69
|
+
else:
|
|
70
|
+
path = wheel.path
|
|
71
|
+
assert path is not None
|
|
72
|
+
lines.append(f"path = {s(path)}")
|
|
73
|
+
lines.append(f"hashes = {{ sha256 = {s(wheel.sha256)} }}")
|
|
74
|
+
if wheel.size is not None:
|
|
75
|
+
lines.append(f"size = {wheel.size}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def parse_package(p: dict) -> LockedPackage:
|
|
79
|
+
tool = p.get("tool", {}).get("kivyforge", {})
|
|
80
|
+
deps = tuple(
|
|
81
|
+
PackageDep(name=d["name"], marker=d.get("marker"))
|
|
82
|
+
for d in p.get("dependencies", [])
|
|
83
|
+
)
|
|
84
|
+
wheels = tuple(parse_wheel(w) for w in p.get("wheels", []))
|
|
85
|
+
return LockedPackage(
|
|
86
|
+
name=p["name"],
|
|
87
|
+
version=p["version"],
|
|
88
|
+
wheels=wheels,
|
|
89
|
+
requires_python=p.get("requires-python"),
|
|
90
|
+
dependencies=deps,
|
|
91
|
+
marker=p.get("marker"),
|
|
92
|
+
direct_requirement=bool(tool.get("direct_requirement", False)),
|
|
93
|
+
source_index=tool.get("source_index"),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_wheel(w: dict) -> LockedWheel:
|
|
98
|
+
return LockedWheel(
|
|
99
|
+
name=w["name"],
|
|
100
|
+
sha256=w.get("hashes", {}).get("sha256", ""),
|
|
101
|
+
url=w.get("url"),
|
|
102
|
+
path=w.get("path"),
|
|
103
|
+
upload_time=w.get("upload-time"),
|
|
104
|
+
size=w.get("size"),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _dep_inline(dep: PackageDep) -> str:
|
|
109
|
+
if dep.marker:
|
|
110
|
+
return f"{{ name = {s(dep.name)}, marker = {s(dep.marker)} }}"
|
|
111
|
+
return f"{{ name = {s(dep.name)} }}"
|
kivyforge/lock/reader.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Shared lockfile drift detection + the ``LockError`` type.
|
|
2
|
+
|
|
3
|
+
Every backend records the pyproject SHA-256 in its lockfile and compares it on
|
|
4
|
+
build to detect drift; those neutral helpers live here so iOS, macOS, and Linux
|
|
5
|
+
readers all reuse them. The iOS ``pylock.ios.toml`` parser lives in
|
|
6
|
+
``kivyforge.platforms.ios.lock.reader``; the wheel+runtime parser lives in
|
|
7
|
+
``kivyforge.lock.wheelruntime.serialize``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
from typing import Protocol
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LockError(Exception):
|
|
17
|
+
"""A malformed or unreadable lockfile."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _HasPyprojectSha(Protocol):
|
|
21
|
+
pyproject_sha256: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compute_pyproject_sha256(text: str) -> str:
|
|
25
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_in_sync(lock: _HasPyprojectSha, pyproject_text: str) -> bool:
|
|
29
|
+
"""True if the lock's recorded pyproject hash matches the current pyproject."""
|
|
30
|
+
return lock.pyproject_sha256 == compute_pyproject_sha256(pyproject_text)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Platform-neutral pip helpers shared by every wheel resolver (spec 02).
|
|
2
|
+
|
|
3
|
+
The iOS resolver (``kivyforge.platforms.ios.lock.resolver``) and the generic
|
|
4
|
+
wheel+runtime resolver (``kivyforge.lock.wheelruntime.resolver``) both drive pip
|
|
5
|
+
in dry-run ``--report`` mode per platform tag; the pip-version gate, version
|
|
6
|
+
formatting, ABI tag list, ``--python-version`` normalization, and requires-dist
|
|
7
|
+
name parsing are identical and live here.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
|
|
14
|
+
from packaging.requirements import InvalidRequirement, Requirement
|
|
15
|
+
|
|
16
|
+
# Minimum host pip that understands PEP 730 iOS platform tags (pip 24.3, 2024-10-27).
|
|
17
|
+
# Earlier pip cannot match an ``ios_<dt>_*`` --platform request against a wheel
|
|
18
|
+
# tagged at a lower floor (e.g. ios_13_0_*), so compatible wheels are reported as
|
|
19
|
+
# missing. kivyforge targets Python that ships iOS support, so requiring a current
|
|
20
|
+
# pip is simpler — and far less surprising — than reimplementing tag expansion.
|
|
21
|
+
MIN_PIP_VERSION = (24, 3)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def version_str(version: tuple[int, ...]) -> str:
|
|
25
|
+
return ".".join(str(part) for part in version)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def pip_version(python_executable: str) -> tuple[int, ...] | None:
|
|
29
|
+
"""Best-effort ``(major, minor, ...)`` version of pip for ``python_executable``.
|
|
30
|
+
|
|
31
|
+
Returns ``None`` when pip can't be queried — treated as "unknown" (don't
|
|
32
|
+
block) rather than "too old", since a genuinely broken pip surfaces its own
|
|
33
|
+
error when resolution runs.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
proc = subprocess.run(
|
|
37
|
+
[python_executable, "-m", "pip", "--version"],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
)
|
|
41
|
+
except (OSError, ValueError):
|
|
42
|
+
return None
|
|
43
|
+
if proc.returncode != 0:
|
|
44
|
+
return None
|
|
45
|
+
# Format: "pip 24.3.1 from /path/site-packages/pip (python 3.15)".
|
|
46
|
+
tokens = proc.stdout.split()
|
|
47
|
+
if len(tokens) < 2 or tokens[0] != "pip":
|
|
48
|
+
return None
|
|
49
|
+
return _parse_version(tokens[1])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _parse_version(text: str) -> tuple[int, ...] | None:
|
|
53
|
+
parts: list[int] = []
|
|
54
|
+
for chunk in text.split("."):
|
|
55
|
+
digits = "".join(c for c in chunk if c.isdigit())
|
|
56
|
+
if not digits:
|
|
57
|
+
break
|
|
58
|
+
parts.append(int(digits))
|
|
59
|
+
return tuple(parts) or None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def pip_python_version(python_version: str) -> str:
|
|
63
|
+
"""``pip install --python-version`` accepts only integer dotted parts."""
|
|
64
|
+
parts: list[str] = []
|
|
65
|
+
for chunk in python_version.split("."):
|
|
66
|
+
digits = "".join(c for c in chunk if c.isdigit())
|
|
67
|
+
if not digits:
|
|
68
|
+
break
|
|
69
|
+
parts.append(digits)
|
|
70
|
+
if len(parts) >= 2:
|
|
71
|
+
return f"{parts[0]}.{parts[1]}"
|
|
72
|
+
return python_version
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def abi_tags(python_version: str) -> tuple[str, ...]:
|
|
76
|
+
"""ABI tags to offer pip; cp<XY> first, then the limited-API/none fallbacks.
|
|
77
|
+
|
|
78
|
+
Per the Phase 0 findings we must not over-constrain abi (abi3 wheels would
|
|
79
|
+
be missed otherwise).
|
|
80
|
+
"""
|
|
81
|
+
major_minor = "".join(python_version.split(".")[:2])
|
|
82
|
+
return (f"cp{major_minor}", "abi3", "none")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def dep_names_for_environment(
|
|
86
|
+
requires_dist: list[str], environment: dict[str, str]
|
|
87
|
+
) -> list[str]:
|
|
88
|
+
"""The dependency edges that actually apply on the target platform.
|
|
89
|
+
|
|
90
|
+
``Requires-Dist`` lists every edge a distribution *might* have, gated by
|
|
91
|
+
markers: Kivy's raw list names ``pytest``, ``sphinx`` and
|
|
92
|
+
``kivy-deps.angle`` alongside its real runtime dependencies. Recording it
|
|
93
|
+
unfiltered would describe a dependency graph the lock does not install, so
|
|
94
|
+
each marker is evaluated against the target environment. ``extra`` is empty
|
|
95
|
+
there, which drops extra-gated edges too.
|
|
96
|
+
"""
|
|
97
|
+
names: list[str] = []
|
|
98
|
+
for raw in requires_dist or []:
|
|
99
|
+
try:
|
|
100
|
+
requirement = Requirement(raw)
|
|
101
|
+
except InvalidRequirement:
|
|
102
|
+
# Unparseable metadata is upstream's problem, not a reason to fail
|
|
103
|
+
# the lock; fall back to the permissive name-only reading.
|
|
104
|
+
names.extend(_dep_names([raw]))
|
|
105
|
+
continue
|
|
106
|
+
if requirement.marker is not None and not requirement.marker.evaluate(
|
|
107
|
+
environment
|
|
108
|
+
):
|
|
109
|
+
continue
|
|
110
|
+
names.append(requirement.name)
|
|
111
|
+
return names
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _dep_names(requires_dist: list[str]) -> list[str]:
|
|
115
|
+
names: list[str] = []
|
|
116
|
+
for raw in requires_dist or []:
|
|
117
|
+
# Strip everything after the name: extras, specifiers, markers.
|
|
118
|
+
name = raw.split(";", 1)[0].split("[", 1)[0]
|
|
119
|
+
for sep in ("==", ">=", "<=", "~=", "!=", ">", "<", "(", " "):
|
|
120
|
+
name = name.split(sep, 1)[0]
|
|
121
|
+
name = name.strip()
|
|
122
|
+
if name:
|
|
123
|
+
names.append(name)
|
|
124
|
+
return names
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Generic ``wheel + bundled-runtime`` lock engine.
|
|
2
|
+
|
|
3
|
+
The reusable core for every platform whose lock is "platform-tagged wheels plus
|
|
4
|
+
a bundled CPython runtime": macOS now; Linux, Windows, Android later. Each such
|
|
5
|
+
platform is a thin :class:`PlatformLockProfile` over this engine — no new
|
|
6
|
+
builder, resolver, model, or serializer. iOS is intentionally *not* in this
|
|
7
|
+
family (its per-slice framework/xcframework/SPM lock is a different shape).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .builder import (
|
|
13
|
+
WheelRuntimeBuildError,
|
|
14
|
+
build_wheel_runtime_lock,
|
|
15
|
+
diff_summary,
|
|
16
|
+
semantic_equal,
|
|
17
|
+
)
|
|
18
|
+
from .model import (
|
|
19
|
+
LockedNativeBinary,
|
|
20
|
+
PythonRuntime,
|
|
21
|
+
RuntimeArtifact,
|
|
22
|
+
WheelRuntimeLock,
|
|
23
|
+
)
|
|
24
|
+
from .native_binaries import resolve_native_binaries
|
|
25
|
+
from .profile import PlatformLockProfile
|
|
26
|
+
from .resolver import (
|
|
27
|
+
PipWheelResolver,
|
|
28
|
+
ResolvedPackage,
|
|
29
|
+
ResolvedWheel,
|
|
30
|
+
Variant,
|
|
31
|
+
WheelResolver,
|
|
32
|
+
WheelResolverError,
|
|
33
|
+
get_wheel_resolver,
|
|
34
|
+
)
|
|
35
|
+
from .runtime import (
|
|
36
|
+
PbsProvider,
|
|
37
|
+
ReleaseAsset,
|
|
38
|
+
RuntimeProvider,
|
|
39
|
+
RuntimeProviderError,
|
|
40
|
+
pbs_asset_glob,
|
|
41
|
+
)
|
|
42
|
+
from .serialize import dumps, load, loads
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"LockedNativeBinary",
|
|
46
|
+
"PbsProvider",
|
|
47
|
+
"PipWheelResolver",
|
|
48
|
+
"PlatformLockProfile",
|
|
49
|
+
"PythonRuntime",
|
|
50
|
+
"ReleaseAsset",
|
|
51
|
+
"ResolvedPackage",
|
|
52
|
+
"ResolvedWheel",
|
|
53
|
+
"RuntimeArtifact",
|
|
54
|
+
"RuntimeProvider",
|
|
55
|
+
"RuntimeProviderError",
|
|
56
|
+
"Variant",
|
|
57
|
+
"WheelResolver",
|
|
58
|
+
"WheelResolverError",
|
|
59
|
+
"WheelRuntimeBuildError",
|
|
60
|
+
"WheelRuntimeLock",
|
|
61
|
+
"build_wheel_runtime_lock",
|
|
62
|
+
"diff_summary",
|
|
63
|
+
"dumps",
|
|
64
|
+
"get_wheel_resolver",
|
|
65
|
+
"load",
|
|
66
|
+
"loads",
|
|
67
|
+
"pbs_asset_glob",
|
|
68
|
+
"resolve_native_binaries",
|
|
69
|
+
"semantic_equal",
|
|
70
|
+
]
|