kivyforge 0.1.0__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 +11 -0
- kivyforge/artifacts/__init__.py +41 -0
- kivyforge/artifacts/cache.py +81 -0
- kivyforge/artifacts/collect.py +424 -0
- kivyforge/artifacts/download.py +150 -0
- kivyforge/artifacts/frameworks.py +256 -0
- kivyforge/artifacts/include_files.py +538 -0
- kivyforge/artifacts/native_stage_util.py +374 -0
- kivyforge/artifacts/runtime.py +65 -0
- kivyforge/artifacts/verify.py +45 -0
- kivyforge/artifacts/wheels.py +92 -0
- kivyforge/build_outcome.py +111 -0
- kivyforge/bundle/__init__.py +1 -0
- kivyforge/bundle/app_metadata.py +334 -0
- kivyforge/bundle/casefold.py +88 -0
- kivyforge/bundle/entry_launcher.py +128 -0
- kivyforge/bundle/pycompile.py +323 -0
- kivyforge/bundle/workdir.py +131 -0
- kivyforge/capabilities.py +174 -0
- kivyforge/cli/__init__.py +55 -0
- kivyforge/cli/_common.py +171 -0
- kivyforge/cli/_output.py +164 -0
- kivyforge/cli/_platform.py +137 -0
- kivyforge/cli/_version.py +170 -0
- kivyforge/cli/build.py +168 -0
- kivyforge/cli/capabilities.py +63 -0
- kivyforge/cli/clean.py +179 -0
- kivyforge/cli/doctor.py +145 -0
- kivyforge/cli/init.py +863 -0
- kivyforge/cli/init_writer.py +773 -0
- kivyforge/cli/lock.py +472 -0
- kivyforge/cli/open_cmd.py +27 -0
- kivyforge/cli/package.py +199 -0
- kivyforge/cli/run.py +277 -0
- kivyforge/cli/status.py +96 -0
- kivyforge/cli/upgrade.py +354 -0
- kivyforge/config/__init__.py +47 -0
- kivyforge/config/errors.py +43 -0
- kivyforge/config/icons.py +60 -0
- kivyforge/config/keys.py +313 -0
- kivyforge/config/loader.py +4008 -0
- kivyforge/config/model.py +1091 -0
- kivyforge/config/version.py +124 -0
- kivyforge/doctor/__init__.py +19 -0
- kivyforge/doctor/checks_common.py +229 -0
- kivyforge/doctor/probe.py +544 -0
- kivyforge/doctor/result.py +71 -0
- kivyforge/host.py +29 -0
- kivyforge/lock/__init__.py +44 -0
- kivyforge/lock/_pip_shim.py +261 -0
- kivyforge/lock/find_links.py +202 -0
- kivyforge/lock/inputs.py +125 -0
- kivyforge/lock/model.py +113 -0
- kivyforge/lock/pep751.py +184 -0
- kivyforge/lock/pip_progress.py +74 -0
- kivyforge/lock/prefer.py +89 -0
- kivyforge/lock/reader.py +12 -0
- kivyforge/lock/resolver.py +142 -0
- kivyforge/lock/sources.py +513 -0
- kivyforge/lock/wheelruntime/__init__.py +70 -0
- kivyforge/lock/wheelruntime/builder.py +432 -0
- kivyforge/lock/wheelruntime/model.py +102 -0
- kivyforge/lock/wheelruntime/native_binaries.py +127 -0
- kivyforge/lock/wheelruntime/pbs_github.py +288 -0
- kivyforge/lock/wheelruntime/profile.py +105 -0
- kivyforge/lock/wheelruntime/resolver.py +331 -0
- kivyforge/lock/wheelruntime/runtime.py +171 -0
- kivyforge/lock/wheelruntime/serialize.py +270 -0
- kivyforge/net.py +21 -0
- kivyforge/platforms/__init__.py +114 -0
- kivyforge/platforms/android/__init__.py +258 -0
- kivyforge/platforms/android/adb.py +340 -0
- kivyforge/platforms/android/bootstrap/__init__.py +7 -0
- kivyforge/platforms/android/bootstrap/contract.py +215 -0
- kivyforge/platforms/android/bootstrap/render.py +408 -0
- kivyforge/platforms/android/bootstrap/templates/android_pkg/__init__.py +81 -0
- kivyforge/platforms/android/bootstrap/templates/android_pkg/config.py +27 -0
- kivyforge/platforms/android/bootstrap/templates/android_pkg/loadingscreen.py +6 -0
- kivyforge/platforms/android/bootstrap/templates/androidtest/KivyforgeContractTest.java +127 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivy_bootstrap.py +52 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_bootstrap.py +54 -0
- kivyforge/platforms/android/bootstrap/templates/bootstrap_py/_kivyforge_selftest.py +183 -0
- kivyforge/platforms/android/bootstrap/templates/cpp/CMakeLists.txt +20 -0
- kivyforge/platforms/android/bootstrap/templates/cpp/main.c +157 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/jnius/NativeInvocationHandler.java +48 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonActivity.java +319 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonBundle.java +135 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/kivy/android/PythonService.java +271 -0
- kivyforge/platforms/android/bootstrap/templates/java/org/renpy/android/Hardware.java +255 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/LICENSE +21 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/NOTICE +11 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/P4A_VENDOR.toml +107 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/android/activity.py +214 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/android/broadcast.py +103 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/android/darkmode.py +57 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/android/permissions.py +621 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/android/runnable.py +58 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/android/storage.py +117 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/java/PythonActivity.changeKeyboard.sdl3.java.in +9 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/java/org/kivy/android/GenericBroadcastReceiver.java +19 -0
- kivyforge/platforms/android/bootstrap/templates/p4a/java/org/kivy/android/GenericBroadcastReceiverCallback.java +9 -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 +2123 -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 +1691 -0
- kivyforge/platforms/android/doctor.py +1310 -0
- kivyforge/platforms/android/elf.py +267 -0
- kivyforge/platforms/android/generate/__init__.py +1 -0
- kivyforge/platforms/android/generate/launcher.py +49 -0
- kivyforge/platforms/android/generate/manifest.py +378 -0
- kivyforge/platforms/android/generate/project.py +539 -0
- kivyforge/platforms/android/generate/services.py +234 -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 +195 -0
- kivyforge/platforms/android/icons.py +223 -0
- kivyforge/platforms/android/lock/__init__.py +1 -0
- kivyforge/platforms/android/lock/archives.py +109 -0
- kivyforge/platforms/android/lock/builder.py +552 -0
- kivyforge/platforms/android/lock/generation.py +39 -0
- kivyforge/platforms/android/lock/markers.py +71 -0
- kivyforge/platforms/android/lock/maven.py +294 -0
- kivyforge/platforms/android/lock/model.py +106 -0
- kivyforge/platforms/android/lock/python_meta.py +226 -0
- kivyforge/platforms/android/lock/reader.py +195 -0
- kivyforge/platforms/android/lock/resolver.py +363 -0
- kivyforge/platforms/android/lock/writer.py +118 -0
- kivyforge/platforms/android/policy.py +240 -0
- kivyforge/platforms/android/sdk_packages.py +123 -0
- kivyforge/platforms/android/signing.py +155 -0
- kivyforge/platforms/android/smoke.py +46 -0
- kivyforge/platforms/android/splash.py +280 -0
- kivyforge/platforms/android/stage/__init__.py +1 -0
- kivyforge/platforms/android/stage/bundle.py +329 -0
- kivyforge/platforms/android/stage/conflicts.py +73 -0
- kivyforge/platforms/android/stage/jnilibs.py +332 -0
- kivyforge/platforms/android/stage/layouts.py +39 -0
- kivyforge/platforms/android/stage/runtime.py +61 -0
- kivyforge/platforms/android/stage/wheels.py +107 -0
- kivyforge/platforms/android/toolchain.py +41 -0
- kivyforge/platforms/base.py +226 -0
- kivyforge/platforms/ios/__init__.py +161 -0
- kivyforge/platforms/ios/assets.py +193 -0
- kivyforge/platforms/ios/buildsettings.py +315 -0
- kivyforge/platforms/ios/cli.py +788 -0
- kivyforge/platforms/ios/doctor.py +811 -0
- kivyforge/platforms/ios/entitlements.py +287 -0
- kivyforge/platforms/ios/generator.py +446 -0
- kivyforge/platforms/ios/includes.py +249 -0
- kivyforge/platforms/ios/legacy_module.py +94 -0
- kivyforge/platforms/ios/lock/__init__.py +79 -0
- kivyforge/platforms/ios/lock/builder.py +577 -0
- kivyforge/platforms/ios/lock/markers.py +69 -0
- kivyforge/platforms/ios/lock/model.py +74 -0
- kivyforge/platforms/ios/lock/python_meta.py +200 -0
- kivyforge/platforms/ios/lock/reader.py +173 -0
- kivyforge/platforms/ios/lock/resolver.py +388 -0
- kivyforge/platforms/ios/lock/spm.py +317 -0
- kivyforge/platforms/ios/lock/writer.py +86 -0
- kivyforge/platforms/ios/lock/xcframework.py +188 -0
- kivyforge/platforms/ios/materialize.py +67 -0
- kivyforge/platforms/ios/plist.py +91 -0
- kivyforge/platforms/ios/privacy.py +39 -0
- kivyforge/platforms/ios/skeleton.py +163 -0
- kivyforge/platforms/ios/sources.py +128 -0
- kivyforge/platforms/ios/staging.py +281 -0
- kivyforge/platforms/ios/swift_packages.py +324 -0
- kivyforge/platforms/ios/templates/kivyforge_bootstrap.h +42 -0
- kivyforge/platforms/ios/templates/kivyforge_bootstrap.m +225 -0
- kivyforge/platforms/ios/templates/kivyforge_native_modules.h.in +35 -0
- kivyforge/platforms/ios/templates/main.m +16 -0
- kivyforge/platforms/ios/templates/main_config.h.in +15 -0
- kivyforge/platforms/ios/xcframeworks.py +194 -0
- kivyforge/platforms/ios/xcode/__init__.py +80 -0
- kivyforge/platforms/ios/xcode/commands.py +329 -0
- kivyforge/platforms/ios/xcode/runner.py +424 -0
- kivyforge/platforms/linux/__init__.py +156 -0
- kivyforge/platforms/linux/appimage.py +229 -0
- kivyforge/platforms/linux/bundle.py +263 -0
- kivyforge/platforms/linux/cli.py +282 -0
- kivyforge/platforms/linux/desktop.py +73 -0
- kivyforge/platforms/linux/doctor.py +478 -0
- kivyforge/platforms/linux/elftools.py +95 -0
- kivyforge/platforms/linux/icons.py +117 -0
- kivyforge/platforms/linux/launcher.py +136 -0
- kivyforge/platforms/linux/lock/__init__.py +136 -0
- kivyforge/platforms/linux/lock/profile.py +254 -0
- kivyforge/platforms/linux/lock/runtime.py +81 -0
- kivyforge/platforms/linux/native_stage.py +47 -0
- kivyforge/platforms/linux/runtime_stage.py +128 -0
- kivyforge/platforms/linux/wheels_stage.py +103 -0
- kivyforge/platforms/macos/__init__.py +142 -0
- kivyforge/platforms/macos/bundle.py +337 -0
- kivyforge/platforms/macos/cli.py +324 -0
- kivyforge/platforms/macos/doctor.py +604 -0
- kivyforge/platforms/macos/icns.py +84 -0
- kivyforge/platforms/macos/includes.py +97 -0
- kivyforge/platforms/macos/launcher.py +231 -0
- kivyforge/platforms/macos/lock/__init__.py +152 -0
- kivyforge/platforms/macos/lock/profile.py +119 -0
- kivyforge/platforms/macos/lock/runtime.py +63 -0
- kivyforge/platforms/macos/machotools.py +402 -0
- kivyforge/platforms/macos/native_links.py +289 -0
- kivyforge/platforms/macos/native_stage.py +330 -0
- kivyforge/platforms/macos/notarize.py +151 -0
- kivyforge/platforms/macos/plist.py +43 -0
- kivyforge/platforms/macos/runtime_stage.py +130 -0
- kivyforge/platforms/macos/signing.py +84 -0
- kivyforge/platforms/macos/swift_packages.py +287 -0
- kivyforge/platforms/macos/wheels_stage.py +108 -0
- kivyforge/platforms/windows/__init__.py +154 -0
- kivyforge/platforms/windows/assets.py +76 -0
- kivyforge/platforms/windows/bundle.py +330 -0
- kivyforge/platforms/windows/cli.py +427 -0
- kivyforge/platforms/windows/doctor.py +575 -0
- kivyforge/platforms/windows/fsswap.py +68 -0
- kivyforge/platforms/windows/icons.py +52 -0
- kivyforge/platforms/windows/launcher/__init__.py +198 -0
- kivyforge/platforms/windows/launcher/build_launcher.py +583 -0
- kivyforge/platforms/windows/launcher/launcher.c +379 -0
- kivyforge/platforms/windows/lock/__init__.py +122 -0
- kivyforge/platforms/windows/lock/profile.py +101 -0
- kivyforge/platforms/windows/lock/runtime.py +69 -0
- kivyforge/platforms/windows/naming.py +52 -0
- kivyforge/platforms/windows/native_stage.py +66 -0
- kivyforge/platforms/windows/pathdepth.py +60 -0
- kivyforge/platforms/windows/petools.py +98 -0
- kivyforge/platforms/windows/rcedit.py +121 -0
- kivyforge/platforms/windows/runtime_stage.py +242 -0
- kivyforge/platforms/windows/signing.py +173 -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 +5 -0
- kivyforge/platforms/windows/vendor/__init__.py +12 -0
- kivyforge/platforms/windows/vendor/fetch_rcedit.py +99 -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/report/__init__.py +31 -0
- kivyforge/report/console.py +231 -0
- kivyforge/report/diagnostics.py +300 -0
- kivyforge/report/envelope.py +79 -0
- kivyforge/report/exit_codes.py +56 -0
- kivyforge/report/failures.py +164 -0
- kivyforge/report/streams.py +71 -0
- kivyforge/report/transfers.py +209 -0
- kivyforge/report/verbosity.py +74 -0
- kivyforge/status.py +179 -0
- kivyforge/streaming.py +92 -0
- kivyforge/versioning.py +285 -0
- kivyforge-0.1.0.dist-info/METADATA +578 -0
- kivyforge-0.1.0.dist-info/RECORD +282 -0
- kivyforge-0.1.0.dist-info/WHEEL +5 -0
- kivyforge-0.1.0.dist-info/entry_points.txt +3 -0
- kivyforge-0.1.0.dist-info/licenses/LICENSE +21 -0
- kivyforge-0.1.0.dist-info/top_level.txt +1 -0
kivyforge/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""kivyforge: build, run, and package Kivy apps for Android, iOS, macOS,
|
|
2
|
+
Windows, and Linux.
|
|
3
|
+
|
|
4
|
+
kivyforge reads a user-authored ``pyproject.toml`` (PEP 621 ``[project]``
|
|
5
|
+
plus ``[tool.kivy]`` and one ``[tool.kivy.<platform>]`` overlay per target),
|
|
6
|
+
resolves a PEP 751 ``pylock.<platform>.toml`` build manifest, and materializes
|
|
7
|
+
it into that platform's project or bundle. See ``docs/design`` for the full
|
|
8
|
+
specification.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""kivyforge artifact layer: fetch/cache/verify, wheels, frameworks, runtimes (spec 03)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .cache import ArtifactCache
|
|
6
|
+
from .download import Downloader, DownloadError, UrllibDownloader, fetch_artifact
|
|
7
|
+
from .frameworks import (
|
|
8
|
+
FrameworkConflict,
|
|
9
|
+
copy_wheel_frameworks,
|
|
10
|
+
extract_xcframework_archive,
|
|
11
|
+
)
|
|
12
|
+
from .runtime import (
|
|
13
|
+
PythonOrgRuntime,
|
|
14
|
+
PythonRuntime,
|
|
15
|
+
RuntimeArtifact,
|
|
16
|
+
get_runtime,
|
|
17
|
+
)
|
|
18
|
+
from .verify import HashMismatch, sha256_bytes, sha256_file, verify_file
|
|
19
|
+
from .wheels import BuildSlice, WheelSelectionError, select_wheel
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"ArtifactCache",
|
|
23
|
+
"DownloadError",
|
|
24
|
+
"Downloader",
|
|
25
|
+
"UrllibDownloader",
|
|
26
|
+
"fetch_artifact",
|
|
27
|
+
"FrameworkConflict",
|
|
28
|
+
"copy_wheel_frameworks",
|
|
29
|
+
"extract_xcframework_archive",
|
|
30
|
+
"PythonOrgRuntime",
|
|
31
|
+
"PythonRuntime",
|
|
32
|
+
"RuntimeArtifact",
|
|
33
|
+
"get_runtime",
|
|
34
|
+
"HashMismatch",
|
|
35
|
+
"sha256_bytes",
|
|
36
|
+
"sha256_file",
|
|
37
|
+
"verify_file",
|
|
38
|
+
"BuildSlice",
|
|
39
|
+
"WheelSelectionError",
|
|
40
|
+
"select_wheel",
|
|
41
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Content-addressed artifact cache (spec 03).
|
|
2
|
+
|
|
3
|
+
Artifacts (wheels, xcframework archives, Python.xcframework) are cached by
|
|
4
|
+
SHA-256 in a per-user, OS-appropriate cache directory so repeated builds and
|
|
5
|
+
``--no-cache`` opt-outs are cheap and deterministic. The cache key is the
|
|
6
|
+
content hash, so a tampered/replaced upstream artifact can never silently
|
|
7
|
+
satisfy a cache hit for a different hash.
|
|
8
|
+
|
|
9
|
+
The cache root follows each host's convention: ``~/Library/Caches/kivyforge``
|
|
10
|
+
on macOS, ``$XDG_CACHE_HOME/kivyforge`` (default ``~/.cache/kivyforge``) on
|
|
11
|
+
Linux and other Unixes, and ``%LOCALAPPDATA%\\kivyforge\\Cache`` on Windows.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import platform as _platform
|
|
18
|
+
import shutil
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
APP_NAME = "kivyforge"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _default_cache_root() -> Path:
|
|
25
|
+
"""The per-user artifact cache directory for the current host OS."""
|
|
26
|
+
system = _platform.system()
|
|
27
|
+
if system == "Darwin":
|
|
28
|
+
return Path.home() / "Library" / "Caches" / APP_NAME / "artifacts"
|
|
29
|
+
if system == "Windows":
|
|
30
|
+
base = os.environ.get("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")
|
|
31
|
+
return Path(base) / APP_NAME / "Cache" / "artifacts"
|
|
32
|
+
# Linux and other Unixes: the XDG Base Directory spec.
|
|
33
|
+
xdg = os.environ.get("XDG_CACHE_HOME")
|
|
34
|
+
base = Path(xdg) if xdg else (Path.home() / ".cache")
|
|
35
|
+
return base / APP_NAME / "artifacts"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
DEFAULT_CACHE_ROOT = _default_cache_root()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ArtifactCache:
|
|
42
|
+
def __init__(self, root: str | Path | None = None) -> None:
|
|
43
|
+
self.root = DEFAULT_CACHE_ROOT if root is None else Path(root)
|
|
44
|
+
|
|
45
|
+
def path_for(self, sha256: str, filename: str) -> Path:
|
|
46
|
+
# Shard by the first two hex chars to keep directories small.
|
|
47
|
+
return self.root / sha256[:2] / f"{sha256}-{filename}"
|
|
48
|
+
|
|
49
|
+
def get(self, sha256: str, filename: str) -> Path | None:
|
|
50
|
+
candidate = self.path_for(sha256, filename)
|
|
51
|
+
return candidate if candidate.is_file() else None
|
|
52
|
+
|
|
53
|
+
def find_by_filename(self, filename: str) -> Path | None:
|
|
54
|
+
"""Return a cached artifact path matching ``{sha256}-{filename}``, if any."""
|
|
55
|
+
if not self.root.is_dir():
|
|
56
|
+
return None
|
|
57
|
+
suffix = f"-{filename}"
|
|
58
|
+
for shard in self.root.iterdir():
|
|
59
|
+
if not shard.is_dir():
|
|
60
|
+
continue
|
|
61
|
+
for path in shard.iterdir():
|
|
62
|
+
if path.is_file() and path.name.endswith(suffix):
|
|
63
|
+
return path
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def put_file(self, src: str | Path, sha256: str, filename: str) -> Path:
|
|
67
|
+
dest = self.path_for(sha256, filename)
|
|
68
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
if not dest.is_file():
|
|
70
|
+
shutil.copyfile(src, dest)
|
|
71
|
+
return dest
|
|
72
|
+
|
|
73
|
+
def put_bytes(self, data: bytes, sha256: str, filename: str) -> Path:
|
|
74
|
+
dest = self.path_for(sha256, filename)
|
|
75
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
dest.write_bytes(data)
|
|
77
|
+
return dest
|
|
78
|
+
|
|
79
|
+
def clear(self) -> None:
|
|
80
|
+
if self.root.is_dir():
|
|
81
|
+
shutil.rmtree(self.root)
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"""Collect lockfile artifacts into the staging tree (build steps 2-5, spec 06).
|
|
2
|
+
|
|
3
|
+
Downloads/verifies the Python.xcframework, installs pinned wheels into
|
|
4
|
+
``pip-deps/``, copies wheel-embedded ``.frameworks/`` into ``Frameworks/``, and
|
|
5
|
+
extracts user-declared native xcframeworks; ``Frameworks/`` is rebuilt from the
|
|
6
|
+
lock every run. The subprocess runner is injectable so the orchestration can be
|
|
7
|
+
unit-tested without invoking pip.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import tarfile
|
|
16
|
+
import tempfile
|
|
17
|
+
import zipfile
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from ..build_outcome import discard_note
|
|
22
|
+
from ..bundle.pycompile import PycompileError, byte_compile, select_compiler
|
|
23
|
+
from ..config.model import DesktopBuildSettings
|
|
24
|
+
from ..lock.model import LockedWheel
|
|
25
|
+
from ..platforms.ios.lock.model import Lockfile
|
|
26
|
+
from ..report.diagnostics import BYTECOMPILE_NO_INTERP
|
|
27
|
+
from ..report.failures import ClassifiedError, reclassify
|
|
28
|
+
from .cache import ArtifactCache
|
|
29
|
+
from .download import Downloader, fetch_artifact
|
|
30
|
+
from .frameworks import (
|
|
31
|
+
PlacedFramework,
|
|
32
|
+
clear_staged_frameworks,
|
|
33
|
+
copy_wheel_frameworks,
|
|
34
|
+
extract_xcframework_archive,
|
|
35
|
+
)
|
|
36
|
+
from .wheels import BuildSlice, select_wheel
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CollectError(ClassifiedError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def pip_install_command(
|
|
44
|
+
wheel_files: list[str],
|
|
45
|
+
*,
|
|
46
|
+
target_dir: str,
|
|
47
|
+
platform_tag: str,
|
|
48
|
+
python_version: str,
|
|
49
|
+
abi: str,
|
|
50
|
+
python_executable: str | None = None,
|
|
51
|
+
) -> list[str]:
|
|
52
|
+
"""Build the pip cross-install command (spec 05 step 4); no re-resolution."""
|
|
53
|
+
py = python_executable or sys.executable
|
|
54
|
+
cmd = [
|
|
55
|
+
py,
|
|
56
|
+
"-m",
|
|
57
|
+
"pip",
|
|
58
|
+
"install",
|
|
59
|
+
# pip is the one tool here that can genuinely prompt: an index needing
|
|
60
|
+
# auth asks for credentials, which in CI hangs until the job times out.
|
|
61
|
+
"--no-input",
|
|
62
|
+
# pip would byte-compile what it installs with the *host* interpreter,
|
|
63
|
+
# whose .pyc the target's CPython cannot load. That includes a wheel's
|
|
64
|
+
# data files, whose __pycache__ then outlives them. A release build
|
|
65
|
+
# compiles pip-deps itself, with a matching interpreter.
|
|
66
|
+
"--no-compile",
|
|
67
|
+
"--no-deps",
|
|
68
|
+
"--only-binary=:all:",
|
|
69
|
+
"--platform",
|
|
70
|
+
platform_tag,
|
|
71
|
+
"--python-version",
|
|
72
|
+
python_version,
|
|
73
|
+
"--abi",
|
|
74
|
+
abi,
|
|
75
|
+
"--implementation",
|
|
76
|
+
"cp",
|
|
77
|
+
"--target",
|
|
78
|
+
target_dir,
|
|
79
|
+
]
|
|
80
|
+
cmd += wheel_files
|
|
81
|
+
return cmd
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def collect_artifacts(
|
|
85
|
+
lock: Lockfile,
|
|
86
|
+
layout,
|
|
87
|
+
*,
|
|
88
|
+
build_slices: list[BuildSlice],
|
|
89
|
+
project_root: Path,
|
|
90
|
+
cache: ArtifactCache | None = None,
|
|
91
|
+
downloader: Downloader | None = None,
|
|
92
|
+
no_cache: bool = False,
|
|
93
|
+
runner=subprocess.run,
|
|
94
|
+
python_executable: str | None = None,
|
|
95
|
+
release: bool = False,
|
|
96
|
+
build_settings: DesktopBuildSettings | None = None,
|
|
97
|
+
echo: Callable[[str], None] = lambda msg: None,
|
|
98
|
+
note=discard_note,
|
|
99
|
+
) -> None:
|
|
100
|
+
cache = cache or ArtifactCache()
|
|
101
|
+
|
|
102
|
+
# Python.xcframework is multi-slice and slice-independent, so it is fetched
|
|
103
|
+
# once regardless of how many pip-deps slices we populate. pip-deps itself is
|
|
104
|
+
# installed per slice (device/simulator never share .so files): a bare
|
|
105
|
+
# `kivyforge build` passes both slices so either Xcode destination builds
|
|
106
|
+
# without a re-run, while a targeted build passes only its slice.
|
|
107
|
+
_install_python_xcframework(
|
|
108
|
+
lock, layout, cache=cache, downloader=downloader, no_cache=no_cache
|
|
109
|
+
)
|
|
110
|
+
# Wheel-embedded frameworks are multi-slice xcframeworks; union them across
|
|
111
|
+
# slices (dedup by name) so user-declared natives below don't clobber them.
|
|
112
|
+
# Shared registry of staged frameworks (name -> provenance + content hash).
|
|
113
|
+
# Threaded across every pip-deps slice and into native xcframework
|
|
114
|
+
# extraction so identical duplicates dedupe and conflicting ones fail loudly.
|
|
115
|
+
staged: dict[str, PlacedFramework] = {}
|
|
116
|
+
# Frameworks/ is rebuilt from this lock alone, so a removed wheel or
|
|
117
|
+
# native entry takes its xcframework with it.
|
|
118
|
+
clear_staged_frameworks(layout.frameworks)
|
|
119
|
+
for build_slice in build_slices:
|
|
120
|
+
slice_pip_deps = layout.pip_deps_for_slice(build_slice.target)
|
|
121
|
+
_install_wheels(
|
|
122
|
+
lock,
|
|
123
|
+
layout,
|
|
124
|
+
slice_pip_deps=slice_pip_deps,
|
|
125
|
+
build_slice=build_slice,
|
|
126
|
+
project_root=project_root,
|
|
127
|
+
cache=cache,
|
|
128
|
+
downloader=downloader,
|
|
129
|
+
no_cache=no_cache,
|
|
130
|
+
runner=runner,
|
|
131
|
+
python_executable=python_executable,
|
|
132
|
+
)
|
|
133
|
+
_compile_pip_deps(
|
|
134
|
+
slice_pip_deps,
|
|
135
|
+
python_version=lock.python_xcframework.version,
|
|
136
|
+
release=release,
|
|
137
|
+
build_settings=build_settings,
|
|
138
|
+
echo=echo,
|
|
139
|
+
note=note,
|
|
140
|
+
)
|
|
141
|
+
_stamp_collected(slice_pip_deps)
|
|
142
|
+
copy_wheel_frameworks(slice_pip_deps, layout.frameworks, existing=staged)
|
|
143
|
+
_install_native_xcframeworks(
|
|
144
|
+
lock,
|
|
145
|
+
layout,
|
|
146
|
+
staged=staged,
|
|
147
|
+
project_root=project_root,
|
|
148
|
+
cache=cache,
|
|
149
|
+
downloader=downloader,
|
|
150
|
+
no_cache=no_cache,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _stamp_collected(slice_pip_deps: Path) -> None:
|
|
155
|
+
"""Mark a pip-deps slice as collected.
|
|
156
|
+
|
|
157
|
+
The Build Python run script uses this marker — not directory emptiness — to
|
|
158
|
+
tell "collected, no third-party deps" (valid: an app with an empty pip-deps
|
|
159
|
+
slice) from "never collected" (error: wrong/uncollected Xcode destination).
|
|
160
|
+
It lives beside the slice dir so it is never rsync'd into the app bundle.
|
|
161
|
+
"""
|
|
162
|
+
marker = slice_pip_deps.parent / f"{slice_pip_deps.name}.collected"
|
|
163
|
+
marker.write_text("", encoding="utf-8")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _setting_applies(value: bool | str, *, release: bool) -> bool:
|
|
167
|
+
"""A ``build_settings`` tri-state resolved for the build being produced."""
|
|
168
|
+
if isinstance(value, bool):
|
|
169
|
+
return value
|
|
170
|
+
return release # DESKTOP_RELEASE_ONLY
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _compile_pip_deps(
|
|
174
|
+
slice_pip_deps: Path,
|
|
175
|
+
*,
|
|
176
|
+
python_version: str,
|
|
177
|
+
release: bool,
|
|
178
|
+
build_settings: DesktopBuildSettings | None,
|
|
179
|
+
echo: Callable[[str], None],
|
|
180
|
+
note=discard_note,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Byte-compile/strip a collected pip-deps slice, per ``build_settings``.
|
|
183
|
+
|
|
184
|
+
Mirrors each desktop backend's own ``_resolve_byte_compile`` (macOS/Linux/
|
|
185
|
+
Windows ``bundle.py``), with one iOS-specific simplification: the
|
|
186
|
+
Python.xcframework ships no standalone interpreter binary to shell out to
|
|
187
|
+
(it's a linkable library, not an executable), so the "staged interpreter"
|
|
188
|
+
rung of the compiler ladder never applies here — it always falls straight
|
|
189
|
+
to "this process's interpreter, if its CPython minor matches the target",
|
|
190
|
+
or degrades.
|
|
191
|
+
"""
|
|
192
|
+
settings = build_settings or DesktopBuildSettings()
|
|
193
|
+
if not _setting_applies(settings.byte_compile, release=release):
|
|
194
|
+
return
|
|
195
|
+
compiler = select_compiler(
|
|
196
|
+
staged_interpreter=Path(), native=False, python_version=python_version
|
|
197
|
+
)
|
|
198
|
+
if compiler is None:
|
|
199
|
+
minor = ".".join(python_version.split(".")[:2])
|
|
200
|
+
headline = (
|
|
201
|
+
f"no final CPython {minor} found (this project ships {python_version})"
|
|
202
|
+
)
|
|
203
|
+
why_and_fix = (
|
|
204
|
+
" Pre-releases do not count: CPython only freezes the .pyc "
|
|
205
|
+
f"magic number at the first release candidate, so a {minor} alpha "
|
|
206
|
+
f"writes bytecode {python_version} refuses to import.\n"
|
|
207
|
+
f" Fix: install a final CPython {minor} — kivyforge finds it "
|
|
208
|
+
"automatically — or set byte_compile = false in "
|
|
209
|
+
"[tool.kivy.ios.python.build_settings]."
|
|
210
|
+
)
|
|
211
|
+
if settings.byte_compile is True:
|
|
212
|
+
raise CollectError(
|
|
213
|
+
"[tool.kivy.ios.python.build_settings].byte_compile = true, but "
|
|
214
|
+
f"{headline}.\n{why_and_fix}"
|
|
215
|
+
)
|
|
216
|
+
echo(f"[stage] not byte-compiling pip-deps: {headline}.\n{why_and_fix}")
|
|
217
|
+
note(
|
|
218
|
+
BYTECOMPILE_NO_INTERP,
|
|
219
|
+
f"not byte-compiling pip-deps: {headline}.",
|
|
220
|
+
{"payload": "pip-deps"},
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
strip_source = _setting_applies(settings.strip_source, release=release)
|
|
224
|
+
with_what = " ".join(compiler) if compiler else "this interpreter"
|
|
225
|
+
echo(
|
|
226
|
+
f"[stage] byte-compiling pip-deps with {with_what}"
|
|
227
|
+
+ (" (.pyc only)" if strip_source else "")
|
|
228
|
+
)
|
|
229
|
+
try:
|
|
230
|
+
byte_compile(
|
|
231
|
+
[slice_pip_deps],
|
|
232
|
+
compiler=compiler,
|
|
233
|
+
strip_source=strip_source,
|
|
234
|
+
stripdir=slice_pip_deps,
|
|
235
|
+
)
|
|
236
|
+
except PycompileError as exc:
|
|
237
|
+
raise CollectError(
|
|
238
|
+
f"{exc}\n Fix it, or set "
|
|
239
|
+
"[tool.kivy.ios.python.build_settings].byte_compile = false.",
|
|
240
|
+
**reclassify(exc),
|
|
241
|
+
) from exc
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _install_python_xcframework(lock, layout, *, cache, downloader, no_cache) -> None:
|
|
245
|
+
px = lock.python_xcframework
|
|
246
|
+
filename = px.url.rsplit("/", 1)[-1]
|
|
247
|
+
archive = fetch_artifact(
|
|
248
|
+
name=f"Python.xcframework {px.version}",
|
|
249
|
+
sha256=px.sha256,
|
|
250
|
+
filename=filename,
|
|
251
|
+
url=px.url,
|
|
252
|
+
cache=cache,
|
|
253
|
+
downloader=downloader,
|
|
254
|
+
no_cache=no_cache,
|
|
255
|
+
)
|
|
256
|
+
dest = layout.python_xcframework
|
|
257
|
+
if dest.exists():
|
|
258
|
+
shutil.rmtree(dest)
|
|
259
|
+
with tempfile.TemporaryDirectory(prefix="kivy-pyxc-") as tmp:
|
|
260
|
+
tmp_dir = Path(tmp)
|
|
261
|
+
with tarfile.open(archive, "r:gz") as tf:
|
|
262
|
+
# filter="data" rejects path traversal, absolute paths, and
|
|
263
|
+
# unsafe special files (Python 3.12+ / PEP 706).
|
|
264
|
+
tf.extractall(tmp_dir, filter="data")
|
|
265
|
+
found = next(
|
|
266
|
+
(p for p in tmp_dir.rglob("Python.xcframework") if p.is_dir()), None
|
|
267
|
+
)
|
|
268
|
+
if found is None:
|
|
269
|
+
raise CollectError(
|
|
270
|
+
"Python.xcframework not found inside downloaded archive."
|
|
271
|
+
)
|
|
272
|
+
shutil.copytree(found, dest)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _install_wheels(
|
|
276
|
+
lock,
|
|
277
|
+
layout,
|
|
278
|
+
*,
|
|
279
|
+
slice_pip_deps,
|
|
280
|
+
build_slice,
|
|
281
|
+
project_root,
|
|
282
|
+
cache,
|
|
283
|
+
downloader,
|
|
284
|
+
no_cache,
|
|
285
|
+
runner,
|
|
286
|
+
python_executable,
|
|
287
|
+
) -> None:
|
|
288
|
+
with tempfile.TemporaryDirectory(prefix="kivy-wheels-") as tmp:
|
|
289
|
+
stage = Path(tmp)
|
|
290
|
+
wheel_files: list[str] = []
|
|
291
|
+
selected_wheels: list[LockedWheel] = []
|
|
292
|
+
for pkg in lock.packages:
|
|
293
|
+
wheel = select_wheel(pkg, build_slice)
|
|
294
|
+
selected_wheels.append(wheel)
|
|
295
|
+
local = fetch_artifact(
|
|
296
|
+
name=f"{pkg.name} {pkg.version}",
|
|
297
|
+
sha256=wheel.sha256,
|
|
298
|
+
filename=wheel.name,
|
|
299
|
+
url=wheel.url,
|
|
300
|
+
path=wheel.path,
|
|
301
|
+
project_root=project_root,
|
|
302
|
+
cache=cache,
|
|
303
|
+
downloader=downloader,
|
|
304
|
+
no_cache=no_cache,
|
|
305
|
+
)
|
|
306
|
+
# Cache entries are content-addressed ({sha256}-{name}); pip rejects
|
|
307
|
+
# that filename, so stage each wheel under its canonical PEP 427 name.
|
|
308
|
+
staged = stage / wheel.name
|
|
309
|
+
if local.resolve() != staged.resolve():
|
|
310
|
+
shutil.copy2(local, staged)
|
|
311
|
+
wheel_files.append(str(staged))
|
|
312
|
+
# Each collect always starts from a clean slate so no stale .so
|
|
313
|
+
# files from a previous slice accumulate in the slice-specific dir --
|
|
314
|
+
# also when the lock now pins nothing, or removing the last dependency
|
|
315
|
+
# would leave the previous collect's packages in the app.
|
|
316
|
+
if slice_pip_deps.is_dir():
|
|
317
|
+
shutil.rmtree(slice_pip_deps)
|
|
318
|
+
slice_pip_deps.mkdir(parents=True, exist_ok=True)
|
|
319
|
+
if not wheel_files:
|
|
320
|
+
return
|
|
321
|
+
abi = "cp" + "".join(lock.python_xcframework.version.split(".")[:2])
|
|
322
|
+
# Use the selected wheels' actual platform tag for pip's --platform.
|
|
323
|
+
# The wheel tag reflects the Python binary's compile-time minimum OS
|
|
324
|
+
# (e.g. ios_13_0 from python.org's cp315 binary), which may be lower
|
|
325
|
+
# than the project's deployment_target. pip requires --platform to
|
|
326
|
+
# match the wheel being installed, so use the selected native wheel's
|
|
327
|
+
# tag rather than the computed build_slice tag.
|
|
328
|
+
pip_platform = _native_platform_tag(selected_wheels, build_slice)
|
|
329
|
+
cmd = pip_install_command(
|
|
330
|
+
wheel_files,
|
|
331
|
+
target_dir=str(slice_pip_deps),
|
|
332
|
+
platform_tag=pip_platform,
|
|
333
|
+
python_version=".".join(lock.python_xcframework.version.split(".")[:2]),
|
|
334
|
+
abi=abi,
|
|
335
|
+
python_executable=python_executable,
|
|
336
|
+
)
|
|
337
|
+
proc = runner(cmd, capture_output=True, text=True)
|
|
338
|
+
if proc.returncode != 0:
|
|
339
|
+
raise CollectError(
|
|
340
|
+
f"pip install of pinned wheels failed:\n{proc.stderr or proc.stdout}"
|
|
341
|
+
)
|
|
342
|
+
# pip --target creates a bin/ of host-platform console scripts.
|
|
343
|
+
# Strip it immediately — these macOS executables cannot run on iOS
|
|
344
|
+
# and should not appear in the Xcode project tree or app bundle.
|
|
345
|
+
bin_dir = slice_pip_deps / "bin"
|
|
346
|
+
if bin_dir.is_dir():
|
|
347
|
+
shutil.rmtree(bin_dir)
|
|
348
|
+
for wheel_file in wheel_files:
|
|
349
|
+
_drop_wheel_data_files(Path(wheel_file), slice_pip_deps)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _drop_wheel_data_files(wheel: Path, slice_pip_deps: Path) -> None:
|
|
353
|
+
"""Remove what *wheel* installed from its ``.data/data/`` tree.
|
|
354
|
+
|
|
355
|
+
That tree is a wheel's ``data_files``: paths relative to an install prefix
|
|
356
|
+
(``share/...``, examples, headers, test sources), which ``pip --target``
|
|
357
|
+
lays down at the root of the slice. Nothing on iOS can find them: the app's
|
|
358
|
+
``sys.prefix`` is not ``pip-deps``. They are dead weight, and a release
|
|
359
|
+
build byte-compiles them, so one Python 2 example fails the whole package
|
|
360
|
+
(pyobjus ships 18). macOS never installs this tree (``wheels_stage._unpack``
|
|
361
|
+
keeps only ``purelib``/``platlib``); this brings iOS in line.
|
|
362
|
+
|
|
363
|
+
Only the files the wheel itself names are removed, never a whole
|
|
364
|
+
directory, so a data file beside a real package cannot take the package
|
|
365
|
+
with it. Directories left empty are pruned.
|
|
366
|
+
"""
|
|
367
|
+
with zipfile.ZipFile(wheel) as zf:
|
|
368
|
+
names = zf.namelist()
|
|
369
|
+
root = slice_pip_deps.resolve()
|
|
370
|
+
emptied: set[Path] = set()
|
|
371
|
+
for name in names:
|
|
372
|
+
parts = name.split("/")
|
|
373
|
+
if len(parts) < 3 or not parts[0].endswith(".data") or parts[1] != "data":
|
|
374
|
+
continue
|
|
375
|
+
installed = (slice_pip_deps / Path(*parts[2:])).resolve()
|
|
376
|
+
if not installed.is_relative_to(root) or not installed.is_file():
|
|
377
|
+
continue
|
|
378
|
+
installed.unlink()
|
|
379
|
+
emptied.update(p for p in installed.parents if p.is_relative_to(root))
|
|
380
|
+
# Deepest first, so a parent is only tried once its children are gone.
|
|
381
|
+
for directory in sorted(emptied, key=lambda p: len(p.parts), reverse=True):
|
|
382
|
+
if directory != root and directory.is_dir() and not any(directory.iterdir()):
|
|
383
|
+
directory.rmdir()
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _install_native_xcframeworks(
|
|
387
|
+
lock, layout, *, staged, project_root, cache, downloader, no_cache
|
|
388
|
+
) -> None:
|
|
389
|
+
for xc in lock.xcframeworks:
|
|
390
|
+
filename = (xc.url or xc.path or xc.name).rsplit("/", 1)[-1]
|
|
391
|
+
archive = fetch_artifact(
|
|
392
|
+
name=xc.name,
|
|
393
|
+
sha256=xc.sha256,
|
|
394
|
+
filename=filename,
|
|
395
|
+
url=xc.url,
|
|
396
|
+
path=xc.path,
|
|
397
|
+
project_root=project_root,
|
|
398
|
+
cache=cache,
|
|
399
|
+
downloader=downloader,
|
|
400
|
+
no_cache=no_cache,
|
|
401
|
+
)
|
|
402
|
+
extract_xcframework_archive(
|
|
403
|
+
archive,
|
|
404
|
+
layout.frameworks,
|
|
405
|
+
name=xc.name,
|
|
406
|
+
archive_format=xc.archive_format,
|
|
407
|
+
archive_member=xc.archive_member,
|
|
408
|
+
existing=staged,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _native_platform_tag(selected: list[LockedWheel], slice_: BuildSlice) -> str:
|
|
413
|
+
"""Return the pip --platform tag to use for the selected wheels.
|
|
414
|
+
|
|
415
|
+
All native wheels in a single build are produced by the same Python binary
|
|
416
|
+
and therefore share the same platform tag (e.g. ios_13_0_arm64_iphoneos).
|
|
417
|
+
Using the wheel's actual tag (rather than the project deployment_target tag)
|
|
418
|
+
ensures pip accepts the wheel even when the wheel's min-OS is lower than the
|
|
419
|
+
project's deployment_target.
|
|
420
|
+
"""
|
|
421
|
+
for wheel in selected:
|
|
422
|
+
if not wheel.is_pure_python:
|
|
423
|
+
return wheel.platform_tag
|
|
424
|
+
return slice_.platform_tag
|