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,235 @@
|
|
|
1
|
+
"""Assemble the Linux AppDir (linux-spec).
|
|
2
|
+
|
|
3
|
+
Orchestrates the stages: pick the assembly arch from the lock (+ ``--arch``),
|
|
4
|
+
stage the runtime + wheels, copy the app sources, render the icons + ``AppRun``
|
|
5
|
+
launcher + ``.desktop`` entry. Produces the layout documented in linux-spec:
|
|
6
|
+
|
|
7
|
+
build/linux/<Name>.AppDir/
|
|
8
|
+
├── AppRun
|
|
9
|
+
├── <app_id>.desktop
|
|
10
|
+
├── <app_id>.png
|
|
11
|
+
└── usr/{app, lib, python, share/icons/hicolor/...}
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import platform
|
|
17
|
+
import shutil
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import click
|
|
22
|
+
|
|
23
|
+
from kivyforge.artifacts.cache import ArtifactCache
|
|
24
|
+
from kivyforge.bundle.pycompile import PycompileError, byte_compile, select_compiler
|
|
25
|
+
from kivyforge.config.model import Config
|
|
26
|
+
|
|
27
|
+
from . import AppDirError
|
|
28
|
+
from .desktop import validate_desktop_file, write_desktop_entry
|
|
29
|
+
from .icons import stage_icons
|
|
30
|
+
from .launcher import build_apprun, entry_point_rel_path
|
|
31
|
+
from .lock import LinuxLockfile
|
|
32
|
+
from .native_stage import stage_native_binaries
|
|
33
|
+
from .runtime_stage import stage_runtime
|
|
34
|
+
from .wheels_stage import stage_wheels
|
|
35
|
+
|
|
36
|
+
_IGNORE = shutil.ignore_patterns("__pycache__", "*.pyc")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def resolve_assembly_arch(locked: tuple[str, ...], arch: str | None) -> str:
|
|
40
|
+
"""The single arch to assemble for an ``--arch`` request.
|
|
41
|
+
|
|
42
|
+
Linux ships one arch per AppImage, so this always resolves to exactly one.
|
|
43
|
+
``None`` -> the lock's arch (there is exactly one this phase). An explicit
|
|
44
|
+
arch not covered by the lock is an error.
|
|
45
|
+
"""
|
|
46
|
+
if not locked:
|
|
47
|
+
raise AppDirError("the lock covers no architectures; re-run `kivyforge lock`.")
|
|
48
|
+
if arch is None:
|
|
49
|
+
return locked[0]
|
|
50
|
+
if arch not in locked:
|
|
51
|
+
raise AppDirError(
|
|
52
|
+
f"--arch {arch} is not in the lock (covers {', '.join(locked)}).\n"
|
|
53
|
+
f" Add {arch!r} to [tool.kivy.linux].archs and re-lock, or pick a "
|
|
54
|
+
"locked arch."
|
|
55
|
+
)
|
|
56
|
+
return arch
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _setting_applies(value: bool | str, *, release: bool) -> bool:
|
|
60
|
+
"""A ``build_settings`` tri-state resolved for the build being produced."""
|
|
61
|
+
if isinstance(value, bool):
|
|
62
|
+
return value
|
|
63
|
+
return release # DESKTOP_RELEASE_ONLY
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _resolve_byte_compile(
|
|
67
|
+
config: Config,
|
|
68
|
+
*,
|
|
69
|
+
staged_interpreter: Path,
|
|
70
|
+
target_arch: str,
|
|
71
|
+
python_version: str,
|
|
72
|
+
release: bool,
|
|
73
|
+
) -> tuple[tuple[str, ...] | None, bool]:
|
|
74
|
+
"""Decide whether to byte-compile, and with which interpreter.
|
|
75
|
+
|
|
76
|
+
Returns ``(compiler_argv_or_None, strip_source)``; ``None`` means do not
|
|
77
|
+
byte-compile. ``byte_compile = true`` is read as "I insist", so a missing
|
|
78
|
+
interpreter is an error; the default ``"release"`` degrades to shipping
|
|
79
|
+
source with a warning rather than breaking a build the user never
|
|
80
|
+
configured.
|
|
81
|
+
"""
|
|
82
|
+
settings = config.linux_required.build_settings
|
|
83
|
+
if not _setting_applies(settings.byte_compile, release=release):
|
|
84
|
+
return None, False
|
|
85
|
+
native = platform.machine().lower() == target_arch
|
|
86
|
+
compiler = select_compiler(
|
|
87
|
+
staged_interpreter=staged_interpreter,
|
|
88
|
+
native=native,
|
|
89
|
+
python_version=python_version,
|
|
90
|
+
)
|
|
91
|
+
if compiler is None:
|
|
92
|
+
message = (
|
|
93
|
+
f"this project ships CPython {python_version}, and no CPython of "
|
|
94
|
+
"that minor could be found to byte-compile with (a .pyc is only "
|
|
95
|
+
"loadable by the exact CPython minor that wrote it)"
|
|
96
|
+
)
|
|
97
|
+
if settings.byte_compile is True:
|
|
98
|
+
raise AppDirError(
|
|
99
|
+
"[tool.kivy.linux.build_settings].byte_compile = true but "
|
|
100
|
+
f"{message}.\n"
|
|
101
|
+
" Install a matching CPython, or set byte_compile = false."
|
|
102
|
+
)
|
|
103
|
+
click.echo(f"[stage] not byte-compiling: {message}.")
|
|
104
|
+
return None, False
|
|
105
|
+
strip = _setting_applies(settings.strip_source, release=release)
|
|
106
|
+
return compiler, strip
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def build_appdir(
|
|
110
|
+
config: Config,
|
|
111
|
+
lock: LinuxLockfile,
|
|
112
|
+
project_root: Path,
|
|
113
|
+
*,
|
|
114
|
+
arch: str | None = None,
|
|
115
|
+
staging_dir: Path | None = None,
|
|
116
|
+
no_cache: bool = False,
|
|
117
|
+
cache: ArtifactCache | None = None,
|
|
118
|
+
release: bool = False,
|
|
119
|
+
echo=click.echo,
|
|
120
|
+
) -> Path:
|
|
121
|
+
"""Build the AppDir tree and return its path."""
|
|
122
|
+
target_arch = resolve_assembly_arch(lock.archs, arch)
|
|
123
|
+
cache = cache or ArtifactCache()
|
|
124
|
+
staging_dir = staging_dir or (project_root / "build" / "linux")
|
|
125
|
+
appdir = staging_dir / f"{config.display_name}.AppDir"
|
|
126
|
+
|
|
127
|
+
# Assemble into a temp tree and swap it in only on success, so a failure
|
|
128
|
+
# mid-assembly (e.g. a transient runtime/wheel fetch error) leaves the
|
|
129
|
+
# previous, working AppDir untouched instead of a half-written one.
|
|
130
|
+
staging_dir.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
work = Path(tempfile.mkdtemp(dir=staging_dir, prefix=f".{appdir.name}.tmp-"))
|
|
132
|
+
try:
|
|
133
|
+
(work / "usr").mkdir(parents=True)
|
|
134
|
+
|
|
135
|
+
echo(
|
|
136
|
+
f"Staging CPython {lock.python_runtime.version} runtime ({target_arch}) ..."
|
|
137
|
+
)
|
|
138
|
+
stage_runtime(
|
|
139
|
+
lock.python_runtime,
|
|
140
|
+
target_arch,
|
|
141
|
+
work / "usr" / "python",
|
|
142
|
+
project_root=project_root,
|
|
143
|
+
cache=cache,
|
|
144
|
+
no_cache=no_cache,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
echo(f"Installing {len(lock.packages)} locked packages ...")
|
|
148
|
+
stage_wheels(
|
|
149
|
+
lock.packages,
|
|
150
|
+
target_arch,
|
|
151
|
+
work / "usr" / "lib",
|
|
152
|
+
project_root=project_root,
|
|
153
|
+
cache=cache,
|
|
154
|
+
no_cache=no_cache,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if lock.native_binaries:
|
|
158
|
+
echo(f"Staging {len(lock.native_binaries)} native binaries ...")
|
|
159
|
+
stage_native_binaries(
|
|
160
|
+
lock,
|
|
161
|
+
work / "usr",
|
|
162
|
+
project_root=project_root,
|
|
163
|
+
cache=cache,
|
|
164
|
+
no_cache=no_cache,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
_copy_app_sources(config, project_root, work / "usr" / "app")
|
|
168
|
+
|
|
169
|
+
compiler, strip_source = _resolve_byte_compile(
|
|
170
|
+
config,
|
|
171
|
+
staged_interpreter=work / "usr" / "python" / "bin" / "python3",
|
|
172
|
+
target_arch=target_arch,
|
|
173
|
+
python_version=lock.python_runtime.version,
|
|
174
|
+
release=release,
|
|
175
|
+
)
|
|
176
|
+
if compiler is not None:
|
|
177
|
+
with_what = " ".join(compiler) if compiler else "this interpreter"
|
|
178
|
+
echo(
|
|
179
|
+
f"[stage] byte-compiling the Python payload with {with_what}"
|
|
180
|
+
+ (" (.pyc only)" if strip_source else "")
|
|
181
|
+
)
|
|
182
|
+
try:
|
|
183
|
+
byte_compile(
|
|
184
|
+
[work / "usr" / "app", work / "usr" / "lib"],
|
|
185
|
+
compiler=compiler,
|
|
186
|
+
strip_source=strip_source,
|
|
187
|
+
stripdir=work,
|
|
188
|
+
)
|
|
189
|
+
except PycompileError as exc:
|
|
190
|
+
raise AppDirError(
|
|
191
|
+
f"{exc}\n Fix it, or set "
|
|
192
|
+
"[tool.kivy.linux.build_settings].byte_compile = false."
|
|
193
|
+
) from exc
|
|
194
|
+
|
|
195
|
+
stage_icons(config, project_root, work)
|
|
196
|
+
|
|
197
|
+
build_apprun(
|
|
198
|
+
work / "AppRun",
|
|
199
|
+
entry_point=config.kivy.entry_point,
|
|
200
|
+
app_id=config.linux_required.app_id,
|
|
201
|
+
has_native_binaries=bool(lock.native_binaries),
|
|
202
|
+
)
|
|
203
|
+
desktop_path = work / f"{config.linux_required.app_id}.desktop"
|
|
204
|
+
write_desktop_entry(config, desktop_path)
|
|
205
|
+
validate_desktop_file(desktop_path)
|
|
206
|
+
except BaseException:
|
|
207
|
+
shutil.rmtree(work, ignore_errors=True)
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
# Swap the freshly built tree in for the previous one. The rename is atomic
|
|
211
|
+
# within staging_dir; the brief unlink→rename window only exists after a
|
|
212
|
+
# fully successful build.
|
|
213
|
+
if appdir.exists():
|
|
214
|
+
shutil.rmtree(appdir)
|
|
215
|
+
work.replace(appdir)
|
|
216
|
+
return appdir
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _copy_app_sources(config: Config, project_root: Path, dest: Path) -> None:
|
|
220
|
+
src = (project_root / config.kivy.app_dir).resolve()
|
|
221
|
+
if not src.is_dir():
|
|
222
|
+
raise AppDirError(
|
|
223
|
+
f"[tool.kivy].app_dir points to {config.kivy.app_dir!r}, which is not "
|
|
224
|
+
f"a directory under {project_root}."
|
|
225
|
+
)
|
|
226
|
+
# A dotted entry_point (e.g. "pkg.start") maps to a nested source file
|
|
227
|
+
# (src/pkg/start.py), matching the shared app_dir/entry_point layout.
|
|
228
|
+
entry_rel = f"{entry_point_rel_path(config.kivy.entry_point)}.py"
|
|
229
|
+
entry = src / entry_rel
|
|
230
|
+
if not entry.is_file():
|
|
231
|
+
raise AppDirError(
|
|
232
|
+
f"entry point {entry_rel} not found in "
|
|
233
|
+
f"{config.kivy.app_dir}/ (set [tool.kivy].entry_point)."
|
|
234
|
+
)
|
|
235
|
+
shutil.copytree(src, dest, ignore=_IGNORE)
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Linux dispatch for the shared ``build`` / ``run`` / ``package`` verbs.
|
|
2
|
+
|
|
3
|
+
Keeps the Linux AppDir flow out of the (iOS/Xcode-centric) verb modules: each
|
|
4
|
+
verb resolves the target platform and, when it is Linux, calls in here. Loads the
|
|
5
|
+
Linux config + ``pylock.linux.toml``, runs the drift check, and drives the
|
|
6
|
+
AppDir bundler.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import subprocess
|
|
12
|
+
import time
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
from kivyforge.cli._common import ToolchainError, lockfile_path_for
|
|
18
|
+
from kivyforge.config import ConfigError, load_config
|
|
19
|
+
from kivyforge.lock.reader import LockError, is_in_sync
|
|
20
|
+
|
|
21
|
+
from .. import HostCapabilityError, get_platform
|
|
22
|
+
from . import AppDirError
|
|
23
|
+
from .appimage import build_appimage
|
|
24
|
+
from .bundle import build_appdir, resolve_assembly_arch
|
|
25
|
+
from .lock import LinuxLockfile
|
|
26
|
+
from .lock import load as load_linux_lock
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def linux_build(
|
|
30
|
+
project_root: Path,
|
|
31
|
+
*,
|
|
32
|
+
arch: str | None,
|
|
33
|
+
no_verify_lock: bool,
|
|
34
|
+
no_cache: bool,
|
|
35
|
+
) -> Path:
|
|
36
|
+
"""Assemble the Linux AppDir; return its path."""
|
|
37
|
+
_require_linux_host()
|
|
38
|
+
config, lock = _load_and_verify(project_root, no_verify_lock)
|
|
39
|
+
appdir = _assemble(
|
|
40
|
+
config, lock, project_root, arch=arch, no_cache=no_cache, release=False
|
|
41
|
+
)
|
|
42
|
+
click.echo(f"Built {appdir.relative_to(project_root)}")
|
|
43
|
+
return appdir
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def linux_package(
|
|
47
|
+
project_root: Path,
|
|
48
|
+
*,
|
|
49
|
+
fmt: str,
|
|
50
|
+
arch: str | None,
|
|
51
|
+
no_verify_lock: bool,
|
|
52
|
+
no_cache: bool,
|
|
53
|
+
) -> Path:
|
|
54
|
+
"""Produce the distributable artifact: an ``.AppImage`` (default) or the AppDir."""
|
|
55
|
+
_require_linux_host()
|
|
56
|
+
config, lock = _load_and_verify(project_root, no_verify_lock)
|
|
57
|
+
target_arch = _resolve_arch(lock, arch)
|
|
58
|
+
appdir = _assemble(
|
|
59
|
+
config, lock, project_root, arch=target_arch, no_cache=no_cache, release=True
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if fmt == "folder":
|
|
63
|
+
click.echo(
|
|
64
|
+
f"Packaged {appdir.relative_to(project_root)} (AppDir folder).\n"
|
|
65
|
+
" Run it with ./AppRun, or `kivyforge package -f appimage` for a "
|
|
66
|
+
"single-file distributable."
|
|
67
|
+
)
|
|
68
|
+
return appdir
|
|
69
|
+
|
|
70
|
+
output = (
|
|
71
|
+
project_root
|
|
72
|
+
/ "dist"
|
|
73
|
+
/ "linux"
|
|
74
|
+
/ f"{config.app_slug}-{config.project.version}-{target_arch}.AppImage"
|
|
75
|
+
)
|
|
76
|
+
try:
|
|
77
|
+
result = build_appimage(
|
|
78
|
+
appdir,
|
|
79
|
+
output,
|
|
80
|
+
target_arch,
|
|
81
|
+
project_root=project_root,
|
|
82
|
+
no_cache=no_cache,
|
|
83
|
+
echo=click.echo,
|
|
84
|
+
)
|
|
85
|
+
except AppDirError as exc:
|
|
86
|
+
raise ToolchainError(str(exc)) from exc
|
|
87
|
+
|
|
88
|
+
click.echo(
|
|
89
|
+
f"Packaged {result.relative_to(project_root)}.\n"
|
|
90
|
+
" Distribute the .AppImage directly (chmod +x, then run). The host needs "
|
|
91
|
+
"glibc ≥ the effective floor, libGL/libEGL, and an X11/Wayland session.\n"
|
|
92
|
+
" No libfuse2 package is required (static-FUSE runtime embedded). If the "
|
|
93
|
+
"host lacks kernel FUSE (/dev/fuse) — e.g. some containers/CI — run it "
|
|
94
|
+
"with --appimage-extract-and-run (or APPIMAGE_EXTRACT_AND_RUN=1)."
|
|
95
|
+
)
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _assemble(config, lock, project_root, *, arch, no_cache, release) -> Path:
|
|
100
|
+
try:
|
|
101
|
+
return build_appdir(
|
|
102
|
+
config, lock, project_root, arch=arch, no_cache=no_cache, release=release
|
|
103
|
+
)
|
|
104
|
+
except AppDirError as exc:
|
|
105
|
+
raise ToolchainError(str(exc)) from exc
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _resolve_arch(lock: LinuxLockfile, arch: str | None) -> str:
|
|
109
|
+
try:
|
|
110
|
+
return resolve_assembly_arch(lock.archs, arch)
|
|
111
|
+
except AppDirError as exc:
|
|
112
|
+
raise ToolchainError(str(exc)) from exc
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _load_and_verify(
|
|
116
|
+
project_root: Path, no_verify_lock: bool
|
|
117
|
+
) -> tuple[object, LinuxLockfile]:
|
|
118
|
+
config = _load_config(project_root)
|
|
119
|
+
lock = _load_lock(project_root)
|
|
120
|
+
pyproject = project_root / "pyproject.toml"
|
|
121
|
+
if not no_verify_lock and not is_in_sync(lock, pyproject.read_text("utf-8")):
|
|
122
|
+
raise ToolchainError(
|
|
123
|
+
f"{lockfile_path_for('linux').name} is out of date with "
|
|
124
|
+
"pyproject.toml.\n"
|
|
125
|
+
" Run `kivyforge lock -p linux` to regenerate it (or pass "
|
|
126
|
+
"--no-verify-lock to build against the stale lock anyway)."
|
|
127
|
+
)
|
|
128
|
+
return config, lock
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def linux_run(
|
|
132
|
+
project_root: Path,
|
|
133
|
+
*,
|
|
134
|
+
arch: str | None,
|
|
135
|
+
no_build: bool,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Build (unless --no-build) and launch the app via ``AppRun`` in foreground."""
|
|
138
|
+
if no_build:
|
|
139
|
+
_require_linux_host()
|
|
140
|
+
config = _load_config(project_root)
|
|
141
|
+
appdir = project_root / "build" / "linux" / f"{config.display_name}.AppDir"
|
|
142
|
+
if not appdir.exists():
|
|
143
|
+
raise ToolchainError(
|
|
144
|
+
f"no built AppDir at {appdir.relative_to(project_root)}; run "
|
|
145
|
+
"without --no-build first."
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
appdir = linux_build(
|
|
149
|
+
project_root, arch=arch, no_verify_lock=False, no_cache=False
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
apprun = appdir / "AppRun"
|
|
153
|
+
click.echo(f"Launching {appdir.name} ...")
|
|
154
|
+
# Foreground exec so the dev sees stdout/stderr + tracebacks.
|
|
155
|
+
proc = subprocess.run([str(apprun)])
|
|
156
|
+
if proc.returncode != 0:
|
|
157
|
+
raise ToolchainError(f"{appdir.name} exited with status {proc.returncode}.")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def linux_status(project_root: Path) -> None:
|
|
161
|
+
"""Show app identity, Python version, lock sync, and build state."""
|
|
162
|
+
config = _load_config(project_root)
|
|
163
|
+
linux = config.linux_required
|
|
164
|
+
click.echo(f"App: {config.display_name} ({linux.app_id})")
|
|
165
|
+
click.echo(f"Python: {linux.python_version or '(unset)'}")
|
|
166
|
+
click.echo(f"Lock: {_lock_state(project_root)}")
|
|
167
|
+
|
|
168
|
+
appdir = project_root / "build" / "linux" / f"{config.display_name}.AppDir"
|
|
169
|
+
click.echo(f"Build: {_build_state(appdir)}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _lock_state(project_root: Path) -> str:
|
|
173
|
+
path = lockfile_path_for("linux", project_root)
|
|
174
|
+
if not path.is_file():
|
|
175
|
+
return "missing (run `kivyforge lock -p linux`)"
|
|
176
|
+
try:
|
|
177
|
+
lock = load_linux_lock(path)
|
|
178
|
+
except LockError:
|
|
179
|
+
return "unreadable (run `kivyforge lock -p linux`)"
|
|
180
|
+
pyproject = project_root / "pyproject.toml"
|
|
181
|
+
if is_in_sync(lock, pyproject.read_text("utf-8")):
|
|
182
|
+
return "in sync"
|
|
183
|
+
return "out of date (run `kivyforge lock -p linux`)"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _build_state(appdir: Path) -> str:
|
|
187
|
+
if not appdir.exists():
|
|
188
|
+
return "not built"
|
|
189
|
+
age = time.time() - appdir.stat().st_mtime
|
|
190
|
+
return f"last built {_humanize(age)}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _humanize(seconds: float) -> str:
|
|
194
|
+
seconds = int(seconds)
|
|
195
|
+
if seconds < 60:
|
|
196
|
+
return "just now"
|
|
197
|
+
if seconds < 3600:
|
|
198
|
+
m = seconds // 60
|
|
199
|
+
return f"{m} minute{'s' if m != 1 else ''} ago"
|
|
200
|
+
if seconds < 86400:
|
|
201
|
+
h = seconds // 3600
|
|
202
|
+
return f"{h} hour{'s' if h != 1 else ''} ago"
|
|
203
|
+
d = seconds // 86400
|
|
204
|
+
return f"{d} day{'s' if d != 1 else ''} ago"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _require_linux_host() -> None:
|
|
208
|
+
try:
|
|
209
|
+
get_platform("linux").check_host_capability()
|
|
210
|
+
except HostCapabilityError as exc:
|
|
211
|
+
raise ToolchainError(str(exc)) from exc
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _load_config(project_root: Path):
|
|
215
|
+
try:
|
|
216
|
+
return load_config(
|
|
217
|
+
project_root / "pyproject.toml", require_ios=False, require_linux=True
|
|
218
|
+
)
|
|
219
|
+
except ConfigError as exc:
|
|
220
|
+
raise ToolchainError(exc.format()) from exc
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _load_lock(project_root: Path) -> LinuxLockfile:
|
|
224
|
+
path = lockfile_path_for("linux", project_root)
|
|
225
|
+
if not path.is_file():
|
|
226
|
+
raise ToolchainError(
|
|
227
|
+
f"no {path.name} found. Run `kivyforge lock -p linux` first."
|
|
228
|
+
)
|
|
229
|
+
try:
|
|
230
|
+
return load_linux_lock(path)
|
|
231
|
+
except LockError as exc:
|
|
232
|
+
raise ToolchainError(str(exc)) from exc
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Generate the freedesktop ``.desktop`` entry for the AppDir (linux-spec).
|
|
2
|
+
|
|
3
|
+
The Linux analog of the macOS ``Info.plist``: kivyforge owns the entry, built
|
|
4
|
+
from ``[project]`` + ``[tool.kivy]`` + ``[tool.kivy.linux]``. ``Exec=AppRun %f``
|
|
5
|
+
and ``StartupWMClass=<app_id>`` (matched by the ``AppRun`` launcher's SDL
|
|
6
|
+
WM_CLASS env) so the running window groups under the app's own icon. The
|
|
7
|
+
generated file must pass ``desktop-file-validate`` (also a ``doctor`` check).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from kivyforge.config.model import Config
|
|
17
|
+
|
|
18
|
+
from . import AppDirError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def render_desktop_entry(config: Config) -> str:
|
|
22
|
+
"""The ``.desktop`` file contents for *config* (pure/testable)."""
|
|
23
|
+
linux = config.linux_required
|
|
24
|
+
categories = "".join(f"{c};" for c in linux.desktop.categories)
|
|
25
|
+
lines = [
|
|
26
|
+
"[Desktop Entry]",
|
|
27
|
+
"Type=Application",
|
|
28
|
+
f"Name={config.display_name}",
|
|
29
|
+
]
|
|
30
|
+
if config.project.description:
|
|
31
|
+
lines.append(f"Comment={config.project.description}")
|
|
32
|
+
lines += [
|
|
33
|
+
"Exec=AppRun %f",
|
|
34
|
+
f"Icon={linux.app_id}",
|
|
35
|
+
f"Categories={categories}",
|
|
36
|
+
f"StartupWMClass={linux.app_id}",
|
|
37
|
+
"Terminal=false",
|
|
38
|
+
]
|
|
39
|
+
return "\n".join(lines) + "\n"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def write_desktop_entry(config: Config, dest: Path) -> None:
|
|
43
|
+
"""Write ``<app_id>.desktop`` for *config* at *dest*."""
|
|
44
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
dest.write_text(render_desktop_entry(config), encoding="utf-8")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_desktop_file(dest: Path) -> None:
|
|
49
|
+
"""Run ``desktop-file-validate`` on *dest*; hard-fail on validation errors.
|
|
50
|
+
|
|
51
|
+
The generated ``.desktop`` must pass ``desktop-file-validate`` (linux-spec):
|
|
52
|
+
an invalid entry produces an AppImage that launches but fails desktop
|
|
53
|
+
integration (menu/search/icon association). ``desktop-file-validate`` (from
|
|
54
|
+
``desktop-file-utils``) is optional on the build host — when it isn't
|
|
55
|
+
installed we can't run it, so we skip (config-time category validation still
|
|
56
|
+
guards the common typo). When it *is* installed, any reported error is a hard
|
|
57
|
+
failure.
|
|
58
|
+
"""
|
|
59
|
+
tool = shutil.which("desktop-file-validate")
|
|
60
|
+
if tool is None:
|
|
61
|
+
return
|
|
62
|
+
try:
|
|
63
|
+
proc = subprocess.run([tool, str(dest)], capture_output=True, text=True)
|
|
64
|
+
except (OSError, ValueError):
|
|
65
|
+
return
|
|
66
|
+
if proc.returncode != 0:
|
|
67
|
+
detail = (proc.stdout + proc.stderr).strip() or "validation failed"
|
|
68
|
+
raise AppDirError(
|
|
69
|
+
f"generated {dest.name} failed desktop-file-validate:\n{detail}\n"
|
|
70
|
+
" Fix [tool.kivy.linux.desktop] / [project] metadata and rebuild."
|
|
71
|
+
)
|