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,112 @@
|
|
|
1
|
+
"""macOS lock stack (``pylock.macos.toml``).
|
|
2
|
+
|
|
3
|
+
A thin macOS *profile* (:class:`MacosProfile`) over the generic wheel+runtime
|
|
4
|
+
lock engine (``kivyforge.lock.wheelruntime``). This module is the stable macOS
|
|
5
|
+
public surface: it binds the profile into the core and re-exports the engine's
|
|
6
|
+
types under macOS-friendly names so callers (the CLI, tests) never reach into the
|
|
7
|
+
generic package directly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from kivyforge.config.model import Config
|
|
17
|
+
from kivyforge.lock.wheelruntime import (
|
|
18
|
+
PythonRuntime as MacosPythonRuntime,
|
|
19
|
+
)
|
|
20
|
+
from kivyforge.lock.wheelruntime import (
|
|
21
|
+
ResolvedPackage,
|
|
22
|
+
ResolvedWheel,
|
|
23
|
+
RuntimeArtifact,
|
|
24
|
+
RuntimeProvider,
|
|
25
|
+
RuntimeProviderError,
|
|
26
|
+
Variant,
|
|
27
|
+
WheelResolver,
|
|
28
|
+
WheelResolverError,
|
|
29
|
+
WheelRuntimeBuildError,
|
|
30
|
+
WheelRuntimeLock,
|
|
31
|
+
build_wheel_runtime_lock,
|
|
32
|
+
diff_summary,
|
|
33
|
+
get_wheel_resolver,
|
|
34
|
+
semantic_equal,
|
|
35
|
+
)
|
|
36
|
+
from kivyforge.lock.wheelruntime import serialize as _ser
|
|
37
|
+
|
|
38
|
+
from .profile import MacosProfile, macos_platform_tag, wheel_arch
|
|
39
|
+
from .runtime import PythonBuildStandaloneProvider
|
|
40
|
+
|
|
41
|
+
# macOS-friendly aliases for the generic engine types.
|
|
42
|
+
MacosLockfile = WheelRuntimeLock
|
|
43
|
+
MacosBuildError = WheelRuntimeBuildError
|
|
44
|
+
MacosResolverError = WheelResolverError
|
|
45
|
+
|
|
46
|
+
_PROFILE = MacosProfile()
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"MacosBuildError",
|
|
50
|
+
"MacosLockfile",
|
|
51
|
+
"MacosProfile",
|
|
52
|
+
"MacosPythonRuntime",
|
|
53
|
+
"MacosResolverError",
|
|
54
|
+
"PythonBuildStandaloneProvider",
|
|
55
|
+
"ResolvedPackage",
|
|
56
|
+
"ResolvedWheel",
|
|
57
|
+
"RuntimeArtifact",
|
|
58
|
+
"RuntimeProvider",
|
|
59
|
+
"RuntimeProviderError",
|
|
60
|
+
"Variant",
|
|
61
|
+
"WheelResolver",
|
|
62
|
+
"build_macos_lockfile",
|
|
63
|
+
"diff_summary",
|
|
64
|
+
"dumps",
|
|
65
|
+
"get_macos_resolver",
|
|
66
|
+
"load",
|
|
67
|
+
"loads",
|
|
68
|
+
"macos_platform_tag",
|
|
69
|
+
"semantic_equal",
|
|
70
|
+
"wheel_arch",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_macos_lockfile(
|
|
75
|
+
config: Config,
|
|
76
|
+
pyproject_text: str,
|
|
77
|
+
*,
|
|
78
|
+
project_root: Path | None = None,
|
|
79
|
+
resolver: WheelResolver | None = None,
|
|
80
|
+
runtime_provider: RuntimeProvider | None = None,
|
|
81
|
+
offline: bool = False,
|
|
82
|
+
now: datetime | None = None,
|
|
83
|
+
on_warning: Callable[[str], None] | None = None,
|
|
84
|
+
) -> MacosLockfile:
|
|
85
|
+
return build_wheel_runtime_lock(
|
|
86
|
+
_PROFILE,
|
|
87
|
+
config,
|
|
88
|
+
pyproject_text,
|
|
89
|
+
project_root=project_root,
|
|
90
|
+
resolver=resolver,
|
|
91
|
+
runtime_provider=runtime_provider,
|
|
92
|
+
native_binaries=config.macos.binaries if config.macos else (),
|
|
93
|
+
offline=offline,
|
|
94
|
+
now=now,
|
|
95
|
+
on_warning=on_warning,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def dumps(lock: MacosLockfile) -> str:
|
|
100
|
+
return _ser.dumps(lock)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def loads(text: str) -> MacosLockfile:
|
|
104
|
+
return _ser.loads(text, platform="macos")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def load(path: str | Path) -> MacosLockfile:
|
|
108
|
+
return _ser.load(path, platform="macos")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_macos_resolver(backend: str = "pip", **kwargs) -> WheelResolver:
|
|
112
|
+
return get_wheel_resolver(backend, **kwargs)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""macOS lock profile — the platform-specific inputs to the wheel+runtime core.
|
|
2
|
+
|
|
3
|
+
macOS builds target ``arm64`` only, but wheel *tags* are a wider set: a
|
|
4
|
+
``universal2`` wheel covers arm64 and is accepted, and ``x86_64`` tags are still
|
|
5
|
+
parsed (they simply never match). Keeping ``VALID_WHEEL_ARCHS`` wider than the
|
|
6
|
+
build-target set is deliberate — narrowing it would reject most macOS wheels on
|
|
7
|
+
PyPI. The bundled runtime comes from python-build-standalone (darwin triples).
|
|
8
|
+
Everything else — resolve/serialize/build orchestration — is the shared core.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
from kivyforge.config.errors import ConfigError
|
|
16
|
+
from kivyforge.config.model import Config
|
|
17
|
+
from kivyforge.lock.wheelruntime.profile import PlatformLockProfile
|
|
18
|
+
from kivyforge.lock.wheelruntime.resolver import Variant
|
|
19
|
+
from kivyforge.lock.wheelruntime.runtime import RuntimeProvider
|
|
20
|
+
|
|
21
|
+
from .runtime import PythonBuildStandaloneProvider
|
|
22
|
+
|
|
23
|
+
# Default macOS deployment floor for the pip ``--platform`` request when the
|
|
24
|
+
# project sets no ``minimum_system_version``. pip matches wheels tagged at or
|
|
25
|
+
# below this floor (plus universal2), maximizing compatible wheels.
|
|
26
|
+
DEFAULT_MACOS_FLOOR = "11.0"
|
|
27
|
+
|
|
28
|
+
VALID_WHEEL_ARCHS = frozenset({"arm64", "x86_64", "universal2"})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def macos_platform_tag(floor: str, arch: str) -> str:
|
|
32
|
+
"""The pip ``--platform`` tag for a macOS deployment floor + arch."""
|
|
33
|
+
return f"macosx_{floor.replace('.', '_')}_{arch}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def wheel_arch(platform_tag: str) -> str | None:
|
|
37
|
+
"""The architecture a macOS wheel platform tag targets, or ``None``.
|
|
38
|
+
|
|
39
|
+
``macosx_11_0_arm64`` -> ``arm64``; ``macosx_11_0_x86_64`` -> ``x86_64``;
|
|
40
|
+
``..._universal2`` -> ``universal2``. The arch is what follows the
|
|
41
|
+
``macosx_<major>_<minor>_`` prefix (``x86_64`` itself has an underscore, so a
|
|
42
|
+
naive rsplit is wrong).
|
|
43
|
+
"""
|
|
44
|
+
match = re.fullmatch(r"macosx_\d+_\d+_(.+)", platform_tag)
|
|
45
|
+
if match is None:
|
|
46
|
+
return None
|
|
47
|
+
arch = match.group(1)
|
|
48
|
+
return arch if arch in VALID_WHEEL_ARCHS else None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MacosProfile(PlatformLockProfile):
|
|
52
|
+
platform = "macos"
|
|
53
|
+
|
|
54
|
+
def overlay(self, config: Config):
|
|
55
|
+
return config.macos
|
|
56
|
+
|
|
57
|
+
def missing_overlay_error(self) -> str:
|
|
58
|
+
return "pyproject.toml has no [tool.kivy.macos] table; nothing to lock."
|
|
59
|
+
|
|
60
|
+
def python_version(self, config: Config) -> str:
|
|
61
|
+
# [tool.kivy.macos.python].version is required (the loader raises a
|
|
62
|
+
# ConfigError when it is missing), so it is always set here. Never
|
|
63
|
+
# silently substitute a hidden default — that would pin an unexpected
|
|
64
|
+
# (and possibly unreleased) Python instead of surfacing the misconfig.
|
|
65
|
+
version = config.macos_required.python_version
|
|
66
|
+
if not version:
|
|
67
|
+
raise ConfigError(
|
|
68
|
+
"missing required [tool.kivy.macos.python].version",
|
|
69
|
+
key_path="tool.kivy.macos.python.version",
|
|
70
|
+
)
|
|
71
|
+
return version
|
|
72
|
+
|
|
73
|
+
def _floor(self, config: Config) -> str:
|
|
74
|
+
return config.macos_required.minimum_system_version or DEFAULT_MACOS_FLOOR
|
|
75
|
+
|
|
76
|
+
def variants(self, config: Config) -> tuple[Variant, ...]:
|
|
77
|
+
floor = self._floor(config)
|
|
78
|
+
return tuple(
|
|
79
|
+
Variant(arch=arch, platform_tag=macos_platform_tag(floor, arch))
|
|
80
|
+
for arch in config.macos_required.archs
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def wheel_covers(self, platform_tag: str, archs: tuple[str, ...]) -> set[str]:
|
|
84
|
+
arch = wheel_arch(platform_tag)
|
|
85
|
+
if arch == "universal2":
|
|
86
|
+
return set(archs)
|
|
87
|
+
if arch in archs:
|
|
88
|
+
return {arch}
|
|
89
|
+
return set()
|
|
90
|
+
|
|
91
|
+
def runtime_provider(self, config: Config) -> RuntimeProvider:
|
|
92
|
+
return PythonBuildStandaloneProvider()
|
|
93
|
+
|
|
94
|
+
def declared_floor(self, config: Config) -> str | None:
|
|
95
|
+
return config.macos_required.minimum_system_version
|
|
96
|
+
|
|
97
|
+
def coverage_error(self, name: str, missing: list[str]) -> str:
|
|
98
|
+
return (
|
|
99
|
+
f"{name} is missing macOS wheel(s) for arch(es): {', '.join(missing)}.\n"
|
|
100
|
+
f" A compiled package must publish an arm64 or universal2 wheel to "
|
|
101
|
+
f"be locked reproducibly.\n"
|
|
102
|
+
f" Intel-only wheels cannot be used: kivyforge builds arm64-only "
|
|
103
|
+
f"macOS apps."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def floor_error(self, declared: str, runtime_floor: str, version: str) -> str:
|
|
107
|
+
return (
|
|
108
|
+
f"minimum_system_version {declared} is below the macOS {runtime_floor} "
|
|
109
|
+
f"floor required by the Python {version} runtime.\n"
|
|
110
|
+
f" Raise [tool.kivy.macos].minimum_system_version to at least "
|
|
111
|
+
f"{runtime_floor}."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def wheel_scope_hint(self) -> str:
|
|
115
|
+
return (
|
|
116
|
+
"Vendored wheels must live under the project directory, a sibling "
|
|
117
|
+
"directory, or the enclosing repository (e.g. a wheels/ directory "
|
|
118
|
+
"beside pyproject.toml)."
|
|
119
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""macOS runtime provider — python-build-standalone with darwin triples.
|
|
2
|
+
|
|
3
|
+
A one-line specialization of the generic :class:`PbsProvider`: it fixes the
|
|
4
|
+
arch→target-triple map to macOS's, and otherwise inherits the shared PBS
|
|
5
|
+
resolution + injectable metadata fetcher.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from kivyforge.lock.wheelruntime.runtime import (
|
|
11
|
+
MetadataFetcher,
|
|
12
|
+
PbsProvider,
|
|
13
|
+
ReleaseAsset,
|
|
14
|
+
RuntimeProvider,
|
|
15
|
+
RuntimeProviderError,
|
|
16
|
+
pbs_asset_glob,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"DARWIN_TRIPLES",
|
|
21
|
+
"PythonBuildStandaloneProvider",
|
|
22
|
+
"ReleaseAsset",
|
|
23
|
+
"RuntimeProvider",
|
|
24
|
+
"RuntimeProviderError",
|
|
25
|
+
"get_runtime_provider",
|
|
26
|
+
"pbs_asset_name",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# kivyforge arch name -> PBS/LLVM darwin target triple.
|
|
30
|
+
DARWIN_TRIPLES = {
|
|
31
|
+
"arm64": "aarch64-apple-darwin",
|
|
32
|
+
"x86_64": "x86_64-apple-darwin",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# macOS-facing alias for the generic PBS asset glob helper.
|
|
36
|
+
pbs_asset_name = pbs_asset_glob
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PythonBuildStandaloneProvider(PbsProvider):
|
|
40
|
+
"""PBS provider fixed to macOS darwin triples."""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
metadata_fetcher: MetadataFetcher | None = None,
|
|
45
|
+
*,
|
|
46
|
+
floor: str | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
super().__init__(DARWIN_TRIPLES, metadata_fetcher, floor=floor)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_runtime_provider(
|
|
52
|
+
name: str = "python-build-standalone", **kwargs
|
|
53
|
+
) -> RuntimeProvider:
|
|
54
|
+
if name in ("python-build-standalone", "pbs", "python_build_standalone"):
|
|
55
|
+
return PythonBuildStandaloneProvider(**kwargs)
|
|
56
|
+
raise RuntimeProviderError(
|
|
57
|
+
f"unknown runtime provider {name!r} (expected python-build-standalone)"
|
|
58
|
+
)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Thin wrappers over the macOS binary tools the bundler needs.
|
|
2
|
+
|
|
3
|
+
``lipo -archs`` (read a Mach-O's architectures), ``codesign`` (ad-hoc signing —
|
|
4
|
+
the mandatory Apple-Silicon floor), and Mach-O detection (so we only ever sign
|
|
5
|
+
actual binaries, not text/data files). Kept isolated + injectable-friendly so
|
|
6
|
+
the higher-level bundler logic stays testable and these are the only lines that
|
|
7
|
+
shell out to Apple tools.
|
|
8
|
+
|
|
9
|
+
There is no ``lipo -create`` wrapper: macOS builds are arm64-only, so nothing
|
|
10
|
+
is ever merged into a universal binary.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import struct
|
|
16
|
+
import subprocess
|
|
17
|
+
import time
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from . import AppBundleError
|
|
21
|
+
|
|
22
|
+
# codesign, when signing with a real (keychain-backed) identity, intermittently
|
|
23
|
+
# fails with errSecInternalComponent under rapid successive invocations — a
|
|
24
|
+
# long-standing macOS securityd flakiness (not a config problem; ad-hoc signing
|
|
25
|
+
# uses no keychain and never hits it). A short retry with backoff reliably
|
|
26
|
+
# clears it; see e.g. https://developer.apple.com/forums/thread/700333.
|
|
27
|
+
_CODESIGN_TRANSIENT_ERRORS = ("errSecInternalComponent",)
|
|
28
|
+
_CODESIGN_MAX_ATTEMPTS = 5
|
|
29
|
+
_CODESIGN_RETRY_DELAY_S = 1.0
|
|
30
|
+
|
|
31
|
+
# Mach-O magic numbers (little/big-endian, 32/64-bit) and the fat/universal
|
|
32
|
+
# magics. A file that starts with any of these is a Mach-O we may lipo/sign.
|
|
33
|
+
_MACHO_MAGICS = frozenset(
|
|
34
|
+
{
|
|
35
|
+
0xFEEDFACE, # MH_MAGIC (32-bit)
|
|
36
|
+
0xCEFAEDFE, # MH_CIGAM
|
|
37
|
+
0xFEEDFACF, # MH_MAGIC_64
|
|
38
|
+
0xCFFAEDFE, # MH_CIGAM_64
|
|
39
|
+
0xCAFEBABE, # FAT_MAGIC (universal)
|
|
40
|
+
0xBEBAFECA, # FAT_CIGAM
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_macho(path: Path) -> bool:
|
|
46
|
+
"""True if *path* is a regular file whose first 4 bytes are a Mach-O magic.
|
|
47
|
+
|
|
48
|
+
Note ``0xCAFEBABE`` is also the Java class-file magic, but the bundler only
|
|
49
|
+
ever walks a CPython tree + installed wheels, where it unambiguously means a
|
|
50
|
+
universal Mach-O.
|
|
51
|
+
"""
|
|
52
|
+
if not path.is_file() or path.is_symlink():
|
|
53
|
+
return False
|
|
54
|
+
try:
|
|
55
|
+
with path.open("rb") as fh:
|
|
56
|
+
head = fh.read(4)
|
|
57
|
+
except OSError:
|
|
58
|
+
return False
|
|
59
|
+
if len(head) < 4:
|
|
60
|
+
return False
|
|
61
|
+
return struct.unpack(">I", head)[0] in _MACHO_MAGICS
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def macho_arches(path: Path) -> tuple[str, ...]:
|
|
65
|
+
"""The architectures present in a Mach-O, via ``lipo -archs`` (empty on error)."""
|
|
66
|
+
proc = _run(["lipo", "-archs", str(path)], check=False)
|
|
67
|
+
if proc.returncode != 0:
|
|
68
|
+
return ()
|
|
69
|
+
return tuple(proc.stdout.split())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def codesign_adhoc(path: Path) -> None:
|
|
73
|
+
"""Ad-hoc sign a single Mach-O / bundle in place (``codesign --sign -``)."""
|
|
74
|
+
_run(["codesign", "--force", "--sign", "-", "--timestamp=none", str(path)])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def codesign_identity(
|
|
78
|
+
path: Path, identity: str, *, entitlements: Path | None = None
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Developer ID sign a Mach-O / bundle with Hardened Runtime + timestamp.
|
|
81
|
+
|
|
82
|
+
``--options runtime`` (Hardened Runtime) and a secure ``--timestamp`` are
|
|
83
|
+
both notarization requirements. *entitlements* only applies to the main
|
|
84
|
+
executable, so callers pass it when sealing the ``.app`` (and the launcher),
|
|
85
|
+
not for nested dylib/so binaries.
|
|
86
|
+
"""
|
|
87
|
+
cmd = [
|
|
88
|
+
"codesign",
|
|
89
|
+
"--force",
|
|
90
|
+
"--sign",
|
|
91
|
+
identity,
|
|
92
|
+
"--options",
|
|
93
|
+
"runtime",
|
|
94
|
+
"--timestamp",
|
|
95
|
+
]
|
|
96
|
+
if entitlements is not None:
|
|
97
|
+
cmd += ["--entitlements", str(entitlements)]
|
|
98
|
+
cmd.append(str(path))
|
|
99
|
+
_run_with_retry(cmd)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def codesign_verify(path: Path) -> bool:
|
|
103
|
+
"""True if *path* has a valid signature (``codesign --verify``)."""
|
|
104
|
+
return _run(["codesign", "--verify", str(path)], check=False).returncode == 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _run_with_retry(cmd: list[str]) -> None:
|
|
108
|
+
"""Run *cmd* (a codesign invocation), retrying transient keychain flakiness.
|
|
109
|
+
|
|
110
|
+
Only retries errors matching ``_CODESIGN_TRANSIENT_ERRORS``; anything else
|
|
111
|
+
(a genuinely missing/expired identity, a bad entitlements plist, ...)
|
|
112
|
+
raises immediately.
|
|
113
|
+
"""
|
|
114
|
+
for attempt in range(1, _CODESIGN_MAX_ATTEMPTS + 1):
|
|
115
|
+
try:
|
|
116
|
+
_run(cmd)
|
|
117
|
+
return
|
|
118
|
+
except AppBundleError as exc:
|
|
119
|
+
transient = any(m in str(exc) for m in _CODESIGN_TRANSIENT_ERRORS)
|
|
120
|
+
if not transient or attempt == _CODESIGN_MAX_ATTEMPTS:
|
|
121
|
+
raise
|
|
122
|
+
time.sleep(_CODESIGN_RETRY_DELAY_S * attempt)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess:
|
|
126
|
+
try:
|
|
127
|
+
proc = subprocess.run(cmd, capture_output=True, text=True)
|
|
128
|
+
except FileNotFoundError as exc:
|
|
129
|
+
raise AppBundleError(
|
|
130
|
+
f"required macOS tool {cmd[0]!r} not found.\n"
|
|
131
|
+
" Install the Xcode command-line tools: xcode-select --install"
|
|
132
|
+
) from exc
|
|
133
|
+
if check and proc.returncode != 0:
|
|
134
|
+
raise AppBundleError(
|
|
135
|
+
f"{cmd[0]} failed ({proc.returncode}): "
|
|
136
|
+
f"{(proc.stderr or proc.stdout).strip()}"
|
|
137
|
+
)
|
|
138
|
+
return proc
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Stage locked native binaries into the ``.app``'s ``Resources/bin`` (macos-spec).
|
|
2
|
+
|
|
3
|
+
The ``[tool.kivy.macos.native.binaries]`` channel: each entry is fetched +
|
|
4
|
+
SHA-256-verified from ``pylock.macos.toml`` (URL or vendored path), then staged
|
|
5
|
+
into ``Contents/Resources/bin`` — a single native file copied in with its exec
|
|
6
|
+
bit set, or a ``.zip`` / ``.tar.gz`` / ``.tgz`` of several extracted with a
|
|
7
|
+
path-traversal guard. The directory is created only when the lock actually pins
|
|
8
|
+
binaries (no empty ``bin/``). The bundle's signing sweep later covers everything
|
|
9
|
+
under it.
|
|
10
|
+
|
|
11
|
+
The staging mechanics live in the shared
|
|
12
|
+
:mod:`kivyforge.artifacts.native_stage_util`; this module is the macOS-facing
|
|
13
|
+
wrapper that translates the neutral :class:`NativeStageError` into
|
|
14
|
+
``AppBundleError``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from kivyforge.artifacts.cache import ArtifactCache
|
|
22
|
+
from kivyforge.artifacts.native_stage_util import NativeStageError, stage_binaries
|
|
23
|
+
|
|
24
|
+
from . import AppBundleError
|
|
25
|
+
from .lock import MacosLockfile
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def stage_native_binaries(
|
|
29
|
+
lock: MacosLockfile,
|
|
30
|
+
resources: Path,
|
|
31
|
+
*,
|
|
32
|
+
project_root: Path,
|
|
33
|
+
cache: ArtifactCache | None = None,
|
|
34
|
+
no_cache: bool = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Stage every pinned native binary for the lock into ``resources/bin``."""
|
|
37
|
+
try:
|
|
38
|
+
stage_binaries(
|
|
39
|
+
lock.native_binaries,
|
|
40
|
+
resources,
|
|
41
|
+
project_root=project_root,
|
|
42
|
+
cache=cache,
|
|
43
|
+
no_cache=no_cache,
|
|
44
|
+
bin_label="Contents/Resources/bin",
|
|
45
|
+
)
|
|
46
|
+
except NativeStageError as exc:
|
|
47
|
+
raise AppBundleError(str(exc)) from exc
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Notarize + staple the signed ``.app`` (macos-spec, Developer ID workstream).
|
|
2
|
+
|
|
3
|
+
The distribution path since macOS 10.15 is **sign + notarize + staple**: zip the
|
|
4
|
+
Developer-ID-signed ``.app`` (``ditto -c -k --keepParent`` preserves the bundle
|
|
5
|
+
metadata Gatekeeper checks), submit it with ``xcrun notarytool submit --wait``,
|
|
6
|
+
and on acceptance ``xcrun stapler staple`` the ticket into the bundle so
|
|
7
|
+
Gatekeeper trusts it offline.
|
|
8
|
+
|
|
9
|
+
Credentials come from a **keychain profile** created once with::
|
|
10
|
+
|
|
11
|
+
xcrun notarytool store-credentials <profile> \
|
|
12
|
+
--apple-id <you@example.com> --team-id <TEAMID> --password <app-specific>
|
|
13
|
+
|
|
14
|
+
so nothing secret ever lives in ``pyproject.toml`` (CI keychains work the same
|
|
15
|
+
way). ``.dmg``-level notarization stays external — kivyforge never builds
|
|
16
|
+
``.dmg`` files (see common packaging scope).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import subprocess
|
|
23
|
+
import tempfile
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
import click
|
|
27
|
+
|
|
28
|
+
from . import AppBundleError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def notarize_and_staple(app: Path, *, profile: str, echo=click.echo) -> None:
|
|
32
|
+
"""Submit *app* to Apple's notary service and staple the ticket.
|
|
33
|
+
|
|
34
|
+
Blocks until the notary service responds (``--wait``; typically a couple of
|
|
35
|
+
minutes). On rejection, fetches the notary log and surfaces the per-file
|
|
36
|
+
issues in the error message.
|
|
37
|
+
"""
|
|
38
|
+
with tempfile.TemporaryDirectory(prefix="kivyforge-notarize-") as tmp:
|
|
39
|
+
archive = Path(tmp) / f"{app.stem}.zip"
|
|
40
|
+
echo(f"Zipping {app.name} for notarization ...")
|
|
41
|
+
_run(["ditto", "-c", "-k", "--keepParent", str(app), str(archive)])
|
|
42
|
+
|
|
43
|
+
echo("Submitting to the Apple notary service (this can take minutes) ...")
|
|
44
|
+
proc = _run(
|
|
45
|
+
[
|
|
46
|
+
"xcrun",
|
|
47
|
+
"notarytool",
|
|
48
|
+
"submit",
|
|
49
|
+
str(archive),
|
|
50
|
+
"--keychain-profile",
|
|
51
|
+
profile,
|
|
52
|
+
"--wait",
|
|
53
|
+
"--output-format",
|
|
54
|
+
"json",
|
|
55
|
+
],
|
|
56
|
+
check=False,
|
|
57
|
+
)
|
|
58
|
+
submission_id, status = _parse_submission(proc.stdout)
|
|
59
|
+
if proc.returncode != 0 and status is None:
|
|
60
|
+
raise AppBundleError(
|
|
61
|
+
f"notarytool submit failed ({proc.returncode}): "
|
|
62
|
+
f"{(proc.stderr or proc.stdout).strip()}\n"
|
|
63
|
+
f" Check the keychain profile: `xcrun notarytool history "
|
|
64
|
+
f"--keychain-profile {profile}`."
|
|
65
|
+
)
|
|
66
|
+
if status != "Accepted":
|
|
67
|
+
raise AppBundleError(_rejection_message(submission_id, status, profile))
|
|
68
|
+
echo(f"Notarization accepted (submission {submission_id}).")
|
|
69
|
+
|
|
70
|
+
echo("Stapling the notarization ticket ...")
|
|
71
|
+
_run(["xcrun", "stapler", "staple", str(app)])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _parse_submission(stdout: str) -> tuple[str | None, str | None]:
|
|
75
|
+
"""(submission id, status) from ``notarytool --output-format json`` output."""
|
|
76
|
+
try:
|
|
77
|
+
data = json.loads(stdout or "{}")
|
|
78
|
+
except json.JSONDecodeError:
|
|
79
|
+
return None, None
|
|
80
|
+
return data.get("id"), data.get("status")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _rejection_message(
|
|
84
|
+
submission_id: str | None, status: str | None, profile: str
|
|
85
|
+
) -> str:
|
|
86
|
+
lines = [f"notarization was not accepted (status: {status or 'unknown'})."]
|
|
87
|
+
if submission_id:
|
|
88
|
+
log = _notary_log(submission_id, profile)
|
|
89
|
+
if log:
|
|
90
|
+
lines.append(f" Notary log issues:\n{log}")
|
|
91
|
+
else:
|
|
92
|
+
lines.append(
|
|
93
|
+
f" Fetch the log: `xcrun notarytool log {submission_id} "
|
|
94
|
+
f"--keychain-profile {profile}`."
|
|
95
|
+
)
|
|
96
|
+
lines.append(
|
|
97
|
+
" Common causes: a binary missed Hardened Runtime / timestamp signing, "
|
|
98
|
+
"or the identity is not a Developer ID Application certificate."
|
|
99
|
+
)
|
|
100
|
+
return "\n".join(lines)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _notary_log(submission_id: str, profile: str) -> str:
|
|
104
|
+
"""The notary log's per-file issues, indented; empty string on any failure."""
|
|
105
|
+
proc = _run(
|
|
106
|
+
[
|
|
107
|
+
"xcrun",
|
|
108
|
+
"notarytool",
|
|
109
|
+
"log",
|
|
110
|
+
submission_id,
|
|
111
|
+
"--keychain-profile",
|
|
112
|
+
profile,
|
|
113
|
+
],
|
|
114
|
+
check=False,
|
|
115
|
+
)
|
|
116
|
+
if proc.returncode != 0:
|
|
117
|
+
return ""
|
|
118
|
+
try:
|
|
119
|
+
issues = json.loads(proc.stdout).get("issues") or []
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
return ""
|
|
122
|
+
lines = [
|
|
123
|
+
f" {issue.get('severity', '?')}: {issue.get('path', '?')}: "
|
|
124
|
+
f"{issue.get('message', '?')}"
|
|
125
|
+
for issue in issues
|
|
126
|
+
]
|
|
127
|
+
return "\n".join(lines)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess:
|
|
131
|
+
try:
|
|
132
|
+
proc = subprocess.run(cmd, capture_output=True, text=True)
|
|
133
|
+
except FileNotFoundError as exc:
|
|
134
|
+
raise AppBundleError(
|
|
135
|
+
f"required macOS tool {cmd[0]!r} not found.\n"
|
|
136
|
+
" Install the Xcode command-line tools: xcode-select --install"
|
|
137
|
+
) from exc
|
|
138
|
+
if check and proc.returncode != 0:
|
|
139
|
+
raise AppBundleError(
|
|
140
|
+
f"{cmd[0]} failed ({proc.returncode}): "
|
|
141
|
+
f"{(proc.stderr or proc.stdout).strip()}"
|
|
142
|
+
)
|
|
143
|
+
return proc
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Build the ``.app`` ``Info.plist`` from the project config (macos-spec).
|
|
2
|
+
|
|
3
|
+
kivyforge owns the identity/versioning keys (mirroring the iOS managed-plist
|
|
4
|
+
discipline); there is no user Info.plist merge on macOS in this phase.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from kivyforge.config.model import Config
|
|
10
|
+
|
|
11
|
+
# LSMinimumSystemVersion when the project sets none. Matches the macOS lock's
|
|
12
|
+
# default pip floor so the plist and the resolved wheels agree.
|
|
13
|
+
DEFAULT_MINIMUM_SYSTEM_VERSION = "11.0"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_info_plist(config: Config, *, executable: str, icon_file: str | None) -> dict:
|
|
17
|
+
macos = config.macos_required
|
|
18
|
+
plist: dict[str, object] = {
|
|
19
|
+
"CFBundleName": config.display_name,
|
|
20
|
+
"CFBundleDisplayName": config.display_name,
|
|
21
|
+
"CFBundleIdentifier": macos.bundle_id,
|
|
22
|
+
"CFBundleExecutable": executable,
|
|
23
|
+
"CFBundlePackageType": "APPL",
|
|
24
|
+
"CFBundleInfoDictionaryVersion": "6.0",
|
|
25
|
+
"CFBundleShortVersionString": config.project.version,
|
|
26
|
+
"CFBundleVersion": str(macos.build),
|
|
27
|
+
"LSMinimumSystemVersion": (
|
|
28
|
+
macos.minimum_system_version or DEFAULT_MINIMUM_SYSTEM_VERSION
|
|
29
|
+
),
|
|
30
|
+
"NSHighResolutionCapable": True,
|
|
31
|
+
# A GUI app (Kivy opens a window); without this Finder still launches it,
|
|
32
|
+
# but it is the correct declaration for a windowed, non-agent app.
|
|
33
|
+
"LSBackgroundOnly": False,
|
|
34
|
+
}
|
|
35
|
+
if icon_file is not None:
|
|
36
|
+
plist["CFBundleIconFile"] = icon_file
|
|
37
|
+
return plist
|