diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 596597b1ab223e..6e0e67d2a613c8 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -574,10 +574,13 @@ process and user. the groups of which the specified username is a member, plus the specified group id. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. .. versionadded:: 3.2 + .. versionchanged:: 3.16 + Support for Android now exists. + .. function:: putenv(key, value, /) @@ -610,21 +613,30 @@ process and user. Set the current process's effective group id. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. + + .. versionchanged:: 3.16 + Support for Android now exists. .. function:: seteuid(euid, /) Set the current process's effective user id. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. + + .. versionchanged:: 3.16 + Support for Android now exists. .. function:: setgid(gid, /) Set the current process' group id. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. + + .. versionchanged:: 3.16 + Support for Android now exists. .. function:: setgroups(groups, /) @@ -718,32 +730,44 @@ process and user. Set the current process's real and effective group ids. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. + + .. versionchanged:: 3.16 + Support for Android now exists. .. function:: setresgid(rgid, egid, sgid, /) Set the current process's real, effective, and saved group ids. - .. availability:: Unix, not WASI, not Android, not macOS, not iOS. + .. availability:: Unix, not WASI, not macOS, not iOS. .. versionadded:: 3.2 + .. versionchanged:: 3.16 + Support for Android now exists. + .. function:: setresuid(ruid, euid, suid, /) Set the current process's real, effective, and saved user ids. - .. availability:: Unix, not WASI, not Android, not macOS, not iOS. + .. availability:: Unix, not WASI, not macOS, not iOS. .. versionadded:: 3.2 + .. versionchanged:: 3.16 + Support for Android now exists. + .. function:: setreuid(ruid, euid, /) Set the current process's real and effective user ids. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. + + .. versionchanged:: 3.16 + Support for Android now exists. .. function:: getsid(pid, /) @@ -766,7 +790,10 @@ process and user. Set the current process's user id. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. + + .. versionchanged:: 3.16 + Support for Android now exists. .. placed in this section since it relates to errno.... a little weak @@ -2324,11 +2351,14 @@ features: Change the root directory of the current process to *path*. - .. availability:: Unix, not WASI, not Android. + .. availability:: Unix, not WASI. .. versionchanged:: 3.6 Accepts a :term:`path-like object`. + .. versionchanged:: 3.16 + Support for Android now exists. + .. function:: fchdir(fd) diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 836aa91bb0885b..fb9249df4be33e 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1372,10 +1372,13 @@ The :mod:`!socket` module also offers various network-related services: .. audit-event:: socket.sethostname name socket.sethostname - .. availability:: Unix, not Android. + .. availability:: Unix. .. versionadded:: 3.3 + .. versionchanged:: 3.16 + Support for Android now exists. + .. function:: if_nameindex() diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst index 8b4940ceb9521a..88b5f35a796796 100644 --- a/Doc/using/configure.rst +++ b/Doc/using/configure.rst @@ -1342,6 +1342,16 @@ See :source:`Platforms/Apple/iOS/README.md`. Specify the name for the framework (default: ``Python``). +An iOS build configured without ``--enable-framework`` produces a static +``libpython``, for embedding directly in an app binary. Such a build cannot +load extension modules at runtime, and so does not support binary wheels; it +requires ``MODULE_BUILDTYPE=static`` and :option:`--disable-test-modules`, and +rejects :option:`--enable-shared`. See +:source:`Platforms/Apple/iOS/README.md` for the full list of limitations. + +.. versionadded:: 3.16 + iOS builds may be configured without a framework. + Cross Compiling Options ----------------------- diff --git a/Doc/using/ios.rst b/Doc/using/ios.rst index 31d9e2f2c816e7..80b9b3c3a07f06 100644 --- a/Doc/using/ios.rst +++ b/Doc/using/ios.rst @@ -142,6 +142,13 @@ should ensure these stub binaries are on your path. Installing Python on iOS ======================== +The official iOS release artefact is a framework build, distributed as an +``XCFramework``; this is the configuration described in the rest of this +document, and the only one that supports binary extension modules. Static +builds, where ``libpython`` and every extension module are linked directly into +the app binary, are also possible, with limitations; see +:source:`Platforms/Apple/iOS/README.md` for details. + Tools for building iOS apps --------------------------- diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 858dc3b8a878e7..243bd078d37c99 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -474,6 +474,14 @@ os process via a pidfd. Available on Linux 5.6+. (Contributed by Maurycy Pawłowski-Wieroński in :gh:`149464`.) +* The following functions are now available on Android: + :func:`os.chroot`, :func:`os.initgroups`, :func:`os.setegid`, + :func:`os.seteuid`, :func:`os.setgid`, :func:`os.setregid`, + :func:`os.setresgid`, :func:`os.setresuid`, :func:`os.setreuid`, + and :func:`os.setuid`. Calling them without sufficient privileges + now raises :exc:`PermissionError` instead of the functions being unavailable. + (Contributed by Md Arif in :gh:`152936`.) + pydoc ----- @@ -513,6 +521,15 @@ shlex (Contributed by Jay Berry in :gh:`148846`.) +socket +------ + +* The :func:`socket.sethostname` function is now available on Android. + Calling it without sufficient privileges now raises :exc:`PermissionError` + instead of the function being unavailable. + (Contributed by Md Arif in :gh:`152936`.) + + sqlite3 ------- diff --git a/Lib/pdb.py b/Lib/pdb.py index 458eb835265236..c3440a5d11a588 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -84,7 +84,6 @@ import signal import socket import typing -import asyncio import inspect import weakref import builtins @@ -102,6 +101,8 @@ from types import CodeType from warnings import deprecated +lazy import asyncio + try: import _pyrepl.utils except ModuleNotFoundError: @@ -902,6 +903,10 @@ def _hold_exceptions(self, exceptions): self._chained_exception_index = 0 def _get_asyncio_task(self): + # If asyncio has never been imported there cannot be a running task, + # so skip the import rather than pay for it on every interaction. + if 'asyncio' not in sys.modules: + return None try: task = asyncio.current_task() except RuntimeError: diff --git a/Lib/tarfile.py b/Lib/tarfile.py index f46e938fd314dd..c4cbbdb980857d 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2196,7 +2196,9 @@ def getmember(self, name): than once in the archive, its last occurrence is assumed to be the most up-to-date version. """ - tarinfo = self._getmember(name.rstrip('/')) + tarinfo = self._getmember(name) + if tarinfo is None and name.endswith('/'): + tarinfo = self._getmember(name.rstrip('/')) if tarinfo is None: raise KeyError("filename %r not found" % name) return tarinfo diff --git a/Lib/test/test_os/test_posix.py b/Lib/test/test_os/test_posix.py index 814f945aac7453..f13ad46aac45aa 100644 --- a/Lib/test/test_os/test_posix.py +++ b/Lib/test/test_os/test_posix.py @@ -107,6 +107,10 @@ def test_getresgid(self): @unittest.skipUnless(hasattr(posix, 'setresuid'), 'test needs posix.setresuid()') def test_setresuid(self): + # Android blocks this function for non-root users regardless of the arguments. + if support.is_android and os.getuid() != 0: + self.assertRaises(PermissionError, posix.setresuid, -1, -1, -1) + return current_user_ids = posix.getresuid() self.assertIsNone(posix.setresuid(*current_user_ids)) # -1 means don't change that value. @@ -124,6 +128,10 @@ def test_setresuid_exception(self): @unittest.skipUnless(hasattr(posix, 'setresgid'), 'test needs posix.setresgid()') def test_setresgid(self): + # Android blocks this function for non-root users regardless of the arguments. + if support.is_android and os.getuid() != 0: + self.assertRaises(PermissionError, posix.setresgid, -1, -1, -1) + return current_group_ids = posix.getresgid() self.assertIsNone(posix.setresgid(*current_group_ids)) # -1 means don't change that value. diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py index 11ec57a259a9ab..6af26accc13d71 100644 --- a/Lib/test/test_readline.py +++ b/Lib/test/test_readline.py @@ -169,6 +169,44 @@ def test_write_read_limited_history(self): # Readline seems to report an additional history element. self.assertIn(readline.get_current_history_length(), (2, 3)) + def test_write_read_zero_length_history(self): + previous_length = readline.get_history_length() + self.addCleanup(readline.set_history_length, previous_length) + + readline.clear_history() + readline.add_history("first line") + readline.set_history_length(0) + readline.write_history_file(TESTFN) + self.addCleanup(os.remove, TESTFN) + + readline.clear_history() + # libedit cannot read an empty history file, only one that still + # has its header line. How many items remain is not checked: + # libedit's own history_truncate_file() ignores a length of 0. + readline.read_history_file(TESTFN) + + @unittest.skipUnless(hasattr(readline, "append_history_file"), + "append_history not available") + def test_append_limited_history(self): + previous_length = readline.get_history_length() + self.addCleanup(readline.set_history_length, previous_length) + + readline.clear_history() + readline.add_history("first line") + readline.add_history("second line") + readline.write_history_file(TESTFN) + self.addCleanup(os.remove, TESTFN) + + readline.add_history("third line") + readline.set_history_length(2) + readline.append_history_file(1, TESTFN) + + readline.clear_history() + readline.read_history_file(TESTFN) + self.assertEqual(readline.get_history_item(1), "second line") + self.assertEqual(readline.get_history_item(2), "third line") + self.assertEqual(readline.get_history_item(3), None) + class TestReadline(unittest.TestCase): diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 10106c3ada9ba5..62edb7115ed682 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -255,6 +255,19 @@ def test_add_dir_getmember(self): self.add_dir_and_getmember('bar') self.add_dir_and_getmember('a'*101) + def test_extract_name_with_trailing_slash(self): + # gh-127636: './mydir/' is deliberately a regular-file member + # (REGTYPE, not DIRTYPE) whose stored name ends in a slash. It + # extracts as a file. Do not "fix" this by setting DIRTYPE; the + # trailing-slash name on a non-directory is what is being tested. + with tarfile.open(tmpname, 'w') as tar: + tar.addfile(tarfile.TarInfo('./mydir/')) + with os_helper.temp_dir() as tmpdir, tarfile.open(tmpname) as tar: + names = tar.getnames() + self.assertEqual(names, ['./mydir/']) + tar.extract(names[0], tmpdir, filter='fully_trusted') + self.assertTrue(os.path.isfile(os.path.join(tmpdir, 'mydir'))) + @unittest.skipUnless(hasattr(os, "getuid") and hasattr(os, "getgid"), "Missing getuid or getgid implementation") def add_dir_and_getmember(self, name): diff --git a/Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst b/Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst new file mode 100644 index 00000000000000..4b8c1c197ddf5d --- /dev/null +++ b/Misc/NEWS.d/next/Build/2026-08-20-13-32-54.gh-issue-156109.ZTJHi9.rst @@ -0,0 +1,5 @@ +iOS builds may now be configured without a framework, producing a static +``libpython`` for embedding in an app binary. Such a build cannot load extension +modules at runtime, and requires ``MODULE_BUILDTYPE=static`` and +``--disable-test-modules`` to be requested explicitly. A shared iOS build must +still be a framework build. diff --git a/Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst b/Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst new file mode 100644 index 00000000000000..51957a73b5603b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-04-00-19-23.gh-issue-127636.o_uD9U.rst @@ -0,0 +1,3 @@ +Fix :meth:`tarfile.TarFile.extract` to accept archive member names with a +trailing forward slash, including those returned by +:meth:`tarfile.TarFile.getnames`. Contributed by Xiao Yuan. diff --git a/Misc/NEWS.d/next/Library/2026-07-08-06-54-57.gh-issue-152936.TaFl0J.rst b/Misc/NEWS.d/next/Library/2026-07-08-06-54-57.gh-issue-152936.TaFl0J.rst new file mode 100644 index 00000000000000..d8021b248977f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-08-06-54-57.gh-issue-152936.TaFl0J.rst @@ -0,0 +1,6 @@ +:func:`os.chroot`, :func:`os.initgroups`, :func:`os.setegid`, +:func:`os.seteuid`, :func:`os.setgid`, :func:`os.setregid`, +:func:`os.setresgid`, :func:`os.setresuid`, :func:`os.setreuid`, +:func:`os.setuid`, and :func:`socket.sethostname` are now available on +Android. Calling them without sufficient privileges now raises +:exc:`PermissionError` instead of the functions being unavailable. diff --git a/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst new file mode 100644 index 00000000000000..f5f64c494296cc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-31-11-40-00.gh-issue-156774.Pd8Lz1.rst @@ -0,0 +1,2 @@ +Speed up :mod:`pdb` startup by only importing :mod:`asyncio` when the program +being debugged has already imported it. diff --git a/Misc/NEWS.d/next/Library/2026-09-08-04-10-00.gh-issue-123018.hV2kQe.rst b/Misc/NEWS.d/next/Library/2026-09-08-04-10-00.gh-issue-123018.hV2kQe.rst new file mode 100644 index 00000000000000..d75aa6950eb071 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-08-04-10-00.gh-issue-123018.hV2kQe.rst @@ -0,0 +1,6 @@ +Fix :func:`readline.write_history_file` and +:func:`readline.append_history_file` producing a history file that +:func:`readline.read_history_file` could not load when a limit was set with +:func:`readline.set_history_length` and Python was built against +``libedit``. This works around `NetBSD PR 60322 +`_. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 847237142854da..7636d51485c730 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4399,6 +4399,14 @@ static PyObject * os_chroot_impl(PyObject *module, path_t *path) /*[clinic end generated code: output=de80befc763a4475 input=14822965652c3dc3]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return path_error(path); + } +#endif int res; Py_BEGIN_ALLOW_THREADS res = chroot(path->narrow); @@ -9875,6 +9883,14 @@ os_initgroups_impl(PyObject *module, PyObject *oname, gid_t gid) /*[clinic end generated code: output=59341244521a9e3f input=7e4514dff4526a95]*/ #endif { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif const char *username = PyBytes_AS_STRING(oname); if (initgroups(username, gid) == -1) @@ -10301,6 +10317,14 @@ static PyObject * os_setuid_impl(PyObject *module, uid_t uid) /*[clinic end generated code: output=a0a41fd0d1ec555f input=c921a3285aa22256]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setuid(uid) < 0) return posix_error(); Py_RETURN_NONE; @@ -10322,6 +10346,14 @@ static PyObject * os_seteuid_impl(PyObject *module, uid_t euid) /*[clinic end generated code: output=102e3ad98361519a input=ba93d927e4781aa9]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (seteuid(euid) < 0) return posix_error(); Py_RETURN_NONE; @@ -10343,6 +10375,14 @@ static PyObject * os_setegid_impl(PyObject *module, gid_t egid) /*[clinic end generated code: output=4e4b825a6a10258d input=4080526d0ccd6ce3]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setegid(egid) < 0) return posix_error(); Py_RETURN_NONE; @@ -10365,6 +10405,14 @@ static PyObject * os_setreuid_impl(PyObject *module, uid_t ruid, uid_t euid) /*[clinic end generated code: output=62d991210006530a input=0ca8978de663880c]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setreuid(ruid, euid) < 0) { return posix_error(); } else { @@ -10389,6 +10437,14 @@ static PyObject * os_setregid_impl(PyObject *module, gid_t rgid, gid_t egid) /*[clinic end generated code: output=aa803835cf5342f3 input=c59499f72846db78]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setregid(rgid, egid) < 0) return posix_error(); Py_RETURN_NONE; @@ -10409,6 +10465,14 @@ static PyObject * os_setgid_impl(PyObject *module, gid_t gid) /*[clinic end generated code: output=bdccd7403f6ad8c3 input=27d30c4059045dc6]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setgid(gid) < 0) return posix_error(); Py_RETURN_NONE; @@ -15506,6 +15570,14 @@ static PyObject * os_setresuid_impl(PyObject *module, uid_t ruid, uid_t euid, uid_t suid) /*[clinic end generated code: output=834a641e15373e97 input=9e33cb79a82792f3]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setresuid(ruid, euid, suid) < 0) return posix_error(); Py_RETURN_NONE; @@ -15529,6 +15601,14 @@ static PyObject * os_setresgid_impl(PyObject *module, gid_t rgid, gid_t egid, gid_t sgid) /*[clinic end generated code: output=6aa402f3d2e514a9 input=33e9e0785ef426b1]*/ { +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return posix_error(); + } +#endif if (setresgid(rgid, egid, sgid) < 0) return posix_error(); Py_RETURN_NONE; diff --git a/Modules/readline.c b/Modules/readline.c index fc79a5866dfd38..e0ff7a9ffebf79 100644 --- a/Modules/readline.c +++ b/Modules/readline.c @@ -10,16 +10,27 @@ /* Standard definitions */ #include "Python.h" +#include "pycore_fileutils.h" // _Py_open_noraise() #include "pycore_pyatomic_ft_wrappers.h" #include "pycore_pylifecycle.h" // _Py_SetLocaleFromEnv() #include // errno +#include // getpwuid_r() #include // SIGWINCH #include // free() #include // strdup() +#ifdef HAVE_FCNTL_H +# include // O_RDWR +#endif #ifdef HAVE_SYS_SELECT_H # include // select() #endif +#ifdef HAVE_SYS_STAT_H +# include // fstat() +#endif +#ifdef HAVE_UNISTD_H +# include // ftruncate() +#endif #if defined(HAVE_SETLOCALE) /* GNU readline() mistakenly sets the LC_CTYPE locale. @@ -319,6 +330,176 @@ readline_read_history_file_impl(PyObject *module, PyObject *filename_obj) static int _history_length = -1; /* do not truncate history by default */ +#ifndef __APPLE__ +/* macOS libedit carries a patch fixing the bug this works around. */ +/* Return libedit's default history file, "~/.history". This must resolve + * the same file as libedit's private _default_history_file() + * (https://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libedit/readline.c) so that we + * truncate the same file write_history() and append_history() just wrote: the + * home directory comes from the password database, $HOME is not consulted. + * The result must be freed with PyMem_RawFree(). */ +static char * +_py_libedit_default_history_file(void) +{ + struct passwd *pw = NULL; + char *path = NULL; +#ifdef HAVE_GETPWUID_R + struct passwd pwd; + char *buf = NULL; + Py_ssize_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize == -1) { + bufsize = 1024; + } + for (;;) { + char *newbuf = PyMem_RawRealloc(buf, bufsize); + if (newbuf == NULL) { + break; + } + buf = newbuf; + int status = getpwuid_r(getuid(), &pwd, buf, bufsize, &pw); + if (status == 0) { + break; + } + pw = NULL; + if (status != ERANGE || bufsize > (PY_SSIZE_T_MAX >> 1)) { + break; + } + bufsize <<= 1; + } +#else + pw = getpwuid(getuid()); +#endif + if (pw != NULL && pw->pw_dir != NULL) { + size_t len = strlen(pw->pw_dir) + sizeof("/.history"); + path = PyMem_RawMalloc(len); + if (path != NULL) { + PyOS_snprintf(path, len, "%s/.history", pw->pw_dir); + } + } +#ifdef HAVE_GETPWUID_R + PyMem_RawFree(buf); +#endif + return path; +} + +/* libedit's history_truncate_file() keeps the last nlines lines of the + * file, which drops the "_HiStOrY_V2_" header line that its own + * write_history() emits and that its read_history() requires: once + * truncated, the file can no longer be loaded (gh-123018). Truncate the + * file ourselves and keep the header. + * + * Upstream libedit (NetBSD, and the portable releases from thrysoee.dk) + * still has this bug, reported as https://gnats.netbsd.org/60322. Apple's + * libedit fork patches its history_truncate_file() to copy the first line, + * so this workaround is not compiled on macOS. We only keep the header + * when it is actually there, so a file without one (e.g. written by GNU + * readline) is not given a bogus header. + * + * History files are small, so the whole file is read into memory and + * rewritten in place, as libedit does. GNU readline instead writes a + * temporary file and renames it over the original. */ +static int +_py_libedit_history_truncate_file(const char *filename, int nlines) +{ + static const char cookie[] = "_HiStOrY_V2_\n"; + const size_t cookie_len = sizeof(cookie) - 1; + char *default_file = NULL; + char *buf = NULL; + FILE *fp = NULL; + int ret = -1; + + if (filename == NULL) { + default_file = _py_libedit_default_history_file(); + if (default_file == NULL) { + return -1; + } + filename = default_file; + } + int fd = _Py_open_noraise(filename, O_RDWR); + if (fd < 0) { + goto done; + } + fp = fdopen(fd, "r+"); + if (fp == NULL) { + close(fd); + goto done; + } + struct stat st; + if (fstat(fd, &st) != 0) { + goto done; + } + size_t size = (size_t)st.st_size; + buf = PyMem_RawMalloc(size); + if (buf == NULL || fread(buf, 1, size, fp) != size) { + goto done; + } + + size_t header = 0; + if (size >= cookie_len && memcmp(buf, cookie, cookie_len) == 0) { + header = cookie_len; + } + const char *end = buf + size; + + /* Count the lines following the header. */ + size_t total = 0; + for (const char *p = buf + header; + (p = memchr(p, '\n', end - p)) != NULL; + p++) { + total++; + } + if (end > buf + header && end[-1] != '\n') { + total++; /* an unterminated last line */ + } + if (total <= (size_t)nlines) { + ret = 0; /* nothing to drop */ + goto done; + } + + /* Skip the leading lines, keeping the header and the last nlines. */ + const char *tail = buf + header; + for (size_t skip = total - (size_t)nlines; skip > 0; skip--) { + const char *nl = memchr(tail, '\n', end - tail); + if (nl == NULL) { + tail = end; /* the unterminated last line is dropped too */ + break; + } + tail = nl + 1; + } + size_t taillen = end - tail; + if (fseek(fp, 0, SEEK_SET) != 0 + || fwrite(buf, 1, header, fp) != header + || fwrite(tail, 1, taillen, fp) != taillen + || fflush(fp) != 0 + || ftruncate(fd, (off_t)(header + taillen)) != 0) { + goto done; + } + ret = 0; + +done: + if (fp != NULL) { + fclose(fp); + } + PyMem_RawFree(buf); + PyMem_RawFree(default_file); + return ret; +} +#endif /* !__APPLE__ */ + +/* Truncate the history file after write_history() or append_history(). + * Like the history_truncate_file() calls this replaces, failures are + * ignored by the callers: the history was already saved successfully and + * a failed truncation only leaves the file longer than requested. */ +static int +_py_history_truncate_file(const char *filename, int nlines) +{ +#ifndef __APPLE__ + if (using_libedit_emulation) { + return _py_libedit_history_truncate_file(filename, nlines); + } +#endif + return history_truncate_file(filename, nlines); +} + /* Exported function to save a readline history file */ /*[clinic input] @@ -360,7 +541,7 @@ readline_write_history_file_impl(PyObject *module, PyObject *filename_obj) errno = err = write_history(filename); int history_length = FT_ATOMIC_LOAD_INT_RELAXED(_history_length); if (!err && history_length >= 0) - history_truncate_file(filename, history_length); + _py_history_truncate_file(filename, history_length); Py_XDECREF(filename_bytes); errno = err; if (errno) @@ -419,7 +600,7 @@ readline_append_history_file_impl(PyObject *module, int nelements, nelements - libedit_append_replace_history_offset, filename); int history_length = FT_ATOMIC_LOAD_INT_RELAXED(_history_length); if (!err && history_length >= 0) - history_truncate_file(filename, history_length); + _py_history_truncate_file(filename, history_length); Py_XDECREF(filename_bytes); errno = err; if (errno) diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 82899572f80255..d189c6d478836c 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -5988,6 +5988,15 @@ _socket_sethostname(PyObject *module, PyObject *hnobj) Py_buffer buf; int res, flag = 0; +#ifdef __ANDROID__ + // On Android, calling this function as a non-root user leads to a process crash + // rather than returning a permission error. + if (getuid() != 0) { + errno = EPERM; + return set_error(); + } +#endif + #if defined(_AIX) || (defined(__sun) && defined(__SVR4) && Py_SUNOS_VERSION <= 510) /* issue #18259, sethostname is not declared in any useful header file on AIX * the same is true for Solaris 10 */ diff --git a/Platforms/Apple/iOS/README.md b/Platforms/Apple/iOS/README.md index faeeead1df03a2..4bf083fb25ea06 100644 --- a/Platforms/Apple/iOS/README.md +++ b/Platforms/Apple/iOS/README.md @@ -90,8 +90,13 @@ Python build for a single framework, the following options are available. installed. If `DIR` is not specified, the framework will be installed into a subdirectory of the `iOS/Frameworks` folder. - This argument *must* be provided when configuring iOS builds. iOS does not - support non-framework builds. + This argument is required for any iOS build that will be distributed, and + for any build that needs to load binary extension modules. + + Omitting it builds a static `libpython` for embedding directly in an app + binary, instead of a `Python.framework`. That configuration comes with + significant restrictions; see [Building a static + Python](#building-a-static-python) below. * `--with-framework-name=NAME` @@ -113,9 +118,11 @@ framework to contain non-library content, so the iOS build will produce a The `lib` folder will be needed at runtime to support the Python library. If you want to use Python in a real iOS project, you need to produce multiple -`Python.framework` builds, one for each ABI and architecture. iOS builds of -Python *must* be constructed as framework builds. To support this, you must -provide the `--enable-framework` flag when configuring the build. The build +`Python.framework` builds, one for each ABI and architecture. Unless you are +statically linking Python into your app (see [Building a static +Python](#building-a-static-python) below), iOS builds of Python *must* be +constructed as framework builds. To support this, you must provide the +`--enable-framework` flag when configuring the build. The build also requires the use of cross-compilation. The minimal commands for building Python for the ARM64 iOS simulator will look something like: ``` @@ -216,6 +223,68 @@ target, provide the version number as part of the `--host` argument - for example, `--host=arm64-apple-ios15.4-simulator` would compile an ARM64 simulator build with a deployment target of 15.4. +### Building a static Python + +The official iOS release artefact is a framework build. However, if you are +embedding Python in an app that links `libpython` at compile time, you can +instead build a static `libpython3.x.a`, and link that archive directly into +your app binary. + +The App Store requirement that binary modules be packaged as signed frameworks +does not apply to a static build, because a static build loads nothing at +runtime; but for the same reason, this configuration cannot use *any* binary +module that isn't compiled into the app binary. The restrictions that follow +from that must be opted into explicitly at configure time: + +* `MODULE_BUILDTYPE=static` is required. There is no framework for a shared + extension module to link against, so every extension module, including the + ones in the standard library, must be linked into `libpython`. + +* `--disable-test-modules` is required. Some test modules must be compiled as + shared libraries (see `Modules/Setup.stdlib.in`), so they cannot be built in + this configuration at all. + +A non-framework build is selected by omitting `--enable-framework`; +`--disable-framework` is accepted as an explicit spelling of the same thing. + +The minimal commands for a static build targeting ARM64 iOS devices are then: +``` +export PATH="$(pwd)/Platforms/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" +./configure \ + --prefix=/path/to/install/dir \ + --disable-test-modules \ + MODULE_BUILDTYPE=static \ + --host=arm64-apple-ios \ + --build=arm64-apple-darwin \ + --with-build-python=/path/to/python.exe +make +make install +``` +This produces a `libpython3.x.a` containing the interpreter and the standard +library's extension modules; `make install` installs that archive, along with +the standard library's Python source, into the location given by `--prefix`. +Unlike a framework build, `--prefix` is not set for you, so specify it +explicitly - otherwise `libpython` will be installed into `/usr/local`. + +#### Limitations of a static build + +* **Binary wheels cannot be used.** There is no `libpython` dylib for a + third-party extension module to link against, and a static Python has nothing + to `dlopen` in any case. Pure Python wheels work as normal; any package with a + C extension must be compiled into the app binary alongside `libpython`. + +* **The standard library's extension modules are not loadable modules.** They + live in the archive, not in `.framework` bundles in the app's `Frameworks` + folder, so the packaging described in + [Using Python on iOS](https://docs.python.org/3/using/ios.html) does not apply + to them. + +* **The test suite cannot be run as-is**, as the test modules are not built. + +* This configuration is not covered by the `Platforms/Apple` build script, nor + by CPython's CI. It is not the configuration used to produce official + releases. + ## Testing Python on iOS ### Testing a multi-architecture framework diff --git a/Python/emscripten_syscalls.c b/Python/emscripten_syscalls.c index 48ca208dedb14b..cdd5fc6e91e89b 100644 --- a/Python/emscripten_syscalls.c +++ b/Python/emscripten_syscalls.c @@ -132,6 +132,10 @@ EM_JS_MACROS(void, _emscripten_promising_main_js, (void), { }; }) +EM_JS_DEPS(_emscripten_promising_main, + "$FS,$PATH,$FS_getMode,$resolveGlobalSymbol," + "emscripten_exit_with_live_runtime"); + __attribute__((constructor)) void _emscripten_promising_main(void) { _emscripten_promising_main_js(); } @@ -199,6 +203,8 @@ EM_JS_MACROS(__externref_t, __maybe_fd_read_async, ( }; ); +EM_JS_DEPS(__maybe_fd_read_async, "$SYSCALLS"); + // Bind original fd_read syscall to __wasi_fd_read_orig(). __wasi_errno_t __wasi_fd_read_orig(__wasi_fd_t fd, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_size_t *nread) @@ -280,6 +286,8 @@ EM_JS_MACROS(__externref_t, __maybe_poll_async, (intptr_t fds, int nfds, int tim })(); }); +EM_JS_DEPS(__maybe_poll_async, "$FS"); + // Bind original poll syscall to syscall_poll_orig(). int syscall_poll_orig(intptr_t fds, int nfds, int timeout) __attribute__((__import_module__("env"), diff --git a/Python/emscripten_trampoline.c b/Python/emscripten_trampoline.c index 1833311ca74d9d..75cfde6b76f217 100644 --- a/Python/emscripten_trampoline.c +++ b/Python/emscripten_trampoline.c @@ -94,6 +94,9 @@ addOnPreRun(function setEmscriptenTrampoline() { }); ); +EM_JS_DEPS(_PyEM_TrampolineCall, + "$wasmTable,$wasmMemory,$addFunction,$addOnPreRun"); + PyObject* _PyEM_TrampolineCall(PyCFunctionWithKeywords func, PyObject* self, diff --git a/configure b/configure index 7e4844263a3491..ae3f0450dc7af1 100755 --- a/configure +++ b/configure @@ -4441,29 +4441,25 @@ then : case $enableval in no) - case $ac_sys_system in - iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - - if test "x${prefix}" = "xNONE"; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= - esac + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + + if test "x${prefix}" = "xNONE"; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= ;; *) PYTHONFRAMEWORKPREFIX="${enableval}" @@ -4558,28 +4554,24 @@ then : else case e in #( e) - case $ac_sys_system in - iOS) as_fn_error $? "iOS builds must use --enable-framework" "$LINENO" 5 ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - if test "x${prefix}" = "xNONE" ; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= - esac + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + if test "x${prefix}" = "xNONE" ; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= ;; esac fi @@ -8027,6 +8019,9 @@ printf "%s\n" "#define Py_ENABLE_SHARED 1" >>confdefs.h RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; iOS) + if test -z "$PYTHONFRAMEWORK"; then + as_fn_error $? "iOS shared builds must use --enable-framework; an iOS app can only load a signed framework, never a bare dylib" "$LINENO" 5 + fi LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) @@ -14540,7 +14535,11 @@ printf "%s\n" "#define THREAD_STACK_SIZE 0x$stack_size" >>confdefs.h fi LINKFORSHARED="$LINKFORSHARED" elif test $ac_sys_system = "iOS"; then - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + + if test "$enable_framework"; then + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi fi ;; OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; @@ -20366,14 +20365,9 @@ printf "%s\n" "$MACHDEP_OBJS" >&6; } fi if test "$ac_sys_system" = "Linux-android"; then - # When these functions are used in an unprivileged process, they crash rather - # than returning an error. - blocked_funcs="chroot initgroups setegid seteuid setgid sethostname - setregid setresgid setresuid setreuid setuid" - # These functions are unimplemented and always return an error # (https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/android13-release/public/domain.te#1044) - blocked_funcs="$blocked_funcs sem_open sem_unlink" + blocked_funcs="sem_open sem_unlink" # Before API level 23, when fchmodat is called with the unimplemented flag # AT_SYMLINK_NOFOLLOW, instead of returning ENOTSUP as it should, it actually @@ -28293,8 +28287,8 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" fi -# On iOS the shared libraries must be linked with the Python framework -if test "$ac_sys_system" = "iOS"; then +# On iOS the shared libraries must be linked with the framework, when built +if test "$ac_sys_system" = "iOS" && test "$enable_framework"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi @@ -35116,6 +35110,13 @@ fi { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $TEST_MODULES" >&5 printf "%s\n" "$TEST_MODULES" >&6; } +if test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK" +then : + if test "$TEST_MODULES" != "no" +then : + as_fn_error $? "iOS non-framework builds must use --disable-test-modules" "$LINENO" 5 +fi +fi # Check for --with-build-details-suffix @@ -35406,6 +35407,14 @@ case $host_cpu in #( esac +if test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK" +then : + if test "$MODULE_BUILDTYPE" != "static" +then : + as_fn_error $? "iOS non-framework builds must use MODULE_BUILDTYPE=static" "$LINENO" 5 +fi +fi + MODULE_BLOCK= diff --git a/configure.ac b/configure.ac index 8345c97349b738..1c42900cb975c3 100644 --- a/configure.ac +++ b/configure.ac @@ -578,29 +578,25 @@ AC_ARG_ENABLE([framework], case $enableval in no) - case $ac_sys_system in - iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - - if test "x${prefix}" = "xNONE"; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= - esac + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + + if test "x${prefix}" = "xNONE"; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= ;; *) PYTHONFRAMEWORKPREFIX="${enableval}" @@ -688,28 +684,24 @@ AC_ARG_ENABLE([framework], esac esac ],[ - case $ac_sys_system in - iOS) AC_MSG_ERROR([iOS builds must use --enable-framework]) ;; - *) - PYTHONFRAMEWORK= - PYTHONFRAMEWORKDIR=no-framework - PYTHONFRAMEWORKPREFIX= - PYTHONFRAMEWORKINSTALLDIR= - PYTHONFRAMEWORKINSTALLNAMEPREFIX= - RESSRCDIR= - FRAMEWORKINSTALLFIRST= - FRAMEWORKINSTALLLAST= - FRAMEWORKALTINSTALLFIRST= - FRAMEWORKALTINSTALLLAST= - FRAMEWORKPYTHONW= - INSTALLTARGETS="commoninstall bininstall maninstall" - if test "x${prefix}" = "xNONE" ; then - FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" - else - FRAMEWORKUNIXTOOLSPREFIX="${prefix}" - fi - enable_framework= - esac + PYTHONFRAMEWORK= + PYTHONFRAMEWORKDIR=no-framework + PYTHONFRAMEWORKPREFIX= + PYTHONFRAMEWORKINSTALLDIR= + PYTHONFRAMEWORKINSTALLNAMEPREFIX= + RESSRCDIR= + FRAMEWORKINSTALLFIRST= + FRAMEWORKINSTALLLAST= + FRAMEWORKALTINSTALLFIRST= + FRAMEWORKALTINSTALLLAST= + FRAMEWORKPYTHONW= + INSTALLTARGETS="commoninstall bininstall maninstall" + if test "x${prefix}" = "xNONE" ; then + FRAMEWORKUNIXTOOLSPREFIX="${ac_default_prefix}" + else + FRAMEWORKUNIXTOOLSPREFIX="${prefix}" + fi + enable_framework= ]) AC_SUBST([PYTHONFRAMEWORK]) AC_SUBST([PYTHONFRAMEWORKIDENTIFIER]) @@ -1676,6 +1668,9 @@ if test $enable_shared = "yes"; then RUNSHARED=DYLD_LIBRARY_PATH=`pwd`${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}} ;; iOS) + if test -z "$PYTHONFRAMEWORK"; then + AC_MSG_ERROR([iOS shared builds must use --enable-framework; an iOS app can only load a signed framework, never a bare dylib]) + fi LDLIBRARY='libpython$(LDVERSION).dylib' ;; AIX*) @@ -3836,7 +3831,11 @@ then fi LINKFORSHARED="$LINKFORSHARED" elif test $ac_sys_system = "iOS"; then - LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + LINKFORSHARED="-Wl,-stack_size,$stack_size $LINKFORSHARED" + + if test "$enable_framework"; then + LINKFORSHARED="$LINKFORSHARED "'$(PYTHONFRAMEWORKDIR)/$(PYTHONFRAMEWORK)' + fi fi ;; OpenUNIX*|UnixWare*) LINKFORSHARED="-Wl,-Bexport";; @@ -5494,14 +5493,9 @@ else fi if test "$ac_sys_system" = "Linux-android"; then - # When these functions are used in an unprivileged process, they crash rather - # than returning an error. - blocked_funcs="chroot initgroups setegid seteuid setgid sethostname - setregid setresgid setresuid setreuid setuid" - # These functions are unimplemented and always return an error # (https://android.googlesource.com/platform/system/sepolicy/+/refs/heads/android13-release/public/domain.te#1044) - blocked_funcs="$blocked_funcs sem_open sem_unlink" + blocked_funcs="sem_open sem_unlink" # Before API level 23, when fchmodat is called with the unimplemented flag # AT_SYMLINK_NOFOLLOW, instead of returning ENOTSUP as it should, it actually @@ -6783,8 +6777,8 @@ if test "$PY_ENABLE_SHARED" = "1" && ( test -n "$ANDROID_API_LEVEL" || test "$MA LIBPYTHON="-lpython${VERSION}${ABIFLAGS}" fi -# On iOS the shared libraries must be linked with the Python framework -if test "$ac_sys_system" = "iOS"; then +# On iOS the shared libraries must be linked with the framework, when built +if test "$ac_sys_system" = "iOS" && test "$enable_framework"; then MODULE_DEPS_SHARED="$MODULE_DEPS_SHARED \$(PYTHONFRAMEWORKDIR)/\$(PYTHONFRAMEWORK)" fi @@ -8174,6 +8168,11 @@ AC_ARG_ENABLE([test-modules], AS_VAR_IF([enable_test_modules], [yes], [TEST_MODULES=yes], [TEST_MODULES=no]) ], [TEST_MODULES=yes]) AC_MSG_RESULT([$TEST_MODULES]) +dnl Some test modules can only be built as shared libraries (see +dnl Modules/Setup.stdlib.in), which a non-framework iOS build cannot do. +AS_IF([test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"], + [AS_IF([test "$TEST_MODULES" != "no"], + [AC_MSG_ERROR([iOS non-framework builds must use --disable-test-modules])])]) AC_SUBST([TEST_MODULES]) # Check for --with-build-details-suffix @@ -8413,6 +8412,12 @@ AS_CASE([$host_cpu], ) AC_SUBST([MODULE_BUILDTYPE]) +dnl A non-framework iOS build has no framework for a shared extension module +dnl to link against, so every extension module must be built into libpython. +AS_IF([test "$ac_sys_system" = "iOS" && test -z "$PYTHONFRAMEWORK"], + [AS_IF([test "$MODULE_BUILDTYPE" != "static"], + [AC_MSG_ERROR([iOS non-framework builds must use MODULE_BUILDTYPE=static])])]) + dnl _MODULE_BLOCK_ADD([VAR], [VALUE]) dnl internal: adds $1=quote($2) to MODULE_BLOCK AC_DEFUN([_MODULE_BLOCK_ADD], [AS_VAR_APPEND([MODULE_BLOCK], ["$1=_AS_QUOTE([$2])$as_nl"])])