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.
- kivyforge/__init__.py +9 -0
- kivyforge/artifacts/__init__.py +41 -0
- kivyforge/artifacts/cache.py +81 -0
- kivyforge/artifacts/collect.py +354 -0
- kivyforge/artifacts/download.py +125 -0
- kivyforge/artifacts/frameworks.py +240 -0
- kivyforge/artifacts/native_stage_util.py +346 -0
- kivyforge/artifacts/runtime.py +65 -0
- kivyforge/artifacts/verify.py +45 -0
- kivyforge/artifacts/wheels.py +89 -0
- kivyforge/bundle/__init__.py +1 -0
- kivyforge/bundle/pycompile.py +161 -0
- kivyforge/cli/__init__.py +52 -0
- kivyforge/cli/_common.py +62 -0
- kivyforge/cli/_platform.py +84 -0
- kivyforge/cli/build.py +111 -0
- kivyforge/cli/clean.py +135 -0
- kivyforge/cli/doctor.py +52 -0
- kivyforge/cli/init.py +612 -0
- kivyforge/cli/init_writer.py +716 -0
- kivyforge/cli/lock.py +261 -0
- kivyforge/cli/open_cmd.py +15 -0
- kivyforge/cli/package.py +134 -0
- kivyforge/cli/run.py +162 -0
- kivyforge/cli/status.py +20 -0
- kivyforge/cli/upgrade.py +314 -0
- kivyforge/config/__init__.py +46 -0
- kivyforge/config/errors.py +43 -0
- kivyforge/config/icons.py +60 -0
- kivyforge/config/loader.py +3079 -0
- kivyforge/config/model.py +817 -0
- kivyforge/doctor/__init__.py +19 -0
- kivyforge/doctor/checks_common.py +95 -0
- kivyforge/doctor/probe.py +357 -0
- kivyforge/doctor/result.py +41 -0
- kivyforge/lock/__init__.py +42 -0
- kivyforge/lock/_pip_shim.py +63 -0
- kivyforge/lock/find_links.py +182 -0
- kivyforge/lock/model.py +69 -0
- kivyforge/lock/pep751.py +111 -0
- kivyforge/lock/reader.py +30 -0
- kivyforge/lock/resolver.py +124 -0
- kivyforge/lock/wheelruntime/__init__.py +70 -0
- kivyforge/lock/wheelruntime/builder.py +280 -0
- kivyforge/lock/wheelruntime/model.py +94 -0
- kivyforge/lock/wheelruntime/native_binaries.py +127 -0
- kivyforge/lock/wheelruntime/pbs_github.py +133 -0
- kivyforge/lock/wheelruntime/profile.py +104 -0
- kivyforge/lock/wheelruntime/resolver.py +248 -0
- kivyforge/lock/wheelruntime/runtime.py +161 -0
- kivyforge/lock/wheelruntime/serialize.py +245 -0
- kivyforge/platforms/__init__.py +114 -0
- kivyforge/platforms/android/__init__.py +140 -0
- kivyforge/platforms/android/adb.py +215 -0
- kivyforge/platforms/android/bootstrap/__init__.py +7 -0
- kivyforge/platforms/android/bootstrap/contract.py +166 -0
- kivyforge/platforms/android/bootstrap/render.py +244 -0
- kivyforge/platforms/android/bootstrap/templates/androidtest/KivyforgeContractTest.java +76 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivy_bootstrap.py +50 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_bootstrap.py +54 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_selftest.py +97 -0
- kivyforge/platforms/android/bootstrap/templates/cpp/CMakeLists.txt +20 -0
- kivyforge/platforms/android/bootstrap/templates/cpp/main.c +150 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/jnius/NativeInvocationHandler.java +48 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonActivity.java +75 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonBundle.java +132 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonService.java +189 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/renpy/android/Hardware.java +276 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/LICENSE-SDL.txt +18 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/SDL_REVISION.txt +3 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDevice.java +22 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceBLESteamController.java +650 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceManager.java +698 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/HIDDeviceUSB.java +309 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDL.java +90 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLActivity.java +2120 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLAudioManager.java +514 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLControllerManager.java +856 -0
- kivyforge/platforms/android/bootstrap/templates/sdl2/org/libsdl/app/SDLSurface.java +405 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/LICENSE-SDL.txt +18 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/SDL_REVISION.txt +3 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDevice.java +21 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceBLESteamController.java +829 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceManager.java +698 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/HIDDeviceUSB.java +354 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDL.java +90 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLActivity.java +2240 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLAudioManager.java +126 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLControllerManager.java +1010 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLDummyEdit.java +66 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLInputConnection.java +136 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLSensorManager.java +73 -0
- kivyforge/platforms/android/bootstrap/templates/sdl3/org/libsdl/app/SDLSurface.java +464 -0
- kivyforge/platforms/android/cli.py +1023 -0
- kivyforge/platforms/android/doctor.py +899 -0
- kivyforge/platforms/android/elf.py +148 -0
- kivyforge/platforms/android/generate/__init__.py +1 -0
- kivyforge/platforms/android/generate/manifest.py +261 -0
- kivyforge/platforms/android/generate/project.py +462 -0
- kivyforge/platforms/android/generate/services.py +187 -0
- kivyforge/platforms/android/gradle_wrapper/NOTICE +3 -0
- kivyforge/platforms/android/gradle_wrapper/SHA256SUMS +1 -0
- kivyforge/platforms/android/gradle_wrapper/gradle-wrapper.jar +0 -0
- kivyforge/platforms/android/gradle_wrapper/gradlew +164 -0
- kivyforge/platforms/android/gradle_wrapper/gradlew.bat +90 -0
- kivyforge/platforms/android/gradlew.py +60 -0
- kivyforge/platforms/android/icons.py +237 -0
- kivyforge/platforms/android/lock/__init__.py +1 -0
- kivyforge/platforms/android/lock/archives.py +109 -0
- kivyforge/platforms/android/lock/builder.py +387 -0
- kivyforge/platforms/android/lock/markers.py +71 -0
- kivyforge/platforms/android/lock/maven.py +252 -0
- kivyforge/platforms/android/lock/model.py +113 -0
- kivyforge/platforms/android/lock/python_meta.py +199 -0
- kivyforge/platforms/android/lock/reader.py +206 -0
- kivyforge/platforms/android/lock/resolver.py +276 -0
- kivyforge/platforms/android/lock/writer.py +121 -0
- kivyforge/platforms/android/policy.py +214 -0
- kivyforge/platforms/android/signing.py +145 -0
- kivyforge/platforms/android/smoke.py +43 -0
- kivyforge/platforms/android/splash.py +293 -0
- kivyforge/platforms/android/stage/__init__.py +1 -0
- kivyforge/platforms/android/stage/bundle.py +295 -0
- kivyforge/platforms/android/stage/jnilibs.py +186 -0
- kivyforge/platforms/android/stage/runtime.py +61 -0
- kivyforge/platforms/android/stage/wheels.py +106 -0
- kivyforge/platforms/android/toolchain.py +29 -0
- kivyforge/platforms/base.py +157 -0
- kivyforge/platforms/ios/__init__.py +129 -0
- kivyforge/platforms/ios/assets.py +193 -0
- kivyforge/platforms/ios/buildsettings.py +128 -0
- kivyforge/platforms/ios/cli.py +543 -0
- kivyforge/platforms/ios/doctor.py +410 -0
- kivyforge/platforms/ios/entitlements.py +34 -0
- kivyforge/platforms/ios/generator.py +318 -0
- kivyforge/platforms/ios/lock/__init__.py +71 -0
- kivyforge/platforms/ios/lock/builder.py +393 -0
- kivyforge/platforms/ios/lock/markers.py +69 -0
- kivyforge/platforms/ios/lock/model.py +95 -0
- kivyforge/platforms/ios/lock/python_meta.py +187 -0
- kivyforge/platforms/ios/lock/reader.py +191 -0
- kivyforge/platforms/ios/lock/resolver.py +304 -0
- kivyforge/platforms/ios/lock/spm.py +217 -0
- kivyforge/platforms/ios/lock/writer.py +114 -0
- kivyforge/platforms/ios/lock/xcframework.py +188 -0
- kivyforge/platforms/ios/materialize.py +59 -0
- kivyforge/platforms/ios/plist.py +91 -0
- kivyforge/platforms/ios/privacy.py +39 -0
- kivyforge/platforms/ios/skeleton.py +165 -0
- kivyforge/platforms/ios/sources.py +63 -0
- kivyforge/platforms/ios/staging.py +225 -0
- kivyforge/platforms/ios/swift_packages.py +324 -0
- kivyforge/platforms/ios/templates/kivyforge_bootstrap.h +34 -0
- kivyforge/platforms/ios/templates/kivyforge_bootstrap.m +146 -0
- kivyforge/platforms/ios/templates/main.m +15 -0
- kivyforge/platforms/ios/templates/main_config.h.in +12 -0
- kivyforge/platforms/ios/xcframeworks.py +194 -0
- kivyforge/platforms/ios/xcode/__init__.py +76 -0
- kivyforge/platforms/ios/xcode/commands.py +296 -0
- kivyforge/platforms/ios/xcode/runner.py +316 -0
- kivyforge/platforms/linux/__init__.py +120 -0
- kivyforge/platforms/linux/appimage.py +169 -0
- kivyforge/platforms/linux/bundle.py +235 -0
- kivyforge/platforms/linux/cli.py +232 -0
- kivyforge/platforms/linux/desktop.py +71 -0
- kivyforge/platforms/linux/doctor.py +438 -0
- kivyforge/platforms/linux/elftools.py +95 -0
- kivyforge/platforms/linux/icons.py +129 -0
- kivyforge/platforms/linux/launcher.py +107 -0
- kivyforge/platforms/linux/lock/__init__.py +126 -0
- kivyforge/platforms/linux/lock/profile.py +254 -0
- kivyforge/platforms/linux/lock/runtime.py +75 -0
- kivyforge/platforms/linux/native_stage.py +47 -0
- kivyforge/platforms/linux/runtime_stage.py +107 -0
- kivyforge/platforms/linux/wheels_stage.py +103 -0
- kivyforge/platforms/macos/__init__.py +121 -0
- kivyforge/platforms/macos/bundle.py +255 -0
- kivyforge/platforms/macos/cli.py +269 -0
- kivyforge/platforms/macos/doctor.py +480 -0
- kivyforge/platforms/macos/icns.py +75 -0
- kivyforge/platforms/macos/launcher.py +118 -0
- kivyforge/platforms/macos/lock/__init__.py +112 -0
- kivyforge/platforms/macos/lock/profile.py +119 -0
- kivyforge/platforms/macos/lock/runtime.py +58 -0
- kivyforge/platforms/macos/machotools.py +138 -0
- kivyforge/platforms/macos/native_stage.py +47 -0
- kivyforge/platforms/macos/notarize.py +143 -0
- kivyforge/platforms/macos/plist.py +37 -0
- kivyforge/platforms/macos/runtime_stage.py +110 -0
- kivyforge/platforms/macos/signing.py +78 -0
- kivyforge/platforms/macos/wheels_stage.py +108 -0
- kivyforge/platforms/windows/__init__.py +133 -0
- kivyforge/platforms/windows/assets.py +76 -0
- kivyforge/platforms/windows/bundle.py +298 -0
- kivyforge/platforms/windows/cli.py +314 -0
- kivyforge/platforms/windows/doctor.py +563 -0
- kivyforge/platforms/windows/fsswap.py +87 -0
- kivyforge/platforms/windows/icons.py +64 -0
- kivyforge/platforms/windows/launcher/__init__.py +193 -0
- kivyforge/platforms/windows/launcher/build_launcher.py +507 -0
- kivyforge/platforms/windows/launcher/launcher.c +379 -0
- kivyforge/platforms/windows/lock/__init__.py +112 -0
- kivyforge/platforms/windows/lock/profile.py +101 -0
- kivyforge/platforms/windows/lock/runtime.py +64 -0
- kivyforge/platforms/windows/naming.py +52 -0
- kivyforge/platforms/windows/native_stage.py +66 -0
- kivyforge/platforms/windows/petools.py +98 -0
- kivyforge/platforms/windows/rcedit.py +116 -0
- kivyforge/platforms/windows/runtime_stage.py +221 -0
- kivyforge/platforms/windows/signing.py +168 -0
- kivyforge/platforms/windows/vendor/LICENSE.rcedit +29 -0
- kivyforge/platforms/windows/vendor/NOTICE +17 -0
- kivyforge/platforms/windows/vendor/SHA256SUMS +4 -0
- kivyforge/platforms/windows/vendor/TOOLSET.txt +4 -0
- kivyforge/platforms/windows/vendor/__init__.py +12 -0
- kivyforge/platforms/windows/vendor/fetch_rcedit.py +97 -0
- kivyforge/platforms/windows/vendor/launcher-amd64.exe +0 -0
- kivyforge/platforms/windows/vendor/rcedit-x64.exe +0 -0
- kivyforge/platforms/windows/wheels_stage.py +139 -0
- kivyforge-3.0.0.dev0.dist-info/METADATA +609 -0
- kivyforge-3.0.0.dev0.dist-info/RECORD +225 -0
- kivyforge-3.0.0.dev0.dist-info/WHEEL +5 -0
- kivyforge-3.0.0.dev0.dist-info/entry_points.txt +3 -0
- kivyforge-3.0.0.dev0.dist-info/licenses/LICENSE +21 -0
- 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,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())
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|