Merge branch 'main' into issue-136438-interpreters

This commit is contained in:
Petr Viktorin 2025-12-18 15:06:03 +01:00 committed by GitHub
commit 9735b12d87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1996 changed files with 190992 additions and 69385 deletions

View file

@ -0,0 +1,73 @@
{
"image": "ghcr.io/python/wasicontainer:latest",
"onCreateCommand": [
// Install common tooling.
"dnf",
"install",
"-y",
// For umask fix below.
"/usr/bin/setfacl"
],
"updateContentCommand": {
// Using the shell for `nproc` usage.
"python": "python3 Tools/wasm/wasi build --quiet -- --with-pydebug -C"
},
"postCreateCommand": {
// https://github.com/orgs/community/discussions/26026
"umask fix: workspace": ["sudo", "setfacl", "-bnR", "."],
"umask fix: /tmp": ["sudo", "setfacl", "-bnR", "/tmp"]
},
"customizations": {
"vscode": {
"extensions": [
// Highlighting for Parser/Python.asdl.
"brettcannon.zephyr-asdl",
// Highlighting for configure.ac.
"maelvalais.autoconf",
// C auto-complete.
"ms-vscode.cpptools",
// Python auto-complete.
"ms-python.python"
],
"settings": {
"C_Cpp.default.compilerPath": "/usr/bin/clang",
"C_Cpp.default.cStandard": "c11",
"C_Cpp.default.defines": [
"CONFIG_64",
"Py_BUILD_CORE"
],
"C_Cpp.default.includePath": [
"${workspaceFolder}/*",
"${workspaceFolder}/Include/**"
],
// https://github.com/microsoft/vscode-cpptools/issues/10732
"C_Cpp.errorSquiggles": "disabled",
"editor.insertSpaces": true,
"editor.rulers": [
80
],
"editor.tabSize": 4,
"editor.trimAutoWhitespace": true,
"files.associations": {
"*.h": "c"
},
"files.encoding": "utf8",
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"python.analysis.diagnosticSeverityOverrides": {
// Complains about shadowing the stdlib w/ the stdlib.
"reportShadowedImports": "none",
// Doesn't like _frozen_importlib.
"reportMissingImports": "none"
},
"python.analysis.extraPaths": [
"Lib"
],
"[restructuredtext]": {
"editor.tabSize": 3
}
}
}
}
}

4
.gitattributes vendored
View file

