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,157 @@
1
+ """The ``Platform`` backend interface (common design doc 05).
2
+
3
+ A platform backend describes one build target (``ios``, ``macos``, ...) and owns
4
+ its verb behavior: metadata, host mapping, host-capability, package formats, and
5
+ the ``build``/``run``/``package``/``doctor`` implementations. The ``cli`` verbs
6
+ resolve the target and dispatch to these methods, so adding a platform is a
7
+ matter of registering one ``Platform`` subclass — the verbs stay backend-agnostic
8
+ (see ``docs/design/common/05-platform-architecture.md``).
9
+
10
+ Each verb method lazily imports its backend's implementation module
11
+ (``platforms/<os>/cli.py`` / ``doctor.py``) so importing the backend class stays
12
+ cheap and cycle-free (registry -> backend -> bundler/lock/cli is only paid when a
13
+ verb actually runs).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from abc import ABC, abstractmethod
19
+ from typing import TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from pathlib import Path
23
+
24
+ from kivyforge.doctor.result import CheckResult
25
+
26
+
27
+ class HostCapabilityError(Exception):
28
+ """The current host cannot build for this target (e.g. iOS off macOS)."""
29
+
30
+
31
+ class Platform(ABC):
32
+ """Describes a single build target and its host requirements."""
33
+
34
+ #: Canonical platform name, e.g. ``"ios"``. Also the ``pylock.<name>.toml``
35
+ #: infix and the ``[tool.kivy.<name>]`` overlay key.
36
+ name: str
37
+
38
+ #: Accepted CLI/env aliases in addition to ``name``.
39
+ aliases: tuple[str, ...] = ()
40
+
41
+ #: The ``platform.system()`` value for which this platform is the *host
42
+ #: default* (resolution-chain step 3). Desktop targets set their own OS
43
+ #: (macOS -> ``"Darwin"``); cross-compiled targets (iOS, Android) set
44
+ #: ``None`` because no host maps to them — they always need explicit
45
+ #: selection.
46
+ host_system: str | None = None
47
+
48
+ #: Distributable artifact shapes for ``package -f`` (first = default).
49
+ package_formats: tuple[str, ...] = ()
50
+
51
+ @property
52
+ def default_package_format(self) -> str | None:
53
+ return self.package_formats[0] if self.package_formats else None
54
+
55
+ @property
56
+ def selectors(self) -> tuple[str, ...]:
57
+ """All names that select this platform (``name`` + ``aliases``)."""
58
+ return (self.name, *self.aliases)
59
+
60
+ @abstractmethod
61
+ def check_host_capability(self, *, host_system: str | None = None) -> None:
62
+ """Raise :class:`HostCapabilityError` if this host cannot build the target.
63
+
64
+ ``host_system`` defaults to the real host (``platform.system()``); it is
65
+ injectable so the check is unit-testable off the target host.
66
+ """
67
+ raise NotImplementedError
68
+
69
+ # -- verb dispatch ----------------------------------------------------- #
70
+ #
71
+ # Default implementations raise so an unimplemented verb surfaces clearly.
72
+ # Every registered backend overrides the verbs it supports; the ``cli`` verbs
73
+ # pass the full option superset and each backend takes what it needs.
74
+ #
75
+ # Options that only one platform acts on (``debug``/``fmt``/``abi`` and the
76
+ # Android keystore overrides) are keyword-with-default: the CLI rejects them
77
+ # for the platforms they do not apply to, so those backends accept and
78
+ # ignore them rather than each verb growing a per-platform call shape.
79
+
80
+ def build(
81
+ self,
82
+ project_root: Path,
83
+ *,
84
+ target: str | None,
85
+ arch: str | None,
86
+ no_verify_lock: bool,
87
+ no_cache: bool,
88
+ team_id: str | None,
89
+ signing_identity: str | None,
90
+ export_method: str,
91
+ debug: bool = False,
92
+ fmt: str | None = None,
93
+ abi: str | None = None,
94
+ ) -> None:
95
+ raise NotImplementedError(f"build is not supported for {self.name!r}.")
96
+
97
+ def run(
98
+ self,
99
+ project_root: Path,
100
+ *,
101
+ target: str,
102
+ arch: str | None,
103
+ destination: str | None,
104
+ no_build: bool,
105
+ ) -> None:
106
+ raise NotImplementedError(f"run is not supported for {self.name!r}.")
107
+
108
+ def package(
109
+ self,
110
+ project_root: Path,
111
+ *,
112
+ fmt: str,
113
+ arch: str | None,
114
+ team_id: str | None,
115
+ signing_identity: str | None,
116
+ export_method: str,
117
+ notarize: bool | None,
118
+ notary_profile: str | None,
119
+ no_verify_lock: bool,
120
+ no_cache: bool,
121
+ abi: str | None = None,
122
+ keystore: str | None = None,
123
+ key_alias: str | None = None,
124
+ ) -> None:
125
+ raise NotImplementedError(f"package is not supported for {self.name!r}.")
126
+
127
+ def open_project(self, project_root: Path) -> None:
128
+ from kivyforge.cli._common import ToolchainError
129
+
130
+ raise ToolchainError(
131
+ f"`open` is an iOS/Xcode command; {self.name} has no project to open."
132
+ )
133
+
134
+ def status(self, project_root: Path) -> None:
135
+ raise NotImplementedError(f"status is not supported for {self.name!r}.")
136
+
137
+ def doctor(
138
+ self, cwd: Path, *, kivyforge_version: str, offline: bool
139
+ ) -> list[CheckResult]:
140
+ raise NotImplementedError(f"doctor is not supported for {self.name!r}.")
141
+
142
+ def reject_ios_only_target(self, target: str | None) -> None:
143
+ """Desktop backends reject the iOS-only ``--simulator/--device/--release``."""
144
+ if target is None:
145
+ return
146
+ from kivyforge.cli._common import ToolchainError
147
+
148
+ artifact = {"macos": ".app", "linux": "AppDir", "windows": "onedir folder"}.get(
149
+ self.name, "bundle"
150
+ )
151
+ raise ToolchainError(
152
+ f"--{target} is an iOS target; {self.name} has no simulator/device/"
153
+ "release targets.\n"
154
+ f" Use `kivyforge build -p {self.name}` (optionally --arch) to build "
155
+ f"the {artifact}, or `kivyforge package -p {self.name}` for the "
156
+ "distributable."
157
+ )
@@ -0,0 +1,129 @@
1
+ """iOS platform backend: metadata, host capability, and verb dispatch.
2
+
3
+ The iOS build/run/package/open/status/doctor logic lives in ``.cli`` and
4
+ ``.doctor``; the verb methods here lazily import them so importing the backend
5
+ class stays cheap and cycle-free.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import platform as _platform
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING
13
+
14
+ from ..base import HostCapabilityError, Platform
15
+
16
+ if TYPE_CHECKING:
17
+ from kivyforge.doctor.result import CheckResult
18
+
19
+
20
+ class IosPlatform(Platform):
21
+ name = "ios"
22
+ # iOS is cross-compiled from macOS: no host maps to it, so it is never a
23
+ # host default and must be selected explicitly (-p ios / KIVYFORGE_PLATFORM).
24
+ host_system = None
25
+ # The distributable is the Xcode-built app / .ipa (App Store submission is
26
+ # external). Only one shape today, so `-f` defaults to it.
27
+ package_formats = ("ipa",)
28
+
29
+ def check_host_capability(self, *, host_system: str | None = None) -> None:
30
+ host = host_system if host_system is not None else _platform.system()
31
+ if host != "Darwin":
32
+ raise HostCapabilityError(
33
+ "building for iOS requires macOS with Xcode; "
34
+ f"this host is {host!r}.\n"
35
+ " iOS wheels and the Python.xcframework are cross-built on a "
36
+ "macOS host — there is no on-device or off-macOS build path.\n"
37
+ " Run `kivyforge doctor -p ios` on a Mac to check Xcode setup."
38
+ )
39
+
40
+ def build(
41
+ self,
42
+ project_root: Path,
43
+ *,
44
+ target: str | None,
45
+ arch: str | None,
46
+ no_verify_lock: bool,
47
+ no_cache: bool,
48
+ team_id: str | None,
49
+ signing_identity: str | None,
50
+ export_method: str,
51
+ debug: bool = False,
52
+ fmt: str | None = None,
53
+ abi: str | None = None,
54
+ ) -> None:
55
+ from .cli import ios_build
56
+
57
+ ios_build(
58
+ project_root,
59
+ target=target,
60
+ arch=arch,
61
+ no_verify_lock=no_verify_lock,
62
+ no_cache=no_cache,
63
+ team_id=team_id,
64
+ signing_identity=signing_identity,
65
+ export_method=export_method,
66
+ )
67
+
68
+ def run(
69
+ self,
70
+ project_root: Path,
71
+ *,
72
+ target: str,
73
+ arch: str | None,
74
+ destination: str | None,
75
+ no_build: bool,
76
+ ) -> None:
77
+ from .cli import ios_run
78
+
79
+ ios_run(
80
+ project_root,
81
+ target=target,
82
+ destination=destination,
83
+ no_build=no_build,
84
+ )
85
+
86
+ def package(
87
+ self,
88
+ project_root: Path,
89
+ *,
90
+ fmt: str,
91
+ arch: str | None,
92
+ team_id: str | None,
93
+ signing_identity: str | None,
94
+ export_method: str,
95
+ notarize: bool | None,
96
+ notary_profile: str | None,
97
+ no_verify_lock: bool,
98
+ no_cache: bool,
99
+ abi: str | None = None,
100
+ keystore: str | None = None,
101
+ key_alias: str | None = None,
102
+ ) -> None:
103
+ from .cli import ios_package
104
+
105
+ ios_package(
106
+ project_root,
107
+ team_id=team_id,
108
+ signing_identity=signing_identity,
109
+ export_method=export_method,
110
+ no_verify_lock=no_verify_lock,
111
+ no_cache=no_cache,
112
+ )
113
+
114
+ def open_project(self, project_root: Path) -> None:
115
+ from .cli import ios_open
116
+
117
+ ios_open(project_root)
118
+
119
+ def status(self, project_root: Path) -> None:
120
+ from .cli import ios_status
121
+
122
+ ios_status(project_root)
123
+
124
+ def doctor(
125
+ self, cwd: Path, *, kivyforge_version: str, offline: bool
126
+ ) -> list[CheckResult]:
127
+ from .doctor import ios_doctor
128
+
129
+ return ios_doctor(cwd, kivyforge_version=kivyforge_version, offline=offline)
@@ -0,0 +1,193 @@
1
+ """Generate iOS app icons, splash assets, and launch screen (spec 01 / 06).
2
+
3
+ Uses Xcode 14+ single-size ``AppIcon`` (1024x1024; Xcode scales at build time)
4
+ and a ``LaunchScreen.storyboard`` with an optional ``Splash`` image set — not the
5
+ deprecated ``LaunchImage.launchimage`` catalog.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ import shutil
13
+ from pathlib import Path
14
+
15
+ from kivyforge.config.icons import validate_icon_source
16
+ from kivyforge.config.model import Config
17
+
18
+ _DEFAULT_SPLASH_BACKGROUND = "#ffffff"
19
+ _HEX_COLOR = re.compile(r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
20
+
21
+
22
+ def catalog_needed(config: Config) -> bool:
23
+ """True when ``Assets.xcassets`` should be emitted."""
24
+ ios = config.ios
25
+ return bool(ios and (ios.icons.source or ios.splash.source))
26
+
27
+
28
+ def launch_screen_needed(config: Config) -> bool:
29
+ """True when ``LaunchScreen.storyboard`` should be emitted."""
30
+ ios = config.ios
31
+ return bool(ios and (ios.splash.source or ios.splash.background))
32
+
33
+
34
+ def generate_asset_catalog(
35
+ config: Config, resources_dir: str | Path, *, project_root: Path | None = None
36
+ ) -> Path | None:
37
+ """Create ``Assets.xcassets`` when icons and/or splash image are configured.
38
+
39
+ Returns the catalog path, or None when no catalog entries are needed.
40
+ """
41
+ if not catalog_needed(config):
42
+ return None
43
+
44
+ assert config.ios is not None
45
+ root = project_root or Path.cwd()
46
+ catalog = Path(resources_dir) / "Assets.xcassets"
47
+ catalog.mkdir(parents=True, exist_ok=True)
48
+ _write_catalog_root(catalog)
49
+
50
+ if config.ios.icons.source:
51
+ _generate_app_icon(catalog, (root / config.ios.icons.source).resolve())
52
+ if config.ios.splash.source:
53
+ _generate_splash_imageset(catalog, (root / config.ios.splash.source).resolve())
54
+ return catalog
55
+
56
+
57
+ def write_launch_screen(
58
+ config: Config, project_dir: str | Path, *, project_root: Path | None = None
59
+ ) -> Path | None:
60
+ """Write ``LaunchScreen.storyboard`` when splash is configured."""
61
+ if not launch_screen_needed(config):
62
+ return None
63
+
64
+ assert config.ios is not None
65
+ project_dir = Path(project_dir)
66
+ background = config.ios.splash.background or _DEFAULT_SPLASH_BACKGROUND
67
+ rgb = _parse_hex_color(background)
68
+ has_image = bool(config.ios.splash.source)
69
+
70
+ path = project_dir / "LaunchScreen.storyboard"
71
+ path.write_text(_launch_screen_xml(rgb=rgb, has_image=has_image), encoding="utf-8")
72
+ return path
73
+
74
+
75
+ def _write_catalog_root(catalog: Path) -> None:
76
+ (catalog / "Contents.json").write_text(
77
+ json.dumps({"info": {"author": "kivyforge", "version": 1}}, indent=2),
78
+ encoding="utf-8",
79
+ )
80
+
81
+
82
+ def _generate_app_icon(catalog: Path, source: Path) -> None:
83
+ validate_icon_source(source)
84
+
85
+ appicon_dir = catalog / "AppIcon.appiconset"
86
+ appicon_dir.mkdir(parents=True, exist_ok=True)
87
+
88
+ contents = {
89
+ "images": [
90
+ {
91
+ "filename": "AppIcon.png",
92
+ "idiom": "universal",
93
+ "platform": "ios",
94
+ "size": "1024x1024",
95
+ }
96
+ ],
97
+ "info": {"author": "xcode", "version": 1},
98
+ }
99
+ (appicon_dir / "Contents.json").write_text(
100
+ json.dumps(contents, indent=2), encoding="utf-8"
101
+ )
102
+ shutil.copy2(source, appicon_dir / "AppIcon.png")
103
+
104
+
105
+ def _generate_splash_imageset(catalog: Path, source: Path) -> None:
106
+ imageset = catalog / "Splash.imageset"
107
+ imageset.mkdir(parents=True, exist_ok=True)
108
+ # Single unscaled "universal" image (one high-res asset Xcode scales as
109
+ # needed), matching the AppIcon set. Declaring 1x/2x/3x slots but filling
110
+ # only 1x left empty 2x/3x slots in Xcode.
111
+ contents = {
112
+ "images": [
113
+ {
114
+ "filename": "Splash.png",
115
+ "idiom": "universal",
116
+ }
117
+ ],
118
+ "info": {"author": "xcode", "version": 1},
119
+ }
120
+ (imageset / "Contents.json").write_text(
121
+ json.dumps(contents, indent=2), encoding="utf-8"
122
+ )
123
+ shutil.copy2(source, imageset / "Splash.png")
124
+
125
+
126
+ def _parse_hex_color(value: str) -> tuple[float, float, float]:
127
+ if not _HEX_COLOR.match(value):
128
+ raise ValueError(f"invalid hex color {value!r}; expected #rrggbb or #rgb")
129
+ digits = value[1:]
130
+ if len(digits) == 3:
131
+ digits = "".join(ch * 2 for ch in digits)
132
+ return (
133
+ int(digits[0:2], 16) / 255.0,
134
+ int(digits[2:4], 16) / 255.0,
135
+ int(digits[4:6], 16) / 255.0,
136
+ )
137
+
138
+
139
+ def _launch_screen_xml(*, rgb: tuple[float, float, float], has_image: bool) -> str:
140
+ r, g, b = rgb
141
+ color = (
142
+ f'<color key="backgroundColor" red="{r:.6f}" green="{g:.6f}" '
143
+ f'blue="{b:.6f}" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>'
144
+ )
145
+ image_block = ""
146
+ image_constraints = ""
147
+ resources_block = ""
148
+ if has_image:
149
+ image_block = """
150
+ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" translatesAutoresizingMaskIntoConstraints="NO" id="splash-image">
151
+ <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
152
+ </imageView>"""
153
+ image_constraints = """
154
+ <constraint firstItem="splash-image" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="c1"/>
155
+ <constraint firstItem="splash-image" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="c2"/>
156
+ <constraint firstItem="splash-image" firstAttribute="width" relation="lessThanOrEqual" secondItem="Ze5-6b-2t3" secondAttribute="width" id="c3"/>
157
+ <constraint firstItem="splash-image" firstAttribute="height" relation="lessThanOrEqual" secondItem="Ze5-6b-2t3" secondAttribute="height" id="c4"/>"""
158
+ resources_block = """
159
+ <resources>
160
+ <image name="Splash" width="128" height="128"/>
161
+ </resources>"""
162
+
163
+ return f"""<?xml version="1.0" encoding="UTF-8"?>
164
+ <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
165
+ <device id="retina6_12" orientation="portrait" appearance="light"/>
166
+ <dependencies>
167
+ <deployment identifier="iOS"/>
168
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
169
+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
170
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
171
+ </dependencies>
172
+ <scenes>
173
+ <scene sceneID="EHf-IW-A2E">
174
+ <objects>
175
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
176
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
177
+ <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
178
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
179
+ <subviews>{image_block}
180
+ </subviews>
181
+ <viewLayoutGuide key="safeArea" id="safe-area"/>
182
+ {color}
183
+ <constraints>{image_constraints}
184
+ </constraints>
185
+ </view>
186
+ </viewController>
187
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
188
+ </objects>
189
+ <point key="canvasLocation" x="53" y="375"/>
190
+ </scene>
191
+ </scenes>{resources_block}
192
+ </document>
193
+ """
@@ -0,0 +1,128 @@
1
+ """Toolchain-managed Xcode build settings (spec 06 §"Toolchain-managed build settings")."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from kivyforge.config.model import Config
6
+
7
+ from .staging import StagingLayout
8
+
9
+ BUILD_PYTHON_SCRIPT = (
10
+ "set -e\n"
11
+ 'UTILS="$PROJECT_DIR/Python.xcframework/build/build_utils.sh"\n'
12
+ 'if [ ! -f "$UTILS" ]; then UTILS="$PROJECT_DIR/Python.xcframework/build/utils.sh"; fi\n'
13
+ 'source "$UTILS"\n'
14
+ "# Copy the platform-appropriate pip-deps slice into the app bundle so\n"
15
+ "# that device and simulator builds never share compiled extension modules.\n"
16
+ 'if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then\n'
17
+ ' PIP_DEPS_SRC="$PROJECT_DIR/pip-deps-simulator"\n'
18
+ ' COLLECT_HINT="kivyforge build --simulator"\n'
19
+ "else\n"
20
+ ' PIP_DEPS_SRC="$PROJECT_DIR/pip-deps-device"\n'
21
+ ' COLLECT_HINT="kivyforge build --device"\n'
22
+ "fi\n"
23
+ "# Fail loudly if this platform's slice was never collected. Only one slice\n"
24
+ "# is populated per `kivyforge build/run <target>`, but Xcode picks the slice\n"
25
+ "# from its own destination -- switching the destination to an uncollected\n"
26
+ "# target would otherwise ship an app with no dependencies (crash at launch).\n"
27
+ "# The `.collected` marker is written by collect (kivyforge build/run); it,\n"
28
+ "# not directory emptiness, is the signal -- an empty slice WITH the marker is\n"
29
+ "# valid (an app with no third-party dependencies).\n"
30
+ 'if [ ! -f "${PIP_DEPS_SRC}.collected" ]; then\n'
31
+ ' echo "error: ${PIP_DEPS_SRC} was never collected for this '
32
+ "platform. Run '$COLLECT_HINT' (or 'kivyforge run') first, then rebuild.\"\n"
33
+ " exit 1\n"
34
+ "fi\n"
35
+ 'mkdir -p "$CODESIGNING_FOLDER_PATH/pip-deps"\n'
36
+ 'rsync -a --delete "$PIP_DEPS_SRC/" "$CODESIGNING_FOLDER_PATH/pip-deps/"\n'
37
+ "install_python Python.xcframework app pip-deps\n"
38
+ )
39
+
40
+
41
+ def managed_settings(
42
+ config: Config, *, configuration: str, layout: StagingLayout | None = None
43
+ ) -> dict[str, str]:
44
+ """Return the managed build settings for a given configuration (Debug/Release).
45
+
46
+ ``ENABLE_TESTABILITY`` and ``DEBUG_INFORMATION_FORMAT`` are per-configuration
47
+ (spec 06); the rest are constant across configs.
48
+ """
49
+ ios = config.ios
50
+ assert ios is not None
51
+ is_release = configuration.lower() == "release"
52
+
53
+ settings: dict[str, str] = {
54
+ "PRODUCT_BUNDLE_IDENTIFIER": ios.bundle_id,
55
+ "PRODUCT_NAME": "$(TARGET_NAME)",
56
+ "IPHONEOS_DEPLOYMENT_TARGET": ios.deployment_target,
57
+ "TARGETED_DEVICE_FAMILY": "1,2",
58
+ "SDKROOT": "iphoneos",
59
+ "INFOPLIST_FILE": f"{config.app_slug}-Info.plist",
60
+ "ENABLE_USER_SCRIPT_SANDBOXING": "NO",
61
+ "ENABLE_TESTABILITY": "NO" if is_release else "YES",
62
+ "DEBUG_INFORMATION_FORMAT": "dwarf-with-dsym" if is_release else "dwarf",
63
+ "FRAMEWORK_SEARCH_PATHS": "$(PROJECT_DIR)",
64
+ "HEADER_SEARCH_PATHS": _header_search_paths(layout),
65
+ # Embedded dynamic frameworks (Python.xcframework, the SDL3/ANGLE family,
66
+ # wheel-embedded and SPM frameworks) are loaded via @rpath at runtime.
67
+ # Manage the runpath explicitly rather than relying on pbxproj embedding
68
+ # side effects, so any embed path resolves under .app/Frameworks/.
69
+ "LD_RUNPATH_SEARCH_PATHS": "$(inherited) @executable_path/Frameworks",
70
+ # UIKit is always required: SDL apps use it transitively through
71
+ # SDL3.framework; pure-Python apps call UIApplicationMain directly.
72
+ "OTHER_LDFLAGS": "-framework UIKit",
73
+ "GCC_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER": "NO",
74
+ "ALWAYS_SEARCH_USER_PATHS": "NO",
75
+ # The SDL3 family and Python frameworks ship ad-hoc code-signed; Xcode's
76
+ # built-in default COPY_PHASE_STRIP=YES tries to strip them on embed and
77
+ # warns "not stripping binary because it is signed". Disable it (Xcode's
78
+ # own project templates also default this to NO).
79
+ "COPY_PHASE_STRIP": "NO",
80
+ }
81
+
82
+ if ios.icons.source:
83
+ # Designate the AppIcon set in Assets.xcassets as the app icon. Without
84
+ # this the catalog still compiles, but Xcode assigns no icon — the
85
+ # generated AppIcon.appiconset is silently ignored.
86
+ settings["ASSETCATALOG_COMPILER_APPICON_NAME"] = "AppIcon"
87
+
88
+ if ios.entitlements:
89
+ settings["CODE_SIGN_ENTITLEMENTS"] = f"{config.app_slug}.entitlements"
90
+
91
+ return settings
92
+
93
+
94
+ def _header_search_paths(layout: StagingLayout | None) -> str:
95
+ paths = ["$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"]
96
+ if layout is not None:
97
+ sdl_xc = layout.frameworks / "SDL3.xcframework"
98
+ if sdl_xc.is_dir():
99
+ for slice_name in ("ios-arm64_x86_64-simulator", "ios-arm64"):
100
+ paths.append(
101
+ f"$(PROJECT_DIR)/Frameworks/SDL3.xcframework/"
102
+ f"{slice_name}/SDL3.framework/Headers"
103
+ )
104
+ return " ".join(f'"{path}"' for path in paths)
105
+
106
+
107
+ def signing_settings(config: Config) -> dict[str, str]:
108
+ """Map [tool.kivy.ios.signing] to CODE_SIGN_* build settings (spec 06)."""
109
+ ios = config.ios
110
+ assert ios is not None
111
+ signing = ios.signing
112
+ settings: dict[str, str] = {
113
+ "CODE_SIGN_STYLE": "Automatic" if signing.auto_signing else "Manual",
114
+ }
115
+ if signing.team_id:
116
+ settings["DEVELOPMENT_TEAM"] = signing.team_id
117
+ if signing.identity:
118
+ settings["CODE_SIGN_IDENTITY"] = signing.identity
119
+ if signing.provisioning_profile:
120
+ settings["PROVISIONING_PROFILE_SPECIFIER"] = signing.provisioning_profile
121
+ return settings
122
+
123
+
124
+ def user_build_settings(config: Config) -> dict[str, str]:
125
+ """Free-form [tool.kivy.ios.xcode.build_settings] (reserved keys already rejected)."""
126
+ if not config.ios:
127
+ return {}
128
+ return dict(config.ios.build_settings)