diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 5a4c155..cfd9f5c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -90,16 +90,6 @@ jobs:
fail-fast: false
matrix:
target: ['macOS', 'iOS', 'tvOS', 'watchOS']
- include:
- - briefcase-run-args:
- - run-tests: false
-
- - target: macOS
- run-tests: true
-
- - target: iOS
- briefcase-run-args: ' -d "iPhone SE (3rd generation)"'
- run-tests: true
steps:
- uses: actions/checkout@v4.1.7
@@ -125,20 +115,98 @@ jobs:
name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ briefcase-testbed:
+ name: Briefcase testbed (${{ matrix.target }})
+ runs-on: macOS-latest
+ needs: [ config, build ]
+ strategy:
+ fail-fast: false
+ matrix:
+ target: ["macOS", "iOS"]
+ include:
+ - briefcase-run-args:
+
+ - target: iOS
+ briefcase-run-args: ' -d "iPhone SE (3rd generation)"'
+
+ steps:
+ - uses: actions/checkout@v4.1.7
+
+ - name: Get build artifact
+ uses: actions/download-artifact@v4.3.0
+ with:
+ pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ path: dist
+ merge-multiple: true
+
+ - name: Set up Python
+ uses: actions/setup-python@v5.6.0
+ with:
+ # Appending -dev ensures that we can always build the dev release.
+ # It's a no-op for versions that have been published.
+ python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev
+ # Ensure that we *always* use the latest build, not a cached version.
+ # It's an edge case, but when a new alpha is released, we need to use it ASAP.
+ check-latest: true
+
- uses: actions/checkout@v4.1.7
- if: matrix.run-tests
with:
repository: beeware/Python-support-testbed
path: Python-support-testbed
- name: Install dependencies
- if: matrix.run-tests
run: |
# Use the development version of Briefcase
python -m pip install git+https://github.com/beeware/briefcase.git
- name: Run support testbed check
- if: matrix.run-tests
timeout-minutes: 10
working-directory: Python-support-testbed
run: briefcase run ${{ matrix.target }} Xcode --test ${{ matrix.briefcase-run-args }} -C support_package=\'../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz\'
+
+ cpython-testbed:
+ name: CPython testbed (${{ matrix.target }})
+ runs-on: macOS-latest
+ needs: [ config, build ]
+ strategy:
+ fail-fast: false
+ matrix:
+ target: ["iOS"]
+
+ steps:
+ - uses: actions/checkout@v4.1.7
+
+ - name: Get build artifact
+ uses: actions/download-artifact@v4.3.0
+ with:
+ pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+ path: dist
+ merge-multiple: true
+
+ - name: Set up Python
+ uses: actions/setup-python@v5.6.0
+ with:
+ # Appending -dev ensures that we can always build the dev release.
+ # It's a no-op for versions that have been published.
+ python-version: ${{ needs.config.outputs.PYTHON_VER }}-dev
+ # Ensure that we *always* use the latest build, not a cached version.
+ # It's an edge case, but when a new alpha is released, we need to use it ASAP.
+ check-latest: true
+
+ - name: Unpack support package
+ run: |
+ mkdir support
+ cd support
+ tar zxvf ../dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.target }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz
+
+ - name: Run CPython testbed
+ timeout-minutes: 10
+ working-directory: support
+ run: |
+ # Run a representative subset of CPython core tests:
+ # - test_builtin as a test of core language tools
+ # - test_grammar as a test of core language features
+ # - test_os as a test of system library calls
+ # - test_bz2 as a simple test of third party libraries
+ # - test_ctypes as a test of FFI
+ python -m testbed run -- test --single-process --rerun -W test_builtin test_grammar test_os test_bz2 test_ctypes
diff --git a/Makefile b/Makefile
index f625e7f..c59cfd1 100644
--- a/Makefile
+++ b/Makefile
@@ -19,7 +19,7 @@ BUILD_NUMBER=custom
# of a release cycle, as official binaries won't be published.
# PYTHON_MICRO_VERSION is the full version number, without any alpha/beta/rc suffix. (e.g., 3.10.0)
# PYTHON_VER is the major/minor version (e.g., 3.10)
-PYTHON_VERSION=3.13.3
+PYTHON_VERSION=3.13.4
PYTHON_PKG_VERSION=$(PYTHON_VERSION)
PYTHON_MICRO_VERSION=$(shell echo $(PYTHON_VERSION) | grep -Eo "\d+\.\d+\.\d+")
PYTHON_PKG_MICRO_VERSION=$(shell echo $(PYTHON_PKG_VERSION) | grep -Eo "\d+\.\d+\.\d+")
diff --git a/patch/Python/Python.patch b/patch/Python/Python.patch
index 39c3cfc..d1aebfd 100644
--- a/patch/Python/Python.patch
+++ b/patch/Python/Python.patch
@@ -132,7 +132,7 @@ index 510c7b9568a..810b08879f6 100644
import _osx_support
osname, release, machine = _osx_support.get_platform_osx(
diff --git a/Misc/platform_triplet.c b/Misc/platform_triplet.c
-index ec0857a4a99..2350e9dc821 100644
+index f5cd73bdea8..50f2f8988b7 100644
--- a/Misc/platform_triplet.c
+++ b/Misc/platform_triplet.c
@@ -257,6 +257,26 @@
@@ -1269,150 +1269,19 @@ index c3e261ecd9e..26ef7a95de4 100644
CFBundleSupportedPlatforms
iPhoneOS
-diff --git a/iOS/Resources/bin/arm64-apple-ios-clang b/iOS/Resources/bin/arm64-apple-ios-clang
-index c39519cd1f8..f50d5b5142f 100755
---- a/iOS/Resources/bin/arm64-apple-ios-clang
-+++ b/iOS/Resources/bin/arm64-apple-ios-clang
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios "$@"
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
-diff --git a/iOS/Resources/bin/arm64-apple-ios-clang++ b/iOS/Resources/bin/arm64-apple-ios-clang++
-index d9b12925f38..0794731d7dc 100755
---- a/iOS/Resources/bin/arm64-apple-ios-clang++
-+++ b/iOS/Resources/bin/arm64-apple-ios-clang++
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios "$@"
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} "$@"
-diff --git a/iOS/Resources/bin/arm64-apple-ios-cpp b/iOS/Resources/bin/arm64-apple-ios-cpp
-index 24da23d3448..24fa1506bab 100755
---- a/iOS/Resources/bin/arm64-apple-ios-cpp
-+++ b/iOS/Resources/bin/arm64-apple-ios-cpp
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios -E "$@"
-+xcrun --sdk iphoneos${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET} -E "$@"
-diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang b/iOS/Resources/bin/arm64-apple-ios-simulator-clang
-index 92e8d853d6e..4891a00876e 100755
---- a/iOS/Resources/bin/arm64-apple-ios-simulator-clang
-+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator "$@"
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
-diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-clang++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
-index 076469cc70c..58b2a5f6f18 100755
---- a/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
-+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-clang++
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios-simulator "$@"
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
-diff --git a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
-index c57f28cee5b..c9df94e8b7c 100755
---- a/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
-+++ b/iOS/Resources/bin/arm64-apple-ios-simulator-cpp
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios-simulator -E "$@"
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target arm64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
-diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
-index 17cbe0c8a1e..f4739a7b945 100755
---- a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
-+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator "$@"
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
-diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
-index 565d47b24c2..c348ae4c103 100755
---- a/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
-+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-clang++
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios-simulator "$@"
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang++ -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator "$@"
-diff --git a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
-index 63fc8e8de2d..6d7f8084c9f 100755
---- a/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
-+++ b/iOS/Resources/bin/x86_64-apple-ios-simulator-cpp
-@@ -1,2 +1,2 @@
- #!/bin/sh
--xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios-simulator -E "$@"
-+xcrun --sdk iphonesimulator${IOS_SDK_VERSION} clang -target x86_64-apple-ios${IPHONEOS_DEPLOYMENT_TARGET}-simulator -E "$@"
diff --git a/iOS/testbed/__main__.py b/iOS/testbed/__main__.py
-index b436c9af99d..c05497ede3a 100644
+index c05497ede3a..1146bf3b988 100644
--- a/iOS/testbed/__main__.py
+++ b/iOS/testbed/__main__.py
-@@ -123,6 +123,36 @@
- )
-
-
-+# Select a simulator device to use.
-+async def select_simulator_device():
-+ # List the testing simulators, in JSON format
-+ raw_json = await async_check_output(
-+ "xcrun", "simctl", "--set", "testing", "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"))
-+ or "iPhone SE " in devicetype["name"]
-+ )
-+ )
-+
-+ return se_simulators[-1][1]
-+
-+
- # Return a list of UDIDs associated with booted simulators
- async def list_devices():
- try:
-@@ -371,12 +401,16 @@
- plistlib.dump(info, f)
-
-
--async def run_testbed(simulator: str, args: list[str], verbose: bool=False):
-+async def run_testbed(simulator: str | None, args: list[str], verbose: bool=False):
- location = Path(__file__).parent
- print("Updating plist...", end="", flush=True)
- update_plist(location, args)
- print(" done.", flush=True)
-
-+ if simulator is None:
-+ simulator = await select_simulator_device()
-+ print(f"Running test on {simulator}", flush=True)
-+
- # We need to get an exclusive lock on simulator creation, to avoid issues
- # with multiple simulators starting and being unable to tell which
- # simulator is due to which testbed instance. See
-@@ -453,8 +487,10 @@
- )
- run.add_argument(
- "--simulator",
-- default="iPhone SE (3rd Generation)",
-- help="The name of the simulator to use (default: 'iPhone SE (3rd Generation)')",
-+ help=(
-+ "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to ",
-+ "the most recently released 'entry level' iPhone device."
-+ )
+@@ -127,7 +127,7 @@
+ async def select_simulator_device():
+ # List the testing simulators, in JSON format
+ raw_json = await async_check_output(
+- "xcrun", "simctl", "--set", "testing", "list", "-j"
++ "xcrun", "simctl", "list", "-j"
)
- run.add_argument(
- "-v", "--verbose",
+ json_data = json.loads(raw_json)
+
--- /dev/null
+++ b/tvOS/README.rst
@@ -0,0 +1,108 @@