Skip to content

Commit c591f5e

Browse files
Merge branch 'main' into base64-base32-padded
2 parents 384f361 + 113038f commit c591f5e

File tree

94 files changed

+3884
-1772
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+3884
-1772
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,16 @@ jobs:
206206
strategy:
207207
fail-fast: false
208208
matrix:
209-
# macos-26 is Apple Silicon, macos-26-intel is Intel.
210-
# macos-26-intel only runs tests against the GIL-enabled CPython.
209+
# macos-26 is Apple Silicon, macos-15-intel is Intel.
210+
# macos-15-intel only runs tests against the GIL-enabled CPython.
211211
os:
212212
- macos-26
213-
- macos-26-intel
213+
- macos-15-intel
214214
free-threading:
215215
- false
216216
- true
217217
exclude:
218-
- os: macos-26-intel
218+
- os: macos-15-intel
219219
free-threading: true
220220
uses: ./.github/workflows/reusable-macos.yml
221221
with:

.github/workflows/reusable-macos.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ jobs:
5252
--prefix=/opt/python-dev \
5353
--with-openssl="$(brew --prefix openssl@3.5)"
5454
- name: Build CPython
55-
if : ${{ inputs.free-threading || inputs.os != 'macos-26-intel' }}
55+
if : ${{ inputs.free-threading || inputs.os != 'macos-15-intel' }}
5656
run: gmake -j8
5757
- name: Build CPython for compiler warning check
58-
if : ${{ !inputs.free-threading && inputs.os == 'macos-26-intel' }}
58+
if : ${{ !inputs.free-threading && inputs.os == 'macos-15-intel' }}
5959
run: set -o pipefail; gmake -j8 --output-sync 2>&1 | tee compiler_output_macos.txt
6060
- name: Display build info
6161
run: make pythoninfo
6262
- name: Check compiler warnings
63-
if : ${{ !inputs.free-threading && inputs.os == 'macos-26-intel' }}
63+
if : ${{ !inputs.free-threading && inputs.os == 'macos-15-intel' }}
6464
run: >-
6565
python3 Tools/build/check_warnings.py
6666
--compiler-output-file-path=compiler_output_macos.txt

Android/android.py

Lines changed: 117 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@
3434
TESTBED_DIR = ANDROID_DIR / "testbed"
3535
CROSS_BUILD_DIR = PYTHON_DIR / "cross-build"
3636

37-
HOSTS = ["aarch64-linux-android", "x86_64-linux-android"]
37+
HOSTS = [
38+
"aarch64-linux-android",
39+
"arm-linux-androideabi",
40+
"i686-linux-android",
41+
"x86_64-linux-android",
42+
]
3843
APP_ID = "org.python.testbed"
3944
DECODE_ARGS = ("UTF-8", "backslashreplace")
4045

@@ -205,38 +210,48 @@ def make_build_python(context):
205210
#
206211
# If you're a member of the Python core team, and you'd like to be able to push
207212
# these tags yourself, please contact Malcolm Smith or Russell Keith-Magee.
208-
def unpack_deps(host, prefix_dir):
213+
def unpack_deps(host, prefix_dir, cache_dir):
209214
os.chdir(prefix_dir)
210215
deps_url = "https://github.com/beeware/cpython-android-source-deps/releases/download"
211216
for name_ver in ["bzip2-1.0.8-3", "libffi-3.4.4-3", "openssl-3.5.5-0",
212-
"sqlite-3.50.4-0", "xz-5.4.6-1", "zstd-1.5.7-1"]:
217+
"sqlite-3.50.4-0", "xz-5.4.6-1", "zstd-1.5.7-2"]:
213218
filename = f"{name_ver}-{host}.tar.gz"
214-
download(f"{deps_url}/{name_ver}/{filename}")
215-
shutil.unpack_archive(filename)
216-
os.remove(filename)
219+
out_path = download(f"{deps_url}/{name_ver}/{filename}", cache_dir)
220+
shutil.unpack_archive(out_path)
217221

218222

219-
def download(url, target_dir="."):
220-
out_path = f"{target_dir}/{basename(url)}"
221-
run(["curl", "-Lf", "--retry", "5", "--retry-all-errors", "-o", out_path, url])
223+
def download(url, cache_dir):
224+
out_path = cache_dir / basename(url)
225+
cache_dir.mkdir(parents=True, exist_ok=True)
226+
if not out_path.is_file():
227+
run(["curl", "-Lf", "--retry", "5", "--retry-all-errors", "-o", out_path, url])
228+
else:
229+
print(f"Using cached version of {basename(url)}")
222230
return out_path
223231

224232

225-
def configure_host_python(context):
233+
def configure_host_python(context, host=None):
234+
if host is None:
235+
host = context.host
226236
if context.clean:
227-
clean(context.host)
237+
clean(host)
228238

