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,324 @@
1
+ """Wire locked Swift packages into the generated ``.xcodeproj`` (spec 07 §"Xcode
2
+ project generation").
3
+
4
+ ``kivyforge build`` runs ``sync_swift_packages`` as part of the every-build,
5
+ idempotent project sync. For each ``[[tool.kivyforge.swift_packages]]`` entry it
6
+ ensures:
7
+
8
+ * a package reference (``XCRemoteSwiftPackageReference`` for ``url`` entries,
9
+ ``XCLocalSwiftPackageReference`` for ``path`` entries),
10
+ * one ``XCSwiftPackageProductDependency`` per product, linked into the
11
+ Frameworks (Link) phase when ``link`` is set, and
12
+ * an **explicit** Embed Frameworks / Copy Files entry (with ``CodeSignOnCopy``)
13
+ when ``embed`` is set — required because ``pbxproj`` only wires the *link*
14
+ step and Xcode does not auto-embed dynamic SPM products in our generated,
15
+ non-GUI project (see docs/dev/swift-spm-findings.md).
16
+
17
+ References/dependencies for entries no longer in the lock are pruned, and a
18
+ ``Package.resolved`` is written from the pinned revisions so Xcode resolves to
19
+ the locked commits without network drift.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import os
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+ from pbxproj import PBXGenericObject, PBXList
30
+ from pbxproj.pbxsections.PBXBuildFile import PBXBuildFile
31
+ from pbxproj.pbxsections.XCLocalSwiftPackageReference import (
32
+ XCLocalSwiftPackageReference,
33
+ )
34
+
35
+ from .lock.model import LockedSwiftPackage
36
+
37
+ # pbxproj ships no type stubs and builds objects dynamically (PBXGenericObject),
38
+ # so the project graph is treated as ``Any`` throughout this interop module.
39
+
40
+ # PBXCopyFilesBuildPhase destination for frameworks (Embed Frameworks).
41
+ FRAMEWORKS_DST_SUBFOLDER_SPEC = "10"
42
+ PACKAGE_RESOLVED_VERSION = 2
43
+
44
+
45
+ def sync_swift_packages(
46
+ project: Any,
47
+ target_name: str,
48
+ packages: tuple[LockedSwiftPackage, ...],
49
+ *,
50
+ staging_root: Path | None = None,
51
+ ) -> None:
52
+ """Idempotently reconcile the project's SPM graph with ``packages``.
53
+
54
+ ``staging_root`` is the ``<app>-ios/`` directory that holds the generated
55
+ ``.xcodeproj``. Local package ``path`` entries are stored in the lock
56
+ relative to the *project root* (where ``pyproject.toml`` lives), but Xcode
57
+ resolves ``XCLocalSwiftPackageReference.relativePath`` relative to the
58
+ project file's own directory — one level deeper. We translate the path
59
+ accordingly (e.g. ``swift-shims`` -> ``../swift-shims``).
60
+ """
61
+ for sp in packages:
62
+ ref = _ensure_reference(project, sp, staging_root)
63
+ for product in sp.products:
64
+ dep: Any = project.get_or_create_package_dependency(
65
+ product, target_name, (ref, product)
66
+ )
67
+ if dep is None:
68
+ continue
69
+ # Repoint at the ensured reference: an existing dependency is reused
70
+ # by product name, so if the reference id changed (e.g. a local
71
+ # path was corrected and the old reference pruned) the stale pointer
72
+ # would otherwise dangle.
73
+ dep["package"] = ref.get_id()
74
+ if sp.link:
75
+ _ensure_link(project, target_name, dep)
76
+ else:
77
+ _remove_build_files(project, dep.get_id(), "PBXFrameworksBuildPhase")
78
+ if sp.embed:
79
+ _ensure_embed(project, target_name, dep)
80
+ else:
81
+ _remove_build_files(project, dep.get_id(), "PBXCopyFilesBuildPhase")
82
+ _prune(project, target_name, packages, staging_root)
83
+
84
+
85
+ # -- references ---------------------------------------------------------------- #
86
+
87
+
88
+ def local_relative_path(path: str, staging_root: Path | None) -> str:
89
+ """Translate a project-root-relative local package ``path`` into one
90
+ relative to the ``<app>-ios/`` staging root (where the ``.xcodeproj`` lives),
91
+ which is how Xcode interprets ``XCLocalSwiftPackageReference.relativePath``.
92
+
93
+ With ``staging_root`` unset the path is returned unchanged (used by callers
94
+ that already provide a project-relative path).
95
+ """
96
+ if staging_root is None:
97
+ return path
98
+ target = (staging_root.parent / path).resolve()
99
+ # Xcode stores relativePath with forward slashes; keep it host independent.
100
+ return Path(os.path.relpath(target, staging_root.resolve())).as_posix()
101
+
102
+
103
+ def _ensure_reference(
104
+ project: Any, sp: LockedSwiftPackage, staging_root: Path | None
105
+ ) -> Any:
106
+ if sp.url:
107
+ requirement = xcode_requirement(sp.requirement or {})
108
+ ref = project.get_or_create_package_reference(sp.url, (sp.url, requirement))
109
+ # Reflect a changed requirement rule on re-lock (get_or_create won't).
110
+ ref["requirement"] = PBXGenericObject().parse(requirement)
111
+ return ref
112
+ return _get_or_create_local_reference(
113
+ project, local_relative_path(sp.path or "", staging_root)
114
+ )
115
+
116
+
117
+ def _get_or_create_local_reference(project: Any, relative_path: str) -> Any:
118
+ for ref in project.objects.get_objects_in_section("XCLocalSwiftPackageReference"):
119
+ if ref["relativePath"] == relative_path:
120
+ return ref
121
+ ref: Any = XCLocalSwiftPackageReference.create(relative_path)
122
+ project.objects[ref.get_id()] = ref
123
+ for proj in project.objects.get_objects_in_section("PBXProject"):
124
+ if "packageReferences" not in proj:
125
+ proj["packageReferences"] = PBXList()
126
+ if ref.get_id() not in proj["packageReferences"]:
127
+ proj["packageReferences"].append(ref.get_id())
128
+ return ref
129
+
130
+
131
+ def xcode_requirement(rule: dict[str, object]) -> dict[str, str]:
132
+ """Map a lock ``requirement`` rule to the Xcode ``requirement`` object."""
133
+ if not rule:
134
+ raise ValueError("remote swift package is missing a requirement rule")
135
+ ((kind, value),) = rule.items()
136
+ if kind == "exact":
137
+ return {"kind": "exactVersion", "version": str(value)}
138
+ if kind == "from":
139
+ return {"kind": "upToNextMajorVersion", "minimumVersion": str(value)}
140
+ if kind == "up_to_next_minor":
141
+ return {"kind": "upToNextMinorVersion", "minimumVersion": str(value)}
142
+ if kind == "range" and isinstance(value, list) and len(value) == 2:
143
+ return {
144
+ "kind": "versionRange",
145
+ "minimumVersion": str(value[0]),
146
+ "maximumVersion": str(value[1]),
147
+ }
148
+ if kind == "branch":
149
+ return {"kind": "branch", "branch": str(value)}
150
+ if kind == "revision":
151
+ return {"kind": "revision", "revision": str(value)}
152
+ raise ValueError(f"unrenderable swift package requirement {rule!r}")
153
+
154
+
155
+ # -- link / embed build files -------------------------------------------------- #
156
+
157
+
158
+ def _ensure_link(project: Any, target_name: str, dep: Any) -> None:
159
+ phase = _link_phase(project, target_name)
160
+ if phase is None or _has_build_file(project, phase, dep.get_id()):
161
+ return
162
+ build_file: Any = PBXBuildFile.create(dep, is_product=True)
163
+ project.objects[build_file.get_id()] = build_file
164
+ phase.add_build_file(build_file)
165
+
166
+
167
+ def _ensure_embed(project: Any, target_name: str, dep: Any) -> None:
168
+ phase = _embed_phase(project, target_name)
169
+ if _has_build_file(project, phase, dep.get_id()):
170
+ return
171
+ build_file: Any = PBXBuildFile.create(
172
+ dep, attributes=["CodeSignOnCopy"], is_product=True
173
+ )
174
+ project.objects[build_file.get_id()] = build_file
175
+ phase.add_build_file(build_file)
176
+
177
+
178
+ def _link_phase(project: Any, target_name: str) -> Any:
179
+ target = project.get_target_by_name(target_name)
180
+ if target is None:
181
+ return None
182
+ phases = target.get_or_create_build_phase("PBXFrameworksBuildPhase")
183
+ return phases[0] if phases else None
184
+
185
+
186
+ def _embed_phase(project: Any, target_name: str) -> Any:
187
+ target = project.get_target_by_name(target_name)
188
+ if target is None:
189
+ raise RuntimeError(f"target {target_name!r} not found in Xcode project")
190
+ phases = target.get_or_create_build_phase(
191
+ "PBXCopyFilesBuildPhase",
192
+ search_parameters={"dstSubfolderSpec": FRAMEWORKS_DST_SUBFOLDER_SPEC},
193
+ create_parameters=(
194
+ "Embed Frameworks",
195
+ [],
196
+ "",
197
+ FRAMEWORKS_DST_SUBFOLDER_SPEC,
198
+ ),
199
+ )
200
+ return phases[0]
201
+
202
+
203
+ def _has_build_file(project: Any, phase: Any, dep_id: str) -> bool:
204
+ for build_file_id in phase["files"]:
205
+ build_file = project.objects[build_file_id]
206
+ if _product_ref(build_file) == dep_id:
207
+ return True
208
+ return False
209
+
210
+
211
+ # -- pruning ------------------------------------------------------------------- #
212
+
213
+
214
+ def _prune(
215
+ project: Any,
216
+ target_name: str,
217
+ packages: tuple[LockedSwiftPackage, ...],
218
+ staging_root: Path | None,
219
+ ) -> None:
220
+ desired_products = {p for sp in packages for p in sp.products}
221
+ desired_remote = {_normalize_url(sp.url) for sp in packages if sp.url}
222
+ desired_local = {
223
+ local_relative_path(sp.path, staging_root) for sp in packages if sp.path
224
+ }
225
+
226
+ for dep in list(
227
+ project.objects.get_objects_in_section("XCSwiftPackageProductDependency")
228
+ ):
229
+ if dep["productName"] not in desired_products:
230
+ _remove_product_dependency(project, target_name, dep)
231
+
232
+ for ref in list(
233
+ project.objects.get_objects_in_section("XCRemoteSwiftPackageReference")
234
+ ):
235
+ if _normalize_url(ref["repositoryURL"]) not in desired_remote:
236
+ _remove_reference(project, ref)
237
+
238
+ for ref in list(
239
+ project.objects.get_objects_in_section("XCLocalSwiftPackageReference")
240
+ ):
241
+ if ref["relativePath"] not in desired_local:
242
+ _remove_reference(project, ref)
243
+
244
+
245
+ def _remove_product_dependency(project: Any, target_name: str, dep: Any) -> None:
246
+ dep_id = dep.get_id()
247
+ _remove_build_files(project, dep_id, "PBXFrameworksBuildPhase")
248
+ _remove_build_files(project, dep_id, "PBXCopyFilesBuildPhase")
249
+ for target in project.objects.get_targets(target_name):
250
+ if "packageProductDependencies" not in target:
251
+ continue
252
+ deps = target["packageProductDependencies"]
253
+ if dep_id in deps:
254
+ deps.remove(dep_id)
255
+ del project.objects[dep_id]
256
+
257
+
258
+ def _remove_reference(project: Any, ref: Any) -> None:
259
+ ref_id = ref.get_id()
260
+ for proj in project.objects.get_objects_in_section("PBXProject"):
261
+ if "packageReferences" in proj and ref_id in proj["packageReferences"]:
262
+ proj["packageReferences"].remove(ref_id)
263
+ del project.objects[ref_id]
264
+
265
+
266
+ def _remove_build_files(project: Any, dep_id: str, section: str) -> None:
267
+ for phase in project.objects.get_objects_in_section(section):
268
+ for build_file_id in list(phase["files"]):
269
+ build_file = project.objects[build_file_id]
270
+ if _product_ref(build_file) == dep_id:
271
+ phase.remove_build_file(build_file)
272
+
273
+
274
+ def _product_ref(build_file: Any) -> str | None:
275
+ return build_file["productRef"] if "productRef" in build_file else None
276
+
277
+
278
+ # -- Package.resolved ---------------------------------------------------------- #
279
+
280
+
281
+ def package_resolved_json(packages: tuple[LockedSwiftPackage, ...]) -> str:
282
+ """Render a ``Package.resolved`` (schema v2) from the pinned revisions."""
283
+ pins = []
284
+ remote = [p for p in packages if p.url and p.revision]
285
+ for sp in sorted(remote, key=lambda p: _identity(p.url or "")):
286
+ state: dict[str, str] = {"revision": sp.revision or ""}
287
+ if sp.version:
288
+ state["version"] = sp.version
289
+ pins.append(
290
+ {
291
+ "identity": _identity(sp.url or ""),
292
+ "kind": "remoteSourceControl",
293
+ "location": sp.url,
294
+ "state": state,
295
+ }
296
+ )
297
+ return (
298
+ json.dumps({"pins": pins, "version": PACKAGE_RESOLVED_VERSION}, indent=2) + "\n"
299
+ )
300
+
301
+
302
+ def write_package_resolved(
303
+ path: str | Path, packages: tuple[LockedSwiftPackage, ...]
304
+ ) -> None:
305
+ """Write (or remove) ``Package.resolved`` for the project's remote packages."""
306
+ path = Path(path)
307
+ remote = [p for p in packages if p.url and p.revision]
308
+ if not remote:
309
+ path.unlink(missing_ok=True)
310
+ return
311
+ path.parent.mkdir(parents=True, exist_ok=True)
312
+ path.write_text(package_resolved_json(packages), encoding="utf-8")
313
+
314
+
315
+ def _identity(url: str) -> str:
316
+ name = _normalize_url(url).rsplit("/", 1)[-1]
317
+ return name
318
+
319
+
320
+ def _normalize_url(url: str) -> str:
321
+ u = url.strip().lower().rstrip("/")
322
+ if u.endswith(".git"):
323
+ u = u[:-4]
324
+ return u.rstrip("/")
@@ -0,0 +1,34 @@
1
+ /*
2
+ * kivyforge bootstrap — public header.
3
+ *
4
+ * Generated projects include this in main.m to call kivyforge_main().
5
+ * All SDL and CPython implementation details live in kivyforge_bootstrap.m,
6
+ * keeping the generated main.m stable across Kivy dependency changes.
7
+ */
8
+ #ifndef KIVYFORGE_BOOTSTRAP_H
9
+ #define KIVYFORGE_BOOTSTRAP_H
10
+
11
+ /**
12
+ * Start a kivyforge application.
13
+ *
14
+ * Sets up CPython using the bundle layout created by `kivyforge build` and
15
+ * imports @p entry_module. When SDL3 is available (Kivy apps) the UIKit
16
+ * lifecycle is driven by SDL_RunApp; otherwise Python runs directly on the
17
+ * main thread (headless) — selected at compile time, transparent to the caller.
18
+ *
19
+ * @param argc Forwarded from main().
20
+ * @param argv Forwarded from main().
21
+ * @param entry_module Python module to import (e.g. "myapp.main").
22
+ * @param app_dir Bundle-relative name of the app directory ("app").
23
+ * @param python_ver Python major.minor version string (e.g. "3.15").
24
+ * @return Exit status.
25
+ */
26
+ int kivyforge_main(
27
+ int argc,
28
+ char *argv[],
29
+ const char *entry_module,
30
+ const char *app_dir,
31
+ const char *python_ver
32
+ );
33
+
34
+ #endif /* KIVYFORGE_BOOTSTRAP_H */
@@ -0,0 +1,146 @@
1
+ /*
2
+ * kivyforge_bootstrap.m — dual-mode iOS bootstrap.
3
+ *
4
+ * When SDL3 is available (Kivy wheel embedded in Frameworks/) SDL_RunApp
5
+ * drives the UIKit lifecycle. When SDL3 is absent (pure-Python / no-Kivy
6
+ * apps) a minimal UIApplicationDelegate does the same job without the SDL
7
+ * dependency. The right path is chosen at compile time by __has_include so
8
+ * the same source file works in both configurations without any ifdef noise
9
+ * in generated projects.
10
+ *
11
+ * Python headers → Python.xcframework (always present)
12
+ * SDL3 headers → Frameworks/SDL3.xcframework (only for Kivy apps)
13
+ *
14
+ * HEADER_SEARCH_PATHS is managed by `kivyforge build`; it adds SDL3 paths
15
+ * only when SDL3.xcframework is present, which is exactly the condition
16
+ * __has_include tests.
17
+ */
18
+
19
+ /* ── common ─────────────────────────────────────────────────────────────── */
20
+ #import <Foundation/Foundation.h>
21
+ #import <UIKit/UIKit.h>
22
+ #include <Python.h>
23
+ #include "kivyforge_bootstrap.h"
24
+
25
+ typedef struct {
26
+ const char *entry_module;
27
+ const char *app_dir;
28
+ const char *python_ver;
29
+ } _BootstrapArgs;
30
+
31
+ static _BootstrapArgs _g_args;
32
+
33
+ static void _crash(NSString *message) {
34
+ NSLog(@"kivyforge bootstrap fatal: %@", message);
35
+ exit(1);
36
+ }
37
+
38
+ /* Shared Python initialisation — no SDL dependency. */
39
+ static void _run_python(void) {
40
+ NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
41
+ NSString *pythonHome = [resourcePath stringByAppendingPathComponent:@"python"];
42
+ NSString *appPath = [resourcePath stringByAppendingPathComponent:
43
+ [NSString stringWithUTF8String:_g_args.app_dir]];
44
+ NSString *pipDeps = [resourcePath stringByAppendingPathComponent:@"pip-deps"];
45
+ NSString *pyVer = [NSString stringWithUTF8String:_g_args.python_ver];
46
+
47
+ setenv("PYTHONHOME", [pythonHome UTF8String], 1);
48
+ /* sys.platform == "ios" triggers Kivy's iOS code paths. */
49
+ setenv("KIVY_BUILD", "ios", 1);
50
+
51
+ NSString *stdlib = [pythonHome stringByAppendingPathComponent:
52
+ [@"lib/python" stringByAppendingString:pyVer]];
53
+ NSString *dynload = [stdlib stringByAppendingPathComponent:@"lib-dynload"];
54
+ NSString *pythonPath = [@[stdlib, dynload, appPath]
55
+ componentsJoinedByString:@":"];
56
+ setenv("PYTHONPATH", [pythonPath UTF8String], 1);
57
+
58
+ PyStatus status;
59
+ PyPreConfig preconfig;
60
+ PyPreConfig_InitIsolatedConfig(&preconfig);
61
+ preconfig.utf8_mode = 1;
62
+ status = Py_PreInitialize(&preconfig);
63
+ if (PyStatus_Exception(status)) _crash(@"Py_PreInitialize failed");
64
+
65
+ PyConfig config;
66
+ PyConfig_InitPythonConfig(&config);
67
+ config.buffered_stdio = 0;
68
+ config.write_bytecode = 0;
69
+ config.install_signal_handlers = 1;
70
+ config.use_environment = 1;
71
+ PyConfig_SetBytesString(&config, &config.home, [pythonHome UTF8String]);
72
+
73
+ status = Py_InitializeFromConfig(&config);
74
+ PyConfig_Clear(&config);
75
+ if (PyStatus_Exception(status)) _crash(@"Py_InitializeFromConfig failed");
76
+
77
+ NSString *addsite = [NSString stringWithFormat:
78
+ @"import site; site.addsitedir(\"%@\")", pipDeps];
79
+ if (PyRun_SimpleString([addsite UTF8String]) != 0)
80
+ _crash(@"failed to add pip-deps as a site directory");
81
+
82
+ PyObject *module = PyImport_ImportModule(_g_args.entry_module);
83
+ if (module == NULL) {
84
+ PyErr_Print();
85
+ _crash([NSString stringWithFormat:
86
+ @"failed to import entry module \"%s\"", _g_args.entry_module]);
87
+ }
88
+ Py_DECREF(module);
89
+ Py_Finalize();
90
+ }
91
+
92
+ /* ── SDL3 path — Kivy apps ──────────────────────────────────────────────── */
93
+ #if __has_include(<SDL3/SDL_main.h>)
94
+
95
+ #define SDL_MAIN_HANDLED
96
+ #include <SDL3/SDL_main.h>
97
+ #include <SDL3/SDL.h>
98
+
99
+ static int _sdl_callback(int argc, char *argv[]) {
100
+ @autoreleasepool { _run_python(); }
101
+ return 0;
102
+ }
103
+
104
+ int kivyforge_main(
105
+ int argc,
106
+ char *argv[],
107
+ const char *entry_module,
108
+ const char *app_dir,
109
+ const char *python_ver)
110
+ {
111
+ _g_args.entry_module = entry_module;
112
+ _g_args.app_dir = app_dir;
113
+ _g_args.python_ver = python_ver;
114
+ return SDL_RunApp(argc, argv, _sdl_callback, NULL);
115
+ }
116
+
117
+ /* ── headless path — pure-Python / no-Kivy apps ─────────────────────────── */
118
+ #else
119
+
120
+ /*
121
+ * Without SDL there is no UI toolkit driving the app, so Python runs directly
122
+ * on the main thread. We deliberately do NOT call UIApplicationMain: doing so
123
+ * starts the UIKit app lifecycle, which (a) emits "UIScene lifecycle will soon
124
+ * be required" for a delegate that has no scene, and (b) triggers CoreAnimation
125
+ * app-launch measurements that can never complete because a headless app never
126
+ * presents a first frame. Running Python directly keeps pure-Python apps clean
127
+ * and matches their console/headless nature. Kivy apps take the SDL path above,
128
+ * where SDL_RunApp owns the full UIKit lifecycle.
129
+ */
130
+ int kivyforge_main(
131
+ int argc,
132
+ char *argv[],
133
+ const char *entry_module,
134
+ const char *app_dir,
135
+ const char *python_ver)
136
+ {
137
+ (void)argc;
138
+ (void)argv;
139
+ _g_args.entry_module = entry_module;
140
+ _g_args.app_dir = app_dir;
141
+ _g_args.python_ver = python_ver;
142
+ @autoreleasepool { _run_python(); }
143
+ return 0;
144
+ }
145
+
146
+ #endif /* __has_include(<SDL3/SDL_main.h>) */
@@ -0,0 +1,15 @@
1
+ /*
2
+ * kivyforge generated entry point.
3
+ *
4
+ * All SDL and CPython bootstrap logic lives in kivyforge_bootstrap.m so that
5
+ * this file stays stable across Kivy dependency updates. Per-project values
6
+ * (entry module, app directory, Python version) are supplied by
7
+ * main_config.h, which is regenerated by `kivyforge build` whenever project
8
+ * settings change.
9
+ */
10
+ #include "kivyforge_bootstrap.h"
11
+ #include "main_config.h"
12
+
13
+ int main(int argc, char *argv[]) {
14
+ return kivyforge_main(argc, argv, ENTRY_POINT, APP_DIR, PYTHON_MAJOR_MINOR);
15
+ }
@@ -0,0 +1,12 @@
1
+ /*
2
+ * Per-project defines for main.m. Regenerated by `kivyforge build` whenever
3
+ * [tool.kivy].app_dir or entry_point change. Do not edit by hand.
4
+ */
5
+ #ifndef MAIN_CONFIG_H
6
+ #define MAIN_CONFIG_H
7
+
8
+ #define APP_DIR "@APP_DIR@"
9
+ #define ENTRY_POINT "@ENTRY_POINT@"
10
+ #define PYTHON_MAJOR_MINOR "@PYTHON_MAJOR_MINOR@"
11
+
12
+ #endif /* MAIN_CONFIG_H */