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,112 @@
1
+ """macOS lock stack (``pylock.macos.toml``).
2
+
3
+ A thin macOS *profile* (:class:`MacosProfile`) over the generic wheel+runtime
4
+ lock engine (``kivyforge.lock.wheelruntime``). This module is the stable macOS
5
+ public surface: it binds the profile into the core and re-exports the engine's
6
+ types under macOS-friendly names so callers (the CLI, tests) never reach into the
7
+ generic package directly.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Callable
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+
16
+ from kivyforge.config.model import Config
17
+ from kivyforge.lock.wheelruntime import (
18
+ PythonRuntime as MacosPythonRuntime,
19
+ )
20
+ from kivyforge.lock.wheelruntime import (
21
+ ResolvedPackage,
22
+ ResolvedWheel,
23
+ RuntimeArtifact,
24
+ RuntimeProvider,
25
+ RuntimeProviderError,
26
+ Variant,
27
+ WheelResolver,
28
+ WheelResolverError,
29
+ WheelRuntimeBuildError,
30
+ WheelRuntimeLock,
31
+ build_wheel_runtime_lock,
32
+ diff_summary,
33
+ get_wheel_resolver,
34
+ semantic_equal,
35
+ )
36
+ from kivyforge.lock.wheelruntime import serialize as _ser
37
+
38
+ from .profile import MacosProfile, macos_platform_tag, wheel_arch
39
+ from .runtime import PythonBuildStandaloneProvider
40
+
41
+ # macOS-friendly aliases for the generic engine types.
42
+ MacosLockfile = WheelRuntimeLock
43
+ MacosBuildError = WheelRuntimeBuildError
44
+ MacosResolverError = WheelResolverError
45
+
46
+ _PROFILE = MacosProfile()
47
+
48
+ __all__ = [
49
+ "MacosBuildError",
50
+ "MacosLockfile",
51
+ "MacosProfile",
52
+ "MacosPythonRuntime",
53
+ "MacosResolverError",
54
+ "PythonBuildStandaloneProvider",
55
+ "ResolvedPackage",
56
+ "ResolvedWheel",
57
+ "RuntimeArtifact",
58
+ "RuntimeProvider",
59
+ "RuntimeProviderError",
60
+ "Variant",
61
+ "WheelResolver",
62
+ "build_macos_lockfile",
63
+ "diff_summary",
64
+ "dumps",
65
+ "get_macos_resolver",
66
+ "load",
67
+ "loads",
68
+ "macos_platform_tag",
69
+ "semantic_equal",
70
+ "wheel_arch",
71
+ ]
72
+
73
+
74
+ def build_macos_lockfile(
75
+ config: Config,
76
+ pyproject_text: str,
77
+ *,
78
+ project_root: Path | None = None,
79
+ resolver: WheelResolver | None = None,
80
+ runtime_provider: RuntimeProvider | None = None,
81
+ offline: bool = False,
82
+ now: datetime | None = None,
83
+ on_warning: Callable[[str], None] | None = None,
84
+ ) -> MacosLockfile:
85
+ return build_wheel_runtime_lock(
86
+ _PROFILE,
87
+ config,
88
+ pyproject_text,
89
+ project_root=project_root,
90
+ resolver=resolver,
91
+ runtime_provider=runtime_provider,
92
+ native_binaries=config.macos.binaries if config.macos else (),
93
+ offline=offline,
94
+ now=now,
95
+ on_warning=on_warning,
96
+ )
97
+
98
+
99
+ def dumps(lock: MacosLockfile) -> str:
100
+ return _ser.dumps(lock)
101
+
102
+
103
+ def loads(text: str) -> MacosLockfile:
104
+ return _ser.loads(text, platform="macos")
105
+
106
+
107
+ def load(path: str | Path) -> MacosLockfile:
108
+ return _ser.load(path, platform="macos")
109
+
110
+
111
+ def get_macos_resolver(backend: str = "pip", **kwargs) -> WheelResolver:
112
+ return get_wheel_resolver(backend, **kwargs)
@@ -0,0 +1,119 @@
1
+ """macOS lock profile — the platform-specific inputs to the wheel+runtime core.
2
+
3
+ macOS builds target ``arm64`` only, but wheel *tags* are a wider set: a
4
+ ``universal2`` wheel covers arm64 and is accepted, and ``x86_64`` tags are still
5
+ parsed (they simply never match). Keeping ``VALID_WHEEL_ARCHS`` wider than the
6
+ build-target set is deliberate — narrowing it would reject most macOS wheels on
7
+ PyPI. The bundled runtime comes from python-build-standalone (darwin triples).
8
+ Everything else — resolve/serialize/build orchestration — is the shared core.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+
15
+ from kivyforge.config.errors import ConfigError
16
+ from kivyforge.config.model import Config
17
+ from kivyforge.lock.wheelruntime.profile import PlatformLockProfile
18
+ from kivyforge.lock.wheelruntime.resolver import Variant
19
+ from kivyforge.lock.wheelruntime.runtime import RuntimeProvider
20
+
21
+ from .runtime import PythonBuildStandaloneProvider
22
+
23
+ # Default macOS deployment floor for the pip ``--platform`` request when the
24
+ # project sets no ``minimum_system_version``. pip matches wheels tagged at or
25
+ # below this floor (plus universal2), maximizing compatible wheels.
26
+ DEFAULT_MACOS_FLOOR = "11.0"
27
+
28
+ VALID_WHEEL_ARCHS = frozenset({"arm64", "x86_64", "universal2"})
29
+
30
+
31
+ def macos_platform_tag(floor: str, arch: str) -> str:
32
+ """The pip ``--platform`` tag for a macOS deployment floor + arch."""
33
+ return f"macosx_{floor.replace('.', '_')}_{arch}"
34
+
35
+
36
+ def wheel_arch(platform_tag: str) -> str | None:
37
+ """The architecture a macOS wheel platform tag targets, or ``None``.
38
+
39
+ ``macosx_11_0_arm64`` -> ``arm64``; ``macosx_11_0_x86_64`` -> ``x86_64``;
40
+ ``..._universal2`` -> ``universal2``. The arch is what follows the
41
+ ``macosx_<major>_<minor>_`` prefix (``x86_64`` itself has an underscore, so a
42
+ naive rsplit is wrong).
43
+ """
44
+ match = re.fullmatch(r"macosx_\d+_\d+_(.+)", platform_tag)
45
+ if match is None:
46
+ return None
47
+ arch = match.group(1)
48
+ return arch if arch in VALID_WHEEL_ARCHS else None
49
+
50
+
51
+ class MacosProfile(PlatformLockProfile):
52
+ platform = "macos"
53
+
54
+ def overlay(self, config: Config):
55
+ return config.macos
56
+
57
+ def missing_overlay_error(self) -> str:
58
+ return "pyproject.toml has no [tool.kivy.macos] table; nothing to lock."
59
+
60
+ def python_version(self, config: Config) -> str:
61
+ # [tool.kivy.macos.python].version is required (the loader raises a
62
+ # ConfigError when it is missing), so it is always set here. Never
63
+ # silently substitute a hidden default — that would pin an unexpected
64
+ # (and possibly unreleased) Python instead of surfacing the misconfig.
65
+ version = config.macos_required.python_version
66
+ if not version:
67
+ raise ConfigError(
68
+ "missing required [tool.kivy.macos.python].version",
69
+ key_path="tool.kivy.macos.python.version",
70
+ )
71
+ return version
72
+
73
+ def _floor(self, config: Config) -> str:
74
+ return config.macos_required.minimum_system_version or DEFAULT_MACOS_FLOOR
75
+
76
+ def variants(self, config: Config) -> tuple[Variant, ...]:
77
+ floor = self._floor(config)
78
+ return tuple(
79
+ Variant(arch=arch, platform_tag=macos_platform_tag(floor, arch))
80
+ for arch in config.macos_required.archs
81
+ )
82
+
83
+ def wheel_covers(self, platform_tag: str, archs: tuple[str, ...]) -> set[str]:
84
+ arch = wheel_arch(platform_tag)
85
+ if arch == "universal2":
86
+ return set(archs)
87
+ if arch in archs:
88
+ return {arch}
89
+ return set()
90
+
91
+ def runtime_provider(self, config: Config) -> RuntimeProvider:
92
+ return PythonBuildStandaloneProvider()
93
+
94
+ def declared_floor(self, config: Config) -> str | None:
95
+ return config.macos_required.minimum_system_version
96
+
97
+ def coverage_error(self, name: str, missing: list[str]) -> str:
98
+ return (
99
+ f"{name} is missing macOS wheel(s) for arch(es): {', '.join(missing)}.\n"
100
+ f" A compiled package must publish an arm64 or universal2 wheel to "
101
+ f"be locked reproducibly.\n"
102
+ f" Intel-only wheels cannot be used: kivyforge builds arm64-only "
103
+ f"macOS apps."
104
+ )
105
+
106
+ def floor_error(self, declared: str, runtime_floor: str, version: str) -> str:
107
+ return (
108
+ f"minimum_system_version {declared} is below the macOS {runtime_floor} "
109
+ f"floor required by the Python {version} runtime.\n"
110
+ f" Raise [tool.kivy.macos].minimum_system_version to at least "
111
+ f"{runtime_floor}."
112
+ )
113
+
114
+ def wheel_scope_hint(self) -> str:
115
+ return (
116
+ "Vendored wheels must live under the project directory, a sibling "
117
+ "directory, or the enclosing repository (e.g. a wheels/ directory "
118
+ "beside pyproject.toml)."
119
+ )
@@ -0,0 +1,58 @@
1
+ """macOS runtime provider — python-build-standalone with darwin triples.
2
+
3
+ A one-line specialization of the generic :class:`PbsProvider`: it fixes the
4
+ arch→target-triple map to macOS's, and otherwise inherits the shared PBS
5
+ resolution + injectable metadata fetcher.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from kivyforge.lock.wheelruntime.runtime import (
11
+ MetadataFetcher,
12
+ PbsProvider,
13
+ ReleaseAsset,
14
+ RuntimeProvider,
15
+ RuntimeProviderError,
16
+ pbs_asset_glob,
17
+ )
18
+
19
+ __all__ = [
20
+ "DARWIN_TRIPLES",
21
+ "PythonBuildStandaloneProvider",
22
+ "ReleaseAsset",
23
+ "RuntimeProvider",
24
+ "RuntimeProviderError",
25
+ "get_runtime_provider",
26
+ "pbs_asset_name",
27
+ ]
28
+
29
+ # kivyforge arch name -> PBS/LLVM darwin target triple.
30
+ DARWIN_TRIPLES = {
31
+ "arm64": "aarch64-apple-darwin",
32
+ "x86_64": "x86_64-apple-darwin",
33
+ }
34
+
35
+ # macOS-facing alias for the generic PBS asset glob helper.
36
+ pbs_asset_name = pbs_asset_glob
37
+
38
+
39
+ class PythonBuildStandaloneProvider(PbsProvider):
40
+ """PBS provider fixed to macOS darwin triples."""
41
+
42
+ def __init__(
43
+ self,
44
+ metadata_fetcher: MetadataFetcher | None = None,
45
+ *,
46
+ floor: str | None = None,
47
+ ) -> None:
48
+ super().__init__(DARWIN_TRIPLES, metadata_fetcher, floor=floor)
49
+
50
+
51
+ def get_runtime_provider(
52
+ name: str = "python-build-standalone", **kwargs
53
+ ) -> RuntimeProvider:
54
+ if name in ("python-build-standalone", "pbs", "python_build_standalone"):
55
+ return PythonBuildStandaloneProvider(**kwargs)
56
+ raise RuntimeProviderError(
57
+ f"unknown runtime provider {name!r} (expected python-build-standalone)"
58
+ )
@@ -0,0 +1,138 @@
1
+ """Thin wrappers over the macOS binary tools the bundler needs.
2
+
3
+ ``lipo -archs`` (read a Mach-O's architectures), ``codesign`` (ad-hoc signing —
4
+ the mandatory Apple-Silicon floor), and Mach-O detection (so we only ever sign
5
+ actual binaries, not text/data files). Kept isolated + injectable-friendly so
6
+ the higher-level bundler logic stays testable and these are the only lines that
7
+ shell out to Apple tools.
8
+
9
+ There is no ``lipo -create`` wrapper: macOS builds are arm64-only, so nothing
10
+ is ever merged into a universal binary.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import struct
16
+ import subprocess
17
+ import time
18
+ from pathlib import Path
19
+
20
+ from . import AppBundleError
21
+
22
+ # codesign, when signing with a real (keychain-backed) identity, intermittently
23
+ # fails with errSecInternalComponent under rapid successive invocations — a
24
+ # long-standing macOS securityd flakiness (not a config problem; ad-hoc signing
25
+ # uses no keychain and never hits it). A short retry with backoff reliably
26
+ # clears it; see e.g. https://developer.apple.com/forums/thread/700333.
27
+ _CODESIGN_TRANSIENT_ERRORS = ("errSecInternalComponent",)
28
+ _CODESIGN_MAX_ATTEMPTS = 5
29
+ _CODESIGN_RETRY_DELAY_S = 1.0
30
+
31
+ # Mach-O magic numbers (little/big-endian, 32/64-bit) and the fat/universal
32
+ # magics. A file that starts with any of these is a Mach-O we may lipo/sign.
33
+ _MACHO_MAGICS = frozenset(
34
+ {
35
+ 0xFEEDFACE, # MH_MAGIC (32-bit)
36
+ 0xCEFAEDFE, # MH_CIGAM
37
+ 0xFEEDFACF, # MH_MAGIC_64
38
+ 0xCFFAEDFE, # MH_CIGAM_64
39
+ 0xCAFEBABE, # FAT_MAGIC (universal)
40
+ 0xBEBAFECA, # FAT_CIGAM
41
+ }
42
+ )
43
+
44
+
45
+ def is_macho(path: Path) -> bool:
46
+ """True if *path* is a regular file whose first 4 bytes are a Mach-O magic.
47
+
48
+ Note ``0xCAFEBABE`` is also the Java class-file magic, but the bundler only
49
+ ever walks a CPython tree + installed wheels, where it unambiguously means a
50
+ universal Mach-O.
51
+ """
52
+ if not path.is_file() or path.is_symlink():
53
+ return False
54
+ try:
55
+ with path.open("rb") as fh:
56
+ head = fh.read(4)
57
+ except OSError:
58
+ return False
59
+ if len(head) < 4:
60
+ return False
61
+ return struct.unpack(">I", head)[0] in _MACHO_MAGICS
62
+
63
+
64
+ def macho_arches(path: Path) -> tuple[str, ...]:
65
+ """The architectures present in a Mach-O, via ``lipo -archs`` (empty on error)."""
66
+ proc = _run(["lipo", "-archs", str(path)], check=False)
67
+ if proc.returncode != 0:
68
+ return ()
69
+ return tuple(proc.stdout.split())
70
+
71
+
72
+ def codesign_adhoc(path: Path) -> None:
73
+ """Ad-hoc sign a single Mach-O / bundle in place (``codesign --sign -``)."""
74
+ _run(["codesign", "--force", "--sign", "-", "--timestamp=none", str(path)])
75
+
76
+
77
+ def codesign_identity(
78
+ path: Path, identity: str, *, entitlements: Path | None = None
79
+ ) -> None:
80
+ """Developer ID sign a Mach-O / bundle with Hardened Runtime + timestamp.
81
+
82
+ ``--options runtime`` (Hardened Runtime) and a secure ``--timestamp`` are
83
+ both notarization requirements. *entitlements* only applies to the main
84
+ executable, so callers pass it when sealing the ``.app`` (and the launcher),
85
+ not for nested dylib/so binaries.
86
+ """
87
+ cmd = [
88
+ "codesign",
89
+ "--force",
90
+ "--sign",
91
+ identity,
92
+ "--options",
93
+ "runtime",
94
+ "--timestamp",
95
+ ]
96
+ if entitlements is not None:
97
+ cmd += ["--entitlements", str(entitlements)]
98
+ cmd.append(str(path))
99
+ _run_with_retry(cmd)
100
+
101
+
102
+ def codesign_verify(path: Path) -> bool:
103
+ """True if *path* has a valid signature (``codesign --verify``)."""
104
+ return _run(["codesign", "--verify", str(path)], check=False).returncode == 0
105
+
106
+
107
+ def _run_with_retry(cmd: list[str]) -> None:
108
+ """Run *cmd* (a codesign invocation), retrying transient keychain flakiness.
109
+
110
+ Only retries errors matching ``_CODESIGN_TRANSIENT_ERRORS``; anything else
111
+ (a genuinely missing/expired identity, a bad entitlements plist, ...)
112
+ raises immediately.
113
+ """
114
+ for attempt in range(1, _CODESIGN_MAX_ATTEMPTS + 1):
115
+ try:
116
+ _run(cmd)
117
+ return
118
+ except AppBundleError as exc:
119
+ transient = any(m in str(exc) for m in _CODESIGN_TRANSIENT_ERRORS)
120
+ if not transient or attempt == _CODESIGN_MAX_ATTEMPTS:
121
+ raise
122
+ time.sleep(_CODESIGN_RETRY_DELAY_S * attempt)
123
+
124
+
125
+ def _run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess:
126
+ try:
127
+ proc = subprocess.run(cmd, capture_output=True, text=True)
128
+ except FileNotFoundError as exc:
129
+ raise AppBundleError(
130
+ f"required macOS tool {cmd[0]!r} not found.\n"
131
+ " Install the Xcode command-line tools: xcode-select --install"
132
+ ) from exc
133
+ if check and proc.returncode != 0:
134
+ raise AppBundleError(
135
+ f"{cmd[0]} failed ({proc.returncode}): "
136
+ f"{(proc.stderr or proc.stdout).strip()}"
137
+ )
138
+ return proc
@@ -0,0 +1,47 @@
1
+ """Stage locked native binaries into the ``.app``'s ``Resources/bin`` (macos-spec).
2
+
3
+ The ``[tool.kivy.macos.native.binaries]`` channel: each entry is fetched +
4
+ SHA-256-verified from ``pylock.macos.toml`` (URL or vendored path), then staged
5
+ into ``Contents/Resources/bin`` — a single native file copied in with its exec
6
+ bit set, or a ``.zip`` / ``.tar.gz`` / ``.tgz`` of several extracted with a
7
+ path-traversal guard. The directory is created only when the lock actually pins
8
+ binaries (no empty ``bin/``). The bundle's signing sweep later covers everything
9
+ under it.
10
+
11
+ The staging mechanics live in the shared
12
+ :mod:`kivyforge.artifacts.native_stage_util`; this module is the macOS-facing
13
+ wrapper that translates the neutral :class:`NativeStageError` into
14
+ ``AppBundleError``.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pathlib import Path
20
+
21
+ from kivyforge.artifacts.cache import ArtifactCache
22
+ from kivyforge.artifacts.native_stage_util import NativeStageError, stage_binaries
23
+
24
+ from . import AppBundleError
25
+ from .lock import MacosLockfile
26
+
27
+
28
+ def stage_native_binaries(
29
+ lock: MacosLockfile,
30
+ resources: Path,
31
+ *,
32
+ project_root: Path,
33
+ cache: ArtifactCache | None = None,
34
+ no_cache: bool = False,
35
+ ) -> None:
36
+ """Stage every pinned native binary for the lock into ``resources/bin``."""
37
+ try:
38
+ stage_binaries(
39
+ lock.native_binaries,
40
+ resources,
41
+ project_root=project_root,
42
+ cache=cache,
43
+ no_cache=no_cache,
44
+ bin_label="Contents/Resources/bin",
45
+ )
46
+ except NativeStageError as exc:
47
+ raise AppBundleError(str(exc)) from exc
@@ -0,0 +1,143 @@
1
+ """Notarize + staple the signed ``.app`` (macos-spec, Developer ID workstream).
2
+
3
+ The distribution path since macOS 10.15 is **sign + notarize + staple**: zip the
4
+ Developer-ID-signed ``.app`` (``ditto -c -k --keepParent`` preserves the bundle
5
+ metadata Gatekeeper checks), submit it with ``xcrun notarytool submit --wait``,
6
+ and on acceptance ``xcrun stapler staple`` the ticket into the bundle so
7
+ Gatekeeper trusts it offline.
8
+
9
+ Credentials come from a **keychain profile** created once with::
10
+
11
+ xcrun notarytool store-credentials <profile> \
12
+ --apple-id <you@example.com> --team-id <TEAMID> --password <app-specific>
13
+
14
+ so nothing secret ever lives in ``pyproject.toml`` (CI keychains work the same
15
+ way). ``.dmg``-level notarization stays external — kivyforge never builds
16
+ ``.dmg`` files (see common packaging scope).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import subprocess
23
+ import tempfile
24
+ from pathlib import Path
25
+
26
+ import click
27
+
28
+ from . import AppBundleError
29
+
30
+
31
+ def notarize_and_staple(app: Path, *, profile: str, echo=click.echo) -> None:
32
+ """Submit *app* to Apple's notary service and staple the ticket.
33
+
34
+ Blocks until the notary service responds (``--wait``; typically a couple of
35
+ minutes). On rejection, fetches the notary log and surfaces the per-file
36
+ issues in the error message.
37
+ """
38
+ with tempfile.TemporaryDirectory(prefix="kivyforge-notarize-") as tmp:
39
+ archive = Path(tmp) / f"{app.stem}.zip"
40
+ echo(f"Zipping {app.name} for notarization ...")
41
+ _run(["ditto", "-c", "-k", "--keepParent", str(app), str(archive)])
42
+
43
+ echo("Submitting to the Apple notary service (this can take minutes) ...")
44
+ proc = _run(
45
+ [
46
+ "xcrun",
47
+ "notarytool",
48
+ "submit",
49
+ str(archive),
50
+ "--keychain-profile",
51
+ profile,
52
+ "--wait",
53
+ "--output-format",
54
+ "json",
55
+ ],
56
+ check=False,
57
+ )
58
+ submission_id, status = _parse_submission(proc.stdout)
59
+ if proc.returncode != 0 and status is None:
60
+ raise AppBundleError(
61
+ f"notarytool submit failed ({proc.returncode}): "
62
+ f"{(proc.stderr or proc.stdout).strip()}\n"
63
+ f" Check the keychain profile: `xcrun notarytool history "
64
+ f"--keychain-profile {profile}`."
65
+ )
66
+ if status != "Accepted":
67
+ raise AppBundleError(_rejection_message(submission_id, status, profile))
68
+ echo(f"Notarization accepted (submission {submission_id}).")
69
+
70
+ echo("Stapling the notarization ticket ...")
71
+ _run(["xcrun", "stapler", "staple", str(app)])
72
+
73
+
74
+ def _parse_submission(stdout: str) -> tuple[str | None, str | None]:
75
+ """(submission id, status) from ``notarytool --output-format json`` output."""
76
+ try:
77
+ data = json.loads(stdout or "{}")
78
+ except json.JSONDecodeError:
79
+ return None, None
80
+ return data.get("id"), data.get("status")
81
+
82
+
83
+ def _rejection_message(
84
+ submission_id: str | None, status: str | None, profile: str
85
+ ) -> str:
86
+ lines = [f"notarization was not accepted (status: {status or 'unknown'})."]
87
+ if submission_id:
88
+ log = _notary_log(submission_id, profile)
89
+ if log:
90
+ lines.append(f" Notary log issues:\n{log}")
91
+ else:
92
+ lines.append(
93
+ f" Fetch the log: `xcrun notarytool log {submission_id} "
94
+ f"--keychain-profile {profile}`."
95
+ )
96
+ lines.append(
97
+ " Common causes: a binary missed Hardened Runtime / timestamp signing, "
98
+ "or the identity is not a Developer ID Application certificate."
99
+ )
100
+ return "\n".join(lines)
101
+
102
+
103
+ def _notary_log(submission_id: str, profile: str) -> str:
104
+ """The notary log's per-file issues, indented; empty string on any failure."""
105
+ proc = _run(
106
+ [
107
+ "xcrun",
108
+ "notarytool",
109
+ "log",
110
+ submission_id,
111
+ "--keychain-profile",
112
+ profile,
113
+ ],
114
+ check=False,
115
+ )
116
+ if proc.returncode != 0:
117
+ return ""
118
+ try:
119
+ issues = json.loads(proc.stdout).get("issues") or []
120
+ except json.JSONDecodeError:
121
+ return ""
122
+ lines = [
123
+ f" {issue.get('severity', '?')}: {issue.get('path', '?')}: "
124
+ f"{issue.get('message', '?')}"
125
+ for issue in issues
126
+ ]
127
+ return "\n".join(lines)
128
+
129
+
130
+ def _run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess:
131
+ try:
132
+ proc = subprocess.run(cmd, capture_output=True, text=True)
133
+ except FileNotFoundError as exc:
134
+ raise AppBundleError(
135
+ f"required macOS tool {cmd[0]!r} not found.\n"
136
+ " Install the Xcode command-line tools: xcode-select --install"
137
+ ) from exc
138
+ if check and proc.returncode != 0:
139
+ raise AppBundleError(
140
+ f"{cmd[0]} failed ({proc.returncode}): "
141
+ f"{(proc.stderr or proc.stdout).strip()}"
142
+ )
143
+ return proc
@@ -0,0 +1,37 @@
1
+ """Build the ``.app`` ``Info.plist`` from the project config (macos-spec).
2
+
3
+ kivyforge owns the identity/versioning keys (mirroring the iOS managed-plist
4
+ discipline); there is no user Info.plist merge on macOS in this phase.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from kivyforge.config.model import Config
10
+
11
+ # LSMinimumSystemVersion when the project sets none. Matches the macOS lock's
12
+ # default pip floor so the plist and the resolved wheels agree.
13
+ DEFAULT_MINIMUM_SYSTEM_VERSION = "11.0"
14
+
15
+
16
+ def build_info_plist(config: Config, *, executable: str, icon_file: str | None) -> dict:
17
+ macos = config.macos_required
18
+ plist: dict[str, object] = {
19
+ "CFBundleName": config.display_name,
20
+ "CFBundleDisplayName": config.display_name,
21
+ "CFBundleIdentifier": macos.bundle_id,
22
+ "CFBundleExecutable": executable,
23
+ "CFBundlePackageType": "APPL",
24
+ "CFBundleInfoDictionaryVersion": "6.0",
25
+ "CFBundleShortVersionString": config.project.version,
26
+ "CFBundleVersion": str(macos.build),
27
+ "LSMinimumSystemVersion": (
28
+ macos.minimum_system_version or DEFAULT_MINIMUM_SYSTEM_VERSION
29
+ ),
30
+ "NSHighResolutionCapable": True,
31
+ # A GUI app (Kivy opens a window); without this Finder still launches it,
32
+ # but it is the correct declaration for a windowed, non-agent app.
33
+ "LSBackgroundOnly": False,
34
+ }
35
+ if icon_file is not None:
36
+ plist["CFBundleIconFile"] = icon_file
37
+ return plist