229-
host_dir = subdir(context.host, create=True)
239+
host_dir = subdir(host, create=True)
230240
prefix_dir = host_dir / "prefix"
231241
if not prefix_dir.exists():
232242
prefix_dir.mkdir()
233-
unpack_deps(context.host, prefix_dir)
243+
cache_dir = (
244+
Path(context.cache_dir).resolve()
245+
if context.cache_dir
246+
else CROSS_BUILD_DIR / "downloads"
247+
)
248+
unpack_deps(host, prefix_dir, cache_dir)
234249

235250
os.chdir(host_dir)
236251
command = [
237252
# Basic cross-compiling configuration
238253
relpath(PYTHON_DIR / "configure"),
239-
f"--host={context.host}",
254+
f"--host={host}",
240255
f"--build={sysconfig.get_config_var('BUILD_GNU_TYPE')}",
241256
f"--with-build-python={build_python_path()}",
242257
"--without-ensurepip",
@@ -252,14 +267,16 @@ def configure_host_python(context):
252267

253268
if context.args:
254269
command.extend(context.args)
255-
run(command, host=context.host)
270+
run(command, host=host)
256271

257272

258-
def make_host_python(context):
273+
def make_host_python(context, host=None):
274+
if host is None:
275+
host = context.host
259276
# The CFLAGS and LDFLAGS set in android-env include the prefix dir, so
260277
# delete any previous Python installation to prevent it being used during
261278
# the build.
262-
host_dir = subdir(context.host)
279+
host_dir = subdir(host)
263280
prefix_dir = host_dir / "prefix"
264281
for pattern in ("include/python*", "lib/libpython*", "lib/python*"):
265282
delete_glob(f"{prefix_dir}/{pattern}")
@@ -278,20 +295,28 @@ def make_host_python(context):
278295
)
279296

280297

281-
def build_all(context):
282-
steps = [configure_build_python, make_build_python, configure_host_python,
283-
make_host_python]
284-
for step in steps:
285-
step(context)
298+
def build_targets(context):
299+
if context.target in {"all", "build"}:
300+
configure_build_python(context)
301+
make_build_python(context)
302+
303+
for host in HOSTS:
304+
if context.target in {"all", "hosts", host}:
305+
configure_host_python(context, host)
306+
make_host_python(context, host)
286307

287308

288309
def clean(host):
289310
delete_glob(CROSS_BUILD_DIR / host)
290311

291312

292-
def clean_all(context):
293-
for host in HOSTS + ["build"]:
294-
clean(host)
313+
def clean_targets(context):
314+
if context.target in {"all", "build"}:
315+
clean("build")
316+
317+
for host in HOSTS:
318+
if context.target in {"all", "hosts", host}:
319+
clean(host)
295320

296321

297322
def setup_ci():
@@ -854,31 +879,85 @@ def add_parser(*args, **kwargs):
854879

