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,194 @@
|
|
|
1
|
+
"""Wire ``Frameworks/*.xcframework`` into the generated ``.xcodeproj`` (spec 06).
|
|
2
|
+
|
|
3
|
+
``kivyforge build`` runs ``sync_native_frameworks`` as part of the every-build,
|
|
4
|
+
idempotent project sync. For each ``.xcframework`` staged under ``Frameworks/``
|
|
5
|
+
it ensures a file reference (under the ``Frameworks`` group) and then wires the
|
|
6
|
+
**link** and **embed** phases independently:
|
|
7
|
+
|
|
8
|
+
* ``link`` -> a build file in the Frameworks (Link Binary) phase,
|
|
9
|
+
* ``embed`` -> a build file in the Embed Frameworks (Copy Files) phase, with
|
|
10
|
+
``CodeSignOnCopy``.
|
|
11
|
+
|
|
12
|
+
The link/embed intent comes from the locked ``[[tool.kivyforge.xcframeworks]]``
|
|
13
|
+
entries (keyed by framework name). Wheel-embedded frameworks (SDL3/ANGLE, etc.)
|
|
14
|
+
have no lock entry and default to Embed & Sign (link + embed), preserving prior
|
|
15
|
+
behavior. Reconciliation is full each run, so flipping ``link``/``embed`` in the
|
|
16
|
+
lock removes the now-unwanted build file. Finally, references for frameworks no
|
|
17
|
+
longer present on disk are pruned so a removed dependency does not linger in the
|
|
18
|
+
project as a dangling reference.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from pbxproj.pbxextensions.ProjectFiles import FileOptions, TreeType
|
|
27
|
+
from pbxproj.pbxsections.PBXBuildFile import PBXBuildFile
|
|
28
|
+
|
|
29
|
+
# pbxproj ships no type stubs and builds objects dynamically (PBXGenericObject),
|
|
30
|
+
# so the project graph is treated as ``Any`` throughout this interop module.
|
|
31
|
+
|
|
32
|
+
# PBXCopyFilesBuildPhase destination for frameworks (Embed Frameworks).
|
|
33
|
+
FRAMEWORKS_DST_SUBFOLDER_SPEC = "10"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def sync_native_frameworks(
|
|
37
|
+
project: Any,
|
|
38
|
+
target_name: str,
|
|
39
|
+
*,
|
|
40
|
+
frameworks_dir: Path,
|
|
41
|
+
frameworks_group: Any,
|
|
42
|
+
link_embed: dict[str, tuple[bool, bool]],
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Reconcile ``Frameworks/*.xcframework`` refs + link/embed phases with disk.
|
|
45
|
+
|
|
46
|
+
``link_embed`` maps a framework's on-disk stem (e.g. ``"Sentry"`` for
|
|
47
|
+
``Sentry.xcframework``) to ``(link, embed)``. Frameworks not present in the
|
|
48
|
+
map default to ``(True, True)``.
|
|
49
|
+
"""
|
|
50
|
+
if frameworks_dir.is_dir():
|
|
51
|
+
for xc in sorted(frameworks_dir.glob("*.xcframework")):
|
|
52
|
+
rel = f"Frameworks/{xc.name}"
|
|
53
|
+
ref = _ensure_file_ref(project, rel, target_name, frameworks_group)
|
|
54
|
+
if ref is None:
|
|
55
|
+
continue
|
|
56
|
+
link, embed = link_embed.get(xc.stem, (True, True))
|
|
57
|
+
if link:
|
|
58
|
+
_ensure_link(project, target_name, ref)
|
|
59
|
+
else:
|
|
60
|
+
_remove_build_files(project, ref.get_id(), "PBXFrameworksBuildPhase")
|
|
61
|
+
if embed:
|
|
62
|
+
_ensure_embed(project, target_name, ref)
|
|
63
|
+
else:
|
|
64
|
+
_remove_build_files(project, ref.get_id(), "PBXCopyFilesBuildPhase")
|
|
65
|
+
_prune(project, frameworks_dir)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# -- file reference ------------------------------------------------------------ #
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _ensure_file_ref(
|
|
72
|
+
project: Any, rel: str, target_name: str, group: Any
|
|
73
|
+
) -> Any | None:
|
|
74
|
+
existing = project.get_files_by_path(rel, tree=TreeType.SOURCE_ROOT)
|
|
75
|
+
if existing:
|
|
76
|
+
ref = existing[0]
|
|
77
|
+
_ensure_under_group(project, ref, group)
|
|
78
|
+
return ref
|
|
79
|
+
# First add: let pbxproj create the file reference, place it under the group,
|
|
80
|
+
# and register the framework search path. It also creates default link+embed
|
|
81
|
+
# build files; the caller's reconciliation trims them to the lock's intent.
|
|
82
|
+
project.add_file(
|
|
83
|
+
rel,
|
|
84
|
+
parent=group,
|
|
85
|
+
target_name=target_name,
|
|
86
|
+
tree=TreeType.SOURCE_ROOT,
|
|
87
|
+
file_options=FileOptions(embed_framework=True, code_sign_on_copy=True),
|
|
88
|
+
)
|
|
89
|
+
refs = project.get_files_by_path(rel, tree=TreeType.SOURCE_ROOT)
|
|
90
|
+
if not refs:
|
|
91
|
+
return None
|
|
92
|
+
ref = refs[0]
|
|
93
|
+
_ensure_under_group(project, ref, group)
|
|
94
|
+
return ref
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _ensure_under_group(project: Any, file_ref: Any, group: Any) -> None:
|
|
98
|
+
"""Move an existing file reference under ``group`` (idempotent)."""
|
|
99
|
+
if group.has_child(file_ref):
|
|
100
|
+
return
|
|
101
|
+
for grp in project.objects.get_objects_in_section("PBXGroup"):
|
|
102
|
+
if grp.has_child(file_ref):
|
|
103
|
+
grp.remove_child(file_ref)
|
|
104
|
+
group.add_child(file_ref)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# -- link / embed build files -------------------------------------------------- #
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _ensure_link(project: Any, target_name: str, file_ref: Any) -> None:
|
|
111
|
+
phase = _link_phase(project, target_name)
|
|
112
|
+
if phase is None or _has_build_file(project, phase, file_ref.get_id()):
|
|
113
|
+
return
|
|
114
|
+
build_file: Any = PBXBuildFile.create(file_ref)
|
|
115
|
+
project.objects[build_file.get_id()] = build_file
|
|
116
|
+
phase.add_build_file(build_file)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _ensure_embed(project: Any, target_name: str, file_ref: Any) -> None:
|
|
120
|
+
phase = _embed_phase(project, target_name)
|
|
121
|
+
if _has_build_file(project, phase, file_ref.get_id()):
|
|
122
|
+
return
|
|
123
|
+
build_file: Any = PBXBuildFile.create(file_ref, attributes=["CodeSignOnCopy"])
|
|
124
|
+
project.objects[build_file.get_id()] = build_file
|
|
125
|
+
phase.add_build_file(build_file)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _link_phase(project: Any, target_name: str) -> Any:
|
|
129
|
+
target = project.get_target_by_name(target_name)
|
|
130
|
+
if target is None:
|
|
131
|
+
return None
|
|
132
|
+
phases = target.get_or_create_build_phase("PBXFrameworksBuildPhase")
|
|
133
|
+
return phases[0] if phases else None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _embed_phase(project: Any, target_name: str) -> Any:
|
|
137
|
+
target = project.get_target_by_name(target_name)
|
|
138
|
+
if target is None:
|
|
139
|
+
raise RuntimeError(f"target {target_name!r} not found in Xcode project")
|
|
140
|
+
phases = target.get_or_create_build_phase(
|
|
141
|
+
"PBXCopyFilesBuildPhase",
|
|
142
|
+
search_parameters={"dstSubfolderSpec": FRAMEWORKS_DST_SUBFOLDER_SPEC},
|
|
143
|
+
create_parameters=(
|
|
144
|
+
"Embed Frameworks",
|
|
145
|
+
[],
|
|
146
|
+
"",
|
|
147
|
+
FRAMEWORKS_DST_SUBFOLDER_SPEC,
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
return phases[0]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _has_build_file(project: Any, phase: Any, ref_id: str) -> bool:
|
|
154
|
+
for build_file_id in phase["files"]:
|
|
155
|
+
build_file = project.objects[build_file_id]
|
|
156
|
+
if _file_ref(build_file) == ref_id:
|
|
157
|
+
return True
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _remove_build_files(project: Any, ref_id: str, section: str) -> None:
|
|
162
|
+
for phase in project.objects.get_objects_in_section(section):
|
|
163
|
+
for build_file_id in list(phase["files"]):
|
|
164
|
+
build_file = project.objects[build_file_id]
|
|
165
|
+
if _file_ref(build_file) == ref_id:
|
|
166
|
+
phase.remove_build_file(build_file)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _file_ref(build_file: Any) -> str | None:
|
|
170
|
+
return build_file["fileRef"] if "fileRef" in build_file else None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# -- pruning ------------------------------------------------------------------- #
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _prune(project: Any, frameworks_dir: Path) -> None:
|
|
177
|
+
"""Remove references for ``Frameworks/*.xcframework`` no longer on disk."""
|
|
178
|
+
for ref in list(project.objects.get_objects_in_section("PBXFileReference")):
|
|
179
|
+
path = ref["path"] if "path" in ref else None
|
|
180
|
+
if (
|
|
181
|
+
not path
|
|
182
|
+
or not path.startswith("Frameworks/")
|
|
183
|
+
or not path.endswith(".xcframework")
|
|
184
|
+
):
|
|
185
|
+
continue
|
|
186
|
+
name = path.split("/", 1)[1]
|
|
187
|
+
if not (frameworks_dir / name).is_dir():
|
|
188
|
+
ref_id = ref.get_id()
|
|
189
|
+
# Drop the link/embed build files first so remove_file_by_id sees no
|
|
190
|
+
# remaining references and actually deletes the file reference (it
|
|
191
|
+
# otherwise short-circuits while any build file still points at it).
|
|
192
|
+
_remove_build_files(project, ref_id, "PBXFrameworksBuildPhase")
|
|
193
|
+
_remove_build_files(project, ref_id, "PBXCopyFilesBuildPhase")
|
|
194
|
+
project.remove_file_by_id(ref_id)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""xcodebuild / simctl / devicectl command construction and execution (spec 05).
|
|
2
|
+
|
|
3
|
+
The command builders are pure functions returning ``argv`` lists (and the
|
|
4
|
+
``ExportOptions.plist`` dict), so they unit-test hermetically. Actual process
|
|
5
|
+
execution funnels through a single injectable ``run`` helper.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .commands import (
|
|
11
|
+
SigningError,
|
|
12
|
+
XcodeBuild,
|
|
13
|
+
archive_command,
|
|
14
|
+
build_command,
|
|
15
|
+
default_simulator_arch,
|
|
16
|
+
export_command,
|
|
17
|
+
export_options_plist,
|
|
18
|
+
preflight_signing,
|
|
19
|
+
product_app_path,
|
|
20
|
+
resolve_team_id,
|
|
21
|
+
sdk_for,
|
|
22
|
+
)
|
|
23
|
+
from .runner import (
|
|
24
|
+
CommandError,
|
|
25
|
+
Device,
|
|
26
|
+
SimulatorDevice,
|
|
27
|
+
devicectl_install,
|
|
28
|
+
devicectl_launch,
|
|
29
|
+
devicectl_list,
|
|
30
|
+
devicectl_list_json,
|
|
31
|
+
open_command,
|
|
32
|
+
parse_devicectl_devices,
|
|
33
|
+
parse_simctl_devices,
|
|
34
|
+
pick_device,
|
|
35
|
+
pick_simulator,
|
|
36
|
+
resolve_device_destination,
|
|
37
|
+
resolve_simulator_destination,
|
|
38
|
+
run_command,
|
|
39
|
+
simctl_boot,
|
|
40
|
+
simctl_install,
|
|
41
|
+
simctl_launch,
|
|
42
|
+
simctl_list,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"XcodeBuild",
|
|
47
|
+
"SigningError",
|
|
48
|
+
"build_command",
|
|
49
|
+
"default_simulator_arch",
|
|
50
|
+
"archive_command",
|
|
51
|
+
"export_command",
|
|
52
|
+
"export_options_plist",
|
|
53
|
+
"preflight_signing",
|
|
54
|
+
"product_app_path",
|
|
55
|
+
"resolve_team_id",
|
|
56
|
+
"sdk_for",
|
|
57
|
+
"run_command",
|
|
58
|
+
"open_command",
|
|
59
|
+
"CommandError",
|
|
60
|
+
"SimulatorDevice",
|
|
61
|
+
"parse_simctl_devices",
|
|
62
|
+
"pick_simulator",
|
|
63
|
+
"resolve_simulator_destination",
|
|
64
|
+
"simctl_boot",
|
|
65
|
+
"simctl_install",
|
|
66
|
+
"simctl_launch",
|
|
67
|
+
"simctl_list",
|
|
68
|
+
"Device",
|
|
69
|
+
"devicectl_install",
|
|
70
|
+
"devicectl_launch",
|
|
71
|
+
"devicectl_list",
|
|
72
|
+
"devicectl_list_json",
|
|
73
|
+
"parse_devicectl_devices",
|
|
74
|
+
"pick_device",
|
|
75
|
+
"resolve_device_destination",
|
|
76
|
+
]
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""xcodebuild command + ExportOptions.plist construction (spec 05 step 7).
|
|
2
|
+
|
|
3
|
+
Pure builders: no subprocess, no filesystem side effects beyond returning the
|
|
4
|
+
argv/plist the caller will execute or write. This keeps the signing pre-flight,
|
|
5
|
+
SDK selection, archive/export pipeline, and export-options mapping fully
|
|
6
|
+
unit-testable on any host.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from kivyforge.config.model import Config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def default_simulator_arch() -> str:
|
|
21
|
+
"""The iOS-simulator CPU arch matching the host Mac.
|
|
22
|
+
|
|
23
|
+
Apple-Silicon Macs run the simulator as ``arm64``; Intel Macs as ``x86_64``.
|
|
24
|
+
This is the default when ``--arch`` is not given, so a bare or targeted
|
|
25
|
+
simulator build collects (and ``xcodebuild`` builds) the slice the developer's
|
|
26
|
+
own machine can actually run — collecting only ``arm64`` would break the very
|
|
27
|
+
first simulator build on an Intel host even though the lock pinned ``x86_64``.
|
|
28
|
+
``platform.machine()`` reports ``x86_64`` under Rosetta too, which is correct:
|
|
29
|
+
an x86_64 Python process drives an x86_64 simulator.
|
|
30
|
+
"""
|
|
31
|
+
return "x86_64" if platform.machine() == "x86_64" else "arm64"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# build-target -> xcodebuild SDK.
|
|
35
|
+
SDK = {
|
|
36
|
+
"simulator": "iphonesimulator",
|
|
37
|
+
"device": "iphoneos",
|
|
38
|
+
"release": "iphoneos",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
TEAM_ID_ENV = "KIVYFORGE_TEAM_ID"
|
|
42
|
+
SIGNING_IDENTITY_ENV = "KIVYFORGE_SIGNING_IDENTITY"
|
|
43
|
+
|
|
44
|
+
# --export-method -> ExportOptions.plist `method` value.
|
|
45
|
+
EXPORT_METHOD = {
|
|
46
|
+
"app-store": "app-store",
|
|
47
|
+
"ad-hoc": "ad-hoc",
|
|
48
|
+
"development": "development",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SigningError(Exception):
|
|
53
|
+
"""Code signing is required but no team_id could be resolved."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def sdk_for(target: str) -> str:
|
|
57
|
+
try:
|
|
58
|
+
return SDK[target]
|
|
59
|
+
except KeyError:
|
|
60
|
+
raise ValueError(f"unknown build target {target!r}") from None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def configuration_for(target: str) -> str:
|
|
64
|
+
return "Release" if target == "release" else "Debug"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class XcodeBuild:
|
|
69
|
+
"""Locates the generated project for a config under a project root."""
|
|
70
|
+
|
|
71
|
+
project_root: Path
|
|
72
|
+
scheme: str
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_config(cls, config: Config, project_root: str | Path) -> XcodeBuild:
|
|
76
|
+
return cls(project_root=Path(project_root), scheme=config.app_slug)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def staging(self) -> Path:
|
|
80
|
+
return self.project_root / f"{self.scheme}-ios"
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def project_path(self) -> Path:
|
|
84
|
+
return self.staging / f"{self.scheme}.xcodeproj"
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def build_dir(self) -> Path:
|
|
88
|
+
return self.staging / "build"
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def archive_path(self) -> Path:
|
|
92
|
+
return self.build_dir / f"{self.scheme}.xcarchive"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def ipa_path(self) -> Path:
|
|
96
|
+
return self.build_dir / f"{self.scheme}.ipa"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def resolve_team_id(
|
|
100
|
+
config: Config,
|
|
101
|
+
*,
|
|
102
|
+
team_id_flag: str | None = None,
|
|
103
|
+
env: Mapping[str, str] | None = None,
|
|
104
|
+
) -> str | None:
|
|
105
|
+
"""Resolve the effective team_id (flag → env → pyproject), or None."""
|
|
106
|
+
effective_env: Mapping[str, str] = os.environ if env is None else env
|
|
107
|
+
candidates = (
|
|
108
|
+
team_id_flag,
|
|
109
|
+
effective_env.get(TEAM_ID_ENV),
|
|
110
|
+
config.ios_required.signing.team_id or None,
|
|
111
|
+
)
|
|
112
|
+
for value in candidates:
|
|
113
|
+
if value:
|
|
114
|
+
return value
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def resolve_signing_identity(
|
|
119
|
+
config: Config,
|
|
120
|
+
*,
|
|
121
|
+
identity_flag: str | None = None,
|
|
122
|
+
env: Mapping[str, str] | None = None,
|
|
123
|
+
) -> str | None:
|
|
124
|
+
effective_env: Mapping[str, str] = os.environ if env is None else env
|
|
125
|
+
for value in (
|
|
126
|
+
identity_flag,
|
|
127
|
+
effective_env.get(SIGNING_IDENTITY_ENV),
|
|
128
|
+
config.ios_required.signing.identity,
|
|
129
|
+
):
|
|
130
|
+
if value:
|
|
131
|
+
return value
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def preflight_signing(
|
|
136
|
+
config: Config,
|
|
137
|
+
target: str,
|
|
138
|
+
*,
|
|
139
|
+
team_id_flag: str | None = None,
|
|
140
|
+
env: dict | None = None,
|
|
141
|
+
) -> str | None:
|
|
142
|
+
"""Fail fast when --device/--release lack a team_id (spec 05 step 7).
|
|
143
|
+
|
|
144
|
+
Returns the resolved team_id (None for --simulator, which does not sign).
|
|
145
|
+
"""
|
|
146
|
+
if target == "simulator":
|
|
147
|
+
return None
|
|
148
|
+
team_id = resolve_team_id(config, team_id_flag=team_id_flag, env=env)
|
|
149
|
+
if not team_id:
|
|
150
|
+
raise SigningError(
|
|
151
|
+
"code signing required for --device/--release, but no team_id is set.\n"
|
|
152
|
+
" Set it one of these ways:\n"
|
|
153
|
+
' - [tool.kivy.ios.signing].team_id = "ABCDE12345" in pyproject.toml, '
|
|
154
|
+
"then re-lock\n"
|
|
155
|
+
" - kivyforge build --release --team-id ABCDE12345\n"
|
|
156
|
+
" - export KIVYFORGE_TEAM_ID=ABCDE12345"
|
|
157
|
+
)
|
|
158
|
+
return team_id
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def build_command(
|
|
162
|
+
xb: XcodeBuild,
|
|
163
|
+
target: str,
|
|
164
|
+
*,
|
|
165
|
+
arch: str | None = None,
|
|
166
|
+
derived_data_path: str | Path | None = None,
|
|
167
|
+
signing_identity: str | None = None,
|
|
168
|
+
allow_provisioning_updates: bool = False,
|
|
169
|
+
) -> list[str]:
|
|
170
|
+
"""`xcodebuild build` argv for --simulator/--device (Debug).
|
|
171
|
+
|
|
172
|
+
``derived_data_path`` pins where the ``.app`` is written so ``kivyforge run``
|
|
173
|
+
can locate the product deterministically. ``signing_identity`` overrides the
|
|
174
|
+
project's baked ``CODE_SIGN_IDENTITY`` for device builds (simulator builds are
|
|
175
|
+
unsigned, so it is ignored there). ``allow_provisioning_updates`` passes
|
|
176
|
+
``-allowProvisioningUpdates`` — required for automatic signing to actually
|
|
177
|
+
register the App ID / fetch or create a matching profile when driven from
|
|
178
|
+
the command line (Xcode's IDE does this silently; bare ``xcodebuild`` does
|
|
179
|
+
not unless asked — see "Resolving common notarization issues"-adjacent
|
|
180
|
+
Apple guidance on ``xcodebuild`` + automatic signing).
|
|
181
|
+
"""
|
|
182
|
+
cmd = [
|
|
183
|
+
"xcodebuild",
|
|
184
|
+
"-project",
|
|
185
|
+
str(xb.project_path),
|
|
186
|
+
"-scheme",
|
|
187
|
+
xb.scheme,
|
|
188
|
+
"-configuration",
|
|
189
|
+
configuration_for(target),
|
|
190
|
+
"-sdk",
|
|
191
|
+
sdk_for(target),
|
|
192
|
+
]
|
|
193
|
+
if derived_data_path is not None:
|
|
194
|
+
cmd += ["-derivedDataPath", str(derived_data_path)]
|
|
195
|
+
# python.org's install_stdlib uses lib-$ARCHS; a universal simulator build
|
|
196
|
+
# (arm64 x86_64) produces an invalid path. Pin a single arch for CLI builds.
|
|
197
|
+
if target == "simulator":
|
|
198
|
+
cmd += [f"ARCHS={arch or default_simulator_arch()}", "ONLY_ACTIVE_ARCH=NO"]
|
|
199
|
+
else:
|
|
200
|
+
if signing_identity:
|
|
201
|
+
cmd.append(f"CODE_SIGN_IDENTITY={signing_identity}")
|
|
202
|
+
if allow_provisioning_updates:
|
|
203
|
+
cmd.append("-allowProvisioningUpdates")
|
|
204
|
+
cmd.append("build")
|
|
205
|
+
return cmd
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def product_app_path(derived_data_path: str | Path, scheme: str, target: str) -> Path:
|
|
209
|
+
"""Path to the built ``<scheme>.app`` under a pinned DerivedData dir."""
|
|
210
|
+
sdk = sdk_for(target)
|
|
211
|
+
config = configuration_for(target)
|
|
212
|
+
return (
|
|
213
|
+
Path(derived_data_path)
|
|
214
|
+
/ "Build"
|
|
215
|
+
/ "Products"
|
|
216
|
+
/ f"{config}-{sdk}"
|
|
217
|
+
/ f"{scheme}.app"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def archive_command(
|
|
222
|
+
xb: XcodeBuild,
|
|
223
|
+
*,
|
|
224
|
+
signing_identity: str | None = None,
|
|
225
|
+
allow_provisioning_updates: bool = False,
|
|
226
|
+
) -> list[str]:
|
|
227
|
+
"""`xcodebuild archive` argv for --release (step 7.1).
|
|
228
|
+
|
|
229
|
+
``signing_identity`` overrides the project's baked ``CODE_SIGN_IDENTITY`` for
|
|
230
|
+
the archive's signing step — only pass this for an *explicit* user override;
|
|
231
|
+
forcing the pyproject default (typically "Apple Development", meant for
|
|
232
|
+
``--device`` debug builds) onto a distribution archive requests the wrong
|
|
233
|
+
profile type and fails. ``allow_provisioning_updates``: see ``build_command``.
|
|
234
|
+
"""
|
|
235
|
+
cmd = [
|
|
236
|
+
"xcodebuild",
|
|
237
|
+
"archive",
|
|
238
|
+
"-project",
|
|
239
|
+
str(xb.project_path),
|
|
240
|
+
"-scheme",
|
|
241
|
+
xb.scheme,
|
|
242
|
+
"-configuration",
|
|
243
|
+
"Release",
|
|
244
|
+
"-sdk",
|
|
245
|
+
"iphoneos",
|
|
246
|
+
"-archivePath",
|
|
247
|
+
str(xb.archive_path),
|
|
248
|
+
]
|
|
249
|
+
if signing_identity:
|
|
250
|
+
cmd.append(f"CODE_SIGN_IDENTITY={signing_identity}")
|
|
251
|
+
if allow_provisioning_updates:
|
|
252
|
+
cmd.append("-allowProvisioningUpdates")
|
|
253
|
+
return cmd
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def export_command(
|
|
257
|
+
xb: XcodeBuild, options_plist: Path, *, allow_provisioning_updates: bool = False
|
|
258
|
+
) -> list[str]:
|
|
259
|
+
"""`xcodebuild -exportArchive` argv for --release (step 7.2)."""
|
|
260
|
+
cmd = [
|
|
261
|
+
"xcodebuild",
|
|
262
|
+
"-exportArchive",
|
|
263
|
+
"-archivePath",
|
|
264
|
+
str(xb.archive_path),
|
|
265
|
+
"-exportPath",
|
|
266
|
+
str(xb.build_dir),
|
|
267
|
+
"-exportOptionsPlist",
|
|
268
|
+
str(options_plist),
|
|
269
|
+
]
|
|
270
|
+
if allow_provisioning_updates:
|
|
271
|
+
cmd.append("-allowProvisioningUpdates")
|
|
272
|
+
return cmd
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def export_options_plist(
|
|
276
|
+
*,
|
|
277
|
+
method: str,
|
|
278
|
+
team_id: str,
|
|
279
|
+
upload_symbols: bool = True,
|
|
280
|
+
signing_identity: str | None = None,
|
|
281
|
+
) -> dict:
|
|
282
|
+
"""Map --export-method + signing to ExportOptions.plist keys (spec 05).
|
|
283
|
+
|
|
284
|
+
``signing_identity`` pins the certificate used to re-sign during export
|
|
285
|
+
(``-exportArchive``); omitted, Xcode selects one for the export method.
|
|
286
|
+
"""
|
|
287
|
+
if method not in EXPORT_METHOD:
|
|
288
|
+
raise ValueError(f"unknown export method {method!r}")
|
|
289
|
+
options: dict = {
|
|
290
|
+
"method": EXPORT_METHOD[method],
|
|
291
|
+
"teamID": team_id,
|
|
292
|
+
"uploadSymbols": upload_symbols,
|
|
293
|
+
}
|
|
294
|
+
if signing_identity:
|
|
295
|
+
options["signingCertificate"] = signing_identity
|
|
296
|
+
return options
|