Skip to content

Commit e38bcad

Browse files
authored
Merge branch 'develop' into less-output-during-boot
2 parents 47c97f0 + 6b66944 commit e38bcad

File tree

33 files changed

+510
-674
lines changed

33 files changed

+510
-674
lines changed

.github/workflows/push.yml

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ jobs:
1919
runs-on: ubuntu-latest
2020
steps:
2121
- name: Checkout python-for-android
22-
uses: actions/checkout@v4
22+
uses: actions/checkout@v5
2323
- name: Set up Python 3.x
24-
uses: actions/setup-python@v5
24+
uses: actions/setup-python@v6
2525
with:
2626
python-version: 3.x
2727
- name: Run flake8
@@ -35,16 +35,18 @@ jobs:
3535
needs: flake8
3636
runs-on: ${{ matrix.os }}
3737
strategy:
38+
fail-fast: false
3839
matrix:
39-
python-version: ['3.8', '3.9', '3.10', '3.11']
40-
os: [ubuntu-latest, macOs-latest]
40+
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
41+
os: [ubuntu-latest, macos-latest]
4142
steps:
4243
- name: Checkout python-for-android
43-
uses: actions/checkout@v4
44+
uses: actions/checkout@v5
4445
- name: Set up Python ${{ matrix.python-version }}
45-
uses: actions/setup-python@v5
46+
uses: actions/setup-python@v6
4647
with:
4748
python-version: ${{ matrix.python-version }}
49+
allow-prereleases: true
4850
- name: Tox tests
4951
run: |
5052
python -m pip install --upgrade pip
@@ -78,7 +80,7 @@ jobs:
7880
target: testapps-qt
7981
steps:
8082
- name: Checkout python-for-android
81-
uses: actions/checkout@v4
83+
uses: actions/checkout@v5
8284
- name: Build python-for-android docker image
8385
run: |
8486
docker build --tag=kivy/python-for-android .
@@ -114,9 +116,9 @@ jobs:
114116
continue-on-error: true
115117
strategy:
116118
matrix:
117-
# macos-latest (ATM macos-14) runs on Apple Silicon,
118-
# macos-13 runs on Intel
119-
runs_on: ['macos-latest', 'macos-13']
119+
# macos-latest (ATM macos-15) runs on Apple Silicon,
120+
# macos-15-intel runs on Intel
121+
runs_on: ['macos-latest', 'macos-15-intel']
120122
bootstrap:
121123
- name: sdl2
122124
target: testapps-with-numpy
@@ -129,14 +131,14 @@ jobs:
129131
ANDROID_NDK_HOME: ${HOME}/.android/android-ndk
130132
steps:
131133
- name: Checkout python-for-android
132-
uses: actions/checkout@v4
134+
uses: actions/checkout@v5
133135
- name: Set up Python 3.x
134-
uses: actions/setup-python@v5
136+
uses: actions/setup-python@v6
135137
with:
136138
python-version: 3.x
137139
- name: Install python-for-android
138140
run: |
139-
python3 -m pip install -e .
141+
python3 -m pip install --editable .
140142
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
141143
run: |
142144
python3 pythonforandroid/prerequisites.py
@@ -162,6 +164,26 @@ jobs:
162164
name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts
163165
path: dist
164166

167+
test_on_emulator:
168+
name: Run App on Emulator
169+
needs: ubuntu_build
170+
runs-on: ubuntu-latest
171+
172+
steps:
173+
- uses: actions/checkout@v5
174+
- name: Download Artifacts
175+
uses: actions/download-artifact@v5
176+
with:
177+
name: ubuntu-latest-sdl2-artifacts
178+
path: dist/
179+
180+
- name: Setup and start Android Emulator
181+
uses: reactivecircus/android-emulator-runner@v2
182+
with:
183+
api-level: 30
184+
arch: x86_64
185+
script: ci/run_emulator_tests.sh
186+
165187
ubuntu_rebuild_updated_recipes:
166188
name: Test updated recipes for arch ${{ matrix.android_arch }} [ ubuntu-latest ]
167189
needs: [flake8]
@@ -174,7 +196,7 @@ jobs:
174196
REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }}
175197
steps:
176198
- name: Checkout python-for-android (all-history)
177-
uses: actions/checkout@v4
199+
uses: actions/checkout@v5
178200
with:
179201
fetch-depth: 0
180202
# helps with GitHub runner getting out of space
@@ -201,9 +223,9 @@ jobs:
201223
strategy:
202224
matrix:
203225
android_arch: ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
204-
# macos-latest (ATM macos-14) runs on Apple Silicon,
205-
# macos-13 runs on Intel
206-
runs_on: ['macos-latest', 'macos-13']
226+
# macos-latest (ATM macos-15) runs on Apple Silicon,
227+
# macos-15-intel runs on Intel
228+
runs_on: ['macos-latest', 'macos-15-intel']
207229
env:
208230
ANDROID_HOME: ${HOME}/.android
209231
ANDROID_SDK_ROOT: ${HOME}/.android/android-sdk
@@ -212,16 +234,16 @@ jobs:
212234
REBUILD_UPDATED_RECIPES_EXTRA_ARGS: --arch=${{ matrix.android_arch }}
213235
steps:
214236
- name: Checkout python-for-android (all-history)
215-
uses: actions/checkout@v4
237+
uses: actions/checkout@v5
216238
with:
217239
fetch-depth: 0
218240
- name: Set up Python 3.x
219-
uses: actions/setup-python@v5
241+
uses: actions/setup-python@v6
220242
with:
221243
python-version: 3.x
222244
- name: Install python-for-android
223245
run: |
224-
python3 -m pip install -e .
246+
python3 -m pip install --editable .
225247
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
226248
run: |
227249
python3 pythonforandroid/prerequisites.py
@@ -244,7 +266,7 @@ jobs:
244266
documentation:
245267
runs-on: ubuntu-latest
246268
steps:
247-
- uses: actions/checkout@v4
269+
- uses: actions/checkout@v5
248270
- name: Requirements
249271
run: |
250272
python -m pip install --upgrade pip

