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,543 @@
|
|
|
1
|
+
"""iOS build/run/package/open/status logic (specs 05/06).
|
|
2
|
+
|
|
3
|
+
Extracted from the shared ``cli`` verbs so all iOS-specific behavior lives behind
|
|
4
|
+
the iOS backend, symmetric with ``platforms/macos/cli.py`` and
|
|
5
|
+
``platforms/linux/cli.py``. The verb modules stay thin dispatchers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import plistlib
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from kivyforge.artifacts.collect import CollectError, collect_artifacts
|
|
19
|
+
from kivyforge.artifacts.wheels import BuildSlice
|
|
20
|
+
from kivyforge.cli._common import (
|
|
21
|
+
LOCKFILE_NAME,
|
|
22
|
+
ToolchainError,
|
|
23
|
+
lockfile_path,
|
|
24
|
+
)
|
|
25
|
+
from kivyforge.config import ConfigError, load_config
|
|
26
|
+
from kivyforge.config.icons import IconSourceError
|
|
27
|
+
from kivyforge.lock import LockError, is_in_sync
|
|
28
|
+
from kivyforge.platforms.base import HostCapabilityError
|
|
29
|
+
|
|
30
|
+
from .lock import load
|
|
31
|
+
from .materialize import materialize_project
|
|
32
|
+
from .staging import StagingError, create_staging
|
|
33
|
+
from .xcode import (
|
|
34
|
+
CommandError,
|
|
35
|
+
SigningError,
|
|
36
|
+
XcodeBuild,
|
|
37
|
+
archive_command,
|
|
38
|
+
build_command,
|
|
39
|
+
default_simulator_arch,
|
|
40
|
+
devicectl_install,
|
|
41
|
+
devicectl_launch,
|
|
42
|
+
devicectl_list,
|
|
43
|
+
export_command,
|
|
44
|
+
export_options_plist,
|
|
45
|
+
open_command,
|
|
46
|
+
preflight_signing,
|
|
47
|
+
product_app_path,
|
|
48
|
+
resolve_device_destination,
|
|
49
|
+
resolve_simulator_destination,
|
|
50
|
+
run_command,
|
|
51
|
+
simctl_install,
|
|
52
|
+
simctl_launch,
|
|
53
|
+
simctl_list,
|
|
54
|
+
)
|
|
55
|
+
from .xcode.commands import SIGNING_IDENTITY_ENV, resolve_signing_identity
|
|
56
|
+
|
|
57
|
+
# ---- build --------------------------------------------------------------- #
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def ios_build(
|
|
61
|
+
project_root: Path,
|
|
62
|
+
*,
|
|
63
|
+
target: str | None,
|
|
64
|
+
arch: str | None,
|
|
65
|
+
no_verify_lock: bool,
|
|
66
|
+
no_cache: bool,
|
|
67
|
+
team_id: str | None,
|
|
68
|
+
signing_identity: str | None,
|
|
69
|
+
export_method: str,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Download artifacts, generate the Xcode project, and optionally build it."""
|
|
72
|
+
_require_macos_host()
|
|
73
|
+
# Signing pre-flight runs before any artifact work so --device/--release
|
|
74
|
+
# fail fast on a missing team_id (spec 05 step 7).
|
|
75
|
+
config = _load_config(project_root / "pyproject.toml")
|
|
76
|
+
if target is not None:
|
|
77
|
+
try:
|
|
78
|
+
preflight_signing(config, target, team_id_flag=team_id)
|
|
79
|
+
except SigningError as exc:
|
|
80
|
+
raise ToolchainError(str(exc)) from exc
|
|
81
|
+
|
|
82
|
+
prepare_build(
|
|
83
|
+
config,
|
|
84
|
+
project_root,
|
|
85
|
+
target=target,
|
|
86
|
+
arch=arch,
|
|
87
|
+
no_verify_lock=no_verify_lock,
|
|
88
|
+
no_cache=no_cache,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if target is None:
|
|
92
|
+
click.echo(
|
|
93
|
+
"Project ready. Open it with `kivyforge open` or build with "
|
|
94
|
+
"`kivyforge build --simulator`."
|
|
95
|
+
)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
xb = XcodeBuild.from_config(config, project_root)
|
|
99
|
+
try:
|
|
100
|
+
_xcodebuild_step7(
|
|
101
|
+
xb,
|
|
102
|
+
config,
|
|
103
|
+
target=target,
|
|
104
|
+
arch=arch,
|
|
105
|
+
team_id_flag=team_id,
|
|
106
|
+
signing_identity_flag=signing_identity,
|
|
107
|
+
export_method=export_method,
|
|
108
|
+
)
|
|
109
|
+
except (CommandError, SigningError) as exc:
|
|
110
|
+
raise ToolchainError(str(exc)) from exc
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def prepare_build(
|
|
114
|
+
config,
|
|
115
|
+
project_root: Path,
|
|
116
|
+
*,
|
|
117
|
+
target: str | None,
|
|
118
|
+
arch: str | None,
|
|
119
|
+
no_verify_lock: bool,
|
|
120
|
+
no_cache: bool,
|
|
121
|
+
) -> BuildSlice:
|
|
122
|
+
"""Run build steps 1-6 (drift, artifact collection, project generation).
|
|
123
|
+
|
|
124
|
+
Shared by ``kivyforge build`` and ``kivyforge run``. A bare ``toolchain
|
|
125
|
+
build`` (no target) collects both the device and simulator pip-deps slices
|
|
126
|
+
so the generated project builds either Xcode destination without re-running
|
|
127
|
+
the toolchain; a targeted build collects only its slice. Returns the primary
|
|
128
|
+
slice (the targeted one, or the device slice for a bare build).
|
|
129
|
+
"""
|
|
130
|
+
pyproject = project_root / "pyproject.toml"
|
|
131
|
+
lock = _load_lock(project_root)
|
|
132
|
+
if not no_verify_lock and not is_in_sync(lock, pyproject.read_text("utf-8")):
|
|
133
|
+
raise ToolchainError(
|
|
134
|
+
f"{LOCKFILE_NAME} is out of date with pyproject.toml.\n"
|
|
135
|
+
" Run `kivyforge lock` to regenerate it (or pass --no-verify-lock "
|
|
136
|
+
"to build against the stale lock anyway)."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
build_slices = _resolve_slices(target, arch, config.ios.deployment_target)
|
|
140
|
+
tags = ", ".join(s.platform_tag for s in build_slices)
|
|
141
|
+
# Only `kivyforge package` (target="release") gets byte-compiled/stripped
|
|
142
|
+
# app sources + pip-deps; `build`/`run` always keep readable .py, matching
|
|
143
|
+
# every other platform's release-only default.
|
|
144
|
+
release = target == "release"
|
|
145
|
+
try:
|
|
146
|
+
layout = create_staging(
|
|
147
|
+
config,
|
|
148
|
+
project_root,
|
|
149
|
+
release=release,
|
|
150
|
+
python_version=lock.python_xcframework.version if release else None,
|
|
151
|
+
echo=click.echo,
|
|
152
|
+
)
|
|
153
|
+
click.echo(f"Collecting artifacts for {tags} ...")
|
|
154
|
+
collect_artifacts(
|
|
155
|
+
lock,
|
|
156
|
+
layout,
|
|
157
|
+
build_slices=build_slices,
|
|
158
|
+
project_root=project_root,
|
|
159
|
+
no_cache=no_cache,
|
|
160
|
+
release=release,
|
|
161
|
+
build_settings=config.ios.python_build_settings,
|
|
162
|
+
echo=click.echo,
|
|
163
|
+
)
|
|
164
|
+
materialize_project(
|
|
165
|
+
config,
|
|
166
|
+
project_root,
|
|
167
|
+
layout=layout,
|
|
168
|
+
python_version=lock.python_xcframework.version,
|
|
169
|
+
last_upgrade_check=_xcode_last_upgrade_check(),
|
|
170
|
+
swift_packages=lock.swift_packages,
|
|
171
|
+
xcframeworks=lock.xcframeworks,
|
|
172
|
+
)
|
|
173
|
+
except CollectError as exc:
|
|
174
|
+
raise ToolchainError(str(exc)) from exc
|
|
175
|
+
except IconSourceError as exc:
|
|
176
|
+
raise ToolchainError(str(exc)) from exc
|
|
177
|
+
except StagingError as exc:
|
|
178
|
+
raise ToolchainError(str(exc)) from exc
|
|
179
|
+
|
|
180
|
+
staging = project_root / f"{config.app_slug}-ios"
|
|
181
|
+
click.echo(f"Generated {staging.relative_to(project_root)}")
|
|
182
|
+
return build_slices[0]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _xcodebuild_step7(
|
|
186
|
+
xb: XcodeBuild,
|
|
187
|
+
config,
|
|
188
|
+
*,
|
|
189
|
+
target: str,
|
|
190
|
+
arch: str | None,
|
|
191
|
+
team_id_flag: str | None,
|
|
192
|
+
signing_identity_flag: str | None,
|
|
193
|
+
export_method: str,
|
|
194
|
+
) -> None:
|
|
195
|
+
auto_signing = config.ios.signing.auto_signing
|
|
196
|
+
if target in ("simulator", "device"):
|
|
197
|
+
# Effective signing identity: --signing-identity → env → pyproject.
|
|
198
|
+
# The pyproject default ("Apple Development") is meant for exactly this
|
|
199
|
+
# case — a device debug build.
|
|
200
|
+
identity = resolve_signing_identity(config, identity_flag=signing_identity_flag)
|
|
201
|
+
sim_arch = arch if target == "simulator" else None
|
|
202
|
+
click.echo(f"xcodebuild build ({target}) ...")
|
|
203
|
+
run_command(
|
|
204
|
+
build_command(
|
|
205
|
+
xb,
|
|
206
|
+
target,
|
|
207
|
+
arch=sim_arch,
|
|
208
|
+
signing_identity=identity,
|
|
209
|
+
allow_provisioning_updates=auto_signing and target == "device",
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
# --release: archive, then export a signed .ipa (spec 05 step 7).
|
|
215
|
+
# Only an *explicit* --signing-identity flag or KIVYFORGE_SIGNING_IDENTITY
|
|
216
|
+
# env var applies here — the pyproject default is for --device debug builds
|
|
217
|
+
# and would force the wrong certificate type (Development) onto what needs
|
|
218
|
+
# to be a Distribution-signed archive/export (see archive_command).
|
|
219
|
+
release_identity = signing_identity_flag or os.environ.get(SIGNING_IDENTITY_ENV)
|
|
220
|
+
resolved_team_id = preflight_signing(config, "release", team_id_flag=team_id_flag)
|
|
221
|
+
if resolved_team_id is None:
|
|
222
|
+
# preflight_signing only returns None for --simulator; unreachable here.
|
|
223
|
+
raise SigningError(
|
|
224
|
+
"code signing required for --release, but no team_id is set."
|
|
225
|
+
)
|
|
226
|
+
xb.build_dir.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
click.echo("xcodebuild archive ...")
|
|
228
|
+
run_command(
|
|
229
|
+
archive_command(
|
|
230
|
+
xb,
|
|
231
|
+
signing_identity=release_identity,
|
|
232
|
+
allow_provisioning_updates=auto_signing,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
options = export_options_plist(
|
|
237
|
+
method=export_method,
|
|
238
|
+
team_id=resolved_team_id,
|
|
239
|
+
upload_symbols=config.ios.signing.upload_symbols,
|
|
240
|
+
signing_identity=release_identity,
|
|
241
|
+
)
|
|
242
|
+
options_path = xb.build_dir / "ExportOptions.plist"
|
|
243
|
+
with open(options_path, "wb") as fh:
|
|
244
|
+
plistlib.dump(options, fh)
|
|
245
|
+
|
|
246
|
+
click.echo("xcodebuild -exportArchive ...")
|
|
247
|
+
run_command(
|
|
248
|
+
export_command(xb, options_path, allow_provisioning_updates=auto_signing)
|
|
249
|
+
)
|
|
250
|
+
click.echo(f"Exported {xb.ipa_path.relative_to(xb.project_root)}")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _resolve_slices(
|
|
254
|
+
target: str | None, arch: str | None, deployment_target: str
|
|
255
|
+
) -> list[BuildSlice]:
|
|
256
|
+
"""Slices to collect for *target*.
|
|
257
|
+
|
|
258
|
+
A bare ``kivyforge build`` (``target is None``) collects both device and
|
|
259
|
+
simulator so the "ready to open" project builds either Xcode destination —
|
|
260
|
+
Xcode's default destination is usually a simulator, so collecting only the
|
|
261
|
+
device slice would make the very first in-Xcode build fail. A targeted build
|
|
262
|
+
collects only the slice it is about to build.
|
|
263
|
+
|
|
264
|
+
The simulator slice defaults to the host's native arch (``--arch`` overrides):
|
|
265
|
+
a single ``pip-deps-simulator`` directory holds one arch's compiled ``.so``
|
|
266
|
+
files, so the right one to stage is the arch the developer's machine actually
|
|
267
|
+
runs — ``x86_64`` on Intel, ``arm64`` on Apple Silicon. The lock pins both
|
|
268
|
+
simulator arches; the build picks the host's.
|
|
269
|
+
"""
|
|
270
|
+
sim_arch = arch or default_simulator_arch()
|
|
271
|
+
if target is None:
|
|
272
|
+
return [
|
|
273
|
+
BuildSlice("device", "arm64", deployment_target),
|
|
274
|
+
BuildSlice("simulator", sim_arch, deployment_target),
|
|
275
|
+
]
|
|
276
|
+
return [_resolve_slice(target, arch, deployment_target)]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _resolve_slice(
|
|
280
|
+
target: str | None, arch: str | None, deployment_target: str
|
|
281
|
+
) -> BuildSlice:
|
|
282
|
+
if target == "simulator":
|
|
283
|
+
return BuildSlice(
|
|
284
|
+
"simulator", arch or default_simulator_arch(), deployment_target
|
|
285
|
+
)
|
|
286
|
+
# device or release builds target device/arm64.
|
|
287
|
+
return BuildSlice("device", "arm64", deployment_target)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _xcode_last_upgrade_check() -> str | None:
|
|
291
|
+
"""LastUpgradeCheck for the installed Xcode (e.g. Xcode 26.5 -> "2650").
|
|
292
|
+
|
|
293
|
+
Setting the generated project's ``LastUpgradeCheck`` to the running Xcode's
|
|
294
|
+
version suppresses the "Update to recommended settings" banner. Returns
|
|
295
|
+
``None`` when Xcode can't be queried, in which case the generator falls back
|
|
296
|
+
to its built-in constant.
|
|
297
|
+
"""
|
|
298
|
+
from kivyforge.doctor.probe import RealProbe
|
|
299
|
+
|
|
300
|
+
version = RealProbe().xcode_version()
|
|
301
|
+
return _encode_last_upgrade_check(version) if version else None
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _encode_last_upgrade_check(version: str) -> str | None:
|
|
305
|
+
"""Encode an Xcode version string ("26.5", "16.2.1") as Xcode's integer
|
|
306
|
+
LastUpgradeCheck code (major*100 + minor*10 + patch), or ``None``.
|
|
307
|
+
"""
|
|
308
|
+
match = re.search(r"(\d+)(?:\.(\d+))?(?:\.(\d+))?", version)
|
|
309
|
+
if match is None:
|
|
310
|
+
return None
|
|
311
|
+
major, minor, patch = (int(match.group(i) or 0) for i in (1, 2, 3))
|
|
312
|
+
return str(major * 100 + minor * 10 + patch)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _require_macos_host() -> None:
|
|
316
|
+
"""Gate the Xcode-bound verbs (mirrors the macOS/Linux/Windows backends).
|
|
317
|
+
|
|
318
|
+
``IosPlatform.check_host_capability`` has always described this rule; until
|
|
319
|
+
now nothing called it, so an off-macOS invocation failed later with
|
|
320
|
+
whatever error xcodebuild or SPM happened to raise first.
|
|
321
|
+
"""
|
|
322
|
+
from kivyforge.platforms import get_platform
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
get_platform("ios").check_host_capability()
|
|
326
|
+
except HostCapabilityError as exc:
|
|
327
|
+
raise ToolchainError(str(exc)) from exc
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _load_config(pyproject: Path):
|
|
331
|
+
try:
|
|
332
|
+
return load_config(pyproject)
|
|
333
|
+
except ConfigError as exc:
|
|
334
|
+
raise ToolchainError(exc.format()) from exc
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _load_lock(project_root: Path):
|
|
338
|
+
path = lockfile_path(project_root)
|
|
339
|
+
if not path.is_file():
|
|
340
|
+
raise ToolchainError(f"no {LOCKFILE_NAME} found. Run `kivyforge lock` first.")
|
|
341
|
+
try:
|
|
342
|
+
return load(path)
|
|
343
|
+
except LockError as exc:
|
|
344
|
+
raise ToolchainError(str(exc)) from exc
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ---- run ----------------------------------------------------------------- #
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def ios_run(
|
|
351
|
+
project_root: Path,
|
|
352
|
+
*,
|
|
353
|
+
target: str,
|
|
354
|
+
destination: str | None,
|
|
355
|
+
no_build: bool,
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Build (unless --no-build), install, and launch the app."""
|
|
358
|
+
_require_macos_host()
|
|
359
|
+
try:
|
|
360
|
+
config = load_config(project_root / "pyproject.toml")
|
|
361
|
+
except ConfigError as exc:
|
|
362
|
+
raise ToolchainError(exc.format()) from exc
|
|
363
|
+
|
|
364
|
+
xb = XcodeBuild.from_config(config, project_root)
|
|
365
|
+
derived_data = xb.build_dir / "DerivedData"
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
if not no_build:
|
|
369
|
+
if target == "device":
|
|
370
|
+
preflight_signing(config, "device")
|
|
371
|
+
prepare_build(
|
|
372
|
+
config,
|
|
373
|
+
project_root,
|
|
374
|
+
target=target,
|
|
375
|
+
arch=None,
|
|
376
|
+
no_verify_lock=False,
|
|
377
|
+
no_cache=False,
|
|
378
|
+
)
|
|
379
|
+
click.echo(f"xcodebuild build ({target}) ...")
|
|
380
|
+
run_command(build_command(xb, target, derived_data_path=derived_data))
|
|
381
|
+
|
|
382
|
+
app = product_app_path(derived_data, xb.scheme, target)
|
|
383
|
+
if not app.exists():
|
|
384
|
+
raise ToolchainError(
|
|
385
|
+
f"built app not found at {app}.\n"
|
|
386
|
+
" Run without --no-build, or open the project in Xcode and build "
|
|
387
|
+
"once to populate DerivedData."
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
bundle_id = config.ios_required.bundle_id
|
|
391
|
+
if target == "simulator":
|
|
392
|
+
_run_simulator(destination, app, bundle_id)
|
|
393
|
+
else:
|
|
394
|
+
_run_device(destination, app, bundle_id)
|
|
395
|
+
except SigningError as exc:
|
|
396
|
+
raise ToolchainError(str(exc)) from exc
|
|
397
|
+
except CommandError as exc:
|
|
398
|
+
raise ToolchainError(str(exc)) from exc
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def ios_list_devices() -> None:
|
|
402
|
+
for builder in (simctl_list, devicectl_list):
|
|
403
|
+
proc = run_command(builder(), check=False)
|
|
404
|
+
if proc.stdout:
|
|
405
|
+
click.echo(proc.stdout)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _run_simulator(destination: str | None, app: Path, bundle_id: str) -> None:
|
|
409
|
+
device = resolve_simulator_destination(destination)
|
|
410
|
+
label = f"{device.name} ({device.udid})"
|
|
411
|
+
click.echo(f"Installing on simulator {label} ...")
|
|
412
|
+
run_command(simctl_install(device.udid, app))
|
|
413
|
+
click.echo(f"Launching {bundle_id} ...")
|
|
414
|
+
run_command(simctl_launch(device.udid, bundle_id))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _run_device(destination: str | None, app: Path, bundle_id: str) -> None:
|
|
418
|
+
device = resolve_device_destination(destination)
|
|
419
|
+
label = f"{device.name} ({device.identifier})"
|
|
420
|
+
click.echo(f"Installing on device {label} ...")
|
|
421
|
+
run_command(devicectl_install(device.identifier, app))
|
|
422
|
+
click.echo(f"Launching {bundle_id} ...")
|
|
423
|
+
run_command(devicectl_launch(device.identifier, bundle_id))
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# ---- package ------------------------------------------------------------- #
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def ios_package(
|
|
430
|
+
project_root: Path,
|
|
431
|
+
*,
|
|
432
|
+
team_id: str | None,
|
|
433
|
+
signing_identity: str | None,
|
|
434
|
+
export_method: str,
|
|
435
|
+
no_verify_lock: bool,
|
|
436
|
+
no_cache: bool,
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Build the release archive and export a signed ``.ipa``."""
|
|
439
|
+
_require_macos_host()
|
|
440
|
+
config = _load_config(project_root / "pyproject.toml")
|
|
441
|
+
try:
|
|
442
|
+
prepare_build(
|
|
443
|
+
config,
|
|
444
|
+
project_root,
|
|
445
|
+
target="release",
|
|
446
|
+
arch=None,
|
|
447
|
+
no_verify_lock=no_verify_lock,
|
|
448
|
+
no_cache=no_cache,
|
|
449
|
+
)
|
|
450
|
+
xb = XcodeBuild.from_config(config, project_root)
|
|
451
|
+
_xcodebuild_step7(
|
|
452
|
+
xb,
|
|
453
|
+
config,
|
|
454
|
+
target="release",
|
|
455
|
+
arch=None,
|
|
456
|
+
team_id_flag=team_id,
|
|
457
|
+
signing_identity_flag=signing_identity,
|
|
458
|
+
export_method=export_method,
|
|
459
|
+
)
|
|
460
|
+
except (CommandError, SigningError) as exc:
|
|
461
|
+
raise ToolchainError(str(exc)) from exc
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
# ---- open ---------------------------------------------------------------- #
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def ios_open(project_root: Path) -> None:
|
|
468
|
+
"""Open ``<app>-ios/<app>.xcodeproj`` in Xcode."""
|
|
469
|
+
_require_macos_host()
|
|
470
|
+
try:
|
|
471
|
+
config = load_config(project_root / "pyproject.toml")
|
|
472
|
+
except ConfigError as exc:
|
|
473
|
+
raise ToolchainError(exc.format()) from exc
|
|
474
|
+
|
|
475
|
+
slug = config.app_slug
|
|
476
|
+
xcodeproj = project_root / f"{slug}-ios" / f"{slug}.xcodeproj"
|
|
477
|
+
if not xcodeproj.exists():
|
|
478
|
+
raise ToolchainError(
|
|
479
|
+
f"{xcodeproj.name} does not exist yet.\n"
|
|
480
|
+
" Run `kivyforge build` first to generate the Xcode project."
|
|
481
|
+
)
|
|
482
|
+
try:
|
|
483
|
+
run_command(open_command(xcodeproj))
|
|
484
|
+
except CommandError as exc:
|
|
485
|
+
raise ToolchainError(str(exc)) from exc
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---- status -------------------------------------------------------------- #
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def ios_status(project_root: Path) -> None:
|
|
492
|
+
"""Show app identity, Python version, lock sync, and build state."""
|
|
493
|
+
pyproject = project_root / "pyproject.toml"
|
|
494
|
+
try:
|
|
495
|
+
config = load_config(pyproject)
|
|
496
|
+
except ConfigError as exc:
|
|
497
|
+
raise ToolchainError(exc.format()) from exc
|
|
498
|
+
|
|
499
|
+
ios = config.ios_required
|
|
500
|
+
click.echo(f"App: {config.display_name} ({ios.bundle_id})")
|
|
501
|
+
click.echo(f"Python: {ios.python_version or '(unset)'}")
|
|
502
|
+
click.echo(f"Lock: {_lock_state(project_root, pyproject)}")
|
|
503
|
+
|
|
504
|
+
click.echo("Build:")
|
|
505
|
+
build_dir = project_root / f"{config.app_slug}-ios" / "build" / "DerivedData"
|
|
506
|
+
sim_label = f"simulator ({default_simulator_arch()})"
|
|
507
|
+
for label, target in ((sim_label, "simulator"), ("device", "device")):
|
|
508
|
+
app = product_app_path(build_dir, config.app_slug, target)
|
|
509
|
+
click.echo(f" {label:<20}{_build_state(app)}")
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _lock_state(project_root: Path, pyproject: Path) -> str:
|
|
513
|
+
lockfile = project_root / LOCKFILE_NAME
|
|
514
|
+
if not lockfile.is_file():
|
|
515
|
+
return "missing (run `kivyforge lock`)"
|
|
516
|
+
try:
|
|
517
|
+
lock = load(lockfile)
|
|
518
|
+
except LockError:
|
|
519
|
+
return "unreadable (run `kivyforge lock`)"
|
|
520
|
+
if is_in_sync(lock, pyproject.read_text("utf-8")):
|
|
521
|
+
return "in sync"
|
|
522
|
+
return "out of date (run `kivyforge lock`)"
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _build_state(app: Path) -> str:
|
|
526
|
+
if not app.exists():
|
|
527
|
+
return "not built"
|
|
528
|
+
age = time.time() - app.stat().st_mtime
|
|
529
|
+
return f"last built {_humanize(age)}"
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _humanize(seconds: float) -> str:
|
|
533
|
+
seconds = int(seconds)
|
|
534
|
+
if seconds < 60:
|
|
535
|
+
return "just now"
|
|
536
|
+
if seconds < 3600:
|
|
537
|
+
m = seconds // 60
|
|
538
|
+
return f"{m} minute{'s' if m != 1 else ''} ago"
|
|
539
|
+
if seconds < 86400:
|
|
540
|
+
h = seconds // 3600
|
|
541
|
+
return f"{h} hour{'s' if h != 1 else ''} ago"
|
|
542
|
+
d = seconds // 86400
|
|
543
|
+
return f"{d} day{'s' if d != 1 else ''} ago"
|