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.
Files changed (225) hide show
  1. kivyforge/__init__.py +9 -0
  2. kivyforge/artifacts/__init__.py +41 -0
  3. kivyforge/artifacts/cache.py +81 -0
  4. kivyforge/artifacts/collect.py +354 -0
  5. kivyforge/artifacts/download.py +125 -0
  6. kivyforge/artifacts/frameworks.py +240 -0
  7. kivyforge/artifacts/native_stage_util.py +346 -0
  8. kivyforge/artifacts/runtime.py +65 -0
  9. kivyforge/artifacts/verify.py +45 -0
  10. kivyforge/artifacts/wheels.py +89 -0
  11. kivyforge/bundle/__init__.py +1 -0
  12. kivyforge/bundle/pycompile.py +161 -0
  13. kivyforge/cli/__init__.py +52 -0
  14. kivyforge/cli/_common.py +62 -0
  15. kivyforge/cli/_platform.py +84 -0
  16. kivyforge/cli/build.py +111 -0
  17. kivyforge/cli/clean.py +135 -0
  18. kivyforge/cli/doctor.py +52 -0
  19. kivyforge/cli/init.py +612 -0
  20. kivyforge/cli/init_writer.py +716 -0
  21. kivyforge/cli/lock.py +261 -0
  22. kivyforge/cli/open_cmd.py +15 -0
  23. kivyforge/cli/package.py +134 -0
  24. kivyforge/cli/run.py +162 -0
  25. kivyforge/cli/status.py +20 -0
  26. kivyforge/cli/upgrade.py +314 -0
  27. kivyforge/config/__init__.py +46 -0
  28. kivyforge/config/errors.py +43 -0
  29. kivyforge/config/icons.py +60 -0
  30. kivyforge/config/loader.py +3079 -0
  31. kivyforge/config/model.py +817 -0
  32. kivyforge/doctor/__init__.py +19 -0
  33. kivyforge/doctor/checks_common.py +95 -0
  34. kivyforge/doctor/probe.py +357 -0
  35. kivyforge/doctor/result.py +41 -0
  36. kivyforge/lock/__init__.py +42 -0
  37. kivyforge/lock/_pip_shim.py +63 -0
  38. kivyforge/lock/find_links.py +182 -0
  39. kivyforge/lock/model.py +69 -0
  40. kivyforge/lock/pep751.py +111 -0
  41. kivyforge/lock/reader.py +30 -0
  42. kivyforge/lock/resolver.py +124 -0
  43. kivyforge/lock/wheelruntime/__init__.py +70 -0
  44. kivyforge/lock/wheelruntime/builder.py +280 -0
  45. kivyforge/lock/wheelruntime/model.py +94 -0
  46. kivyforge/lock/wheelruntime/native_binaries.py +127 -0
  47. kivyforge/lock/wheelruntime/pbs_github.py +133 -0
  48. kivyforge/lock/wheelruntime/profile.py +104 -0
  49. kivyforge/lock/wheelruntime/resolver.py +248 -0
  50. kivyforge/lock/wheelruntime/runtime.py +161 -0
  51. kivyforge/lock/wheelruntime/serialize.py +245 -0
  52. kivyforge/platforms/__init__.py +114 -0
  53. kivyforge/platforms/android/__init__.py +140 -0
  54. kivyforge/platforms/android/adb.py +215 -0
  55. kivyforge/platforms/android/bootstrap/__init__.py +7 -0
  56. kivyforge/platforms/android/bootstrap/contract.py +166 -0
  57. kivyforge/platforms/android/bootstrap/render.py +244 -0
  58. kivyforge/platforms/android/bootstrap/templates/androidtest/KivyforgeContractTest.java +76 -0
  59. kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivy_bootstrap.py +50 -0
  60. kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_bootstrap.py +54 -0
  61. kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_selftest.py +97 -0
  62. kivyforge/platforms/android/bootstrap/templates/cpp/CMakeLists.txt +20 -0
  63. kivyforge/platforms/android/bootstrap/templates/cpp/main.c +150 -0
  64. kivyforge/platforms/android/bootstrap/templates/java/org/jnius/NativeInvocationHandler.java +48 -0
  65. kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonActivity.java +75 -0
  66. kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonBundle.java +132 -0
  67. kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonService.java +189 -0
  68. kivyforge/platforms/android/bootstrap/templates/java/org/renpy/android/Hardware.java +276 -0
  69. kivyforge/platforms/android/bootstrap/templates/sdl2/LICENSE-SDL.txt +18 -0
  70. kivyforge/platforms/android/bootstrap/templates/sdl2/SDL_REVISION.txt +3 -0
  71. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDevice.java +22 -0
  72. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceBLESteamController.java +650 -0
  73. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceManager.java +698 -0
  74. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceUSB.java +309 -0
  75. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDL.java +90 -0
  76. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLActivity.java +2120 -0
  77. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLAudioManager.java +514 -0
  78. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLControllerManager.java +856 -0
  79. kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLSurface.java +405 -0
  80. kivyforge/platforms/android/bootstrap/templates/sdl3/LICENSE-SDL.txt +18 -0
  81. kivyforge/platforms/android/bootstrap/templates/sdl3/SDL_REVISION.txt +3 -0
  82. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDevice.java +21 -0
  83. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceBLESteamController.java +829 -0
  84. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceManager.java +698 -0
  85. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceUSB.java +354 -0
  86. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDL.java +90 -0
  87. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLActivity.java +2240 -0
  88. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLAudioManager.java +126 -0
  89. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLControllerManager.java +1010 -0
  90. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLDummyEdit.java +66 -0
  91. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLInputConnection.java +136 -0
  92. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLSensorManager.java +73 -0
  93. kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLSurface.java +464 -0
  94. kivyforge/platforms/android/cli.py +1023 -0
  95. kivyforge/platforms/android/doctor.py +899 -0
  96. kivyforge/platforms/android/elf.py +148 -0
  97. kivyforge/platforms/android/generate/__init__.py +1 -0
  98. kivyforge/platforms/android/generate/manifest.py +261 -0
  99. kivyforge/platforms/android/generate/project.py +462 -0
  100. kivyforge/platforms/android/generate/services.py +187 -0
  101. kivyforge/platforms/android/gradle_wrapper/NOTICE +3 -0
  102. kivyforge/platforms/android/gradle_wrapper/SHA256SUMS +1 -0
  103. kivyforge/platforms/android/gradle_wrapper/gradle-wrapper.jar +0 -0
  104. kivyforge/platforms/android/gradle_wrapper/gradlew +164 -0
  105. kivyforge/platforms/android/gradle_wrapper/gradlew.bat +90 -0
  106. kivyforge/platforms/android/gradlew.py +60 -0
  107. kivyforge/platforms/android/icons.py +237 -0
  108. kivyforge/platforms/android/lock/__init__.py +1 -0
  109. kivyforge/platforms/android/lock/archives.py +109 -0
  110. kivyforge/platforms/android/lock/builder.py +387 -0
  111. kivyforge/platforms/android/lock/markers.py +71 -0
  112. kivyforge/platforms/android/lock/maven.py +252 -0
  113. kivyforge/platforms/android/lock/model.py +113 -0
  114. kivyforge/platforms/android/lock/python_meta.py +199 -0
  115. kivyforge/platforms/android/lock/reader.py +206 -0
  116. kivyforge/platforms/android/lock/resolver.py +276 -0
  117. kivyforge/platforms/android/lock/writer.py +121 -0
  118. kivyforge/platforms/android/policy.py +214 -0
  119. kivyforge/platforms/android/signing.py +145 -0
  120. kivyforge/platforms/android/smoke.py +43 -0
  121. kivyforge/platforms/android/splash.py +293 -0
  122. kivyforge/platforms/android/stage/__init__.py +1 -0
  123. kivyforge/platforms/android/stage/bundle.py +295 -0
  124. kivyforge/platforms/android/stage/jnilibs.py +186 -0
  125. kivyforge/platforms/android/stage/runtime.py +61 -0
  126. kivyforge/platforms/android/stage/wheels.py +106 -0
  127. kivyforge/platforms/android/toolchain.py +29 -0
  128. kivyforge/platforms/base.py +157 -0
  129. kivyforge/platforms/ios/__init__.py +129 -0
  130. kivyforge/platforms/ios/assets.py +193 -0
  131. kivyforge/platforms/ios/buildsettings.py +128 -0
  132. kivyforge/platforms/ios/cli.py +543 -0
  133. kivyforge/platforms/ios/doctor.py +410 -0
  134. kivyforge/platforms/ios/entitlements.py +34 -0
  135. kivyforge/platforms/ios/generator.py +318 -0
  136. kivyforge/platforms/ios/lock/__init__.py +71 -0
  137. kivyforge/platforms/ios/lock/builder.py +393 -0
  138. kivyforge/platforms/ios/lock/markers.py +69 -0
  139. kivyforge/platforms/ios/lock/model.py +95 -0
  140. kivyforge/platforms/ios/lock/python_meta.py +187 -0
  141. kivyforge/platforms/ios/lock/reader.py +191 -0
  142. kivyforge/platforms/ios/lock/resolver.py +304 -0
  143. kivyforge/platforms/ios/lock/spm.py +217 -0
  144. kivyforge/platforms/ios/lock/writer.py +114 -0
  145. kivyforge/platforms/ios/lock/xcframework.py +188 -0
  146. kivyforge/platforms/ios/materialize.py +59 -0
  147. kivyforge/platforms/ios/plist.py +91 -0
  148. kivyforge/platforms/ios/privacy.py +39 -0
  149. kivyforge/platforms/ios/skeleton.py +165 -0
  150. kivyforge/platforms/ios/sources.py +63 -0
  151. kivyforge/platforms/ios/staging.py +225 -0
  152. kivyforge/platforms/ios/swift_packages.py +324 -0
  153. kivyforge/platforms/ios/templates/kivyforge_bootstrap.h +34 -0
  154. kivyforge/platforms/ios/templates/kivyforge_bootstrap.m +146 -0
  155. kivyforge/platforms/ios/templates/main.m +15 -0
  156. kivyforge/platforms/ios/templates/main_config.h.in +12 -0
  157. kivyforge/platforms/ios/xcframeworks.py +194 -0
  158. kivyforge/platforms/ios/xcode/__init__.py +76 -0
  159. kivyforge/platforms/ios/xcode/commands.py +296 -0
  160. kivyforge/platforms/ios/xcode/runner.py +316 -0
  161. kivyforge/platforms/linux/__init__.py +120 -0
  162. kivyforge/platforms/linux/appimage.py +169 -0
  163. kivyforge/platforms/linux/bundle.py +235 -0
  164. kivyforge/platforms/linux/cli.py +232 -0
  165. kivyforge/platforms/linux/desktop.py +71 -0
  166. kivyforge/platforms/linux/doctor.py +438 -0
  167. kivyforge/platforms/linux/elftools.py +95 -0
  168. kivyforge/platforms/linux/icons.py +129 -0
  169. kivyforge/platforms/linux/launcher.py +107 -0
  170. kivyforge/platforms/linux/lock/__init__.py +126 -0
  171. kivyforge/platforms/linux/lock/profile.py +254 -0
  172. kivyforge/platforms/linux/lock/runtime.py +75 -0
  173. kivyforge/platforms/linux/native_stage.py +47 -0
  174. kivyforge/platforms/linux/runtime_stage.py +107 -0
  175. kivyforge/platforms/linux/wheels_stage.py +103 -0
  176. kivyforge/platforms/macos/__init__.py +121 -0
  177. kivyforge/platforms/macos/bundle.py +255 -0
  178. kivyforge/platforms/macos/cli.py +269 -0
  179. kivyforge/platforms/macos/doctor.py +480 -0
  180. kivyforge/platforms/macos/icns.py +75 -0
  181. kivyforge/platforms/macos/launcher.py +118 -0
  182. kivyforge/platforms/macos/lock/__init__.py +112 -0
  183. kivyforge/platforms/macos/lock/profile.py +119 -0
  184. kivyforge/platforms/macos/lock/runtime.py +58 -0
  185. kivyforge/platforms/macos/machotools.py +138 -0
  186. kivyforge/platforms/macos/native_stage.py +47 -0
  187. kivyforge/platforms/macos/notarize.py +143 -0
  188. kivyforge/platforms/macos/plist.py +37 -0
  189. kivyforge/platforms/macos/runtime_stage.py +110 -0
  190. kivyforge/platforms/macos/signing.py +78 -0
  191. kivyforge/platforms/macos/wheels_stage.py +108 -0
  192. kivyforge/platforms/windows/__init__.py +133 -0
  193. kivyforge/platforms/windows/assets.py +76 -0
  194. kivyforge/platforms/windows/bundle.py +298 -0
  195. kivyforge/platforms/windows/cli.py +314 -0
  196. kivyforge/platforms/windows/doctor.py +563 -0
  197. kivyforge/platforms/windows/fsswap.py +87 -0
  198. kivyforge/platforms/windows/icons.py +64 -0
  199. kivyforge/platforms/windows/launcher/__init__.py +193 -0
  200. kivyforge/platforms/windows/launcher/build_launcher.py +507 -0
  201. kivyforge/platforms/windows/launcher/launcher.c +379 -0
  202. kivyforge/platforms/windows/lock/__init__.py +112 -0
  203. kivyforge/platforms/windows/lock/profile.py +101 -0
  204. kivyforge/platforms/windows/lock/runtime.py +64 -0
  205. kivyforge/platforms/windows/naming.py +52 -0
  206. kivyforge/platforms/windows/native_stage.py +66 -0
  207. kivyforge/platforms/windows/petools.py +98 -0
  208. kivyforge/platforms/windows/rcedit.py +116 -0
  209. kivyforge/platforms/windows/runtime_stage.py +221 -0
  210. kivyforge/platforms/windows/signing.py +168 -0
  211. kivyforge/platforms/windows/vendor/LICENSE.rcedit +29 -0
  212. kivyforge/platforms/windows/vendor/NOTICE +17 -0
  213. kivyforge/platforms/windows/vendor/SHA256SUMS +4 -0
  214. kivyforge/platforms/windows/vendor/TOOLSET.txt +4 -0
  215. kivyforge/platforms/windows/vendor/__init__.py +12 -0
  216. kivyforge/platforms/windows/vendor/fetch_rcedit.py +97 -0
  217. kivyforge/platforms/windows/vendor/launcher-amd64.exe +0 -0
  218. kivyforge/platforms/windows/vendor/rcedit-x64.exe +0 -0
  219. kivyforge/platforms/windows/wheels_stage.py +139 -0
  220. kivyforge-3.0.0.dev0.dist-info/METADATA +609 -0
  221. kivyforge-3.0.0.dev0.dist-info/RECORD +225 -0
  222. kivyforge-3.0.0.dev0.dist-info/WHEEL +5 -0
  223. kivyforge-3.0.0.dev0.dist-info/entry_points.txt +3 -0
  224. kivyforge-3.0.0.dev0.dist-info/licenses/LICENSE +21 -0
  225. kivyforge-3.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,245 @@
