mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
Merge branch 'main' into issue-136438-interpreters
This commit is contained in:
commit
9735b12d87
1996 changed files with 190992 additions and 69385 deletions
73
.devcontainer/wasi/devcontainer.json
Normal file
73
.devcontainer/wasi/devcontainer.json
Normal 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
4
.gitattributes
vendored
|
|
@ -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
66
.github/CODEOWNERS
vendored
|
|
@ -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
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
20
.github/CONTRIBUTING.rst
vendored
20
.github/CONTRIBUTING.rst
vendored
|
|
@ -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
|
||||
|
|
|
|||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
3
.github/actionlint.yaml
vendored
3
.github/actionlint.yaml
vendored
|
|
@ -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
|
||||
|
||||
|
|
|
|||
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
168
.github/workflows/build.yml
vendored
168
.github/workflows/build.yml
vendored
|
|
@ -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) }}
|
||||
|
|
|
|||
118
.github/workflows/jit.yml
vendored
118
.github/workflows/jit.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
3
.github/workflows/mypy.yml
vendored
3
.github/workflows/mypy.yml
vendored
|
|
@ -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",
|
||||
|
|
|
|||
31
.github/workflows/project-updater.yml
vendored
31
.github/workflows/project-updater.yml
vendored
|
|
@ -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 }}
|
||||
53
.github/workflows/reusable-context.yml
vendored
53
.github/workflows/reusable-context.yml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
18
.github/workflows/reusable-macos.yml
vendored
18
.github/workflows/reusable-macos.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
13
.github/workflows/reusable-san.yml
vendored
13
.github/workflows/reusable-san.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
15
.github/workflows/reusable-ubuntu.yml
vendored
15
.github/workflows/reusable-ubuntu.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
39
.github/workflows/reusable-wasi.yml
vendored
39
.github/workflows/reusable-wasi.yml
vendored
|
|
@ -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"
|
||||
|
|
|
|||
2
.github/workflows/reusable-windows-msi.yml
vendored
2
.github/workflows/reusable-windows-msi.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
2
.github/workflows/reusable-windows.yml
vendored
2
.github/workflows/reusable-windows.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
15
.github/workflows/tail-call.yml
vendored
15
.github/workflows/tail-call.yml
vendored
|
|
@ -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
20
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class PythonSuite {
|
|||
val status = PythonTestRunner(
|
||||
InstrumentationRegistry.getInstrumentation().targetContext
|
||||
).run(
|
||||
InstrumentationRegistry.getArguments()
|
||||
InstrumentationRegistry.getArguments().getString("pythonArgs")!!,
|
||||
)
|
||||
assertEquals(0, status)
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
22
Apple/.ruff.toml
Normal 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
1065
Apple/__main__.py
Normal file
File diff suppressed because it is too large
Load diff
339
Apple/iOS/README.md
Normal file
339
Apple/iOS/README.md
Normal 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.
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-ar
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-ar
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphoneos${IOS_SDK_VERSION} ar "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-clang
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-clang
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-clang++
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-clang++
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-cpp
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-cpp
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-ar
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-simulator-strip
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch arm64 "$@"
|
||||
2
Apple/iOS/Resources/bin/arm64-apple-ios-strip
Executable file
2
Apple/iOS/Resources/bin/arm64-apple-ios-strip
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphoneos${IOS_SDK_VERSION} strip -arch arm64 "$@"
|
||||
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar
Executable file
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-ar
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} ar "$@"
|
||||
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
Executable file
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
|
||||
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
Executable file
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
|
||||
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
Executable file
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
|
||||
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip
Executable file
2
Apple/iOS/Resources/bin/x86_64-apple-ios-simulator-strip
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
xcrun --sdk iphonesimulator${IOS_SDK_VERSION} strip -arch x86_64 "$@"
|
||||
|
|
@ -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>
|
||||
151
Apple/testbed/Python.xcframework/build/utils.sh
Executable file
151
Apple/testbed/Python.xcframework/build/utils.sh
Executable 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
|
||||
}
|
||||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
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
3804
Doc/_static/tachyon-example-heatmap.html
generated
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
`translation’s repository <TRANSLATION_REPO_>`_ instead.
|
||||
|
||||
You can also open a discussion item on our
|
||||
`Documentation Discourse forum <https://discuss.python.org/c/documentation/26>`_.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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::
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
138
Doc/c-api/curses.rst
Normal 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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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*:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue