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,410 @@
1
+ """iOS-specific doctor checks (spec 05). Pure over a ``Probe``.
2
+
3
+ Reuses the platform-neutral checks (pip, toolchain version, app source dir) from
4
+ ``checks_common`` and adds the iOS environment (Xcode, command-line tools,
5
+ simulator runtimes) and project checks (signing, provisioning, icon, Swift
6
+ toolchain, find_links, reachable hosts, native binaries, privacy manifests) over
7
+ the ``pylock.ios.toml`` model.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from urllib.parse import urlparse
14
+
15
+ from kivyforge.config import ConfigError, load_config
16
+ from kivyforge.config.icons import APP_ICON_SIZE, icon_source_problem
17
+ from kivyforge.config.model import Config
18
+ from kivyforge.doctor import checks_common as C
19
+ from kivyforge.doctor.checks_common import _ver_tuple
20
+ from kivyforge.doctor.probe import Probe, RealProbe
21
+ from kivyforge.doctor.result import CheckResult, Status
22
+ from kivyforge.lock.find_links import find_links_doctor_detail
23
+ from kivyforge.lock.reader import LockError
24
+
25
+ from .lock import load
26
+ from .lock.model import Lockfile
27
+
28
+ MIN_XCODE = "15.0" # devicectl (run --device) requires Xcode 15+
29
+
30
+
31
+ # ---- environment checks -------------------------------------------------
32
+
33
+
34
+ def check_xcode_version(probe: Probe) -> CheckResult:
35
+ version = probe.xcode_version()
36
+ if version is None:
37
+ return CheckResult(
38
+ "Xcode version",
39
+ Status.FAIL,
40
+ "Xcode not found",
41
+ hint="install Xcode from the App Store, then `xcode-select --switch`.",
42
+ )
43
+ if _ver_tuple(version) < _ver_tuple(MIN_XCODE):
44
+ return CheckResult(
45
+ "Xcode version",
46
+ Status.FAIL,
47
+ f"{version} (need >= {MIN_XCODE})",
48
+ hint=f"update Xcode to {MIN_XCODE} or newer.",
49
+ )
50
+ return CheckResult("Xcode version", Status.PASS, version)
51
+
52
+
53
+ def check_command_line_tools(probe: Probe) -> CheckResult:
54
+ path = probe.xcode_select_path()
55
+ if not path:
56
+ return CheckResult(
57
+ "Command-line tools",
58
+ Status.FAIL,
59
+ "xcode-select path not set",
60
+ hint="run `xcode-select --install` or `sudo xcode-select --switch`.",
61
+ )
62
+ if not probe.has_xcrun_clang():
63
+ return CheckResult(
64
+ "Command-line tools",
65
+ Status.FAIL,
66
+ "xcrun clang not found",
67
+ hint="run `xcode-select --install`.",
68
+ )
69
+ return CheckResult("Command-line tools", Status.PASS, path)
70
+
71
+
72
+ def check_simulator_runtimes(probe: Probe, config: Config | None) -> CheckResult:
73
+ runtimes = probe.simulator_runtimes()
74
+ if not runtimes:
75
+ return CheckResult(
76
+ "Simulator runtimes",
77
+ Status.WARN,
78
+ "no iOS simulator runtimes installed",
79
+ hint="install one via Xcode > Settings > Components.",
80
+ )
81
+ if config is not None:
82
+ floor = config.ios_required.deployment_target
83
+ ok = [r for r in runtimes if _ver_tuple(r) >= _ver_tuple(floor)]
84
+ if not ok:
85
+ return CheckResult(
86
+ "Simulator runtimes",
87
+ Status.WARN,
88
+ f"installed {', '.join(runtimes)} < deployment_target {floor}",
89
+ hint=f"install an iOS {floor}+ simulator runtime.",
90
+ )
91
+ return CheckResult("Simulator runtimes", Status.PASS, ", ".join(runtimes))
92
+
93
+
94
+ # ---- project checks -----------------------------------------------------
95
+
96
+
97
+ def check_signing_identity(probe: Probe, config: Config) -> CheckResult:
98
+ signing = config.ios_required.signing
99
+ if signing.auto_signing:
100
+ return CheckResult("Signing identity", Status.PASS, "automatic signing")
101
+ identities = probe.keychain_identities()
102
+ if not any(signing.identity in line for line in identities):
103
+ return CheckResult(
104
+ "Signing identity",
105
+ Status.FAIL,
106
+ f"identity {signing.identity!r} not in keychain",
107
+ hint="import the signing certificate or set auto_signing = true.",
108
+ )
109
+ return CheckResult("Signing identity", Status.PASS, signing.identity)
110
+
111
+
112
+ def check_swift_toolchain(probe: Probe, config: Config) -> CheckResult:
113
+ """SPM resolution (lock) and compilation (build) need Xcode's Swift toolchain.
114
+
115
+ Skipped for pure-wheel projects — only relevant when ``swift_packages`` is
116
+ declared (spec 07).
117
+ """
118
+ packages = config.ios_required.swift_packages
119
+ if not packages:
120
+ return CheckResult(
121
+ "Swift package toolchain", Status.SKIP, "no swift_packages declared"
122
+ )
123
+ if not probe.has_swift_toolchain():
124
+ return CheckResult(
125
+ "Swift package toolchain",
126
+ Status.FAIL,
127
+ "swift not found",
128
+ hint="install Xcode's Swift toolchain (`xcode-select --switch "
129
+ "/Applications/Xcode.app`) — required to resolve/build Swift packages.",
130
+ )
131
+ return CheckResult(
132
+ "Swift package toolchain",
133
+ Status.PASS,
134
+ f"{len(packages)} swift package(s) declared",
135
+ )
136
+
137
+
138
+ def check_app_icon(config: Config, project_root: Path) -> CheckResult:
139
+ source = config.ios_required.icons.source
140
+ if not source:
141
+ return CheckResult("App icon", Status.SKIP, "not configured")
142
+ path = (project_root / source).resolve()
143
+ problem = icon_source_problem(path)
144
+ if problem:
145
+ return CheckResult(
146
+ "App icon",
147
+ Status.FAIL,
148
+ f"{source} invalid",
149
+ hint=problem,
150
+ )
151
+ return CheckResult("App icon", Status.PASS, f"{APP_ICON_SIZE}x{APP_ICON_SIZE} PNG")
152
+
153
+
154
+ def check_provisioning_profile(config: Config, project_root: Path) -> CheckResult:
155
+ profile = config.ios_required.signing.provisioning_profile
156
+ if not profile:
157
+ return CheckResult("Provisioning profile", Status.PASS, "not set")
158
+ path = (
159
+ (project_root / profile) if not Path(profile).is_absolute() else Path(profile)
160
+ )
161
+ if not path.exists():
162
+ return CheckResult(
163
+ "Provisioning profile",
164
+ Status.FAIL,
165
+ f"{profile} not found",
166
+ hint="point provisioning_profile at an existing .mobileprovision.",
167
+ )
168
+ return CheckResult("Provisioning profile", Status.PASS, profile)
169
+
170
+
171
+ def check_find_links(config: Config, project_root: Path) -> CheckResult:
172
+ entries = config.ios_required.find_links
173
+ if not entries:
174
+ return CheckResult("find_links directories", Status.SKIP, "not configured")
175
+
176
+ root = project_root.resolve()
177
+ problems: list[tuple[str, str | None]] = []
178
+ warnings: list[tuple[str, str | None]] = []
179
+ ok: list[str] = []
180
+
181
+ for entry in entries:
182
+ path = (root / entry).resolve()
183
+ detail, hint = find_links_doctor_detail(root, entry, path)
184
+ if not path.exists() or not path.is_dir():
185
+ problems.append((detail, hint))
186
+ elif not any(path.glob("*.whl")):
187
+ warnings.append((detail, hint))
188
+ else:
189
+ ok.append(detail)
190
+
191
+ if problems:
192
+ detail = "; ".join(d for d, _ in problems)
193
+ hint = next((h for _, h in problems if h), "")
194
+ return CheckResult("find_links directories", Status.FAIL, detail, hint=hint)
195
+ if warnings:
196
+ detail = "; ".join(d for d, _ in warnings)
197
+ hint = next((h for _, h in warnings if h), "")
198
+ return CheckResult("find_links directories", Status.WARN, detail, hint=hint)
199
+ return CheckResult("find_links directories", Status.PASS, "; ".join(ok))
200
+
201
+
202
+ def check_hosts_reachable(probe: Probe, lock: Lockfile | None) -> CheckResult:
203
+ if lock is None:
204
+ return CheckResult(
205
+ "Required hosts reachable", Status.SKIP, "no pylock.ios.toml"
206
+ )
207
+ hosts = sorted(_lock_hosts(lock))
208
+ if not hosts:
209
+ return CheckResult(
210
+ "Required hosts reachable", Status.PASS, "all artifacts vendored"
211
+ )
212
+ unreachable = [h for h in hosts if not probe.tcp_reachable(h, 443)]
213
+ if unreachable:
214
+ return CheckResult(
215
+ "Required hosts reachable",
216
+ Status.FAIL,
217
+ f"unreachable: {', '.join(unreachable)}",
218
+ hint="check your network/proxy; these hosts serve pinned artifacts.",
219
+ )
220
+ return CheckResult("Required hosts reachable", Status.PASS, ", ".join(hosts))
221
+
222
+
223
+ def check_app_native_binaries(
224
+ probe: Probe, config: Config, project_root: Path
225
+ ) -> CheckResult:
226
+ app_dir = project_root / config.kivy.app_dir
227
+ if not app_dir.is_dir():
228
+ return CheckResult(
229
+ "App-local native binaries", Status.SKIP, f"{config.kivy.app_dir} missing"
230
+ )
231
+ offenders: list[str] = []
232
+ unknown: list[str] = []
233
+ for pattern in ("*.so", "*.dylib"):
234
+ for binary in app_dir.rglob(pattern):
235
+ rel = binary.relative_to(project_root)
236
+ platforms = probe.binary_platforms(binary)
237
+ if not platforms:
238
+ # No platform could be read: truncated, corrupt, not a Mach-O,
239
+ # or lacking an LC_BUILD_VERSION. Can't confirm it's iOS-safe.
240
+ unknown.append(str(rel))
241
+ elif not platforms & {"ios", "ios-simulator"}:
242
+ offenders.append(f"{rel} ({', '.join(sorted(platforms))})")
243
+ if offenders:
244
+ return CheckResult(
245
+ "App-local native binaries",
246
+ Status.FAIL,
247
+ "; ".join(offenders),
248
+ hint="non-iOS native code won't load on device; ship it as an iOS wheel.",
249
+ )
250
+ if unknown:
251
+ return CheckResult(
252
+ "App-local native binaries",
253
+ Status.WARN,
254
+ "unrecognized binary: " + "; ".join(unknown),
255
+ hint="could not read an iOS Mach-O platform from these files "
256
+ "(truncated, corrupt, or not a Mach-O); confirm they are iOS dylibs "
257
+ "or ship them as an iOS wheel.",
258
+ )
259
+ return CheckResult("App-local native binaries", Status.PASS, "none non-iOS")
260
+
261
+
262
+ def check_app_privacy_manifest(config: Config, project_root: Path) -> CheckResult:
263
+ manifest = project_root / f"{config.app_slug}-ios" / "PrivacyInfo.xcprivacy"
264
+ if not manifest.exists():
265
+ return CheckResult(
266
+ "App-level privacy manifest",
267
+ Status.WARN,
268
+ "PrivacyInfo.xcprivacy absent",
269
+ hint="`kivyforge build` generates a minimal stub; if you use "
270
+ "required-reason APIs, set [tool.kivy.ios.privacy_manifest].source.",
271
+ )
272
+ return CheckResult("App-level privacy manifest", Status.PASS, "present")
273
+
274
+
275
+ def check_xcframework_privacy_manifests(
276
+ config: Config, project_root: Path
277
+ ) -> CheckResult:
278
+ frameworks = project_root / f"{config.app_slug}-ios" / "Frameworks"
279
+ if not frameworks.is_dir():
280
+ return CheckResult(
281
+ "xcframework privacy manifests", Status.SKIP, "project not built"
282
+ )
283
+ missing = []
284
+ for xc in sorted(frameworks.glob("*.xcframework")):
285
+ if not any(xc.rglob("PrivacyInfo.xcprivacy")):
286
+ missing.append(xc.name)
287
+ if missing:
288
+ return CheckResult(
289
+ "xcframework privacy manifests",
290
+ Status.WARN,
291
+ f"no PrivacyInfo in: {', '.join(missing)}",
292
+ hint="each framework's author must add a privacy manifest; "
293
+ "kivyforge cannot add it for them.",
294
+ )
295
+ return CheckResult("xcframework privacy manifests", Status.PASS, "all present")
296
+
297
+
298
+ def _lock_hosts(lock: Lockfile) -> set[str]:
299
+ hosts: set[str] = set()
300
+ for pkg in lock.packages:
301
+ for wheel in pkg.wheels:
302
+ if wheel.url:
303
+ _add_host(hosts, wheel.url)
304
+ for xc in lock.xcframeworks:
305
+ if xc.url:
306
+ _add_host(hosts, xc.url)
307
+ for sp in lock.swift_packages:
308
+ if sp.url:
309
+ host = _git_host(sp.url)
310
+ if host:
311
+ hosts.add(host)
312
+ if lock.python_xcframework.url:
313
+ _add_host(hosts, lock.python_xcframework.url)
314
+ return hosts
315
+
316
+
317
+ def _add_host(hosts: set[str], url: str) -> None:
318
+ netloc = urlparse(url).hostname
319
+ if netloc:
320
+ hosts.add(netloc)
321
+
322
+
323
+ def _git_host(url: str) -> str | None:
324
+ """Host of a Git URL, handling both ``https://`` and scp-like ``git@host:``."""
325
+ if "://" in url:
326
+ return urlparse(url).hostname
327
+ if "@" in url and ":" in url:
328
+ return url.split("@", 1)[1].split(":", 1)[0] or None
329
+ return urlparse(url).hostname
330
+
331
+
332
+ def run_ios_checks(
333
+ probe: Probe,
334
+ *,
335
+ kivyforge_version: str,
336
+ config: Config | None = None,
337
+ project_root: Path | None = None,
338
+ lock: Lockfile | None = None,
339
+ offline: bool = False,
340
+ ) -> list[CheckResult]:
341
+ """Run all iOS checks. Project checks SKIP in environment mode (no config)."""
342
+ project_root = project_root or Path.cwd()
343
+ results = [
344
+ check_xcode_version(probe),
345
+ check_command_line_tools(probe),
346
+ C.check_pip_version(probe),
347
+ check_simulator_runtimes(probe, config),
348
+ C.check_kivyforge_version(probe, kivyforge_version, offline=offline),
349
+ ]
350
+
351
+ if config is None:
352
+ for name in (
353
+ "App source directory",
354
+ "Signing identity",
355
+ "Provisioning profile",
356
+ "App icon",
357
+ "Swift package toolchain",
358
+ "find_links directories",
359
+ "Required hosts reachable",
360
+ "App-local native binaries",
361
+ "App-level privacy manifest",
362
+ "xcframework privacy manifests",
363
+ ):
364
+ results.append(CheckResult(name, Status.SKIP, C.SKIP_NOTE))
365
+ return results
366
+
367
+ results += [
368
+ C.check_app_dir(config, project_root),
369
+ check_signing_identity(probe, config),
370
+ check_provisioning_profile(config, project_root),
371
+ check_app_icon(config, project_root),
372
+ check_swift_toolchain(probe, config),
373
+ check_find_links(config, project_root),
374
+ check_hosts_reachable(probe, lock),
375
+ check_app_native_binaries(probe, config, project_root),
376
+ check_app_privacy_manifest(config, project_root),
377
+ check_xcframework_privacy_manifests(config, project_root),
378
+ ]
379
+ return results
380
+
381
+
382
+ def ios_doctor(
383
+ cwd: Path, *, kivyforge_version: str, offline: bool
384
+ ) -> list[CheckResult]:
385
+ """Load the iOS project + lock (if present) and run the iOS check set."""
386
+ pyproject = cwd / "pyproject.toml"
387
+ config = None
388
+ lock = None
389
+ parse_results: list[CheckResult] = []
390
+ if pyproject.is_file():
391
+ try:
392
+ config = load_config(pyproject)
393
+ except ConfigError as exc:
394
+ parse_results.append(
395
+ CheckResult("pyproject.toml", Status.FAIL, exc.format())
396
+ )
397
+ lockfile = cwd / "pylock.ios.toml"
398
+ if lockfile.is_file():
399
+ try:
400
+ lock = load(lockfile)
401
+ except LockError as exc:
402
+ parse_results.append(C.lock_parse_fail("ios", exc))
403
+ return parse_results + run_ios_checks(
404
+ RealProbe(),
405
+ kivyforge_version=kivyforge_version,
406
+ config=config,
407
+ project_root=cwd,
408
+ lock=lock,
409
+ offline=offline,
410
+ )
@@ -0,0 +1,34 @@
1
+ """Generate ``<app>.entitlements`` from ``[tool.kivy.ios.entitlements]`` (spec 06)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import plistlib
6
+ from pathlib import Path
7
+
8
+ from kivyforge.config.model import Config
9
+
10
+
11
+ def has_entitlements(config: Config) -> bool:
12
+ return bool(config.ios and config.ios.entitlements)
13
+
14
+
15
+ def build_entitlements(config: Config) -> dict:
16
+ if not config.ios:
17
+ return {}
18
+ return dict(config.ios.entitlements)
19
+
20
+
21
+ def write_entitlements(config: Config, path: str | Path) -> Path | None:
22
+ """Write the entitlements plist; returns None when there are none.
23
+
24
+ When no entitlements are configured, any previously generated file is
25
+ removed so a stale ``<app>.entitlements`` (from a since-deleted config
26
+ table) is not left referenced in the bundle.
27
+ """
28
+ path = Path(path)
29
+ if not has_entitlements(config):
30
+ path.unlink(missing_ok=True)
31
+ return None
32
+ with open(path, "wb") as f:
33
+ plistlib.dump(build_entitlements(config), f, sort_keys=True)
34
+ return path