.github/workflows/pypi-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
twine check dist/*
2020
- name: Publish package
2121
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
22-
uses: pypa/gh-action-pypi-publish@v1.4.2
22+
uses: pypa/gh-action-pypi-publish@v1.13.0
2323
with:
2424
user: __token__
2525
password: ${{ secrets.pypi_password }}

ci/run_emulator_tests.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
set -euxo pipefail
3+
4+
# Find the built APK file
5+
APK_FILE=$(find dist -name "*.apk" -print -quit)
6+
7+
if [ -z "$APK_FILE" ]; then
8+
echo "Error: No APK file found in dist/"
9+
exit 1
10+
fi
11+
12+
echo "Installing $APK_FILE..."
13+
adb install "$APK_FILE"
14+
15+
# Extract package and activity names
16+
AAPT2_PATH=$(find ${ANDROID_HOME}/build-tools/ -name aapt2 | sort -r | head -n 1)
17+
APP_PACKAGE=$(${AAPT2_PATH} dump badging "${APK_FILE}" | awk -F"'" '/package: name=/{print $2}')
18+
APP_ACTIVITY=$(${AAPT2_PATH} dump badging "${APK_FILE}" | awk -F"'" '/launchable-activity/ {print $2}')
19+
20+
echo "Launching $APP_PACKAGE/$APP_ACTIVITY..."
21+
adb shell am start -n "$APP_PACKAGE/$APP_ACTIVITY" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER
22+
23+
# Poll for test completion with timeout
24+
MAX_WAIT=300
25+
POLL_INTERVAL=5
26+
elapsed=0
27+
28+
echo "Waiting for tests to complete (max ${MAX_WAIT}s)..."
29+
30+
while [ $elapsed -lt $MAX_WAIT ]; do
31+
# Dump current logs
32+
adb logcat -d -s python:I *:S > app_logs.txt
33+
34+
# Check if all success patterns are present
35+
if grep --extended-regexp --quiet "I python[ ]+: Initialized python" app_logs.txt && \
36+
grep --extended-regexp --quiet "I python[ ]+: Ran 14 tests in" app_logs.txt && \
37+
grep --extended-regexp --quiet "I python[ ]+: OK" app_logs.txt; then
38+
echo "✅ SUCCESS: App launched and all unit tests passed in ${elapsed}s."
39+
exit 0
40+
fi
41+
42+
# Check for early failure indicators
43+
if grep --extended-regexp --quiet "I python[ ]+: FAILED" app_logs.txt; then
44+
echo "❌ FAILURE: Tests failed after ${elapsed}s."
45+
echo "--- Full Logs ---"
46+
cat app_logs.txt
47+
echo "-----------------"
48+
exit 1
49+
fi
50+
51+
sleep $POLL_INTERVAL
52+
elapsed=$((elapsed + POLL_INTERVAL))
53+
echo "Still waiting... (${elapsed}s elapsed)"
54+
done
55+
56+
echo "❌ TIMEOUT: Tests did not complete within ${MAX_WAIT}s."
57+
echo "--- Full Logs ---"
58+
cat app_logs.txt
59+
echo "-----------------"
60+
exit 1

pythonforandroid/archs.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,7 @@ def get_env(self, with_flags_in_cc=True):
132132
env['CPPFLAGS'] = ' '.join(self.common_cppflags).format(
133133
ctx=self.ctx,
134134
command_prefix=self.command_prefix,
135-
python_includes=join(
136-
self.ctx.get_python_install_dir(self.arch),
137-
'include/python{}'.format(self.ctx.python_recipe.version[0:3]),
138-
),
135+
python_includes=join(self.ctx.python_recipe.get_build_dir(self.arch), 'Include')
139136
)
140137

141138
# LDFLAGS: Link the extra global link paths first before anything else

pythonforandroid/bootstraps/common/build/jni/application/src/start.c

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#define ENTRYPOINT_MAXLEN 128
3232
#define LOG(n, x) __android_log_write(ANDROID_LOG_INFO, (n), (x))
3333
#define LOGP(x) LOG("python", (x))
34+
#define P4A_MIN_VER 11
3435

3536
static PyObject *androidembed_log(PyObject *self, PyObject *args) {
3637
char *logstr = NULL;
@@ -154,11 +155,6 @@ int main(int argc, char *argv[]) {
154155
Py_NoSiteFlag=1;
155156
#endif
156157

157-
#if PY_MAJOR_VERSION < 3
158-
Py_SetProgramName("android_python");
159-
#else
160-
Py_SetProgramName(L"android_python");
161-
#endif
162158

163159
#if PY_MAJOR_VERSION >= 3
164160
/* our logging module for android
@@ -174,40 +170,80 @@ int main(int argc, char *argv[]) {
174170
char python_bundle_dir[256];
175171
snprintf(python_bundle_dir, 256,
176172
"%s/_python_bundle", getenv("ANDROID_UNPACK"));
177-
if (dir_exists(python_bundle_dir)) {
178-
LOGP("_python_bundle dir exists");
179-
snprintf(paths, 256,
180-
"%s/stdlib.zip:%s/modules",
181-
python_bundle_dir, python_bundle_dir);
182173

183-
LOGP("calculated paths to be...");
184-
LOGP(paths);
174+
#if PY_MAJOR_VERSION >= 3
185175

186-
#if PY_MAJOR_VERSION >= 3
187-
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
188-
Py_SetPath(wchar_paths);
176+
#if PY_MINOR_VERSION >= P4A_MIN_VER
177+
PyConfig config;
178+
PyConfig_InitPythonConfig(&config);
179+
config.program_name = L"android_python";
180+
#else
181+
Py_SetProgramName(L"android_python");
189182
#endif
190183

191-
LOGP("set wchar paths...");
184+
#else
185+
Py_SetProgramName("android_python");
186+
#endif
187+
188+
if (dir_exists(python_bundle_dir)) {
189+
LOGP("_python_bundle dir exists");
190+
191+
#if PY_MAJOR_VERSION >= 3
192+
#if PY_MINOR_VERSION >= P4A_MIN_VER
193+
194+
wchar_t wchar_zip_path[256];
195+
wchar_t wchar_modules_path[256];
196+
swprintf(wchar_zip_path, 256, L"%s/stdlib.zip", python_bundle_dir);
197+
swprintf(wchar_modules_path, 256, L"%s/modules", python_bundle_dir);
198+
199+
config.module_search_paths_set = 1;
200+
PyWideStringList_Append(&config.module_search_paths, wchar_zip_path);
201+
PyWideStringList_Append(&config.module_search_paths, wchar_modules_path);
202+
#else
203+
char paths[512];
204+
snprintf(paths, 512, "%s/stdlib.zip:%s/modules", python_bundle_dir, python_bundle_dir);
205+
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
206+
Py_SetPath(wchar_paths);
207+
#endif
208+
209+
#endif
210+
211+
LOGP("set wchar paths...");
192212
} else {
193213
LOGP("_python_bundle does not exist...this not looks good, all python"
194214
" recipes should have this folder, should we expect a crash soon?");
195215
}
196216

197-
Py_Initialize();
217+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= P4A_MIN_VER
218+
PyStatus status = Py_InitializeFromConfig(&config);
219+
if (PyStatus_Exception(status)) {
220+
LOGP("Python initialization failed:");
221+
LOGP(status.err_msg);
222+
}
223+
#else
224+
Py_Initialize();
225+
LOGP("Python initialized using legacy Py_Initialize().");
226+
#endif
227+
198228
LOGP("Initialized python");
199229

200-
/* ensure threads will work.
201-
*/
202-
LOGP("AND: Init threads");
203-
PyEval_InitThreads();
230+
/* < 3.9 requires explicit GIL initialization
231+
* 3.9+ PyEval_InitThreads() is deprecated and unnecessary
232+
*/
233+
#if PY_VERSION_HEX < 0x03090000
234+
LOGP("Initializing threads (required for Python < 3.9)");
235+
PyEval_InitThreads();
236+
#endif
204237

205238
#if PY_MAJOR_VERSION < 3
206239
initandroidembed();
207240
#endif
208241

209-
PyRun_SimpleString("import androidembed\nandroidembed.log('testing python "
210-
"print redirection')");
242+
PyRun_SimpleString(
243+
"import androidembed\n"
244+
"androidembed.log('testing python print redirection')"
245+
246+
);
211247

212248
/* inject our bootstrap code to redirect python stdin/stdout
213249
* replace sys.path with our path

0 commit comments

Comments
 (0)