Skip to content

Commit c8f25aa

Browse files
committed
python: update to 3.13
1 parent 2ebea90 commit c8f25aa

File tree

17 files changed

+289
-224
lines changed

17 files changed

+289
-224
lines changed

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

Lines changed: 36 additions & 25 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,6 +170,21 @@ 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"));
173+
174+
#if PY_MAJOR_VERSION >= 3
175+
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");
182+
#endif
183+
184+
#else
185+
Py_SetProgramName("android_python");
186+
#endif
187+
177188
if (dir_exists(python_bundle_dir)) {
178189
LOGP("_python_bundle dir exists");
179190
snprintf(paths, 256,
@@ -185,7 +196,14 @@ int main(int argc, char *argv[]) {
185196

186197
#if PY_MAJOR_VERSION >= 3
187198
wchar_t *wchar_paths = Py_DecodeLocale(paths, NULL);
199+
200+
// Py_SetPath is deprecated in python 3.13
201+
#if PY_MINOR_VERSION >= P4A_MIN_VER
202+
PyWideStringList_Append(&config.module_search_paths, wchar_paths);
203+
#else
188204
Py_SetPath(wchar_paths);
205+
#endif
206+
189207
#endif
190208

191209
LOGP("set wchar paths...");
@@ -194,13 +212,21 @@ int main(int argc, char *argv[]) {
194212
" recipes should have this folder, should we expect a crash soon?");
195213
}
196214

197-
Py_Initialize();
215+
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= P4A_MIN_VER
216+
Py_InitializeFromConfig(&config);
217+
#else
218+
Py_Initialize();
219+
#endif
220+
198221
LOGP("Initialized python");
199222

200-
/* ensure threads will work.
201-
*/
202-
LOGP("AND: Init threads");
203-
PyEval_InitThreads();
223+
/* < 3.9 requires explicit GIL initialization
224+
* 3.9+ PyEval_InitThreads() is deprecated and unnecessary
225+
*/
226+
#if PY_VERSION_HEX < 0x03090000
227+
LOGP("Initializing threads (required for Python < 3.9)");
228+
PyEval_InitThreads();
229+
#endif
204230

205231
#if PY_MAJOR_VERSION < 3
206232
initandroidembed();
@@ -325,21 +351,6 @@ int main(int argc, char *argv[]) {
325351

326352
LOGP("Python for android ended.");
327353

328-
/* Shut down: since regular shutdown causes issues sometimes
329-
(seems to be an incomplete shutdown breaking next launch)
330-
we'll use sys.exit(ret) to shutdown, since that one works.
331-
332-
Reference discussion:
333-
334-
https://github.com/kivy/kivy/pull/6107#issue-246120816
335-
*/
336-
char terminatecmd[256];
337-
snprintf(
338-
terminatecmd, sizeof(terminatecmd),
339-
"import sys; sys.exit(%d)\n", ret
340-
);
341-
PyRun_SimpleString(terminatecmd);
342-
343354
/* This should never actually be reached, but we'll leave the clean-up
344355
* here just to be safe.
345356
*/

pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,22 @@ protected static void addLibraryIfExists(ArrayList<String> libsList, String patt
3939
}
4040

4141
protected static ArrayList<String> getLibraries(File libsDir) {
42-
ArrayList<String> libsList = new ArrayList<String>();
43-
addLibraryIfExists(libsList, "sqlite3", libsDir);
44-
addLibraryIfExists(libsList, "ffi", libsDir);
45-
addLibraryIfExists(libsList, "png16", libsDir);
46-
addLibraryIfExists(libsList, "ssl.*", libsDir);
47-
addLibraryIfExists(libsList, "crypto.*", libsDir);
48-
addLibraryIfExists(libsList, "SDL2", libsDir);
49-
addLibraryIfExists(libsList, "SDL2_image", libsDir);
50-
addLibraryIfExists(libsList, "SDL2_mixer", libsDir);
51-
addLibraryIfExists(libsList, "SDL2_ttf", libsDir);
52-
addLibraryIfExists(libsList, "SDL3", libsDir);
53-
addLibraryIfExists(libsList, "SDL3_image", libsDir);
54-
addLibraryIfExists(libsList, "SDL3_mixer", libsDir);
55-
addLibraryIfExists(libsList, "SDL3_ttf", libsDir);
56-
libsList.add("python3.5m");
57-
libsList.add("python3.6m");
58-
libsList.add("python3.7m");
59-
libsList.add("python3.8");
60-
libsList.add("python3.9");
61-
libsList.add("python3.10");
62-
libsList.add("python3.11");
42+
ArrayList<String> libsList = new ArrayList<>();
43+
44+
String[] libNames = {
45+
"sqlite3", "ffi", "png16", "ssl.*", "crypto.*",
46+
"SDL2", "SDL2_image", "SDL2_mixer", "SDL2_ttf",
47+
"SDL3", "SDL3_image", "SDL3_mixer", "SDL3_ttf"
48+
};
49+
50+
for (String name : libNames) {
51+
addLibraryIfExists(libsList, name, libsDir);
52+
}
53+
54+
for (int v = 5; v <= 13; v++) {
55+
libsList.add("python3." + v + (v <= 7 ? "m" : ""));
56+
}
57+
6358
libsList.add("main");
6459
return libsList;
6560
}
@@ -79,7 +74,7 @@ public static void loadLibraries(File filesDir, File libsDir) {
7974
// load, and it has failed, give a more
8075
// general error
8176
Log.v(TAG, "Library loading error: " + e.getMessage());
82-
if (lib.startsWith("python3.11") && !foundPython) {
77+
if (lib.startsWith("python3.13") && !foundPython) {
8378
throw new RuntimeException("Could not load any libpythonXXX.so");
8479
} else if (lib.startsWith("python")) {
8580
continue;

pythonforandroid/recipe.py

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from urllib.request import urlretrieve
1313
from os import listdir, unlink, environ, curdir, walk
1414
from sys import stdout
15+
from multiprocessing import cpu_count
1516
import time
1617
try:
1718
from urlparse import urlparse
@@ -517,7 +518,7 @@ def unpack(self, arch):
517518
for entry in listdir(extraction_filename):
518519
# Previously we filtered out the .git folder, but during the build process for some recipes
519520
# (e.g. when version is parsed by `setuptools_scm`) that may be needed.
520-
shprint(sh.cp, '-Rv',
521+
shprint(sh.cp, '-R',
521522
join(extraction_filename, entry),
522523
directory_name)
523524
else:
@@ -830,6 +831,8 @@ def build_arch(self, arch, *extra_args):
830831
shprint(
831832
sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
832833
'V=1',
834+
"-j",
835+
str(cpu_count()),
833836
'NDK_DEBUG=' + ("1" if self.ctx.build_as_debuggable else "0"),
834837
'APP_PLATFORM=android-' + str(self.ctx.ndk_api),
835838
'APP_ABI=' + arch.arch,
@@ -878,6 +881,8 @@ class PythonRecipe(Recipe):
878881
hostpython_prerequisites = []
879882
'''List of hostpython packages required to build a recipe'''
880883

884+
_host_recipe = None
885+
881886
def __init__(self, *args, **kwargs):
882887
super().__init__(*args, **kwargs)
883888
if 'python3' not in self.depends:
@@ -890,6 +895,10 @@ def __init__(self, *args, **kwargs):
890895
depends = list(set(depends))
891896
self.depends = depends
892897

898+
def prebuild_arch(self, arch):
899+
self._host_recipe = Recipe.get_recipe("hostpython3", self.ctx)
900+
return super().prebuild_arch(arch)
901+
893902
def clean_build(self, arch=None):
894903
super().clean_build(arch=arch)
895904
name = self.folder_name
@@ -907,8 +916,7 @@ def clean_build(self, arch=None):
907916
def real_hostpython_location(self):
908917
host_name = 'host{}'.format(self.ctx.python_recipe.name)
909918
if host_name == 'hostpython3':
910-
python_recipe = Recipe.get_recipe(host_name, self.ctx)
911-
return python_recipe.python_exe
919+
return self._host_recipe.python_exe
912920
else:
913921
python_recipe = self.ctx.python_recipe
914922
return 'python{}'.format(python_recipe.version)
@@ -927,14 +935,44 @@ def folder_name(self):
927935
name = self.name
928936
return name
929937

938+
def patch_shebang(self, _file, original_bin):
939+
_file_des = open(_file, "r")
940+
941+
try:
942+
data = _file_des.readlines()
943+
except UnicodeDecodeError:
944+
return
945+
946+
if "#!" in (line := data[0]):
947+
if line.split("#!")[-1].strip() == original_bin:
948+
return
949+
950+
info(f"Fixing shebang for '{_file}'")
951+
data.pop(0)
952+
data.insert(0, "#!" + original_bin + "\n")
953+
_file_des.close()
954+
_file_des = open(_file, "w")
955+
_file_des.write("".join(data))
956+
_file_des.close()
957+
958+
def patch_shebangs(self, path, original_bin):
959+
# set correct shebang
960+
for file in listdir(path):
961+
_file = join(path, file)
962+
self.patch_shebang(_file, original_bin)
963+
930964
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
965+
if self._host_recipe is None:
966+
self._host_recipe = Recipe.get_recipe("hostpython3", self.ctx)
967+
931968
env = super().get_recipe_env(arch, with_flags_in_cc)
932-
env['PYTHONNOUSERSITE'] = '1'
933969
# Set the LANG, this isn't usually important but is a better default
934970
# as it occasionally matters how Python e.g. reads files
935971
env['LANG'] = "en_GB.UTF-8"
936972
# Binaries made by packages installed by pip
937-
env["PATH"] = join(self.hostpython_site_dir, "bin") + ":" + env["PATH"]
973+
env["PATH"] = self._host_recipe.site_bin + ":" + env["PATH"]
974+
host_env = self.get_hostrecipe_env()
975+
env['PYTHONPATH'] = host_env["PYTHONPATH"]
938976

939977
if not self.call_hostpython_via_targetpython:
940978
env['CFLAGS'] += ' -I{}'.format(
@@ -945,18 +983,6 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
945983
self.ctx.python_recipe.link_version,
946984
)
947985

948-
hppath = []
949-
hppath.append(join(dirname(self.hostpython_location), 'Lib'))
950-
hppath.append(join(hppath[0], 'site-packages'))
951-
builddir = join(dirname(self.hostpython_location), 'build')
952-
if exists(builddir):
953-
hppath += [join(builddir, d) for d in listdir(builddir)
954-
if isdir(join(builddir, d))]
955-
if len(hppath) > 0:
956-
if 'PYTHONPATH' in env:
957-
env['PYTHONPATH'] = ':'.join(hppath + [env['PYTHONPATH']])
958-
else:
959-
env['PYTHONPATH'] = ':'.join(hppath)
960986
return env
961987

962988
def should_build(self, arch):
@@ -993,25 +1019,36 @@ def install_python_package(self, arch, name=None, env=None, is_dir=True):
9931019
'--install-lib=.',
9941020
_env=hpenv, *self.setup_extra_args)
9951021

996-
# If asked, also install in the hostpython build dir
997-
if self.install_in_hostpython:
998-
self.install_hostpython_package(arch)
1022+
if isfile("setup.py"):
1023+
shprint(hostpython, 'setup.py', 'install', '-O2',
1024+
'--root={}'.format(self.ctx.get_python_install_dir(arch.arch)),
1025+
'--install-lib=.',
1026+
_env=hpenv, *self.setup_extra_args)
1027+
1028+
# If asked, also install in the hostpython build dir
1029+
if self.install_in_hostpython:
1030+
self.install_hostpython_package(arch)
1031+
else:
1032+
warning("`PythonRecipe.install_python_package` called without `setup.py` file!")
9991033

1000-
def get_hostrecipe_env(self, arch):
1034+
def get_hostrecipe_env(self):
10011035
env = environ.copy()
1002-
env['PYTHONPATH'] = self.hostpython_site_dir
1036+
_python_path = self._host_recipe.get_path_to_python()
1037+
env['PYTHONPATH'] = self._host_recipe.site_dir + ":" + join(
1038+
_python_path, "Modules") + ":" + glob.glob(join(_python_path, "build", "lib*"))[0]
10031039
return env
10041040

10051041
@property
10061042
def hostpython_site_dir(self):
10071043
return join(dirname(self.real_hostpython_location), 'Lib', 'site-packages')
10081044

10091045
def install_hostpython_package(self, arch):
1010-
env = self.get_hostrecipe_env(arch)
1046+
env = self.get_hostrecipe_env()
10111047
real_hostpython = sh.Command(self.real_hostpython_location)
10121048
shprint(real_hostpython, 'setup.py', 'install', '-O2',
10131049
'--root={}'.format(dirname(self.real_hostpython_location)),
10141050
'--install-lib=Lib/site-packages',
1051+
'--root={}'.format(self._host_recipe.site_root),
10151052
_env=env, *self.setup_extra_args)
10161053

10171054
@property
@@ -1029,15 +1066,17 @@ def install_hostpython_prerequisites(self, packages=None, force_upgrade=True):
10291066
pip_options = [
10301067
"install",
10311068
*packages,
1032-
"--target", self.hostpython_site_dir, "--python-version",
1069+
"--target", self._host_recipe.site_dir, "--python-version",
10331070
self.ctx.python_recipe.version,
10341071
# Don't use sources, instead wheels
10351072
"--only-binary=:all:",
10361073
]
10371074
if force_upgrade:
10381075
pip_options.append("--upgrade")
10391076
# Use system's pip
1040-
shprint(sh.pip, *pip_options)
1077+
pip_env = self.get_hostrecipe_env()
1078+
pip_env["HOME"] = "/tmp"
1079+
shprint(sh.Command(self.real_hostpython_location), "-m", "pip", *pip_options, _env=pip_env)
10411080

10421081
def restore_hostpython_prerequisites(self, packages):
10431082
_packages = []
@@ -1076,7 +1115,7 @@ def build_compiled_components(self, arch):
10761115
env['STRIP'], '{}', ';', _env=env)
10771116

10781117
def install_hostpython_package(self, arch):
1079-
env = self.get_hostrecipe_env(arch)
1118+
env = self.get_hostrecipe_env()
10801119
self.rebuild_compiled_components(arch, env)
10811120
super().install_hostpython_package(arch)
10821121

@@ -1231,7 +1270,7 @@ def get_recipe_env(self, arch, **kwargs):
12311270
return env
12321271

12331272
def get_wheel_platform_tag(self, arch):
1234-
return "android_" + {
1273+
return f"android_{self.ctx.ndk_api}_" + {
12351274
"armeabi-v7a": "arm",
12361275
"arm64-v8a": "aarch64",
12371276
"x86_64": "x86_64",
@@ -1268,6 +1307,9 @@ def build_arch(self, arch):
12681307
self.install_hostpython_prerequisites(
12691308
packages=["build[virtualenv]", "pip"] + self.hostpython_prerequisites
12701309
)
1310+
python_bin_dir = join(self._host_recipe.site_dir, "bin")
1311+
self.patch_shebangs(python_bin_dir, self.real_hostpython_location)
1312+
12711313
build_dir = self.get_build_dir(arch.arch)
12721314
env = self.get_recipe_env(arch, with_flags_in_cc=True)
12731315
# make build dir separately

0 commit comments

Comments
 (0)