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,387 @@
|
|
|
1
|
+
"""Assemble an ``AndroidLockfile`` from a validated ``Config`` (android/02).
|
|
2
|
+
|
|
3
|
+
Resolution semantics, in the documented order: runtime per ABI (with the
|
|
4
|
+
``min_sdk >= min_api`` check), per-ABI wheels (fail-fasts live in the
|
|
5
|
+
resolver + the completeness check here), ``.aar``/``.jar`` pins, Gradle/Maven
|
|
6
|
+
pins (skipped without declared coordinates), ``include_files`` hashes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import dataclasses
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from kivyforge import __version__
|
|
17
|
+
from kivyforge.artifacts.verify import sha256_file
|
|
18
|
+
from kivyforge.config.model import AndroidConfig, Config
|
|
19
|
+
from kivyforge.lock.find_links import (
|
|
20
|
+
FindLinksError,
|
|
21
|
+
find_links_resolution_hint,
|
|
22
|
+
resolve_find_links,
|
|
23
|
+
validate_find_links,
|
|
24
|
+
wheel_path_from_project_root,
|
|
25
|
+
)
|
|
26
|
+
from kivyforge.lock.model import (
|
|
27
|
+
LockedPackage,
|
|
28
|
+
LockedWheel,
|
|
29
|
+
PackageDep,
|
|
30
|
+
canonical_name,
|
|
31
|
+
)
|
|
32
|
+
from kivyforge.lock.reader import compute_pyproject_sha256
|
|
33
|
+
|
|
34
|
+
from .archives import ArchiveResolverError, resolve_android_libs
|
|
35
|
+
from .maven import GradleMavenResolver, MavenResolverError, resolve_gradle_pins
|
|
36
|
+
from .model import (
|
|
37
|
+
DEFAULT_REQUIRES_PYTHON,
|
|
38
|
+
AndroidLockfile,
|
|
39
|
+
LockedIncludeFile,
|
|
40
|
+
PythonAndroidRuntime,
|
|
41
|
+
)
|
|
42
|
+
from .python_meta import (
|
|
43
|
+
PythonAndroidError,
|
|
44
|
+
PythonAndroidProvider,
|
|
45
|
+
PythonOrgAndroidProvider,
|
|
46
|
+
)
|
|
47
|
+
from .resolver import Resolver, ResolverError, get_resolver
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from kivyforge.artifacts.download import Downloader
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BuildError(Exception):
|
|
54
|
+
"""A lock build failure surfaced with an actionable message."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_lockfile(
|
|
58
|
+
config: Config,
|
|
59
|
+
pyproject_text: str,
|
|
60
|
+
*,
|
|
61
|
+
project_root: Path | None = None,
|
|
62
|
+
resolver: Resolver | None = None,
|
|
63
|
+
python_provider: PythonAndroidProvider | None = None,
|
|
64
|
+
maven_resolver: GradleMavenResolver | None = None,
|
|
65
|
+
lib_downloader: Downloader | None = None,
|
|
66
|
+
offline: bool = False,
|
|
67
|
+
now: datetime | None = None,
|
|
68
|
+
) -> AndroidLockfile:
|
|
69
|
+
if config.android is None:
|
|
70
|
+
raise BuildError(
|
|
71
|
+
"pyproject.toml has no [tool.kivy.android] table; nothing to lock."
|
|
72
|
+
)
|
|
73
|
+
android = config.android
|
|
74
|
+
resolver = resolver or get_resolver("pip")
|
|
75
|
+
python_provider = python_provider or PythonOrgAndroidProvider()
|
|
76
|
+
root = (project_root or Path.cwd()).resolve()
|
|
77
|
+
|
|
78
|
+
python_version = android.python_required.version
|
|
79
|
+
|
|
80
|
+
find_links_entries = android.find_links
|
|
81
|
+
try:
|
|
82
|
+
validate_find_links(root, find_links_entries, platform="android")
|
|
83
|
+
except FindLinksError as exc:
|
|
84
|
+
raise BuildError(str(exc)) from exc
|
|
85
|
+
find_links = resolve_find_links(root, find_links_entries)
|
|
86
|
+
|
|
87
|
+
runtimes = _resolve_runtimes(
|
|
88
|
+
android, python_version, python_provider, offline=offline
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
direct = {canonical_name(_req_name(d)) for d in config.project.dependencies}
|
|
92
|
+
excluded = {canonical_name(e) for e in android.exclude}
|
|
93
|
+
# You can't exclude what you explicitly depend on (android/01 §exclude).
|
|
94
|
+
excluded -= direct
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
resolved = resolver.resolve(
|
|
98
|
+
list(config.project.dependencies),
|
|
99
|
+
python_version=python_version,
|
|
100
|
+
min_sdk=android.min_sdk,
|
|
101
|
+
abis=tuple(android.abis),
|
|
102
|
+
extra_index_urls=list(android.extra_index_urls),
|
|
103
|
+
find_links=find_links,
|
|
104
|
+
offline=offline,
|
|
105
|
+
)
|
|
106
|
+
except ResolverError as exc:
|
|
107
|
+
hint = find_links_resolution_hint(
|
|
108
|
+
root, find_links_entries, pip_stderr=str(exc), platform="android"
|
|
109
|
+
)
|
|
110
|
+
if hint:
|
|
111
|
+
raise BuildError(f"{exc}\n{hint}") from exc
|
|
112
|
+
raise BuildError(str(exc)) from exc
|
|
113
|
+
|
|
114
|
+
packages = []
|
|
115
|
+
for rp in resolved:
|
|
116
|
+
if canonical_name(rp.name) in excluded:
|
|
117
|
+
continue
|
|
118
|
+
wheels = tuple(
|
|
119
|
+
_locked_wheel_from_resolved(w, project_root=root) for w in rp.wheels
|
|
120
|
+
)
|
|
121
|
+
_check_abis_complete(rp.name, wheels, android.min_sdk, android.abis)
|
|
122
|
+
packages.append(
|
|
123
|
+
LockedPackage(
|
|
124
|
+
name=rp.name,
|
|
125
|
+
version=rp.version,
|
|
126
|
+
wheels=wheels,
|
|
127
|
+
requires_python=rp.requires_python,
|
|
128
|
+
dependencies=tuple(PackageDep(name=d) for d in rp.dependencies),
|
|
129
|
+
direct_requirement=canonical_name(rp.name) in direct,
|
|
130
|
+
source_index=rp.source_index,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
android_libs = resolve_android_libs(
|
|
136
|
+
android.aars + android.jars,
|
|
137
|
+
project_root=root,
|
|
138
|
+
downloader=lib_downloader,
|
|
139
|
+
offline=offline,
|
|
140
|
+
)
|
|
141
|
+
except ArchiveResolverError as exc:
|
|
142
|
+
raise BuildError(str(exc)) from exc
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
gradle_pins = resolve_gradle_pins(
|
|
146
|
+
android.gradle, resolver=maven_resolver, offline=offline
|
|
147
|
+
)
|
|
148
|
+
except MavenResolverError as exc:
|
|
149
|
+
raise BuildError(str(exc)) from exc
|
|
150
|
+
|
|
151
|
+
include_files = _hash_include_files(android, root)
|
|
152
|
+
|
|
153
|
+
return AndroidLockfile(
|
|
154
|
+
requires_python=config.project.requires_python or DEFAULT_REQUIRES_PYTHON,
|
|
155
|
+
packages=tuple(packages),
|
|
156
|
+
python_android=tuple(runtimes),
|
|
157
|
+
kivyforge_version=__version__,
|
|
158
|
+
generated_at=(now or datetime.now(UTC)).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
159
|
+
pyproject_sha256=compute_pyproject_sha256(pyproject_text),
|
|
160
|
+
tool_kivy_android_schema_version=android.schema_version,
|
|
161
|
+
kivy_generation=android.kivy_generation,
|
|
162
|
+
android_libs=tuple(android_libs),
|
|
163
|
+
gradle=gradle_pins,
|
|
164
|
+
include_files=tuple(include_files),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _resolve_runtimes(
|
|
169
|
+
android: AndroidConfig,
|
|
170
|
+
python_version: str,
|
|
171
|
+
provider: PythonAndroidProvider,
|
|
172
|
+
*,
|
|
173
|
+
offline: bool,
|
|
174
|
+
) -> list[PythonAndroidRuntime]:
|
|
175
|
+
runtimes: list[PythonAndroidRuntime] = []
|
|
176
|
+
for abi in android.abis:
|
|
177
|
+
try:
|
|
178
|
+
info = provider.get(python_version, abi, offline=offline)
|
|
179
|
+
except PythonAndroidError as exc:
|
|
180
|
+
raise BuildError(str(exc)) from exc
|
|
181
|
+
if android.min_sdk < info.min_api:
|
|
182
|
+
raise BuildError(
|
|
183
|
+
f"[tool.kivy.android].min_sdk {android.min_sdk} is below the "
|
|
184
|
+
f"python.org runtime's minimum API {info.min_api} "
|
|
185
|
+
f"(Python {python_version}, {abi}).\n"
|
|
186
|
+
f" Raise min_sdk to at least {info.min_api}."
|
|
187
|
+
)
|
|
188
|
+
runtimes.append(
|
|
189
|
+
PythonAndroidRuntime(
|
|
190
|
+
version=info.version,
|
|
191
|
+
abi=info.abi,
|
|
192
|
+
url=info.url,
|
|
193
|
+
sha256=info.sha256,
|
|
194
|
+
min_api=info.min_api,
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
return runtimes
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _hash_include_files(android: AndroidConfig, root: Path) -> list[LockedIncludeFile]:
|
|
201
|
+
"""One pin per staged file; a directory source expands to its files."""
|
|
202
|
+
pins: list[LockedIncludeFile] = []
|
|
203
|
+
for entry in android.include_files:
|
|
204
|
+
for source in entry.sources:
|
|
205
|
+
local = (root / source).resolve()
|
|
206
|
+
if local.is_dir():
|
|
207
|
+
for child in sorted(local.rglob("*")):
|
|
208
|
+
if child.is_file():
|
|
209
|
+
rel = (Path(source) / child.relative_to(local)).as_posix()
|
|
210
|
+
pins.append(
|
|
211
|
+
LockedIncludeFile(
|
|
212
|
+
source=rel,
|
|
213
|
+
dest=entry.dest,
|
|
214
|
+
sha256=sha256_file(child),
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
elif local.is_file():
|
|
218
|
+
pins.append(
|
|
219
|
+
LockedIncludeFile(
|
|
220
|
+
source=Path(source).as_posix(),
|
|
221
|
+
dest=entry.dest,
|
|
222
|
+
sha256=sha256_file(local),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
raise BuildError(
|
|
227
|
+
f"include_files source does not exist: {source} "
|
|
228
|
+
f"(resolved to {local})"
|
|
229
|
+
)
|
|
230
|
+
pins.sort(key=lambda p: (p.dest, p.source))
|
|
231
|
+
return pins
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _locked_wheel_from_resolved(w, *, project_root: Path) -> LockedWheel:
|
|
235
|
+
url, path = _normalize_wheel_source(w.url, project_root=project_root)
|
|
236
|
+
sha256 = w.sha256
|
|
237
|
+
if not sha256 and path:
|
|
238
|
+
sha256 = sha256_file((project_root / path).resolve())
|
|
239
|
+
if not sha256:
|
|
240
|
+
raise BuildError(
|
|
241
|
+
f"could not determine SHA-256 for wheel {w.filename!r}; "
|
|
242
|
+
f"re-run lock with network access or check the vendored file."
|
|
243
|
+
)
|
|
244
|
+
return LockedWheel(
|
|
245
|
+
name=w.filename,
|
|
246
|
+
url=url,
|
|
247
|
+
path=path,
|
|
248
|
+
sha256=sha256,
|
|
249
|
+
upload_time=w.upload_time,
|
|
250
|
+
size=w.size,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _normalize_wheel_source(
|
|
255
|
+
url: str, *, project_root: Path
|
|
256
|
+
) -> tuple[str | None, str | None]:
|
|
257
|
+
from urllib.parse import urlparse
|
|
258
|
+
from urllib.request import url2pathname
|
|
259
|
+
|
|
260
|
+
if url.startswith(("http://", "https://")):
|
|
261
|
+
return url, None
|
|
262
|
+
if url.startswith("file:"):
|
|
263
|
+
local = Path(url2pathname(urlparse(url).path))
|
|
264
|
+
else:
|
|
265
|
+
local = Path(url)
|
|
266
|
+
resolved = local.resolve()
|
|
267
|
+
root = project_root.resolve()
|
|
268
|
+
try:
|
|
269
|
+
rel = wheel_path_from_project_root(root, resolved)
|
|
270
|
+
except FindLinksError as exc:
|
|
271
|
+
raise BuildError(
|
|
272
|
+
f"wheel resolved to {resolved}, which is outside the allowed "
|
|
273
|
+
f"find_links scope for project directory {root}.\n"
|
|
274
|
+
f" Vendored wheels must live under the project directory, a "
|
|
275
|
+
f"sibling directory, or the enclosing repository."
|
|
276
|
+
) from exc
|
|
277
|
+
return None, rel
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _check_abis_complete(
|
|
281
|
+
name: str,
|
|
282
|
+
wheels: tuple[LockedWheel, ...],
|
|
283
|
+
min_sdk: int,
|
|
284
|
+
abis: tuple[str, ...],
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Fail fast if a compiled package is missing a targeted ABI (android/02).
|
|
287
|
+
|
|
288
|
+
Compatibility rule: an ``android_<api>_<abi>`` wheel covers a targeted ABI
|
|
289
|
+
when its tag API level is <= ``min_sdk`` (the tag is a floor, android/01
|
|
290
|
+
§find_links) and the ABI matches exactly. Pure-Python packages are complete
|
|
291
|
+
with their single ``py3-none-any`` entry.
|
|
292
|
+
"""
|
|
293
|
+
if any(w.is_pure_python for w in wheels):
|
|
294
|
+
return
|
|
295
|
+
covered: set[str] = set()
|
|
296
|
+
for wheel in wheels:
|
|
297
|
+
tag = wheel.platform_tag # e.g. "android_24_arm64_v8a"
|
|
298
|
+
parts = tag.split("_", 2) # ["android", "24", "arm64_v8a"]
|
|
299
|
+
if parts[0] != "android" or len(parts) != 3:
|
|
300
|
+
continue
|
|
301
|
+
try:
|
|
302
|
+
api = int(parts[1])
|
|
303
|
+
except ValueError:
|
|
304
|
+
continue
|
|
305
|
+
if api <= min_sdk and parts[2] in abis:
|
|
306
|
+
covered.add(parts[2])
|
|
307
|
+
missing = [abi for abi in abis if abi not in covered]
|
|
308
|
+
if missing:
|
|
309
|
+
tags = ", ".join(f"android_{min_sdk}_{abi}" for abi in missing)
|
|
310
|
+
raise BuildError(
|
|
311
|
+
f"{name} is missing Android wheel slice(s): {tags}.\n"
|
|
312
|
+
f" A compiled package must publish (or vendor via find_links) a "
|
|
313
|
+
f"wheel for every targeted ABI to be locked reproducibly; narrow "
|
|
314
|
+
f"[tool.kivy.android].abis or supply the missing wheel (android/03)."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _normalized(lock: AndroidLockfile) -> AndroidLockfile:
|
|
319
|
+
"""Sort every resolver-derived collection, ignoring ``generated_at``.
|
|
320
|
+
|
|
321
|
+
``writer.py`` always sorts these before serializing, but a freshly
|
|
322
|
+
resolved lockfile (as ``--check`` builds to compare against) reflects
|
|
323
|
+
resolver-graph order instead -- same content, different tuple order.
|
|
324
|
+
Plain dataclass equality is order-sensitive, so comparing raw objects
|
|
325
|
+
reports a same-content lock as stale (found comparing a written lock
|
|
326
|
+
against itself, re-resolved). Sorting both sides by the writer's own
|
|
327
|
+
keys before comparing makes the comparison mean what "in sync" should:
|
|
328
|
+
same content, not same resolver iteration order.
|
|
329
|
+
"""
|
|
330
|
+
gradle = dataclasses.replace(
|
|
331
|
+
lock.gradle,
|
|
332
|
+
resolved=tuple(
|
|
333
|
+
dataclasses.replace(
|
|
334
|
+
module, artifacts=tuple(sorted(module.artifacts, key=lambda a: a.name))
|
|
335
|
+
)
|
|
336
|
+
for module in sorted(lock.gradle.resolved, key=lambda m: m.coordinate)
|
|
337
|
+
),
|
|
338
|
+
)
|
|
339
|
+
return dataclasses.replace(
|
|
340
|
+
lock,
|
|
341
|
+
generated_at="",
|
|
342
|
+
packages=tuple(sorted(lock.packages, key=lambda p: p.sort_key)),
|
|
343
|
+
python_android=tuple(sorted(lock.python_android, key=lambda r: r.abi)),
|
|
344
|
+
android_libs=tuple(
|
|
345
|
+
sorted(lock.android_libs, key=lambda x: (x.name.lower(), x.version))
|
|
346
|
+
),
|
|
347
|
+
include_files=tuple(
|
|
348
|
+
sorted(lock.include_files, key=lambda p: (p.dest, p.source))
|
|
349
|
+
),
|
|
350
|
+
gradle=gradle,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def semantic_equal(a: AndroidLockfile, b: AndroidLockfile) -> bool:
|
|
355
|
+
"""Compare two lockfiles ignoring volatile timestamps and tuple order."""
|
|
356
|
+
return _normalized(a) == _normalized(b)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def diff_summary(old: AndroidLockfile, new: AndroidLockfile) -> list[str]:
|
|
360
|
+
"""Human-readable summary of what changed between two locks (for --check)."""
|
|
361
|
+
out: list[str] = []
|
|
362
|
+
old_pkgs = {canonical_name(p.name): p for p in old.packages}
|
|
363
|
+
new_pkgs = {canonical_name(p.name): p for p in new.packages}
|
|
364
|
+
for name in sorted(new_pkgs.keys() - old_pkgs.keys()):
|
|
365
|
+
out.append(f" + {new_pkgs[name].name} {new_pkgs[name].version} (added)")
|
|
366
|
+
for name in sorted(old_pkgs.keys() - new_pkgs.keys()):
|
|
367
|
+
out.append(f" - {old_pkgs[name].name} {old_pkgs[name].version} (removed)")
|
|
368
|
+
for name in sorted(old_pkgs.keys() & new_pkgs.keys()):
|
|
369
|
+
if old_pkgs[name].version != new_pkgs[name].version:
|
|
370
|
+
out.append(
|
|
371
|
+
f" ~ {new_pkgs[name].name}: "
|
|
372
|
+
f"{old_pkgs[name].version} -> {new_pkgs[name].version}"
|
|
373
|
+
)
|
|
374
|
+
old_rt = {r.abi: r.version for r in old.python_android}
|
|
375
|
+
new_rt = {r.abi: r.version for r in new.python_android}
|
|
376
|
+
if old_rt != new_rt:
|
|
377
|
+
out.append(f" ~ python.org Android runtime: {old_rt} -> {new_rt}")
|
|
378
|
+
return out
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _req_name(requirement: str) -> str:
|
|
382
|
+
from packaging.requirements import InvalidRequirement, Requirement
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
return Requirement(requirement).name
|
|
386
|
+
except InvalidRequirement:
|
|
387
|
+
return requirement
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""The PEP 508 marker environment of the *target*, not the lock host (android/02).
|
|
2
|
+
|
|
3
|
+
pip's ``--platform`` / ``--abi`` / ``--python-version`` decide which wheel
|
|
4
|
+
*tags* are acceptable. They do **not** change how environment markers are
|
|
5
|
+
evaluated: pip evaluates every ``; sys_platform == "win32"``-style marker
|
|
6
|
+
against the interpreter running pip. Cross-resolving therefore inherits the
|
|
7
|
+
host's identity, and the same ``kivyforge lock -p android`` produces different
|
|
8
|
+
locks — or fails outright — depending on the machine that ran it.
|
|
9
|
+
|
|
10
|
+
The concrete failure that motivated this: Kivy declares
|
|
11
|
+
|
|
12
|
+
kivy-deps.angle~=0.4.0; sys_platform == "win32"
|
|
13
|
+
|
|
14
|
+
so locking for Android **on Windows** kept that requirement, looked for an
|
|
15
|
+
``android_24_arm64_v8a`` wheel of a Windows-only ANGLE build, and failed. The
|
|
16
|
+
same lock succeeded on Linux. A lock file that depends on who ran it is not a
|
|
17
|
+
lock file, so kivyforge supplies the environment explicitly instead.
|
|
18
|
+
|
|
19
|
+
Values follow CPython's own Android build: ``sys.platform`` is ``"android"``
|
|
20
|
+
(CPython 3.13+, PEP 738) and ``platform.system()`` is ``"Android"``.
|
|
21
|
+
``platform_release`` / ``platform_version`` are left empty on purpose — they
|
|
22
|
+
describe the device kernel, which is unknowable at lock time and which no
|
|
23
|
+
sane wheel should gate on.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
# Android ABI -> the machine name CPython reports there (``platform.machine()``).
|
|
29
|
+
ANDROID_ABI_MACHINE = {
|
|
30
|
+
"arm64_v8a": "aarch64",
|
|
31
|
+
"armeabi_v7a": "armv7l",
|
|
32
|
+
"x86_64": "x86_64",
|
|
33
|
+
"x86": "i686",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MarkerEnvironmentError(Exception):
|
|
38
|
+
"""The target marker environment could not be described."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def android_marker_environment(*, python_version: str, abi: str) -> dict[str, str]:
|
|
42
|
+
"""The PEP 508 environment a wheel would see on ``abi`` running Android.
|
|
43
|
+
|
|
44
|
+
``python_version`` is the full runtime version (e.g. ``3.14.6``).
|
|
45
|
+
"""
|
|
46
|
+
machine = ANDROID_ABI_MACHINE.get(abi)
|
|
47
|
+
if machine is None:
|
|
48
|
+
raise MarkerEnvironmentError(
|
|
49
|
+
f"unknown Android ABI {abi!r}; cannot describe its marker "
|
|
50
|
+
f"environment (known: {', '.join(sorted(ANDROID_ABI_MACHINE))})."
|
|
51
|
+
)
|
|
52
|
+
parts = python_version.split(".")
|
|
53
|
+
if len(parts) < 2:
|
|
54
|
+
raise MarkerEnvironmentError(
|
|
55
|
+
f"python version {python_version!r} is not a full X.Y.Z version; "
|
|
56
|
+
"cannot describe the target marker environment."
|
|
57
|
+
)
|
|
58
|
+
short = ".".join(parts[:2])
|
|
59
|
+
return {
|
|
60
|
+
"implementation_name": "cpython",
|
|
61
|
+
"implementation_version": python_version,
|
|
62
|
+
"os_name": "posix",
|
|
63
|
+
"platform_machine": machine,
|
|
64
|
+
"platform_python_implementation": "CPython",
|
|
65
|
+
"platform_release": "",
|
|
66
|
+
"platform_system": "Android",
|
|
67
|
+
"platform_version": "",
|
|
68
|
+
"python_full_version": python_version,
|
|
69
|
+
"python_version": short,
|
|
70
|
+
"sys_platform": "android",
|
|
71
|
+
}
|