@ -68,6 +68,7 @@ PCbuild/readme.txt dos
**/clinic/*.cpp.h generated
**/clinic/*.h.h generated
*_db.h generated
Doc/_static/tachyon-example-*.html generated
Doc/c-api/lifecycle.dot.svg generated
Doc/data/stable_abi.dat generated
Doc/library/token-list.inc generated
@ -83,10 +84,12 @@ Include/opcode_ids.h generated
Include/token.h generated
Lib/_opcode_metadata.py generated
Lib/keyword.py generated
Lib/idlelib/help.html generated
Lib/test/certdata/*.pem generated
Lib/test/certdata/*.0 generated
Lib/test/levenshtein_examples.json generated
Lib/test/test_stable_abi_ctypes.py generated
Lib/test/test_zoneinfo/data/*.json generated
Lib/token.py generated
Misc/sbom.spdx.json generated
Objects/typeslots.inc generated
@ -103,3 +106,4 @@ Python/stdlib_module_names.h generated
Tools/peg_generator/pegen/grammar_parser.py generated
aclocal.m4 generated
configure generated
*.min.js generated

66
.github/CODEOWNERS vendored
View file

@ -80,11 +80,16 @@ Tools/patchcheck/ @AA-Turner
# ----------------------------------------------------------------------------
# Autotools
configure* @erlend-aasland @corona10 @AA-Turner
Makefile.pre.in @erlend-aasland @AA-Turner
Modules/Setup* @erlend-aasland @AA-Turner
configure* @erlend-aasland @corona10 @AA-Turner @emmatyping
Makefile.pre.in @erlend-aasland @AA-Turner @emmatyping
Modules/makesetup @erlend-aasland @AA-Turner @emmatyping
Modules/Setup* @erlend-aasland @AA-Turner @emmatyping
Tools/build/regen-configure.sh @AA-Turner
# generate-build-details
Tools/build/generate-build-details.py @FFY00
Lib/test/test_build_details.py @FFY00
# ----------------------------------------------------------------------------
# Documentation
@ -121,6 +126,9 @@ Doc/howto/clinic.rst @erlend-aasland @AA-Turner
# C Analyser
Tools/c-analyzer/ @ericsnowcurrently
# C API Documentation Checks
Tools/check-c-api-docs/ @ZeroIntensity
# Fuzzing
Modules/_xxtestfuzz/ @ammaraskar
@ -149,6 +157,7 @@ Lib/test/test_android.py @mhsmith @freakboy3742
# iOS
Doc/using/ios.rst @freakboy3742
Lib/_ios_support.py @freakboy3742
Apple/ @freakboy3742
iOS/ @freakboy3742
# macOS
@ -157,16 +166,16 @@ Lib/_osx_support.py @python/macos-team
Lib/test/test__osx_support.py @python/macos-team
# WebAssembly
Tools/wasm/README.md @brettcannon @freakboy3742
Tools/wasm/README.md @brettcannon @freakboy3742 @emmatyping
# WebAssembly (Emscripten)
Tools/wasm/config.site-wasm32-emscripten @freakboy3742
Tools/wasm/emscripten @freakboy3742
Tools/wasm/config.site-wasm32-emscripten @freakboy3742 @emmatyping
Tools/wasm/emscripten @freakboy3742 @emmatyping
# WebAssembly (WASI)
Tools/wasm/wasi-env @brettcannon
Tools/wasm/wasi.py @brettcannon
Tools/wasm/wasi @brettcannon
Tools/wasm/wasi-env @brettcannon @emmatyping
Tools/wasm/wasi.py @brettcannon @emmatyping
Tools/wasm/wasi @brettcannon @emmatyping
# Windows
PC/ @python/windows-team
@ -239,10 +248,10 @@ Lib/test/test_getpath.py @FFY00
Modules/getpath* @FFY00
# Hashing / ``hash()`` and related
Include/cpython/pyhash.h @gpshead @picnixz @tiran
Include/internal/pycore_pyhash.h @gpshead @picnixz @tiran
Include/pyhash.h @gpshead @picnixz @tiran
Python/pyhash.c @gpshead @picnixz @tiran
Include/cpython/pyhash.h @gpshead @picnixz
Include/internal/pycore_pyhash.h @gpshead @picnixz
Include/pyhash.h @gpshead @picnixz
Python/pyhash.c @gpshead @picnixz
# The import system (including importlib)
**/*import* @brettcannon @ericsnowcurrently @ncoghlan @warsaw
@ -280,10 +289,10 @@ Tools/jit/ @brandtbucher @savannahostrowski @diegorusso
InternalDocs/jit.md @brandtbucher @savannahostrowski @diegorusso @AA-Turner
# Micro-op / μop / Tier 2 Optimiser
Python/optimizer.c @markshannon
Python/optimizer.c @markshannon @Fidget-Spinner
Python/optimizer_analysis.c @markshannon @tomasr8 @Fidget-Spinner
Python/optimizer_bytecodes.c @markshannon @tomasr8 @Fidget-Spinner
Python/optimizer_symbols.c @markshannon @tomasr8
Python/optimizer_symbols.c @markshannon @tomasr8 @Fidget-Spinner
# Parser, Lexer, and Grammar
Grammar/python.gram @pablogsal @lysnikolaou
@ -313,7 +322,7 @@ Tools/build/generate_global_objects.py @ericsnowcurrently
# Remote Debugging
Python/remote_debug.h @pablogsal
Python/remote_debugging.c @pablogsal
Modules/_remote_debugging_module.c @pablogsal @ambv @1st1
Modules/_remote_debugging/ @pablogsal
# Sub-Interpreters
**/*crossinterp* @ericsnowcurrently
@ -369,14 +378,14 @@ Lib/calendar.py @AA-Turner
Lib/test/test_calendar.py @AA-Turner
# Cryptographic Primitives and Applications
**/*hashlib* @gpshead @picnixz @tiran
**/*hashopenssl* @gpshead @picnixz @tiran
**/*hashlib* @gpshead @picnixz
**/*hashopenssl* @gpshead @picnixz
**/*hmac* @gpshead @picnixz
**/*ssl* @gpshead @picnixz
Modules/_hacl/ @gpshead @picnixz
Modules/*blake* @gpshead @picnixz @tiran
Modules/*md5* @gpshead @picnixz @tiran
Modules/*sha* @gpshead @picnixz @tiran
Modules/*blake* @gpshead @picnixz
Modules/*md5* @gpshead @picnixz
Modules/*sha* @gpshead @picnixz
# Codecs
Modules/cjkcodecs/ @corona10
@ -405,11 +414,15 @@ Lib/test/test_dataclasses/ @ericvsmith
# Dates and times
Doc/**/*time.rst @pganssle @abalkin
Doc/library/zoneinfo.rst @pganssle
Include/datetime.h @pganssle @abalkin
Include/internal/pycore_time.h @pganssle @abalkin
Lib/test/test_zoneinfo/ @pganssle
Lib/zoneinfo/ @pganssle
Lib/*time.py @pganssle @abalkin
Lib/test/datetimetester.py @pganssle @abalkin
Lib/test/test_*time.py @pganssle @abalkin
Modules/*zoneinfo* @pganssle
Modules/*time* @pganssle @abalkin
Python/pytime.c @pganssle @abalkin
@ -524,6 +537,11 @@ Lib/pydoc.py @AA-Turner
Lib/pydoc_data/ @AA-Turner
Lib/test/test_pydoc/ @AA-Turner
# Profiling (Sampling)
Doc/library/profiling*.rst @pablogsal
Lib/profiling/ @pablogsal
Lib/test/test_profiling/ @pablogsal
# PyREPL
Lib/_pyrepl/ @pablogsal @lysnikolaou @ambv
Lib/test/test_pyrepl/ @pablogsal @lysnikolaou @ambv
@ -603,9 +621,9 @@ Lib/test/test_zipfile/_path/ @jaraco
Lib/zipfile/_path/ @jaraco
# Zstandard
Lib/compression/zstd/ @AA-Turner
Lib/test/test_zstd.py @AA-Turner
Modules/_zstd/ @AA-Turner
Lib/compression/zstd/ @AA-Turner @emmatyping
Lib/test/test_zstd.py @AA-Turner @emmatyping
Modules/_zstd/ @AA-Turner @emmatyping
# ----------------------------------------------------------------------------

View file

@ -28,23 +28,23 @@ Please be aware that our workflow does deviate slightly from the typical GitHub
project. Details on how to properly submit a pull request are covered in
`Lifecycle of a Pull Request <https://devguide.python.org/getting-started/pull-request-lifecycle.html>`_.
We utilize various bots and status checks to help with this, so do follow the
comments they leave and their "Details" links, respectively. The key points of
our workflow that are not covered by a bot or status check are:
comments they leave and their "Details" links, respectively.
- All discussions that are not directly related to the code in the pull request
should happen on `GitHub Issues <https://github.com/python/cpython/issues>`_.
- Upon your first non-trivial pull request (which includes documentation changes),
feel free to add yourself to ``Misc/ACKS``
The final key part of our workflow is that all discussions that are not
directly related to the code in the pull request should happen on
`GitHub Issues <https://github.com/python/cpython/issues>`__, generally in the
pull request's parent issue.
Setting Expectations
--------------------
Due to the fact that this project is entirely volunteer-run (i.e. no one is paid
to work on Python full-time), we unfortunately can make no guarantees as to if
Due to the fact that this project is run by volunteers,
unfortunately we cannot make any guarantees as to if
or when a core developer will get around to reviewing your pull request.
If no core developer has done a review or responded to changes made because of a
"changes requested" review, please feel free to email python-dev to ask if
someone could take a look at your pull request.
"changes requested" review within a month, you can ask for someone to
review your pull request via a post in the `Core Development Discourse
category <https://discuss.python.org/c/core-dev/23>`__.
Code of Conduct

View file

@ -5,3 +5,6 @@ contact_links:
- name: "Proposing new features"
about: "Submit major feature proposal (e.g. syntax changes) to an ideas forum first."
url: "https://discuss.python.org/c/ideas/6"
- name: "Python Install Manager issues"
about: "Report issues with the Python Install Manager (for Windows)"
url: "https://github.com/python/pymanager/issues"

View file

@ -1,6 +1,7 @@
self-hosted-runner:
# Pending https://github.com/rhysd/actionlint/issues/533
labels: ["windows-11-arm"]
# and https://github.com/rhysd/actionlint/issues/571
labels: ["windows-11-arm", "macos-15-intel"]
config-variables: null

View file

@ -12,6 +12,11 @@ updates:
update-types:
- "version-update:semver-minor"
- "version-update:semver-patch"
cooldown:
# https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns
# Cooldowns protect against supply chain attacks by avoiding the
# highest-risk window immediately after new releases.
default-days: 14
- package-ecosystem: "pip"
directory: "/Tools/"
schedule:
@ -19,3 +24,5 @@ updates:
labels:
- "skip issue"
- "skip news"
cooldown:
default-days: 14

View file

@ -109,20 +109,10 @@ jobs:
python-version: '3.x'
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}-${{ env.pythonLocation }}
- name: Install dependencies
run: sudo ./.github/workflows/posix-deps-apt.sh
- name: Add ccache to PATH
run: echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: false
- name: Configure CPython
run: |
# Build Python with the libpython dynamic library
@ -152,6 +142,9 @@ jobs:
- name: Check for unsupported C global variables
if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME
run: make check-c-globals
- name: Check for undocumented C APIs
run: make check-c-api-docs
build-windows:
name: >-
@ -198,32 +191,23 @@ jobs:
macOS
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-macos == 'true'
strategy:
fail-fast: false
matrix:
# Cirrus and macos-14 are M1, macos-13 is default GHA Intel.
# macOS 13 only runs tests against the GIL-enabled CPython.
# Cirrus used for upstream, macos-14 for forks.
# macos-14 is M1, macos-15-intel is Intel.
# macos-15-intel only runs tests against the GIL-enabled CPython.
os:
- ghcr.io/cirruslabs/macos-runner:sonoma
- macos-14
- macos-13
is-fork: # only used for the exclusion trick
- ${{ github.repository_owner != 'python' }}
- macos-15-intel
free-threading:
- false
- true
exclude:
- os: ghcr.io/cirruslabs/macos-runner:sonoma
is-fork: true
- os: macos-14
is-fork: false
- os: macos-13
- os: macos-15-intel
free-threading: true
uses: ./.github/workflows/reusable-macos.yml
with:
config_hash: ${{ needs.build-context.outputs.config-hash }}
free-threading: ${{ matrix.free-threading }}
os: ${{ matrix.os }}
@ -233,7 +217,7 @@ jobs:
${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }}
${{ fromJSON(matrix.bolt) && '(bolt)' || '' }}
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
strategy:
fail-fast: false
matrix:
@ -255,7 +239,6 @@ jobs:
bolt: true
uses: ./.github/workflows/reusable-ubuntu.yml
with:
config_hash: ${{ needs.build-context.outputs.config-hash }}
bolt-optimizations: ${{ matrix.bolt }}
free-threading: ${{ matrix.free-threading }}
os: ${{ matrix.os }}
@ -265,7 +248,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
strategy:
fail-fast: false
matrix:
@ -273,7 +256,7 @@ jobs:
# Keep 1.1.1w in our list despite it being upstream EOL and otherwise
# unsupported as it most resembles other 1.1.1-work-a-like ssl APIs
# supported by important vendors such as AWS-LC.
openssl_ver: [1.1.1w, 3.0.17, 3.2.5, 3.3.4, 3.4.2, 3.5.2]
openssl_ver: [1.1.1w, 3.0.18, 3.2.6, 3.3.5, 3.4.3, 3.5.4]
# See Tools/ssl/make_ssl_data.py for notes on adding a new version
env:
OPENSSL_VER: ${{ matrix.openssl_ver }}
@ -286,11 +269,6 @@ jobs:
persist-credentials: false
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Install dependencies
@ -312,10 +290,6 @@ jobs:
- name: Add ccache to PATH
run: |
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: false
- name: Configure CPython
run: ./configure CFLAGS="-fdiagnostics-format=json" --config-cache --enable-slower-safety --with-pydebug --with-openssl="$OPENSSL_DIR"
- name: Build CPython
@ -330,7 +304,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
strategy:
fail-fast: false
matrix:
@ -347,11 +321,6 @@ jobs:
persist-credentials: false
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Install dependencies
@ -378,10 +347,6 @@ jobs:
- name: Add ccache to PATH
run: |
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: false
- name: Configure CPython
run: |
./configure CFLAGS="-fdiagnostics-format=json" \
@ -403,15 +368,14 @@ jobs:
build-android:
name: Android (${{ matrix.arch }})
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-android == 'true'
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
# Use the same runs-on configuration as build-macos and build-ubuntu.
- arch: aarch64
runs-on: ${{ github.repository_owner == 'python' && 'ghcr.io/cirruslabs/macos-runner:sonoma' || 'macos-14' }}
runs-on: macos-14
- arch: x86_64
runs-on: ubuntu-24.04
@ -421,24 +385,45 @@ jobs:
with:
persist-credentials: false
- name: Build and test
run: ./Android/android.py ci ${{ matrix.arch }}-linux-android
run: ./Android/android.py ci --fast-ci ${{ matrix.arch }}-linux-android
build-ios:
name: iOS
needs: build-context
if: needs.build-context.outputs.run-ios == 'true'
timeout-minutes: 60
runs-on: macos-14
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# GitHub recommends explicitly selecting the desired Xcode version:
# https://github.com/actions/runner-images/issues/12541#issuecomment-3083850140
# This became a necessity as a result of
# https://github.com/actions/runner-images/issues/12541 and
# https://github.com/actions/runner-images/issues/12751.
- name: Select Xcode version
run: |
sudo xcode-select --switch /Applications/Xcode_15.4.app
- name: Build and test
run: python3 Apple ci iOS --fast-ci --simulator 'iPhone SE (3rd generation),OS=17.5'
build-wasi:
name: 'WASI'
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-wasi == 'true'
uses: ./.github/workflows/reusable-wasi.yml
with:
config_hash: ${{ needs.build-context.outputs.config-hash }}
test-hypothesis:
name: "Hypothesis tests on Ubuntu"
runs-on: ubuntu-24.04
timeout-minutes: 60
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
env:
OPENSSL_VER: 3.0.16
OPENSSL_VER: 3.0.18
PYTHONSTRICTEXTENSIONBUILD: 1
steps:
- uses: actions/checkout@v4
@ -465,10 +450,6 @@ jobs:
- name: Add ccache to PATH
run: |
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: false
- name: Setup directory envs for out-of-tree builds
run: |
echo "CPYTHON_RO_SRCDIR=$(realpath -m "${GITHUB_WORKSPACE}"/../cpython-ro-srcdir)" >> "$GITHUB_ENV"
@ -479,11 +460,6 @@ jobs:
run: sudo mount --bind -o ro "$GITHUB_WORKSPACE" "$CPYTHON_RO_SRCDIR"
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: ${{ env.CPYTHON_BUILDDIR }}/config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Configure CPython out-of-tree
working-directory: ${{ env.CPYTHON_BUILDDIR }}
run: |
@ -552,13 +528,13 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 60
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04]
env:
OPENSSL_VER: 3.0.16
OPENSSL_VER: 3.0.18
PYTHONSTRICTEXTENSIONBUILD: 1
ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0
steps:
@ -567,11 +543,6 @@ jobs:
persist-credentials: false
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Install dependencies
@ -597,11 +568,6 @@ jobs:
- name: Add ccache to PATH
run: |
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: ${{ github.event_name == 'push' }}
max-size: "200M"
- name: Configure CPython
run: ./configure --config-cache --with-address-sanitizer --without-pymalloc
- name: Build CPython
@ -615,7 +581,7 @@ jobs:
# ${{ '' } is a hack to nest jobs under the same sidebar category.
name: Sanitizers${{ '' }} # zizmor: ignore[obfuscation]
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
strategy:
fail-fast: false
matrix:
@ -633,7 +599,6 @@ jobs:
uses: ./.github/workflows/reusable-san.yml
with:
sanitizer: ${{ matrix.sanitizer }}
config_hash: ${{ needs.build-context.outputs.config-hash }}
free-threading: ${{ matrix.free-threading }}
cross-build-linux:
@ -641,18 +606,13 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
needs: build-context
if: needs.build-context.outputs.run-tests == 'true'
if: needs.build-context.outputs.run-ubuntu == 'true'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}
- name: Register gcc problem matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Set build dir
@ -732,6 +692,7 @@ jobs:
- build-ubuntu-ssltests-awslc
- build-ubuntu-ssltests-openssl
- build-android
- build-ios
- build-wasi
- test-hypothesis
- build-asan
@ -751,24 +712,24 @@ jobs:
test-hypothesis,
cifuzz,
allowed-skips: >-
${{
!fromJSON(needs.build-context.outputs.run-docs)
&& '
check-docs,
'
|| ''
}}
${{ !fromJSON(needs.build-context.outputs.run-docs) && 'check-docs,' || '' }}
${{
needs.build-context.outputs.run-tests != 'true'
&& '
check-autoconf-regen,
check-generated-files,
build-macos,
'
|| ''
}}
${{ !fromJSON(needs.build-context.outputs.run-windows-tests) && 'build-windows,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-ci-fuzz) && 'cifuzz,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-macos) && 'build-macos,' || '' }}
${{
!fromJSON(needs.build-context.outputs.run-ubuntu)
&& '
build-ubuntu,
build-ubuntu-ssltests-awslc,
build-ubuntu-ssltests-openssl,
build-android,
build-wasi,
test-hypothesis,
build-asan,
build-san,
@ -776,18 +737,7 @@ jobs:
'
|| ''
}}
${{
!fromJSON(needs.build-context.outputs.run-windows-tests)
&& '
build-windows,
'
|| ''
}}
${{
!fromJSON(needs.build-context.outputs.run-ci-fuzz)
&& '
cifuzz,
'
|| ''
}}
${{ !fromJSON(needs.build-context.outputs.run-android) && 'build-android,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-ios) && 'build-ios,' || '' }}
${{ !fromJSON(needs.build-context.outputs.run-wasi) && 'build-wasi,' || '' }}
jobs: ${{ toJSON(needs) }}

View file

@ -68,20 +68,20 @@ jobs:
- true
- false
llvm:
- 19
- 21
include:
- target: i686-pc-windows-msvc/msvc
architecture: Win32
runner: windows-latest
runner: windows-2022
- target: x86_64-pc-windows-msvc/msvc
architecture: x64
runner: windows-latest
runner: windows-2022
- target: aarch64-pc-windows-msvc/msvc
architecture: ARM64
runner: windows-11-arm
- target: x86_64-apple-darwin/clang
architecture: x86_64
runner: macos-13
runner: macos-15-intel
- target: aarch64-apple-darwin/clang
architecture: aarch64
runner: macos-14
@ -106,15 +106,10 @@ jobs:
./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '' }} -p ${{ matrix.architecture }}
./PCbuild/rt.bat ${{ matrix.debug && '-d' || '' }} -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
# The `find` line is required as a result of https://github.com/actions/runner-images/issues/9966.
# This is a bug in the macOS runner image where the pre-installed Python is installed in the same
# directory as the Homebrew Python, which causes the build to fail for macos-13. This line removes
# the symlink to the pre-installed Python so that the Homebrew Python is used instead.
- name: macOS
if: runner.os == 'macOS'
run: |
brew update
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
brew install llvm@${{ matrix.llvm }}
export SDKROOT="$(xcrun --show-sdk-path)"
# Set MACOSX_DEPLOYMENT_TARGET and -Werror=unguarded-availability to
@ -134,30 +129,81 @@ jobs:
make all --jobs 4
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
# XXX: GH-133171
# jit-with-disabled-gil:
# name: Free-Threaded (Debug)
# needs: interpreter
# runs-on: ubuntu-24.04
# timeout-minutes: 90
# strategy:
# fail-fast: false
# matrix:
# llvm:
# - 19
# steps:
# - uses: actions/checkout@v4
# with:
# persist-credentials: false
# - uses: actions/setup-python@v5
# with:
# python-version: '3.11'
# - name: Build with JIT enabled and GIL disabled
# run: |
# sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
# export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
# ./configure --enable-experimental-jit --with-pydebug --disable-gil
# make all --jobs 4
# - name: Run tests
# run: |
# ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
jit-with-disabled-gil:
name: Free-Threaded (Debug)
needs: interpreter
runs-on: ubuntu-24.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
llvm:
- 21
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build with JIT enabled and GIL disabled
run: |
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
./configure --enable-experimental-jit --with-pydebug --disable-gil
make all --jobs 4
- name: Run tests
run: |
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
continue-on-error: true
no-opt-jit:
name: JIT without optimizations (Debug)
needs: interpreter
runs-on: ubuntu-24.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
llvm:
- 21
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build with JIT
run: |
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
./configure --enable-experimental-jit --with-pydebug
make all --jobs 4
- name: Run tests without optimizations
run: |
PYTHON_UOPS_OPTIMIZE=0 ./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
tail-call-jit:
name: JIT with tail calling interpreter
needs: interpreter
runs-on: ubuntu-24.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
llvm:
- 21
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build with JIT and tailcall
run: |
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
CC=clang-${{ matrix.llvm }} ./configure --enable-experimental-jit --with-tail-call-interp --with-pydebug
make all --jobs 4

View file

@ -16,6 +16,7 @@ on:
- "Tools/build/check_extension_modules.py"
- "Tools/build/check_warnings.py"
- "Tools/build/compute-changes.py"
- "Tools/build/consts_getter.py"
- "Tools/build/deepfreeze.py"
- "Tools/build/generate-build-details.py"
- "Tools/build/generate_sbom.py"
@ -25,6 +26,7 @@ on:
- "Tools/build/update_file.py"
- "Tools/build/verify_ensurepip_wheels.py"
- "Tools/cases_generator/**"
- "Tools/check-c-api-docs/**"
- "Tools/clinic/**"
- "Tools/jit/**"
- "Tools/peg_generator/**"
@ -57,6 +59,7 @@ jobs:
"Lib/tomllib",
"Tools/build",
"Tools/cases_generator",
"Tools/check-c-api-docs",
"Tools/clinic",
"Tools/jit",
"Tools/peg_generator",

View file

@ -1,31 +0,0 @@
name: Update GH projects
on:
issues:
types:
- opened
- labeled
permissions:
contents: read
jobs:
add-to-project:
name: Add issues to projects
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
# if an issue has any of these labels, it will be added
# to the corresponding project
- { project: 2, label: "release-blocker, deferred-blocker" }
- { project: 32, label: sprint }
steps:
- uses: actions/add-to-project@v1.0.0
with:
project-url: https://github.com/orgs/python/projects/${{ matrix.project }}
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
labeled: ${{ matrix.label }}

View file

@ -17,24 +17,36 @@ on: # yamllint disable-line rule:truthy
# || 'falsy-branch'
# }}
#
config-hash:
description: Config hash value for use in cache keys
value: ${{ jobs.compute-changes.outputs.config-hash }} # str
run-docs:
description: Whether to build the docs
value: ${{ jobs.compute-changes.outputs.run-docs }} # bool
run-tests:
description: Whether to run the regular tests
value: ${{ jobs.compute-changes.outputs.run-tests }} # bool
run-windows-tests:
description: Whether to run the Windows tests
value: ${{ jobs.compute-changes.outputs.run-windows-tests }} # bool
run-windows-msi:
description: Whether to run the MSI installer smoke tests
value: ${{ jobs.compute-changes.outputs.run-windows-msi }} # bool
run-android:
description: Whether to run the Android tests
value: ${{ jobs.compute-changes.outputs.run-android }} # bool
run-ci-fuzz:
description: Whether to run the CIFuzz job
value: ${{ jobs.compute-changes.outputs.run-ci-fuzz }} # bool
run-docs:
description: Whether to build the docs
value: ${{ jobs.compute-changes.outputs.run-docs }} # bool
run-ios:
description: Whether to run the iOS tests
value: ${{ jobs.compute-changes.outputs.run-ios }} # bool
run-macos:
description: Whether to run the macOS tests
value: ${{ jobs.compute-changes.outputs.run-macos }} # bool
run-tests:
description: Whether to run the regular tests
value: ${{ jobs.compute-changes.outputs.run-tests }} # bool
run-ubuntu:
description: Whether to run the Ubuntu tests
value: ${{ jobs.compute-changes.outputs.run-ubuntu }} # bool
run-wasi:
description: Whether to run the WASI tests
value: ${{ jobs.compute-changes.outputs.run-wasi }} # bool
run-windows-msi:
description: Whether to run the MSI installer smoke tests
value: ${{ jobs.compute-changes.outputs.run-windows-msi }} # bool
run-windows-tests:
description: Whether to run the Windows tests
value: ${{ jobs.compute-changes.outputs.run-windows-tests }} # bool
jobs:
compute-changes:
@ -42,10 +54,14 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
config-hash: ${{ steps.config-hash.outputs.hash }}
run-android: ${{ steps.changes.outputs.run-android }}
run-ci-fuzz: ${{ steps.changes.outputs.run-ci-fuzz }}
run-docs: ${{ steps.changes.outputs.run-docs }}
run-ios: ${{ steps.changes.outputs.run-ios }}
run-macos: ${{ steps.changes.outputs.run-macos }}
run-tests: ${{ steps.changes.outputs.run-tests }}
run-ubuntu: ${{ steps.changes.outputs.run-ubuntu }}
run-wasi: ${{ steps.changes.outputs.run-wasi }}
run-windows-msi: ${{ steps.changes.outputs.run-windows-msi }}
run-windows-tests: ${{ steps.changes.outputs.run-windows-tests }}
steps:
@ -100,8 +116,3 @@ jobs:
GITHUB_EVENT_NAME: ${{ github.event_name }}
CCF_TARGET_REF: ${{ github.base_ref || github.event.repository.default_branch }}
CCF_HEAD_REF: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Compute hash for config cache key
id: config-hash
run: |
echo "hash=${{ hashFiles('configure', 'configure.ac', '.github/workflows/build.yml') }}" >> "$GITHUB_OUTPUT"

View file

@ -3,9 +3,6 @@ name: Reusable macOS
on:
workflow_call:
inputs:
config_hash:
required: true
type: string
free-threading:
required: false
type: boolean
@ -36,16 +33,11 @@ jobs:
persist-credentials: false
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ inputs.config_hash }}
- name: Install Homebrew dependencies
run: |
brew install pkg-config openssl@3.0 xz gdbm tcl-tk@8 make
brew install pkg-config openssl@3.0 xz gdbm tcl-tk@9 make
# Because alternate versions are not symlinked into place by default:
brew link --overwrite tcl-tk@8
brew link --overwrite tcl-tk@9
- name: Configure CPython
run: |
MACOSX_DEPLOYMENT_TARGET=10.15 \
@ -60,15 +52,15 @@ jobs:
--prefix=/opt/python-dev \
--with-openssl="$(brew --prefix openssl@3.0)"
- name: Build CPython
if : ${{ inputs.free-threading || inputs.os != 'macos-13' }}
if : ${{ inputs.free-threading || inputs.os != 'macos-15-intel' }}
run: gmake -j8
- name: Build CPython for compiler warning check
if : ${{ !inputs.free-threading && inputs.os == 'macos-13' }}
if : ${{ !inputs.free-threading && inputs.os == 'macos-15-intel' }}
run: set -o pipefail; gmake -j8 --output-sync 2>&1 | tee compiler_output_macos.txt
- name: Display build info
run: make pythoninfo
- name: Check compiler warnings
if : ${{ !inputs.free-threading && inputs.os == 'macos-13' }}
if : ${{ !inputs.free-threading && inputs.os == 'macos-15-intel' }}
run: >-
python3 Tools/build/check_warnings.py
--compiler-output-file-path=compiler_output_macos.txt

View file

@ -6,9 +6,6 @@ on:
sanitizer:
required: true
type: string
config_hash:
required: true
type: string
free-threading:
description: Whether to use free-threaded mode
required: false
@ -34,11 +31,6 @@ jobs:
persist-credentials: false
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ inputs.sanitizer }}-${{ inputs.config_hash }}
- name: Install dependencies
run: |
sudo ./.github/workflows/posix-deps-apt.sh
@ -77,11 +69,6 @@ jobs:
- name: Add ccache to PATH
run: |
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: ${{ github.event_name == 'push' }}
max-size: "200M"
- name: Configure CPython
run: >-
./configure

View file

@ -3,9 +3,6 @@ name: Reusable Ubuntu
on:
workflow_call:
inputs:
config_hash:
required: true
type: string
bolt-optimizations:
description: Whether to enable BOLT optimizations
required: false
@ -30,7 +27,7 @@ jobs:
runs-on: ${{ inputs.os }}
timeout-minutes: 60
env:
OPENSSL_VER: 3.0.15
OPENSSL_VER: 3.0.18
PYTHONSTRICTEXTENSIONBUILD: 1
TERM: linux
steps:
@ -64,11 +61,6 @@ jobs:
- name: Add ccache to PATH
run: |
echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: Configure ccache action
uses: hendrikmuhs/ccache-action@v1.2
with:
save: ${{ github.event_name == 'push' }}
max-size: "200M"
- name: Setup directory envs for out-of-tree builds
run: |
echo "CPYTHON_RO_SRCDIR=$(realpath -m "${GITHUB_WORKSPACE}"/../cpython-ro-srcdir)" >> "$GITHUB_ENV"
@ -79,11 +71,6 @@ jobs:
run: sudo mount --bind -o ro "$GITHUB_WORKSPACE" "$CPYTHON_RO_SRCDIR"
- name: Runner image version
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: Restore config.cache
uses: actions/cache@v4
with:
path: ${{ env.CPYTHON_BUILDDIR }}/config.cache
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ inputs.config_hash }}
- name: Configure CPython out-of-tree
working-directory: ${{ env.CPYTHON_BUILDDIR }}
# `test_unpickle_module_race` writes to the source directory, which is

View file

@ -2,10 +2,6 @@ name: Reusable WASI
on:
workflow_call:
inputs:
config_hash:
required: true
type: string
env:
FORCE_COLOR: 1
@ -13,11 +9,11 @@ env:
jobs:
build-wasi-reusable:
name: 'build and test'
runs-on: ubuntu-24.04
runs-on: ubuntu-24.04-arm
timeout-minutes: 60
env:
WASMTIME_VERSION: 22.0.0
WASI_SDK_VERSION: 24
WASMTIME_VERSION: 38.0.3
WASI_SDK_VERSION: 29
WASI_SDK_PATH: /opt/wasi-sdk
CROSS_BUILD_PYTHON: cross-build/build
CROSS_BUILD_WASI: cross-build/wasm32-wasip1
@ -40,13 +36,8 @@ jobs:
if: steps.cache-wasi-sdk.outputs.cache-hit != 'true'
run: |
mkdir "${WASI_SDK_PATH}" && \
curl -s -S --location "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux.tar.gz" | \
curl -s -S --location "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-arm64-linux.tar.gz" | \
tar --strip-components 1 --directory "${WASI_SDK_PATH}" --extract --gunzip
- name: "Configure ccache action"
uses: hendrikmuhs/ccache-action@v1.2
with:
save: ${{ github.event_name == 'push' }}
max-size: "200M"
- name: "Add ccache to PATH"
run: echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV"
- name: "Install Python"
@ -55,29 +46,15 @@ jobs:
python-version: '3.x'
- name: "Runner image version"
run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV"
- name: "Restore Python build config.cache"
uses: actions/cache@v4
with:
path: ${{ env.CROSS_BUILD_PYTHON }}/config.cache
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python.
# Include the hash of `Tools/wasm/wasi.py` as it may change the environment variables.
# (Make sure to keep the key in sync with the other config.cache step below.)
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ env.WASI_SDK_VERSION }}-${{ env.WASMTIME_VERSION }}-${{ inputs.config_hash }}-${{ hashFiles('Tools/wasm/wasi.py') }}-${{ env.pythonLocation }}
- name: "Configure build Python"
run: python3 Tools/wasm/wasi.py configure-build-python -- --config-cache --with-pydebug
run: python3 Tools/wasm/wasi configure-build-python -- --config-cache --with-pydebug
- name: "Make build Python"
run: python3 Tools/wasm/wasi.py make-build-python
- name: "Restore host config.cache"
uses: actions/cache@v4
with:
path: ${{ env.CROSS_BUILD_WASI }}/config.cache
# Should be kept in sync with the other config.cache step above.
key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ env.WASI_SDK_VERSION }}-${{ env.WASMTIME_VERSION }}-${{ inputs.config_hash }}-${{ hashFiles('Tools/wasm/wasi.py') }}-${{ env.pythonLocation }}
run: python3 Tools/wasm/wasi make-build-python
- name: "Configure host"
# `--with-pydebug` inferred from configure-build-python
run: python3 Tools/wasm/wasi.py configure-host -- --config-cache
run: python3 Tools/wasm/wasi configure-host -- --config-cache
- name: "Make host"
run: python3 Tools/wasm/wasi.py make-host
run: python3 Tools/wasm/wasi make-host
- name: "Display build info"
run: make --directory "${CROSS_BUILD_WASI}" pythoninfo
- name: "Test"

View file

@ -17,7 +17,7 @@ env:
jobs:
build:
name: installer for ${{ inputs.arch }}
runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-latest' }}
runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2022' }}
timeout-minutes: 60
env:
ARCH: ${{ inputs.arch }}

View file

@ -21,7 +21,7 @@ env:
jobs:
build:
name: Build and test (${{ inputs.arch }})
runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-latest' }}
runs-on: ${{ inputs.arch == 'arm64' && 'windows-11-arm' || 'windows-2022' }}
timeout-minutes: 60
env:
ARCH: ${{ inputs.arch }}

View file

@ -49,16 +49,16 @@ jobs:
include:
# - target: i686-pc-windows-msvc/msvc
# architecture: Win32
# runner: windows-latest
# runner: windows-2022
- target: x86_64-pc-windows-msvc/msvc
architecture: x64
runner: windows-latest
runner: windows-2022
# - target: aarch64-pc-windows-msvc/msvc
# architecture: ARM64
# runner: windows-latest
# runner: windows-2022
- target: x86_64-apple-darwin/clang
architecture: x86_64
runner: macos-13
runner: macos-15-intel
- target: aarch64-apple-darwin/clang
architecture: aarch64
runner: macos-14
@ -101,17 +101,10 @@ jobs:
set LLVMInstallDir=C:\Program Files\LLVM
./PCbuild/build.bat --tail-call-interp -p ${{ matrix.architecture }}
# The `find` line is required as a result of https://github.com/actions/runner-images/issues/9966.
# This is a bug in the macOS runner image where the pre-installed Python is installed in the same
# directory as the Homebrew Python, which causes the build to fail for macos-13. This line removes
# the symlink to the pre-installed Python so that the Homebrew Python is used instead.
# Note: when a new LLVM is released, the homebrew installation directory changes, so the builds will fail.
# We either need to upgrade LLVM or change the directory being pointed to.
- name: Native macOS (release)
if: runner.os == 'macOS'
run: |
brew update
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
brew install llvm@${{ matrix.llvm }}
export SDKROOT="$(xcrun --show-sdk-path)"
export PATH="/usr/local/opt/llvm@${{ matrix.llvm }}/bin:$PATH"

20
.gitignore vendored
View file

@ -45,6 +45,7 @@ gmon.out
.pytest_cache/
.ruff_cache/
.DS_Store
.pixi/
*.exe
@ -71,15 +72,15 @@ Lib/test/data/*
/Makefile
/Makefile.pre
/iOSTestbed.*
iOS/Frameworks/
iOS/Resources/Info.plist
iOS/testbed/build
iOS/testbed/Python.xcframework/ios-*/bin
iOS/testbed/Python.xcframework/ios-*/include
iOS/testbed/Python.xcframework/ios-*/lib
iOS/testbed/Python.xcframework/ios-*/Python.framework
iOS/testbed/iOSTestbed.xcodeproj/project.xcworkspace
iOS/testbed/iOSTestbed.xcodeproj/xcuserdata
Apple/iOS/Frameworks/
Apple/iOS/Resources/Info.plist
Apple/testbed/build
Apple/testbed/Python.xcframework/*/bin
Apple/testbed/Python.xcframework/*/include
Apple/testbed/Python.xcframework/*/lib
Apple/testbed/Python.xcframework/*/Python.framework
Apple/testbed/*Testbed.xcodeproj/project.xcworkspace
Apple/testbed/*Testbed.xcodeproj/xcuserdata
Mac/Makefile
Mac/PythonLauncher/Info.plist
Mac/PythonLauncher/Makefile
@ -135,7 +136,6 @@ Tools/unicode/data/
/config.log
/config.status
/config.status.lineno
# hendrikmuhs/ccache-action@v1
/.ccache
/cross-build/
/jit_stencils*.h

View file

@ -1,23 +1,43 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.8
rev: v0.13.2
hooks:
- id: ruff
- id: ruff-check
name: Run Ruff (lint) on Apple/
args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
files: ^Apple/
- id: ruff-check
name: Run Ruff (lint) on Doc/
args: [--exit-non-zero-on-fix]
files: ^Doc/
- id: ruff
- id: ruff-check
name: Run Ruff (lint) on Lib/test/
args: [--exit-non-zero-on-fix]
files: ^Lib/test/
- id: ruff
- id: ruff-check
name: Run Ruff (lint) on Tools/build/
args: [--exit-non-zero-on-fix, --config=Tools/build/.ruff.toml]
files: ^Tools/build/
- id: ruff
- id: ruff-check
name: Run Ruff (lint) on Tools/i18n/
args: [--exit-non-zero-on-fix, --config=Tools/i18n/.ruff.toml]
files: ^Tools/i18n/
- id: ruff-check
name: Run Ruff (lint) on Argument Clinic
args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml]
files: ^Tools/clinic/|Lib/test/test_clinic.py
- id: ruff-check
name: Run Ruff (lint) on Tools/peg_generator/
args: [--exit-non-zero-on-fix, --config=Tools/peg_generator/.ruff.toml]
files: ^Tools/peg_generator/
- id: ruff-check
name: Run Ruff (lint) on Tools/wasm/
args: [--exit-non-zero-on-fix, --config=Tools/wasm/.ruff.toml]
files: ^Tools/wasm/
- id: ruff-format
name: Run Ruff (format) on Apple/
args: [--exit-non-zero-on-fix, --config=Apple/.ruff.toml]
files: ^Apple
- id: ruff-format
name: Run Ruff (format) on Doc/
args: [--check]
@ -26,9 +46,13 @@ repos:
name: Run Ruff (format) on Tools/build/check_warnings.py
args: [--check, --config=Tools/build/.ruff.toml]
files: ^Tools/build/check_warnings.py
- id: ruff-format
name: Run Ruff (format) on Tools/wasm/
args: [--check, --config=Tools/wasm/.ruff.toml]
files: ^Tools/wasm/
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.1.0
rev: 25.9.0
hooks:
- id: black
name: Run Black on Tools/jit/
@ -39,7 +63,6 @@ repos:
hooks:
- id: remove-tabs
types: [python]
exclude: ^Tools/c-analyzer/cpython/_parser.py
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
@ -60,7 +83,7 @@ repos:
files: '^\.github/CODEOWNERS|\.(gram)$'
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.2
rev: 0.34.0
hooks:
- id: check-dependabot
- id: check-github-workflows
@ -72,7 +95,7 @@ repos:
- id: actionlint
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.11.0
rev: v1.14.1
hooks:
- id: zizmor

View file

@ -2,6 +2,7 @@
import asyncio
import argparse
import json
import os
import platform
import re
@ -28,6 +29,7 @@ in_source_tree = (
ANDROID_DIR.name == "Android" and (PYTHON_DIR / "pyconfig.h.in").exists()
)
ENV_SCRIPT = ANDROID_DIR / "android-env.sh"
TESTBED_DIR = ANDROID_DIR / "testbed"
CROSS_BUILD_DIR = PYTHON_DIR / "cross-build"
@ -128,12 +130,11 @@ def android_env(host):
sysconfig_filename = next(sysconfig_files).name
host = re.fullmatch(r"_sysconfigdata__android_(.+).py", sysconfig_filename)[1]
env_script = ANDROID_DIR / "android-env.sh"
env_output = subprocess.run(
f"set -eu; "
f"HOST={host}; "
f"PREFIX={prefix}; "
f". {env_script}; "
f". {ENV_SCRIPT}; "
f"export",
check=True, shell=True, capture_output=True, encoding='utf-8',
).stdout
@ -150,7 +151,7 @@ def android_env(host):
env[key] = value
if not env:
raise ValueError(f"Found no variables in {env_script.name} output:\n"
raise ValueError(f"Found no variables in {ENV_SCRIPT.name} output:\n"
+ env_output)
return env
@ -184,10 +185,16 @@ def make_build_python(context):
run(["make", "-j", str(os.cpu_count())])
# To create new builds of these dependencies, usually all that's necessary is to
# push a tag to the cpython-android-source-deps repository, and GitHub Actions
# will do the rest.
#
# If you're a member of the Python core team, and you'd like to be able to push
# these tags yourself, please contact Malcolm Smith or Russell Keith-Magee.
def unpack_deps(host, prefix_dir):
os.chdir(prefix_dir)
deps_url = "https://github.com/beeware/cpython-android-source-deps/releases/download"
for name_ver in ["bzip2-1.0.8-3", "libffi-3.4.4-3", "openssl-3.0.15-4",
for name_ver in ["bzip2-1.0.8-3", "libffi-3.4.4-3", "openssl-3.0.18-0",
"sqlite-3.50.4-0", "xz-5.4.6-1", "zstd-1.5.7-1"]:
filename = f"{name_ver}-{host}.tar.gz"
download(f"{deps_url}/{name_ver}/{filename}")
@ -274,15 +281,30 @@ def clean_all(context):
def setup_ci():
# https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
if "GITHUB_ACTIONS" in os.environ and platform.system() == "Linux":
run(
["sudo", "tee", "/etc/udev/rules.d/99-kvm4all.rules"],
input='KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"\n',
text=True,
)
run(["sudo", "udevadm", "control", "--reload-rules"])
run(["sudo", "udevadm", "trigger", "--name-match=kvm"])
if "GITHUB_ACTIONS" in os.environ:
# Enable emulator hardware acceleration
# (https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/).
if platform.system() == "Linux":
run(
["sudo", "tee", "/etc/udev/rules.d/99-kvm4all.rules"],
input='KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"\n',
text=True,
)
run(["sudo", "udevadm", "control", "--reload-rules"])
run(["sudo", "udevadm", "trigger", "--name-match=kvm"])
# Free up disk space by deleting unused versions of the NDK
# (https://github.com/freakboy3742/pyspamsum/pull/108).
for line in ENV_SCRIPT.read_text().splitlines():
if match := re.fullmatch(r"ndk_version=(.+)", line):
ndk_version = match[1]
break
else:
raise ValueError(f"Failed to find NDK version in {ENV_SCRIPT.name}")
for item in (android_home / "ndk").iterdir():
if item.name[0].isdigit() and item.name != ndk_version:
delete_glob(item)
def setup_sdk():
@ -546,27 +568,33 @@ async def gradle_task(context):
task_prefix = "connected"
env["ANDROID_SERIAL"] = context.connected
if context.command:
mode = "-c"
module = context.command
else:
mode = "-m"
module = context.module or "test"
if context.ci_mode:
context.args[0:0] = [
# See _add_ci_python_opts in libregrtest/main.py.
"-W", "error", "-bb", "-E",
# Randomization is disabled because order-dependent failures are
# much less likely to pass on a rerun in single-process mode.
"-m", "test",
f"--{context.ci_mode}-ci", "--single-process", "--no-randomize"
]
if not any(arg in context.args for arg in ["-c", "-m"]):
context.args[0:0] = ["-m", "test"]
args = [
gradlew, "--console", "plain", f"{task_prefix}DebugAndroidTest",
] + [
# Build-time properties
f"-Ppython.{name}={value}"
f"-P{name}={value}"
for name, value in [
("sitePackages", context.site_packages), ("cwd", context.cwd)
] if value
] + [
# Runtime properties
f"-Pandroid.testInstrumentationRunnerArguments.python{name}={value}"
for name, value in [
("Mode", mode), ("Module", module), ("Args", join_command(context.args))
] if value
("python.sitePackages", context.site_packages),
("python.cwd", context.cwd),
(
"android.testInstrumentationRunnerArguments.pythonArgs",
json.dumps(context.args),
),
]
if value
]
if context.verbose >= 2:
args.append("--info")
@ -734,17 +762,14 @@ def ci(context):
else:
with TemporaryDirectory(prefix=SCRIPT_NAME) as temp_dir:
print("::group::Tests")
# Prove the package is self-contained by using it to run the tests.
shutil.unpack_archive(package_path, temp_dir)
# Arguments are similar to --fast-ci, but in single-process mode.
launcher_args = ["--managed", "maxVersion", "-v"]
test_args = [
"--single-process", "--fail-env-changed", "--rerun", "--slowest",
"--verbose3", "-u", "all,-cpu", "--timeout=600"
launcher_args = [
"--managed", "maxVersion", "-v", f"--{context.ci_mode}-ci"
]
run(
["./android.py", "test", *launcher_args, "--", *test_args],
["./android.py", "test", *launcher_args],
cwd=temp_dir
)
print("::endgroup::")
@ -827,18 +852,11 @@ def parse_args():
test.add_argument(
"--cwd", metavar="DIR", type=abspath,
help="Directory to copy as the app's working directory.")
mode_group = test.add_mutually_exclusive_group()
mode_group.add_argument(
"-c", dest="command", help="Execute the given Python code.")
mode_group.add_argument(
"-m", dest="module", help="Execute the module with the given name.")
test.epilog = (
"If neither -c nor -m are passed, the default is '-m test', which will "
"run Python's own test suite.")
test.add_argument(
"args", nargs="*", help=f"Arguments to add to sys.argv. "
f"Separate them from {SCRIPT_NAME}'s own arguments with `--`.")
"args", nargs="*", help=f"Python command-line arguments. "
f"Separate them from {SCRIPT_NAME}'s own arguments with `--`. "
f"If neither -c nor -m are included, `-m test` will be prepended, "
f"which will run Python's own test suite.")
# Package arguments.
for subcommand in [package, ci]:
@ -846,6 +864,16 @@ def parse_args():
"-g", action="store_true", default=False, dest="debug",
help="Include debug information in package")
# CI arguments
for subcommand in [test, ci]:
group = subcommand.add_mutually_exclusive_group(required=subcommand is ci)
group.add_argument(
"--fast-ci", action="store_const", dest="ci_mode", const="fast",
help="Add test arguments for GitHub Actions")
group.add_argument(
"--slow-ci", action="store_const", dest="ci_mode", const="slow",
help="Add test arguments for buildbots")
return parser.parse_args()

View file

@ -47,7 +47,7 @@ for ((i, prefix) in prefixes.withIndex()) {
val libDir = file("$prefix/lib")
val version = run {
for (filename in libDir.list()!!) {
"""python(\d+\.\d+)""".toRegex().matchEntire(filename)?.let {
"""python(\d+\.\d+[a-z]*)""".toRegex().matchEntire(filename)?.let {
return@run it.groupValues[1]
}
}
@ -64,9 +64,10 @@ for ((i, prefix) in prefixes.withIndex()) {
val libPythonDir = file("$libDir/python$pythonVersion")
val triplet = run {
for (filename in libPythonDir.list()!!) {
"""_sysconfigdata__android_(.+).py""".toRegex().matchEntire(filename)?.let {
return@run it.groupValues[1]
}
"""_sysconfigdata_[a-z]*_android_(.+).py""".toRegex()
.matchEntire(filename)?.let {
return@run it.groupValues[1]
}
}
throw GradleException("Failed to find Python triplet in $libPythonDir")
}
@ -78,7 +79,7 @@ android {
val androidEnvFile = file("../../android-env.sh").absoluteFile
namespace = "org.python.testbed"
compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = "org.python.testbed"
@ -91,7 +92,7 @@ android {
}
throw GradleException("Failed to find API level in $androidEnvFile")
}
targetSdk = 34
targetSdk = 35
versionCode = 1
versionName = "1.0"

View file

@ -20,7 +20,7 @@ class PythonSuite {
val status = PythonTestRunner(
InstrumentationRegistry.getInstrumentation().targetContext
).run(
InstrumentationRegistry.getArguments()
InstrumentationRegistry.getArguments().getString("pythonArgs")!!,
)
assertEquals(0, status)
} finally {

View file

@ -3,6 +3,7 @@
#include <jni.h>
#include <pthread.h>
#include <Python.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@ -15,6 +16,13 @@ static void throw_runtime_exception(JNIEnv *env, const char *message) {
message);
}
static void throw_errno(JNIEnv *env, const char *error_prefix) {
char error_message[1024];
snprintf(error_message, sizeof(error_message),
"%s: %s", error_prefix, strerror(errno));
throw_runtime_exception(env, error_message);
}
// --- Stdio redirection ------------------------------------------------------
@ -95,10 +103,7 @@ JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToL
for (StreamInfo *si = STREAMS; si->file; si++) {
char *error_prefix;
if ((error_prefix = redirect_stream(si))) {
char error_message[1024];
snprintf(error_message, sizeof(error_message),
"%s: %s", error_prefix, strerror(errno));
throw_runtime_exception(env, error_message);
throw_errno(env, error_prefix);
return;
}
}
@ -107,13 +112,38 @@ JNIEXPORT void JNICALL Java_org_python_testbed_PythonTestRunner_redirectStdioToL
// --- Python initialization ---------------------------------------------------
static PyStatus set_config_string(
JNIEnv *env, PyConfig *config, wchar_t **config_str, jstring value
) {
const char *value_utf8 = (*env)->GetStringUTFChars(env, value, NULL);
PyStatus status = PyConfig_SetBytesString(config, config_str, value_utf8);
(*env)->ReleaseStringUTFChars(env, value, value_utf8);
return status;
static char *init_signals() {
// Some tests use SIGUSR1, but that's blocked by default in an Android app in
// order to make it available to `sigwait` in the Signal Catcher thread.
// (https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:art/runtime/signal_catcher.cc).
// That thread's functionality is only useful for debugging the JVM, so disabling
// it should not weaken the tests.
//
// There's no safe way of stopping the thread completely (#123982), but simply
// unblocking SIGUSR1 is enough to fix most tests.
//
// However, in tests that generate multiple different signals in quick
// succession, it's possible for SIGUSR1 to arrive while the main thread is busy
// running the C-level handler for a different signal. In that case, the SIGUSR1
// may be sent to the Signal Catcher thread instead, which will generate a log
// message containing the text "reacting to signal".
//
// Such tests may need to be changed in one of the following ways:
// * Use a signal other than SIGUSR1 (e.g. test_stress_delivery_simultaneous in
// test_signal.py).
// * Send the signal to a specific thread rather than the whole process (e.g.
// test_signals in test_threadsignals.py.
sigset_t set;
if (sigemptyset(&set)) {
return "sigemptyset";
}
if (sigaddset(&set, SIGUSR1)) {
return "sigaddset";
}
if ((errno = pthread_sigmask(SIG_UNBLOCK, &set, NULL))) {
return "pthread_sigmask";
}
return NULL;
}
static void throw_status(JNIEnv *env, PyStatus status) {
@ -121,27 +151,47 @@ static void throw_status(JNIEnv *env, PyStatus status) {
}
JNIEXPORT int JNICALL Java_org_python_testbed_PythonTestRunner_runPython(
JNIEnv *env, jobject obj, jstring home, jstring runModule
JNIEnv *env, jobject obj, jstring home, jarray args
) {
const char *home_utf8 = (*env)->GetStringUTFChars(env, home, NULL);
char cwd[PATH_MAX];
snprintf(cwd, sizeof(cwd), "%s/%s", home_utf8, "cwd");
if (chdir(cwd)) {
throw_errno(env, "chdir");
return 1;
}
char *error_prefix;
if ((error_prefix = init_signals())) {
throw_errno(env, error_prefix);
return 1;
}
PyConfig config;
PyStatus status;
PyConfig_InitIsolatedConfig(&config);
PyConfig_InitPythonConfig(&config);
status = set_config_string(env, &config, &config.home, home);
if (PyStatus_Exception(status)) {
jsize argc = (*env)->GetArrayLength(env, args);
const char *argv[argc + 1];
for (int i = 0; i < argc; i++) {
jobject arg = (*env)->GetObjectArrayElement(env, args, i);
argv[i] = (*env)->GetStringUTFChars(env, arg, NULL);
}
argv[argc] = NULL;
// PyConfig_SetBytesArgv "must be called before other methods, since the
// preinitialization configuration depends on command line arguments"
if (PyStatus_Exception(status = PyConfig_SetBytesArgv(&config, argc, (char**)argv))) {
throw_status(env, status);
return 1;
}
status = set_config_string(env, &config, &config.run_module, runModule);
status = PyConfig_SetBytesString(&config, &config.home, home_utf8);
if (PyStatus_Exception(status)) {
throw_status(env, status);
return 1;
}
// Some tests generate SIGPIPE and SIGXFSZ, which should be ignored.
config.install_signal_handlers = 1;
status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
throw_status(env, status);

View file

@ -5,6 +5,7 @@ import android.os.*
import android.system.Os
import android.widget.TextView
import androidx.appcompat.app.*
import org.json.JSONArray
import java.io.*
@ -15,30 +16,25 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val status = PythonTestRunner(this).run("-m", "test", "-W -uall")
val status = PythonTestRunner(this).run("""["-m", "test", "-W", "-uall"]""")
findViewById<TextView>(R.id.tvHello).text = "Exit status $status"
}
}
class PythonTestRunner(val context: Context) {
fun run(instrumentationArgs: Bundle) = run(
instrumentationArgs.getString("pythonMode")!!,
instrumentationArgs.getString("pythonModule")!!,
instrumentationArgs.getString("pythonArgs") ?: "",
)
/** Run Python.
*
* @param mode Either "-c" or "-m".
* @param module Python statements for "-c" mode, or a module name for
* "-m" mode.
* @param args Arguments to add to sys.argv. Will be parsed by `shlex.split`.
* @param args Python command-line, encoded as JSON.
* @return The Python exit status: zero on success, nonzero on failure. */
fun run(mode: String, module: String, args: String) : Int {
Os.setenv("PYTHON_MODE", mode, true)
Os.setenv("PYTHON_MODULE", module, true)
Os.setenv("PYTHON_ARGS", args, true)
fun run(args: String) : Int {
// We leave argument 0 as an empty string, which is a placeholder for the
// executable name in embedded mode.
val argsJsonArray = JSONArray(args)
val argsStringArray = Array<String>(argsJsonArray.length() + 1) { it -> ""}
for (i in 0..<argsJsonArray.length()) {
argsStringArray[i + 1] = argsJsonArray.getString(i)
}
// Python needs this variable to help it find the temporary directory,
// but Android only sets it on API level 33 and later.
@ -47,10 +43,7 @@ class PythonTestRunner(val context: Context) {
val pythonHome = extractAssets()
System.loadLibrary("main_activity")
redirectStdioToLogcat()
// The main module is in src/main/python. We don't simply call it
// "main", as that could clash with third-party test code.
return runPython(pythonHome.toString(), "android_testbed_main")
return runPython(pythonHome.toString(), argsStringArray)
}
private fun extractAssets() : File {
@ -59,6 +52,13 @@ class PythonTestRunner(val context: Context) {
throw RuntimeException("Failed to delete $pythonHome")
}
extractAssetDir("python", context.filesDir)
// Empty directories are lost in the asset packing/unpacking process.
val cwd = File(pythonHome, "cwd")
if (!cwd.exists()) {
cwd.mkdir()
}
return pythonHome
}
@ -88,5 +88,5 @@ class PythonTestRunner(val context: Context) {
}
private external fun redirectStdioToLogcat()
private external fun runPython(home: String, runModule: String) : Int
private external fun runPython(home: String, args: Array<String>) : Int
}

View file

@ -1,48 +0,0 @@
import os
import runpy
import shlex
import signal
import sys
# Some tests use SIGUSR1, but that's blocked by default in an Android app in
# order to make it available to `sigwait` in the Signal Catcher thread.
# (https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:art/runtime/signal_catcher.cc).
# That thread's functionality is only useful for debugging the JVM, so disabling
# it should not weaken the tests.
#
# There's no safe way of stopping the thread completely (#123982), but simply
# unblocking SIGUSR1 is enough to fix most tests.
#
# However, in tests that generate multiple different signals in quick
# succession, it's possible for SIGUSR1 to arrive while the main thread is busy
# running the C-level handler for a different signal. In that case, the SIGUSR1
# may be sent to the Signal Catcher thread instead, which will generate a log
# message containing the text "reacting to signal".
#
# Such tests may need to be changed in one of the following ways:
# * Use a signal other than SIGUSR1 (e.g. test_stress_delivery_simultaneous in
# test_signal.py).
# * Send the signal to a specific thread rather than the whole process (e.g.
# test_signals in test_threadsignals.py.
signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGUSR1])
mode = os.environ["PYTHON_MODE"]
module = os.environ["PYTHON_MODULE"]
sys.argv[1:] = shlex.split(os.environ["PYTHON_ARGS"])
cwd = f"{sys.prefix}/cwd"
if not os.path.exists(cwd):
# Empty directories are lost in the asset packing/unpacking process.
os.mkdir(cwd)
os.chdir(cwd)
if mode == "-c":
# In -c mode, sys.path starts with an empty string, which means whatever the current
# working directory is at the moment of each import.
sys.path.insert(0, "")
exec(module, {})
elif mode == "-m":
sys.path.insert(0, os.getcwd())
runpy.run_module(module, run_name="__main__", alter_sys=True)
else:
raise ValueError(f"unknown mode: {mode}")

