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,148 @@
|
|
|
1
|
+
"""Hermetic ELF reader: LOAD-segment alignment + DT_NEEDED (android/04 §16 KB).
|
|
2
|
+
|
|
3
|
+
Pure-Python parsing of the parts kivyforge's doctor needs — no NDK/readelf
|
|
4
|
+
dependency, so the 16 KB alignment check runs on any host. Android 15/16 devices
|
|
5
|
+
with 16 KB memory pages refuse to load 4 KB-aligned ``.so``s; the check scans
|
|
6
|
+
every staged library's LOAD-segment ``p_align`` and flags anything below 16 KB.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import struct
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
PAGE_16K = 0x4000
|
|
16
|
+
|
|
17
|
+
_PT_LOAD = 1
|
|
18
|
+
_DT_NEEDED = 1
|
|
19
|
+
_DT_STRTAB = 5
|
|
20
|
+
_DT_NULL = 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ElfError(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# e_machine values for the two ABIs the python.org runtime ships.
|
|
28
|
+
EM_X86_64 = 0x3E
|
|
29
|
+
EM_AARCH64 = 0xB7
|
|
30
|
+
_MACHINE_NAMES = {
|
|
31
|
+
EM_X86_64: "x86_64",
|
|
32
|
+
EM_AARCH64: "aarch64",
|
|
33
|
+
0x28: "armv7 (32-bit)",
|
|
34
|
+
0x03: "i386 (32-bit)",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def machine_name(e_machine: int) -> str:
|
|
39
|
+
return _MACHINE_NAMES.get(e_machine, f"e_machine 0x{e_machine:x}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class ElfInfo:
|
|
44
|
+
is_64: bool
|
|
45
|
+
max_load_align: int
|
|
46
|
+
needed: tuple[str, ...]
|
|
47
|
+
machine: int = 0
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def is_16k_aligned(self) -> bool:
|
|
51
|
+
return self.max_load_align >= PAGE_16K
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def read_elf(path: Path) -> ElfInfo:
|
|
55
|
+
"""Parse an ELF's LOAD alignments + DT_NEEDED; raise ``ElfError`` if not ELF."""
|
|
56
|
+
data = path.read_bytes()
|
|
57
|
+
if len(data) < 64 or data[:4] != b"\x7fELF":
|
|
58
|
+
raise ElfError(f"{path} is not an ELF file")
|
|
59
|
+
ei_class = data[4]
|
|
60
|
+
little = data[5] == 1
|
|
61
|
+
is_64 = ei_class == 2
|
|
62
|
+
end = "<" if little else ">"
|
|
63
|
+
e_machine = struct.unpack_from(end + "H", data, 0x12)[0]
|
|
64
|
+
|
|
65
|
+
if is_64:
|
|
66
|
+
e_phoff = struct.unpack_from(end + "Q", data, 0x20)[0]
|
|
67
|
+
e_phentsize = struct.unpack_from(end + "H", data, 0x36)[0]
|
|
68
|
+
e_phnum = struct.unpack_from(end + "H", data, 0x38)[0]
|
|
69
|
+
else:
|
|
70
|
+
e_phoff = struct.unpack_from(end + "I", data, 0x1C)[0]
|
|
71
|
+
e_phentsize = struct.unpack_from(end + "H", data, 0x2A)[0]
|
|
72
|
+
e_phnum = struct.unpack_from(end + "H", data, 0x2C)[0]
|
|
73
|
+
|
|
74
|
+
max_align = 0
|
|
75
|
+
dynamic_off = dynamic_size = 0
|
|
76
|
+
load_segments = []
|
|
77
|
+
for i in range(e_phnum):
|
|
78
|
+
off = e_phoff + i * e_phentsize
|
|
79
|
+
p_type = struct.unpack_from(end + "I", data, off)[0]
|
|
80
|
+
if is_64:
|
|
81
|
+
p_offset = struct.unpack_from(end + "Q", data, off + 8)[0]
|
|
82
|
+
p_filesz = struct.unpack_from(end + "Q", data, off + 0x20)[0]
|
|
83
|
+
p_align = struct.unpack_from(end + "Q", data, off + 0x30)[0]
|
|
84
|
+
p_vaddr = struct.unpack_from(end + "Q", data, off + 0x10)[0]
|
|
85
|
+
else:
|
|
86
|
+
p_offset = struct.unpack_from(end + "I", data, off + 4)[0]
|
|
87
|
+
p_filesz = struct.unpack_from(end + "I", data, off + 0x10)[0]
|
|
88
|
+
p_align = struct.unpack_from(end + "I", data, off + 0x1C)[0]
|
|
89
|
+
p_vaddr = struct.unpack_from(end + "I", data, off + 8)[0]
|
|
90
|
+
if p_type == _PT_LOAD:
|
|
91
|
+
max_align = max(max_align, p_align)
|
|
92
|
+
load_segments.append((p_offset, p_vaddr, p_filesz))
|
|
93
|
+
elif p_type == 2: # PT_DYNAMIC
|
|
94
|
+
dynamic_off, dynamic_size = p_offset, p_filesz
|
|
95
|
+
|
|
96
|
+
needed = _read_needed(data, end, is_64, dynamic_off, dynamic_size, load_segments)
|
|
97
|
+
return ElfInfo(
|
|
98
|
+
is_64=is_64, max_load_align=max_align, needed=needed, machine=e_machine
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _read_needed(data, end, is_64, dyn_off, dyn_size, load_segments) -> tuple[str, ...]:
|
|
103
|
+
if not dyn_off:
|
|
104
|
+
return ()
|
|
105
|
+
entry = 16 if is_64 else 8
|
|
106
|
+
fmt = end + ("qQ" if is_64 else "iI")
|
|
107
|
+
strtab_vaddr = 0
|
|
108
|
+
needed_offsets: list[int] = []
|
|
109
|
+
for i in range(dyn_size // entry):
|
|
110
|
+
tag, val = struct.unpack_from(fmt, data, dyn_off + i * entry)
|
|
111
|
+
if tag == _DT_NULL:
|
|
112
|
+
break
|
|
113
|
+
if tag == _DT_STRTAB:
|
|
114
|
+
strtab_vaddr = val
|
|
115
|
+
elif tag == _DT_NEEDED:
|
|
116
|
+
needed_offsets.append(val)
|
|
117
|
+
if not strtab_vaddr:
|
|
118
|
+
return ()
|
|
119
|
+
strtab_file = _vaddr_to_offset(strtab_vaddr, load_segments)
|
|
120
|
+
if strtab_file is None:
|
|
121
|
+
return ()
|
|
122
|
+
names: list[str] = []
|
|
123
|
+
for str_off in needed_offsets:
|
|
124
|
+
start = strtab_file + str_off
|
|
125
|
+
end_idx = data.find(b"\x00", start)
|
|
126
|
+
if end_idx > start:
|
|
127
|
+
names.append(data[start:end_idx].decode("utf-8", "replace"))
|
|
128
|
+
return tuple(names)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _vaddr_to_offset(vaddr, load_segments) -> int | None:
|
|
132
|
+
for p_offset, p_vaddr, p_filesz in load_segments:
|
|
133
|
+
if p_vaddr <= vaddr < p_vaddr + p_filesz:
|
|
134
|
+
return p_offset + (vaddr - p_vaddr)
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def scan_alignment(jnilibs_dir: Path) -> list[tuple[Path, int]]:
|
|
139
|
+
"""Return ``(path, align)`` for every ``.so`` below 16 KB LOAD alignment."""
|
|
140
|
+
bad: list[tuple[Path, int]] = []
|
|
141
|
+
for so in sorted(jnilibs_dir.rglob("*.so")):
|
|
142
|
+
try:
|
|
143
|
+
info = read_elf(so)
|
|
144
|
+
except (ElfError, OSError, struct.error):
|
|
145
|
+
continue
|
|
146
|
+
if not info.is_16k_aligned:
|
|
147
|
+
bad.append((so, info.max_load_align))
|
|
148
|
+
return bad
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Gradle-project generation: manifest, build.gradle files, resources, pins."""
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Generate ``AndroidManifest.xml`` (android/04 §manifest generation).
|
|
2
|
+
|
|
3
|
+
Deterministic emission from ``AndroidConfig``: permissions (bare names
|
|
4
|
+
auto-prefixed; nothing force-added beyond a foreground service's
|
|
5
|
+
``FOREGROUND_SERVICE*`` pair), implied non-required ``<uses-feature>``
|
|
6
|
+
synthesis (android/01 §auto_features), services/activities/intent filters,
|
|
7
|
+
attribute passthrough, and substituted raw-XML fragments (well-formedness was
|
|
8
|
+
already enforced at config time; fragments are re-parsed here as a belt).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from xml.sax.saxutils import escape, quoteattr
|
|
14
|
+
|
|
15
|
+
from kivyforge.config.model import AndroidConfig, AndroidService
|
|
16
|
+
|
|
17
|
+
ANDROID_NS = "http://schemas.android.com/apk/res/android"
|
|
18
|
+
MAIN_ACTIVITY = "org.kivy.android.PythonActivity"
|
|
19
|
+
GENERATED_THEME = "@style/Theme.Kivyforge"
|
|
20
|
+
|
|
21
|
+
# Google's permission -> implied-hardware-feature table (android/01
|
|
22
|
+
# §implied features), including the two documented deliberate extras
|
|
23
|
+
# (USE_BIOMETRIC mirroring USE_FINGERPRINT; the API-31 Bluetooth pair).
|
|
24
|
+
IMPLIED_FEATURES: dict[str, tuple[str, ...]] = {
|
|
25
|
+
"android.permission.CAMERA": (
|
|
26
|
+
"android.hardware.camera",
|
|
27
|
+
"android.hardware.camera.autofocus",
|
|
28
|
+
),
|
|
29
|
+
"android.permission.RECORD_AUDIO": ("android.hardware.microphone",),
|
|
30
|
+
"android.permission.ACCESS_FINE_LOCATION": (
|
|
31
|
+
"android.hardware.location",
|
|
32
|
+
"android.hardware.location.gps",
|
|
33
|
+
),
|
|
34
|
+
"android.permission.ACCESS_COARSE_LOCATION": (
|
|
35
|
+
"android.hardware.location",
|
|
36
|
+
"android.hardware.location.network",
|
|
37
|
+
),
|
|
38
|
+
"android.permission.BLUETOOTH": ("android.hardware.bluetooth",),
|
|
39
|
+
"android.permission.BLUETOOTH_CONNECT": ("android.hardware.bluetooth",),
|
|
40
|
+
"android.permission.BLUETOOTH_SCAN": (
|
|
41
|
+
"android.hardware.bluetooth",
|
|
42
|
+
"android.hardware.bluetooth_le",
|
|
43
|
+
),
|
|
44
|
+
"android.permission.NFC": ("android.hardware.nfc",),
|
|
45
|
+
"android.permission.USE_BIOMETRIC": ("android.hardware.fingerprint",),
|
|
46
|
+
"android.permission.USE_FINGERPRINT": ("android.hardware.fingerprint",),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# [tool.kivy].orientation -> android:screenOrientation. A single value maps
|
|
50
|
+
# directly; the portrait pair / landscape pair map to their sensor variants;
|
|
51
|
+
# any wider mix means "let the sensor decide".
|
|
52
|
+
_ORIENTATION_MAP = {
|
|
53
|
+
("portrait",): "portrait",
|
|
54
|
+
("portrait-upside-down",): "reversePortrait",
|
|
55
|
+
("landscape-left",): "reverseLandscape",
|
|
56
|
+
("landscape-right",): "landscape",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ManifestError(Exception):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def qualified_permissions(uses: tuple[str, ...]) -> list[str]:
|
|
65
|
+
"""Bare names get the ``android.permission.`` prefix; dotted pass verbatim."""
|
|
66
|
+
return [p if "." in p else f"android.permission.{p}" for p in uses]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def implied_features(permissions: list[str]) -> list[str]:
|
|
70
|
+
out: list[str] = []
|
|
71
|
+
for permission in permissions:
|
|
72
|
+
for feature in IMPLIED_FEATURES.get(permission, ()):
|
|
73
|
+
if feature not in out:
|
|
74
|
+
out.append(feature)
|
|
75
|
+
return sorted(out)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def screen_orientation(orientation: tuple[str, ...]) -> str:
|
|
79
|
+
key = tuple(sorted(orientation))
|
|
80
|
+
if len(orientation) == 1:
|
|
81
|
+
return _ORIENTATION_MAP[tuple(orientation)]
|
|
82
|
+
if key == ("landscape-left", "landscape-right"):
|
|
83
|
+
return "sensorLandscape"
|
|
84
|
+
if key == ("portrait", "portrait-upside-down"):
|
|
85
|
+
return "sensorPortrait"
|
|
86
|
+
return "fullSensor"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def service_class_name(service: AndroidService) -> str:
|
|
90
|
+
"""The generated PythonService subclass FQCN (namespace preserved)."""
|
|
91
|
+
return f"org.kivy.android.Service{service.name}"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def generate_manifest(android: AndroidConfig, *, orientation: tuple[str, ...]) -> str:
|
|
95
|
+
permissions = qualified_permissions(android.permissions.uses)
|
|
96
|
+
# A declared foreground service auto-adds FOREGROUND_SERVICE + the typed
|
|
97
|
+
# permission — the ONLY implicit permissions (android/01 §managed keys).
|
|
98
|
+
for service in android.services:
|
|
99
|
+
if service.foreground:
|
|
100
|
+
_add_unique(permissions, "android.permission.FOREGROUND_SERVICE")
|
|
101
|
+
fstype = service.foreground_service_type or ""
|
|
102
|
+
typed = "android.permission.FOREGROUND_SERVICE_" + _snake_upper(fstype)
|
|
103
|
+
_add_unique(permissions, typed)
|
|
104
|
+
|
|
105
|
+
lines: list[str] = []
|
|
106
|
+
lines.append('<?xml version="1.0" encoding="utf-8"?>')
|
|
107
|
+
lines.append(f'<manifest xmlns:android="{ANDROID_NS}">')
|
|
108
|
+
|
|
109
|
+
for permission in permissions:
|
|
110
|
+
lines.append(f" <uses-permission android:name={quoteattr(permission)} />")
|
|
111
|
+
|
|
112
|
+
# Explicit features override the synthesized entry for the same name.
|
|
113
|
+
explicit = {f.name: f.required for f in android.permissions.features}
|
|
114
|
+
synthesized = (
|
|
115
|
+
[f for f in implied_features(permissions) if f not in explicit]
|
|
116
|
+
if android.permissions.auto_features
|
|
117
|
+
else []
|
|
118
|
+
)
|
|
119
|
+
for name in synthesized:
|
|
120
|
+
lines.append(
|
|
121
|
+
f" <uses-feature android:name={quoteattr(name)} "
|
|
122
|
+
'android:required="false" />'
|
|
123
|
+
)
|
|
124
|
+
for feature in android.permissions.features:
|
|
125
|
+
required = "true" if feature.required else "false"
|
|
126
|
+
lines.append(
|
|
127
|
+
f" <uses-feature android:name={quoteattr(feature.name)} "
|
|
128
|
+
f'android:required="{required}" />'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if android.manifest.extra_manifest_xml.strip():
|
|
132
|
+
lines.append(_substitute(android.manifest.extra_manifest_xml, android))
|
|
133
|
+
|
|
134
|
+
app_attrs: dict[str, str] = {
|
|
135
|
+
"android:label": "@string/app_name",
|
|
136
|
+
"android:icon": "@mipmap/ic_launcher",
|
|
137
|
+
"android:theme": GENERATED_THEME,
|
|
138
|
+
# roundIcon only exists in the generated resources when an icon source
|
|
139
|
+
# is configured; naming it otherwise would fail AAPT.
|
|
140
|
+
**(
|
|
141
|
+
{"android:roundIcon": "@mipmap/ic_launcher_round"}
|
|
142
|
+
if android.icons.source
|
|
143
|
+
else {}
|
|
144
|
+
),
|
|
145
|
+
# extractNativeLibs is controlled via app/build.gradle's
|
|
146
|
+
# packaging.jniLibs.useLegacyPackaging (AGP warns if set in the
|
|
147
|
+
# manifest); the gradle setting is the toolchain-managed source.
|
|
148
|
+
}
|
|
149
|
+
for key, value in sorted(android.manifest.application.items()):
|
|
150
|
+
app_attrs[key] = _attr_str(value)
|
|
151
|
+
lines.append(f" <application {_attrs(app_attrs)}>")
|
|
152
|
+
|
|
153
|
+
activity_attrs: dict[str, str] = {
|
|
154
|
+
"android:name": MAIN_ACTIVITY,
|
|
155
|
+
"android:exported": "true",
|
|
156
|
+
"android:configChanges": "keyboardHidden|orientation|screenSize",
|
|
157
|
+
"android:screenOrientation": screen_orientation(orientation),
|
|
158
|
+
"android:theme": GENERATED_THEME,
|
|
159
|
+
}
|
|
160
|
+
for key, value in sorted(android.manifest.activity.items()):
|
|
161
|
+
activity_attrs[key] = _attr_str(value)
|
|
162
|
+
lines.append(f" <activity {_attrs(activity_attrs)}>")
|
|
163
|
+
lines.append(" <intent-filter>")
|
|
164
|
+
lines.append(' <action android:name="android.intent.action.MAIN" />')
|
|
165
|
+
lines.append(
|
|
166
|
+
' <category android:name="android.intent.category.LAUNCHER" />'
|
|
167
|
+
)
|
|
168
|
+
lines.append(" </intent-filter>")
|
|
169
|
+
|
|
170
|
+
for entry in android.intent_filters:
|
|
171
|
+
lines.append(" <intent-filter>")
|
|
172
|
+
lines.append(
|
|
173
|
+
f" <action android:name={quoteattr(entry.action)} />"
|
|
174
|
+
)
|
|
175
|
+
for category in entry.categories:
|
|
176
|
+
lines.append(
|
|
177
|
+
f" <category android:name={quoteattr(category)} />"
|
|
178
|
+
)
|
|
179
|
+
for data in entry.data:
|
|
180
|
+
attrs = " ".join(
|
|
181
|
+
f"android:{k}={quoteattr(v)}" for k, v in sorted(data.items())
|
|
182
|
+
)
|
|
183
|
+
lines.append(f" <data {attrs} />")
|
|
184
|
+
lines.append(" </intent-filter>")
|
|
185
|
+
|
|
186
|
+
if android.manifest.extra_activity_xml.strip():
|
|
187
|
+
lines.append(_substitute(android.manifest.extra_activity_xml, android))
|
|
188
|
+
lines.append(" </activity>")
|
|
189
|
+
|
|
190
|
+
for extra in android.activities:
|
|
191
|
+
lines.append(
|
|
192
|
+
f" <activity android:name={quoteattr(extra.name)} "
|
|
193
|
+
f'android:exported="{_b(extra.exported)}" />'
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
for service in android.services:
|
|
197
|
+
attrs = {
|
|
198
|
+
"android:name": service_class_name(service),
|
|
199
|
+
"android:process": f":service_{service.name.lower()}",
|
|
200
|
+
"android:exported": _b(service.exported),
|
|
201
|
+
}
|
|
202
|
+
if service.foreground:
|
|
203
|
+
attrs["android:foregroundServiceType"] = (
|
|
204
|
+
service.foreground_service_type or ""
|
|
205
|
+
)
|
|
206
|
+
lines.append(f" <service {_attrs(attrs)} />")
|
|
207
|
+
|
|
208
|
+
if android.manifest.extra_application_xml.strip():
|
|
209
|
+
lines.append(_substitute(android.manifest.extra_application_xml, android))
|
|
210
|
+
|
|
211
|
+
lines.append(" </application>")
|
|
212
|
+
lines.append("</manifest>")
|
|
213
|
+
text = "\n".join(lines) + "\n"
|
|
214
|
+
_assert_well_formed(text)
|
|
215
|
+
return text
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _substitute(fragment: str, android: AndroidConfig) -> str:
|
|
219
|
+
out = fragment.replace("${applicationId}", android.package)
|
|
220
|
+
for key, value in android.manifest.placeholders.items():
|
|
221
|
+
out = out.replace("${" + key + "}", value)
|
|
222
|
+
return "\n".join(f" {line}" for line in out.strip().splitlines())
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _assert_well_formed(text: str) -> None:
|
|
226
|
+
import xml.etree.ElementTree as ET
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
ET.fromstring(text)
|
|
230
|
+
except ET.ParseError as exc: # pragma: no cover - config validation upstream
|
|
231
|
+
raise ManifestError(
|
|
232
|
+
f"generated AndroidManifest.xml is not well-formed: {exc}"
|
|
233
|
+
) from exc
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _attrs(attrs: dict[str, str]) -> str:
|
|
237
|
+
return " ".join(f"{k}={quoteattr(v)}" for k, v in attrs.items())
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _attr_str(value: object) -> str:
|
|
241
|
+
if isinstance(value, bool):
|
|
242
|
+
return "true" if value else "false"
|
|
243
|
+
return escape(str(value))
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _b(value: bool) -> str:
|
|
247
|
+
return "true" if value else "false"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _snake_upper(camel: str) -> str:
|
|
251
|
+
out: list[str] = []
|
|
252
|
+
for ch in camel:
|
|
253
|
+
if ch.isupper():
|
|
254
|
+
out.append("_")
|
|
255
|
+
out.append(ch.upper())
|
|
256
|
+
return "".join(out)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _add_unique(items: list[str], value: str) -> None:
|
|
260
|
+
if value not in items:
|
|
261
|
+
items.append(value)
|