1
+ """Serialize/parse ``pylock.<platform>.toml`` for the wheel+runtime family.
2
+
3
+ Reuses the platform-neutral PEP 751 ``[[packages]]`` emitter/parser
4
+ (``kivyforge.lock.pep751``) and adds the ``[tool.kivyforge]`` extension: the arch
5
+ set plus the bundled Python runtime pin (provider + per-arch artifacts). The
6
+ platform is carried by the filename, so ``loads``/``load`` take it explicitly
7
+ (used only for clear error messages and to stamp the model).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import tomllib
13
+ from pathlib import Path
14
+
15
+ from ..model import LOCK_VERSION
16
+ from ..pep751 import arr as _arr
17
+ from ..pep751 import emit_package as _emit_package
18
+ from ..pep751 import parse_package as _parse_package
19
+ from ..pep751 import s as _s
20
+ from ..reader import LockError
21
+ from .model import (
22
+ TOOL_SCHEMA_VERSION,
23
+ LockedNativeBinary,
24
+ PythonRuntime,
25
+ RuntimeArtifact,
26
+ WheelRuntimeLock,
27
+ )
28
+
29
+ SUPPORTED_LOCK_VERSION_MAJOR = int(LOCK_VERSION.split(".", 1)[0])
30
+ SUPPORTED_TOOL_SCHEMA_VERSION = TOOL_SCHEMA_VERSION
31
+
32
+
33
+ def dumps(lock: WheelRuntimeLock) -> str:
34
+ lines: list[str] = []
35
+
36
+ lines.append(f"lock-version = {_s(lock.lock_version)}")
37
+ lines.append(f"created-by = {_s(lock.created_by)}")
38
+ lines.append(f"requires-python = {_s(lock.requires_python)}")
39
+ lines.append(f"extras = {_arr(lock.extras)}")
40
+ lines.append(f"dependency-groups = {_arr(lock.dependency_groups)}")
41
+ lines.append(f"default-groups = {_arr(lock.default_groups)}")
42
+ lines.append("")
43
+
44
+ for pkg in sorted(lock.packages, key=lambda p: p.sort_key):
45
+ _emit_package(lines, pkg)
46
+
47
+ lines.append("[tool.kivyforge]")
48
+ lines.append(f"schema_version = {lock.schema_version}")
49
+ lines.append(f"kivyforge_version = {_s(lock.kivyforge_version)}")
50
+ lines.append(f"generated_at = {_s(lock.generated_at)}")
51
+ lines.append(f"pyproject_sha256 = {_s(lock.pyproject_sha256)}")
52
+ lines.append(
53
+ f"tool_kivyforge_schema_version = {lock.tool_kivyforge_schema_version}"
54
+ )
55
+ lines.append(f"archs = {_arr(lock.archs)}")
56
+ lines.append("")
57
+ lines.append("[tool.kivyforge.python_runtime]")
58
+ lines.append(f"provider = {_s(lock.python_runtime.provider)}")
59
+ lines.append(f"version = {_s(lock.python_runtime.version)}")
60
+ if lock.python_runtime.floor:
61
+ lines.append(f"floor = {_s(lock.python_runtime.floor)}")
62
+
63
+ for art in sorted(lock.python_runtime.artifacts, key=lambda a: a.arch):
64
+ lines.append("")
65
+ lines.append("[[tool.kivyforge.python_runtime.artifacts]]")
66
+ lines.append(f"arch = {_s(art.arch)}")
67
+ lines.append(f"url = {_s(art.url)}")
68
+ lines.append(f"sha256 = {_s(art.sha256)}")
69
+ lines.append(f"archive_format = {_s(art.archive_format)}")
70
+
71
+ for nb in sorted(lock.native_binaries, key=lambda b: b.name):
72
+ lines.append("")
73
+ lines.append("[[tool.kivyforge.native_binaries]]")
74
+ lines.append(f"name = {_s(nb.name)}")
75
+ lines.append(f"version = {_s(nb.version)}")
76
+ if nb.url is not None:
77
+ lines.append(f"url = {_s(nb.url)}")
78
+ else:
79
+ lines.append(f"path = {_s(nb.path)}")
80
+ lines.append(f"sha256 = {_s(nb.sha256)}")
81
+
82
+ return "\n".join(lines) + "\n"
83
+
84
+
85
+ def loads(text: str, *, platform: str) -> WheelRuntimeLock:
86
+ name = f"pylock.{platform}.toml"
87
+ try:
88
+ raw = tomllib.loads(text)
89
+ except tomllib.TOMLDecodeError as exc:
90
+ raise LockError(f"{name} is not valid TOML: {exc}") from exc
91
+ if not isinstance(raw, dict):
92
+ raise LockError(f"{name} must be a TOML table.")
93
+ try:
94
+ return _from_raw(raw, platform=platform, name=name)
95
+ except LockError:
96
+ raise
97
+ except (KeyError, TypeError, AttributeError, ValueError) as exc:
98
+ raise LockError(f"{name} is malformed: {exc!r}") from exc
99
+
100
+
101
+ def load(path: str | Path, *, platform: str) -> WheelRuntimeLock:
102
+ return loads(Path(path).read_text(encoding="utf-8"), platform=platform)
103
+
104
+
105
+ def _from_raw(raw: dict, *, platform: str, name: str) -> WheelRuntimeLock:
106
+ lock_version = _check_lock_version(raw, name)
107
+
108
+ tool_raw = raw.get("tool", {})
109
+ if not isinstance(tool_raw, dict):
110
+ raise LockError(f"{name} [tool] must be a table.")
111
+ tool = tool_raw.get("kivyforge", {})
112
+ if not isinstance(tool, dict) or not tool:
113
+ raise LockError(f"{name} is missing the [tool.kivyforge] table.")
114
+
115
+ schema_version = _check_tool_schema_version(tool)
116
+ runtime = _parse_runtime(tool, name)
117
+ native_binaries = _parse_native_binaries(tool, name)
118
+
119
+ packages = tuple(_parse_package(p) for p in _as_list(raw, "packages", name))
120
+
121
+ return WheelRuntimeLock(
122
+ platform=platform,
123
+ requires_python=raw.get("requires-python", ">=3.15"),
124
+ packages=packages,
125
+ python_runtime=runtime,
126
+ archs=tuple(tool.get("archs", [])),
127
+ kivyforge_version=tool.get("kivyforge_version", ""),
128
+ generated_at=tool.get("generated_at", ""),
129
+ pyproject_sha256=tool.get("pyproject_sha256", ""),
130
+ tool_kivyforge_schema_version=tool.get("tool_kivyforge_schema_version", 1),
131
+ schema_version=schema_version,
132
+ lock_version=lock_version,
133
+ created_by=raw.get("created-by", "kivyforge"),
134
+ extras=tuple(raw.get("extras", [])),
135
+ dependency_groups=tuple(raw.get("dependency-groups", [])),
136
+ default_groups=tuple(raw.get("default-groups", [])),
137
+ native_binaries=native_binaries,
138
+ )
139
+
140
+
141
+ def _check_lock_version(raw: dict, name: str) -> str:
142
+ lock_version = raw.get("lock-version", LOCK_VERSION)
143
+ if not isinstance(lock_version, str):
144
+ raise LockError(f"{name} lock-version must be a string.")
145
+ major = lock_version.split(".", 1)[0]
146
+ if not major.isdigit():
147
+ raise LockError(f"{name} lock-version {lock_version!r} is not a valid version.")
148
+ if int(major) > SUPPORTED_LOCK_VERSION_MAJOR:
149
+ raise LockError(
150
+ f"{name} lock-version {lock_version} is newer than this kivyforge "
151
+ f"understands (max {SUPPORTED_LOCK_VERSION_MAJOR}.x). Upgrade kivyforge."
152
+ )
153
+ return lock_version
154
+
155
+
156
+ def _check_tool_schema_version(tool: dict) -> int:
157
+ schema_version = tool.get("schema_version", SUPPORTED_TOOL_SCHEMA_VERSION)
158
+ if not isinstance(schema_version, int) or isinstance(schema_version, bool):
159
+ raise LockError("[tool.kivyforge].schema_version must be an integer.")
160
+ if schema_version > SUPPORTED_TOOL_SCHEMA_VERSION:
161
+ raise LockError(
162
+ f"[tool.kivyforge].schema_version {schema_version} is newer than this "
163
+ f"kivyforge understands (max {SUPPORTED_TOOL_SCHEMA_VERSION}). "
164
+ "Upgrade kivyforge."
165
+ )
166
+ return schema_version
167
+
168
+
169
+ def _parse_runtime(tool: dict, name: str) -> PythonRuntime:
170
+ rt = tool.get("python_runtime")
171
+ if not isinstance(rt, dict) or not rt:
172
+ raise LockError(f"{name} is missing [tool.kivyforge.python_runtime].")
173
+ for field in ("provider", "version"):
174
+ value = rt.get(field)
175
+ if not isinstance(value, str) or not value:
176
+ raise LockError(
177
+ f"[tool.kivyforge.python_runtime].{field} is missing or empty."
178
+ )
179
+ artifacts_raw = rt.get("artifacts", [])
180
+ if not isinstance(artifacts_raw, list) or not artifacts_raw:
181
+ raise LockError(
182
+ "[tool.kivyforge.python_runtime] must list at least one artifact."
183
+ )
184
+ artifacts = []
185
+ for a in artifacts_raw:
186
+ for field in ("arch", "url", "sha256"):
187
+ if not isinstance(a.get(field), str) or not a.get(field):
188
+ raise LockError(
189
+ f"a [tool.kivyforge.python_runtime.artifacts] entry is "
190
+ f"missing {field!r}."
191
+ )
192
+ artifacts.append(
193
+ RuntimeArtifact(
194
+ arch=a["arch"],
195
+ url=a["url"],
196
+ sha256=a["sha256"],
197
+ archive_format=a.get("archive_format", "tar.gz"),
198
+ )
199
+ )
200
+ return PythonRuntime(
201
+ provider=rt["provider"],
202
+ version=rt["version"],
203
+ artifacts=tuple(artifacts),
204
+ floor=rt.get("floor"),
205
+ )
206
+
207
+
208
+ def _parse_native_binaries(tool: dict, name: str) -> tuple[LockedNativeBinary, ...]:
209
+ raw = tool.get("native_binaries", [])
210
+ if not isinstance(raw, list):
211
+ raise LockError(f"{name} [tool.kivyforge.native_binaries] must be an array.")
212
+ out: list[LockedNativeBinary] = []
213
+ for entry in raw:
214
+ if not isinstance(entry, dict):
215
+ raise LockError(
216
+ f"{name} [[tool.kivyforge.native_binaries]] entries must be tables."
217
+ )
218
+ for field in ("name", "version", "sha256"):
219
+ if not isinstance(entry.get(field), str) or not entry.get(field):
220
+ raise LockError(
221
+ f"a [[tool.kivyforge.native_binaries]] entry is missing {field!r}."
222
+ )
223
+ url = entry.get("url")
224
+ path = entry.get("path")
225
+ if bool(url) == bool(path):
226
+ raise LockError(
227
+ f"native binary {entry['name']!r} must have exactly one of url/path."
228
+ )
229
+ out.append(
230
+ LockedNativeBinary(
231
+ name=entry["name"],
232
+ version=entry["version"],
233
+ sha256=entry["sha256"],
234
+ url=url,
235
+ path=path,
236
+ )
237
+ )
238
+ return tuple(sorted(out, key=lambda b: b.name))
239
+
240
+
241
+ def _as_list(table: dict, key: str, name: str) -> list:
242
+ value = table.get(key, [])
243
+ if not isinstance(value, list):
244
+ raise LockError(f"{name} {key!r} must be an array of tables.")
245
+ return value
@@ -0,0 +1,114 @@
1
+ """Platform backend registry + target resolution (common design docs 02, 05).
2
+
3
+ The registry is a plain, static dict of in-repo backends — no entry-point plugin
4
+ discovery yet (it can be added later without changing callers). ``resolve_target``
5
+ implements the documented resolution chain:
6
+
7
+ --platform / -p > KIVYFORGE_PLATFORM > host OS > actionable error
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import platform as _platform
13
+ from collections.abc import Collection, Mapping
14
+
15
+ from .android import AndroidPlatform
16
+ from .base import HostCapabilityError, Platform
17
+ from .ios import IosPlatform
18
+ from .linux import LinuxPlatform
19
+ from .macos import MacosPlatform
20
+ from .windows import WindowsPlatform
21
+
22
+ __all__ = [
23
+ "HostCapabilityError",
24
+ "Platform",
25
+ "PlatformResolutionError",
26
+ "PLATFORM_ENV_VAR",
27
+ "available_platform_names",
28
+ "get_platform",
29
+ "resolve_target",
30
+ ]
31
+
32
+ PLATFORM_ENV_VAR = "KIVYFORGE_PLATFORM"
33
+
34
+ # Registered backends, in display order. Add new platforms here.
35
+ _REGISTRY: tuple[Platform, ...] = (
36
+ IosPlatform(),
37
+ MacosPlatform(),
38
+ LinuxPlatform(),
39
+ WindowsPlatform(),
40
+ AndroidPlatform(),
41
+ )
42
+
43
+ # name/alias -> backend
44
+ _BY_SELECTOR: dict[str, Platform] = {
45
+ selector: backend for backend in _REGISTRY for selector in backend.selectors
46
+ }
47
+
48
+
49
+ class PlatformResolutionError(Exception):
50
+ """No target platform could be resolved from CLI/env/host."""
51
+
52
+
53
+ def available_platform_names() -> list[str]:
54
+ """Canonical names of registered platforms (for ``click.Choice``)."""
55
+ return [backend.name for backend in _REGISTRY]
56
+
57
+
58
+ def get_platform(name: str) -> Platform:
59
+ """Return the backend registered under ``name`` or an alias."""
60
+ try:
61
+ return _BY_SELECTOR[name]
62
+ except KeyError:
63
+ raise PlatformResolutionError(
64
+ f"unknown platform {name!r}; "
65
+ f"registered: {', '.join(available_platform_names())}"
66
+ ) from None
67
+
68
+
69
+ def resolve_target(
70
+ cli_platform: str | None,
71
+ *,
72
+ configured: Collection[str],
73
+ env: Mapping[str, str] | None = None,
74
+ host_system: str | None = None,
75
+ verb: str = "build",
76
+ ) -> Platform:
77
+ """Resolve the target platform per the documented chain.
78
+
79
+ ``configured`` is the set of platform names whose overlay this project's
80
+ ``pyproject.toml`` actually declares (e.g. ``{"ios"}`` for ``[tool.kivy.ios]``).
81
+ ``env``/``host_system`` are injectable for testing. ``verb`` names the CLI
82
+ command that called this (e.g. ``"lock"``, ``"run"``) purely so the error
83
+ message's example command is accurate; it never affects resolution.
84
+ """
85
+ env = env if env is not None else {}
86
+
87
+ # 1. explicit --platform / -p
88
+ if cli_platform:
89
+ return get_platform(cli_platform)
90
+
91
+ # 2. KIVYFORGE_PLATFORM session/CI default
92
+ env_platform = env.get(PLATFORM_ENV_VAR)
93
+ if env_platform:
94
+ return get_platform(env_platform)
95
+
96
+ # 3. host OS -> its own platform, only if registered *and* configured
97
+ host = host_system if host_system is not None else _platform.system()
98
+ for backend in _REGISTRY:
99
+ if backend.host_system == host and backend.name in configured:
100
+ return backend
101
+
102
+ # 4. actionable error
103
+ raise PlatformResolutionError(_resolution_help(configured, verb))
104
+
105
+
106
+ def _resolution_help(configured: Collection[str], verb: str = "build") -> str:
107
+ configured_list = ", ".join(sorted(configured)) or "none"
108
+ example = sorted(configured)[0] if configured else "macos"
109
+ return (
110
+ "no target platform resolved.\n"
111
+ f" Pass one explicitly: kivyforge {verb} --platform {example}\n"
112
+ f" Or set a session default: export {PLATFORM_ENV_VAR}={example}\n"
113
+ f" (Configured platforms in this pyproject.toml: {configured_list})"
114
+ )
@@ -0,0 +1,140 @@
1
+ """Android platform backend (docs/design/platforms/android/01-08).
2
+
3
+ Produces a Gradle/AGP project from ``pyproject.toml`` + ``pylock.android.toml``
4
+ — the ABI-independent Python asset bundle, flattened extension ``.so``s in
5
+ ``jniLibs/<abi>/``, the kivyforge-owned bootstrap (stock SDL Java glue +
6
+ ``PythonActivity`` + the ``SDL_main`` native launcher), and a signed ``.apk`` /
7
+ ``.aab``. Android is a cross-compiled target: no host OS maps to it as a
8
+ default, so it always needs explicit ``-p android`` (or ``KIVYFORGE_PLATFORM``).
9
+ Any desktop host can build: the backend consumes prebuilt wheels + the prebuilt
10
+ python.org runtime and drives Gradle/SDK/NDK, all cross-platform.
11
+
12
+ The load model this backend generates was proven on-device before any of this
13
+ code was written — see docs/design/dev/android-loadmodel-findings.md.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from pathlib import Path
19
+ from typing import TYPE_CHECKING
20
+
21
+ from ..base import Platform
22
+
23
+ if TYPE_CHECKING:
24
+ from kivyforge.doctor.result import CheckResult
25
+
26
+
27
+ class AndroidBuildError(Exception):
28
+ """An Android build/stage failure surfaced with an actionable message."""
29
+
30
+
31
+ class AndroidPlatform(Platform):
32
+ name = "android"
33
+ aliases = ()
34
+ # Cross-compiled target: never a host default (resolution-chain step 3).
35
+ host_system = None
36
+ # apk = sideload/CI unit (default); aab = Play upload unit.
37
+ package_formats = ("apk", "aab")
38
+
39
+ def check_host_capability(self, *, host_system: str | None = None) -> None:
40
+ # Windows, macOS, and Linux hosts can all build for Android: kivyforge
41
+ # consumes prebuilt wheels + the prebuilt python.org runtime and drives
42
+ # Gradle + the Android SDK/NDK, which are cross-platform (android/06
43
+ # §host support). Host *adequacy* (JDK, SDK, NDK present) is doctor's
44
+ # job, not a capability gate.
45
+ return
46
+
47
+ def build(
48
+ self,
49
+ project_root: Path,
50
+ *,
51
+ target: str | None,
52
+ arch: str | None,
53
+ no_verify_lock: bool,
54
+ no_cache: bool,
55
+ team_id: str | None,
56
+ signing_identity: str | None,
57
+ export_method: str,
58
+ debug: bool = False,
59
+ fmt: str | None = None,
60
+ abi: str | None = None,
61
+ ) -> None:
62
+ # `target` carries the iOS-only selectors. Without --debug, build stops
63
+ # after generating the project; with it, Gradle assembles the debug
64
+ # artifact (android/06 step 8). `--abi` is the Android spelling of the
65
+ # ABI restriction; `--arch` is accepted for it too.
66
+ self.reject_ios_only_target(target)
67
+ from .cli import android_build
68
+
69
+ android_build(
70
+ project_root,
71
+ debug=debug,
72
+ fmt=fmt or "apk",
73
+ abi=abi or arch,
74
+ no_verify_lock=no_verify_lock,
75
+ no_cache=no_cache,
76
+ )
77
+
78
+ def run(
79
+ self,
80
+ project_root: Path,
81
+ *,
82
+ target: str,
83
+ arch: str | None,
84
+ destination: str | None,
85
+ no_build: bool,
86
+ ) -> None:
87
+ from .cli import android_run
88
+
89
+ android_run(
90
+ project_root,
91
+ no_build=no_build,
92
+ abi=arch,
93
+ serial=destination,
94
+ )
95
+
96
+ def package(
97
+ self,
98
+ project_root: Path,
99
+ *,
100
+ fmt: str,
101
+ arch: str | None,
102
+ team_id: str | None,
103
+ signing_identity: str | None,
104
+ export_method: str,
105
+ notarize: bool | None,
106
+ notary_profile: str | None,
107
+ no_verify_lock: bool,
108
+ no_cache: bool,
109
+ abi: str | None = None,
110
+ keystore: str | None = None,
111
+ key_alias: str | None = None,
112
+ ) -> None:
113
+ from .cli import android_package
114
+
115
+ android_package(
116
+ project_root,
117
+ fmt=fmt,
118
+ abi=abi or arch,
119
+ keystore=keystore,
120
+ key_alias=key_alias,
121
+ no_verify_lock=no_verify_lock,
122
+ no_cache=no_cache,
123
+ )
124
+
125
+ def status(self, project_root: Path) -> None:
126
+ from .cli import android_status
127
+
128
+ android_status(project_root)
129
+
130
+ def open_project(self, project_root: Path) -> None:
131
+ from .cli import android_open
132
+
133
+ android_open(project_root)
134
+
135
+ def doctor(
136
+ self, cwd: Path, *, kivyforge_version: str, offline: bool
137
+ ) -> list[CheckResult]:
138
+ from .doctor import android_doctor
139
+
140
+ return android_doctor(cwd, kivyforge_version=kivyforge_version, offline=offline)