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,393 @@
|
|
|
1
|
+
"""Assemble a ``Lockfile`` from a validated ``Config`` (spec 02 §"Resolution semantics")."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
from urllib.request import url2pathname
|
|
11
|
+
|
|
12
|
+
from kivyforge import __version__
|
|
13
|
+
from kivyforge.artifacts.verify import sha256_file
|
|
14
|
+
from kivyforge.config.model import Config, SwiftPackageDep, XcframeworkDep
|
|
15
|
+
from kivyforge.lock.find_links import (
|
|
16
|
+
FindLinksError,
|
|
17
|
+
find_links_resolution_hint,
|
|
18
|
+
resolve_find_links,
|
|
19
|
+
validate_find_links,
|
|
20
|
+
wheel_path_from_project_root,
|
|
21
|
+
)
|
|
22
|
+
from kivyforge.lock.model import (
|
|
23
|
+
LockedPackage,
|
|
24
|
+
LockedWheel,
|
|
25
|
+
PackageDep,
|
|
26
|
+
canonical_name,
|
|
27
|
+
)
|
|
28
|
+
from kivyforge.lock.reader import compute_pyproject_sha256
|
|
29
|
+
|
|
30
|
+
from .model import (
|
|
31
|
+
LockedSwiftPackage,
|
|
32
|
+
Lockfile,
|
|
33
|
+
PythonXcframework,
|
|
34
|
+
)
|
|
35
|
+
from .python_meta import PythonOrgProvider, PythonXcframeworkProvider
|
|
36
|
+
from .resolver import Resolver, ResolverError, get_resolver
|
|
37
|
+
from .spm import (
|
|
38
|
+
ResolvedSwiftPackage,
|
|
39
|
+
SpmResolver,
|
|
40
|
+
SpmResolverError,
|
|
41
|
+
get_spm_resolver,
|
|
42
|
+
)
|
|
43
|
+
from .xcframework import XcframeworkResolverError, resolve_xcframeworks
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from kivyforge.artifacts.download import Downloader
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BuildError(Exception):
|
|
50
|
+
"""A lock build failure surfaced with an actionable message."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def build_lockfile(
|
|
54
|
+
config: Config,
|
|
55
|
+
pyproject_text: str,
|
|
56
|
+
*,
|
|
57
|
+
project_root: Path | None = None,
|
|
58
|
+
resolver: Resolver | None = None,
|
|
59
|
+
python_provider: PythonXcframeworkProvider | None = None,
|
|
60
|
+
spm_resolver: SpmResolver | None = None,
|
|
61
|
+
xcframework_downloader: Downloader | None = None,
|
|
62
|
+
offline: bool = False,
|
|
63
|
+
now: datetime | None = None,
|
|
64
|
+
) -> Lockfile:
|
|
65
|
+
if config.ios is None:
|
|
66
|
+
raise BuildError(
|
|
67
|
+
"pyproject.toml has no [tool.kivy.ios] table; nothing to lock."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
resolver = resolver or get_resolver("pip")
|
|
71
|
+
python_provider = python_provider or PythonOrgProvider()
|
|
72
|
+
root = (project_root or Path.cwd()).resolve()
|
|
73
|
+
|
|
74
|
+
# [tool.kivy.ios.python].version is required (the loader raises a ConfigError
|
|
75
|
+
# when it is missing), so it is always set here. Never silently substitute a
|
|
76
|
+
# hidden default — that would pin an unexpected (and possibly unreleased)
|
|
77
|
+
# Python instead of surfacing the misconfig.
|
|
78
|
+
python_version = config.ios.python_version
|
|
79
|
+
if not python_version:
|
|
80
|
+
raise BuildError("missing required [tool.kivy.ios.python].version")
|
|
81
|
+
find_links_entries = config.ios.find_links
|
|
82
|
+
try:
|
|
83
|
+
validate_find_links(root, find_links_entries)
|
|
84
|
+
except FindLinksError as exc:
|
|
85
|
+
raise BuildError(str(exc)) from exc
|
|
86
|
+
find_links = resolve_find_links(root, find_links_entries)
|
|
87
|
+
py_info = python_provider.get(python_version, offline=offline)
|
|
88
|
+
|
|
89
|
+
# Rule 11: deployment_target must not be below the xcframework floor.
|
|
90
|
+
if _version_tuple(config.ios.deployment_target) < _version_tuple(py_info.ios_floor):
|
|
91
|
+
raise BuildError(
|
|
92
|
+
f"deployment_target {config.ios.deployment_target} is below the "
|
|
93
|
+
f"minimum iOS {py_info.ios_floor} required by Python "
|
|
94
|
+
f"{python_version}'s xcframework.\n"
|
|
95
|
+
f" Raise [tool.kivy.ios].deployment_target to at least "
|
|
96
|
+
f"{py_info.ios_floor}."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
direct = {canonical_name(_req_name(d)) for d in config.project.dependencies}
|
|
100
|
+
excluded = {canonical_name(e) for e in config.ios.exclude}
|
|
101
|
+
|
|
102
|
+
# Excluded names that are also direct requirements are silently dropped from
|
|
103
|
+
# the exclusion set — you can't exclude what you explicitly depend on.
|
|
104
|
+
excluded -= direct
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
resolved = resolver.resolve(
|
|
108
|
+
list(config.project.dependencies),
|
|
109
|
+
python_version=python_version,
|
|
110
|
+
deployment_target=config.ios.deployment_target,
|
|
111
|
+
extra_index_urls=list(config.ios.extra_index_urls),
|
|
112
|
+
find_links=find_links,
|
|
113
|
+
offline=offline,
|
|
114
|
+
simulator_archs=tuple(config.ios.simulator_archs),
|
|
115
|
+
)
|
|
116
|
+
except ResolverError as exc:
|
|
117
|
+
hint = find_links_resolution_hint(
|
|
118
|
+
root,
|
|
119
|
+
find_links_entries,
|
|
120
|
+
pip_stderr=str(exc),
|
|
121
|
+
)
|
|
122
|
+
if hint:
|
|
123
|
+
raise BuildError(f"{exc}\n{hint}") from exc
|
|
124
|
+
raise BuildError(str(exc)) from exc
|
|
125
|
+
|
|
126
|
+
packages = []
|
|
127
|
+
for rp in resolved:
|
|
128
|
+
if canonical_name(rp.name) in excluded:
|
|
129
|
+
continue
|
|
130
|
+
wheels = tuple(
|
|
131
|
+
_locked_wheel_from_resolved(w, project_root=root) for w in rp.wheels
|
|
132
|
+
)
|
|
133
|
+
_check_slices_complete(
|
|
134
|
+
rp.name,
|
|
135
|
+
wheels,
|
|
136
|
+
config.ios.deployment_target,
|
|
137
|
+
config.ios.simulator_archs,
|
|
138
|
+
)
|
|
139
|
+
packages.append(
|
|
140
|
+
LockedPackage(
|
|
141
|
+
name=rp.name,
|
|
142
|
+
version=rp.version,
|
|
143
|
+
wheels=wheels,
|
|
144
|
+
requires_python=rp.requires_python,
|
|
145
|
+
dependencies=tuple(PackageDep(name=d) for d in rp.dependencies),
|
|
146
|
+
direct_requirement=canonical_name(rp.name) in direct,
|
|
147
|
+
source_index=rp.source_index,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
xcframeworks = _resolve_native_xcframeworks(
|
|
152
|
+
config.ios.xcframeworks, xcframework_downloader, root, offline
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
swift_packages = _resolve_swift_packages(
|
|
156
|
+
config.ios.swift_packages, spm_resolver, root, offline
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return Lockfile(
|
|
160
|
+
requires_python=config.project.requires_python or ">=3.15",
|
|
161
|
+
packages=tuple(packages),
|
|
162
|
+
python_xcframework=PythonXcframework(
|
|
163
|
+
version=py_info.version, url=py_info.url, sha256=py_info.sha256
|
|
164
|
+
),
|
|
165
|
+
kivyforge_version=__version__,
|
|
166
|
+
generated_at=(now or datetime.now(UTC)).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
167
|
+
pyproject_sha256=compute_pyproject_sha256(pyproject_text),
|
|
168
|
+
tool_kivyforge_schema_version=config.ios.schema_version,
|
|
169
|
+
xcframeworks=tuple(xcframeworks),
|
|
170
|
+
swift_packages=tuple(swift_packages),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _resolve_native_xcframeworks(
|
|
175
|
+
declared: tuple[XcframeworkDep, ...],
|
|
176
|
+
downloader: Downloader | None,
|
|
177
|
+
project_root: Path,
|
|
178
|
+
offline: bool,
|
|
179
|
+
):
|
|
180
|
+
"""Resolve declared native xcframeworks; surface failures as ``BuildError``.
|
|
181
|
+
|
|
182
|
+
The resolver fetches/reads each archive, pins its SHA-256, and enumerates the
|
|
183
|
+
slices it ships, so the lock fully captures user-declared native SDKs instead
|
|
184
|
+
of silently dropping them.
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
return resolve_xcframeworks(
|
|
188
|
+
declared,
|
|
189
|
+
project_root=project_root,
|
|
190
|
+
downloader=downloader,
|
|
191
|
+
offline=offline,
|
|
192
|
+
)
|
|
193
|
+
except XcframeworkResolverError as exc:
|
|
194
|
+
raise BuildError(str(exc)) from exc
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _resolve_swift_packages(
|
|
198
|
+
declared: tuple[SwiftPackageDep, ...],
|
|
199
|
+
spm_resolver: SpmResolver | None,
|
|
200
|
+
project_root: Path,
|
|
201
|
+
offline: bool,
|
|
202
|
+
) -> list[LockedSwiftPackage]:
|
|
203
|
+
"""Resolve remote SPM packages to pins; assemble lock entries (sorted by name).
|
|
204
|
+
|
|
205
|
+
Local (``path``) packages need no resolution. The resolver is only built and
|
|
206
|
+
invoked when at least one remote package is declared, so a pure-wheel (or
|
|
207
|
+
local-only) project never requires the Swift toolchain.
|
|
208
|
+
"""
|
|
209
|
+
if not declared:
|
|
210
|
+
return []
|
|
211
|
+
remote = [p for p in declared if p.url]
|
|
212
|
+
resolved: dict[str, ResolvedSwiftPackage] = {}
|
|
213
|
+
if remote:
|
|
214
|
+
resolver = spm_resolver or get_spm_resolver()
|
|
215
|
+
try:
|
|
216
|
+
for r in resolver.resolve(
|
|
217
|
+
remote, project_root=project_root, offline=offline
|
|
218
|
+
):
|
|
219
|
+
resolved[r.name] = r
|
|
220
|
+
except SpmResolverError as exc:
|
|
221
|
+
raise BuildError(str(exc)) from exc
|
|
222
|
+
|
|
223
|
+
out: list[LockedSwiftPackage] = []
|
|
224
|
+
for pkg in declared:
|
|
225
|
+
pin = resolved.get(pkg.name)
|
|
226
|
+
if pkg.url and pin is None:
|
|
227
|
+
raise BuildError(
|
|
228
|
+
f"swift package {pkg.name!r} resolved to no revision; re-run lock "
|
|
229
|
+
f"with network access."
|
|
230
|
+
)
|
|
231
|
+
out.append(
|
|
232
|
+
LockedSwiftPackage(
|
|
233
|
+
name=pkg.name,
|
|
234
|
+
products=pkg.products,
|
|
235
|
+
url=pkg.url,
|
|
236
|
+
path=pkg.path,
|
|
237
|
+
requirement=pkg.requirement if pkg.url else None,
|
|
238
|
+
revision=pin.revision if pin else None,
|
|
239
|
+
version=pin.version if pin else None,
|
|
240
|
+
link=pkg.link,
|
|
241
|
+
embed=pkg.embed,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
out.sort(key=lambda s: s.name)
|
|
245
|
+
return out
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _locked_wheel_from_resolved(w, *, project_root: Path) -> LockedWheel:
|
|
249
|
+
url, path = _normalize_wheel_source(w.url, project_root=project_root)
|
|
250
|
+
sha256 = w.sha256
|
|
251
|
+
if not sha256 and path:
|
|
252
|
+
sha256 = sha256_file((project_root / path).resolve())
|
|
253
|
+
if not sha256:
|
|
254
|
+
raise BuildError(
|
|
255
|
+
f"could not determine SHA-256 for wheel {w.filename!r}; "
|
|
256
|
+
f"re-run lock with network access or check the vendored file."
|
|
257
|
+
)
|
|
258
|
+
return LockedWheel(
|
|
259
|
+
name=w.filename,
|
|
260
|
+
url=url,
|
|
261
|
+
path=path,
|
|
262
|
+
sha256=sha256,
|
|
263
|
+
upload_time=w.upload_time,
|
|
264
|
+
size=w.size,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _normalize_wheel_source(
|
|
269
|
+
url: str, *, project_root: Path
|
|
270
|
+
) -> tuple[str | None, str | None]:
|
|
271
|
+
if url.startswith(("http://", "https://")):
|
|
272
|
+
return url, None
|
|
273
|
+
if url.startswith("file:"):
|
|
274
|
+
# url2pathname turns a file URL path into a native path on every host
|
|
275
|
+
# (e.g. "/C:/x" -> "C:\\x" on Windows), which plain unquote+Path does not.
|
|
276
|
+
local = Path(url2pathname(urlparse(url).path))
|
|
277
|
+
else:
|
|
278
|
+
local = Path(url)
|
|
279
|
+
resolved = local.resolve()
|
|
280
|
+
root = project_root.resolve()
|
|
281
|
+
try:
|
|
282
|
+
rel = wheel_path_from_project_root(root, resolved)
|
|
283
|
+
except FindLinksError as exc:
|
|
284
|
+
raise BuildError(
|
|
285
|
+
f"wheel resolved to {resolved}, which is outside the allowed "
|
|
286
|
+
f"find_links scope for project directory {root}.\n"
|
|
287
|
+
f" Vendored wheels must live under the project directory, a "
|
|
288
|
+
f"sibling directory, or the enclosing repository "
|
|
289
|
+
f"(e.g. examples/wheels/ios/)."
|
|
290
|
+
) from exc
|
|
291
|
+
return None, rel
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _check_slices_complete(
|
|
295
|
+
name, wheels, deployment_target, simulator_archs=None
|
|
296
|
+
) -> None:
|
|
297
|
+
"""Fail fast if a compiled package is missing an iOS slice (spec 02).
|
|
298
|
+
|
|
299
|
+
The required slices are the device slice plus one per configured simulator
|
|
300
|
+
architecture (``[tool.kivy.ios].simulator_archs``), so a project that drops
|
|
301
|
+
``x86_64`` is not held to a slice it no longer targets.
|
|
302
|
+
|
|
303
|
+
Compatibility rule: a wheel tagged ios_<M>_<N>_<arch>_<sdk> satisfies a
|
|
304
|
+
required slice when M.N <= deployment_target AND the arch+sdk suffix
|
|
305
|
+
matches exactly. This mirrors select_wheel's logic: python.org's iOS
|
|
306
|
+
Python binary always tags wheels at its own minimum OS (currently 13.0)
|
|
307
|
+
regardless of IPHONEOS_DEPLOYMENT_TARGET used when compiling the extension.
|
|
308
|
+
"""
|
|
309
|
+
if any(w.is_pure_python for w in wheels):
|
|
310
|
+
return # pure-Python: single py3-none-any entry is complete
|
|
311
|
+
|
|
312
|
+
from .resolver import slice_suffixes
|
|
313
|
+
|
|
314
|
+
required = slice_suffixes(simulator_archs)
|
|
315
|
+
dt_parts = _version_tuple(deployment_target)
|
|
316
|
+
covered: set[str] = set()
|
|
317
|
+
for wheel in wheels:
|
|
318
|
+
tag = wheel.platform_tag # e.g. "ios_13_0_arm64_iphoneos"
|
|
319
|
+
parts = tag.split("_") # ["ios", "13", "0", "arm64", "iphoneos"]
|
|
320
|
+
if parts[0] != "ios" or len(parts) < 3:
|
|
321
|
+
continue
|
|
322
|
+
# Find where the arch+sdk suffix starts (last two underscore-joined segments).
|
|
323
|
+
for suffix in required:
|
|
324
|
+
if tag.endswith(f"_{suffix}"):
|
|
325
|
+
prefix_parts = tag[: -(len(suffix) + 1)].split("_") # ["ios","13","0"]
|
|
326
|
+
try:
|
|
327
|
+
wheel_ver = tuple(int(x) for x in prefix_parts[1:])
|
|
328
|
+
except ValueError:
|
|
329
|
+
break
|
|
330
|
+
if wheel_ver <= dt_parts:
|
|
331
|
+
covered.add(suffix)
|
|
332
|
+
break
|
|
333
|
+
|
|
334
|
+
missing_suffixes = set(required) - covered
|
|
335
|
+
if missing_suffixes:
|
|
336
|
+
dt = deployment_target.replace(".", "_")
|
|
337
|
+
missing_tags = sorted(f"ios_{dt}_{s}" for s in missing_suffixes)
|
|
338
|
+
raise BuildError(
|
|
339
|
+
f"{name} is missing iOS wheel slice(s): {', '.join(missing_tags)}.\n"
|
|
340
|
+
f" A compiled package must publish every targeted iOS slice "
|
|
341
|
+
f"(device + each configured simulator arch) to be locked reproducibly."
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def semantic_equal(a: Lockfile, b: Lockfile) -> bool:
|
|
346
|
+
"""Compare two lockfiles ignoring the volatile ``generated_at`` field.
|
|
347
|
+
|
|
348
|
+
Used by ``lock --check`` so a re-resolve that produces identical pins (but
|
|
349
|
+
a new timestamp) still reports "in sync".
|
|
350
|
+
"""
|
|
351
|
+
return dataclasses.replace(a, generated_at="") == dataclasses.replace(
|
|
352
|
+
b, generated_at=""
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def diff_summary(old: Lockfile, new: Lockfile) -> list[str]:
|
|
357
|
+
"""Human-readable summary of what changed between two locks (for --check)."""
|
|
358
|
+
out: list[str] = []
|
|
359
|
+
old_pkgs = {canonical_name(p.name): p for p in old.packages}
|
|
360
|
+
new_pkgs = {canonical_name(p.name): p for p in new.packages}
|
|
361
|
+
for name in sorted(new_pkgs.keys() - old_pkgs.keys()):
|
|
362
|
+
out.append(f" + {new_pkgs[name].name} {new_pkgs[name].version} (added)")
|
|
363
|
+
for name in sorted(old_pkgs.keys() - new_pkgs.keys()):
|
|
364
|
+
out.append(f" - {old_pkgs[name].name} {old_pkgs[name].version} (removed)")
|
|
365
|
+
for name in sorted(old_pkgs.keys() & new_pkgs.keys()):
|
|
366
|
+
if old_pkgs[name].version != new_pkgs[name].version:
|
|
367
|
+
out.append(
|
|
368
|
+
f" ~ {new_pkgs[name].name}: "
|
|
369
|
+
f"{old_pkgs[name].version} -> {new_pkgs[name].version}"
|
|
370
|
+
)
|
|
371
|
+
if old.python_xcframework.version != new.python_xcframework.version:
|
|
372
|
+
out.append(
|
|
373
|
+
f" ~ Python.xcframework: {old.python_xcframework.version} -> "
|
|
374
|
+
f"{new.python_xcframework.version}"
|
|
375
|
+
)
|
|
376
|
+
return out
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _req_name(requirement: str) -> str:
|
|
380
|
+
from packaging.requirements import InvalidRequirement, Requirement
|
|
381
|
+
|
|
382
|
+
try:
|
|
383
|
+
return Requirement(requirement).name
|
|
384
|
+
except InvalidRequirement:
|
|
385
|
+
return requirement
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _version_tuple(v: str) -> tuple[int, ...]:
|
|
389
|
+
parts = []
|
|
390
|
+
for chunk in v.split("."):
|
|
391
|
+
digits = "".join(c for c in chunk if c.isdigit())
|
|
392
|
+
parts.append(int(digits) if digits else 0)
|
|
393
|
+
return tuple(parts)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""The PEP 508 marker environment of the *target*, not the lock host (spec 02).
|
|
2
|
+
|
|
3
|
+
pip's ``--platform`` decides which wheel *tags* are acceptable. It does **not**
|
|
4
|
+
change how environment markers are evaluated: pip evaluates every
|
|
5
|
+
``; sys_platform == "darwin"``-style marker against the interpreter running
|
|
6
|
+
pip. Because iOS is always cross-resolved from macOS, that silently answers
|
|
7
|
+
every marker as **macOS** — so a dependency gated on ``sys_platform ==
|
|
8
|
+
"darwin"`` is wrongly pulled into an iOS lock, and one gated on
|
|
9
|
+
``sys_platform == "ios"`` is wrongly dropped.
|
|
10
|
+
|
|
11
|
+
That second case is the dangerous one: nothing fails at lock time, and the app
|
|
12
|
+
is simply missing a dependency at runtime. Unlike the Android equivalent (where
|
|
13
|
+
a Windows host produced a loud "no matching distribution" error), this failure
|
|
14
|
+
is silent on the only supported host, so restricting the workflow to macOS does
|
|
15
|
+
not fix it — the environment has to be described explicitly.
|
|
16
|
+
|
|
17
|
+
Values follow CPython's own iOS build (PEP 730): ``sys.platform`` is ``"ios"``
|
|
18
|
+
and ``platform.system()`` is ``"iOS"`` on both device and simulator. Markers
|
|
19
|
+
carry no device/simulator distinction — that lives in
|
|
20
|
+
``sys.implementation._multiarch``, which is not a marker name — so the two are
|
|
21
|
+
told apart here only by ``platform_machine``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MarkerEnvironmentError(Exception):
|
|
28
|
+
"""The target marker environment could not be described."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def machine_for_slice(slice_suffix: str) -> str:
|
|
32
|
+
"""``arm64_iphoneos`` -> ``arm64``; ``x86_64_iphonesimulator`` -> ``x86_64``."""
|
|
33
|
+
if slice_suffix.endswith("_iphoneos"):
|
|
34
|
+
return slice_suffix[: -len("_iphoneos")]
|
|
35
|
+
if slice_suffix.endswith("_iphonesimulator"):
|
|
36
|
+
return slice_suffix[: -len("_iphonesimulator")]
|
|
37
|
+
raise MarkerEnvironmentError(
|
|
38
|
+
f"unrecognised iOS slice {slice_suffix!r}; expected an "
|
|
39
|
+
"``<arch>_iphoneos`` or ``<arch>_iphonesimulator`` suffix."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def ios_marker_environment(*, python_version: str, slice_suffix: str) -> dict[str, str]:
|
|
44
|
+
"""The PEP 508 environment a wheel would see on the given iOS slice.
|
|
45
|
+
|
|
46
|
+
``python_version`` is the full runtime version (e.g. ``3.14.6``).
|
|
47
|
+
"""
|
|
48
|
+
machine = machine_for_slice(slice_suffix)
|
|
49
|
+
parts = python_version.split(".")
|
|
50
|
+
if len(parts) < 2:
|
|
51
|
+
raise MarkerEnvironmentError(
|
|
52
|
+
f"python version {python_version!r} is not a full X.Y.Z version; "
|
|
53
|
+
"cannot describe the target marker environment."
|
|
54
|
+
)
|
|
55
|
+
return {
|
|
56
|
+
"implementation_name": "cpython",
|
|
57
|
+
"implementation_version": python_version,
|
|
58
|
+
"os_name": "posix",
|
|
59
|
+
"platform_machine": machine,
|
|
60
|
+
"platform_python_implementation": "CPython",
|
|
61
|
+
# Device kernel details are unknowable at lock time, and no sane wheel
|
|
62
|
+
# gates on them.
|
|
63
|
+
"platform_release": "",
|
|
64
|
+
"platform_system": "iOS",
|
|
65
|
+
"platform_version": "",
|
|
66
|
+
"python_full_version": python_version,
|
|
67
|
+
"python_version": ".".join(parts[:2]),
|
|
68
|
+
"sys_platform": "ios",
|
|
69
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""In-memory model of ``pylock.ios.toml`` (spec 02).
|
|
2
|
+
|
|
3
|
+
PEP 751-shaped ``[[packages]]`` plus the iOS ``[tool.kivyforge]`` extension
|
|
4
|
+
(``python_xcframework``, xcframeworks, swift_packages). These dataclasses are
|
|
5
|
+
produced by the builder/resolver and serialized by ``writer``. The shared
|
|
6
|
+
``[[packages]]`` model (``LockedPackage``/``LockedWheel``) lives in
|
|
7
|
+
``kivyforge.lock.model``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from kivyforge.lock.model import LOCK_VERSION, LockedPackage
|
|
15
|
+
|
|
16
|
+
CREATED_BY = "kivyforge"
|
|
17
|
+
TOOL_SCHEMA_VERSION = 1
|
|
18
|
+
DEFAULT_REQUIRES_PYTHON = ">=3.15"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class PythonXcframework:
|
|
23
|
+
version: str
|
|
24
|
+
url: str
|
|
25
|
+
sha256: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class LockedXcframework:
|
|
30
|
+
"""One ``[[tool.kivyforge.xcframeworks]]`` entry."""
|
|
31
|
+
|
|
32
|
+
name: str
|
|
33
|
+
version: str
|
|
34
|
+
sha256: str
|
|
35
|
+
slices: tuple[str, ...]
|
|
36
|
+
url: str | None = None
|
|
37
|
+
path: str | None = None
|
|
38
|
+
archive_format: str = "zip"
|
|
39
|
+
archive_member: str | None = None
|
|
40
|
+
privacy_manifest_path: str | None = None
|
|
41
|
+
link: bool = True
|
|
42
|
+
embed: bool = True
|
|
43
|
+
source: str | None = None
|
|
44
|
+
|
|
45
|
+
def __post_init__(self) -> None:
|
|
46
|
+
if bool(self.url) == bool(self.path):
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"xcframework {self.name!r} must have exactly one of url/path"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class LockedSwiftPackage:
|
|
54
|
+
"""One ``[[tool.kivyforge.swift_packages]]`` entry (spec 07).
|
|
55
|
+
|
|
56
|
+
Unlike xcframeworks, kivyforge computes no output hash here: Xcode owns the
|
|
57
|
+
SPM lifecycle, so reproducibility is the pinned ``revision`` (+ the generated
|
|
58
|
+
``Package.resolved``). Exactly one of ``url`` (remote) or ``path`` (local) is
|
|
59
|
+
set; ``requirement``/``revision``/``version`` are remote-only.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
name: str
|
|
63
|
+
products: tuple[str, ...]
|
|
64
|
+
url: str | None = None
|
|
65
|
+
path: str | None = None
|
|
66
|
+
requirement: dict[str, object] | None = None
|
|
67
|
+
revision: str | None = None
|
|
68
|
+
version: str | None = None
|
|
69
|
+
link: bool = True
|
|
70
|
+
embed: bool = True
|
|
71
|
+
|
|
72
|
+
def __post_init__(self) -> None:
|
|
73
|
+
if bool(self.url) == bool(self.path):
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"swift package {self.name!r} must have exactly one of url/path"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True)
|
|
80
|
+
class Lockfile:
|
|
81
|
+
requires_python: str
|
|
82
|
+
packages: tuple[LockedPackage, ...]
|
|
83
|
+
python_xcframework: PythonXcframework
|
|
84
|
+
kivyforge_version: str
|
|
85
|
+
generated_at: str
|
|
86
|
+
pyproject_sha256: str
|
|
87
|
+
tool_kivyforge_schema_version: int
|
|
88
|
+
xcframeworks: tuple[LockedXcframework, ...] = ()
|
|
89
|
+
swift_packages: tuple[LockedSwiftPackage, ...] = ()
|
|
90
|
+
schema_version: int = TOOL_SCHEMA_VERSION
|
|
91
|
+
lock_version: str = LOCK_VERSION
|
|
92
|
+
created_by: str = CREATED_BY
|
|
93
|
+
extras: tuple[str, ...] = ()
|
|
94
|
+
dependency_groups: tuple[str, ...] = ()
|
|
95
|
+
default_groups: tuple[str, ...] = ()
|