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
kivyforge/cli/lock.py ADDED
@@ -0,0 +1,261 @@
1
+ """``kivyforge lock`` — resolve dependencies into pylock.<platform>.toml.
2
+
3
+ The verb resolves the target platform (``-p`` / ``KIVYFORGE_PLATFORM`` / host)
4
+ and dispatches to that backend's lock engine, writing ``pylock.<platform>.toml``.
5
+ Each backend supplies its own build/serialize/compare callables; the drift check
6
+ (``pyproject_sha256``) and atomic write are shared.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import tempfile
13
+ from collections.abc import Callable
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+
17
+ import click
18
+
19
+ from ..config import ConfigError, load_config
20
+ from ..lock.reader import LockError, is_in_sync
21
+ from ..platforms.base import HostCapabilityError
22
+ from ..platforms.ios.lock import (
23
+ BuildError,
24
+ build_lockfile,
25
+ diff_summary,
26
+ dumps,
27
+ load,
28
+ semantic_equal,
29
+ )
30
+ from ._common import ToolchainError, lockfile_path_for
31
+ from ._platform import platform_option, resolve_target
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class _LockOps:
36
+ """Per-platform lock callables (raise ``BuildError``/``LockError``)."""
37
+
38
+ build: Callable
39
+ dumps: Callable
40
+ load: Callable
41
+ semantic_equal: Callable
42
+ diff_summary: Callable
43
+ build_error: type[Exception]
44
+ require_ios: bool
45
+ require_macos: bool
46
+ require_linux: bool = False
47
+ require_windows: bool = False
48
+ require_android: bool = False
49
+ # Wheel+runtime backends (macOS/Linux) surface non-fatal lock warnings
50
+ # (e.g. accepting a vendored plain linux_* wheel) via an on_warning callback.
51
+ emits_warnings: bool = False
52
+ # Most backends resolve with pip alone, so `lock` runs anywhere and the
53
+ # result is committed. iOS is the exception: resolving declared Swift
54
+ # packages shells out to `swift package resolve`, which needs the Xcode
55
+ # toolchain, so the whole iOS workflow — lock included — is macOS-only.
56
+ requires_host_toolchain: bool = False
57
+
58
+
59
+ def _lock_ops(platform: str) -> _LockOps:
60
+ if platform == "ios":
61
+ # Reference the module globals (not a local import) so tests can patch
62
+ # ``lock_cli.build_lockfile`` with a fake-injecting wrapper.
63
+ return _LockOps(
64
+ build=build_lockfile,
65
+ dumps=dumps,
66
+ load=load,
67
+ semantic_equal=semantic_equal,
68
+ diff_summary=diff_summary,
69
+ build_error=BuildError,
70
+ require_ios=True,
71
+ require_macos=False,
72
+ requires_host_toolchain=True,
73
+ )
74
+ if platform == "macos":
75
+ from ..platforms.macos import lock as macos_lock
76
+
77
+ return _LockOps(
78
+ build=macos_lock.build_macos_lockfile,
79
+ dumps=macos_lock.dumps,
80
+ load=macos_lock.load,
81
+ semantic_equal=macos_lock.semantic_equal,
82
+ diff_summary=macos_lock.diff_summary,
83
+ build_error=macos_lock.MacosBuildError,
84
+ require_ios=False,
85
+ require_macos=True,
86
+ emits_warnings=True,
87
+ )
88
+ if platform == "linux":
89
+ from ..platforms.linux import lock as linux_lock
90
+
91
+ return _LockOps(
92
+ build=linux_lock.build_linux_lockfile,
93
+ dumps=linux_lock.dumps,
94
+ load=linux_lock.load,
95
+ semantic_equal=linux_lock.semantic_equal,
96
+ diff_summary=linux_lock.diff_summary,
97
+ build_error=linux_lock.LinuxBuildError,
98
+ require_ios=False,
99
+ require_macos=False,
100
+ require_linux=True,
101
+ emits_warnings=True,
102
+ )
103
+ if platform == "windows":
104
+ from ..platforms.windows import lock as windows_lock
105
+
106
+ return _LockOps(
107
+ build=windows_lock.build_windows_lockfile,
108
+ dumps=windows_lock.dumps,
109
+ load=windows_lock.load,
110
+ semantic_equal=windows_lock.semantic_equal,
111
+ diff_summary=windows_lock.diff_summary,
112
+ build_error=windows_lock.WindowsBuildError,
113
+ require_ios=False,
114
+ require_macos=False,
115
+ require_windows=True,
116
+ emits_warnings=True,
117
+ )
118
+ if platform == "android":
119
+ from ..platforms.android.lock import builder as android_builder
120
+ from ..platforms.android.lock import reader as android_reader
121
+ from ..platforms.android.lock import writer as android_writer
122
+
123
+ return _LockOps(
124
+ build=android_builder.build_lockfile,
125
+ dumps=android_writer.dumps,
126
+ load=android_reader.load,
127
+ semantic_equal=android_builder.semantic_equal,
128
+ diff_summary=android_builder.diff_summary,
129
+ build_error=android_builder.BuildError,
130
+ require_ios=False,
131
+ require_macos=False,
132
+ require_android=True,
133
+ )
134
+ raise ToolchainError(
135
+ f"`kivyforge lock` does not support platform {platform!r} yet."
136
+ )
137
+
138
+
139
+ def _require_host_toolchain(backend) -> None:
140
+ """Gate `lock` for backends whose resolution needs a host toolchain."""
141
+ try:
142
+ backend.check_host_capability()
143
+ except HostCapabilityError as exc:
144
+ raise ToolchainError(str(exc)) from exc
145
+
146
+
147
+ @click.command()
148
+ @platform_option
149
+ @click.option("--update", is_flag=True, help="Re-resolve even if the lock is in sync.")
150
+ @click.option("--offline", is_flag=True, help="Use cached resolution results only.")
151
+ @click.option(
152
+ "--check",
153
+ is_flag=True,
154
+ help="CI pre-flight: exit non-zero if the lock is stale; write nothing.",
155
+ )
156
+ def lock(cli_platform: str | None, update: bool, offline: bool, check: bool) -> None:
157
+ """Generate pylock.<platform>.toml from pyproject.toml."""
158
+ backend, project_root = resolve_target(cli_platform, verb="lock")
159
+ ops = _lock_ops(backend.name)
160
+ if ops.requires_host_toolchain:
161
+ _require_host_toolchain(backend)
162
+
163
+ pyproject = project_root / "pyproject.toml"
164
+ pyproject_text = pyproject.read_text(encoding="utf-8")
165
+ out_path = lockfile_path_for(backend.name, project_root)
166
+
167
+ try:
168
+ config = load_config(
169
+ pyproject,
170
+ require_ios=ops.require_ios,
171
+ require_macos=ops.require_macos,
172
+ require_linux=ops.require_linux,
173
+ require_windows=ops.require_windows,
174
+ require_android=ops.require_android,
175
+ )
176
+ except ConfigError as exc:
177
+ raise ToolchainError(exc.format()) from exc
178
+
179
+ if check:
180
+ _run_check(ops, config, pyproject_text, out_path, project_root, offline)
181
+ return
182
+
183
+ if out_path.is_file() and not update:
184
+ try:
185
+ existing = ops.load(out_path)
186
+ except LockError:
187
+ existing = None
188
+ if existing is not None and is_in_sync(existing, pyproject_text):
189
+ click.echo(f"{out_path.name} is already in sync with pyproject.toml.")
190
+ click.echo(" (use --update to force re-resolution)")
191
+ return
192
+
193
+ new_lock = _build(ops, config, pyproject_text, project_root, offline)
194
+ _atomic_write(out_path, ops.dumps(new_lock))
195
+ click.echo(f"Wrote {out_path.name} ({len(new_lock.packages)} packages pinned).")
196
+
197
+
198
+ def _run_check(
199
+ ops: _LockOps,
200
+ config,
201
+ pyproject_text: str,
202
+ out_path: Path,
203
+ project_root: Path,
204
+ offline: bool,
205
+ ) -> None:
206
+ if not out_path.is_file():
207
+ raise ToolchainError(
208
+ f"{out_path.name} does not exist. Run `kivyforge lock` first."
209
+ )
210
+ try:
211
+ existing = ops.load(out_path)
212
+ except LockError as exc:
213
+ raise ToolchainError(str(exc)) from exc
214
+
215
+ candidate = _build(ops, config, pyproject_text, project_root, offline)
216
+ if ops.semantic_equal(existing, candidate):
217
+ click.echo(f"{out_path.name} is up to date.")
218
+ return
219
+ click.echo(f"{out_path.name} is out of date:", err=True)
220
+ for line in ops.diff_summary(existing, candidate):
221
+ click.echo(line, err=True)
222
+ raise ToolchainError("lock is stale; run `kivyforge lock`.")
223
+
224
+
225
+ def _build(
226
+ ops: _LockOps, config, pyproject_text: str, project_root: Path, offline: bool
227
+ ):
228
+ kwargs = {}
229
+ if ops.emits_warnings:
230
+ kwargs["on_warning"] = lambda msg: click.echo(msg, err=True)
231
+ try:
232
+ return ops.build(
233
+ config,
234
+ pyproject_text,
235
+ project_root=project_root,
236
+ offline=offline,
237
+ **kwargs,
238
+ )
239
+ except ops.build_error as exc:
240
+ raise ToolchainError(str(exc)) from exc
241
+
242
+
243
+ def _atomic_write(path: Path, text: str) -> None:
244
+ """Write to a tempfile, fsync, then rename (spec 02 step 5).
245
+
246
+ Always LF: a lock file is committed and compared across machines, so
247
+ letting the host's default line ending through would make a re-lock on
248
+ another OS rewrite every line. The lock must depend on the target, never
249
+ on who ran it.
250
+ """
251
+ directory = path.parent
252
+ fd, tmp = tempfile.mkstemp(dir=directory, prefix=".pylock.", suffix=".tmp")
253
+ try:
254
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
255
+ f.write(text)
256
+ f.flush()
257
+ os.fsync(f.fileno())
258
+ os.replace(tmp, path)
259
+ finally:
260
+ if os.path.exists(tmp):
261
+ os.unlink(tmp)
@@ -0,0 +1,15 @@
1
+ """``kivyforge open`` — open the generated Xcode project (spec 05)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from ._platform import platform_option, resolve_target
8
+
9
+
10
+ @click.command(name="open")
11
+ @platform_option
12
+ def open_(cli_platform: str | None) -> None:
13
+ """Open <app>-ios/<app>.xcodeproj in Xcode."""
14
+ backend, project_root = resolve_target(cli_platform, verb="open")
15
+ backend.open_project(project_root)
@@ -0,0 +1,134 @@
1
+ """``kivyforge package`` — build the distributable, signed artifact (docs 02/06).
2
+
3
+ ``package`` produces the finished, signed runnable artifact for the resolved
4
+ target (wrapping it into an installer/container is out of scope). For iOS this is
5
+ the release archive + exported ``.ipa``; other platforms register their own
6
+ shapes via ``Platform.package_formats`` and are wired in as they land.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import click
12
+
13
+ from ._common import ToolchainError
14
+ from ._platform import platform_option, reject_android_only, resolve_target
15
+
16
+
17
+ @click.command()
18
+ @platform_option
19
+ @click.option(
20
+ "-f",
21
+ "--format",
22
+ "fmt",
23
+ default=None,
24
+ help="Artifact format for the target (defaults to the platform's default).",
25
+ )
26
+ @click.option(
27
+ "--arch",
28
+ # x86_64 stays: it is the Linux arch and an Android ABI, not a macOS one.
29
+ type=click.Choice(["arm64", "x86_64"]),
30
+ default=None,
31
+ help="macOS: assemble a subset of the locked archs.",
32
+ )
33
+ @click.option(
34
+ "--team-id", default=None, help="Override [tool.kivy.ios.signing].team_id."
35
+ )
36
+ @click.option(
37
+ "--signing-identity",
38
+ default=None,
39
+ help="Override [tool.kivy.<platform>.signing].identity.",
40
+ )
41
+ @click.option(
42
+ "--export-method",
43
+ type=click.Choice(["app-store", "ad-hoc", "development"]),
44
+ default="app-store",
45
+ help="iOS export method.",
46
+ )
47
+ @click.option(
48
+ "--notarize/--no-notarize",
49
+ "notarize",
50
+ default=None,
51
+ help="macOS: notarize + staple the signed .app (default: when "
52
+ "[tool.kivy.macos.signing].notary_profile is configured).",
53
+ )
54
+ @click.option(
55
+ "--notary-profile",
56
+ default=None,
57
+ help="macOS: override [tool.kivy.macos.signing].notary_profile.",
58
+ )
59
+ @click.option(
60
+ "--no-verify-lock", is_flag=True, help="Skip the pyproject drift check (CI only)."
61
+ )
62
+ @click.option("--no-cache", is_flag=True, help="Force re-download of every artifact.")
63
+ # --- Android-only ---
64
+ @click.option(
65
+ "--abi",
66
+ type=click.Choice(["arm64_v8a", "x86_64"]),
67
+ default=None,
68
+ help="Android: restrict this package to one ABI.",
69
+ )
70
+ @click.option(
71
+ "--keystore",
72
+ default=None,
73
+ help="Android: override [tool.kivy.android.signing].keystore.",
74
+ )
75
+ @click.option(
76
+ "--key-alias",
77
+ default=None,
78
+ help="Android: override [tool.kivy.android.signing].key_alias.",
79
+ )
80
+ def package(
81
+ cli_platform: str | None,
82
+ fmt: str | None,
83
+ arch: str | None,
84
+ team_id: str | None,
85
+ signing_identity: str | None,
86
+ export_method: str,
87
+ notarize: bool | None,
88
+ notary_profile: str | None,
89
+ no_verify_lock: bool,
90
+ no_cache: bool,
91
+ abi: str | None,
92
+ keystore: str | None,
93
+ key_alias: str | None,
94
+ ) -> None:
95
+ """Build the signed, distributable artifact for the resolved platform."""
96
+ backend, project_root = resolve_target(cli_platform, verb="package")
97
+ reject_android_only(
98
+ backend,
99
+ {"--abi": abi, "--keystore": keystore, "--key-alias": key_alias},
100
+ )
101
+ fmt = _resolve_format(backend, fmt)
102
+
103
+ backend.package(
104
+ project_root,
105
+ fmt=fmt,
106
+ arch=arch,
107
+ team_id=team_id,
108
+ signing_identity=signing_identity,
109
+ export_method=export_method,
110
+ notarize=notarize,
111
+ notary_profile=notary_profile,
112
+ no_verify_lock=no_verify_lock,
113
+ no_cache=no_cache,
114
+ abi=abi,
115
+ keystore=keystore,
116
+ key_alias=key_alias,
117
+ )
118
+
119
+
120
+ def _resolve_format(backend, fmt: str | None) -> str:
121
+ """Validate/resolve the ``-f`` format against the backend's advertised set."""
122
+ if fmt is None:
123
+ default = backend.default_package_format
124
+ if default is None:
125
+ raise ToolchainError(
126
+ f"{backend.name!r} does not define a package format yet."
127
+ )
128
+ return default
129
+ if fmt not in backend.package_formats:
130
+ allowed = ", ".join(backend.package_formats) or "none"
131
+ raise ToolchainError(
132
+ f"unknown package format {fmt!r} for {backend.name!r}; allowed: {allowed}."
133
+ )
134
+ return fmt
kivyforge/cli/run.py ADDED
@@ -0,0 +1,162 @@
1
+ """``kivyforge run`` — build, install, and launch on a simulator/device (spec 05)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from click.core import ParameterSource
7
+
8
+ from ..platforms.ios.cli import ios_list_devices
9
+ from ._common import ToolchainError
10
+ from ._platform import platform_option, reject_android_only, resolve_target
11
+
12
+
13
+ @click.command()
14
+ @platform_option
15
+ @click.option(
16
+ "--simulator",
17
+ "target",
18
+ flag_value="simulator",
19
+ default=True,
20
+ help="Target the iOS simulator (default).",
21
+ )
22
+ @click.option(
23
+ "--device",
24
+ "target",
25
+ flag_value="device",
26
+ help="Target a connected device (Android: a physical one, never an AVD).",
27
+ )
28
+ @click.option(
29
+ "--arch",
30
+ # x86_64 stays: it is the Linux arch and an Android ABI, not a macOS one.
31
+ type=click.Choice(["arm64", "x86_64", "arm64_v8a"]),
32
+ default=None,
33
+ help="macOS: a subset of locked archs; Android: restrict to one ABI.",
34
+ )
35
+ @click.option(
36
+ "--destination", default=None, help="Specific simulator/device by name or UDID."
37
+ )
38
+ @click.option(
39
+ "--list-devices",
40
+ is_flag=True,
41
+ help="Print available simulators and devices, then exit.",
42
+ )
43
+ @click.option(
44
+ "--no-build",
45
+ is_flag=True,
46
+ help="Skip the implicit build; install + launch the existing app.",
47
+ )
48
+ # --- Android-only ---
49
+ @click.option(
50
+ "--emulator", is_flag=True, help="Android: boot/target an emulator (AVD)."
51
+ )
52
+ @click.option("--avd", default=None, help="Android: which AVD to boot (--emulator).")
53
+ @click.option(
54
+ "--serial", default=None, help="Android: which adb device when several attach."
55
+ )
56
+ @click.option(
57
+ "--smoke",
58
+ is_flag=True,
59
+ help="Android: run the generated contract smoke test instead of launching.",
60
+ )
61
+ @click.option(
62
+ "--release",
63
+ is_flag=True,
64
+ help="Android (with --smoke): target the release variant.",
65
+ )
66
+ @click.option(
67
+ "--abi",
68
+ type=click.Choice(["arm64_v8a", "x86_64"]),
69
+ default=None,
70
+ help="Android: restrict the implicit build to one ABI.",
71
+ )
72
+ def run(
73
+ cli_platform: str | None,
74
+ target: str,
75
+ arch: str | None,
76
+ destination: str | None,
77
+ list_devices: bool,
78
+ no_build: bool,
79
+ emulator: bool,
80
+ avd: str | None,
81
+ serial: str | None,
82
+ smoke: bool,
83
+ release: bool,
84
+ abi: str | None,
85
+ ) -> None:
86
+ """Build (unless --no-build), install, and launch the app."""
87
+ backend, project_root = resolve_target(cli_platform, verb="run")
88
+
89
+ reject_android_only(
90
+ backend,
91
+ {
92
+ "--emulator": emulator,
93
+ "--avd": avd,
94
+ "--serial": serial,
95
+ "--smoke": smoke,
96
+ "--release": release,
97
+ "--abi": abi,
98
+ },
99
+ )
100
+ abi = abi or arch
101
+
102
+ if backend.name == "android":
103
+ from ..platforms.android.cli import android_run, android_smoke
104
+
105
+ # --simulator is the default flag value, so only an *explicit* one is a
106
+ # mistake worth reporting; the source is how click tells the two apart.
107
+ source = click.get_current_context().get_parameter_source("target")
108
+ explicit = target if source is ParameterSource.COMMANDLINE else None
109
+ if explicit == "simulator":
110
+ raise ToolchainError(
111
+ "--simulator is iOS-only; Android's equivalent is --emulator."
112
+ )
113
+ if explicit == "device" and emulator:
114
+ raise ToolchainError(
115
+ "--device and --emulator select opposite targets; pass one, or "
116
+ "neither to auto-select (a single attached device, else an AVD)."
117
+ )
118
+ if explicit == "device" and avd is not None:
119
+ raise ToolchainError(
120
+ "--avd names an emulator to boot, which --device rules out; pass one."
121
+ )
122
+ require_physical = explicit == "device"
123
+
124
+ if list_devices:
125
+ from ..platforms.android import adb as adb_mod
126
+
127
+ click.echo("Devices: " + (", ".join(adb_mod.connected_devices()) or "none"))
128
+ click.echo("AVDs: " + (", ".join(adb_mod.available_avds()) or "none"))
129
+ return
130
+ if smoke:
131
+ android_smoke(
132
+ project_root,
133
+ release=release,
134
+ abi=abi,
135
+ serial=serial,
136
+ avd=avd,
137
+ prefer_emulator=emulator or avd is not None,
138
+ require_physical=require_physical,
139
+ )
140
+ else:
141
+ android_run(
142
+ project_root,
143
+ no_build=no_build,
144
+ abi=abi,
145
+ serial=serial or destination,
146
+ avd=avd,
147
+ prefer_emulator=emulator or avd is not None,
148
+ require_physical=require_physical,
149
+ )
150
+ return
151
+
152
+ if list_devices:
153
+ ios_list_devices()
154
+ return
155
+
156
+ backend.run(
157
+ project_root,
158
+ target=target,
159
+ arch=arch,
160
+ destination=destination,
161
+ no_build=no_build,
162
+ )
@@ -0,0 +1,20 @@
1
+ """``kivyforge status`` — read-only project state snapshot (spec 05).
2
+
3
+ Platform-aware: resolves the target (``-p`` / ``KIVYFORGE_PLATFORM`` / host) and
4
+ dispatches to that backend's ``status`` over its ``pyproject.toml`` +
5
+ ``pylock.<platform>.toml``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import click
11
+
12
+ from ._platform import platform_option, resolve_target
13
+
14
+
15
+ @click.command()
16
+ @platform_option
17
+ def status(cli_platform: str | None) -> None:
18
+ """Show app identity, Python version, lock sync, and build state."""
19
+ backend, project_root = resolve_target(cli_platform, verb="status")
20
+ backend.status(project_root)