22
Apple/.ruff.toml Normal file
View file

@ -0,0 +1,22 @@
extend = "../.ruff.toml" # Inherit the project-wide settings
[format]
preview = true
docstring-code-format = true
[lint]
select = [
"C4", # flake8-comprehensions
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"PGH", # pygrep-hooks
"PT", # flake8-pytest-style
"PYI", # flake8-pyi
"RUF100", # Ban unused `# noqa` comments
"UP", # pyupgrade
"W", # pycodestyle
"YTT", # flake8-2020
]

1065
Apple/__main__.py Normal file

File diff suppressed because it is too large Load diff

339
Apple/iOS/README.md Normal file
View file

@ -0,0 +1,339 @@
# Python on iOS README
**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).**
This document provides a quick overview of some iOS specific features in the
Python distribution.
These instructions are only needed if you're planning to compile Python for iOS
yourself. Most users should *not* need to do this. If you're looking to
experiment with writing an iOS app in Python, tools such as [BeeWare's
Briefcase](https://briefcase.readthedocs.io) and [Kivy's
Buildozer](https://buildozer.readthedocs.io) will provide a much more
approachable user experience.
## Compilers for building on iOS
Building for iOS requires the use of Apple's Xcode tooling. It is strongly
recommended that you use the most recent stable release of Xcode. This will
require the use of the most (or second-most) recently released macOS version,
as Apple does not maintain Xcode for older macOS versions. The Xcode Command
Line Tools are not sufficient for iOS development; you need a *full* Xcode
install.
If you want to run your code on the iOS simulator, you'll also need to install
an iOS Simulator Platform. You should be prompted to select an iOS Simulator
Platform when you first run Xcode. Alternatively, you can add an iOS Simulator
Platform by selecting an open the Platforms tab of the Xcode Settings panel.
## Building Python on iOS
### ABIs and Architectures
iOS apps can be deployed on physical devices, and on the iOS simulator. Although
the API used on these devices is identical, the ABI is different - you need to
link against different libraries for an iOS device build (`iphoneos`) or an
iOS simulator build (`iphonesimulator`).
Apple uses the `XCframework` format to allow specifying a single dependency
that supports multiple ABIs. An `XCframework` is a wrapper around multiple
ABI-specific frameworks that share a common API.
iOS can also support different CPU architectures within each ABI. At present,
there is only a single supported architecture on physical devices - ARM64.
However, the *simulator* supports 2 architectures - ARM64 (for running on Apple
Silicon machines), and x86_64 (for running on older Intel-based machines).
To support multiple CPU architectures on a single platform, Apple uses a "fat
binary" format - a single physical file that contains support for multiple
architectures. It is possible to compile and use a "thin" single architecture
version of a binary for testing purposes; however, the "thin" binary will not be
portable to machines using other architectures.
### Building a multi-architecture iOS XCframework
The `Apple` subfolder of the Python repository acts as a build script that
can be used to coordinate the compilation of a complete iOS XCframework. To use
it, run::
python Apple build iOS
This will:
* Configure and compile a version of Python to run on the build machine
* Download pre-compiled binary dependencies for each platform
* Configure and build a `Python.framework` for each required architecture and
iOS SDK
* Merge the multiple `Python.framework` folders into a single `Python.xcframework`
* Produce a `.tar.gz` archive in the `cross-build/dist` folder containing
the `Python.xcframework`, plus a copy of the Testbed app pre-configured to
use the XCframework.
The `Apple` build script has other entry points that will perform the
individual parts of the overall `build` target, plus targets to test the
build, clean the `cross-build` folder of iOS build products, and perform a
complete "build and test" CI run. The `--clean` flag can also be used on
individual commands to ensure that a stale build product are removed before
building.
### Building a single-architecture framework
If you're using the `Apple` build script, you won't need to build
individual frameworks. However, if you do need to manually configure an iOS
Python build for a single framework, the following options are available.
#### iOS specific arguments to configure
* `--enable-framework[=DIR]`
This argument specifies the location where the Python.framework will be
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.
* `--with-framework-name=NAME`
Specify the name for the Python framework; defaults to `Python`.
> [!NOTE]
> Unless you know what you're doing, changing the name of the Python
> framework on iOS is not advised. If you use this option, you won't be able
> to run the `Apple` build script without making significant manual
> alterations, and you won't be able to use any binary packages unless you
> compile them yourself using your own framework name.
#### Building Python for iOS
The Python build system will create a `Python.framework` that supports a
*single* ABI with a *single* architecture. Unlike macOS, iOS does not allow a
framework to contain non-library content, so the iOS build will produce a
`bin` and `lib` folder in the same output folder as `Python.framework`.
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
also requires the use of cross-compilation. The minimal commands for building
Python for the ARM64 iOS simulator will look something like:
```
export PATH="$(pwd)/Apple/iOS/Resources/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
./configure \
--enable-framework \
--host=arm64-apple-ios-simulator \
--build=arm64-apple-darwin \
--with-build-python=/path/to/python.exe
make
make install
```
In this invocation:
* `Apple/iOS/Resources/bin` has been added to the path, providing some shims for the
compilers and linkers needed by the build. Xcode requires the use of `xcrun`
to invoke compiler tooling. However, if `xcrun` is pre-evaluated and the
result passed to `configure`, these results can embed user- and
version-specific paths into the sysconfig data, which limits the portability
of the compiled Python. Alternatively, if `xcrun` is used *as* the compiler,
it requires that compiler variables like `CC` include spaces, which can
cause significant problems with many C configuration systems which assume that
`CC` will be a single executable.
To work around this problem, the `Apple/iOS/Resources/bin` folder contains some
wrapper scripts that present as simple compilers and linkers, but wrap
underlying calls to `xcrun`. This allows configure to use a `CC`
definition without spaces, and without user- or version-specific paths, while
retaining the ability to adapt to the local Xcode install. These scripts are
included in the `bin` directory of an iOS install.
These scripts will, by default, use the currently active Xcode installation.
If you want to use a different Xcode installation, you can use
`xcode-select` to set a new default Xcode globally, or you can use the
`DEVELOPER_DIR` environment variable to specify an Xcode install. The
scripts will use the default `iphoneos`/`iphonesimulator` SDK version for
the select Xcode install; if you want to use a different SDK, you can set the
`IOS_SDK_VERSION` environment variable. (e.g, setting
`IOS_SDK_VERSION=17.1` would cause the scripts to use the `iphoneos17.1`
and `iphonesimulator17.1` SDKs, regardless of the Xcode default.)
The path has also been cleared of any user customizations. A common source of
bugs is for tools like Homebrew to accidentally leak macOS binaries into an iOS
build. Resetting the path to a known "bare bones" value is the easiest way to
avoid these problems.
* `--host` is the architecture and ABI that you want to build, in GNU compiler
triple format. This will be one of:
- `arm64-apple-ios` for ARM64 iOS devices.
- `arm64-apple-ios-simulator` for the iOS simulator running on Apple
Silicon devices.
- `x86_64-apple-ios-simulator` for the iOS simulator running on Intel
devices.
* `--build` is the GNU compiler triple for the machine that will be running
the compiler. This is one of:
- `arm64-apple-darwin` for Apple Silicon devices.
- `x86_64-apple-darwin` for Intel devices.
* `/path/to/python.exe` is the path to a Python binary on the machine that
will be running the compiler. This is needed because the Python compilation
process involves running some Python code. On a normal desktop build of
Python, you can compile a python interpreter and then use that interpreter to
run Python code. However, the binaries produced for iOS won't run on macOS, so
you need to provide an external Python interpreter. This interpreter must be
the same version as the Python that is being compiled. To be completely safe,
this should be the *exact* same commit hash. However, the longer a Python
release has been stable, the more likely it is that this constraint can be
relaxed - the same micro version will often be sufficient.
* The `install` target for iOS builds is slightly different to other
platforms. On most platforms, `make install` will install the build into
the final runtime location. This won't be the case for iOS, as the final
runtime location will be on a physical device.
However, you still need to run the `install` target for iOS builds, as it
performs some final framework assembly steps. The location specified with
`--enable-framework` will be the location where `make install` will
assemble the complete iOS framework. This completed framework can then
be copied and relocated as required.
For a full CPython build, you also need to specify the paths to iOS builds of
the binary libraries that CPython depends on (such as XZ, LibFFI and OpenSSL).
This can be done by defining library specific environment variables (such as
`LIBLZMA_CFLAGS`, `LIBLZMA_LIBS`), and the `--with-openssl` configure
option. Versions of these libraries pre-compiled for iOS can be found in [this
repository](https://github.com/beeware/cpython-apple-source-deps/releases).
LibFFI is especially important, as many parts of the standard library
(including the `platform`, `sysconfig` and `webbrowser` modules) require
the use of the `ctypes` module at runtime.
By default, Python will be compiled with an iOS deployment target (i.e., the
minimum supported iOS version) of 13.0. To specify a different deployment
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.
## Testing Python on iOS
### Testing a multi-architecture framework
Once you have a built an XCframework, you can test that framework by running:
$ python Apple test iOS
This test will attempt to find an "SE-class" simulator (i.e., an iPhone SE, or
iPhone 16e, or similar), and run the test suite on the most recent version of
iOS that is available. You can specify a simulator using the `--simulator`
command line argument, providing the name of the simulator (e.g., `--simulator
'iPhone 16 Pro'`). You can also use this argument to control the OS version used
for testing; `--simulator 'iPhone 16 Pro,OS=18.2'` would attempt to run the
tests on an iPhone 16 Pro running iOS 18.2.
If the test runner is executed on GitHub Actions, the `GITHUB_ACTIONS`
environment variable will be exposed to the iOS process at runtime.
### Testing a single-architecture framework
The `Apple/testbed` folder that contains an Xcode project that is able to run
the Python test suite on Apple platforms. This project converts the Python test
suite into a single test case in Xcode's XCTest framework. The single XCTest
passes if the test suite passes.
To run the test suite, configure a Python build for an iOS simulator (i.e.,
`--host=arm64-apple-ios-simulator` or `--host=x86_64-apple-ios-simulator`
), specifying a framework build (i.e. `--enable-framework`). Ensure that your
`PATH` has been configured to include the `Apple/iOS/Resources/bin` folder and
exclude any non-iOS tools, then run:
```
make all
make install
make testios
```
This will:
* Build an iOS framework for your chosen architecture;
* Finalize the single-platform framework;
* Make a clean copy of the testbed project;
* Install the Python iOS framework into the copy of the testbed project; and
* Run the test suite on an "entry-level device" simulator (i.e., an iPhone SE,
iPhone 16e, or a similar).
On success, the test suite will exit and report successful completion of the
test suite. On a 2022 M1 MacBook Pro, the test suite takes approximately 15
minutes to run; a couple of extra minutes is required to compile the testbed
project, and then boot and prepare the iOS simulator.
### Debugging test failures
Running `python Apple test iOS` generates a standalone version of the
`Apple/testbed` project, and runs the full test suite. It does this using
`Apple/testbed` itself - the folder is an executable module that can be used
to create and run a clone of the testbed project. The standalone version of the
testbed will be created in a directory named
`cross-build/iOS-testbed.<timestamp>`.
You can generate your own standalone testbed instance by running:
```
python cross-build/iOS/testbed clone my-testbed
```
In this invocation, `my-testbed` is the name of the folder for the new
testbed clone.
If you've built your own XCframework, or you only want to test a single architecture,
you can construct a standalone testbed instance by running:
```
python Apple/testbed clone --platform iOS --framework <path/to/framework> my-testbed
```
The framework path can be the path path to a `Python.xcframework`, or the
path to a folder that contains a single-platform `Python.framework`.
You can then use the `my-testbed` folder to run the Python test suite,
passing in any command line arguments you may require. For example, if you're
trying to diagnose a failure in the `os` module, you might run:
```
python my-testbed run -- test -W test_os
```
This is the equivalent of running `python -m test -W test_os` on a desktop
Python build. Any arguments after the `--` will be passed to testbed as if
they were arguments to `python -m` on a desktop machine.
### Testing in Xcode
You can also open the testbed project in Xcode by running:
```
open my-testbed/iOSTestbed.xcodeproj
```
This will allow you to use the full Xcode suite of tools for debugging.
The arguments used to run the test suite are defined as part of the test plan.
To modify the test plan, select the test plan node of the project tree (it
should be the first child of the root node), and select the "Configurations"
tab. Modify the "Arguments Passed On Launch" value to change the testing
arguments.
The test plan also disables parallel testing, and specifies the use of the
`Testbed.lldbinit` file for providing configuration of the debugger. The
default debugger configuration disables automatic breakpoints on the
`SIGINT`, `SIGUSR1`, `SIGUSR2`, and `SIGXFSZ` signals.
### Testing on an iOS device
To test on an iOS device, the app needs to be signed with known developer
credentials. To obtain these credentials, you must have an iOS Developer
account, and your Xcode install will need to be logged into your account (see
the Accounts tab of the Preferences dialog).
Once the project is open, and you're signed into your Apple Developer account,
select the root node of the project tree (labeled "iOSTestbed"), then the
"Signing & Capabilities" tab in the details page. Select a development team
(this will likely be your own name), and plug in a physical device to your
macOS machine with a USB cable. You should then be able to select your physical
device from the list of targets in the pulldown in the Xcode titlebar.

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"

View file

@ -0,0 +1,2 @@
#!/bin/sh
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@"

View file

@ -19,7 +19,7 @@
<string>iPhoneOS</string>
</array>
<key>MinimumOSVersion</key>
<string>12.0</string>
<string>13.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>

View file

@ -0,0 +1,151 @@
# Utility methods for use in an Xcode project.
#
# An iOS XCframework cannot include any content other than the library binary
# and relevant metadata. However, Python requires a standard library at runtime.
# Therefore, it is necessary to add a build step to an Xcode app target that
# processes the standard library and puts the content into the final app.
#
# In general, these tools will be invoked after bundle resources have been
# copied into the app, but before framework embedding (and signing).
#
# The following is an example script, assuming that:
# * Python.xcframework is in the root of the project
# * There is an `app` folder that contains the app code
# * There is an `app_packages` folder that contains installed Python packages.
# -----
# set -e
# source $PROJECT_DIR/Python.xcframework/build/build_utils.sh
# install_python Python.xcframework app app_packages
# -----
# Copy the standard library from the XCframework into the app bundle.
#
# Accepts one argument:
# 1. The path, relative to the root of the Xcode project, where the Python
# XCframework can be found.
install_stdlib() {
PYTHON_XCFRAMEWORK_PATH=$1
mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib"
if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then
echo "Installing Python modules for iOS Simulator"
if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/ios-arm64-simulator" ]; then
SLICE_FOLDER="ios-arm64-simulator"
else
SLICE_FOLDER="ios-arm64_x86_64-simulator"
fi
else
echo "Installing Python modules for iOS Device"
SLICE_FOLDER="ios-arm64"
fi
# If the XCframework has a shared lib folder, then it's a full framework.
# Copy both the common and slice-specific part of the lib directory.
# Otherwise, it's a single-arch framework; use the "full" lib folder.
if [ -d "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib" ]; then
rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/"
rsync -au "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib-$ARCHS/" "$CODESIGNING_FOLDER_PATH/python/lib/"
else
# A single-arch framework will have a libpython symlink; that can't be included at runtime
rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" --exclude 'libpython*.dylib'
fi
}
# Convert a single .so library into a framework that iOS can load.
#
# Accepts three arguments:
# 1. The path, relative to the root of the Xcode project, where the Python
# XCframework can be found.
# 2. The base path, relative to the installed location in the app bundle, that
# needs to be processed. Any .so file found in this path (or a subdirectory
# of it) will be processed.
# 2. The full path to a single .so file to process. This path should include
# the base path.
install_dylib () {
PYTHON_XCFRAMEWORK_PATH=$1
INSTALL_BASE=$2
FULL_EXT=$3
# The name of the extension file
EXT=$(basename "$FULL_EXT")
# The name and location of the module
MODULE_PATH=$(dirname "$FULL_EXT")
MODULE_NAME=$(echo $EXT | cut -d "." -f 1)
# The location of the extension file, relative to the bundle
RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/}
# The path to the extension file, relative to the install base
PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}
# The full dotted name of the extension module, constructed from the file path.
FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" ".");
# A bundle identifier; not actually used, but required by Xcode framework packaging
FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-")
# The name of the framework folder.
FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework"
# If the framework folder doesn't exist, create it.
if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then
echo "Creating framework for $RELATIVE_EXT"
mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
cp "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/build/$PLATFORM_FAMILY_NAME-dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist"
fi
echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
# Create a placeholder .fwork file where the .so was
echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork
# Create a back reference to the .so file location in the framework
echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin"
# If the framework provides an xcprivacy file, install it.
if [ -e "$MODULE_PATH/$MODULE_NAME.xcprivacy" ]; then
echo "Installing XCPrivacy file for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME"
XCPRIVACY_FILE="$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/PrivacyInfo.xcprivacy"
if [ -e "$XCPRIVACY_FILE" ]; then
rm -rf "$XCPRIVACY_FILE"
fi
mv "$MODULE_PATH/$MODULE_NAME.xcprivacy" "$XCPRIVACY_FILE"
fi
echo "Signing framework as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..."
/usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER"
}
# Process all the dynamic libraries in a path into Framework format.
#
# Accepts two arguments:
# 1. The path, relative to the root of the Xcode project, where the Python
# XCframework can be found.
# 2. The base path, relative to the installed location in the app bundle, that
# needs to be processed. Any .so file found in this path (or a subdirectory
# of it) will be processed.
process_dylibs () {
PYTHON_XCFRAMEWORK_PATH=$1
LIB_PATH=$2
find "$CODESIGNING_FOLDER_PATH/$LIB_PATH" -name "*.so" | while read FULL_EXT; do
install_dylib $PYTHON_XCFRAMEWORK_PATH "$LIB_PATH/" "$FULL_EXT"
done
}
# The entry point for post-processing a Python XCframework.
#
# Accepts 1 or more arguments:
# 1. The path, relative to the root of the Xcode project, where the Python
# XCframework can be found. If the XCframework is in the root of the project,
# 2+. The path of a package, relative to the root of the packaged app, that contains
# library content that should be processed for binary libraries.
install_python() {
PYTHON_XCFRAMEWORK_PATH=$1
shift
install_stdlib $PYTHON_XCFRAMEWORK_PATH
PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib")
echo "Install Python $PYTHON_VER standard library extension modules..."
process_dylibs $PYTHON_XCFRAMEWORK_PATH python/lib/$PYTHON_VER/lib-dynload
for package_path in $@; do
echo "Installing $package_path extension modules ..."
process_dylibs $PYTHON_XCFRAMEWORK_PATH $package_path
done
}

