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,19 @@
|
|
|
1
|
+
"""Health checks for ``kivyforge doctor`` (spec 05).
|
|
2
|
+
|
|
3
|
+
Checks are pure functions over an injectable :class:`Probe`, so every
|
|
4
|
+
PASS/WARN/FAIL/SKIP path is unit-testable without Xcode, a network, or a
|
|
5
|
+
keychain. ``cli/doctor.py`` wires the real probe and renders the report.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .probe import Probe, RealProbe
|
|
11
|
+
from .result import CheckResult, Status, worst_status
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Probe",
|
|
15
|
+
"RealProbe",
|
|
16
|
+
"CheckResult",
|
|
17
|
+
"Status",
|
|
18
|
+
"worst_status",
|
|
19
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Platform-neutral doctor checks shared by every backend (spec 05).
|
|
2
|
+
|
|
3
|
+
These checks are pure functions over an injectable :class:`Probe` and make no
|
|
4
|
+
assumptions about the target platform, so iOS, macOS, and Linux all reuse them.
|
|
5
|
+
Platform-specific checks live in ``platforms/<os>/doctor.py``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from ..config.model import Config
|
|
13
|
+
from ..lock.resolver import MIN_PIP_VERSION, version_str
|
|
14
|
+
from .probe import Probe
|
|
15
|
+
from .result import CheckResult, Status
|
|
16
|
+
|
|
17
|
+
SKIP_NOTE = "no pyproject.toml found in current directory"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def lock_parse_fail(platform: str, exc: Exception) -> CheckResult:
|
|
21
|
+
"""A FAIL result for an unreadable ``pylock.<platform>.toml``."""
|
|
22
|
+
return CheckResult(
|
|
23
|
+
f"pylock.{platform}.toml",
|
|
24
|
+
Status.FAIL,
|
|
25
|
+
f"failed to parse: {exc}",
|
|
26
|
+
hint=f"Regenerate it with `kivyforge lock -p {platform}`.",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ver_tuple(v: str) -> tuple[int, ...]:
|
|
31
|
+
parts = []
|
|
32
|
+
for chunk in v.split("."):
|
|
33
|
+
digits = "".join(c for c in chunk if c.isdigit())
|
|
34
|
+
parts.append(int(digits) if digits else 0)
|
|
35
|
+
return tuple(parts)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_pip_version(probe: Probe) -> CheckResult:
|
|
39
|
+
"""pip >= 24.3 is required to match PEP 730 iOS platform tags at lock time."""
|
|
40
|
+
minimum = version_str(MIN_PIP_VERSION)
|
|
41
|
+
version = probe.pip_version()
|
|
42
|
+
if version is None:
|
|
43
|
+
return CheckResult(
|
|
44
|
+
"pip version",
|
|
45
|
+
Status.WARN,
|
|
46
|
+
"could not determine pip version",
|
|
47
|
+
hint="ensure pip is available: `python -m ensurepip --upgrade`.",
|
|
48
|
+
)
|
|
49
|
+
if _ver_tuple(version) < MIN_PIP_VERSION:
|
|
50
|
+
return CheckResult(
|
|
51
|
+
"pip version",
|
|
52
|
+
Status.FAIL,
|
|
53
|
+
f"{version} (need >= {minimum})",
|
|
54
|
+
hint=(
|
|
55
|
+
"pip < 24.3 cannot match iOS platform tags (PEP 730); "
|
|
56
|
+
"upgrade with `python -m pip install -U pip`."
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
return CheckResult("pip version", Status.PASS, version)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_kivyforge_version(
|
|
63
|
+
probe: Probe, current: str, *, offline: bool
|
|
64
|
+
) -> CheckResult:
|
|
65
|
+
if offline:
|
|
66
|
+
return CheckResult("kivyforge version", Status.PASS, f"{current} (offline)")
|
|
67
|
+
latest = probe.latest_kivyforge_version()
|
|
68
|
+
if latest and _ver_tuple(latest) > _ver_tuple(current):
|
|
69
|
+
return CheckResult(
|
|
70
|
+
"kivyforge version",
|
|
71
|
+
Status.WARN,
|
|
72
|
+
f"{current} (latest {latest})",
|
|
73
|
+
hint="upgrade with `pip install -U kivyforge`.",
|
|
74
|
+
)
|
|
75
|
+
return CheckResult("kivyforge version", Status.PASS, current)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_app_dir(config: Config, project_root: Path) -> CheckResult:
|
|
79
|
+
"""``app_dir`` must resolve to an existing directory — it carries the app's
|
|
80
|
+
Python source into the bundle. Config validation only checks the *string*
|
|
81
|
+
(relative, non-empty, etc.), not that it exists on disk; a missing one would
|
|
82
|
+
otherwise build an app with no code (caught at build time, but reported here
|
|
83
|
+
first).
|
|
84
|
+
"""
|
|
85
|
+
app_dir = config.kivy.app_dir
|
|
86
|
+
path = project_root / app_dir
|
|
87
|
+
if not path.is_dir():
|
|
88
|
+
return CheckResult(
|
|
89
|
+
"App source directory",
|
|
90
|
+
Status.FAIL,
|
|
91
|
+
f"app_dir {app_dir!r} not found",
|
|
92
|
+
hint="create the directory or fix [tool.kivy].app_dir; it must "
|
|
93
|
+
"point at your app's Python source.",
|
|
94
|
+
)
|
|
95
|
+
return CheckResult("App source directory", Status.PASS, app_dir)
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Environment probes for doctor (the only place that touches the host).
|
|
2
|
+
|
|
3
|
+
``Probe`` is the protocol the checks depend on; ``RealProbe`` is the macOS
|
|
4
|
+
implementation (subprocess/socket/Mach-O parsing). Unit tests supply a fake
|
|
5
|
+
object with the same methods, so the check logic never shells out.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import platform as _platform
|
|
12
|
+
import shutil
|
|
13
|
+
import socket
|
|
14
|
+
import struct
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Protocol
|
|
20
|
+
|
|
21
|
+
# How long to keep retrying to restore the build output after the lock probe
|
|
22
|
+
# moved it aside. It was just renamed there on the same volume with no open
|
|
23
|
+
# handles, so this normally succeeds on the first try; the retry only rides out
|
|
24
|
+
# a transient antivirus touch.
|
|
25
|
+
_RESTORE_TIMEOUT_S = 10.0
|
|
26
|
+
|
|
27
|
+
# Mach-O platform constants (LC_BUILD_VERSION `platform`).
|
|
28
|
+
PLATFORM_NAMES = {
|
|
29
|
+
1: "macos",
|
|
30
|
+
2: "ios",
|
|
31
|
+
3: "tvos",
|
|
32
|
+
4: "watchos",
|
|
33
|
+
7: "ios-simulator",
|
|
34
|
+
8: "tvos-simulator",
|
|
35
|
+
9: "watchos-simulator",
|
|
36
|
+
}
|
|
37
|
+
LC_BUILD_VERSION = 0x32
|
|
38
|
+
_MACHO_MAGICS = {0xFEEDFACE, 0xFEEDFACF, 0xCEFAEDFE, 0xCFFAEDFE}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Probe(Protocol):
|
|
42
|
+
def xcode_version(self) -> str | None: ...
|
|
43
|
+
def pip_version(self) -> str | None: ...
|
|
44
|
+
def xcode_select_path(self) -> str | None: ...
|
|
45
|
+
def has_xcrun_clang(self) -> bool: ...
|
|
46
|
+
def has_swift_toolchain(self) -> bool: ...
|
|
47
|
+
def simulator_runtimes(self) -> list[str]: ...
|
|
48
|
+
def latest_kivyforge_version(self) -> str | None: ...
|
|
49
|
+
def keychain_identities(self) -> list[str]: ...
|
|
50
|
+
def login_keychain_identities(self) -> list[str]: ...
|
|
51
|
+
def tcp_reachable(self, host: str, port: int) -> bool: ...
|
|
52
|
+
def binary_platforms(self, path: Path) -> set[str]: ...
|
|
53
|
+
def host_system(self) -> str: ...
|
|
54
|
+
def has_codesign(self) -> bool: ...
|
|
55
|
+
def has_notarytool(self) -> bool: ...
|
|
56
|
+
def is_root(self) -> bool: ...
|
|
57
|
+
def linux_libc(self) -> str: ...
|
|
58
|
+
def shared_libraries(self) -> frozenset[str]: ...
|
|
59
|
+
def display_session(self) -> set[str]: ...
|
|
60
|
+
def has_desktop_file_validate(self) -> bool: ...
|
|
61
|
+
def desktop_file_errors(self, path: Path) -> str | None: ...
|
|
62
|
+
def long_paths_enabled(self) -> bool | None: ...
|
|
63
|
+
def has_signtool(self) -> bool: ...
|
|
64
|
+
def code_signing_thumbprints(self, store_scope: str) -> list[str]: ...
|
|
65
|
+
def build_output_locked(self, path: Path) -> bool | None: ...
|
|
66
|
+
def filesystem_type(self, path: Path) -> str | None: ...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class RealProbe:
|
|
70
|
+
def host_system(self) -> str:
|
|
71
|
+
return _platform.system()
|
|
72
|
+
|
|
73
|
+
def has_codesign(self) -> bool:
|
|
74
|
+
return shutil.which("codesign") is not None
|
|
75
|
+
|
|
76
|
+
def has_notarytool(self) -> bool:
|
|
77
|
+
return bool(_capture(["xcrun", "--find", "notarytool"]))
|
|
78
|
+
|
|
79
|
+
def is_root(self) -> bool:
|
|
80
|
+
return hasattr(os, "geteuid") and os.geteuid() == 0
|
|
81
|
+
|
|
82
|
+
def xcode_version(self) -> str | None:
|
|
83
|
+
out = _capture(["xcodebuild", "-version"])
|
|
84
|
+
if not out:
|
|
85
|
+
return None
|
|
86
|
+
first = out.splitlines()[0]
|
|
87
|
+
return first.replace("Xcode", "").strip() or None
|
|
88
|
+
|
|
89
|
+
def pip_version(self) -> str | None:
|
|
90
|
+
# "pip 24.3.1 from /path/site-packages/pip (python 3.15)".
|
|
91
|
+
out = _capture([sys.executable, "-m", "pip", "--version"])
|
|
92
|
+
tokens = out.split()
|
|
93
|
+
if len(tokens) >= 2 and tokens[0] == "pip":
|
|
94
|
+
return tokens[1]
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def xcode_select_path(self) -> str | None:
|
|
98
|
+
return _capture(["xcode-select", "-p"]) or None
|
|
99
|
+
|
|
100
|
+
def has_xcrun_clang(self) -> bool:
|
|
101
|
+
return bool(_capture(["xcrun", "--find", "clang"]))
|
|
102
|
+
|
|
103
|
+
def has_swift_toolchain(self) -> bool:
|
|
104
|
+
return bool(_capture(["xcrun", "--find", "swift"]))
|
|
105
|
+
|
|
106
|
+
def simulator_runtimes(self) -> list[str]:
|
|
107
|
+
out = _capture(["xcrun", "simctl", "list", "runtimes", "iOS"])
|
|
108
|
+
versions = []
|
|
109
|
+
for line in out.splitlines():
|
|
110
|
+
# "iOS 18.0 (...) - com.apple.CoreSimulator.SimRuntime.iOS-18-0"
|
|
111
|
+
line = line.strip()
|
|
112
|
+
if line.startswith("iOS "):
|
|
113
|
+
parts = line.split()
|
|
114
|
+
if len(parts) >= 2:
|
|
115
|
+
versions.append(parts[1])
|
|
116
|
+
return versions
|
|
117
|
+
|
|
118
|
+
def latest_kivyforge_version(self) -> str | None:
|
|
119
|
+
# Best-effort PyPI lookup; failures are non-fatal.
|
|
120
|
+
try:
|
|
121
|
+
import json
|
|
122
|
+
import urllib.request
|
|
123
|
+
|
|
124
|
+
url = "https://pypi.org/pypi/kivyforge/json"
|
|
125
|
+
with urllib.request.urlopen(url, timeout=3) as resp: # noqa: S310
|
|
126
|
+
data = json.load(resp)
|
|
127
|
+
return data.get("info", {}).get("version")
|
|
128
|
+
except Exception:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
def keychain_identities(self) -> list[str]:
|
|
132
|
+
out = _capture(["security", "find-identity", "-v", "-p", "codesigning"])
|
|
133
|
+
return [line.strip() for line in out.splitlines() if line.strip()]
|
|
134
|
+
|
|
135
|
+
def login_keychain_identities(self) -> list[str]:
|
|
136
|
+
"""Identities scoped to ``login.keychain-db`` (Apple recommends keeping
|
|
137
|
+
Developer ID identities out of it; see the ``Signing identity`` check).
|
|
138
|
+
"""
|
|
139
|
+
login_keychain = str(
|
|
140
|
+
Path.home() / "Library" / "Keychains" / "login.keychain-db"
|
|
141
|
+
)
|
|
142
|
+
out = _capture(
|
|
143
|
+
["security", "find-identity", "-v", "-p", "codesigning", login_keychain]
|
|
144
|
+
)
|
|
145
|
+
return [line.strip() for line in out.splitlines() if line.strip()]
|
|
146
|
+
|
|
147
|
+
def tcp_reachable(self, host: str, port: int) -> bool:
|
|
148
|
+
try:
|
|
149
|
+
with socket.create_connection((host, port), timeout=5):
|
|
150
|
+
return True
|
|
151
|
+
except OSError:
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
def binary_platforms(self, path: Path) -> set[str]:
|
|
155
|
+
"""Return the set of OS platforms a Mach-O (or fat) binary targets."""
|
|
156
|
+
try:
|
|
157
|
+
data = path.read_bytes()
|
|
158
|
+
except OSError:
|
|
159
|
+
return set()
|
|
160
|
+
return _macho_platforms(data)
|
|
161
|
+
|
|
162
|
+
def linux_libc(self) -> str:
|
|
163
|
+
"""The host C library family: ``"glibc"``, ``"musl"``, or ``""``.
|
|
164
|
+
|
|
165
|
+
The bundled python-build-standalone runtime is a gnu/glibc ELF build,
|
|
166
|
+
so a musl host (Alpine) can't run it — doctor warns on that.
|
|
167
|
+
"""
|
|
168
|
+
name = _platform.libc_ver()[0].lower()
|
|
169
|
+
if "glibc" in name or name == "libc":
|
|
170
|
+
return "glibc"
|
|
171
|
+
if any(Path("/lib").glob("ld-musl-*.so.1")):
|
|
172
|
+
return "musl"
|
|
173
|
+
return name
|
|
174
|
+
|
|
175
|
+
def shared_libraries(self) -> frozenset[str]:
|
|
176
|
+
"""The sonames the dynamic linker's cache knows about (``ldconfig -p``)."""
|
|
177
|
+
out = _capture(["ldconfig", "-p"]) or _capture(["/sbin/ldconfig", "-p"])
|
|
178
|
+
libs: set[str] = set()
|
|
179
|
+
for line in out.splitlines():
|
|
180
|
+
token = line.strip().split(" ", 1)[0]
|
|
181
|
+
if token and token.startswith("lib"):
|
|
182
|
+
libs.add(token)
|
|
183
|
+
return frozenset(libs)
|
|
184
|
+
|
|
185
|
+
def display_session(self) -> set[str]:
|
|
186
|
+
"""The display servers this session exposes via the environment."""
|
|
187
|
+
sessions: set[str] = set()
|
|
188
|
+
if os.environ.get("WAYLAND_DISPLAY"):
|
|
189
|
+
sessions.add("wayland")
|
|
190
|
+
if os.environ.get("DISPLAY"):
|
|
191
|
+
sessions.add("x11")
|
|
192
|
+
return sessions
|
|
193
|
+
|
|
194
|
+
def has_desktop_file_validate(self) -> bool:
|
|
195
|
+
return shutil.which("desktop-file-validate") is not None
|
|
196
|
+
|
|
197
|
+
def desktop_file_errors(self, path: Path) -> str | None:
|
|
198
|
+
try:
|
|
199
|
+
proc = subprocess.run(
|
|
200
|
+
["desktop-file-validate", str(path)], capture_output=True, text=True
|
|
201
|
+
)
|
|
202
|
+
except (OSError, ValueError):
|
|
203
|
+
return None
|
|
204
|
+
if proc.returncode == 0:
|
|
205
|
+
return None
|
|
206
|
+
return (proc.stdout + proc.stderr).strip() or "validation failed"
|
|
207
|
+
|
|
208
|
+
def long_paths_enabled(self) -> bool | None:
|
|
209
|
+
"""Whether ``LongPathsEnabled`` is on (``None`` when it can't be read).
|
|
210
|
+
|
|
211
|
+
Reads ``HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem`` — the
|
|
212
|
+
per-machine switch that lifts the legacy 260-char ``MAX_PATH`` limit that
|
|
213
|
+
deep bundle trees (a full PBS prefix under ``build/windows``) can hit.
|
|
214
|
+
"""
|
|
215
|
+
try:
|
|
216
|
+
import winreg
|
|
217
|
+
except ImportError:
|
|
218
|
+
return None
|
|
219
|
+
try:
|
|
220
|
+
with winreg.OpenKey(
|
|
221
|
+
winreg.HKEY_LOCAL_MACHINE,
|
|
222
|
+
r"SYSTEM\CurrentControlSet\Control\FileSystem",
|
|
223
|
+
) as key:
|
|
224
|
+
value, _ = winreg.QueryValueEx(key, "LongPathsEnabled")
|
|
225
|
+
return bool(value)
|
|
226
|
+
except OSError:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
def has_signtool(self) -> bool:
|
|
230
|
+
return shutil.which("signtool") is not None
|
|
231
|
+
|
|
232
|
+
def code_signing_thumbprints(self, store_scope: str) -> list[str]:
|
|
233
|
+
"""Thumbprints of code-signing certs in the store *signtool* will use.
|
|
234
|
+
|
|
235
|
+
``machine`` -> ``Cert:\\LocalMachine\\My`` (signtool's ``/sm``);
|
|
236
|
+
anything else -> ``Cert:\\CurrentUser\\My``. Thumbprints are normalized
|
|
237
|
+
to upper-case with no spaces to match the configured value.
|
|
238
|
+
"""
|
|
239
|
+
store = "LocalMachine" if store_scope == "machine" else "CurrentUser"
|
|
240
|
+
script = (
|
|
241
|
+
f"Get-ChildItem Cert:\\{store}\\My -CodeSigningCert "
|
|
242
|
+
"| ForEach-Object { $_.Thumbprint }"
|
|
243
|
+
)
|
|
244
|
+
out = _capture(["powershell", "-NoProfile", "-Command", script])
|
|
245
|
+
return [
|
|
246
|
+
line.strip().replace(" ", "").upper()
|
|
247
|
+
for line in out.splitlines()
|
|
248
|
+
if line.strip()
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
def build_output_locked(self, path: Path) -> bool | None:
|
|
252
|
+
"""Whether a built onedir *path* is currently held open by a process.
|
|
253
|
+
|
|
254
|
+
``None`` when *path* does not exist (nothing built). Otherwise performs
|
|
255
|
+
the exact metadata rename ``build``/``package`` will do — move the tree
|
|
256
|
+
aside — and immediately restores it, returning ``True`` when a sharing/
|
|
257
|
+
access lock blocks it (a running instance of the app, or an Explorer/
|
|
258
|
+
terminal window sitting in the folder), ``False`` when it is free.
|
|
259
|
+
|
|
260
|
+
A diagnostic must never lose the user's build: if the aside succeeds the
|
|
261
|
+
restore is retried and, in the vanishingly unlikely event it still fails,
|
|
262
|
+
raises ``OSError`` naming where the tree now sits — rather than silently
|
|
263
|
+
leaving the live build displaced under a hidden name.
|
|
264
|
+
"""
|
|
265
|
+
if not path.exists():
|
|
266
|
+
return None
|
|
267
|
+
aside = path.with_name(f".{path.name}.doctorlock-{os.getpid()}")
|
|
268
|
+
try:
|
|
269
|
+
os.replace(path, aside)
|
|
270
|
+
except OSError:
|
|
271
|
+
return True
|
|
272
|
+
deadline = time.monotonic() + _RESTORE_TIMEOUT_S
|
|
273
|
+
delay = 0.1
|
|
274
|
+
while True:
|
|
275
|
+
try:
|
|
276
|
+
os.replace(aside, path)
|
|
277
|
+
return False
|
|
278
|
+
except OSError as exc:
|
|
279
|
+
if time.monotonic() >= deadline:
|
|
280
|
+
raise OSError(
|
|
281
|
+
f"doctor moved {path.name!r} aside to test its build "
|
|
282
|
+
f"lock but could not restore it; the build output is now "
|
|
283
|
+
f"at {aside}. Move it back to {path} manually."
|
|
284
|
+
) from exc
|
|
285
|
+
time.sleep(delay)
|
|
286
|
+
delay = min(delay * 1.5, 1.0)
|
|
287
|
+
|
|
288
|
+
def filesystem_type(self, path: Path) -> str | None:
|
|
289
|
+
"""The filesystem of the volume holding *path* (e.g. ``"NTFS"`` /
|
|
290
|
+
``"ReFS"``), or ``None`` when it can't be read. A trusted ReFS Dev Drive
|
|
291
|
+
is where Defender runs asynchronous 'performance mode' scans.
|
|
292
|
+
"""
|
|
293
|
+
import ctypes
|
|
294
|
+
|
|
295
|
+
if not hasattr(ctypes, "windll"):
|
|
296
|
+
return None
|
|
297
|
+
drive = os.path.splitdrive(os.path.abspath(str(path)))[0]
|
|
298
|
+
if not drive:
|
|
299
|
+
return None
|
|
300
|
+
buf = ctypes.create_unicode_buffer(261)
|
|
301
|
+
try:
|
|
302
|
+
ok = ctypes.windll.kernel32.GetVolumeInformationW( # type: ignore[attr-defined]
|
|
303
|
+
drive + "\\", None, 0, None, None, None, buf, len(buf)
|
|
304
|
+
)
|
|
305
|
+
except OSError:
|
|
306
|
+
return None
|
|
307
|
+
return buf.value if ok else None
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _capture(argv: list[str]) -> str:
|
|
311
|
+
try:
|
|
312
|
+
proc = subprocess.run(argv, capture_output=True, text=True)
|
|
313
|
+
except (OSError, ValueError):
|
|
314
|
+
return ""
|
|
315
|
+
return proc.stdout if proc.returncode == 0 else ""
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _macho_platforms(data: bytes) -> set[str]:
|
|
319
|
+
if len(data) < 4:
|
|
320
|
+
return set()
|
|
321
|
+
magic = struct.unpack(">I", data[:4])[0]
|
|
322
|
+
# Fat binary: parse each arch slice.
|
|
323
|
+
if magic in (0xCAFEBABE, 0xBEBAFECA):
|
|
324
|
+
platforms: set[str] = set()
|
|
325
|
+
nfat = struct.unpack(">I", data[4:8])[0]
|
|
326
|
+
for i in range(nfat):
|
|
327
|
+
off = 8 + i * 20
|
|
328
|
+
(offset,) = struct.unpack(">I", data[off + 8 : off + 12])
|
|
329
|
+
platforms |= _thin_platforms(data[offset:])
|
|
330
|
+
return platforms
|
|
331
|
+
return _thin_platforms(data)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _thin_platforms(data: bytes) -> set[str]:
|
|
335
|
+
if len(data) < 4:
|
|
336
|
+
return set()
|
|
337
|
+
magic = struct.unpack(">I", data[:4])[0]
|
|
338
|
+
if magic not in _MACHO_MAGICS:
|
|
339
|
+
return set()
|
|
340
|
+
little = magic in (0xCEFAEDFE, 0xCFFAEDFE)
|
|
341
|
+
is64 = magic in (0xFEEDFACF, 0xCFFAEDFE)
|
|
342
|
+
end = "<" if little else ">"
|
|
343
|
+
header = 32 if is64 else 28
|
|
344
|
+
ncmds = struct.unpack(end + "I", data[16:20])[0]
|
|
345
|
+
offset = header
|
|
346
|
+
platforms: set[str] = set()
|
|
347
|
+
for _ in range(ncmds):
|
|
348
|
+
if offset + 8 > len(data):
|
|
349
|
+
break
|
|
350
|
+
cmd, cmdsize = struct.unpack(end + "II", data[offset : offset + 8])
|
|
351
|
+
if cmd == LC_BUILD_VERSION and offset + 16 <= len(data):
|
|
352
|
+
(plat,) = struct.unpack(end + "I", data[offset + 8 : offset + 12])
|
|
353
|
+
platforms.add(PLATFORM_NAMES.get(plat, f"platform-{plat}"))
|
|
354
|
+
if cmdsize == 0:
|
|
355
|
+
break
|
|
356
|
+
offset += cmdsize
|
|
357
|
+
return platforms
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Check result types for doctor."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Status(Enum):
|
|
10
|
+
PASS = "PASS"
|
|
11
|
+
WARN = "WARN"
|
|
12
|
+
FAIL = "FAIL"
|
|
13
|
+
SKIP = "SKIP"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Severity ordering for computing the overall worst status.
|
|
17
|
+
_ORDER = {Status.PASS: 0, Status.SKIP: 0, Status.WARN: 1, Status.FAIL: 2}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class CheckResult:
|
|
22
|
+
name: str
|
|
23
|
+
status: Status
|
|
24
|
+
detail: str = ""
|
|
25
|
+
hint: str = ""
|
|
26
|
+
|
|
27
|
+
def render(self) -> str:
|
|
28
|
+
line = f"[{self.status.value}] {self.name}"
|
|
29
|
+
if self.detail:
|
|
30
|
+
line += f": {self.detail}"
|
|
31
|
+
if self.hint and self.status in (Status.WARN, Status.FAIL):
|
|
32
|
+
line += f"\n hint: {self.hint}"
|
|
33
|
+
return line
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def worst_status(results: list[CheckResult]) -> Status:
|
|
37
|
+
worst = Status.PASS
|
|
38
|
+
for r in results:
|
|
39
|
+
if _ORDER[r.status] > _ORDER[worst]:
|
|
40
|
+
worst = r.status
|
|
41
|
+
return worst
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""kivyforge shared lock core (spec 02).
|
|
2
|
+
|
|
3
|
+
Platform-neutral lock building blocks shared by every backend: the PEP 751
|
|
4
|
+
``[[packages]]`` model, the drift check, the neutral pip helpers, and the
|
|
5
|
+
generic wheel+runtime engine (``kivyforge.lock.wheelruntime``). The iOS lock
|
|
6
|
+
surface lives in ``kivyforge.platforms.ios.lock``; the macOS/Linux surfaces in
|
|
7
|
+
``kivyforge.platforms.<os>.lock``.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .model import (
|
|
13
|
+
LOCK_VERSION,
|
|
14
|
+
LockedPackage,
|
|
15
|
+
LockedWheel,
|
|
16
|
+
PackageDep,
|
|
17
|
+
canonical_name,
|
|
18
|
+
)
|
|
19
|
+
from .reader import LockError, compute_pyproject_sha256, is_in_sync
|
|
20
|
+
from .resolver import (
|
|
21
|
+
MIN_PIP_VERSION,
|
|
22
|
+
abi_tags,
|
|
23
|
+
pip_python_version,
|
|
24
|
+
pip_version,
|
|
25
|
+
version_str,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"LOCK_VERSION",
|
|
30
|
+
"LockedPackage",
|
|
31
|
+
"LockedWheel",
|
|
32
|
+
"PackageDep",
|
|
33
|
+
"canonical_name",
|
|
34
|
+
"LockError",
|
|
35
|
+
"compute_pyproject_sha256",
|
|
36
|
+
"is_in_sync",
|
|
37
|
+
"MIN_PIP_VERSION",
|
|
38
|
+
"abi_tags",
|
|
39
|
+
"pip_python_version",
|
|
40
|
+
"pip_version",
|
|
41
|
+
"version_str",
|
|
42
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Run pip with the *target's* PEP 508 marker environment.
|
|
2
|
+
|
|
3
|
+
kivyforge invokes this instead of ``-m pip`` when cross-resolving. It replaces
|
|
4
|
+
``packaging.markers.default_environment`` — the single function every
|
|
5
|
+
``Marker.evaluate()`` consults — with the target environment supplied as JSON in
|
|
6
|
+
``KIVYFORGE_TARGET_MARKER_ENV``, then hands control to pip unchanged.
|
|
7
|
+
|
|
8
|
+
pip still performs the whole resolution, including backtracking and platform
|
|
9
|
+
wheel-tag expansion; only its answer to "what platform am I resolving for?"
|
|
10
|
+
changes. kivyforge deliberately does not reimplement any of that.
|
|
11
|
+
|
|
12
|
+
Platform-neutral by design: each backend describes its own target
|
|
13
|
+
(``platforms/android/lock/markers.py``, ``platforms/ios/lock/markers.py``).
|
|
14
|
+
|
|
15
|
+
This is a documented dependency on a pip internal, so it fails loudly rather
|
|
16
|
+
than silently falling back to host markers: a quiet fallback would reintroduce
|
|
17
|
+
exactly the host-dependent lock this exists to prevent.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
MARKER_ENV_VAR = "KIVYFORGE_TARGET_MARKER_ENV"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main(argv: list[str]) -> int:
|
|
30
|
+
raw = os.environ.get(MARKER_ENV_VAR)
|
|
31
|
+
if not raw:
|
|
32
|
+
sys.stderr.write(
|
|
33
|
+
f"{MARKER_ENV_VAR} is not set; refusing to resolve with the "
|
|
34
|
+
"host's marker environment.\n"
|
|
35
|
+
)
|
|
36
|
+
return 2
|
|
37
|
+
environment = json.loads(raw)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from pip._vendor.packaging import markers
|
|
41
|
+
except ImportError:
|
|
42
|
+
sys.stderr.write(
|
|
43
|
+
"this pip does not vendor packaging.markers where kivyforge "
|
|
44
|
+
"expects it, so environment markers cannot be evaluated for the "
|
|
45
|
+
"target platform. Please report the pip version.\n"
|
|
46
|
+
)
|
|
47
|
+
return 3
|
|
48
|
+
if not hasattr(markers, "default_environment"):
|
|
49
|
+
sys.stderr.write(
|
|
50
|
+
"pip's vendored packaging has no default_environment(); kivyforge "
|
|
51
|
+
"cannot retarget marker evaluation. Please report the pip version.\n"
|
|
52
|
+
)
|
|
53
|
+
return 3
|
|
54
|
+
|
|
55
|
+
markers.default_environment = lambda: dict(environment)
|
|
56
|
+
|
|
57
|
+
from pip._internal.cli.main import main as pip_main
|
|
58
|
+
|
|
59
|
+
return pip_main(argv)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
raise SystemExit(main(sys.argv[1:]))
|