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,76 @@
|
|
|
1
|
+
package org.kivyforge.test;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertTrue;
|
|
4
|
+
import static org.junit.Assert.fail;
|
|
5
|
+
|
|
6
|
+
import android.content.Context;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import androidx.test.core.app.ApplicationProvider;
|
|
9
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
10
|
+
import java.io.File;
|
|
11
|
+
import java.nio.charset.StandardCharsets;
|
|
12
|
+
import java.nio.file.Files;
|
|
13
|
+
import org.junit.Test;
|
|
14
|
+
import org.junit.runner.RunWith;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The kivyforge contract smoke test (android/06 --smoke).
|
|
18
|
+
*
|
|
19
|
+
* Launches PythonActivity, which runs the app in self-test mode (the
|
|
20
|
+
* KIVYFORGE_SELFTEST flag makes the bootstrap import a known stdlib + wheel
|
|
21
|
+
* extension through the sys.meta_path finder, fire an invoke0 proxy
|
|
22
|
+
* round-trip, and pull the Activity through Kivy's bootstrap contract), then
|
|
23
|
+
* asserts the on-device result markers. No app-specific test code — this
|
|
24
|
+
* validates the load-bearing runtime mechanisms (extension-module finder,
|
|
25
|
+
* invoke0 glue, and the Kivy 3 Activity contract) on a real device/emulator.
|
|
26
|
+
*/
|
|
27
|
+
@RunWith(AndroidJUnit4.class)
|
|
28
|
+
public class KivyforgeContractTest {
|
|
29
|
+
|
|
30
|
+
private static final int TIMEOUT_MS = 90_000;
|
|
31
|
+
private static final String RESULT_FILE = "kivyforge_selftest.txt";
|
|
32
|
+
|
|
33
|
+
@Test
|
|
34
|
+
public void contractSelfTestPasses() throws Exception {
|
|
35
|
+
Context ctx = ApplicationProvider.getApplicationContext();
|
|
36
|
+
File result = new File(ctx.getFilesDir(), RESULT_FILE);
|
|
37
|
+
if (result.exists() && !result.delete()) {
|
|
38
|
+
fail("could not clear stale self-test result " + result);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Intent intent = new Intent(ctx, org.kivy.android.PythonActivity.class);
|
|
42
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
43
|
+
intent.putExtra("kivyforge_selftest", true);
|
|
44
|
+
ctx.startActivity(intent);
|
|
45
|
+
|
|
46
|
+
long deadline = System.currentTimeMillis() + TIMEOUT_MS;
|
|
47
|
+
String contents = "";
|
|
48
|
+
while (System.currentTimeMillis() < deadline) {
|
|
49
|
+
if (result.exists()) {
|
|
50
|
+
contents = new String(
|
|
51
|
+
Files.readAllBytes(result.toPath()), StandardCharsets.UTF_8);
|
|
52
|
+
if (contents.contains("SELFTEST_DONE")) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
Thread.sleep(500);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
assertTrue(
|
|
60
|
+
"self-test did not complete within " + TIMEOUT_MS + "ms; got: "
|
|
61
|
+
+ contents,
|
|
62
|
+
contents.contains("SELFTEST_DONE"));
|
|
63
|
+
assertTrue(
|
|
64
|
+
"extension-module finder check failed: " + contents,
|
|
65
|
+
contents.contains("EXT_OK"));
|
|
66
|
+
assertTrue(
|
|
67
|
+
"pyjnius invoke0 proxy round-trip failed: " + contents,
|
|
68
|
+
contents.contains("PROXY_OK"));
|
|
69
|
+
assertTrue(
|
|
70
|
+
"Kivy's Android bootstrap contract failed: " + contents,
|
|
71
|
+
contents.contains("KIVY_CONTRACT_OK"));
|
|
72
|
+
assertTrue(
|
|
73
|
+
"self-test reported a failure: " + contents,
|
|
74
|
+
contents.contains("SELFTEST_ALL_OK"));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""kivyforge's implementation of Kivy's Android bootstrap contract.
|
|
2
|
+
|
|
3
|
+
Kivy 3 holds no Android bootstrap class name of its own: it imports this
|
|
4
|
+
module — the name is Kivy's, not kivyforge's — and asks it for the current
|
|
5
|
+
``android.app.Activity``. That indirection is the whole point of the contract:
|
|
6
|
+
Kivy runs on a kivyforge-built app and a python-for-android-built one without
|
|
7
|
+
knowing, or caring, which produced it. kivyforge's obligation is just this
|
|
8
|
+
file; the activity class name below is kivyforge's business alone.
|
|
9
|
+
|
|
10
|
+
Kivy pulls on first use rather than kivyforge registering at startup, so nothing
|
|
11
|
+
here imports Kivy. That is deliberate: importing Kivy from the bootstrap would
|
|
12
|
+
fix Kivy's ``KIVY_*`` environment and configuration before the app's ``main.py``
|
|
13
|
+
had a chance to set it.
|
|
14
|
+
|
|
15
|
+
The Activity is read fresh on every call and never cached. Android recreates it
|
|
16
|
+
on rotation, on configuration change and after process death, so a stored
|
|
17
|
+
instance both goes stale and pins a JNI reference to a dead Activity.
|
|
18
|
+
|
|
19
|
+
Only ``get_activity()`` is implemented; the contract's optional members are
|
|
20
|
+
deliberately absent. ``get_context()`` would only repeat what Kivy already
|
|
21
|
+
derives from the Activity. ``remove_presplash()`` has nothing to do: kivyforge's
|
|
22
|
+
splash is the platform's own — the system splash window on API 31+, and the
|
|
23
|
+
theme's ``windowBackground`` below it — so it is dismissed by the framework when
|
|
24
|
+
the first frame draws rather than being a View anyone can tear down, unlike
|
|
25
|
+
python-for-android, which overlays a View and must remove it. Kivy treats an
|
|
26
|
+
absent hook as the no-op it is, so there is nothing to stub out.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from jnius import autoclass
|
|
30
|
+
|
|
31
|
+
# kivyforge's own activity, fixed at build time (generate/manifest.py names the
|
|
32
|
+
# same class as the launcher activity). Kivy never sees this string.
|
|
33
|
+
_ACTIVITY_CLASS = "org.kivy.android.PythonActivity"
|
|
34
|
+
|
|
35
|
+
_activity_class = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_activity():
|
|
39
|
+
"""Return the current ``android.app.Activity``, or ``None`` if there is none.
|
|
40
|
+
|
|
41
|
+
``None`` is a legitimate answer, not a failure: code may run in a process
|
|
42
|
+
with no Activity, and Kivy treats it as such.
|
|
43
|
+
"""
|
|
44
|
+
global _activity_class
|
|
45
|
+
if _activity_class is None:
|
|
46
|
+
# Resolved on first call rather than at import, so that a reflection
|
|
47
|
+
# failure surfaces from the call that needs the Activity instead of from
|
|
48
|
+
# Kivy's discovery import, where it would look like a missing module.
|
|
49
|
+
_activity_class = autoclass(_ACTIVITY_CLASS)
|
|
50
|
+
return _activity_class.mActivity
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Kivyforge extension-module finder (Phase-0 prototype).
|
|
2
|
+
|
|
3
|
+
Android exposes native libraries only from the flat nativeLibraryDir; Python
|
|
4
|
+
extension modules are imported by dotted name from nested paths. The build
|
|
5
|
+
flattens every extension .so into jniLibs/<abi>/ as ``libpy.<dotted>.so`` and
|
|
6
|
+
records the mapping in ``ext_manifest.json``; this finder resolves imports
|
|
7
|
+
through that manifest with a stock ExtensionFileLoader.
|
|
8
|
+
|
|
9
|
+
See docs/design/platforms/android/05-bootstrap-android.md, "Extension-module
|
|
10
|
+
finder".
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from importlib.machinery import ExtensionFileLoader, ModuleSpec
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class KivyforgeExtensionFinder:
|
|
20
|
+
def __init__(self, mapping, native_dir):
|
|
21
|
+
self._mapping = mapping
|
|
22
|
+
self._native_dir = native_dir
|
|
23
|
+
|
|
24
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
25
|
+
filename = self._mapping.get(fullname)
|
|
26
|
+
if filename is None:
|
|
27
|
+
return None
|
|
28
|
+
location = os.path.join(self._native_dir, filename)
|
|
29
|
+
if not os.path.exists(location):
|
|
30
|
+
raise ImportError(
|
|
31
|
+
f"kivyforge: manifest maps {fullname!r} to {filename!r}, "
|
|
32
|
+
f"but it is not present in {self._native_dir!r} — "
|
|
33
|
+
"the APK native-library extraction did not deliver it"
|
|
34
|
+
)
|
|
35
|
+
loader = ExtensionFileLoader(fullname, location)
|
|
36
|
+
spec = ModuleSpec(fullname, loader, origin=location)
|
|
37
|
+
# Submodule extensions (e.g. jnius.jnius) need is_package=False, which
|
|
38
|
+
# ModuleSpec already defaults to for ExtensionFileLoader.
|
|
39
|
+
return spec
|
|
40
|
+
|
|
41
|
+
def __repr__(self):
|
|
42
|
+
return (
|
|
43
|
+
f"<KivyforgeExtensionFinder {len(self._mapping)} modules "
|
|
44
|
+
f"at {self._native_dir!r}>"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def install(manifest_path, native_dir):
|
|
49
|
+
with open(manifest_path, encoding="utf-8") as f:
|
|
50
|
+
mapping = json.load(f)
|
|
51
|
+
finder = KivyforgeExtensionFinder(mapping, native_dir)
|
|
52
|
+
sys.meta_path.insert(0, finder)
|
|
53
|
+
print(f"kivyforge bootstrap: finder installed ({len(mapping)} extensions)")
|
|
54
|
+
return finder
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""kivyforge contract self-test (android/05 §contract smoke-test hook).
|
|
2
|
+
|
|
3
|
+
Inert in a normal launch: the native launcher imports and runs this only when
|
|
4
|
+
KIVYFORGE_SELFTEST is set (the instrumented KivyforgeContractTest sets it via
|
|
5
|
+
an intent extra the Activity forwards to the environment). It exercises the two
|
|
6
|
+
novel, load-bearing runtime mechanisms on-device — the flattened-extension
|
|
7
|
+
sys.meta_path finder and the pyjnius invoke0 glue — and writes a result file
|
|
8
|
+
the instrumentation polls.
|
|
9
|
+
|
|
10
|
+
Adds no attack surface or startup cost to a shipped app: it never runs unless
|
|
11
|
+
the test flag is present.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import traceback
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _result_path() -> str:
|
|
19
|
+
private = os.environ.get("ANDROID_PRIVATE", ".")
|
|
20
|
+
return os.path.join(private, "kivyforge_selftest.txt")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run() -> bool:
|
|
24
|
+
lines: list[str] = []
|
|
25
|
+
ok = True
|
|
26
|
+
|
|
27
|
+
# 1. A stdlib lib-dynload extension AND a wheel extension, both resolved
|
|
28
|
+
# through the kivyforge finder (flattened .so + manifest).
|
|
29
|
+
try:
|
|
30
|
+
import _ssl # noqa: F401 (stdlib, DT_NEEDEDs libssl_python.so)
|
|
31
|
+
|
|
32
|
+
import jnius # noqa: F401 (wheel extension: jnius.jnius)
|
|
33
|
+
|
|
34
|
+
lines.append("EXT_OK")
|
|
35
|
+
except Exception:
|
|
36
|
+
ok = False
|
|
37
|
+
lines.append("EXT_FAIL")
|
|
38
|
+
lines.append(traceback.format_exc())
|
|
39
|
+
|
|
40
|
+
# 2. pyjnius invoke0 round-trip: a Python-implemented Java interface driven
|
|
41
|
+
# from Java (the NativeInvocationHandler matched pair).
|
|
42
|
+
try:
|
|
43
|
+
from jnius import PythonJavaClass, autoclass, java_method
|
|
44
|
+
|
|
45
|
+
class _Cmp(PythonJavaClass):
|
|
46
|
+
__javainterfaces__ = ["java/util/Comparator"]
|
|
47
|
+
__javacontext__ = "app"
|
|
48
|
+
|
|
49
|
+
@java_method("(Ljava/lang/Object;Ljava/lang/Object;)I")
|
|
50
|
+
def compare(self, a, b):
|
|
51
|
+
sa, sb = str(a), str(b)
|
|
52
|
+
return -1 if sa < sb else (1 if sa > sb else 0)
|
|
53
|
+
|
|
54
|
+
ArrayList = autoclass("java.util.ArrayList")
|
|
55
|
+
Collections = autoclass("java.util.Collections")
|
|
56
|
+
lst = ArrayList()
|
|
57
|
+
for item in ("cherry", "apple", "banana"):
|
|
58
|
+
lst.add(item)
|
|
59
|
+
Collections.sort(lst, _Cmp())
|
|
60
|
+
ordered = [lst.get(i) for i in range(lst.size())]
|
|
61
|
+
assert ordered == ["apple", "banana", "cherry"], ordered
|
|
62
|
+
lines.append("PROXY_OK")
|
|
63
|
+
except Exception:
|
|
64
|
+
ok = False
|
|
65
|
+
lines.append("PROXY_FAIL")
|
|
66
|
+
lines.append(traceback.format_exc())
|
|
67
|
+
|
|
68
|
+
# 3. Kivy 3's Android bootstrap contract: Kivy names no activity class of
|
|
69
|
+
# its own, it imports _kivy_bootstrap and pulls the Activity from there.
|
|
70
|
+
# Checked on-device because this is kivyforge's side of an interface
|
|
71
|
+
# another project relies on, and the failure it guards against — a
|
|
72
|
+
# bundle that starts fine and dies when Kivy first needs the Activity —
|
|
73
|
+
# is invisible to a unit test of the staging code.
|
|
74
|
+
try:
|
|
75
|
+
import _kivy_bootstrap
|
|
76
|
+
|
|
77
|
+
activity = _kivy_bootstrap.get_activity()
|
|
78
|
+
assert activity is not None, "no Activity while the Activity is running"
|
|
79
|
+
# Prove it is the live Activity rather than some retained husk.
|
|
80
|
+
assert activity.getPackageName(), "Activity did not answer as a Context"
|
|
81
|
+
lines.append("KIVY_CONTRACT_OK")
|
|
82
|
+
except Exception:
|
|
83
|
+
ok = False
|
|
84
|
+
lines.append("KIVY_CONTRACT_FAIL")
|
|
85
|
+
lines.append(traceback.format_exc())
|
|
86
|
+
|
|
87
|
+
if ok:
|
|
88
|
+
lines.append("SELFTEST_ALL_OK")
|
|
89
|
+
lines.append("SELFTEST_DONE")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
with open(_result_path(), "w", encoding="utf-8") as f:
|
|
93
|
+
f.write("\n".join(lines) + "\n")
|
|
94
|
+
except Exception:
|
|
95
|
+
traceback.print_exc()
|
|
96
|
+
print("kivyforge selftest:", " ".join(lines[:1] + ["…"]), flush=True)
|
|
97
|
+
return ok
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.10)
|
|
2
|
+
project(kivyforge_launcher)
|
|
3
|
+
|
|
4
|
+
# The kivyforge native launcher: exports SDL_main, initializes CPython.
|
|
5
|
+
# Headers + libpython come from the python.org Android runtime prefix.
|
|
6
|
+
#
|
|
7
|
+
# Multi-ABI: AGP invokes CMake once per ABI; PYTHON_PREFIX_DIR arrives with a
|
|
8
|
+
# literal {{triplet}} placeholder that resolves to this ABI's target triplet
|
|
9
|
+
# (aarch64-linux-android / x86_64-linux-android) — the same mechanism the
|
|
10
|
+
# python.org testbed uses, so each ABI's launcher links its own libpython.
|
|
11
|
+
string(
|
|
12
|
+
REPLACE {{triplet}} ${CMAKE_LIBRARY_ARCHITECTURE}
|
|
13
|
+
PYTHON_PREFIX_DIR ${PYTHON_PREFIX_DIR}
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
include_directories(${PYTHON_PREFIX_DIR}/include/python${PYTHON_VERSION})
|
|
17
|
+
link_directories(${PYTHON_PREFIX_DIR}/lib)
|
|
18
|
+
|
|
19
|
+
add_library(main SHARED main.c)
|
|
20
|
+
target_link_libraries(main log python${PYTHON_VERSION})
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/* Kivyforge native launcher (libmain.so), from the Phase-0 prototype.
|
|
2
|
+
*
|
|
3
|
+
* Two callers, one sequence:
|
|
4
|
+
* - SDL calls SDL_main() on its own thread after SDLActivity has loaded the
|
|
5
|
+
* SDL family + libpython + this library. By that point PythonActivity has
|
|
6
|
+
* unpacked the bundle and set the environment contract.
|
|
7
|
+
* - PythonService calls nativeStart() from its own background thread, in its
|
|
8
|
+
* own process, after doing the same unpack + environment setup. No SDL is
|
|
9
|
+
* loaded there; nothing below needs it.
|
|
10
|
+
*
|
|
11
|
+
* This implements the 05-bootstrap-android.md launch sequence steps 4-6:
|
|
12
|
+
* PyConfig with site_import deferred and explicit module search paths,
|
|
13
|
+
* finder installation from the bundle manifest, site + addsitedir, then
|
|
14
|
+
* the entry-point import. Everything is read from the environment, so these
|
|
15
|
+
* sources carry no project-specific values.
|
|
16
|
+
*/
|
|
17
|
+
#include <android/log.h>
|
|
18
|
+
#include <jni.h>
|
|
19
|
+
#include <Python.h>
|
|
20
|
+
#include <stdio.h>
|
|
21
|
+
#include <stdlib.h>
|
|
22
|
+
#include <string.h>
|
|
23
|
+
|
|
24
|
+
#define TAG "kivyforge"
|
|
25
|
+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
|
|
26
|
+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
|
27
|
+
|
|
28
|
+
static int fail_status(const char *where, PyStatus status) {
|
|
29
|
+
LOGE("%s failed: %s", where, status.err_msg ? status.err_msg : "(no message)");
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Initialize CPython from the unpacked bundle and import the entry point.
|
|
34
|
+
* Returns 0 on success. Shared by SDL_main and the service entry. */
|
|
35
|
+
static int kf_start_python(void) {
|
|
36
|
+
const char *bundle = getenv("KF_BUNDLE");
|
|
37
|
+
const char *native_dir = getenv("KF_NATIVE_DIR");
|
|
38
|
+
if (!bundle || !native_dir) {
|
|
39
|
+
LOGE("KF_BUNDLE / KF_NATIVE_DIR not set");
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
LOGI("launcher: bundle=%s native=%s", bundle, native_dir);
|
|
43
|
+
|
|
44
|
+
char stdlib_path[1024], bootstrap_path[1024], app_path[1024], sp_path[1024];
|
|
45
|
+
snprintf(stdlib_path, sizeof stdlib_path, "%s/stdlib", bundle);
|
|
46
|
+
snprintf(bootstrap_path, sizeof bootstrap_path, "%s/bootstrap", bundle);
|
|
47
|
+
snprintf(app_path, sizeof app_path, "%s/app", bundle);
|
|
48
|
+
snprintf(sp_path, sizeof sp_path, "%s/site-packages", bundle);
|
|
49
|
+
|
|
50
|
+
PyPreConfig pre;
|
|
51
|
+
PyPreConfig_InitPythonConfig(&pre);
|
|
52
|
+
pre.utf8_mode = 1;
|
|
53
|
+
PyStatus status = Py_PreInitialize(&pre);
|
|
54
|
+
if (PyStatus_Exception(status)) return fail_status("Py_PreInitialize", status);
|
|
55
|
+
|
|
56
|
+
PyConfig config;
|
|
57
|
+
PyConfig_InitPythonConfig(&config);
|
|
58
|
+
config.buffered_stdio = 0;
|
|
59
|
+
config.install_signal_handlers = 1;
|
|
60
|
+
config.site_import = 0; /* deferred: finder first, then site */
|
|
61
|
+
/* No use_system_logger field on the 3.14 Android build: CPython redirects
|
|
62
|
+
* the Python-level sys.stdout/stderr to logcat itself (per the runtime
|
|
63
|
+
* testbed). Finding for the docs: this is automatic on Android. */
|
|
64
|
+
/* Explicit search paths: bypass getpath landmark discovery entirely.
|
|
65
|
+
* The kivyforge bundle is its own layout, not a prefix. */
|
|
66
|
+
config.module_search_paths_set = 1;
|
|
67
|
+
wchar_t *wpaths[3];
|
|
68
|
+
const char *paths[3] = { stdlib_path, bootstrap_path, app_path };
|
|
69
|
+
for (int i = 0; i < 3; i++) {
|
|
70
|
+
wpaths[i] = Py_DecodeLocale(paths[i], NULL);
|
|
71
|
+
status = PyWideStringList_Append(&config.module_search_paths, wpaths[i]);
|
|
72
|
+
if (PyStatus_Exception(status))
|
|
73
|
+
return fail_status("module_search_paths", status);
|
|
74
|
+
}
|
|
75
|
+
status = PyConfig_SetBytesString(&config, &config.home, bundle);
|
|
76
|
+
if (PyStatus_Exception(status)) return fail_status("home", status);
|
|
77
|
+
status = PyConfig_SetBytesString(&config, &config.prefix, bundle);
|
|
78
|
+
if (PyStatus_Exception(status)) return fail_status("prefix", status);
|
|
79
|
+
status = PyConfig_SetBytesString(&config, &config.exec_prefix, bundle);
|
|
80
|
+
if (PyStatus_Exception(status)) return fail_status("exec_prefix", status);
|
|
81
|
+
status = PyConfig_SetBytesString(&config, &config.program_name, "kivyforge-app");
|
|
82
|
+
if (PyStatus_Exception(status)) return fail_status("program_name", status);
|
|
83
|
+
|
|
84
|
+
status = Py_InitializeFromConfig(&config);
|
|
85
|
+
PyConfig_Clear(&config);
|
|
86
|
+
if (PyStatus_Exception(status))
|
|
87
|
+
return fail_status("Py_InitializeFromConfig", status);
|
|
88
|
+
LOGI("launcher: interpreter up");
|
|
89
|
+
|
|
90
|
+
/* Step 5: finder, then site, then pip-deps site dir, then the entry point
|
|
91
|
+
* named by KF_ENTRY_POINT. When KIVYFORGE_SELFTEST is set (the instrumented
|
|
92
|
+
* contract test's intent extra, forwarded to the env by PythonActivity),
|
|
93
|
+
* run the inert self-test hook instead of the app entry point.
|
|
94
|
+
*
|
|
95
|
+
* KF_SERVICE_MARKER, when set, names a file the interpreter stamps once it
|
|
96
|
+
* is up and stamps again if the entry-point import raises — the signal the
|
|
97
|
+
* generated service contract test polls (a service entry point normally
|
|
98
|
+
* never returns, so there is nothing else to wait for). */
|
|
99
|
+
const char *selftest = getenv("KIVYFORGE_SELFTEST");
|
|
100
|
+
char code[4096];
|
|
101
|
+
snprintf(code, sizeof code,
|
|
102
|
+
"import _kivyforge_bootstrap\n"
|
|
103
|
+
"_kivyforge_bootstrap.install(%s'%s/ext_manifest.json', %s'%s')\n"
|
|
104
|
+
"import site\n"
|
|
105
|
+
"site.main()\n"
|
|
106
|
+
"site.addsitedir(%s'%s')\n"
|
|
107
|
+
"import importlib, os, traceback\n"
|
|
108
|
+
"_marker = os.environ.get('KF_SERVICE_MARKER')\n"
|
|
109
|
+
"if _marker:\n"
|
|
110
|
+
" with open(_marker, 'w') as _f:\n"
|
|
111
|
+
" _f.write('SERVICE_READY\\n')\n"
|
|
112
|
+
"try:\n"
|
|
113
|
+
" if %d:\n"
|
|
114
|
+
" import _kivyforge_selftest\n"
|
|
115
|
+
" _kivyforge_selftest.run()\n"
|
|
116
|
+
" else:\n"
|
|
117
|
+
" importlib.import_module(os.environ.get('KF_ENTRY_POINT') or 'main')\n"
|
|
118
|
+
"except Exception:\n"
|
|
119
|
+
" traceback.print_exc()\n"
|
|
120
|
+
" if _marker:\n"
|
|
121
|
+
" with open(_marker, 'a') as _f:\n"
|
|
122
|
+
" _f.write('SERVICE_ENTRY_FAILED\\n')\n"
|
|
123
|
+
" raise SystemExit(1)\n",
|
|
124
|
+
"r", bootstrap_path, "r", native_dir, "r", sp_path,
|
|
125
|
+
(selftest && selftest[0] == '1') ? 1 : 0);
|
|
126
|
+
int rc = PyRun_SimpleString(code);
|
|
127
|
+
LOGI("launcher: entry-point import returned %d", rc);
|
|
128
|
+
|
|
129
|
+
/* Keep the process alive briefly so instrumentation/logcat settle, then
|
|
130
|
+
* finalize. A real app never reaches here while Kivy runs its loop. */
|
|
131
|
+
Py_Finalize();
|
|
132
|
+
return rc == 0 ? 0 : 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* SDL's Java glue dlopens libmain.so and dlsyms "SDL_main" — the symbol must
|
|
136
|
+
* be exported regardless of compiler visibility defaults. */
|
|
137
|
+
__attribute__((visibility("default")))
|
|
138
|
+
int SDL_main(int argc, char *argv[]) {
|
|
139
|
+
return kf_start_python();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* org.kivy.android.PythonService.nativeStart(): the same sequence for a
|
|
143
|
+
* generated service's process. Resolved by JNI name, so it must be exported. */
|
|
144
|
+
__attribute__((visibility("default")))
|
|
145
|
+
JNIEXPORT jint JNICALL
|
|
146
|
+
Java_org_kivy_android_PythonService_nativeStart(JNIEnv *env, jobject thiz) {
|
|
147
|
+
(void)env;
|
|
148
|
+
(void)thiz;
|
|
149
|
+
return (jint)kf_start_python();
|
|
150
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package org.jnius;
|
|
2
|
+
import java.lang.reflect.InvocationHandler;
|
|
3
|
+
import java.lang.reflect.Method;
|
|
4
|
+
|
|
5
|
+
// SPIKE item 1 / kivyforge bootstrap-template simulation.
|
|
6
|
+
//
|
|
7
|
+
// This file is delivered APP-SIDE (via `android.add_src` in buildozer.spec), NOT
|
|
8
|
+
// from the pyjnius wheel. It mirrors how kivyforge will ship
|
|
9
|
+
// NativeInvocationHandler.java as a generated bootstrap template alongside
|
|
10
|
+
// MainActivity.java / PythonActivity.java, compiled + dex'd by the app's Gradle.
|
|
11
|
+
//
|
|
12
|
+
// MATCHED-PAIR COUPLING: the `native Object invoke0(...)` signature below is bound
|
|
13
|
+
// at runtime (RegisterNatives) by the pyjnius wheel. This class and the wheel's
|
|
14
|
+
// invoke0 native-method contract MUST be updated together -- nothing enforces this
|
|
15
|
+
// now that the glue no longer ships in the wheel. Keep in sync with
|
|
16
|
+
// jnius/jnius_proxy.pxi in the pyjnius source.
|
|
17
|
+
public class NativeInvocationHandler implements InvocationHandler {
|
|
18
|
+
static boolean DEBUG = false;
|
|
19
|
+
private long ptr;
|
|
20
|
+
|
|
21
|
+
public NativeInvocationHandler(long ptr) {
|
|
22
|
+
this.ptr = ptr;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public Object invoke(Object proxy, Method method, Object[] args) {
|
|
26
|
+
if ( DEBUG ) {
|
|
27
|
+
String message = "+ java:invoke(<proxy>, " + method + ", " + args;
|
|
28
|
+
System.out.print(message);
|
|
29
|
+
System.out.println(")");
|
|
30
|
+
System.out.flush();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Object ret = invoke0(proxy, method, args);
|
|
34
|
+
|
|
35
|
+
if ( DEBUG ) {
|
|
36
|
+
System.out.print("+ java:invoke returned: ");
|
|
37
|
+
System.out.println(ret);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ret;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public long getPythonObjectPointer() {
|
|
44
|
+
return ptr;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
native Object invoke0(Object proxy, Method method, Object[] args);
|
|
48
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package org.kivy.android;
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle;
|
|
4
|
+
import android.system.Os;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import org.libsdl.app.SDLActivity;
|
|
8
|
+
|
|
9
|
+
import java.io.File;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Kivyforge PythonActivity.
|
|
13
|
+
*
|
|
14
|
+
* Generation-agnostic: the SDL family named in getLibraries() below is
|
|
15
|
+
* substituted at render time (SDL2 for Kivy 2.x, SDL3 for Kivy 3.x), and the
|
|
16
|
+
* matching org/libsdl/app glue is vendored alongside it.
|
|
17
|
+
*
|
|
18
|
+
* Load model (docs/design/platforms/android/05-bootstrap-android.md):
|
|
19
|
+
* - SDLActivity static init loads the SDL family -> libpython -> libmain
|
|
20
|
+
* (getLibraries order below), so the SDL JNIEnv getter is resident before
|
|
21
|
+
* any Python runs.
|
|
22
|
+
* - The asset bundle (_python_bundle) is unpacked to app-private storage
|
|
23
|
+
* before super.onCreate(), version-stamped so a second launch skips it.
|
|
24
|
+
* - The environment contract is set before any native code runs.
|
|
25
|
+
* - libmain's SDL_main initializes CPython (site_import deferred), installs
|
|
26
|
+
* the extension-module finder from the bundle manifest, then imports the
|
|
27
|
+
* entry-point module named by KF_ENTRY_POINT.
|
|
28
|
+
*/
|
|
29
|
+
public class PythonActivity extends SDLActivity {
|
|
30
|
+
private static final String TAG = "kivyforge";
|
|
31
|
+
|
|
32
|
+
// [tool.kivy].entry_point, substituted at render time. The launcher reads
|
|
33
|
+
// it from the environment, so the native sources stay project-independent.
|
|
34
|
+
private static final String ENTRY_POINT = "main";
|
|
35
|
+
|
|
36
|
+
// Kivy-compatibility: kivy.app / kivy.metrics reach the activity through
|
|
37
|
+
// autoclass('org.kivy.android.PythonActivity').mActivity, and
|
|
38
|
+
// org.renpy.android.Hardware.getDPI() reads it too. Preserving this static
|
|
39
|
+
// is what keeps unmodified Kivy-Android code working (android/05
|
|
40
|
+
// §namespace preservation).
|
|
41
|
+
public static PythonActivity mActivity = null;
|
|
42
|
+
|
|
43
|
+
@Override
|
|
44
|
+
protected String getMainFunction() {
|
|
45
|
+
return "SDL_main";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Override
|
|
49
|
+
protected String[] getLibraries() {
|
|
50
|
+
return new String[] {
|
|
51
|
+
"SDL2", "SDL2_image", "SDL2_mixer", "SDL2_ttf",
|
|
52
|
+
"python3.14",
|
|
53
|
+
"main",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Override
|
|
58
|
+
protected void onCreate(Bundle savedInstanceState) {
|
|
59
|
+
mActivity = this;
|
|
60
|
+
try {
|
|
61
|
+
// Contract smoke-test hook (android/05): the instrumented test
|
|
62
|
+
// passes this extra so the launcher runs the inert self-test.
|
|
63
|
+
if (getIntent() != null
|
|
64
|
+
&& getIntent().getBooleanExtra("kivyforge_selftest", false)) {
|
|
65
|
+
Os.setenv("KIVYFORGE_SELFTEST", "1", true);
|
|
66
|
+
}
|
|
67
|
+
File bundleDir = PythonBundle.unpack(this);
|
|
68
|
+
PythonBundle.setEnvironment(this, bundleDir, ENTRY_POINT);
|
|
69
|
+
} catch (Exception e) {
|
|
70
|
+
Log.e(TAG, "bundle unpack failed", e);
|
|
71
|
+
throw new RuntimeException(e);
|
|
72
|
+
}
|
|
73
|
+
super.onCreate(savedInstanceState);
|
|
74
|
+
}
|
|
75
|
+
}
|