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,168 @@
1
+ r"""Authenticode signing hook for the Windows launcher (signing-windows).
2
+
3
+ v1 ships kivyforge's own binaries and the default artifact **unsigned**, but the
4
+ hook is first-class and optional from day one. Identity is always a
5
+ certificate-store **thumbprint** in the configured ``store_scope`` — never a
6
+ ``.pfx`` path/password — so the same store ``signtool`` uses is the one the
7
+ doctor certificate check inspects.
8
+
9
+ Two backends behind a single :class:`Signer` protocol:
10
+
11
+ * :class:`NullSigner` — the unconfigured default; leaves the artifact unsigned.
12
+ * :class:`SigntoolSigner` — ``signtool sign /sha1 <thumbprint> /fd SHA256
13
+ /tr <timestamp_url> /td SHA256`` (adding ``/sm`` for ``store_scope=machine``),
14
+ always RFC-3161 timestamped.
15
+
16
+ Signing runs on the ``dist/windows`` copy only, **after** the resource patch
17
+ (a resource edit invalidates any signature) and **before** any external
18
+ installer. Only the launcher ``.exe`` is signed; payload DLLs/``.pyd``s are
19
+ **not** signed (not planned — onedir keeps it possible if ever needed).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import shutil
25
+ import subprocess
26
+ from collections.abc import Sequence
27
+ from dataclasses import dataclass
28
+ from pathlib import Path
29
+ from typing import Protocol, runtime_checkable
30
+
31
+ from packaging.version import InvalidVersion, Version
32
+
33
+ from . import WindowsBundleError
34
+
35
+ # Windows version resources are four unsigned 16-bit integers.
36
+ _U16_MAX = 0xFFFF
37
+
38
+ # The fourth numeric field folds PEP 440 pre/post/dev into a single monotonic
39
+ # 16-bit value so ordering is preserved: dev < alpha < beta < rc < final < post.
40
+ # The string ProductVersion field always carries the full, human PEP 440 string.
41
+ _PRE_TIER = {"a": 10000, "b": 20000, "rc": 30000}
42
+ _FINAL_TIER = 40000
43
+ _POST_TIER = 50000
44
+
45
+
46
+ def _clamp(value: int, hi: int = _U16_MAX) -> int:
47
+ return max(0, min(value, hi))
48
+
49
+
50
+ def pep440_to_file_version(version: str) -> str:
51
+ """Map a PEP 440 *version* to a deterministic ``a.b.c.d`` of four 16-bit ints.
52
+
53
+ ``a.b.c`` are ``release[0:3]`` (missing parts are 0). ``d`` folds the
54
+ pre/post/dev segment so that, for the same ``a.b.c``:
55
+ ``devN`` (``0+N``) < ``aN`` (``10000+N``) < ``bN`` (``20000+N``) <
56
+ ``rcN`` (``30000+N``) < final (``40000``) < ``postN`` (``50000+N``).
57
+ The result is therefore monotonic **within a single epoch** only. Epoch is
58
+ not representable in the four-field resource (``major`` already holds
59
+ ``release[0]``) and is carried only by the string ProductVersion, so a
60
+ cross-epoch numeric comparison may not order; an unparseable version degrades
61
+ to ``0.0.0.0``.
62
+ """
63
+ try:
64
+ v = Version(version)
65
+ except InvalidVersion:
66
+ return "0.0.0.0"
67
+ release = v.release
68
+ a = _clamp(release[0] if len(release) > 0 else 0)
69
+ b = _clamp(release[1] if len(release) > 1 else 0)
70
+ c = _clamp(release[2] if len(release) > 2 else 0)
71
+ if v.pre is not None:
72
+ letter, n = v.pre
73
+ d = _PRE_TIER.get(letter, _PRE_TIER["rc"]) + _clamp(n, 9999)
74
+ elif v.post is not None:
75
+ d = _POST_TIER + _clamp(v.post, 9999)
76
+ elif v.dev is not None:
77
+ d = _clamp(v.dev, 9999)
78
+ else:
79
+ d = _FINAL_TIER
80
+ return f"{a}.{b}.{c}.{_clamp(d)}"
81
+
82
+
83
+ @runtime_checkable
84
+ class Signer(Protocol):
85
+ """Signs a set of PE files in place (Windows-only, new to the codebase)."""
86
+
87
+ def sign(self, paths: Sequence[Path]) -> None: ...
88
+
89
+ @property
90
+ def configured(self) -> bool: ...
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class NullSigner:
95
+ """The unconfigured default: signing is a no-op, artifact stays unsigned."""
96
+
97
+ configured: bool = False
98
+
99
+ def sign(self, paths: Sequence[Path]) -> None:
100
+ return
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class SigntoolSigner:
105
+ """Authenticode signing via ``signtool`` against a cert-store thumbprint."""
106
+
107
+ thumbprint: str
108
+ timestamp_url: str
109
+ store_scope: str
110
+ signtool: str = "signtool"
111
+ configured: bool = True
112
+
113
+ def command(self, paths: Sequence[Path]) -> list[str]:
114
+ """The exact ``signtool`` argv for *paths* (pure; unit-tested directly)."""
115
+ argv = [
116
+ self.signtool,
117
+ "sign",
118
+ "/sha1",
119
+ self.thumbprint,
120
+ "/fd",
121
+ "SHA256",
122
+ "/tr",
123
+ self.timestamp_url,
124
+ "/td",
125
+ "SHA256",
126
+ ]
127
+ # /sm selects the LocalMachine store; without it signtool uses CurrentUser.
128
+ if self.store_scope == "machine":
129
+ argv.append("/sm")
130
+ argv += [str(p) for p in paths]
131
+ return argv
132
+
133
+ def sign(self, paths: Sequence[Path]) -> None:
134
+ if not paths:
135
+ return
136
+ argv = self.command(paths)
137
+ exe = shutil.which(self.signtool) or self.signtool
138
+ argv[0] = exe
139
+ try:
140
+ proc = subprocess.run(argv, capture_output=True, text=True)
141
+ except OSError as exc:
142
+ raise WindowsBundleError(
143
+ f"failed to run signtool ({self.signtool}): {exc}. signtool ships "
144
+ "with the Windows SDK and is Windows-only."
145
+ ) from exc
146
+ if proc.returncode != 0:
147
+ raise WindowsBundleError(
148
+ f"signtool failed (exit {proc.returncode}):\n"
149
+ f"{(proc.stderr or proc.stdout).strip()}\n"
150
+ " Check the thumbprint, that the cert is in the configured "
151
+ "store_scope, and that the timestamp server is reachable."
152
+ )
153
+
154
+
155
+ def select_signer(signing, *, signtool: str = "signtool") -> Signer:
156
+ """Pick the signer for a ``[tool.kivy.windows.signing]`` config.
157
+
158
+ Returns a :class:`SigntoolSigner` when a thumbprint is configured, else a
159
+ :class:`NullSigner` (unsigned artifact — the v1 default).
160
+ """
161
+ if not signing.configured:
162
+ return NullSigner()
163
+ return SigntoolSigner(
164
+ thumbprint=signing.thumbprint.replace(" ", "").upper(),
165
+ timestamp_url=signing.timestamp_url,
166
+ store_scope=signing.store_scope,
167
+ signtool=signtool,
168
+ )
@@ -0,0 +1,29 @@
1
+ rcedit (https://github.com/electron/rcedit) — MIT License
2
+
3
+ kivyforge vendors the prebuilt `rcedit-x64.exe` (v2.0.0) to patch the per-app
4
+ icon and version resources into the launcher on Windows. It is redistributed
5
+ unmodified under the terms below; the SHA-256 is pinned in this directory's
6
+ SHA256SUMS and re-fetched/verified by `fetch_rcedit.py`.
7
+
8
+ ----------------------------------------------------------------------
9
+
10
+ Copyright (c) 2013 GitHub Inc.
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining
13
+ a copy of this software and associated documentation files (the
14
+ "Software"), to deal in the Software without restriction, including
15
+ without limitation the rights to use, copy, modify, merge, publish,
16
+ distribute, sublicense, and/or sell copies of the Software, and to
17
+ permit persons to whom the Software is furnished to do so, subject to
18
+ the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be
21
+ included in all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ Vendored Windows binaries — provenance
2
+ =======================================
3
+
4
+ launcher-amd64.exe
5
+ kivyforge's OWN onedir launcher. Built from kivyforge/platforms/windows/
6
+ launcher/launcher.c by that directory's build_launcher.py (MSVC, /Brepro
7
+ deterministic). Covered by kivyforge's own license. Nothing is vendored from
8
+ distlib/simple_launcher — that is a design reference only. The binary appends
9
+ nothing (no zip/timestamp tail), so it is byte-reproducible from source; CI
10
+ rebuilds it and byte-compares against this vendored copy.
11
+
12
+ rcedit-x64.exe
13
+ Third-party: electron/rcedit v2.0.0 (MIT), redistributed unmodified to patch
14
+ the per-app icon and version resources into the launcher. See LICENSE.rcedit.
15
+ Source: https://github.com/electron/rcedit/releases/tag/v2.0.0
16
+
17
+ Both binaries are SHA-256-pinned in SHA256SUMS and verified at use time.
@@ -0,0 +1,4 @@
1
+ # SHA-256 of vendored Windows binaries (kivyforge).
2
+ # Regenerate the launcher with: python -m kivyforge.platforms.windows.launcher.build_launcher build
3
+ 2b7f31aa0d81d918cd9fbe1b6c7fa77580f0b72e0b44bb1e2a055a05db19d041 launcher-amd64.exe
4
+ 3e7801db1a5edbec91b49a24a094aad776cb4515488ea5a4ca2289c400eade2a rcedit-x64.exe
@@ -0,0 +1,4 @@
1
+ # MSVC VCTOOLSVERSION + Windows SDK used to build launcher-amd64.exe.
2
+ # Re-vendor via the `revendor_launcher` CI workflow when either drifts.
3
+ 14.51.36231
4
+ 10.0.26100.0
@@ -0,0 +1,12 @@
1
+ """Vendored prebuilt Windows binaries (windows-spec).
2
+
3
+ Contains the compiled onedir launcher (``launcher-amd64.exe``, built from the
4
+ in-repo C source by ``launcher/build_launcher.py``) and ``rcedit-x64.exe`` (the
5
+ resource-patch tool, re-fetched/verified by ``fetch_rcedit.py``). Both are
6
+ SHA-256-pinned in ``SHA256SUMS``; see ``LICENSE.rcedit`` and ``NOTICE`` for
7
+ provenance.
8
+ """
9
+
10
+ # arm64: add a second vendored launcher (launcher-arm64.exe) + its SHA256SUMS
11
+ # entry + a pyproject package-data glob. rcedit-x64.exe is reused (emulated on
12
+ # Arm64 Windows; it only edits PE resources). See arm64-windows.md §5, §10.
@@ -0,0 +1,97 @@
1
+ """Fetch + verify the vendored ``rcedit-x64.exe`` against its pinned SHA-256.
2
+
3
+ rcedit is a third-party prebuilt tool (electron/rcedit, MIT). Rather than trust
4
+ whatever is on disk, this script downloads the pinned release and verifies its
5
+ SHA-256 against ``SHA256SUMS`` before writing it, so refreshing the vendored
6
+ binary is reproducible and tamper-evident. Run it when bumping the pinned
7
+ version (update ``RCEDIT_VERSION`` + the manifest hash together).
8
+
9
+ python -m kivyforge.platforms.windows.vendor.fetch_rcedit # fetch+verify
10
+ python -m kivyforge.platforms.windows.vendor.fetch_rcedit --check # verify only
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import hashlib
17
+ import sys
18
+ import urllib.request
19
+ from pathlib import Path
20
+
21
+ HERE = Path(__file__).resolve().parent
22
+ RCEDIT_NAME = "rcedit-x64.exe"
23
+ RCEDIT_PATH = HERE / RCEDIT_NAME
24
+ MANIFEST = HERE / "SHA256SUMS"
25
+
26
+ RCEDIT_VERSION = "2.0.0"
27
+ RCEDIT_URL = (
28
+ f"https://github.com/electron/rcedit/releases/download/v{RCEDIT_VERSION}/"
29
+ f"{RCEDIT_NAME}"
30
+ )
31
+
32
+
33
+ class RceditFetchError(Exception):
34
+ """rcedit could not be fetched or failed its SHA-256 verification."""
35
+
36
+
37
+ def pinned_sha256() -> str:
38
+ if not MANIFEST.is_file():
39
+ raise RceditFetchError(f"manifest missing: {MANIFEST}")
40
+ for line in MANIFEST.read_text(encoding="utf-8").splitlines():
41
+ line = line.strip()
42
+ if not line or line.startswith("#"):
43
+ continue
44
+ digest, _, name = line.partition(" ")
45
+ if name.strip() == RCEDIT_NAME:
46
+ return digest.strip()
47
+ raise RceditFetchError(f"{RCEDIT_NAME} has no pinned SHA-256 in {MANIFEST}.")
48
+
49
+
50
+ def _sha256(data: bytes) -> str:
51
+ return hashlib.sha256(data).hexdigest()
52
+
53
+
54
+ def verify_only() -> int:
55
+ if not RCEDIT_PATH.is_file():
56
+ raise RceditFetchError(f"{RCEDIT_PATH} is missing; run without --check.")
57
+ actual = _sha256(RCEDIT_PATH.read_bytes())
58
+ expected = pinned_sha256()
59
+ if actual != expected:
60
+ raise RceditFetchError(
61
+ f"{RCEDIT_NAME} SHA-256 {actual} does not match the pin {expected}."
62
+ )
63
+ print(f"verified {RCEDIT_PATH} ({actual})")
64
+ return 0
65
+
66
+
67
+ def fetch() -> int:
68
+ expected = pinned_sha256()
69
+ with urllib.request.urlopen(RCEDIT_URL, timeout=60) as resp: # noqa: S310
70
+ data = resp.read()
71
+ actual = _sha256(data)
72
+ if actual != expected:
73
+ raise RceditFetchError(
74
+ f"downloaded {RCEDIT_NAME} SHA-256 {actual} does not match the pin "
75
+ f"{expected}; refusing to write. (Bump RCEDIT_VERSION + the manifest "
76
+ "together to update.)"
77
+ )
78
+ RCEDIT_PATH.write_bytes(data)
79
+ print(f"fetched {RCEDIT_PATH} ({actual})")
80
+ return 0
81
+
82
+
83
+ def main(argv: list[str] | None = None) -> int:
84
+ parser = argparse.ArgumentParser(description=__doc__)
85
+ parser.add_argument(
86
+ "--check", action="store_true", help="verify the on-disk binary only."
87
+ )
88
+ args = parser.parse_args(argv)
89
+ try:
90
+ return verify_only() if args.check else fetch()
91
+ except (RceditFetchError, OSError) as exc:
92
+ print(f"error: {exc}", file=sys.stderr)
93
+ return 1
94
+
95
+
96
+ if __name__ == "__main__":
97
+ raise SystemExit(main())
@@ -0,0 +1,139 @@
1
+ r"""Install the locked wheels into the bundled prefix — a no-pip scheme installer.
2
+
3
+ Wheels are fetched + SHA-256-verified from ``pylock.windows.toml`` (URL or
4
+ vendored path) and unpacked directly. This is a **Windows-only, deterministic
5
+ wheel-scheme installer**: it never resolves dependencies, never shells out to
6
+ the host ``pip``, and never applies the host interpreter's compatibility rules —
7
+ the lock already decided everything. Windows ships one arch (``amd64``) per
8
+ onedir, so per package a single wheel satisfies coverage: a pure-Python
9
+ (``py3-none-any``) wheel or a ``win_amd64`` wheel.
10
+
11
+ arm64: this staging logic is already arch-generic (it routes by
12
+ ``wheel_arch(tag)``), so only this docstring's "one arch (amd64)" /
13
+ "``win_amd64``" wording changes when ``win_arm64`` lands — no code change here.
14
+ See arm64-windows.md §9.
15
+
16
+ Scheme routing into the ``<bundle>\python`` prefix (the load-bearing part):
17
+
18
+ * package root + ``.data/{purelib,platlib}`` -> ``Lib\site-packages``
19
+ * ``.data/data`` -> the **prefix root** (so ``share\<dep>\bin`` lands at
20
+ ``<prefix>\share\<dep>\bin`` and survives) -- a site-packages-only copy
21
+ silently drops every SDL DLL and Kivy dies importing its window provider. This
22
+ is the real DLL-discovery invariant; "pip is load-bearing" it is not.
23
+ * ``.data/scripts`` -> ``Scripts``
24
+ * ``.data/headers`` -> ``Include``
25
+
26
+ The shipped macOS/Linux ``_unpack`` is intentionally left untouched.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import shutil
32
+ import zipfile
33
+ from pathlib import Path
34
+
35
+ from kivyforge.artifacts.cache import ArtifactCache
36
+ from kivyforge.artifacts.download import DownloadError, fetch_artifact
37
+ from kivyforge.lock.model import LockedPackage, LockedWheel
38
+
39
+ from . import WindowsBundleError
40
+ from .lock import wheel_arch
41
+
42
+
43
+ def stage_wheels(
44
+ packages: tuple[LockedPackage, ...],
45
+ arch: str,
46
+ prefix: Path,
47
+ *,
48
+ project_root: Path,
49
+ cache: ArtifactCache | None = None,
50
+ no_cache: bool = False,
51
+ ) -> None:
52
+ """Install every package's wheel for *arch* into the *prefix* schemes.
53
+
54
+ *prefix* is the staged CPython home (``<bundle>\\python``). Its existing
55
+ ``Lib\\site-packages`` (from the runtime archive) is preserved and installed
56
+ into; the runtime must already be staged.
57
+ """
58
+ cache = cache or ArtifactCache()
59
+ site_packages = prefix / "Lib" / "site-packages"
60
+ site_packages.mkdir(parents=True, exist_ok=True)
61
+
62
+ for pkg in packages:
63
+ wheel = _select_wheel(pkg, arch)
64
+ _install(_fetch(wheel, project_root, cache, no_cache), prefix, site_packages)
65
+
66
+
67
+ def _select_wheel(pkg: LockedPackage, arch: str) -> LockedWheel:
68
+ """Pick the wheel to install: pure-Python, else the one covering *arch*."""
69
+ for wheel in pkg.wheels:
70
+ if wheel.is_pure_python:
71
+ return wheel
72
+ for wheel in pkg.wheels:
73
+ if any(wheel_arch(sub) == arch for sub in wheel.platform_tag.split(".")):
74
+ return wheel
75
+ raise WindowsBundleError(
76
+ f"{pkg.name} {pkg.version} has no wheel for arch {arch} in the lock.\n"
77
+ " Re-run `kivyforge lock -p windows`."
78
+ )
79
+
80
+
81
+ def _fetch(wheel: LockedWheel, project_root, cache, no_cache) -> Path:
82
+ try:
83
+ return fetch_artifact(
84
+ name=wheel.name,
85
+ sha256=wheel.sha256,
86
+ filename=wheel.name,
87
+ url=wheel.url,
88
+ path=wheel.path,
89
+ project_root=project_root,
90
+ cache=cache,
91
+ no_cache=no_cache,
92
+ )
93
+ except DownloadError as exc:
94
+ raise WindowsBundleError(str(exc)) from exc
95
+
96
+
97
+ def _install(wheel: Path, prefix: Path, site_packages: Path) -> None:
98
+ """Unpack *wheel*, routing each install scheme to its prefix location."""
99
+ with zipfile.ZipFile(wheel) as zf:
100
+ _safe_extract(zf, site_packages)
101
+ # Route the .data schemes out of site-packages to their prefix locations.
102
+ # purelib/platlib stay in site-packages; the rest move relative to prefix.
103
+ scheme_targets = {
104
+ "purelib": site_packages,
105
+ "platlib": site_packages,
106
+ "data": prefix,
107
+ "scripts": prefix / "Scripts",
108
+ "headers": prefix / "Include",
109
+ }
110
+ for data_dir in list(site_packages.glob("*.data")):
111
+ for sub in sorted(p.name for p in data_dir.iterdir() if p.is_dir()):
112
+ target = scheme_targets.get(sub)
113
+ if target is None:
114
+ # An unknown scheme subdir: route to the prefix root by name so
115
+ # nothing declared in the wheel is silently dropped.
116
+ target = prefix / sub
117
+ _merge_tree(data_dir / sub, target)
118
+ continue
119
+ _merge_tree(data_dir / sub, target)
120
+ shutil.rmtree(data_dir, ignore_errors=True)
121
+
122
+
123
+ def _merge_tree(src: Path, dest: Path) -> None:
124
+ dest.mkdir(parents=True, exist_ok=True)
125
+ for item in src.iterdir():
126
+ target = dest / item.name
127
+ if item.is_dir():
128
+ _merge_tree(item, target)
129
+ else:
130
+ shutil.copy2(item, target)
131
+
132
+
133
+ def _safe_extract(zf: zipfile.ZipFile, target: Path) -> None:
134
+ base = target.resolve()
135
+ for member in zf.namelist():
136
+ resolved = (target / member).resolve()
137
+ if not (resolved == base or base in resolved.parents):
138
+ raise WindowsBundleError(f"unsafe path in wheel: {member!r}")
139
+ zf.extractall(target) # noqa: S202 — members validated just above