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,110 @@
|
|
|
1
|
+
"""Acquire + normalize the bundled CPython runtime for the ``.app``.
|
|
2
|
+
|
|
3
|
+
Consumes the provider-agnostic runtime pin from ``pylock.macos.toml`` (per-arch
|
|
4
|
+
URL + SHA-256), fetches + verifies the arm64 archive, extracts it, and produces
|
|
5
|
+
the **canonical relocatable CPython tree**. Nothing downstream (launcher,
|
|
6
|
+
bundler, signing) knows or cares which provider produced it — the whole point of
|
|
7
|
+
the runtime-provider seam.
|
|
8
|
+
|
|
9
|
+
macOS is arm64-only, so there is no second arch and no ``lipo`` merge: the
|
|
10
|
+
extracted tree ships as-is.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import shutil
|
|
16
|
+
import tarfile
|
|
17
|
+
import tempfile
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from kivyforge.artifacts.cache import ArtifactCache
|
|
21
|
+
from kivyforge.artifacts.download import DownloadError, fetch_artifact
|
|
22
|
+
from kivyforge.lock.wheelruntime.model import PythonRuntime
|
|
23
|
+
from kivyforge.lock.wheelruntime.runtime import (
|
|
24
|
+
RuntimeProviderError,
|
|
25
|
+
normalized_runtime_root,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from . import AppBundleError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def stage_runtime(
|
|
32
|
+
runtime: PythonRuntime,
|
|
33
|
+
arch: str,
|
|
34
|
+
home: Path,
|
|
35
|
+
*,
|
|
36
|
+
project_root: Path,
|
|
37
|
+
cache: ArtifactCache | None = None,
|
|
38
|
+
no_cache: bool = False,
|
|
39
|
+
) -> Path:
|
|
40
|
+
"""Materialize the runtime for *arch* at *home*; return *home*.
|
|
41
|
+
|
|
42
|
+
*home* is the final CPython home directory (whose ``bin/python3`` the
|
|
43
|
+
launcher execs). It is emptied and recreated. Deliberately placed under the
|
|
44
|
+
bundle's ``Resources/`` (not ``Frameworks/``): a unix-prefix Python tree
|
|
45
|
+
under ``Frameworks/`` trips ``codesign``'s framework auto-discovery on
|
|
46
|
+
non-framework subdirs (e.g. ``lib/tk*``), whereas ``Resources`` content is
|
|
47
|
+
sealed as data and the Mach-O files are signed individually.
|
|
48
|
+
"""
|
|
49
|
+
cache = cache or ArtifactCache()
|
|
50
|
+
|
|
51
|
+
tmp = Path(tempfile.mkdtemp(prefix="kivy-runtime-"))
|
|
52
|
+
try:
|
|
53
|
+
artifact = runtime.artifact_for(arch)
|
|
54
|
+
if artifact is None:
|
|
55
|
+
raise AppBundleError(
|
|
56
|
+
f"the lock has no {arch} runtime artifact; re-run "
|
|
57
|
+
f"`kivyforge lock -p macos` (locked archs: "
|
|
58
|
+
f"{', '.join(a.arch for a in runtime.artifacts)})."
|
|
59
|
+
)
|
|
60
|
+
archive = _fetch(artifact, arch, project_root, cache, no_cache)
|
|
61
|
+
extracted = _extract(archive, tmp / arch, runtime.provider)
|
|
62
|
+
|
|
63
|
+
if home.exists():
|
|
64
|
+
shutil.rmtree(home)
|
|
65
|
+
home.parent.mkdir(parents=True, exist_ok=True)
|
|
66
|
+
shutil.copytree(extracted, home, symlinks=True)
|
|
67
|
+
return home
|
|
68
|
+
finally:
|
|
69
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _fetch(artifact, arch, project_root, cache, no_cache) -> Path:
|
|
73
|
+
filename = artifact.url.rsplit("/", 1)[-1] or f"python-{arch}.tar.gz"
|
|
74
|
+
try:
|
|
75
|
+
return fetch_artifact(
|
|
76
|
+
name=f"python runtime ({arch})",
|
|
77
|
+
sha256=artifact.sha256,
|
|
78
|
+
filename=filename,
|
|
79
|
+
url=artifact.url,
|
|
80
|
+
project_root=project_root,
|
|
81
|
+
cache=cache,
|
|
82
|
+
no_cache=no_cache,
|
|
83
|
+
)
|
|
84
|
+
except DownloadError as exc:
|
|
85
|
+
raise AppBundleError(str(exc)) from exc
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _extract(archive: Path, into: Path, provider: str) -> Path:
|
|
89
|
+
into.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
try:
|
|
91
|
+
with tarfile.open(archive, "r:*") as tf:
|
|
92
|
+
_safe_extractall(tf, into)
|
|
93
|
+
except (tarfile.TarError, OSError) as exc:
|
|
94
|
+
raise AppBundleError(f"failed to extract {archive.name}: {exc}") from exc
|
|
95
|
+
try:
|
|
96
|
+
return normalized_runtime_root(provider, into)
|
|
97
|
+
except RuntimeProviderError as exc:
|
|
98
|
+
raise AppBundleError(f"{archive.name}: {exc}") from exc
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _safe_extractall(tf: tarfile.TarFile, into: Path) -> None:
|
|
102
|
+
"""Extract, rejecting members that would escape *into* (path traversal)."""
|
|
103
|
+
base = into.resolve()
|
|
104
|
+
for member in tf.getmembers():
|
|
105
|
+
target = (into / member.name).resolve()
|
|
106
|
+
if not (target == base or base in target.parents):
|
|
107
|
+
raise AppBundleError(f"unsafe path in archive: {member.name!r}")
|
|
108
|
+
# Paths are validated above; fully_trusted preserves the runtime's
|
|
109
|
+
# permissions and symlinks (the restrictive filters would rewrite them).
|
|
110
|
+
tf.extractall(into, filter="fully_trusted") # noqa: S202 — members validated just above
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Code signing of the assembled ``.app`` (macos-spec).
|
|
2
|
+
|
|
3
|
+
Two tiers, both **inside-out** — every nested Mach-O (the runtime's
|
|
4
|
+
``python3``/``libpython``/``.dylib`` and the wheels' ``.so`` extensions) is
|
|
5
|
+
signed first, deepest first, then the ``.app`` bundle last — because signing
|
|
6
|
+
the bundle seals a hash of its already-signed contents:
|
|
7
|
+
|
|
8
|
+
- **Ad-hoc** (``codesign --sign -``): the mandatory Apple-Silicon floor (the
|
|
9
|
+
kernel refuses to run unsigned arm64 Mach-O). Integrity only, no trust.
|
|
10
|
+
- **Developer ID** (``--sign <identity> --options runtime --timestamp``): the
|
|
11
|
+
distribution tier. Hardened Runtime + a secure timestamp are notarization
|
|
12
|
+
requirements; the main executable additionally carries entitlements that a
|
|
13
|
+
hardened Python bundle needs (see ``DEFAULT_HARDENED_ENTITLEMENTS``).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import plistlib
|
|
19
|
+
import tempfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from .machotools import codesign_adhoc, codesign_identity, is_macho
|
|
23
|
+
|
|
24
|
+
# Hardened-Runtime entitlements a bundled CPython app needs by default:
|
|
25
|
+
# - allow-unsigned-executable-memory: ctypes/cffi trampolines allocate W+X
|
|
26
|
+
# pages, which the Hardened Runtime otherwise forbids.
|
|
27
|
+
# - disable-library-validation: the app loads wheel `.so`/`.dylib` binaries
|
|
28
|
+
# signed by other teams (or re-signed here); library validation would
|
|
29
|
+
# require every image to share the app's team ID.
|
|
30
|
+
# Users may override either (or add more) via [tool.kivy.macos.entitlements].
|
|
31
|
+
DEFAULT_HARDENED_ENTITLEMENTS: dict[str, object] = {
|
|
32
|
+
"com.apple.security.cs.allow-unsigned-executable-memory": True,
|
|
33
|
+
"com.apple.security.cs.disable-library-validation": True,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sign_bundle_adhoc(app: Path) -> int:
|
|
38
|
+
"""Ad-hoc sign every Mach-O in *app*, then *app* itself; return the count.
|
|
39
|
+
|
|
40
|
+
Returns the number of ``codesign`` invocations (nested binaries + the bundle),
|
|
41
|
+
which callers use for a one-line progress message.
|
|
42
|
+
"""
|
|
43
|
+
machos = _machos_inside_out(app)
|
|
44
|
+
for macho in machos:
|
|
45
|
+
codesign_adhoc(macho)
|
|
46
|
+
codesign_adhoc(app)
|
|
47
|
+
return len(machos) + 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def sign_bundle_developer_id(
|
|
51
|
+
app: Path,
|
|
52
|
+
identity: str,
|
|
53
|
+
*,
|
|
54
|
+
extra_entitlements: dict[str, object] | None = None,
|
|
55
|
+
) -> int:
|
|
56
|
+
"""Developer ID deep-sign *app* for distribution; return the codesign count.
|
|
57
|
+
|
|
58
|
+
Nested Mach-O binaries get Hardened Runtime + timestamp; the ``.app`` seal
|
|
59
|
+
(which signs the main executable) additionally carries the merged
|
|
60
|
+
entitlements (defaults + ``[tool.kivy.macos.entitlements]``, user wins).
|
|
61
|
+
"""
|
|
62
|
+
entitlements = {**DEFAULT_HARDENED_ENTITLEMENTS, **(extra_entitlements or {})}
|
|
63
|
+
machos = _machos_inside_out(app)
|
|
64
|
+
with tempfile.TemporaryDirectory(prefix="kivyforge-ent-") as tmp:
|
|
65
|
+
ent_file = Path(tmp) / "entitlements.plist"
|
|
66
|
+
with ent_file.open("wb") as fh:
|
|
67
|
+
plistlib.dump(entitlements, fh)
|
|
68
|
+
for macho in machos:
|
|
69
|
+
codesign_identity(macho, identity)
|
|
70
|
+
codesign_identity(app, identity, entitlements=ent_file)
|
|
71
|
+
return len(machos) + 1
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _machos_inside_out(app: Path) -> list[Path]:
|
|
75
|
+
"""Every Mach-O under *app*, deepest paths first (containers sign last)."""
|
|
76
|
+
machos = [p for p in app.rglob("*") if is_macho(p)]
|
|
77
|
+
machos.sort(key=lambda p: len(p.parts), reverse=True)
|
|
78
|
+
return machos
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Install the locked wheels into the ``.app``'s ``lib/`` (site-packages).
|
|
2
|
+
|
|
3
|
+
Wheels are fetched + SHA-256-verified from ``pylock.macos.toml`` (URL or vendored
|
|
4
|
+
path) and unpacked directly — no pip, so the bundler is independent of the host
|
|
5
|
+
interpreter's version/arch.
|
|
6
|
+
|
|
7
|
+
macOS builds are arm64-only, so exactly one wheel is unpacked per package and
|
|
8
|
+
there is no ``lipo`` merge. A ``universal2`` wheel is still perfectly valid — it
|
|
9
|
+
*contains* arm64 — which is why the arch match accepts it alongside a thin
|
|
10
|
+
``arm64`` wheel and a pure-Python one.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import shutil
|
|
16
|
+
import zipfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from kivyforge.artifacts.cache import ArtifactCache
|
|
20
|
+
from kivyforge.artifacts.download import DownloadError, fetch_artifact
|
|
21
|
+
from kivyforge.lock.model import LockedPackage, LockedWheel
|
|
22
|
+
|
|
23
|
+
from . import AppBundleError
|
|
24
|
+
from .lock import wheel_arch
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def stage_wheels(
|
|
28
|
+
packages: tuple[LockedPackage, ...],
|
|
29
|
+
arch: str,
|
|
30
|
+
lib_dir: Path,
|
|
31
|
+
*,
|
|
32
|
+
project_root: Path,
|
|
33
|
+
cache: ArtifactCache | None = None,
|
|
34
|
+
no_cache: bool = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Unpack every package's wheel for *arch* into *lib_dir*."""
|
|
37
|
+
cache = cache or ArtifactCache()
|
|
38
|
+
if lib_dir.exists():
|
|
39
|
+
shutil.rmtree(lib_dir)
|
|
40
|
+
lib_dir.mkdir(parents=True)
|
|
41
|
+
|
|
42
|
+
for pkg in packages:
|
|
43
|
+
wheel = _select_wheel(pkg, arch)
|
|
44
|
+
_unpack(_fetch(wheel, project_root, cache, no_cache), lib_dir)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _select_wheel(pkg: LockedPackage, arch: str) -> LockedWheel:
|
|
48
|
+
"""The one wheel to unpack for *arch*.
|
|
49
|
+
|
|
50
|
+
Pure-Python and ``universal2`` wheels both satisfy an arm64 build — the
|
|
51
|
+
latter *contains* arm64 — as does a thin ``arm64`` wheel.
|
|
52
|
+
"""
|
|
53
|
+
for wheel in pkg.wheels:
|
|
54
|
+
if wheel.is_pure_python:
|
|
55
|
+
return wheel
|
|
56
|
+
if wheel_arch(wheel.platform_tag) in ("universal2", arch):
|
|
57
|
+
return wheel
|
|
58
|
+
raise AppBundleError(
|
|
59
|
+
f"{pkg.name} {pkg.version} has no {arch} wheel in the lock.\n"
|
|
60
|
+
f" Re-run `kivyforge lock -p macos`."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _fetch(wheel: LockedWheel, project_root, cache, no_cache) -> Path:
|
|
65
|
+
try:
|
|
66
|
+
return fetch_artifact(
|
|
67
|
+
name=wheel.name,
|
|
68
|
+
sha256=wheel.sha256,
|
|
69
|
+
filename=wheel.name,
|
|
70
|
+
url=wheel.url,
|
|
71
|
+
path=wheel.path,
|
|
72
|
+
project_root=project_root,
|
|
73
|
+
cache=cache,
|
|
74
|
+
no_cache=no_cache,
|
|
75
|
+
)
|
|
76
|
+
except DownloadError as exc:
|
|
77
|
+
raise AppBundleError(str(exc)) from exc
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _unpack(wheel: Path, target: Path) -> None:
|
|
81
|
+
"""Extract a wheel into *target*, normalizing ``.data/{purelib,platlib}``."""
|
|
82
|
+
with zipfile.ZipFile(wheel) as zf:
|
|
83
|
+
_safe_extract(zf, target)
|
|
84
|
+
for data_dir in list(target.glob("*.data")):
|
|
85
|
+
for sub in ("purelib", "platlib"):
|
|
86
|
+
src = data_dir / sub
|
|
87
|
+
if src.is_dir():
|
|
88
|
+
_merge_tree(src, target)
|
|
89
|
+
shutil.rmtree(data_dir, ignore_errors=True)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _merge_tree(src: Path, dest: Path) -> None:
|
|
93
|
+
for item in src.iterdir():
|
|
94
|
+
target = dest / item.name
|
|
95
|
+
if item.is_dir():
|
|
96
|
+
target.mkdir(exist_ok=True)
|
|
97
|
+
_merge_tree(item, target)
|
|
98
|
+
else:
|
|
99
|
+
shutil.copy2(item, target)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _safe_extract(zf: zipfile.ZipFile, target: Path) -> None:
|
|
103
|
+
base = target.resolve()
|
|
104
|
+
for member in zf.namelist():
|
|
105
|
+
resolved = (target / member).resolve()
|
|
106
|
+
if not (resolved == base or base in resolved.parents):
|
|
107
|
+
raise AppBundleError(f"unsafe path in wheel: {member!r}")
|
|
108
|
+
zf.extractall(target) # noqa: S202 — members validated just above
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Windows platform backend (windows-spec).
|
|
2
|
+
|
|
3
|
+
Produces a run-from-folder ``onedir`` bundle: a prebuilt windowed-subsystem
|
|
4
|
+
launcher ``.exe`` beside a whole python-build-standalone prefix, the app source,
|
|
5
|
+
a generated bootstrap, and the declared native-binaries channel. Windows is the
|
|
6
|
+
host default on Windows, so a Windows box with ``[tool.kivy.windows]`` configured
|
|
7
|
+
resolves to this backend without ``-p``. It requires a Windows host: the bundled
|
|
8
|
+
runtime is an MSVC amd64 build and the resource/signing tools are Windows-only.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import platform as _platform
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
from ..base import HostCapabilityError, Platform
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from kivyforge.doctor.result import CheckResult
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WindowsBundleError(Exception):
|
|
24
|
+
"""A Windows onedir bundling failure surfaced with an actionable message."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WindowsPlatform(Platform):
|
|
28
|
+
name = "windows"
|
|
29
|
+
aliases = ("win",)
|
|
30
|
+
# Windows is a desktop target built on its own OS, so Windows maps to it as
|
|
31
|
+
# the host default (resolution-chain step 3).
|
|
32
|
+
host_system = "Windows"
|
|
33
|
+
# The onedir folder is both the dev-run target and the shipped distributable;
|
|
34
|
+
# installers stay permanently external (windows-spec). Folder is the only
|
|
35
|
+
# package format.
|
|
36
|
+
package_formats = ("folder",)
|
|
37
|
+
|
|
38
|
+
def check_host_capability(self, *, host_system: str | None = None) -> None:
|
|
39
|
+
# arm64: keep gating on the OS only, never the host CPU. Building an
|
|
40
|
+
# arm64 onedir needs no build-time compilation (runtime + wheels are
|
|
41
|
+
# fetched per target arch, the launcher is prebuilt/vendored, rcedit is
|
|
42
|
+
# emulated), so cross-arch builds are the intended model: an x64 Windows
|
|
43
|
+
# host should cross-build arm64 bundles (and vice versa) with no arm64
|
|
44
|
+
# hardware. The only host-CPU coupling is the msvcp140.dll System32
|
|
45
|
+
# fallback, which arm64 removes by pinning msvcp140.dll as an artifact
|
|
46
|
+
# (see runtime_stage.py). Do NOT add a host==target check. arm64-windows.md §7.
|
|
47
|
+
host = host_system if host_system is not None else _platform.system()
|
|
48
|
+
if host != "Windows":
|
|
49
|
+
raise HostCapabilityError(
|
|
50
|
+
"building for Windows requires a Windows host; "
|
|
51
|
+
f"this host is {host!r}.\n"
|
|
52
|
+
" The onedir bundle stages an MSVC amd64 python-build-standalone "
|
|
53
|
+
"runtime and uses Windows-only tools (rcedit, signtool).\n"
|
|
54
|
+
" Run `kivyforge doctor -p windows` on a Windows box to check "
|
|
55
|
+
"the setup."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def build(
|
|
59
|
+
self,
|
|
60
|
+
project_root: Path,
|
|
61
|
+
*,
|
|
62
|
+
target: str | None,
|
|
63
|
+
arch: str | None,
|
|
64
|
+
no_verify_lock: bool,
|
|
65
|
+
no_cache: bool,
|
|
66
|
+
team_id: str | None,
|
|
67
|
+
signing_identity: str | None,
|
|
68
|
+
export_method: str,
|
|
69
|
+
debug: bool = False,
|
|
70
|
+
fmt: str | None = None,
|
|
71
|
+
abi: str | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
self.reject_ios_only_target(target)
|
|
74
|
+
from .cli import windows_build
|
|
75
|
+
|
|
76
|
+
windows_build(
|
|
77
|
+
project_root,
|
|
78
|
+
arch=arch,
|
|
79
|
+
no_verify_lock=no_verify_lock,
|
|
80
|
+
no_cache=no_cache,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def run(
|
|
84
|
+
self,
|
|
85
|
+
project_root: Path,
|
|
86
|
+
*,
|
|
87
|
+
target: str,
|
|
88
|
+
arch: str | None,
|
|
89
|
+
destination: str | None,
|
|
90
|
+
no_build: bool,
|
|
91
|
+
) -> None:
|
|
92
|
+
from .cli import windows_run
|
|
93
|
+
|
|
94
|
+
windows_run(project_root, arch=arch, no_build=no_build)
|
|
95
|
+
|
|
96
|
+
def package(
|
|
97
|
+
self,
|
|
98
|
+
project_root: Path,
|
|
99
|
+
*,
|
|
100
|
+
fmt: str,
|
|
101
|
+
arch: str | None,
|
|
102
|
+
team_id: str | None,
|
|
103
|
+
signing_identity: str | None,
|
|
104
|
+
export_method: str,
|
|
105
|
+
notarize: bool | None,
|
|
106
|
+
notary_profile: str | None,
|
|
107
|
+
no_verify_lock: bool,
|
|
108
|
+
no_cache: bool,
|
|
109
|
+
abi: str | None = None,
|
|
110
|
+
keystore: str | None = None,
|
|
111
|
+
key_alias: str | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
from .cli import windows_package
|
|
114
|
+
|
|
115
|
+
windows_package(
|
|
116
|
+
project_root,
|
|
117
|
+
fmt=fmt,
|
|
118
|
+
arch=arch,
|
|
119
|
+
no_verify_lock=no_verify_lock,
|
|
120
|
+
no_cache=no_cache,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def status(self, project_root: Path) -> None:
|
|
124
|
+
from .cli import windows_status
|
|
125
|
+
|
|
126
|
+
windows_status(project_root)
|
|
127
|
+
|
|
128
|
+
def doctor(
|
|
129
|
+
self, cwd: Path, *, kivyforge_version: str, offline: bool
|
|
130
|
+
) -> list[CheckResult]:
|
|
131
|
+
from .doctor import windows_doctor
|
|
132
|
+
|
|
133
|
+
return windows_doctor(cwd, kivyforge_version=kivyforge_version, offline=offline)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Access + integrity-check the vendored Windows binaries (windows-spec).
|
|
2
|
+
|
|
3
|
+
The onedir bundler consumes two prebuilt binaries shipped inside the package:
|
|
4
|
+
the launcher (``launcher-amd64.exe``) and the resource-patch tool
|
|
5
|
+
(``rcedit-x64.exe``). This module locates them and verifies each against the
|
|
6
|
+
SHA-256 pinned in ``vendor/SHA256SUMS`` before use, so a corrupted or
|
|
7
|
+
tampered-with vendored asset fails loudly instead of ending up in a shipped app.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from . import WindowsBundleError
|
|
16
|
+
|
|
17
|
+
VENDOR_DIR = Path(__file__).resolve().parent / "vendor"
|
|
18
|
+
MANIFEST = VENDOR_DIR / "SHA256SUMS"
|
|
19
|
+
LAUNCHER_NAME = "launcher-amd64.exe"
|
|
20
|
+
RCEDIT_NAME = "rcedit-x64.exe"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_manifest() -> dict[str, str]:
|
|
24
|
+
"""Parse ``vendor/SHA256SUMS`` into ``{filename: sha256}``."""
|
|
25
|
+
if not MANIFEST.is_file():
|
|
26
|
+
raise WindowsBundleError(
|
|
27
|
+
f"vendored-binary manifest missing: {MANIFEST}. The kivyforge "
|
|
28
|
+
"install is incomplete."
|
|
29
|
+
)
|
|
30
|
+
entries: dict[str, str] = {}
|
|
31
|
+
for line in MANIFEST.read_text(encoding="utf-8").splitlines():
|
|
32
|
+
line = line.strip()
|
|
33
|
+
if not line or line.startswith("#"):
|
|
34
|
+
continue
|
|
35
|
+
digest, _, name = line.partition(" ")
|
|
36
|
+
if digest and name:
|
|
37
|
+
entries[name.strip()] = digest.strip()
|
|
38
|
+
return entries
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _verified_asset(name: str, *, human: str) -> Path:
|
|
42
|
+
path = VENDOR_DIR / name
|
|
43
|
+
if not path.is_file():
|
|
44
|
+
raise WindowsBundleError(
|
|
45
|
+
f"the vendored {human} ({name}) is missing from the kivyforge "
|
|
46
|
+
f"install at {path}.\n"
|
|
47
|
+
" It is built/fetched in CI; a source checkout may need "
|
|
48
|
+
"`python -m kivyforge.platforms.windows.launcher.build_launcher build` "
|
|
49
|
+
"(launcher) or "
|
|
50
|
+
"`python -m kivyforge.platforms.windows.vendor.fetch_rcedit` (rcedit)."
|
|
51
|
+
)
|
|
52
|
+
expected = read_manifest().get(name)
|
|
53
|
+
if not expected:
|
|
54
|
+
raise WindowsBundleError(
|
|
55
|
+
f"the vendored {human} ({name}) has no pinned SHA-256 in {MANIFEST}."
|
|
56
|
+
)
|
|
57
|
+
actual = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
58
|
+
if actual != expected:
|
|
59
|
+
raise WindowsBundleError(
|
|
60
|
+
f"the vendored {human} ({name}) failed its integrity check:\n"
|
|
61
|
+
f" expected sha256 {expected}\n actual sha256 {actual}\n"
|
|
62
|
+
" Refuse to ship a tampered/corrupted binary; re-vendor it."
|
|
63
|
+
)
|
|
64
|
+
return path
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def vendored_launcher() -> Path:
|
|
68
|
+
"""The verified prebuilt launcher (``launcher-amd64.exe``)."""
|
|
69
|
+
# arm64: take a target-arch argument and select launcher-<arch>.exe (rcedit
|
|
70
|
+
# stays x64 — it is emulated and only edits resources). See arm64-windows.md §5.
|
|
71
|
+
return _verified_asset(LAUNCHER_NAME, human="launcher")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def vendored_rcedit() -> Path:
|
|
75
|
+
"""The verified prebuilt resource editor (``rcedit-x64.exe``)."""
|
|
76
|
+
return _verified_asset(RCEDIT_NAME, human="rcedit resource editor")
|