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,410 @@
|
|
|
1
|
+
"""iOS-specific doctor checks (spec 05). Pure over a ``Probe``.
|
|
2
|
+
|
|
3
|
+
Reuses the platform-neutral checks (pip, toolchain version, app source dir) from
|
|
4
|
+
``checks_common`` and adds the iOS environment (Xcode, command-line tools,
|
|
5
|
+
simulator runtimes) and project checks (signing, provisioning, icon, Swift
|
|
6
|
+
toolchain, find_links, reachable hosts, native binaries, privacy manifests) over
|
|
7
|
+
the ``pylock.ios.toml`` model.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
|
|
15
|
+
from kivyforge.config import ConfigError, load_config
|
|
16
|
+
from kivyforge.config.icons import APP_ICON_SIZE, icon_source_problem
|
|
17
|
+
from kivyforge.config.model import Config
|
|
18
|
+
from kivyforge.doctor import checks_common as C
|
|
19
|
+
from kivyforge.doctor.checks_common import _ver_tuple
|
|
20
|
+
from kivyforge.doctor.probe import Probe, RealProbe
|
|
21
|
+
from kivyforge.doctor.result import CheckResult, Status
|
|
22
|
+
from kivyforge.lock.find_links import find_links_doctor_detail
|
|
23
|
+
from kivyforge.lock.reader import LockError
|
|
24
|
+
|
|
25
|
+
from .lock import load
|
|
26
|
+
from .lock.model import Lockfile
|
|
27
|
+
|
|
28
|
+
MIN_XCODE = "15.0" # devicectl (run --device) requires Xcode 15+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---- environment checks -------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_xcode_version(probe: Probe) -> CheckResult:
|
|
35
|
+
version = probe.xcode_version()
|
|
36
|
+
if version is None:
|
|
37
|
+
return CheckResult(
|
|
38
|
+
"Xcode version",
|
|
39
|
+
Status.FAIL,
|
|
40
|
+
"Xcode not found",
|
|
41
|
+
hint="install Xcode from the App Store, then `xcode-select --switch`.",
|
|
42
|
+
)
|
|
43
|
+
if _ver_tuple(version) < _ver_tuple(MIN_XCODE):
|
|
44
|
+
return CheckResult(
|
|
45
|
+
"Xcode version",
|
|
46
|
+
Status.FAIL,
|
|
47
|
+
f"{version} (need >= {MIN_XCODE})",
|
|
48
|
+
hint=f"update Xcode to {MIN_XCODE} or newer.",
|
|
49
|
+
)
|
|
50
|
+
return CheckResult("Xcode version", Status.PASS, version)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_command_line_tools(probe: Probe) -> CheckResult:
|
|
54
|
+
path = probe.xcode_select_path()
|
|
55
|
+
if not path:
|
|
56
|
+
return CheckResult(
|
|
57
|
+
"Command-line tools",
|
|
58
|
+
Status.FAIL,
|
|
59
|
+
"xcode-select path not set",
|
|
60
|
+
hint="run `xcode-select --install` or `sudo xcode-select --switch`.",
|
|
61
|
+
)
|
|
62
|
+
if not probe.has_xcrun_clang():
|
|
63
|
+
return CheckResult(
|
|
64
|
+
"Command-line tools",
|
|
65
|
+
Status.FAIL,
|
|
66
|
+
"xcrun clang not found",
|
|
67
|
+
hint="run `xcode-select --install`.",
|
|
68
|
+
)
|
|
69
|
+
return CheckResult("Command-line tools", Status.PASS, path)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_simulator_runtimes(probe: Probe, config: Config | None) -> CheckResult:
|
|
73
|
+
runtimes = probe.simulator_runtimes()
|
|
74
|
+
if not runtimes:
|
|
75
|
+
return CheckResult(
|
|
76
|
+
"Simulator runtimes",
|
|
77
|
+
Status.WARN,
|
|
78
|
+
"no iOS simulator runtimes installed",
|
|
79
|
+
hint="install one via Xcode > Settings > Components.",
|
|
80
|
+
)
|
|
81
|
+
if config is not None:
|
|
82
|
+
floor = config.ios_required.deployment_target
|
|
83
|
+
ok = [r for r in runtimes if _ver_tuple(r) >= _ver_tuple(floor)]
|
|
84
|
+
if not ok:
|
|
85
|
+
return CheckResult(
|
|
86
|
+
"Simulator runtimes",
|
|
87
|
+
Status.WARN,
|
|
88
|
+
f"installed {', '.join(runtimes)} < deployment_target {floor}",
|
|
89
|
+
hint=f"install an iOS {floor}+ simulator runtime.",
|
|
90
|
+
)
|
|
91
|
+
return CheckResult("Simulator runtimes", Status.PASS, ", ".join(runtimes))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---- project checks -----------------------------------------------------
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def check_signing_identity(probe: Probe, config: Config) -> CheckResult:
|
|
98
|
+
signing = config.ios_required.signing
|
|
99
|
+
if signing.auto_signing:
|
|
100
|
+
return CheckResult("Signing identity", Status.PASS, "automatic signing")
|
|
101
|
+
identities = probe.keychain_identities()
|
|
102
|
+
if not any(signing.identity in line for line in identities):
|
|
103
|
+
return CheckResult(
|
|
104
|
+
"Signing identity",
|
|
105
|
+
Status.FAIL,
|
|
106
|
+
f"identity {signing.identity!r} not in keychain",
|
|
107
|
+
hint="import the signing certificate or set auto_signing = true.",
|
|
108
|
+
)
|
|
109
|
+
return CheckResult("Signing identity", Status.PASS, signing.identity)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def check_swift_toolchain(probe: Probe, config: Config) -> CheckResult:
|
|
113
|
+
"""SPM resolution (lock) and compilation (build) need Xcode's Swift toolchain.
|
|
114
|
+
|
|
115
|
+
Skipped for pure-wheel projects — only relevant when ``swift_packages`` is
|
|
116
|
+
declared (spec 07).
|
|
117
|
+
"""
|
|
118
|
+
packages = config.ios_required.swift_packages
|
|
119
|
+
if not packages:
|
|
120
|
+
return CheckResult(
|
|
121
|
+
"Swift package toolchain", Status.SKIP, "no swift_packages declared"
|
|
122
|
+
)
|
|
123
|
+
if not probe.has_swift_toolchain():
|
|
124
|
+
return CheckResult(
|
|
125
|
+
"Swift package toolchain",
|
|
126
|
+
Status.FAIL,
|
|
127
|
+
"swift not found",
|
|
128
|
+
hint="install Xcode's Swift toolchain (`xcode-select --switch "
|
|
129
|
+
"/Applications/Xcode.app`) — required to resolve/build Swift packages.",
|
|
130
|
+
)
|
|
131
|
+
return CheckResult(
|
|
132
|
+
"Swift package toolchain",
|
|
133
|
+
Status.PASS,
|
|
134
|
+
f"{len(packages)} swift package(s) declared",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def check_app_icon(config: Config, project_root: Path) -> CheckResult:
|
|
139
|
+
source = config.ios_required.icons.source
|
|
140
|
+
if not source:
|
|
141
|
+
return CheckResult("App icon", Status.SKIP, "not configured")
|
|
142
|
+
path = (project_root / source).resolve()
|
|
143
|
+
problem = icon_source_problem(path)
|
|
144
|
+
if problem:
|
|
145
|
+
return CheckResult(
|
|
146
|
+
"App icon",
|
|
147
|
+
Status.FAIL,
|
|
148
|
+
f"{source} invalid",
|
|
149
|
+
hint=problem,
|
|
150
|
+
)
|
|
151
|
+
return CheckResult("App icon", Status.PASS, f"{APP_ICON_SIZE}x{APP_ICON_SIZE} PNG")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def check_provisioning_profile(config: Config, project_root: Path) -> CheckResult:
|
|
155
|
+
profile = config.ios_required.signing.provisioning_profile
|
|
156
|
+
if not profile:
|
|
157
|
+
return CheckResult("Provisioning profile", Status.PASS, "not set")
|
|
158
|
+
path = (
|
|
159
|
+
(project_root / profile) if not Path(profile).is_absolute() else Path(profile)
|
|
160
|
+
)
|
|
161
|
+
if not path.exists():
|
|
162
|
+
return CheckResult(
|
|
163
|
+
"Provisioning profile",
|
|
164
|
+
Status.FAIL,
|
|
165
|
+
f"{profile} not found",
|
|
166
|
+
hint="point provisioning_profile at an existing .mobileprovision.",
|
|
167
|
+
)
|
|
168
|
+
return CheckResult("Provisioning profile", Status.PASS, profile)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def check_find_links(config: Config, project_root: Path) -> CheckResult:
|
|
172
|
+
entries = config.ios_required.find_links
|
|
173
|
+
if not entries:
|
|
174
|
+
return CheckResult("find_links directories", Status.SKIP, "not configured")
|
|
175
|
+
|
|
176
|
+
root = project_root.resolve()
|
|
177
|
+
problems: list[tuple[str, str | None]] = []
|
|
178
|
+
warnings: list[tuple[str, str | None]] = []
|
|
179
|
+
ok: list[str] = []
|
|
180
|
+
|
|
181
|
+
for entry in entries:
|
|
182
|
+
path = (root / entry).resolve()
|
|
183
|
+
detail, hint = find_links_doctor_detail(root, entry, path)
|
|
184
|
+
if not path.exists() or not path.is_dir():
|
|
185
|
+
problems.append((detail, hint))
|
|
186
|
+
elif not any(path.glob("*.whl")):
|
|
187
|
+
warnings.append((detail, hint))
|
|
188
|
+
else:
|
|
189
|
+
ok.append(detail)
|
|
190
|
+
|
|
191
|
+
if problems:
|
|
192
|
+
detail = "; ".join(d for d, _ in problems)
|
|
193
|
+
hint = next((h for _, h in problems if h), "")
|
|
194
|
+
return CheckResult("find_links directories", Status.FAIL, detail, hint=hint)
|
|
195
|
+
if warnings:
|
|
196
|
+
detail = "; ".join(d for d, _ in warnings)
|
|
197
|
+
hint = next((h for _, h in warnings if h), "")
|
|
198
|
+
return CheckResult("find_links directories", Status.WARN, detail, hint=hint)
|
|
199
|
+
return CheckResult("find_links directories", Status.PASS, "; ".join(ok))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def check_hosts_reachable(probe: Probe, lock: Lockfile | None) -> CheckResult:
|
|
203
|
+
if lock is None:
|
|
204
|
+
return CheckResult(
|
|
205
|
+
"Required hosts reachable", Status.SKIP, "no pylock.ios.toml"
|
|
206
|
+
)
|
|
207
|
+
hosts = sorted(_lock_hosts(lock))
|
|
208
|
+
if not hosts:
|
|
209
|
+
return CheckResult(
|
|
210
|
+
"Required hosts reachable", Status.PASS, "all artifacts vendored"
|
|
211
|
+
)
|
|
212
|
+
unreachable = [h for h in hosts if not probe.tcp_reachable(h, 443)]
|
|
213
|
+
if unreachable:
|
|
214
|
+
return CheckResult(
|
|
215
|
+
"Required hosts reachable",
|
|
216
|
+
Status.FAIL,
|
|
217
|
+
f"unreachable: {', '.join(unreachable)}",
|
|
218
|
+
hint="check your network/proxy; these hosts serve pinned artifacts.",
|
|
219
|
+
)
|
|
220
|
+
return CheckResult("Required hosts reachable", Status.PASS, ", ".join(hosts))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def check_app_native_binaries(
|
|
224
|
+
probe: Probe, config: Config, project_root: Path
|
|
225
|
+
) -> CheckResult:
|
|
226
|
+
app_dir = project_root / config.kivy.app_dir
|
|
227
|
+
if not app_dir.is_dir():
|
|
228
|
+
return CheckResult(
|
|
229
|
+
"App-local native binaries", Status.SKIP, f"{config.kivy.app_dir} missing"
|
|
230
|
+
)
|
|
231
|
+
offenders: list[str] = []
|
|
232
|
+
unknown: list[str] = []
|
|
233
|
+
for pattern in ("*.so", "*.dylib"):
|
|
234
|
+
for binary in app_dir.rglob(pattern):
|
|
235
|
+
rel = binary.relative_to(project_root)
|
|
236
|
+
platforms = probe.binary_platforms(binary)
|
|
237
|
+
if not platforms:
|
|
238
|
+
# No platform could be read: truncated, corrupt, not a Mach-O,
|
|
239
|
+
# or lacking an LC_BUILD_VERSION. Can't confirm it's iOS-safe.
|
|
240
|
+
unknown.append(str(rel))
|
|
241
|
+
elif not platforms & {"ios", "ios-simulator"}:
|
|
242
|
+
offenders.append(f"{rel} ({', '.join(sorted(platforms))})")
|
|
243
|
+
if offenders:
|
|
244
|
+
return CheckResult(
|
|
245
|
+
"App-local native binaries",
|
|
246
|
+
Status.FAIL,
|
|
247
|
+
"; ".join(offenders),
|
|
248
|
+
hint="non-iOS native code won't load on device; ship it as an iOS wheel.",
|
|
249
|
+
)
|
|
250
|
+
if unknown:
|
|
251
|
+
return CheckResult(
|
|
252
|
+
"App-local native binaries",
|
|
253
|
+
Status.WARN,
|
|
254
|
+
"unrecognized binary: " + "; ".join(unknown),
|
|
255
|
+
hint="could not read an iOS Mach-O platform from these files "
|
|
256
|
+
"(truncated, corrupt, or not a Mach-O); confirm they are iOS dylibs "
|
|
257
|
+
"or ship them as an iOS wheel.",
|
|
258
|
+
)
|
|
259
|
+
return CheckResult("App-local native binaries", Status.PASS, "none non-iOS")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def check_app_privacy_manifest(config: Config, project_root: Path) -> CheckResult:
|
|
263
|
+
manifest = project_root / f"{config.app_slug}-ios" / "PrivacyInfo.xcprivacy"
|
|
264
|
+
if not manifest.exists():
|
|
265
|
+
return CheckResult(
|
|
266
|
+
"App-level privacy manifest",
|
|
267
|
+
Status.WARN,
|
|
268
|
+
"PrivacyInfo.xcprivacy absent",
|
|
269
|
+
hint="`kivyforge build` generates a minimal stub; if you use "
|
|
270
|
+
"required-reason APIs, set [tool.kivy.ios.privacy_manifest].source.",
|
|
271
|
+
)
|
|
272
|
+
return CheckResult("App-level privacy manifest", Status.PASS, "present")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def check_xcframework_privacy_manifests(
|
|
276
|
+
config: Config, project_root: Path
|
|
277
|
+
) -> CheckResult:
|
|
278
|
+
frameworks = project_root / f"{config.app_slug}-ios" / "Frameworks"
|
|
279
|
+
if not frameworks.is_dir():
|
|
280
|
+
return CheckResult(
|
|
281
|
+
"xcframework privacy manifests", Status.SKIP, "project not built"
|
|
282
|
+
)
|
|
283
|
+
missing = []
|
|
284
|
+
for xc in sorted(frameworks.glob("*.xcframework")):
|
|
285
|
+
if not any(xc.rglob("PrivacyInfo.xcprivacy")):
|
|
286
|
+
missing.append(xc.name)
|
|
287
|
+
if missing:
|
|
288
|
+
return CheckResult(
|
|
289
|
+
"xcframework privacy manifests",
|
|
290
|
+
Status.WARN,
|
|
291
|
+
f"no PrivacyInfo in: {', '.join(missing)}",
|
|
292
|
+
hint="each framework's author must add a privacy manifest; "
|
|
293
|
+
"kivyforge cannot add it for them.",
|
|
294
|
+
)
|
|
295
|
+
return CheckResult("xcframework privacy manifests", Status.PASS, "all present")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _lock_hosts(lock: Lockfile) -> set[str]:
|
|
299
|
+
hosts: set[str] = set()
|
|
300
|
+
for pkg in lock.packages:
|
|
301
|
+
for wheel in pkg.wheels:
|
|
302
|
+
if wheel.url:
|
|
303
|
+
_add_host(hosts, wheel.url)
|
|
304
|
+
for xc in lock.xcframeworks:
|
|
305
|
+
if xc.url:
|
|
306
|
+
_add_host(hosts, xc.url)
|
|
307
|
+
for sp in lock.swift_packages:
|
|
308
|
+
if sp.url:
|
|
309
|
+
host = _git_host(sp.url)
|
|
310
|
+
if host:
|
|
311
|
+
hosts.add(host)
|
|
312
|
+
if lock.python_xcframework.url:
|
|
313
|
+
_add_host(hosts, lock.python_xcframework.url)
|
|
314
|
+
return hosts
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _add_host(hosts: set[str], url: str) -> None:
|
|
318
|
+
netloc = urlparse(url).hostname
|
|
319
|
+
if netloc:
|
|
320
|
+
hosts.add(netloc)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _git_host(url: str) -> str | None:
|
|
324
|
+
"""Host of a Git URL, handling both ``https://`` and scp-like ``git@host:``."""
|
|
325
|
+
if "://" in url:
|
|
326
|
+
return urlparse(url).hostname
|
|
327
|
+
if "@" in url and ":" in url:
|
|
328
|
+
return url.split("@", 1)[1].split(":", 1)[0] or None
|
|
329
|
+
return urlparse(url).hostname
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def run_ios_checks(
|
|
333
|
+
probe: Probe,
|
|
334
|
+
*,
|
|
335
|
+
kivyforge_version: str,
|
|
336
|
+
config: Config | None = None,
|
|
337
|
+
project_root: Path | None = None,
|
|
338
|
+
lock: Lockfile | None = None,
|
|
339
|
+
offline: bool = False,
|
|
340
|
+
) -> list[CheckResult]:
|
|
341
|
+
"""Run all iOS checks. Project checks SKIP in environment mode (no config)."""
|
|
342
|
+
project_root = project_root or Path.cwd()
|
|
343
|
+
results = [
|
|
344
|
+
check_xcode_version(probe),
|
|
345
|
+
check_command_line_tools(probe),
|
|
346
|
+
C.check_pip_version(probe),
|
|
347
|
+
check_simulator_runtimes(probe, config),
|
|
348
|
+
C.check_kivyforge_version(probe, kivyforge_version, offline=offline),
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
if config is None:
|
|
352
|
+
for name in (
|
|
353
|
+
"App source directory",
|
|
354
|
+
"Signing identity",
|
|
355
|
+
"Provisioning profile",
|
|
356
|
+
"App icon",
|
|
357
|
+
"Swift package toolchain",
|
|
358
|
+
"find_links directories",
|
|
359
|
+
"Required hosts reachable",
|
|
360
|
+
"App-local native binaries",
|
|
361
|
+
"App-level privacy manifest",
|
|
362
|
+
"xcframework privacy manifests",
|
|
363
|
+
):
|
|
364
|
+
results.append(CheckResult(name, Status.SKIP, C.SKIP_NOTE))
|
|
365
|
+
return results
|
|
366
|
+
|
|
367
|
+
results += [
|
|
368
|
+
C.check_app_dir(config, project_root),
|
|
369
|
+
check_signing_identity(probe, config),
|
|
370
|
+
check_provisioning_profile(config, project_root),
|
|
371
|
+
check_app_icon(config, project_root),
|
|
372
|
+
check_swift_toolchain(probe, config),
|
|
373
|
+
check_find_links(config, project_root),
|
|
374
|
+
check_hosts_reachable(probe, lock),
|
|
375
|
+
check_app_native_binaries(probe, config, project_root),
|
|
376
|
+
check_app_privacy_manifest(config, project_root),
|
|
377
|
+
check_xcframework_privacy_manifests(config, project_root),
|
|
378
|
+
]
|
|
379
|
+
return results
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def ios_doctor(
|
|
383
|
+
cwd: Path, *, kivyforge_version: str, offline: bool
|
|
384
|
+
) -> list[CheckResult]:
|
|
385
|
+
"""Load the iOS project + lock (if present) and run the iOS check set."""
|
|
386
|
+
pyproject = cwd / "pyproject.toml"
|
|
387
|
+
config = None
|
|
388
|
+
lock = None
|
|
389
|
+
parse_results: list[CheckResult] = []
|
|
390
|
+
if pyproject.is_file():
|
|
391
|
+
try:
|
|
392
|
+
config = load_config(pyproject)
|
|
393
|
+
except ConfigError as exc:
|
|
394
|
+
parse_results.append(
|
|
395
|
+
CheckResult("pyproject.toml", Status.FAIL, exc.format())
|
|
396
|
+
)
|
|
397
|
+
lockfile = cwd / "pylock.ios.toml"
|
|
398
|
+
if lockfile.is_file():
|
|
399
|
+
try:
|
|
400
|
+
lock = load(lockfile)
|
|
401
|
+
except LockError as exc:
|
|
402
|
+
parse_results.append(C.lock_parse_fail("ios", exc))
|
|
403
|
+
return parse_results + run_ios_checks(
|
|
404
|
+
RealProbe(),
|
|
405
|
+
kivyforge_version=kivyforge_version,
|
|
406
|
+
config=config,
|
|
407
|
+
project_root=cwd,
|
|
408
|
+
lock=lock,
|
|
409
|
+
offline=offline,
|
|
410
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Generate ``<app>.entitlements`` from ``[tool.kivy.ios.entitlements]`` (spec 06)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import plistlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from kivyforge.config.model import Config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def has_entitlements(config: Config) -> bool:
|
|
12
|
+
return bool(config.ios and config.ios.entitlements)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_entitlements(config: Config) -> dict:
|
|
16
|
+
if not config.ios:
|
|
17
|
+
return {}
|
|
18
|
+
return dict(config.ios.entitlements)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def write_entitlements(config: Config, path: str | Path) -> Path | None:
|
|
22
|
+
"""Write the entitlements plist; returns None when there are none.
|
|
23
|
+
|
|
24
|
+
When no entitlements are configured, any previously generated file is
|
|
25
|
+
removed so a stale ``<app>.entitlements`` (from a since-deleted config
|
|
26
|
+
table) is not left referenced in the bundle.
|
|
27
|
+
"""
|
|
28
|
+
path = Path(path)
|
|
29
|
+
if not has_entitlements(config):
|
|
30
|
+
path.unlink(missing_ok=True)
|
|
31
|
+
return None
|
|
32
|
+
with open(path, "wb") as f:
|
|
33
|
+
plistlib.dump(build_entitlements(config), f, sort_keys=True)
|
|
34
|
+
return path
|