855880
# Subcommands
856881
build = add_parser(
857-
"build", help="Run configure-build, make-build, configure-host and "
858-
"make-host")
882+
"build",
883+
help="Run configure and make for the selected target"
884+
)
859885
configure_build = add_parser(
860886
"configure-build", help="Run `configure` for the build Python")
861-
add_parser(
887+
make_build = add_parser(
862888
"make-build", help="Run `make` for the build Python")
863889
configure_host = add_parser(
864890
"configure-host", help="Run `configure` for Android")
865891
make_host = add_parser(
866892
"make-host", help="Run `make` for Android")
867893

868-
add_parser("clean", help="Delete all build directories")
894+
clean = add_parser(
895+
"clean",
896+
help="Delete build directories for the selected target"
897+
)
898+
869899
add_parser("build-testbed", help="Build the testbed app")
870900
test = add_parser("test", help="Run the testbed app")
871901
package = add_parser("package", help="Make a release package")
872902
ci = add_parser("ci", help="Run build, package and test")
873903
env = add_parser("env", help="Print environment variables")
874904

875905
# Common arguments
906+
# --cross-build-dir argument
907+
for cmd in [
908+
clean,
909+
configure_build,
910+
make_build,
911+
configure_host,
912+
make_host,
913+
build,
914+
package,
915+
test,
916+
ci,
917+
]:
918+
cmd.add_argument(
919+
"--cross-build-dir",
920+
action="store",
921+
default=os.environ.get("CROSS_BUILD_DIR"),
922+
dest="cross_build_dir",
923+
type=Path,
924+
help=(
925+
"Path to the cross-build directory "
926+
f"(default: {CROSS_BUILD_DIR}). Can also be set "
927+
"with the CROSS_BUILD_DIR environment variable."
928+
),
929+
)
930+
931+
# --cache-dir option
932+
for cmd in [configure_host, build, ci]:
933+
cmd.add_argument(
934+
"--cache-dir",
935+
default=os.environ.get("CACHE_DIR"),
936+
help="The directory to store cached downloads.",
937+
)
938+
939+
# --clean option
876940
for subcommand in [build, configure_build, configure_host, ci]:
877941
subcommand.add_argument(
878942
"--clean", action="store_true", default=False, dest="clean",
879943
help="Delete the relevant build directories first")
880944

881-
host_commands = [build, configure_host, make_host, package, ci]
945+
# Allow "all", "build" and "hosts" targets for some commands
946+
for subcommand in [clean, build]:
947+
subcommand.add_argument(
948+
"target",
949+
nargs="?",
950+
default="all",
951+
choices=["all", "build", "hosts"] + HOSTS,
952+
help=(
953+
"The host triplet (e.g., aarch64-linux-android), "
954+
"or 'build' for just the build platform, or 'hosts' for all "
955+
"host platforms, or 'all' for the build platform and all "
956+
"hosts. Defaults to 'all'"
957+
),
958+
)
959+
960+
host_commands = [configure_host, make_host, package, ci]
882961
if in_source_tree:
883962
host_commands.append(env)
884963
for subcommand in host_commands:
@@ -940,13 +1019,19 @@ def main():
9401019
stream.reconfigure(line_buffering=True)
9411020

9421021
context = parse_args()
1022+
1023+
# Set the CROSS_BUILD_DIR if an argument was provided
1024+
if context.cross_build_dir:
1025+
global CROSS_BUILD_DIR
1026+
CROSS_BUILD_DIR = context.cross_build_dir.resolve()
1027+
9431028
dispatch = {
9441029
"configure-build": configure_build_python,
9451030
"make-build": make_build_python,
9461031
"configure-host": configure_host_python,
9471032
"make-host": make_host_python,
948-
"build": build_all,
949-
"clean": clean_all,
1033+
"build": build_targets,
1034+
"clean": clean_targets,
9501035
"build-testbed": build_testbed,
9511036
"test": run_testbed,
9521037
"package": package,

Android/testbed/app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ val inSourceTree = (
1515

1616
val KNOWN_ABIS = mapOf(
1717
"aarch64-linux-android" to "arm64-v8a",
18+
"arm-linux-androideabi" to "armeabi-v7a",
19+
"i686-linux-android" to "x86",
1820
"x86_64-linux-android" to "x86_64",
1921
)
2022

Doc/c-api/module.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ Feature slots
230230
When creating a module, Python checks the value of this slot
231231
using :c:func:`PyABIInfo_Check`.
232232
233+
This slot is required, except for modules created from
234+
:c:struct:`PyModuleDef`.
235+
233236
.. versionadded:: 3.15
234237
235238
.. c:macro:: Py_mod_multiple_interpreters
@@ -620,9 +623,9 @@ rather than from an extension's :ref:`export hook <extension-export-hook>`.
620623
and the :py:class:`~importlib.machinery.ModuleSpec` *spec*.
621624
622625
The *slots* argument must point to an array of :c:type:`PyModuleDef_Slot`
623-
structures, terminated by an entry slot with slot ID of 0
626+
structures, terminated by an entry with slot ID of 0
624627
(typically written as ``{0}`` or ``{0, NULL}`` in C).
625-
The *slots* argument may not be ``NULL``.
628+
The array must include a :c:data:`Py_mod_abi` entry.
626629
627630
The *spec* argument may be any ``ModuleSpec``-like object, as described
628631
in :c:macro:`Py_mod_create` documentation.

Doc/conf.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,17 @@
572572
stable_abi_file = 'data/stable_abi.dat'
573573
threadsafety_file = 'data/threadsafety.dat'
574574

575+
# Options for notfound.extension
576+
# -------------------------------
577+
578+
if not os.getenv("READTHEDOCS"):
579+
if language_code:
580+
notfound_urls_prefix = (
581+
f'/{language_code.replace("_", "-").lower()}/{version}/'
582+
)
583+
else:
584+
notfound_urls_prefix = f'/{version}/'
585+
575586
# Options for sphinxext-opengraph
576587
# -------------------------------
577588

Doc/extending/first-extension-module.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,19 @@ Define this array just before your export hook:
265265

266266
.. code-block:: c
267267
268+
PyABIInfo_VAR(abi_info);
269+
268270
static PyModuleDef_Slot spam_slots[] = {
271+
{Py_mod_abi, &abi_info},
269272
{Py_mod_name, "spam"},
270273
{Py_mod_doc, "A wonderful module with an example function"},
271274
{0, NULL}
272275
};
273276
277+
The ``PyABIInfo_VAR(abi_info);`` macro and the :c:data:`Py_mod_abi` slot
278+
are a bit of boilerplate that helps prevent extensions compiled for
279+
a different version of Python from crashing the interpreter.
280+
274281
For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
275282
strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.
276283

0 commit comments

Comments
 (0)