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,89 @@
|
|
|
1
|
+
"""Select pinned iOS wheels for a build slice (spec 03 / spec 05 step 4).
|
|
2
|
+
|
|
3
|
+
Slice selection is driven by the build target + ``--arch``. The pip
|
|
4
|
+
cross-install command lives in ``collect.py`` next to where it is invoked.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from ..lock.model import LockedPackage, LockedWheel
|
|
12
|
+
|
|
13
|
+
# Build target -> the simulator/device platform-tag suffix.
|
|
14
|
+
TARGET_SUFFIX = {
|
|
15
|
+
("device", "arm64"): "arm64_iphoneos",
|
|
16
|
+
("simulator", "arm64"): "arm64_iphonesimulator",
|
|
17
|
+
("simulator", "x86_64"): "x86_64_iphonesimulator",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class BuildSlice:
|
|
23
|
+
target: str # "device" | "simulator"
|
|
24
|
+
arch: str # "arm64" | "x86_64"
|
|
25
|
+
deployment_target: str
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def platform_tag(self) -> str:
|
|
29
|
+
suffix = TARGET_SUFFIX.get((self.target, self.arch))
|
|
30
|
+
if suffix is None:
|
|
31
|
+
raise ValueError(f"unsupported target/arch: {self.target}/{self.arch}")
|
|
32
|
+
dt = self.deployment_target.replace(".", "_")
|
|
33
|
+
return f"ios_{dt}_{suffix}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def select_wheel(package: LockedPackage, slice_: BuildSlice) -> LockedWheel:
|
|
37
|
+
"""Pick the wheel matching the build slice (pure-Python wheels match any).
|
|
38
|
+
|
|
39
|
+
For native wheels, the arch/sdk suffix must match exactly, but the iOS
|
|
40
|
+
version component uses compatibility matching: a wheel tagged ios_13_0_*
|
|
41
|
+
is valid for a project targeting ios_16_0 because the wheel can run on
|
|
42
|
+
any iOS >= 13.0. The best (highest compatible) version is preferred.
|
|
43
|
+
|
|
44
|
+
Background: wheel tags are determined by the Python binary's compile-time
|
|
45
|
+
sysconfig.get_platform(), which reflects the Python.xcframework minimum OS
|
|
46
|
+
(e.g. python.org cp315 targets 13.0 regardless of IPHONEOS_DEPLOYMENT_TARGET
|
|
47
|
+
used when compiling the extension code itself).
|
|
48
|
+
"""
|
|
49
|
+
pure = [w for w in package.wheels if w.is_pure_python]
|
|
50
|
+
if pure:
|
|
51
|
+
return pure[0]
|
|
52
|
+
|
|
53
|
+
arch_sdk = TARGET_SUFFIX.get((slice_.target, slice_.arch))
|
|
54
|
+
if arch_sdk is None:
|
|
55
|
+
raise ValueError(f"unsupported target/arch: {slice_.target}/{slice_.arch}")
|
|
56
|
+
|
|
57
|
+
dt_parts = tuple(int(x) for x in slice_.deployment_target.split("."))
|
|
58
|
+
|
|
59
|
+
candidates: list[tuple[tuple[int, ...], LockedWheel]] = []
|
|
60
|
+
for wheel in package.wheels:
|
|
61
|
+
tag = wheel.platform_tag # e.g. "ios_13_0_arm64_iphonesimulator"
|
|
62
|
+
if not tag.endswith(f"_{arch_sdk}"):
|
|
63
|
+
continue
|
|
64
|
+
# Extract the ios_<major>_<minor> prefix.
|
|
65
|
+
prefix = tag[: -(len(arch_sdk) + 1)] # "ios_13_0"
|
|
66
|
+
parts = prefix.split("_") # ["ios", "13", "0"]
|
|
67
|
+
if parts[0] != "ios" or len(parts) < 3:
|
|
68
|
+
continue
|
|
69
|
+
try:
|
|
70
|
+
wheel_ver = tuple(int(x) for x in parts[1:])
|
|
71
|
+
except ValueError:
|
|
72
|
+
continue
|
|
73
|
+
if wheel_ver <= dt_parts:
|
|
74
|
+
candidates.append((wheel_ver, wheel))
|
|
75
|
+
|
|
76
|
+
if candidates:
|
|
77
|
+
# Prefer the highest compatible version.
|
|
78
|
+
candidates.sort(key=lambda t: t[0], reverse=True)
|
|
79
|
+
return candidates[0][1]
|
|
80
|
+
|
|
81
|
+
available = ", ".join(sorted(w.platform_tag for w in package.wheels))
|
|
82
|
+
raise WheelSelectionError(
|
|
83
|
+
f"{package.name} has no compatible wheel for slice "
|
|
84
|
+
f"{slice_.platform_tag} (have: {available})."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class WheelSelectionError(Exception):
|
|
89
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Bundling helpers shared by more than one platform backend."""
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Byte-compile a staged Python payload, optionally stripping the sources.
|
|
2
|
+
|
|
3
|
+
Shared by every backend that ships a Python tree: Android's asset bundle and
|
|
4
|
+
the three desktop bundles. The mechanics below are identical everywhere; what
|
|
5
|
+
differs per platform is only *which* trees get compiled and *which* interpreter
|
|
6
|
+
does the compiling, both of which the caller supplies.
|
|
7
|
+
|
|
8
|
+
Two layouts, and the difference matters:
|
|
9
|
+
|
|
10
|
+
- Keeping the source, the ``.pyc`` goes in ``__pycache__/`` as usual, so imports
|
|
11
|
+
find it next to the ``.py`` it was built from.
|
|
12
|
+
- Shipping ``.pyc`` only requires the *legacy* layout — PEP 3147 sourceless
|
|
13
|
+
imports look for ``foo.pyc`` at the source's own path, never inside
|
|
14
|
+
``__pycache__/``. Compiling to ``__pycache__/`` and then deleting the ``.py``
|
|
15
|
+
would produce a tree that imports nothing at all.
|
|
16
|
+
|
|
17
|
+
Hash-based, unchecked invalidation (PEP 552) is used rather than the default
|
|
18
|
+
mtime+size: it saves a stat per import for a payload that cannot have a newer
|
|
19
|
+
source than the one shipped, and it keeps an mtime out of every ``.pyc``.
|
|
20
|
+
|
|
21
|
+
Paths are stripped to be payload-relative because a ``.pyc`` records the path it
|
|
22
|
+
was compiled from: the default would leak local host paths into the shipped
|
|
23
|
+
artifact and make otherwise-identical builds differ per machine.
|
|
24
|
+
|
|
25
|
+
``compiler`` is an interpreter argv prefix whose CPython *minor* matches the
|
|
26
|
+
target runtime — empty means "this interpreter". Establishing that match is the
|
|
27
|
+
caller's job, because a ``.pyc`` is only loadable by the exact CPython minor
|
|
28
|
+
that wrote it.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import shutil
|
|
34
|
+
import subprocess
|
|
35
|
+
import sys
|
|
36
|
+
from collections.abc import Sequence
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PycompileError(Exception):
|
|
41
|
+
"""Byte-compilation failed, or the compiler could not be run."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def select_compiler(
|
|
45
|
+
*, staged_interpreter: Path, native: bool, python_version: str
|
|
46
|
+
) -> tuple[str, ...] | None:
|
|
47
|
+
"""Pick the interpreter argv to byte-compile the staged payload with.
|
|
48
|
+
|
|
49
|
+
The staged interpreter is the one actually shipped, so it is the first
|
|
50
|
+
choice — but only when *native*, i.e. the caller has confirmed it can run
|
|
51
|
+
on this host without emulation, which kivyforge never depends on. When it
|
|
52
|
+
can't run here, fall back to this process's own interpreter: a ``.pyc``'s
|
|
53
|
+
magic number is keyed to CPython's *minor* version only, never
|
|
54
|
+
architecture, so any interpreter of the right minor will do regardless of
|
|
55
|
+
which arch it runs on. Returns ``None`` when neither works, telling the
|
|
56
|
+
caller to degrade (skip compiling, ship source) rather than write a
|
|
57
|
+
``.pyc`` nothing can load.
|
|
58
|
+
"""
|
|
59
|
+
if native and staged_interpreter.is_file():
|
|
60
|
+
return (str(staged_interpreter),)
|
|
61
|
+
if sys.version_info[:2] == _target_minor(python_version):
|
|
62
|
+
return ()
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _target_minor(python_version: str) -> tuple[int, int]:
|
|
67
|
+
parts = python_version.split(".")[:2]
|
|
68
|
+
return (int(parts[0].split("rc")[0]), int(parts[1].split("rc")[0]))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def byte_compile(
|
|
72
|
+
trees: Sequence[Path],
|
|
73
|
+
*,
|
|
74
|
+
compiler: Sequence[str] = (),
|
|
75
|
+
strip_source: bool = False,
|
|
76
|
+
stripdir: Path,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Compile every tree in *trees* in place; strip ``.py`` when asked.
|
|
79
|
+
|
|
80
|
+
Missing trees are skipped (a backend may stage some payload areas only
|
|
81
|
+
conditionally). Raises :class:`PycompileError` naming the tree that failed.
|
|
82
|
+
"""
|
|
83
|
+
for tree in trees:
|
|
84
|
+
if not tree.is_dir():
|
|
85
|
+
continue
|
|
86
|
+
if not compile_tree(
|
|
87
|
+
tree, compiler=compiler, legacy=strip_source, stripdir=stripdir
|
|
88
|
+
):
|
|
89
|
+
raise PycompileError(
|
|
90
|
+
f"byte-compiling {tree.name}/ failed; the output above names the "
|
|
91
|
+
"file.\n"
|
|
92
|
+
" A syntax error in app code fails here rather than at runtime."
|
|
93
|
+
)
|
|
94
|
+
if strip_source:
|
|
95
|
+
strip_sources(tree)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def strip_sources(tree: Path) -> None:
|
|
99
|
+
"""Delete every ``.py`` that has a sibling ``.pyc``, and all ``__pycache__``.
|
|
100
|
+
|
|
101
|
+
Only sources with a compiled counterpart are removed, so a file the
|
|
102
|
+
compiler skipped is never silently dropped from the payload. The
|
|
103
|
+
``__pycache__`` sweep matters because nothing can import from it once the
|
|
104
|
+
sources are gone — leaving it behind would ship dead weight.
|
|
105
|
+
"""
|
|
106
|
+
for source in tree.rglob("*.py"):
|
|
107
|
+
if source.with_suffix(".pyc").is_file():
|
|
108
|
+
source.unlink()
|
|
109
|
+
for cache in tree.rglob("__pycache__"):
|
|
110
|
+
shutil.rmtree(cache, ignore_errors=True)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def compile_tree(
|
|
114
|
+
target: Path,
|
|
115
|
+
*,
|
|
116
|
+
compiler: Sequence[str] = (),
|
|
117
|
+
legacy: bool = False,
|
|
118
|
+
stripdir: Path,
|
|
119
|
+
) -> bool:
|
|
120
|
+
"""Byte-compile *target*; return whether it succeeded.
|
|
121
|
+
|
|
122
|
+
Uses ``compileall`` in-process when *compiler* is empty, otherwise shells
|
|
123
|
+
out to the named interpreter so the ``.pyc`` carries the target runtime's
|
|
124
|
+
magic number.
|
|
125
|
+
"""
|
|
126
|
+
if not compiler:
|
|
127
|
+
import compileall
|
|
128
|
+
import py_compile
|
|
129
|
+
|
|
130
|
+
return bool(
|
|
131
|
+
compileall.compile_dir(
|
|
132
|
+
target,
|
|
133
|
+
quiet=1,
|
|
134
|
+
legacy=legacy,
|
|
135
|
+
optimize=0,
|
|
136
|
+
invalidation_mode=py_compile.PycInvalidationMode.UNCHECKED_HASH,
|
|
137
|
+
force=True,
|
|
138
|
+
stripdir=str(stripdir),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
argv = [
|
|
143
|
+
*compiler,
|
|
144
|
+
"-m",
|
|
145
|
+
"compileall",
|
|
146
|
+
"-q",
|
|
147
|
+
"-f",
|
|
148
|
+
"--invalidation-mode",
|
|
149
|
+
"unchecked-hash",
|
|
150
|
+
"-s",
|
|
151
|
+
str(stripdir),
|
|
152
|
+
]
|
|
153
|
+
if legacy:
|
|
154
|
+
argv.append("-b")
|
|
155
|
+
argv.append(str(target))
|
|
156
|
+
try:
|
|
157
|
+
return subprocess.run(argv, check=False).returncode == 0
|
|
158
|
+
except OSError as exc:
|
|
159
|
+
raise PycompileError(
|
|
160
|
+
f"could not run {' '.join(compiler)} to byte-compile {target.name}: {exc}"
|
|
161
|
+
) from exc
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""``kivyforge`` — the kivyforge 3.0 command-line interface.
|
|
2
|
+
|
|
3
|
+
A slim click-based dispatcher. Each verb lives in its own module under
|
|
4
|
+
``kivyforge.cli`` and is registered onto the top-level group here.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
from .. import __version__
|
|
12
|
+
from . import (
|
|
13
|
+
build,
|
|
14
|
+
clean,
|
|
15
|
+
doctor,
|
|
16
|
+
init,
|
|
17
|
+
lock,
|
|
18
|
+
open_cmd,
|
|
19
|
+
package,
|
|
20
|
+
run,
|
|
21
|
+
status,
|
|
22
|
+
upgrade,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.group(
|
|
27
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
28
|
+
help=(
|
|
29
|
+
"kivyforge 3.0 — a declarative iOS bundler for Kivy apps.\n\n"
|
|
30
|
+
"Run commands from the directory that contains your pyproject.toml."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
@click.version_option(__version__, "-V", "--version", prog_name="kivyforge")
|
|
34
|
+
def main() -> None:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Real 3.0 verbs.
|
|
39
|
+
main.add_command(init.init)
|
|
40
|
+
main.add_command(lock.lock)
|
|
41
|
+
main.add_command(build.build)
|
|
42
|
+
main.add_command(run.run)
|
|
43
|
+
main.add_command(open_cmd.open_)
|
|
44
|
+
main.add_command(package.package)
|
|
45
|
+
main.add_command(upgrade.upgrade)
|
|
46
|
+
main.add_command(clean.clean)
|
|
47
|
+
main.add_command(status.status)
|
|
48
|
+
main.add_command(doctor.doctor)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|
kivyforge/cli/_common.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Shared helpers for the ``kivyforge`` CLI.
|
|
2
|
+
|
|
3
|
+
All verbs look for ``pyproject.toml`` in the **current working directory
|
|
4
|
+
only** — no parent-directory traversal (per spec 05).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
PYPROJECT_NAME = "pyproject.toml"
|
|
14
|
+
# iOS lockfile name, kept for the iOS verbs' backward-compatible call sites.
|
|
15
|
+
# New/platform-aware code uses ``lockfile_name``/``lockfile_path_for``.
|
|
16
|
+
LOCKFILE_NAME = "pylock.ios.toml"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def lockfile_name(platform: str) -> str:
|
|
20
|
+
"""The per-platform lockfile filename, e.g. ``pylock.macos.toml``."""
|
|
21
|
+
return f"pylock.{platform}.toml"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ToolchainError(click.ClickException):
|
|
25
|
+
"""A user-facing error that prints cleanly and exits non-zero.
|
|
26
|
+
|
|
27
|
+
Unlike a bare exception, ``click`` renders this as ``Error: <message>``
|
|
28
|
+
without a traceback, which is what we want for expected failure modes
|
|
29
|
+
(missing pyproject, validation errors, drift, etc.).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
exit_code = 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def find_pyproject(start: Path | None = None) -> Path:
|
|
36
|
+
"""Return the path to ``pyproject.toml`` in the current directory.
|
|
37
|
+
|
|
38
|
+
Raises ``ToolchainError`` if it is not present. No parent traversal is
|
|
39
|
+
performed — the tool is intentionally CWD-scoped so it never picks up an
|
|
40
|
+
unrelated ``pyproject.toml`` from an ancestor directory.
|
|
41
|
+
"""
|
|
42
|
+
base = Path.cwd() if start is None else start
|
|
43
|
+
candidate = base / PYPROJECT_NAME
|
|
44
|
+
if not candidate.is_file():
|
|
45
|
+
raise ToolchainError(
|
|
46
|
+
f"no {PYPROJECT_NAME} found in the current directory ({base}).\n"
|
|
47
|
+
f" Run kivyforge commands from the directory that contains your "
|
|
48
|
+
f"{PYPROJECT_NAME}, or run `kivyforge init` to create one."
|
|
49
|
+
)
|
|
50
|
+
return candidate
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def lockfile_path(start: Path | None = None) -> Path:
|
|
54
|
+
"""Return the path where ``pylock.ios.toml`` lives (sibling to pyproject)."""
|
|
55
|
+
base = Path.cwd() if start is None else start
|
|
56
|
+
return base / LOCKFILE_NAME
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def lockfile_path_for(platform: str, start: Path | None = None) -> Path:
|
|
60
|
+
"""Return the ``pylock.<platform>.toml`` path (sibling to pyproject)."""
|
|
61
|
+
base = Path.cwd() if start is None else start
|
|
62
|
+
return base / lockfile_name(platform)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""CLI glue for target-platform selection (common design doc 02).
|
|
2
|
+
|
|
3
|
+
Provides the shared ``-p/--platform`` option and ``resolve_target``, which reads
|
|
4
|
+
which ``[tool.kivy.<platform>]`` overlays a project configures and runs the
|
|
5
|
+
resolution chain, surfacing failures as clean ``ToolchainError`` messages.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import tomllib
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from ..platforms import (
|
|
17
|
+
Platform,
|
|
18
|
+
PlatformResolutionError,
|
|
19
|
+
available_platform_names,
|
|
20
|
+
)
|
|
21
|
+
from ..platforms import (
|
|
22
|
+
resolve_target as _resolve_target,
|
|
23
|
+
)
|
|
24
|
+
from ._common import ToolchainError, find_pyproject
|
|
25
|
+
|
|
26
|
+
platform_option = click.option(
|
|
27
|
+
"--platform",
|
|
28
|
+
"-p",
|
|
29
|
+
"cli_platform",
|
|
30
|
+
type=click.Choice(available_platform_names()),
|
|
31
|
+
default=None,
|
|
32
|
+
help="Target platform (overrides KIVYFORGE_PLATFORM and the host default).",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def configured_platforms(pyproject: Path) -> set[str]:
|
|
37
|
+
"""Platform names whose ``[tool.kivy.<name>]`` overlay this project declares."""
|
|
38
|
+
try:
|
|
39
|
+
raw = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
40
|
+
except (OSError, tomllib.TOMLDecodeError):
|
|
41
|
+
return set()
|
|
42
|
+
kivy = raw.get("tool", {}).get("kivy", {})
|
|
43
|
+
if not isinstance(kivy, dict):
|
|
44
|
+
return set()
|
|
45
|
+
return {name for name in available_platform_names() if name in kivy}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def reject_android_only(backend: Platform, options: dict[str, object]) -> None:
|
|
49
|
+
"""Fail when Android-only options are used against another platform.
|
|
50
|
+
|
|
51
|
+
The shared verbs carry the full option superset, so an option that only
|
|
52
|
+
Android acts on would otherwise be silently ignored elsewhere — the worst
|
|
53
|
+
outcome, since the user believes it took effect.
|
|
54
|
+
"""
|
|
55
|
+
used = [name for name, value in options.items() if value]
|
|
56
|
+
if not used or backend.name == "android":
|
|
57
|
+
return
|
|
58
|
+
raise ToolchainError(
|
|
59
|
+
f"{', '.join(used)} {'is' if len(used) == 1 else 'are'} "
|
|
60
|
+
f"Android-only; not valid for {backend.name}."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def resolve_target(
|
|
65
|
+
cli_platform: str | None, *, verb: str = "build"
|
|
66
|
+
) -> tuple[Platform, Path]:
|
|
67
|
+
"""Resolve the target platform and return it with the project root.
|
|
68
|
+
|
|
69
|
+
Looks for ``pyproject.toml`` in the CWD (like every verb), determines the
|
|
70
|
+
configured overlays, and runs the resolution chain. ``verb`` names the
|
|
71
|
+
calling CLI command so an unresolved-platform error's example command is
|
|
72
|
+
accurate (e.g. ``kivyforge lock --platform ios``, not always ``build``).
|
|
73
|
+
"""
|
|
74
|
+
pyproject = find_pyproject()
|
|
75
|
+
try:
|
|
76
|
+
backend = _resolve_target(
|
|
77
|
+
cli_platform,
|
|
78
|
+
configured=configured_platforms(pyproject),
|
|
79
|
+
env=os.environ,
|
|
80
|
+
verb=verb,
|
|
81
|
+
)
|
|
82
|
+
except PlatformResolutionError as exc:
|
|
83
|
+
raise ToolchainError(str(exc)) from exc
|
|
84
|
+
return backend, pyproject.parent
|
kivyforge/cli/build.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""``kivyforge build`` — materialize the lock into an Xcode project (specs 05/06).
|
|
2
|
+
|
|
3
|
+
Build performs, in order:
|
|
4
|
+
|
|
5
|
+
1. **Drift check** — the lock must be in sync with ``pyproject.toml``.
|
|
6
|
+
2-5. **Artifact collection** — Python.xcframework, pinned wheels into
|
|
7
|
+
``pip-deps/``, wheel-embedded + native ``.xcframework`` into ``Frameworks/``.
|
|
8
|
+
6. **Project materialization** — generate ``<app>-ios/`` and the ``.xcodeproj``.
|
|
9
|
+
7. **xcodebuild** — only when a target flag is given (Phase 6); a bare
|
|
10
|
+
``kivyforge build`` stops after step 6 with a ready-to-open project.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
from ._platform import platform_option, reject_android_only, resolve_target
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command()
|
|
21
|
+
@platform_option
|
|
22
|
+
@click.option(
|
|
23
|
+
"--simulator",
|
|
24
|
+
"target",
|
|
25
|
+
flag_value="simulator",
|
|
26
|
+
help="Build for the iOS simulator (Debug).",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--device", "target", flag_value="device", help="Build for a device (Debug)."
|
|
30
|
+
)
|
|
31
|
+
@click.option(
|
|
32
|
+
"--release", "target", flag_value="release", help="Archive + export a .ipa."
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--arch",
|
|
36
|
+
# x86_64 stays: it is the Linux arch and an Android ABI. It is not a macOS
|
|
37
|
+
# or iOS-simulator arch any more -- those backends reject it themselves.
|
|
38
|
+
type=click.Choice(["arm64", "x86_64"]),
|
|
39
|
+
default=None,
|
|
40
|
+
help="iOS: simulator arch. macOS: assemble a subset of the locked archs.",
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--no-verify-lock", is_flag=True, help="Skip the pyproject drift check (CI only)."
|
|
44
|
+
)
|
|
45
|
+
@click.option("--no-cache", is_flag=True, help="Force re-download of every artifact.")
|
|
46
|
+
@click.option(
|
|
47
|
+
"--team-id", default=None, help="Override [tool.kivy.ios.signing].team_id."
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--signing-identity",
|
|
51
|
+
default=None,
|
|
52
|
+
help="Override [tool.kivy.ios.signing].identity.",
|
|
53
|
+
)
|
|
54
|
+
@click.option(
|
|
55
|
+
"--export-method",
|
|
56
|
+
type=click.Choice(["app-store", "ad-hoc", "development"]),
|
|
57
|
+
default="app-store",
|
|
58
|
+
help="Export method (only meaningful with --release).",
|
|
59
|
+
)
|
|
60
|
+
# --- Android-only ---
|
|
61
|
+
@click.option(
|
|
62
|
+
"--debug",
|
|
63
|
+
is_flag=True,
|
|
64
|
+
help="Android: assemble the debug-signed artifact after generating.",
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"-f",
|
|
68
|
+
"--format",
|
|
69
|
+
"fmt",
|
|
70
|
+
type=click.Choice(["apk", "aab"]),
|
|
71
|
+
default=None,
|
|
72
|
+
help="Android (with --debug): debug artifact format (default apk).",
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--abi",
|
|
76
|
+
type=click.Choice(["arm64_v8a", "x86_64"]),
|
|
77
|
+
default=None,
|
|
78
|
+
help="Android: restrict this build to one ABI.",
|
|
79
|
+
)
|
|
80
|
+
def build(
|
|
81
|
+
cli_platform: str | None,
|
|
82
|
+
target: str | None,
|
|
83
|
+
arch: str | None,
|
|
84
|
+
no_verify_lock: bool,
|
|
85
|
+
no_cache: bool,
|
|
86
|
+
team_id: str | None,
|
|
87
|
+
signing_identity: str | None,
|
|
88
|
+
export_method: str,
|
|
89
|
+
debug: bool,
|
|
90
|
+
fmt: str | None,
|
|
91
|
+
abi: str | None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Download artifacts, generate the platform project, and optionally build it."""
|
|
94
|
+
# Resolve the target platform before any work so an unresolved target fails
|
|
95
|
+
# fast with an actionable message (common design doc 02).
|
|
96
|
+
backend, project_root = resolve_target(cli_platform, verb="build")
|
|
97
|
+
reject_android_only(backend, {"--debug": debug, "-f/--format": fmt, "--abi": abi})
|
|
98
|
+
|
|
99
|
+
backend.build(
|
|
100
|
+
project_root,
|
|
101
|
+
target=target,
|
|
102
|
+
arch=arch,
|
|
103
|
+
no_verify_lock=no_verify_lock,
|
|
104
|
+
no_cache=no_cache,
|
|
105
|
+
team_id=team_id,
|
|
106
|
+
signing_identity=signing_identity,
|
|
107
|
+
export_method=export_method,
|
|
108
|
+
debug=debug,
|
|
109
|
+
fmt=fmt,
|
|
110
|
+
abi=abi,
|
|
111
|
+
)
|