View file

@ -1,11 +1,11 @@
#import <XCTest/XCTest.h>
#import <Python/Python.h>
@interface iOSTestbedTests : XCTestCase
@interface TestbedTests : XCTestCase
@end
@implementation iOSTestbedTests
@implementation TestbedTests
- (void)testPython {
@ -35,20 +35,23 @@
setenv("NO_COLOR", "1", true);
setenv("PYTHON_COLORS", "0", true);
if (getenv("GITHUB_ACTIONS")) {
NSLog(@"Running in a GitHub Actions environment");
}
// Arguments to pass into the test suite runner.
// argv[0] must identify the process; any subsequent arg
// will be handled as if it were an argument to `python -m test`
// The processInfo arguments contain the binary that is running,
// followed by the arguments defined in the test plan. This means:
// run_module = test_args[1]
// argv = ["iOSTestbed"] + test_args[2:]
// argv = ["Testbed"] + test_args[2:]
test_args = [[NSProcessInfo processInfo] arguments];
if (test_args == NULL) {
NSLog(@"Unable to identify test arguments.");
}
NSLog(@"Test arguments: %@", test_args);
argv = malloc(sizeof(char *) * ([test_args count] - 1));
argv[0] = "iOSTestbed";
argv[0] = "Testbed";
for (int i = 1; i < [test_args count] - 1; i++) {
argv[i] = [[test_args objectAtIndex:i+1] UTF8String];
}

View file

@ -1,11 +1,16 @@
import argparse
import json
import os
import re
import shlex
import shutil
import subprocess
import sys
from pathlib import Path
TEST_SLICES = {
"iOS": "ios-arm64_x86_64-simulator",
}
DECODE_ARGS = ("UTF-8", "backslashreplace")
@ -21,45 +26,49 @@ LOG_PREFIX_REGEX = re.compile(
# Select a simulator device to use.
def select_simulator_device():
def select_simulator_device(platform):
# List the testing simulators, in JSON format
raw_json = subprocess.check_output(["xcrun", "simctl", "list", "-j"])
json_data = json.loads(raw_json)
# Any device will do; we'll look for "SE" devices - but the name isn't
# consistent over time. Older Xcode versions will use "iPhone SE (Nth
# generation)"; As of 2025, they've started using "iPhone 16e".
#
# When Xcode is updated after a new release, new devices will be available
# and old ones will be dropped from the set available on the latest iOS
# version. Select the one with the highest minimum runtime version - this
# is an indicator of the "newest" released device, which should always be
# supported on the "most recent" iOS version.
se_simulators = sorted(
(devicetype["minRuntimeVersion"], devicetype["name"])
for devicetype in json_data["devicetypes"]
if devicetype["productFamily"] == "iPhone"
and (
(
"iPhone " in devicetype["name"]
and devicetype["name"].endswith("e")
if platform == "iOS":
# Any iOS device will do; we'll look for "SE" devices - but the name
# isn't consistent over time. Older Xcode versions will use "iPhone SE
# (Nth generation)"; As of 2025, they've started using "iPhone 16e".
#
# When Xcode is updated after a new release, new devices will be
# available and old ones will be dropped from the set available on the
# latest iOS version. Select the one with the highest minimum runtime
# version - this is an indicator of the "newest" released device, which
# should always be supported on the "most recent" iOS version.
se_simulators = sorted(
(devicetype["minRuntimeVersion"], devicetype["name"])
for devicetype in json_data["devicetypes"]
if devicetype["productFamily"] == "iPhone"
and (
(
"iPhone " in devicetype["name"]
and devicetype["name"].endswith("e")
)
or "iPhone SE " in devicetype["name"]
)
or "iPhone SE " in devicetype["name"]
)
)
simulator = se_simulators[-1][1]
else:
raise ValueError(f"Unknown platform {platform}")
return se_simulators[-1][1]
return simulator
def xcode_test(location, simulator, verbose):
def xcode_test(location: Path, platform: str, simulator: str, verbose: bool):
# Build and run the test suite on the named simulator.
args = [
"-project",
str(location / "iOSTestbed.xcodeproj"),
str(location / f"{platform}Testbed.xcodeproj"),
"-scheme",
"iOSTestbed",
f"{platform}Testbed",
"-destination",
f"platform=iOS Simulator,name={simulator}",
f"platform={platform} Simulator,name={simulator}",
"-derivedDataPath",
str(location / "DerivedData"),
]
@ -71,6 +80,13 @@ def xcode_test(location, simulator, verbose):
check=True,
)
# Any environment variable prefixed with TEST_RUNNER_ is exposed into the
# test runner environment. There are some variables (like those identifying
# CI platforms) that can be useful to have access to.
test_env = os.environ.copy()
if "GITHUB_ACTIONS" in os.environ:
test_env["TEST_RUNNER_GITHUB_ACTIONS"] = os.environ["GITHUB_ACTIONS"]
print("Running test project...")
# Test execution *can't* be run -quiet; verbose mode
# is how we see the output of the test output.
@ -78,6 +94,7 @@ def xcode_test(location, simulator, verbose):
["xcodebuild", "test-without-building"] + args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=test_env,
)
while line := (process.stdout.readline()).decode(*DECODE_ARGS):
# Strip the timestamp/process prefix from each log line
@ -89,10 +106,24 @@ def xcode_test(location, simulator, verbose):
exit(status)
def copy(src, tgt):
"""An all-purpose copy.
If src is a file, it is copied. If src is a symlink, it is copied *as a
symlink*. If src is a directory, the full tree is duplicated, with symlinks
being preserved.
"""
if src.is_file() or src.is_symlink():
shutil.copyfile(src, tgt, follow_symlinks=False)
else:
shutil.copytree(src, tgt, symlinks=True)
def clone_testbed(
source: Path,
target: Path,
framework: Path,
platform: str,
apps: list[Path],
) -> None:
if target.exists():
@ -101,11 +132,11 @@ def clone_testbed(
if framework is None:
if not (
source / "Python.xcframework/ios-arm64_x86_64-simulator/bin"
source / "Python.xcframework" / TEST_SLICES[platform] / "bin"
).is_dir():
print(
f"The testbed being cloned ({source}) does not contain "
f"a simulator framework. Re-run with --framework"
"a framework with slices. Re-run with --framework"
)
sys.exit(11)
else:
@ -124,33 +155,49 @@ def clone_testbed(
print("Cloning testbed project:")
print(f" Cloning {source}...", end="")
shutil.copytree(source, target, symlinks=True)
# Only copy the files for the platform being cloned plus the files common
# to all platforms. The XCframework will be copied later, if needed.
target.mkdir(parents=True)
for name in [
"__main__.py",
"TestbedTests",
"Testbed.lldbinit",
f"{platform}Testbed",
f"{platform}Testbed.xcodeproj",
f"{platform}Testbed.xctestplan",
]:
copy(source / name, target / name)
print(" done")
orig_xc_framework_path = source / "Python.xcframework"
xc_framework_path = target / "Python.xcframework"
sim_framework_path = xc_framework_path / "ios-arm64_x86_64-simulator"
test_framework_path = xc_framework_path / TEST_SLICES[platform]
if framework is not None:
if framework.suffix == ".xcframework":
print(" Installing XCFramework...", end="")
if xc_framework_path.is_dir():
shutil.rmtree(xc_framework_path)
else:
xc_framework_path.unlink(missing_ok=True)
xc_framework_path.symlink_to(
framework.relative_to(xc_framework_path.parent, walk_up=True)
)
print(" done")
else:
print(" Installing simulator framework...", end="")
if sim_framework_path.is_dir():
shutil.rmtree(sim_framework_path)
# We're only installing a slice of a framework; we need
# to do a full tree copy to make sure we don't damage
# symlinked content.
shutil.copytree(orig_xc_framework_path, xc_framework_path)
if test_framework_path.is_dir():
shutil.rmtree(test_framework_path)
else:
sim_framework_path.unlink(missing_ok=True)
sim_framework_path.symlink_to(
framework.relative_to(sim_framework_path.parent, walk_up=True)
test_framework_path.unlink(missing_ok=True)
test_framework_path.symlink_to(
framework.relative_to(test_framework_path.parent, walk_up=True)
)
print(" done")
else:
copy(orig_xc_framework_path, xc_framework_path)
if (
xc_framework_path.is_symlink()
and not xc_framework_path.readlink().is_absolute()
@ -158,39 +205,39 @@ def clone_testbed(
# XCFramework is a relative symlink. Rewrite the symlink relative
# to the new location.
print(" Rewriting symlink to XCframework...", end="")
orig_xc_framework_path = (
resolved_xc_framework_path = (
source / xc_framework_path.readlink()
).resolve()
xc_framework_path.unlink()
xc_framework_path.symlink_to(
orig_xc_framework_path.relative_to(
resolved_xc_framework_path.relative_to(
xc_framework_path.parent, walk_up=True
)
)
print(" done")
elif (
sim_framework_path.is_symlink()
and not sim_framework_path.readlink().is_absolute()
test_framework_path.is_symlink()
and not test_framework_path.readlink().is_absolute()
):
print(" Rewriting symlink to simulator framework...", end="")
# Simulator framework is a relative symlink. Rewrite the symlink
# relative to the new location.
orig_sim_framework_path = (
source / "Python.XCframework" / sim_framework_path.readlink()
orig_test_framework_path = (
source / "Python.XCframework" / test_framework_path.readlink()
).resolve()
sim_framework_path.unlink()
sim_framework_path.symlink_to(
orig_sim_framework_path.relative_to(
sim_framework_path.parent, walk_up=True
test_framework_path.unlink()
test_framework_path.symlink_to(
orig_test_framework_path.relative_to(
test_framework_path.parent, walk_up=True
)
)
print(" done")
else:
print(" Using pre-existing iOS framework.")
print(" Using pre-existing Python framework.")
for app_src in apps:
print(f" Installing app {app_src.name!r}...", end="")
app_target = target / f"iOSTestbed/app/{app_src.name}"
app_target = target / f"Testbed/app/{app_src.name}"
if app_target.is_dir():
shutil.rmtree(app_target)
shutil.copytree(app_src, app_target)
@ -199,46 +246,65 @@ def clone_testbed(
print(f"Successfully cloned testbed: {target.resolve()}")
def update_test_plan(testbed_path, args):
def update_test_plan(testbed_path, platform, args):
# Modify the test plan to use the requested test arguments.
test_plan_path = testbed_path / "iOSTestbed.xctestplan"
test_plan_path = testbed_path / f"{platform}Testbed.xctestplan"
with test_plan_path.open("r", encoding="utf-8") as f:
test_plan = json.load(f)
test_plan["defaultOptions"]["commandLineArgumentEntries"] = [
{"argument": arg} for arg in args
{"argument": shlex.quote(arg)} for arg in args
]
with test_plan_path.open("w", encoding="utf-8") as f:
json.dump(test_plan, f, indent=2)
def run_testbed(simulator: str | None, args: list[str], verbose: bool = False):
def run_testbed(
platform: str,
simulator: str | None,
args: list[str],
verbose: bool = False,
):
location = Path(__file__).parent
print("Updating test plan...", end="")
update_test_plan(location, args)
update_test_plan(location, platform, args)
print(" done.")
if simulator is None:
simulator = select_simulator_device()
simulator = select_simulator_device(platform)
print(f"Running test on {simulator}")
xcode_test(location, simulator=simulator, verbose=verbose)
xcode_test(
location,
platform=platform,
simulator=simulator,
verbose=verbose,
)
def main():
# Look for directories like `iOSTestbed` as an indicator of the platforms
# that the testbed folder supports. The original source testbed can support
# many platforms, but when cloned, only one platform is preserved.
available_platforms = [
platform
for platform in ["iOS"]
if (Path(__file__).parent / f"{platform}Testbed").is_dir()
]
parser = argparse.ArgumentParser(
description=(
"Manages the process of testing a Python project in the iOS simulator."
"Manages the process of testing an Apple Python project "
"through Xcode."
),
)
subcommands = parser.add_subparsers(dest="subcommand")
clone = subcommands.add_parser(
"clone",
description=(
"Clone the testbed project, copying in an iOS Python framework and"
"Clone the testbed project, copying in a Python framework and"
"any specified application code."
),
help="Clone a testbed project to a new location.",
@ -250,6 +316,13 @@ def main():
"XCFramework) to use when running the testbed"
),
)
clone.add_argument(
"--platform",
dest="platform",
choices=available_platforms,
default=available_platforms[0],
help=f"The platform to target (default: {available_platforms[0]})",
)
clone.add_argument(
"--app",
dest="apps",
@ -264,7 +337,10 @@ def main():
run = subcommands.add_parser(
"run",
usage="%(prog)s [-h] [--simulator SIMULATOR] -- <test arg> [<test arg> ...]",
usage=(
"%(prog)s [-h] [--simulator SIMULATOR] -- "
"<test arg> [<test arg> ...]"
),
description=(
"Run a testbed project. The arguments provided after `--` will be "
"passed to the running iOS process as if they were arguments to "
@ -272,6 +348,13 @@ def main():
),
help="Run a testbed project",
)
run.add_argument(
"--platform",
dest="platform",
choices=available_platforms,
default=available_platforms[0],
help=f"The platform to target (default: {available_platforms[0]})",
)
run.add_argument(
"--simulator",
help=(
@ -306,29 +389,34 @@ def main():
framework=Path(context.framework).resolve()
if context.framework
else None,
platform=context.platform,
apps=[Path(app) for app in context.apps],
)
elif context.subcommand == "run":
if test_args:
if not (
Path(__file__).parent
/ "Python.xcframework/ios-arm64_x86_64-simulator/bin"
/ "Python.xcframework"
/ TEST_SLICES[context.platform]
/ "bin"
).is_dir():
print(
f"Testbed does not contain a compiled iOS framework. Use "
f"`python {sys.argv[0]} clone ...` to create a runnable "
f"clone of this testbed."
"Testbed does not contain a compiled Python framework. "
f"Use `python {sys.argv[0]} clone ...` to create a "
"runnable clone of this testbed."
)
sys.exit(20)
run_testbed(
platform=context.platform,
simulator=context.simulator,
verbose=context.verbose,
args=test_args,
)
else:
print(
f"Must specify test arguments (e.g., {sys.argv[0]} run -- test)"
"Must specify test arguments "
f"(e.g., {sys.argv[0]} run -- test)"
)
print()
parser.print_help(sys.stderr)
@ -339,4 +427,9 @@ def main():
if __name__ == "__main__":
# Under the buildbot, stdout is not a TTY, but we must still flush after
# every line to make sure our output appears in the correct order relative
# to the output of our subprocesses.
for stream in [sys.stdout, sys.stderr]:
stream.reconfigure(line_buffering=True)
main()

View file

@ -11,12 +11,11 @@
607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607A66212B0EFA390010BFC8 /* Assets.xcassets */; };
607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607A66232B0EFA390010BFC8 /* LaunchScreen.storyboard */; };
607A66282B0EFA390010BFC8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66272B0EFA390010BFC8 /* main.m */; };
607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */; };
607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 607A66312B0EFA3A0010BFC8 /* TestbedTests.m */; };
607A664C2B0EFC080010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
607A664D2B0EFC080010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
607A66502B0EFFE00010BFC8 /* Python.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; };
607A66512B0EFFE00010BFC8 /* Python.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 607A664A2B0EFB310010BFC8 /* Python.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */; };
608619542CB77BA900F46182 /* app_packages in Resources */ = {isa = PBXBuildFile; fileRef = 608619532CB77BA900F46182 /* app_packages */; };
608619562CB7819B00F46182 /* app in Resources */ = {isa = PBXBuildFile; fileRef = 608619552CB7819B00F46182 /* app */; };
/* End PBXBuildFile section */
@ -64,9 +63,8 @@
607A66242B0EFA390010BFC8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
607A66272B0EFA390010BFC8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
607A662D2B0EFA3A0010BFC8 /* iOSTestbedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSTestbedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iOSTestbedTests.m; sourceTree = "<group>"; };
607A66312B0EFA3A0010BFC8 /* TestbedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestbedTests.m; sourceTree = "<group>"; };
607A664A2B0EFB310010BFC8 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = "<group>"; };
607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "dylib-Info-template.plist"; sourceTree = "<group>"; };
607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "iOSTestbed-Info.plist"; sourceTree = "<group>"; };
608619532CB77BA900F46182 /* app_packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app_packages; sourceTree = "<group>"; };
608619552CB7819B00F46182 /* app */ = {isa = PBXFileReference; lastKnownFileType = folder; path = app; sourceTree = "<group>"; };
@ -99,7 +97,7 @@
60FE0EFB2E56BB6D00524F87 /* iOSTestbed.xctestplan */,
607A664A2B0EFB310010BFC8 /* Python.xcframework */,
607A66142B0EFA380010BFC8 /* iOSTestbed */,
607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */,
607A66302B0EFA3A0010BFC8 /* TestbedTests */,
607A66132B0EFA380010BFC8 /* Products */,
607A664F2B0EFFE00010BFC8 /* Frameworks */,
);
@ -120,7 +118,6 @@
608619552CB7819B00F46182 /* app */,
608619532CB77BA900F46182 /* app_packages */,
607A66592B0F08600010BFC8 /* iOSTestbed-Info.plist */,
607A66572B0F079F0010BFC8 /* dylib-Info-template.plist */,
607A66152B0EFA380010BFC8 /* AppDelegate.h */,
607A66162B0EFA380010BFC8 /* AppDelegate.m */,
607A66212B0EFA390010BFC8 /* Assets.xcassets */,
@ -130,12 +127,12 @@
path = iOSTestbed;
sourceTree = "<group>";
};
607A66302B0EFA3A0010BFC8 /* iOSTestbedTests */ = {
607A66302B0EFA3A0010BFC8 /* TestbedTests */ = {
isa = PBXGroup;
children = (
607A66312B0EFA3A0010BFC8 /* iOSTestbedTests.m */,
607A66312B0EFA3A0010BFC8 /* TestbedTests.m */,
);
path = iOSTestbedTests;
path = TestbedTests;
sourceTree = "<group>";
};
607A664F2B0EFFE00010BFC8 /* Frameworks */ = {
@ -155,8 +152,7 @@
607A660E2B0EFA380010BFC8 /* Sources */,
607A660F2B0EFA380010BFC8 /* Frameworks */,
607A66102B0EFA380010BFC8 /* Resources */,
607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */,
607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */,
607A66552B0F061D0010BFC8 /* Process Python libraries */,
607A664E2B0EFC080010BFC8 /* Embed Frameworks */,
);
buildRules = (
@ -230,7 +226,6 @@
buildActionMask = 2147483647;
files = (
607A66252B0EFA390010BFC8 /* LaunchScreen.storyboard in Resources */,
607A66582B0F079F0010BFC8 /* dylib-Info-template.plist in Resources */,
608619562CB7819B00F46182 /* app in Resources */,
607A66222B0EFA390010BFC8 /* Assets.xcassets in Resources */,
608619542CB77BA900F46182 /* app_packages in Resources */,
@ -247,7 +242,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
607A66552B0F061D0010BFC8 /* Install Target Specific Python Standard Library */ = {
607A66552B0F061D0010BFC8 /* Process Python libraries */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
@ -257,34 +252,14 @@
);
inputPaths = (
);
name = "Install Target Specific Python Standard Library";
name = "Process Python libraries";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nmkdir -p \"$CODESIGNING_FOLDER_PATH/python/lib\"\nif [ \"$EFFECTIVE_PLATFORM_NAME\" = \"-iphonesimulator\" ]; then\n echo \"Installing Python modules for iOS Simulator\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nelse\n echo \"Installing Python modules for iOS Device\"\n rsync -au --delete \"$PROJECT_DIR/Python.xcframework/ios-arm64/lib/\" \"$CODESIGNING_FOLDER_PATH/python/lib/\" \nfi\n";
showEnvVarsInLog = 0;
};
607A66562B0F06200010BFC8 /* Prepare Python Binary Modules */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Prepare Python Binary Modules";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\ninstall_dylib () {\n INSTALL_BASE=$1\n FULL_EXT=$2\n\n # The name of the extension file\n EXT=$(basename \"$FULL_EXT\")\n # The location of the extension file, relative to the bundle\n RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} \n # The path to the extension file, relative to the install base\n PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}\n # The full dotted name of the extension module, constructed from the file path.\n FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d \".\" -f 1 | tr \"/\" \".\"); \n # A bundle identifier; not actually used, but required by Xcode framework packaging\n FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr \"_\" \"-\")\n # The name of the framework folder.\n FRAMEWORK_FOLDER=\"Frameworks/$FULL_MODULE_NAME.framework\"\n\n # If the framework folder doesn't exist, create it.\n if [ ! -d \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\" ]; then\n echo \"Creating framework for $RELATIVE_EXT\" \n mkdir -p \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER\"\n cp \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleExecutable -string \"$FULL_MODULE_NAME\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n plutil -replace CFBundleIdentifier -string \"$FRAMEWORK_BUNDLE_ID\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist\"\n fi\n \n echo \"Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" \n mv \"$FULL_EXT\" \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\"\n # Create a placeholder .fwork file where the .so was\n echo \"$FRAMEWORK_FOLDER/$FULL_MODULE_NAME\" > ${FULL_EXT%.so}.fwork\n # Create a back reference to the .so file location in the framework\n echo \"${RELATIVE_EXT%.so}.fwork\" > \"$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin\" \n}\n\nPYTHON_VER=$(ls -1 \"$CODESIGNING_FOLDER_PATH/python/lib\")\necho \"Install Python $PYTHON_VER standard library extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib python/lib/$PYTHON_VER/lib-dynload/ \"$FULL_EXT\"\ndone\necho \"Install app package extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app_packages\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app_packages/ \"$FULL_EXT\"\ndone\necho \"Install app extension modules...\"\nfind \"$CODESIGNING_FOLDER_PATH/app\" -name \"*.so\" | while read FULL_EXT; do\n install_dylib app/ \"$FULL_EXT\"\ndone\n\n# Clean up dylib template \nrm -f \"$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist\"\necho \"Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)...\"\nfind \"$CODESIGNING_FOLDER_PATH/Frameworks\" -name \"*.framework\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der \"{}\" \\; \n";
shellScript = "set -e\nsource $PROJECT_DIR/Python.xcframework/build/utils.sh\ninstall_python Python.xcframework app app_packages\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -303,7 +278,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607A66322B0EFA3A0010BFC8 /* iOSTestbedTests.m in Sources */,
607A66322B0EFA3A0010BFC8 /* TestbedTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View file

@ -27,7 +27,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "/Users/rkm/projects/pyspamsum/localtest/iOSTestbed.lldbinit"
customLLDBInitFile = "$(SOURCE_ROOT)/Testbed.lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference

View file

@ -1,7 +1,7 @@
This folder can contain any Python application code.
During the build, any binary modules found in this folder will be processed into
iOS Framework form.
Framework form.
When the test suite runs, this folder will be on the PYTHONPATH, and will be the
working directory for the test suite.

View file

@ -2,6 +2,6 @@ This folder can be a target for installing any Python dependencies needed by the
test suite.
During the build, any binary modules found in this folder will be processed into
iOS Framework form.
Framework form.
When the test suite runs, this folder will be on the PYTHONPATH.

View file

@ -184,7 +184,7 @@ venv:
fi
.PHONY: dist-no-html
dist-no-html: dist-text dist-pdf dist-epub dist-texinfo
dist-no-html: dist-text dist-epub dist-texinfo
.PHONY: dist
dist:
@ -241,7 +241,8 @@ dist-pdf:
# as otherwise the full latexmk process is run twice.
# ($$ is needed to escape the $; https://www.gnu.org/software/make/manual/make.html#Basics-of-Variable-References)
-sed -i 's/: all-$$(FMT)/:/' build/latex/Makefile
(cd build/latex; $(MAKE) clean && $(MAKE) --jobs=$$((`nproc`+1)) --output-sync LATEXMKOPTS='-quiet' all-pdf && $(MAKE) FMT=pdf zip bz2)
if [ -n "$(filter output-sync,$(value .FEATURES))" ]; then OUTPUTSYNC=--output-sync; else OUTPUTSYNC=; fi && \
(cd build/latex; $(MAKE) clean && $(MAKE) --jobs=$$((`getconf _NPROCESSORS_ONLN`+1)) $$OUTPUTSYNC LATEXMKOPTS='-quiet' all-pdf && $(MAKE) FMT=pdf zip bz2)
cp build/latex/docs-pdf.zip dist/python-$(DISTVERSION)-docs-pdf-a4.zip
cp build/latex/docs-pdf.tar.bz2 dist/python-$(DISTVERSION)-docs-pdf-a4.tar.bz2
@echo "Build finished and archived!"

3260
Doc/_static/tachyon-example-flamegraph.html generated Normal file

File diff suppressed because one or more lines are too long

3804
Doc/_static/tachyon-example-heatmap.html generated Normal file

File diff suppressed because one or more lines are too long

View file

@ -32,8 +32,9 @@ Contributors to the Python documentation
----------------------------------------
Many people have contributed to the Python language, the Python standard
library, and the Python documentation. See :source:`Misc/ACKS` in the Python
source distribution for a partial list of contributors.
library, and the Python documentation. See the `CPython
GitHub repository <https://github.com/python/cpython/graphs/contributors>`__
for a partial list of contributors.
It is only with the input and contributions of the Python community
that Python has such wonderful documentation -- Thank You!

View file

@ -19,6 +19,12 @@ If you find a bug in this documentation or would like to propose an improvement,
please submit a bug report on the :ref:`issue tracker <using-the-tracker>`. If you
have a suggestion on how to fix it, include that as well.
.. only:: translation
If the bug or suggested improvement concerns the translation of this
documentation, submit the report to the
`translations repository <TRANSLATION_REPO_>`_ instead.
You can also open a discussion item on our
`Documentation Discourse forum <https://discuss.python.org/c/documentation/26>`_.

View file

@ -140,10 +140,6 @@ Allocating Objects on the Heap
* :c:member:`~PyTypeObject.tp_alloc`
.. c:function:: void PyObject_Del(void *op)
Same as :c:func:`PyObject_Free`.
.. c:var:: PyObject _Py_NoneStruct
Object which is visible in Python as ``None``. This should only be accessed
@ -156,3 +152,35 @@ Allocating Objects on the Heap
:ref:`moduleobjects`
To allocate and create extension modules.
Deprecated aliases
^^^^^^^^^^^^^^^^^^
These are :term:`soft deprecated` aliases to existing functions and macros.
They exist solely for backwards compatibility.
.. list-table::
:widths: auto
:header-rows: 1
* * Deprecated alias
* Function
* * .. c:macro:: PyObject_NEW(type, typeobj)
* :c:macro:`PyObject_New`
* * .. c:macro:: PyObject_NEW_VAR(type, typeobj, n)
* :c:macro:`PyObject_NewVar`
* * .. c:macro:: PyObject_INIT(op, typeobj)
* :c:func:`PyObject_Init`
* * .. c:macro:: PyObject_INIT_VAR(op, typeobj, n)
* :c:func:`PyObject_InitVar`
* * .. c:macro:: PyObject_MALLOC(n)
* :c:func:`PyObject_Malloc`
* * .. c:macro:: PyObject_REALLOC(p, n)
* :c:func:`PyObject_Realloc`
* * .. c:macro:: PyObject_FREE(p)
* :c:func:`PyObject_Free`
* * .. c:macro:: PyObject_DEL(p)
* :c:func:`PyObject_Free`
* * .. c:macro:: PyObject_Del(p)
* :c:func:`PyObject_Free`

View file

@ -160,7 +160,7 @@ There are three ways strings and buffers can be converted to C:
``w*`` (read-write :term:`bytes-like object`) [Py_buffer]
This format accepts any object which implements the read-write buffer
interface. It fills a :c:type:`Py_buffer` structure provided by the caller.
The buffer may contain embedded null bytes. The caller have to call
The buffer may contain embedded null bytes. The caller has to call
:c:func:`PyBuffer_Release` when it is done with the buffer.
``es`` (:class:`str`) [const char \*encoding, char \*\*buffer]
@ -305,7 +305,7 @@ the minimal value for the corresponding signed integer type of the same size.
``D`` (:class:`complex`) [Py_complex]
Convert a Python complex number to a C :c:type:`Py_complex` structure.
.. deprecated:: next
.. deprecated:: 3.15
For unsigned integer formats ``B``, ``H``, ``I``, ``k`` and ``K``,
:exc:`DeprecationWarning` is emitted when the value is larger than

View file

@ -261,6 +261,10 @@ readonly, format
MUST be consistent for all consumers. For example, :c:expr:`PyBUF_SIMPLE | PyBUF_WRITABLE`
can be used to request a simple writable buffer.
.. c:macro:: PyBUF_WRITEABLE
This is a :term:`soft deprecated` alias to :c:macro:`PyBUF_WRITABLE`.
.. c:macro:: PyBUF_FORMAT
Controls the :c:member:`~Py_buffer.format` field. If set, this field MUST

View file

@ -47,6 +47,10 @@ called with a non-bytes parameter.
*len* on success, and ``NULL`` on failure. If *v* is ``NULL``, the contents of
the bytes object are uninitialized.
.. deprecated:: 3.15
``PyBytes_FromStringAndSize(NULL, len)`` is :term:`soft deprecated`,
use the :c:type:`PyBytesWriter` API instead.
.. c:function:: PyObject* PyBytes_FromFormat(const char *format, ...)
@ -219,3 +223,209 @@ called with a non-bytes parameter.
reallocation fails, the original bytes object at *\*bytes* is deallocated,
*\*bytes* is set to ``NULL``, :exc:`MemoryError` is set, and ``-1`` is
returned.
.. deprecated:: 3.15
The function is :term:`soft deprecated`,
use the :c:type:`PyBytesWriter` API instead.
.. c:function:: PyObject *PyBytes_Repr(PyObject *bytes, int smartquotes)
Get the string representation of *bytes*. This function is currently used to
implement :meth:`!bytes.__repr__` in Python.
This function does not do type checking; it is undefined behavior to pass
*bytes* as a non-bytes object or ``NULL``.
If *smartquotes* is true, the representation will use a double-quoted string
instead of single-quoted string when single-quotes are present in *bytes*.
For example, the byte string ``'Python'`` would be represented as
``b"'Python'"`` when *smartquotes* is true, or ``b'\'Python\''`` when it is
false.
On success, this function returns a :term:`strong reference` to a
:class:`str` object containing the representation. On failure, this
returns ``NULL`` with an exception set.
.. c:function:: PyObject *PyBytes_DecodeEscape(const char *s, Py_ssize_t len, const char *errors, Py_ssize_t unicode, const char *recode_encoding)
Unescape a backslash-escaped string *s*. *s* must not be ``NULL``.
*len* must be the size of *s*.
*errors* must be one of ``"strict"``, ``"replace"``, or ``"ignore"``. If
*errors* is ``NULL``, then ``"strict"`` is used by default.
On success, this function returns a :term:`strong reference` to a Python
:class:`bytes` object containing the unescaped string. On failure, this
function returns ``NULL`` with an exception set.
.. versionchanged:: 3.9
*unicode* and *recode_encoding* are now unused.
.. _pybyteswriter:
PyBytesWriter
-------------
The :c:type:`PyBytesWriter` API can be used to create a Python :class:`bytes`
object.
.. versionadded:: 3.15
.. c:type:: PyBytesWriter
A bytes writer instance.
The API is **not thread safe**: a writer should only be used by a single
thread at the same time.
The instance must be destroyed by :c:func:`PyBytesWriter_Finish` on
success, or :c:func:`PyBytesWriter_Discard` on error.
Create, Finish, Discard
^^^^^^^^^^^^^^^^^^^^^^^
.. c:function:: PyBytesWriter* PyBytesWriter_Create(Py_ssize_t size)
Create a :c:type:`PyBytesWriter` to write *size* bytes.
If *size* is greater than zero, allocate *size* bytes, and set the
writer size to *size*. The caller is responsible to write *size*
bytes using :c:func:`PyBytesWriter_GetData`.
This function does not overallocate.
On error, set an exception and return ``NULL``.
*size* must be positive or zero.
.. c:function:: PyObject* PyBytesWriter_Finish(PyBytesWriter *writer)
Finish a :c:type:`PyBytesWriter` created by
:c:func:`PyBytesWriter_Create`.
On success, return a Python :class:`bytes` object.
On error, set an exception and return ``NULL``.
The writer instance is invalid after the call in any case.
No API can be called on the writer after :c:func:`PyBytesWriter_Finish`.
.. c:function:: PyObject* PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
Similar to :c:func:`PyBytesWriter_Finish`, but resize the writer
to *size* bytes before creating the :class:`bytes` object.
.. c:function:: PyObject* PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf)
Similar to :c:func:`PyBytesWriter_Finish`, but resize the writer
using *buf* pointer before creating the :class:`bytes` object.
Set an exception and return ``NULL`` if *buf* pointer is outside the
internal buffer bounds.
Function pseudo-code::
Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer);
return PyBytesWriter_FinishWithSize(writer, size);
.. c:function:: void PyBytesWriter_Discard(PyBytesWriter *writer)
Discard a :c:type:`PyBytesWriter` created by :c:func:`PyBytesWriter_Create`.
Do nothing if *writer* is ``NULL``.
The writer instance is invalid after the call.
No API can be called on the writer after :c:func:`PyBytesWriter_Discard`.
High-level API
^^^^^^^^^^^^^^
.. c:function:: int PyBytesWriter_WriteBytes(PyBytesWriter *writer, const void *bytes, Py_ssize_t size)
Grow the *writer* internal buffer by *size* bytes,
write *size* bytes of *bytes* at the *writer* end,
and add *size* to the *writer* size.
If *size* is equal to ``-1``, call ``strlen(bytes)`` to get the
string length.
On success, return ``0``.
On error, set an exception and return ``-1``.
.. c:function:: int PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...)
Similar to :c:func:`PyBytes_FromFormat`, but write the output directly at
the writer end. Grow the writer internal buffer on demand. Then add the
written size to the writer size.
On success, return ``0``.
On error, set an exception and return ``-1``.
Getters
^^^^^^^
.. c:function:: Py_ssize_t PyBytesWriter_GetSize(PyBytesWriter *writer)
Get the writer size.
.. c:function:: void* PyBytesWriter_GetData(PyBytesWriter *writer)
Get the writer data: start of the internal buffer.
The pointer is valid until :c:func:`PyBytesWriter_Finish` or
:c:func:`PyBytesWriter_Discard` is called on *writer*.
Low-level API
^^^^^^^^^^^^^
.. c:function:: int PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size)
Resize the writer to *size* bytes. It can be used to enlarge or to
shrink the writer.
This function typically overallocates to achieve amortized performance when
resizing multiple times.
Newly allocated bytes are left uninitialized.
On success, return ``0``.
On error, set an exception and return ``-1``.
*size* must be positive or zero.
.. c:function:: int PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t grow)
Resize the writer by adding *grow* bytes to the current writer size.
This function typically overallocates to achieve amortized performance when
resizing multiple times.
Newly allocated bytes are left uninitialized.
On success, return ``0``.
On error, set an exception and return ``-1``.
*size* can be negative to shrink the writer.
.. c:function:: void* PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, Py_ssize_t size, void *buf)
Similar to :c:func:`PyBytesWriter_Grow`, but update also the *buf*
pointer.
The *buf* pointer is moved if the internal buffer is moved in memory.
The *buf* relative position within the internal buffer is left
unchanged.
On error, set an exception and return ``NULL``.
*buf* must not be ``NULL``.
Function pseudo-code::
Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer);
if (PyBytesWriter_Grow(writer, size) < 0) {
return NULL;
}
return (char*)PyBytesWriter_GetData(writer) + pos;

View file

@ -15,13 +15,19 @@ Refer to :ref:`using-capsules` for more information on using these objects.
.. c:type:: PyCapsule
This subtype of :c:type:`PyObject` represents an opaque value, useful for C
extension modules who need to pass an opaque value (as a :c:expr:`void*`
extension modules which need to pass an opaque value (as a :c:expr:`void*`
pointer) through Python code to other C code. It is often used to make a C
function pointer defined in one module available to other modules, so the
regular import mechanism can be used to access C APIs defined in dynamically
loaded modules.
.. c:var:: PyTypeObject PyCapsule_Type
The type object corresponding to capsule objects. This is the same object
as :class:`types.CapsuleType` in the Python layer.
.. c:type:: PyCapsule_Destructor
The type of a destructor callback for a capsule. Defined as::

View file

@ -7,7 +7,7 @@ Cell Objects
"Cell" objects are used to implement variables referenced by multiple scopes.
For each such variable, a cell object is created to store the value; the local
variables of each stack frame that references the value contains a reference to
variables of each stack frame that references the value contain a reference to
the cells from outer scopes which also use that variable. When the value is
accessed, the value contained in the cell is used instead of the cell object
itself. This de-referencing of the cell object requires support from the

View file

@ -211,6 +211,17 @@ bound into a function.
.. versionadded:: 3.12
.. c:function:: PyObject *PyCode_Optimize(PyObject *code, PyObject *consts, PyObject *names, PyObject *lnotab_obj)
This is a :term:`soft deprecated` function that does nothing.
Prior to Python 3.10, this function would perform basic optimizations to a
code object.
.. versionchanged:: 3.10
This function now does nothing.
.. _c_codeobject_flags:
Code Object Flags
@ -289,7 +300,7 @@ may change without deprecation warnings.
.. c:function:: Py_ssize_t PyUnstable_Eval_RequestCodeExtraIndex(freefunc free)
Return a new an opaque index value used to adding data to code objects.
Return a new opaque index value used to adding data to code objects.
You generally call this function once (per interpreter) and use the result
with ``PyCode_GetExtra`` and ``PyCode_SetExtra`` to manipulate

View file

@ -7,7 +7,7 @@ Codec registry and support functions
Register a new codec search function.
As side effect, this tries to load the :mod:`!encodings` package, if not yet
As a side effect, this tries to load the :mod:`!encodings` package, if not yet
done, to make sure that it is always first in the list of search functions.
.. c:function:: int PyCodec_Unregister(PyObject *search_function)
@ -39,7 +39,7 @@ Codec registry and support functions
*object* is passed through the decoder function found for the given
*encoding* using the error handling method defined by *errors*. *errors* may
be ``NULL`` to use the default method defined for the codec. Raises a
:exc:`LookupError` if no encoder can be found.
:exc:`LookupError` if no decoder can be found.
Codec lookup API
@ -129,3 +129,13 @@ Registry API for Unicode encoding error handlers
Replace the unicode encode error with ``\N{...}`` escapes.
.. versionadded:: 3.5
Codec utility variables
-----------------------
.. c:var:: const char *Py_hexdigits
A string constant containing the lowercase hexadecimal digits: ``"0123456789abcdef"``.
.. versionadded:: 3.3

View file

@ -16,7 +16,7 @@ Complex Number Objects
The complex number value, using the C :c:type:`Py_complex` representation.
.. deprecated-removed:: next 3.20
.. deprecated-removed:: 3.15 3.20
Use :c:func:`PyComplex_AsCComplex` and
:c:func:`PyComplex_FromCComplex` to convert a
Python complex number to/from the C :c:type:`Py_complex`
@ -82,7 +82,7 @@ Complex Number Objects
.. c:type:: Py_complex
This C structure defines export format for a Python complex
This C structure defines an export format for a Python complex
number object.
.. c:member:: double real

View file

@ -109,11 +109,20 @@ Other Objects
descriptor.rst
slice.rst
memoryview.rst
picklebuffer.rst
weakref.rst
capsule.rst
frame.rst
gen.rst
coro.rst
contextvars.rst
datetime.rst
typehints.rst
C API for extension modules
===========================
.. toctree::
curses.rst
datetime.rst

View file

@ -41,7 +41,7 @@ The return value (*rv*) for these functions should be interpreted as follows:
``rv + 1`` bytes would have been needed to succeed. ``str[size-1]`` is ``'\0'``
in this case.
* When ``rv < 0``, "something bad happened." ``str[size-1]`` is ``'\0'`` in
* When ``rv < 0``, the output conversion failed and ``str[size-1]`` is ``'\0'`` in
this case too, but the rest of *str* is undefined. The exact cause of the error
depends on the underlying platform.
@ -105,7 +105,7 @@ The following functions provide locale-independent string to number conversions.
If ``s`` represents a value that is too large to store in a float
(for example, ``"1e500"`` is such a string on many platforms) then
if ``overflow_exception`` is ``NULL`` return ``Py_INFINITY`` (with
if ``overflow_exception`` is ``NULL`` return :c:macro:`!INFINITY` (with
an appropriate sign) and don't set any exception. Otherwise,
``overflow_exception`` must point to a Python exception object;
raise that exception and return ``-1.0``. In both cases, set
@ -128,18 +128,28 @@ The following functions provide locale-independent string to number conversions.
must be 0 and is ignored. The ``'r'`` format code specifies the
standard :func:`repr` format.
*flags* can be zero or more of the values ``Py_DTSF_SIGN``,
``Py_DTSF_ADD_DOT_0``, or ``Py_DTSF_ALT``, or-ed together:
*flags* can be zero or more of the following values or-ed together:
* ``Py_DTSF_SIGN`` means to always precede the returned string with a sign
character, even if *val* is non-negative.
.. c:macro:: Py_DTSF_SIGN
* ``Py_DTSF_ADD_DOT_0`` means to ensure that the returned string will not look
like an integer.
Always precede the returned string with a sign
character, even if *val* is non-negative.
* ``Py_DTSF_ALT`` means to apply "alternate" formatting rules. See the
documentation for the :c:func:`PyOS_snprintf` ``'#'`` specifier for
details.
.. c:macro:: Py_DTSF_ADD_DOT_0
Ensure that the returned string will not look like an integer.
.. c:macro:: Py_DTSF_ALT
Apply "alternate" formatting rules.
See the documentation for the :c:func:`PyOS_snprintf` ``'#'`` specifier for
details.
.. c:macro:: Py_DTSF_NO_NEG_0
Negative zero is converted to positive zero.
.. versionadded:: 3.11
If *ptype* is non-``NULL``, then the value it points to will be set to one of
``Py_DTST_FINITE``, ``Py_DTST_INFINITE``, or ``Py_DTST_NAN``, signifying that
@ -152,13 +162,85 @@ The following functions provide locale-independent string to number conversions.
.. versionadded:: 3.1
.. c:function:: int PyOS_stricmp(const char *s1, const char *s2)
.. c:function:: int PyOS_mystricmp(const char *str1, const char *str2)
int PyOS_mystrnicmp(const char *str1, const char *str2, Py_ssize_t size)
Case insensitive comparison of strings. The function works almost
identically to :c:func:`!strcmp` except that it ignores the case.
Case insensitive comparison of strings. These functions work almost
identically to :c:func:`!strcmp` and :c:func:`!strncmp` (respectively),
except that they ignore the case of ASCII characters.
Return ``0`` if the strings are equal, a negative value if *str1* sorts
lexicographically before *str2*, or a positive value if it sorts after.
In the *str1* or *str2* arguments, a NUL byte marks the end of the string.
For :c:func:`!PyOS_mystrnicmp`, the *size* argument gives the maximum size
of the string, as if NUL was present at the index given by *size*.
These functions do not use the locale.
.. c:function:: int PyOS_strnicmp(const char *s1, const char *s2, Py_ssize_t size)
.. c:function:: int PyOS_stricmp(const char *str1, const char *str2)
int PyOS_strnicmp(const char *str1, const char *str2, Py_ssize_t size)
Case insensitive comparison of strings. The function works almost
identically to :c:func:`!strncmp` except that it ignores the case.
Case insensitive comparison of strings.
On Windows, these are aliases of :c:func:`!stricmp` and :c:func:`!strnicmp`,
respectively.
On other platforms, they are aliases of :c:func:`PyOS_mystricmp` and
:c:func:`PyOS_mystrnicmp`, respectively.
Character classification and conversion
=======================================
The following macros provide locale-independent (unlike the C standard library
``ctype.h``) character classification and conversion.
The argument must be a signed or unsigned :c:expr:`char`.
.. c:macro:: Py_ISALNUM(c)
Return true if the character *c* is an alphanumeric character.
.. c:macro:: Py_ISALPHA(c)
Return true if the character *c* is an alphabetic character (``a-z`` and ``A-Z``).
.. c:macro:: Py_ISDIGIT(c)
Return true if the character *c* is a decimal digit (``0-9``).
.. c:macro:: Py_ISLOWER(c)
Return true if the character *c* is a lowercase ASCII letter (``a-z``).
.. c:macro:: Py_ISUPPER(c)
Return true if the character *c* is an uppercase ASCII letter (``A-Z``).
.. c:macro:: Py_ISSPACE(c)
Return true if the character *c* is a whitespace character (space, tab,
carriage return, newline, vertical tab, or form feed).
.. c:macro:: Py_ISXDIGIT(c)
Return true if the character *c* is a hexadecimal digit (``0-9``, ``a-f``, and
``A-F``).
.. c:macro:: Py_TOLOWER(c)
Return the lowercase equivalent of the character *c*.
.. c:macro:: Py_TOUPPER(c)
Return the uppercase equivalent of the character *c*.

138
Doc/c-api/curses.rst Normal file
View file

@ -0,0 +1,138 @@
.. highlight:: c
Curses C API
------------
:mod:`curses` exposes a small C interface for extension modules.
Consumers must include the header file :file:`py_curses.h` (which is not
included by default by :file:`Python.h`) and :c:func:`import_curses` must
be invoked, usually as part of the module initialisation function, to populate
:c:var:`PyCurses_API`.
.. warning::
Neither the C API nor the pure Python :mod:`curses` module are compatible
with subinterpreters.
.. c:macro:: import_curses()
Import the curses C API. The macro does not need a semi-colon to be called.
On success, populate the :c:var:`PyCurses_API` pointer.
On failure, set :c:var:`PyCurses_API` to NULL and set an exception.
The caller must check if an error occurred via :c:func:`PyErr_Occurred`:
.. code-block::
import_curses(); // semi-colon is optional but recommended
if (PyErr_Occurred()) { /* cleanup */ }
.. c:var:: void **PyCurses_API
Dynamically allocated object containing the curses C API.
This variable is only available once :c:macro:`import_curses` succeeds.
``PyCurses_API[0]`` corresponds to :c:data:`PyCursesWindow_Type`.
``PyCurses_API[1]``, ``PyCurses_API[2]``, and ``PyCurses_API[3]``
are pointers to predicate functions of type ``int (*)(void)``.
When called, these predicates return whether :func:`curses.setupterm`,
:func:`curses.initscr`, and :func:`curses.start_color` have been called
respectively.
See also the convenience macros :c:macro:`PyCursesSetupTermCalled`,
:c:macro:`PyCursesInitialised`, and :c:macro:`PyCursesInitialisedColor`.
.. note::
The number of entries in this structure is subject to changes.
Consider using :c:macro:`PyCurses_API_pointers` to check if
new fields are available or not.
.. c:macro:: PyCurses_API_pointers
The number of accessible fields (``4``) in :c:var:`PyCurses_API`.
This number is incremented whenever new fields are added.
.. c:var:: PyTypeObject PyCursesWindow_Type
The :ref:`heap type <heap-types>` corresponding to :class:`curses.window`.
.. c:function:: int PyCursesWindow_Check(PyObject *op)
Return true if *op* is a :class:`curses.window` instance, false otherwise.
The following macros are convenience macros expanding into C statements.
In particular, they can only be used as ``macro;`` or ``macro``, but not
``macro()`` or ``macro();``.
.. c:macro:: PyCursesSetupTermCalled
Macro checking if :func:`curses.setupterm` has been called.
The macro expansion is roughly equivalent to:
.. code-block::
{
typedef int (*predicate_t)(void);
predicate_t was_setupterm_called = (predicate_t)PyCurses_API[1];
if (!was_setupterm_called()) {
return NULL;
}
}
.. c:macro:: PyCursesInitialised
Macro checking if :func:`curses.initscr` has been called.
The macro expansion is roughly equivalent to:
.. code-block::
{
typedef int (*predicate_t)(void);
predicate_t was_initscr_called = (predicate_t)PyCurses_API[2];
if (!was_initscr_called()) {
return NULL;
}
}
.. c:macro:: PyCursesInitialisedColor
Macro checking if :func:`curses.start_color` has been called.
The macro expansion is roughly equivalent to:
.. code-block::
{
typedef int (*predicate_t)(void);
predicate_t was_start_color_called = (predicate_t)PyCurses_API[3];
if (!was_start_color_called()) {
return NULL;
}
}
Internal data
-------------
The following objects are exposed by the C API but should be considered
internal-only.
.. c:macro:: PyCurses_CAPSULE_NAME
Name of the curses capsule to pass to :c:func:`PyCapsule_Import`.
Internal usage only. Use :c:macro:`import_curses` instead.

View file

@ -8,11 +8,42 @@ DateTime Objects
Various date and time objects are supplied by the :mod:`datetime` module.
Before using any of these functions, the header file :file:`datetime.h` must be
included in your source (note that this is not included by :file:`Python.h`),
and the macro :c:macro:`!PyDateTime_IMPORT` must be invoked, usually as part of
and the macro :c:macro:`PyDateTime_IMPORT` must be invoked, usually as part of
the module initialisation function. The macro puts a pointer to a C structure
into a static variable, :c:data:`!PyDateTimeAPI`, that is used by the following
into a static variable, :c:data:`PyDateTimeAPI`, that is used by the following
macros.
.. c:macro:: PyDateTime_IMPORT()
Import the datetime C API.
On success, populate the :c:var:`PyDateTimeAPI` pointer.
On failure, set :c:var:`PyDateTimeAPI` to ``NULL`` and set an exception.
The caller must check if an error occurred via :c:func:`PyErr_Occurred`:
.. code-block::
PyDateTime_IMPORT;
if (PyErr_Occurred()) { /* cleanup */ }
.. warning::
This is not compatible with subinterpreters.
.. c:type:: PyDateTime_CAPI
Structure containing the fields for the datetime C API.
The fields of this structure are private and subject to change.
Do not use this directly; prefer ``PyDateTime_*`` APIs instead.
.. c:var:: PyDateTime_CAPI *PyDateTimeAPI
Dynamically allocated object containing the datetime C API.
This variable is only available once :c:macro:`PyDateTime_IMPORT` succeeds.
.. c:type:: PyDateTime_Date
This subtype of :c:type:`PyObject` represents a Python date object.
@ -46,7 +77,7 @@ macros.
.. c:var:: PyTypeObject PyDateTime_DeltaType
This instance of :c:type:`PyTypeObject` represents Python type for
This instance of :c:type:`PyTypeObject` represents the Python type for
the difference between two datetime values;
it is the same object as :class:`datetime.timedelta` in the Python layer.
@ -325,3 +356,16 @@ Macros for the convenience of modules implementing the DB API:
Create and return a new :class:`datetime.date` object given an argument
tuple suitable for passing to :meth:`datetime.date.fromtimestamp`.
Internal data
-------------
The following symbols are exposed by the C API but should be considered
internal-only.
.. c:macro:: PyDateTime_CAPSULE_NAME
Name of the datetime capsule to pass to :c:func:`PyCapsule_Import`.
Internal usage only. Use :c:macro:`PyDateTime_IMPORT` instead.

View file

@ -21,20 +21,104 @@ found in the dictionary of type objects.
.. c:function:: PyObject* PyDescr_NewMember(PyTypeObject *type, struct PyMemberDef *meth)
.. c:var:: PyTypeObject PyMemberDescr_Type
The type object for member descriptor objects created from
:c:type:`PyMemberDef` structures. These descriptors expose fields of a
C struct as attributes on a type, and correspond
to :class:`types.MemberDescriptorType` objects in Python.
.. c:var:: PyTypeObject PyGetSetDescr_Type
The type object for get/set descriptor objects created from
:c:type:`PyGetSetDef` structures. These descriptors implement attributes
whose value is computed by C getter and setter functions, and are used
for many built-in type attributes.
.. c:function:: PyObject* PyDescr_NewMethod(PyTypeObject *type, struct PyMethodDef *meth)
.. c:var:: PyTypeObject PyMethodDescr_Type
The type object for method descriptor objects created from
:c:type:`PyMethodDef` structures. These descriptors expose C functions as
methods on a type, and correspond to :class:`types.MemberDescriptorType`
objects in Python.
.. c:function:: PyObject* PyDescr_NewWrapper(PyTypeObject *type, struct wrapperbase *wrapper, void *wrapped)
.. c:var:: PyTypeObject PyWrapperDescr_Type
The type object for wrapper descriptor objects created by
:c:func:`PyDescr_NewWrapper` and :c:func:`PyWrapper_New`. Wrapper
descriptors are used internally to expose special methods implemented
via wrapper structures, and appear in Python as
:class:`types.WrapperDescriptorType` objects.
.. c:function:: PyObject* PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method)
.. c:function:: int PyDescr_IsData(PyObject *descr)
Return non-zero if the descriptor objects *descr* describes a data attribute, or
Return non-zero if the descriptor object *descr* describes a data attribute, or
``0`` if it describes a method. *descr* must be a descriptor object; there is
no error checking.
.. c:function:: PyObject* PyWrapper_New(PyObject *, PyObject *)
Built-in descriptors
^^^^^^^^^^^^^^^^^^^^
.. c:var:: PyTypeObject PySuper_Type
The type object for super objects. This is the same object as
:class:`super` in the Python layer.
.. c:var:: PyTypeObject PyClassMethod_Type
The type of class method objects. This is the same object as
:class:`classmethod` in the Python layer.
.. c:var:: PyTypeObject PyClassMethodDescr_Type
The type object for C-level class method descriptor objects.
This is the type of the descriptors created for :func:`classmethod` defined in
C extension types, and is the same object as :class:`classmethod`
in Python.
.. c:function:: PyObject *PyClassMethod_New(PyObject *callable)
Create a new :class:`classmethod` object wrapping *callable*.
*callable* must be a callable object and must not be ``NULL``.
On success, this function returns a :term:`strong reference` to a new class
method descriptor. On failure, this function returns ``NULL`` with an
exception set.
.. c:var:: PyTypeObject PyStaticMethod_Type
The type of static method objects. This is the same object as
:class:`staticmethod` in the Python layer.
.. c:function:: PyObject *PyStaticMethod_New(PyObject *callable)
Create a new :class:`staticmethod` object wrapping *callable*.
*callable* must be a callable object and must not be ``NULL``.
On success, this function returns a :term:`strong reference` to a new static
method descriptor. On failure, this function returns ``NULL`` with an
exception set.

View file

@ -43,6 +43,17 @@ Dictionary Objects
prevent modification of the dictionary for non-dynamic class types.
.. c:var:: PyTypeObject PyDictProxy_Type
The type object for mapping proxy objects created by
:c:func:`PyDictProxy_New` and for the read-only ``__dict__`` attribute
of many built-in types. A :c:type:`PyDictProxy_Type` instance provides a
dynamic, read-only view of an underlying dictionary: changes to the
underlying dictionary are reflected in the proxy, but the proxy itself
does not support mutation operations. This corresponds to
:class:`types.MappingProxyType` in Python.
.. c:function:: void PyDict_Clear(PyObject *p)
Empty an existing dictionary of all key-value pairs.
@ -50,7 +61,7 @@ Dictionary Objects
.. c:function:: int PyDict_Contains(PyObject *p, PyObject *key)
Determine if dictionary *p* contains *key*. If an item in *p* is matches
Determine if dictionary *p* contains *key*. If an item in *p* matches
*key*, return ``1``, otherwise return ``0``. On error, return ``-1``.
This is equivalent to the Python expression ``key in p``.
@ -198,7 +209,7 @@ Dictionary Objects
.. c:function:: int PyDict_Pop(PyObject *p, PyObject *key, PyObject **result)
Remove *key* from dictionary *p* and optionally return the removed value.
Do not raise :exc:`KeyError` if the key missing.
Do not raise :exc:`KeyError` if the key is missing.
- If the key is present, set *\*result* to a new reference to the removed
value if *result* is not ``NULL``, and return ``1``.
@ -207,7 +218,7 @@ Dictionary Objects
- On error, raise an exception and return ``-1``.
Similar to :meth:`dict.pop`, but without the default value and
not raising :exc:`KeyError` if the key missing.
not raising :exc:`KeyError` if the key is missing.
.. versionadded:: 3.13
@ -245,6 +256,11 @@ Dictionary Objects
``len(p)`` on a dictionary.
.. c:function:: Py_ssize_t PyDict_GET_SIZE(PyObject *p)
Similar to :c:func:`PyDict_Size`, but without error checking.
.. c:function:: int PyDict_Next(PyObject *p, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue)
Iterate over all key-value pairs in the dictionary *p*. The
@ -426,3 +442,138 @@ Dictionary Objects
it before returning.
.. versionadded:: 3.12
Dictionary View Objects
^^^^^^^^^^^^^^^^^^^^^^^
.. c:function:: int PyDictViewSet_Check(PyObject *op)
Return true if *op* is a view of a set inside a dictionary. This is currently
equivalent to :c:expr:`PyDictKeys_Check(op) || PyDictItems_Check(op)`. This
function always succeeds.
.. c:var:: PyTypeObject PyDictKeys_Type
Type object for a view of dictionary keys. In Python, this is the type of
the object returned by :meth:`dict.keys`.
.. c:function:: int PyDictKeys_Check(PyObject *op)
Return true if *op* is an instance of a dictionary keys view. This function
always succeeds.
.. c:var:: PyTypeObject PyDictValues_Type
Type object for a view of dictionary values. In Python, this is the type of
the object returned by :meth:`dict.values`.
.. c:function:: int PyDictValues_Check(PyObject *op)
Return true if *op* is an instance of a dictionary values view. This function
always succeeds.
.. c:var:: PyTypeObject PyDictItems_Type
Type object for a view of dictionary items. In Python, this is the type of
the object returned by :meth:`dict.items`.
.. c:function:: int PyDictItems_Check(PyObject *op)
Return true if *op* is an instance of a dictionary items view. This function
always succeeds.
Ordered Dictionaries
^^^^^^^^^^^^^^^^^^^^
Python's C API provides interface for :class:`collections.OrderedDict` from C.
Since Python 3.7, dictionaries are ordered by default, so there is usually
little need for these functions; prefer ``PyDict*`` where possible.
.. c:var:: PyTypeObject PyODict_Type
Type object for ordered dictionaries. This is the same object as
:class:`collections.OrderedDict` in the Python layer.
.. c:function:: int PyODict_Check(PyObject *od)
Return true if *od* is an ordered dictionary object or an instance of a
subtype of the :class:`~collections.OrderedDict` type. This function
always succeeds.
.. c:function:: int PyODict_CheckExact(PyObject *od)
Return true if *od* is an ordered dictionary object, but not an instance of
a subtype of the :class:`~collections.OrderedDict` type.
This function always succeeds.
.. c:var:: PyTypeObject PyODictKeys_Type
Analogous to :c:type:`PyDictKeys_Type` for ordered dictionaries.
.. c:var:: PyTypeObject PyODictValues_Type
Analogous to :c:type:`PyDictValues_Type` for ordered dictionaries.
.. c:var:: PyTypeObject PyODictItems_Type
Analogous to :c:type:`PyDictItems_Type` for ordered dictionaries.
.. c:function:: PyObject *PyODict_New(void)
Return a new empty ordered dictionary, or ``NULL`` on failure.
This is analogous to :c:func:`PyDict_New`.
.. c:function:: int PyODict_SetItem(PyObject *od, PyObject *key, PyObject *value)
Insert *value* into the ordered dictionary *od* with a key of *key*.
Return ``0`` on success or ``-1`` with an exception set on failure.
This is analogous to :c:func:`PyDict_SetItem`.
.. c:function:: int PyODict_DelItem(PyObject *od, PyObject *key)
Remove the entry in the ordered dictionary *od* with key *key*.
Return ``0`` on success or ``-1`` with an exception set on failure.
This is analogous to :c:func:`PyDict_DelItem`.
These are :term:`soft deprecated` aliases to ``PyDict`` APIs:
.. list-table::
:widths: auto
:header-rows: 1
* * ``PyODict``
* ``PyDict``
* * .. c:macro:: PyODict_GetItem(od, key)
* :c:func:`PyDict_GetItem`
* * .. c:macro:: PyODict_GetItemWithError(od, key)
* :c:func:`PyDict_GetItemWithError`
* * .. c:macro:: PyODict_GetItemString(od, key)
* :c:func:`PyDict_GetItemString`
* * .. c:macro:: PyODict_Contains(od, key)
* :c:func:`PyDict_Contains`
* * .. c:macro:: PyODict_Size(od)
* :c:func:`PyDict_Size`
* * .. c:macro:: PyODict_SIZE(od)
* :c:func:`PyDict_GET_SIZE`

View file

@ -309,6 +309,14 @@ For convenience, some of these functions will always return a
.. versionadded:: 3.4
.. c:function:: void PyErr_RangedSyntaxLocationObject(PyObject *filename, int lineno, int col_offset, int end_lineno, int end_col_offset)
Similar to :c:func:`PyErr_SyntaxLocationObject`, but also sets the
*end_lineno* and *end_col_offset* information for the current exception.
.. versionadded:: 3.10
.. c:function:: void PyErr_SyntaxLocationEx(const char *filename, int lineno, int col_offset)
Like :c:func:`PyErr_SyntaxLocationObject`, but *filename* is a byte string
@ -331,6 +339,23 @@ For convenience, some of these functions will always return a
use.
.. c:function:: PyObject *PyErr_ProgramTextObject(PyObject *filename, int lineno)
Get the source line in *filename* at line *lineno*. *filename* should be a
Python :class:`str` object.
On success, this function returns a Python string object with the found line.
On failure, this function returns ``NULL`` without an exception set.
.. c:function:: PyObject *PyErr_ProgramText(const char *filename, int lineno)
Similar to :c:func:`PyErr_ProgramTextObject`, but *filename* is a
:c:expr:`const char *`, which is decoded with the
:term:`filesystem encoding and error handler`, instead of a
Python object reference.
Issuing warnings
================
@ -394,6 +419,15 @@ an error value).
.. versionadded:: 3.2
.. c:function:: int PyErr_WarnExplicitFormat(PyObject *category, const char *filename, int lineno, const char *module, PyObject *registry, const char *format, ...)
Similar to :c:func:`PyErr_WarnExplicit`, but uses
:c:func:`PyUnicode_FromFormat` to format the warning message. *format* is
an ASCII-encoded string.
.. versionadded:: 3.2
.. c:function:: int PyErr_ResourceWarning(PyObject *source, Py_ssize_t stack_level, const char *format, ...)
Function similar to :c:func:`PyErr_WarnFormat`, but *category* is
@ -762,6 +796,17 @@ Exception Classes
Exception Objects
=================
.. c:function:: int PyExceptionInstance_Check(PyObject *op)
Return true if *op* is an instance of :class:`BaseException`, false
otherwise. This function always succeeds.
.. c:macro:: PyExceptionInstance_Class(op)
Equivalent to :c:func:`Py_TYPE(op) <Py_TYPE>`.
.. c:function:: PyObject* PyException_GetTraceback(PyObject *ex)
Return the traceback associated with the exception as a new reference, as
@ -939,6 +984,9 @@ because the :ref:`call protocol <call>` takes care of recursion handling.
be concatenated to the :exc:`RecursionError` message caused by the recursion
depth limit.
.. seealso::
The :c:func:`PyUnstable_ThreadState_SetStackProtection` function.
.. versionchanged:: 3.9
This function is now also available in the :ref:`limited API <limited-c-api>`.
@ -979,6 +1027,27 @@ these are the C equivalent to :func:`reprlib.recursive_repr`.
Ends a :c:func:`Py_ReprEnter`. Must be called once for each
invocation of :c:func:`Py_ReprEnter` that returns zero.
.. c:function:: int Py_GetRecursionLimit(void)
Get the recursion limit for the current interpreter. It can be set with
:c:func:`Py_SetRecursionLimit`. The recursion limit prevents the
Python interpreter stack from growing infinitely.
This function cannot fail, and the caller must hold an
:term:`attached thread state`.
.. seealso::
:py:func:`sys.getrecursionlimit`
.. c:function:: void Py_SetRecursionLimit(int new_limit)
Set the recursion limit for the current interpreter.
This function cannot fail, and the caller must hold an
:term:`attached thread state`.
.. seealso::
:py:func:`sys.setrecursionlimit`
.. _standardexceptions:
@ -1207,3 +1276,37 @@ Warning types
.. versionadded:: 3.10
:c:data:`PyExc_EncodingWarning`.
Tracebacks
==========
.. c:var:: PyTypeObject PyTraceBack_Type
Type object for traceback objects. This is available as
:class:`types.TracebackType` in the Python layer.
.. c:function:: int PyTraceBack_Check(PyObject *op)
Return true if *op* is a traceback object, false otherwise. This function
does not account for subtypes.
.. c:function:: int PyTraceBack_Here(PyFrameObject *f)
Replace the :attr:`~BaseException.__traceback__` attribute on the current
exception with a new traceback prepending *f* to the existing chain.
Calling this function without an exception set is undefined behavior.
This function returns ``0`` on success, and returns ``-1`` with an
exception set on failure.
.. c:function:: int PyTraceBack_Print(PyObject *tb, PyObject *f)
Write the traceback *tb* into the file *f*.
This function returns ``0`` on success, and returns ``-1`` with an
exception set on failure.

View file

@ -8,7 +8,8 @@ Defining extension modules
A C extension for CPython is a shared library (for example, a ``.so`` file
on Linux, ``.pyd`` DLL on Windows), which is loadable into the Python process
(for example, it is compiled with compatible compiler settings), and which
exports an :ref:`initialization function <extension-export-hook>`.
exports an :dfn:`export hook` function (or an
old-style :ref:`initialization function <extension-pyinit>`).
To be importable by default (that is, by
:py:class:`importlib.machinery.ExtensionFileLoader`),
@ -23,25 +24,127 @@ and must be named after the module name plus an extension listed in
One suitable tool is Setuptools, whose documentation can be found at
https://setuptools.pypa.io/en/latest/setuptools.html.
Normally, the initialization function returns a module definition initialized
using :c:func:`PyModuleDef_Init`.
This allows splitting the creation process into several phases:
.. _extension-export-hook:
Extension export hook
.....................
.. versionadded:: 3.15
Support for the :samp:`PyModExport_{<name>}` export hook was added in Python
3.15. The older way of defining modules is still available: consult either
the :ref:`extension-pyinit` section or earlier versions of this
documentation if you plan to support earlier Python versions.
The export hook must be an exported function with the following signature:
.. c:function:: PyModuleDef_Slot *PyModExport_modulename(void)
For modules with ASCII-only names, the :ref:`export hook <extension-export-hook>`
must be named :samp:`PyModExport_{<name>}`,
with ``<name>`` replaced by the module's name.
For non-ASCII module names, the export hook must instead be named
:samp:`PyModExportU_{<name>}` (note the ``U``), with ``<name>`` encoded using
Python's *punycode* encoding with hyphens replaced by underscores. In Python:
.. code-block:: python
def hook_name(name):
try:
suffix = b'_' + name.encode('ascii')
except UnicodeEncodeError:
suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
return b'PyModExport' + suffix
The export hook returns an array of :c:type:`PyModuleDef_Slot` entries,
terminated by an entry with a slot ID of ``0``.
These slots describe how the module should be created and initialized.
This array must remain valid and constant until interpreter shutdown.
Typically, it should use ``static`` storage.
Prefer using the :c:macro:`Py_mod_create` and :c:macro:`Py_mod_exec` slots
for any dynamic behavior.
The export hook may return ``NULL`` with an exception set to signal failure.
It is recommended to define the export hook function using a helper macro:
.. c:macro:: PyMODEXPORT_FUNC
Declare an extension module export hook.
This macro:
* specifies the :c:expr:`PyModuleDef_Slot*` return type,
* adds any special linkage declarations required by the platform, and
* for C++, declares the function as ``extern "C"``.
For example, a module called ``spam`` would be defined like this::
PyABIInfo_VAR(abi_info);
static PyModuleDef_Slot spam_slots[] = {
{Py_mod_abi, &abi_info},
{Py_mod_name, "spam"},
{Py_mod_init, spam_init_function},
...
{0, NULL},
};
PyMODEXPORT_FUNC
PyModExport_spam(void)
{
return spam_slots;
}
The export hook is typically the only non-\ ``static``
item defined in the module's C source.
The hook should be kept short -- ideally, one line as above.
If you do need to use Python C API in this function, it is recommended to call
``PyABIInfo_Check(&abi_info, "modulename")`` first to raise an exception,
rather than crash, in common cases of ABI mismatch.
.. note::
It is possible to export multiple modules from a single shared library by
defining multiple export hooks.
However, importing them requires a custom importer or suitably named
copies/links of the extension file, because Python's import machinery only
finds the function corresponding to the filename.
See the `Multiple modules in one library <https://peps.python.org/pep-0489/#multiple-modules-in-one-library>`__
section in :pep:`489` for details.
.. _multi-phase-initialization:
Multi-phase initialization
..........................
The process of creating an extension module follows several phases:
- Python finds and calls the export hook to get information on how to
create the module.
- Before any substantial code is executed, Python can determine which
capabilities the module supports, and it can adjust the environment or
refuse loading an incompatible extension.
- By default, Python itself creates the module object -- that is, it does
the equivalent of :py:meth:`object.__new__` for classes.
It also sets initial attributes like :attr:`~module.__package__` and
:attr:`~module.__loader__`.
- Afterwards, the module object is initialized using extension-specific
code -- the equivalent of :py:meth:`~object.__init__` on classes.
Slots like :c:data:`Py_mod_abi`, :c:data:`Py_mod_gil` and
:c:data:`Py_mod_multiple_interpreters` influence this step.
- By default, Python itself then creates the module object -- that is, it does
the equivalent of calling :py:meth:`~object.__new__` when creating an object.
This step can be overridden using the :c:data:`Py_mod_create` slot.
- Python sets initial module attributes like :attr:`~module.__package__` and
:attr:`~module.__loader__`, and inserts the module object into
:py:attr:`sys.modules`.
- Afterwards, the module object is initialized in an extension-specific way
-- the equivalent of :py:meth:`~object.__init__` when creating an object,
or of executing top-level code in a Python-language module.
The behavior is specified using the :c:data:`Py_mod_exec` slot.
This is called *multi-phase initialization* to distinguish it from the legacy
(but still supported) *single-phase initialization* scheme,
where the initialization function returns a fully constructed module.
See the :ref:`single-phase-initialization section below <single-phase-initialization>`
for details.
(but still supported) :ref:`single-phase initialization <single-phase-initialization>`,
where an initialization function returns a fully constructed module.
.. versionchanged:: 3.5
@ -53,7 +156,7 @@ Multiple module instances
By default, extension modules are not singletons.
For example, if the :py:attr:`sys.modules` entry is removed and the module
is re-imported, a new module object is created, and typically populated with
is re-imported, a new module object is created and, typically, populated with
fresh method and type objects.
The old module is subject to normal garbage collection.
This mirrors the behavior of pure-Python modules.
@ -83,36 +186,34 @@ A module may also be limited to the main interpreter using
the :c:data:`Py_mod_multiple_interpreters` slot.
.. _extension-export-hook:
.. _extension-pyinit:
Initialization function
.......................
``PyInit`` function
...................
The initialization function defined by an extension module has the
following signature:
.. deprecated:: 3.15
This functionality is :term:`soft deprecated`.
It will not get new features, but there are no plans to remove it.
Instead of :c:func:`PyModExport_modulename`, an extension module can define
an older-style :dfn:`initialization function` with the signature:
.. c:function:: PyObject* PyInit_modulename(void)
Its name should be :samp:`PyInit_{<name>}`, with ``<name>`` replaced by the
name of the module.
For non-ASCII module names, use :samp:`PyInitU_{<name>}` instead, with
``<name>`` encoded in the same way as for the
:ref:`export hook <extension-export-hook>` (that is, using Punycode
with underscores).
For modules with ASCII-only names, the function must instead be named
:samp:`PyInit_{<name>}`, with ``<name>`` replaced by the name of the module.
When using :ref:`multi-phase-initialization`, non-ASCII module names
are allowed. In this case, the initialization function name is
:samp:`PyInitU_{<name>}`, with ``<name>`` encoded using Python's
*punycode* encoding with hyphens replaced by underscores. In Python:
If a module exports both :samp:`PyInit_{<name>}` and
:samp:`PyModExport_{<name>}`, the :samp:`PyInit_{<name>}` function
is ignored.
.. code-block:: python
def initfunc_name(name):
try:
suffix = b'_' + name.encode('ascii')
except UnicodeEncodeError:
suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
return b'PyInit' + suffix
It is recommended to define the initialization function using a helper macro:
Like with :c:macro:`PyMODEXPORT_FUNC`, it is recommended to define the
initialization function using a helper macro:
.. c:macro:: PyMODINIT_FUNC
@ -123,6 +224,34 @@ It is recommended to define the initialization function using a helper macro:
* adds any special linkage declarations required by the platform, and
* for C++, declares the function as ``extern "C"``.
Normally, the initialization function (``PyInit_modulename``) returns
a :c:type:`PyModuleDef` instance with non-``NULL``
:c:member:`~PyModuleDef.m_slots`. This allows Python to use
:ref:`multi-phase initialization <multi-phase-initialization>`.
Before it is returned, the ``PyModuleDef`` instance must be initialized
using the following function:
.. c:function:: PyObject* PyModuleDef_Init(PyModuleDef *def)
Ensure a module definition is a properly initialized Python object that
correctly reports its type and a reference count.
Return *def* cast to ``PyObject*``, or ``NULL`` if an error occurred.
Calling this function is required before returning a :c:type:`PyModuleDef`
from a module initialization function.
It should not be used in other contexts.
Note that Python assumes that ``PyModuleDef`` structures are statically
allocated.
This function may return either a new reference or a borrowed one;
this reference must not be released.
.. versionadded:: 3.5
For example, a module called ``spam`` would be defined like this::
static struct PyModuleDef spam_module = {
@ -137,59 +266,23 @@ For example, a module called ``spam`` would be defined like this::
return PyModuleDef_Init(&spam_module);
}
It is possible to export multiple modules from a single shared library by
defining multiple initialization functions. However, importing them requires
using symbolic links or a custom importer, because by default only the
function corresponding to the filename is found.
See the `Multiple modules in one library <https://peps.python.org/pep-0489/#multiple-modules-in-one-library>`__
section in :pep:`489` for details.
The initialization function is typically the only non-\ ``static``
item defined in the module's C source.
.. _multi-phase-initialization:
Multi-phase initialization
..........................
Normally, the :ref:`initialization function <extension-export-hook>`
(``PyInit_modulename``) returns a :c:type:`PyModuleDef` instance with
non-``NULL`` :c:member:`~PyModuleDef.m_slots`.
Before it is returned, the ``PyModuleDef`` instance must be initialized
using the following function:
.. c:function:: PyObject* PyModuleDef_Init(PyModuleDef *def)
Ensure a module definition is a properly initialized Python object that
correctly reports its type and a reference count.
Return *def* cast to ``PyObject*``, or ``NULL`` if an error occurred.
Calling this function is required for :ref:`multi-phase-initialization`.
It should not be used in other contexts.
Note that Python assumes that ``PyModuleDef`` structures are statically
allocated.
This function may return either a new reference or a borrowed one;
this reference must not be released.
.. versionadded:: 3.5
.. _single-phase-initialization:
Legacy single-phase initialization
..................................
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. attention::
Single-phase initialization is a legacy mechanism to initialize extension
.. deprecated:: 3.15
Single-phase initialization is :term:`soft deprecated`.
It is a legacy mechanism to initialize extension
modules, with known drawbacks and design flaws. Extension module authors
are encouraged to use multi-phase initialization instead.
In single-phase initialization, the
:ref:`initialization function <extension-export-hook>` (``PyInit_modulename``)
However, there are no plans to remove support for it.
In single-phase initialization, the old-style
:ref:`initialization function <extension-pyinit>` (``PyInit_modulename``)
should create, populate and return a module object.
This is typically done using :c:func:`PyModule_Create` and functions like
:c:func:`PyModule_AddObjectRef`.
@ -242,6 +335,8 @@ in the following ways:
* Single-phase modules support module lookup functions like
:c:func:`PyState_FindModule`.
* The module's :c:member:`PyModuleDef.m_slots` must be NULL.
.. [#testsinglephase] ``_testsinglephase`` is an internal module used
in CPython's self-test suite; your installation may or may not
include it.

View file

@ -93,6 +93,29 @@ the :mod:`io` APIs instead.
.. versionadded:: 3.8
.. c:function:: PyObject *PyFile_OpenCodeObject(PyObject *path)
Open *path* with the mode ``'rb'``. *path* must be a Python :class:`str`
object. The behavior of this function may be overridden by
:c:func:`PyFile_SetOpenCodeHook` to allow for some preprocessing of the
text.
This is analogous to :func:`io.open_code` in Python.
On success, this function returns a :term:`strong reference` to a Python
file object. On failure, this function returns ``NULL`` with an exception
set.
.. versionadded:: 3.8
.. c:function:: PyObject *PyFile_OpenCode(const char *path)
Similar to :c:func:`PyFile_OpenCodeObject`, but *path* is a
UTF-8 encoded :c:expr:`const char*`.
.. versionadded:: 3.8
.. c:function:: int PyFile_WriteObject(PyObject *obj, PyObject *p, int flags)

View file

@ -78,6 +78,111 @@ Floating-Point Objects
Return the minimum normalized positive float *DBL_MIN* as C :c:expr:`double`.
.. c:macro:: Py_INFINITY
This macro expands a to constant expression of type :c:expr:`double`, that
represents the positive infinity.
It is equivalent to the :c:macro:`!INFINITY` macro from the C11 standard
``<math.h>`` header.
.. deprecated:: 3.15
The macro is :term:`soft deprecated`.
.. c:macro:: Py_NAN
This macro expands a to constant expression of type :c:expr:`double`, that
represents a quiet not-a-number (qNaN) value.
On most platforms, this is equivalent to the :c:macro:`!NAN` macro from
the C11 standard ``<math.h>`` header.
.. c:macro:: Py_HUGE_VAL
Equivalent to :c:macro:`!INFINITY`.
.. deprecated:: 3.14
The macro is :term:`soft deprecated`.
.. c:macro:: Py_MATH_E
The definition (accurate for a :c:expr:`double` type) of the :data:`math.e` constant.
.. c:macro:: Py_MATH_El
High precision (long double) definition of :data:`~math.e` constant.
.. deprecated-removed:: 3.15 3.20
.. c:macro:: Py_MATH_PI
The definition (accurate for a :c:expr:`double` type) of the :data:`math.pi` constant.
.. c:macro:: Py_MATH_PIl
High precision (long double) definition of :data:`~math.pi` constant.
.. deprecated-removed:: 3.15 3.20
.. c:macro:: Py_MATH_TAU
The definition (accurate for a :c:expr:`double` type) of the :data:`math.tau` constant.
.. versionadded:: 3.6
.. c:macro:: Py_RETURN_NAN
Return :data:`math.nan` from a function.
On most platforms, this is equivalent to ``return PyFloat_FromDouble(NAN)``.
.. c:macro:: Py_RETURN_INF(sign)
Return :data:`math.inf` or :data:`-math.inf <math.inf>` from a function,
depending on the sign of *sign*.
On most platforms, this is equivalent to the following::
return PyFloat_FromDouble(copysign(INFINITY, sign));
.. c:macro:: Py_IS_FINITE(X)
Return ``1`` if the given floating-point number *X* is finite,
that is, it is normal, subnormal or zero, but not infinite or NaN.
Return ``0`` otherwise.
.. deprecated:: 3.14
The macro is :term:`soft deprecated`. Use :c:macro:`!isfinite` instead.
.. c:macro:: Py_IS_INFINITY(X)
Return ``1`` if the given floating-point number *X* is positive or negative
infinity. Return ``0`` otherwise.
.. deprecated:: 3.14
The macro is :term:`soft deprecated`. Use :c:macro:`!isinf` instead.
.. c:macro:: Py_IS_NAN(X)
Return ``1`` if the given floating-point number *X* is a not-a-number (NaN)
value. Return ``0`` otherwise.
.. deprecated:: 3.14
The macro is :term:`soft deprecated`. Use :c:macro:`!isnan` instead.
Pack and Unpack functions
-------------------------
@ -96,8 +201,8 @@ NaNs (if such things exist on the platform) isn't handled correctly, and
attempting to unpack a bytes string containing an IEEE INF or NaN will raise an
exception.
Note that NaNs type may not be preserved on IEEE platforms (silent NaN become
quiet), for example on x86 systems in 32-bit mode.
Note that NaNs type may not be preserved on IEEE platforms (signaling NaN become
quiet NaN), for example on x86 systems in 32-bit mode.
On non-IEEE platforms with more precision, or larger dynamic range, than IEEE
754 supports, not all values can be packed; on non-IEEE platforms with less

View file

@ -29,6 +29,12 @@ See also :ref:`Reflection <reflection>`.
Previously, this type was only available after including
``<frameobject.h>``.
.. c:function:: PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyObject *locals)
Create a new frame object. This function returns a :term:`strong reference`
to the new frame object on success, and returns ``NULL`` with an exception
set on failure.
.. c:function:: int PyFrame_Check(PyObject *obj)
Return non-zero if *obj* is a frame object.
@ -161,6 +167,57 @@ See :pep:`667` for more information.
Return non-zero if *obj* is a frame :func:`locals` proxy.
Legacy Local Variable APIs
^^^^^^^^^^^^^^^^^^^^^^^^^^
These APIs are :term:`soft deprecated`. As of Python 3.13, they do nothing.
They exist solely for backwards compatibility.
.. c:function:: void PyFrame_LocalsToFast(PyFrameObject *f, int clear)
This function is :term:`soft deprecated` and does nothing.
Prior to Python 3.13, this function would copy the :attr:`~frame.f_locals`
attribute of *f* to the internal "fast" array of local variables, allowing
changes in frame objects to be visible to the interpreter. If *clear* was
true, this function would process variables that were unset in the locals
dictionary.
.. versionchanged:: 3.13
This function now does nothing.
.. c:function:: void PyFrame_FastToLocals(PyFrameObject *f)
This function is :term:`soft deprecated` and does nothing.
Prior to Python 3.13, this function would copy the internal "fast" array
of local variables (which is used by the interpreter) to the
:attr:`~frame.f_locals` attribute of *f*, allowing changes in local
variables to be visible to frame objects.
.. versionchanged:: 3.13
This function now does nothing.
.. c:function:: int PyFrame_FastToLocalsWithError(PyFrameObject *f)
This function is :term:`soft deprecated` and does nothing.
Prior to Python 3.13, this function was similar to
:c:func:`PyFrame_FastToLocals`, but would return ``0`` on success, and
``-1`` with an exception set on failure.
.. versionchanged:: 3.13
This function now does nothing.
.. seealso::
:pep:`667`
Internal Frames
^^^^^^^^^^^^^^^

View file

@ -102,6 +102,15 @@ There are a few functions specific to Python functions.
dictionary of arguments or ``NULL``.
.. c:function:: int PyFunction_SetKwDefaults(PyObject *op, PyObject *defaults)
Set the keyword-only argument default values of the function object *op*.
*defaults* must be a dictionary of keyword-only arguments or ``Py_None``.
This function returns ``0`` on success, and returns ``-1`` with an exception
set on failure.
.. c:function:: PyObject* PyFunction_GetClosure(PyObject *op)
Return the closure associated with the function object *op*. This can be ``NULL``
@ -175,6 +184,9 @@ There are a few functions specific to Python functions.
.. versionadded:: 3.12
- ``PyFunction_PYFUNC_EVENT_MODIFY_QUALNAME``
.. versionadded:: 3.15
.. c:type:: int (*PyFunction_WatchCallback)(PyFunction_WatchEvent event, PyFunctionObject *func, PyObject *new_value)
@ -197,7 +209,7 @@ There are a few functions specific to Python functions.
runtime behavior depending on optimization decisions, it does not change
the semantics of the Python code being executed.
If *event* is ``PyFunction_EVENT_DESTROY``, Taking a reference in the
If *event* is ``PyFunction_EVENT_DESTROY``, taking a reference in the
callback to the about-to-be-destroyed function will resurrect it, preventing
it from being freed at this time. When the resurrected object is destroyed
later, any watcher callbacks active at that time will be called again.

View file

@ -232,6 +232,10 @@ The :c:member:`~PyTypeObject.tp_traverse` handler must have the following type:
object argument. If *visit* returns a non-zero value that value should be
returned immediately.
The traversal function must not have any side effects. Implementations
may not modify the reference counts of any Python objects nor create or
destroy any Python objects.
To simplify writing :c:member:`~PyTypeObject.tp_traverse` handlers, a :c:func:`Py_VISIT` macro is
provided. In order to use this macro, the :c:member:`~PyTypeObject.tp_traverse` implementation
must name its arguments exactly *visit* and *arg*:

View file

@ -44,3 +44,41 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`.
with ``__name__`` and ``__qualname__`` set to *name* and *qualname*.
A reference to *frame* is stolen by this function. The *frame* argument
must not be ``NULL``.
.. c:function:: PyCodeObject* PyGen_GetCode(PyGenObject *gen)
Return a new :term:`strong reference` to the code object wrapped by *gen*.
This function always succeeds.
Asynchronous Generator Objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. seealso::
:pep:`525`
.. c:var:: PyTypeObject PyAsyncGen_Type
The type object corresponding to asynchronous generator objects. This is
available as :class:`types.AsyncGeneratorType` in the Python layer.
.. versionadded:: 3.6
.. c:function:: PyObject *PyAsyncGen_New(PyFrameObject *frame, PyObject *name, PyObject *qualname)
Create a new asynchronous generator wrapping *frame*, with ``__name__`` and
``__qualname__`` set to *name* and *qualname*. *frame* is stolen by this
function and must not be ``NULL``.
On success, this function returns a :term:`strong reference` to the
new asynchronous generator. On failure, this function returns ``NULL``
with an exception set.
.. versionadded:: 3.6
.. c:function:: int PyAsyncGen_CheckExact(PyObject *op)
Return true if *op* is an asynchronous generator object, false otherwise.
This function always succeeds.
.. versionadded:: 3.6

View file

@ -11,42 +11,98 @@ See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`.
.. versionadded:: 3.2
.. c:type:: Py_uhash_t
Hash value type: unsigned integer.
.. versionadded:: 3.2
.. c:macro:: Py_HASH_ALGORITHM
A numerical value indicating the algorithm for hashing of :class:`str`,
:class:`bytes`, and :class:`memoryview`.
The algorithm name is exposed by :data:`sys.hash_info.algorithm`.
.. versionadded:: 3.4
.. c:macro:: Py_HASH_FNV
Py_HASH_SIPHASH24
Py_HASH_SIPHASH13
Numerical values to compare to :c:macro:`Py_HASH_ALGORITHM` to determine
which algorithm is used for hashing. The hash algorithm can be configured
via the configure :option:`--with-hash-algorithm` option.
.. versionadded:: 3.4
Add :c:macro:`!Py_HASH_FNV` and :c:macro:`!Py_HASH_SIPHASH24`.
.. versionadded:: 3.11
Add :c:macro:`!Py_HASH_SIPHASH13`.
.. c:macro:: Py_HASH_CUTOFF
Buffers of length in range ``[1, Py_HASH_CUTOFF)`` are hashed using DJBX33A
instead of the algorithm described by :c:macro:`Py_HASH_ALGORITHM`.
- A :c:macro:`!Py_HASH_CUTOFF` of 0 disables the optimization.
- :c:macro:`!Py_HASH_CUTOFF` must be non-negative and less or equal than 7.
32-bit platforms should use a cutoff smaller than 64-bit platforms because
it is easier to create colliding strings. A cutoff of 7 on 64-bit platforms
and 5 on 32-bit platforms should provide a decent safety margin.
This corresponds to the :data:`sys.hash_info.cutoff` constant.
.. versionadded:: 3.4
.. c:macro:: PyHASH_MODULUS
The `Mersenne prime <https://en.wikipedia.org/wiki/Mersenne_prime>`_ ``P = 2**n -1``, used for numeric hash scheme.
The `Mersenne prime <https://en.wikipedia.org/wiki/Mersenne_prime>`_ ``P = 2**n -1``,
used for numeric hash scheme.
This corresponds to the :data:`sys.hash_info.modulus` constant.
.. versionadded:: 3.13
.. c:macro:: PyHASH_BITS
The exponent ``n`` of ``P`` in :c:macro:`PyHASH_MODULUS`.
.. versionadded:: 3.13
.. c:macro:: PyHASH_MULTIPLIER
Prime multiplier used in string and various other hashes.
.. versionadded:: 3.13
.. c:macro:: PyHASH_INF
The hash value returned for a positive infinity.
This corresponds to the :data:`sys.hash_info.inf` constant.
.. versionadded:: 3.13
.. c:macro:: PyHASH_IMAG
The multiplier used for the imaginary part of a complex number.
This corresponds to the :data:`sys.hash_info.imag` constant.
.. versionadded:: 3.13
.. c:type:: PyHash_FuncDef
Hash function definition used by :c:func:`PyHash_GetFuncDef`.
@ -59,14 +115,20 @@ See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`.
Hash function name (UTF-8 encoded string).
This corresponds to the :data:`sys.hash_info.algorithm` constant.
.. c:member:: const int hash_bits
Internal size of the hash value in bits.
This corresponds to the :data:`sys.hash_info.hash_bits` constant.
.. c:member:: const int seed_bits
Size of seed input in bits.
This corresponds to the :data:`sys.hash_info.seed_bits` constant.
.. versionadded:: 3.4

View file

@ -129,8 +129,7 @@ Importing Modules
of :class:`~importlib.machinery.SourceFileLoader` otherwise.
The module's :attr:`~module.__file__` attribute will be set to the code
object's :attr:`~codeobject.co_filename`. If applicable,
:attr:`~module.__cached__` will also be set.
object's :attr:`~codeobject.co_filename`.
This function will reload the module if it was already imported. See
:c:func:`PyImport_ReloadModule` for the intended way to reload a module.
@ -142,10 +141,13 @@ Importing Modules
:c:func:`PyImport_ExecCodeModuleWithPathnames`.
.. versionchanged:: 3.12
The setting of :attr:`~module.__cached__` and :attr:`~module.__loader__`
The setting of ``__cached__`` and :attr:`~module.__loader__`
is deprecated. See :class:`~importlib.machinery.ModuleSpec` for
alternatives.
.. versionchanged:: 3.15
``__cached__`` is no longer set.
.. c:function:: PyObject* PyImport_ExecCodeModuleEx(const char *name, PyObject *co, const char *pathname)
@ -157,16 +159,19 @@ Importing Modules
.. c:function:: PyObject* PyImport_ExecCodeModuleObject(PyObject *name, PyObject *co, PyObject *pathname, PyObject *cpathname)
Like :c:func:`PyImport_ExecCodeModuleEx`, but the :attr:`~module.__cached__`
attribute of the module object is set to *cpathname* if it is
non-``NULL``. Of the three functions, this is the preferred one to use.
Like :c:func:`PyImport_ExecCodeModuleEx`, but the path to any compiled file
via *cpathname* is used appropriately when non-``NULL``. Of the three
functions, this is the preferred one to use.
.. versionadded:: 3.3
.. versionchanged:: 3.12
Setting :attr:`~module.__cached__` is deprecated. See
Setting ``__cached__`` is deprecated. See
:class:`~importlib.machinery.ModuleSpec` for alternatives.
.. versionchanged:: 3.15
``__cached__`` no longer set.
.. c:function:: PyObject* PyImport_ExecCodeModuleWithPathnames(const char *name, PyObject *co, const char *pathname, const char *cpathname)
@ -314,6 +319,13 @@ Importing Modules
initialization.
.. c:var:: struct _inittab *PyImport_Inittab
The table of built-in modules used by Python initialization. Do not use this directly;
use :c:func:`PyImport_AppendInittab` and :c:func:`PyImport_ExtendInittab`
instead.
.. c:function:: PyObject* PyImport_ImportModuleAttr(PyObject *mod_name, PyObject *attr_name)
Import the module *mod_name* and get its attribute *attr_name*.
@ -333,3 +345,24 @@ Importing Modules
strings instead of Python :class:`str` objects.
.. versionadded:: 3.14
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))
This function is a building block that enables embedders to implement
the :py:meth:`~importlib.abc.Loader.create_module` step of custom
static extension importers (e.g. of statically-linked extensions).
*spec* must be a :class:`~importlib.machinery.ModuleSpec` object.
*initfunc* must be an :ref:`initialization function <extension-export-hook>`,
the same as for :c:func:`PyImport_AppendInittab`.
On success, create and return a module object.
This module will not be initialized; call :c:func:`PyModule_Exec`
to initialize it.
(Custom importers should do this in their
:py:meth:`~importlib.abc.Loader.exec_module` method.)
On error, return NULL with an exception set.
.. versionadded:: 3.15

View file

@ -41,7 +41,6 @@ The following functions can be safely called before Python is initialized:
* :c:func:`PyObject_SetArenaAllocator`
* :c:func:`Py_SetProgramName`
* :c:func:`Py_SetPythonHome`
* :c:func:`PySys_ResetWarnOptions`
* the configuration functions covered in :ref:`init-config`
* Informative functions:
@ -1020,6 +1019,12 @@ code, or when embedding the Python interpreter:
interpreter lock is also shared by all threads, regardless of to which
interpreter they belong.
.. versionchanged:: 3.12
:pep:`684` introduced the possibility
of a :ref:`per-interpreter GIL <per-interpreter-gil>`.
See :c:func:`Py_NewInterpreterFromConfig`.
.. c:type:: PyThreadState
@ -1108,7 +1113,7 @@ code, or when embedding the Python interpreter:
This function is safe to call without an :term:`attached thread state`; it
will simply return ``NULL`` indicating that there was no prior thread state.
.. seealso:
.. seealso::
:c:func:`PyEval_ReleaseThread`
.. note::
@ -1119,6 +1124,19 @@ code, or when embedding the Python interpreter:
The following functions use thread-local storage, and are not compatible
with sub-interpreters:
.. c:type:: PyGILState_STATE
The type of the value returned by :c:func:`PyGILState_Ensure` and passed to
:c:func:`PyGILState_Release`.
.. c:enumerator:: PyGILState_LOCKED
The GIL was already held when :c:func:`PyGILState_Ensure` was called.
.. c:enumerator:: PyGILState_UNLOCKED
The GIL was not held when :c:func:`PyGILState_Ensure` was called.
.. c:function:: PyGILState_STATE PyGILState_Ensure()
Ensure that the current thread is ready to call the Python C API regardless
@ -1169,12 +1187,12 @@ with sub-interpreters:
made on the main thread. This is mainly a helper/diagnostic function.
.. note::
This function does not account for :term:`thread states <thread state>` created
by something other than :c:func:`PyGILState_Ensure` (such as :c:func:`PyThreadState_New`).
This function may return non-``NULL`` even when the :term:`thread state`
is detached.
Prefer :c:func:`PyThreadState_Get` or :c:func:`PyThreadState_GetUnchecked`
for most cases.
.. seealso: :c:func:`PyThreadState_Get``
.. seealso:: :c:func:`PyThreadState_Get`
.. c:function:: int PyGILState_Check()
@ -1273,11 +1291,11 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
must be :term:`attached <attached thread state>`
.. versionchanged:: 3.9
This function now calls the :c:member:`PyThreadState.on_delete` callback.
This function now calls the :c:member:`!PyThreadState.on_delete` callback.
Previously, that happened in :c:func:`PyThreadState_Delete`.
.. versionchanged:: 3.13
The :c:member:`PyThreadState.on_delete` callback was removed.
The :c:member:`!PyThreadState.on_delete` callback was removed.
.. c:function:: void PyThreadState_Delete(PyThreadState *tstate)
@ -1348,6 +1366,43 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
.. versionadded:: 3.11
.. c:function:: int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size)
Set the stack protection start address and stack protection size
of a Python thread state.
On success, return ``0``.
On failure, set an exception and return ``-1``.
CPython implements :ref:`recursion control <recursion>` for C code by raising
:py:exc:`RecursionError` when it notices that the machine execution stack is close
to overflow. See for example the :c:func:`Py_EnterRecursiveCall` function.
For this, it needs to know the location of the current thread's stack, which it
normally gets from the operating system.
When the stack is changed, for example using context switching techniques like the
Boost library's ``boost::context``, you must call
:c:func:`~PyUnstable_ThreadState_SetStackProtection` to inform CPython of the change.
Call :c:func:`~PyUnstable_ThreadState_SetStackProtection` either before
or after changing the stack.
Do not call any other Python C API between the call and the stack
change.
See :c:func:`PyUnstable_ThreadState_ResetStackProtection` for undoing this operation.
.. versionadded:: 3.15
.. c:function:: void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)
Reset the stack protection start address and stack protection size
of a Python thread state to the operating system defaults.
See :c:func:`PyUnstable_ThreadState_SetStackProtection` for an explanation.
.. versionadded:: 3.15
.. c:function:: PyInterpreterState* PyInterpreterState_Get(void)
Get the current interpreter.
@ -1377,6 +1432,9 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
This is not a replacement for :c:func:`PyModule_GetState()`, which
extensions should use to store interpreter-specific state information.
The returned dictionary is borrowed from the interpreter and is valid until
interpreter shutdown.
.. versionadded:: 3.8
@ -1659,7 +1717,8 @@ function. You can create and destroy them using the following functions:
Only C-level static and global variables are shared between these
module objects.
* For modules using single-phase initialization,
* For modules using legacy
:ref:`single-phase initialization <single-phase-initialization>`,
e.g. :c:func:`PyModule_Create`, the first time a particular extension
is imported, it is initialized normally, and a (shallow) copy of its
module's dictionary is squirreled away.
@ -1711,6 +1770,8 @@ function. You can create and destroy them using the following functions:
haven't been explicitly destroyed at that point.
.. _per-interpreter-gil:
A Per-Interpreter GIL
---------------------
@ -1722,7 +1783,7 @@ being blocked by other interpreters or blocking any others. Thus a
single Python process can truly take advantage of multiple CPU cores
when running Python code. The isolation also encourages a different
approach to concurrency than that of just using threads.
(See :pep:`554`.)
(See :pep:`554` and :pep:`684`.)
Using an isolated interpreter requires vigilance in preserving that
isolation. That especially means not sharing any objects or mutable
@ -1827,6 +1888,29 @@ pointer and a void pointer argument.
called from the main interpreter. Each subinterpreter now has its own
list of scheduled calls.
.. versionchanged:: 3.12
This function now always schedules *func* to be run in the main
interpreter.
.. c:function:: int Py_MakePendingCalls(void)
Execute all pending calls. This is usually executed automatically by the
interpreter.
This function returns ``0`` on success, and returns ``-1`` with an exception
set on failure.
If this is not called in the main thread of the main
interpreter, this function does nothing and returns ``0``.
The caller must hold an :term:`attached thread state`.
.. versionadded:: 3.1
.. versionchanged:: 3.12
This function only runs pending calls in the main interpreter.
.. _profiling:
Profiling and Tracing
@ -2003,6 +2087,11 @@ Reference tracing
is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer
that was provided when :c:func:`PyRefTracer_SetTracer` was called.
If a new tracing function is registered replacing the current a call to the
trace function will be made with the object set to **NULL** and **event** set to
:c:data:`PyRefTracer_TRACKER_REMOVED`. This will happen just before the new
function is registered.
.. versionadded:: 3.13
.. c:var:: int PyRefTracer_CREATE
@ -2015,6 +2104,13 @@ Reference tracing
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
object has been destroyed.
.. c:var:: int PyRefTracer_TRACKER_REMOVED
The value for the *event* parameter to :c:type:`PyRefTracer` functions when the
current tracer is about to be replaced by a new one.
.. versionadded:: 3.14
.. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
Register a reference tracer function. The function will be called when a new
@ -2030,6 +2126,10 @@ Reference tracing
There must be an :term:`attached thread state` when calling this function.
If another tracer function was already registered, the old function will be
called with **event** set to :c:data:`PyRefTracer_TRACKER_REMOVED` just before
the new function is registered.
.. versionadded:: 3.13
.. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data)
@ -2097,7 +2197,7 @@ use a thread key and functions to associate a :c:expr:`void*` value per
thread.
A :term:`thread state` does *not* need to be :term:`attached <attached thread state>`
when calling these functions; they suppl their own locking.
when calling these functions; they supply their own locking.
Note that :file:`Python.h` does not include the declaration of the TLS APIs,
you need to include :file:`pythread.h` to use thread-local storage.
@ -2440,3 +2540,220 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.
In the default build, this macro expands to ``}``.
.. versionadded:: 3.13
Legacy Locking APIs
-------------------
These APIs are obsolete since Python 3.13 with the introduction of
:c:type:`PyMutex`.
.. versionchanged:: 3.15
These APIs are now a simple wrapper around ``PyMutex``.
.. c:type:: PyThread_type_lock
A pointer to a mutual exclusion lock.
.. c:type:: PyLockStatus
The result of acquiring a lock with a timeout.
.. c:namespace:: NULL
.. c:enumerator:: PY_LOCK_FAILURE
Failed to acquire the lock.
.. c:enumerator:: PY_LOCK_ACQUIRED
The lock was successfully acquired.
.. c:enumerator:: PY_LOCK_INTR
The lock was interrupted by a signal.
.. c:function:: PyThread_type_lock PyThread_allocate_lock(void)
Allocate a new lock.
On success, this function returns a lock; on failure, this
function returns ``0`` without an exception set.
The caller does not need to hold an :term:`attached thread state`.
.. versionchanged:: 3.15
This function now always uses :c:type:`PyMutex`. In prior versions, this
would use a lock provided by the operating system.
.. c:function:: void PyThread_free_lock(PyThread_type_lock lock)
Destroy *lock*. The lock should not be held by any thread when calling
this.
The caller does not need to hold an :term:`attached thread state`.
.. c:function:: PyLockStatus PyThread_acquire_lock_timed(PyThread_type_lock lock, long long microseconds, int intr_flag)
Acquire *lock* with a timeout.
This will wait for *microseconds* microseconds to acquire the lock. If the
timeout expires, this function returns :c:enumerator:`PY_LOCK_FAILURE`.
If *microseconds* is ``-1``, this will wait indefinitely until the lock has
been released.
If *intr_flag* is ``1``, acquiring the lock may be interrupted by a signal,
in which case this function returns :c:enumerator:`PY_LOCK_INTR`. Upon
interruption, it's generally expected that the caller makes a call to
:c:func:`Py_MakePendingCalls` to propagate an exception to Python code.
If the lock is successfully acquired, this function returns
:c:enumerator:`PY_LOCK_ACQUIRED`.
The caller does not need to hold an :term:`attached thread state`.
.. c:function:: int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
Acquire *lock*.
If *waitflag* is ``1`` and another thread currently holds the lock, this
function will wait until the lock can be acquired and will always return
``1``.
If *waitflag* is ``0`` and another thread holds the lock, this function will
not wait and instead return ``0``. If the lock is not held by any other
thread, then this function will acquire it and return ``1``.
Unlike :c:func:`PyThread_acquire_lock_timed`, acquiring the lock cannot be
interrupted by a signal.
The caller does not need to hold an :term:`attached thread state`.
.. c:function:: int PyThread_release_lock(PyThread_type_lock lock)
Release *lock*. If *lock* is not held, then this function issues a
fatal error.
The caller does not need to hold an :term:`attached thread state`.
Operating System Thread APIs
============================
.. c:macro:: PYTHREAD_INVALID_THREAD_ID
Sentinel value for an invalid thread ID.
This is currently equivalent to ``(unsigned long)-1``.
.. c:function:: unsigned long PyThread_start_new_thread(void (*func)(void *), void *arg)
Start function *func* in a new thread with argument *arg*.
The resulting thread is not intended to be joined.
*func* must not be ``NULL``, but *arg* may be ``NULL``.
On success, this function returns the identifier of the new thread; on failure,
this returns :c:macro:`PYTHREAD_INVALID_THREAD_ID`.
The caller does not need to hold an :term:`attached thread state`.
.. c:function:: unsigned long PyThread_get_thread_ident(void)
Return the identifier of the current thread, which will never be zero.
This function cannot fail, and the caller does not need to hold an
:term:`attached thread state`.
.. seealso::
:py:func:`threading.get_ident`
.. c:function:: PyObject *PyThread_GetInfo(void)
Get general information about the current thread in the form of a
:ref:`struct sequence <struct-sequence-objects>` object. This information is
accessible as :py:attr:`sys.thread_info` in Python.
On success, this returns a new :term:`strong reference` to the thread
information; on failure, this returns ``NULL`` with an exception set.
The caller must hold an :term:`attached thread state`.
.. c:macro:: PY_HAVE_THREAD_NATIVE_ID
This macro is defined when the system supports native thread IDs.
.. c:function:: unsigned long PyThread_get_thread_native_id(void)
Get the native identifier of the current thread as it was assigned by the operating
system's kernel, which will never be less than zero.
This function is only available when :c:macro:`PY_HAVE_THREAD_NATIVE_ID` is
defined.
This function cannot fail, and the caller does not need to hold an
:term:`attached thread state`.
.. seealso::
:py:func:`threading.get_native_id`
.. c:function:: void PyThread_exit_thread(void)
Terminate the current thread. This function is generally considered unsafe
and should be avoided. It is kept solely for backwards compatibility.
This function is only safe to call if all functions in the full call
stack are written to safely allow it.
.. warning::
If the current system uses POSIX threads (also known as "pthreads"),
this calls :manpage:`pthread_exit(3)`, which attempts to unwind the stack
and call C++ destructors on some libc implementations. However, if a
``noexcept`` function is reached, it may terminate the process.
Other systems, such as macOS, do unwinding.
On Windows, this function calls ``_endthreadex()``, which kills the thread
without calling C++ destructors.
In any case, there is a risk of corruption on the thread's stack.
.. deprecated:: 3.14
.. c:function:: void PyThread_init_thread(void)
Initialize ``PyThread*`` APIs. Python executes this function automatically,
so there's little need to call it from an extension module.
.. c:function:: int PyThread_set_stacksize(size_t size)
Set the stack size of the current thread to *size* bytes.
This function returns ``0`` on success, ``-1`` if *size* is invalid, or
``-2`` if the system does not support changing the stack size. This function
does not set exceptions.
The caller does not need to hold an :term:`attached thread state`.
.. c:function:: size_t PyThread_get_stacksize(void)
Return the stack size of the current thread in bytes, or ``0`` if the system's
default stack size is in use.
The caller does not need to hold an :term:`attached thread state`.

View file

@ -102,7 +102,7 @@ Error Handling
* Set *\*err_msg* and return ``1`` if an error is set.
* Set *\*err_msg* to ``NULL`` and return ``0`` otherwise.
An error message is an UTF-8 encoded string.
An error message is a UTF-8 encoded string.
If *config* has an exit code, format the exit code as an error
message.
@ -1278,6 +1278,11 @@ PyConfig
Default: ``0``.
.. deprecated-removed:: 3.15 3.17
The :option:`-b` and :option:`!-bb` options will become no-op in 3.17.
:c:member:`~PyConfig.bytes_warning` member will be removed in 3.17.
.. c:member:: int warn_default_encoding
If non-zero, emit a :exc:`EncodingWarning` warning when :class:`io.TextIOWrapper`

Some files were not shown because too many files have changed in this diff Show more