diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ecbb02f..c35bb42 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - # Check for updates on Sunday, 8PM UTC - interval: "weekly" - day: "sunday" - time: "20:00" + # Check for updates on the first Sunday of every month, 8PM UTC + interval: "cron" + cronjob: "0 20 * * sun#1" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0601da1..a8ae146 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -56,7 +56,7 @@ jobs: XZ_VERSION: ${{ steps.extract.outputs.XZ_VERSION }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Extract config variables id: extract @@ -92,7 +92,7 @@ jobs: platform: ['macOS', 'iOS', 'tvOS', 'watchOS', 'visionOS'] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Xcode # GitHub recommends explicitly selecting the desired Xcode version: @@ -104,7 +104,7 @@ jobs: sudo xcode-select --switch /Applications/Xcode_16.4.app - name: Set up Python - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. @@ -119,7 +119,7 @@ jobs: make ${{ matrix.platform }} BUILD_NUMBER=${{ needs.config.outputs.BUILD_NUMBER }} - name: Upload build artefacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v6.0.0 with: name: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz path: dist/Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz @@ -139,7 +139,7 @@ jobs: briefcase-run-args: ' -d "iPhone 16e::iOS 18.5"' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Xcode # GitHub recommends explicitly selecting the desired Xcode version: @@ -151,14 +151,14 @@ jobs: sudo xcode-select --switch /Applications/Xcode_16.4.app - name: Get build artifact - uses: actions/download-artifact@v5.0.0 + uses: actions/download-artifact@v7.0.0 with: pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz path: dist merge-multiple: true - name: Set up Python - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. @@ -167,7 +167,7 @@ jobs: # It's an edge case, but when a new alpha is released, we need to use it ASAP. check-latest: true - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: repository: beeware/Python-support-testbed path: Python-support-testbed @@ -196,10 +196,10 @@ jobs: testbed-args: '--simulator "Apple Vision Pro,arch=arm64,OS=2.5"' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get build artifact - uses: actions/download-artifact@v5.0.0 + uses: actions/download-artifact@v7.0.0 with: pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz path: dist @@ -215,7 +215,7 @@ jobs: sudo xcode-select --switch /Applications/Xcode_16.4.app - name: Set up Python - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. @@ -261,17 +261,17 @@ jobs: multiarch: arm64-iphoneos steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get build artifact - uses: actions/download-artifact@v5.0.0 + uses: actions/download-artifact@v7.0.0 with: pattern: Python-${{ needs.config.outputs.PYTHON_VER }}-${{ matrix.platform }}-support.${{ needs.config.outputs.BUILD_NUMBER }}.tar.gz path: dist merge-multiple: true - name: Set up Python - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: # Appending -dev ensures that we can always build the dev release. # It's a no-op for versions that have been published. diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 9b45abb..8f64774 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -8,10 +8,10 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python environment - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: python-version: "3.X" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 617fbf5..1b65e9f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,7 +40,7 @@ jobs: needs: [ config, ci ] steps: - name: Get build artifacts - uses: actions/download-artifact@v5.0.0 + uses: actions/download-artifact@v7.0.0 with: pattern: Python-* path: dist diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..98f687b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,160 @@ + + +# BeeWare Community Code of Conduct + +## Our pledge + +We pledge to make our community welcoming, safe, and equitable for all. + +We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant. + +The guidelines within and enforcement of the BeeWare Community Code of Conduct apply equally to everyone participating in the BeeWare community, including members of the Code of Conduct Response Team. + +## Encouraged behaviors + +While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language. + +With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including: + + 1. Respecting the **purpose of our community**, our activities, and our ways of gathering. + 2. Engaging **kindly and honestly** with others. + 3. Respecting **different viewpoints** and experiences. + 4. **Taking responsibility** for our actions and contributions. + 5. Gracefully giving and accepting **constructive feedback**. + 6. Committing to **repairing harm** when it occurs. + 7. Behaving in other ways that promote and sustain the **well-being of our community**. + +## Restricted behaviors + +We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct. + + 1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop. + 2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people. + 3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of a personal identity or trait. + 4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community. + 5. **Violating confidentiality.** Sharing or acting on someone's personal or private information without their permission. + 6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group. + 7. Behaving in other ways that **threaten the well-being** of our community. + +### Other restrictions + + 1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions. + 2. **Misrepresenting project affiliation.** Speaking or acting in a way that implies an official affiliation with the BeeWare project, where one does not exist. + 3. **Failing to credit sources.** Not properly crediting the sources of content you contribute. + 4. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside the norms of the community. + 5. **Excessive communication.** Disrespecting the time and space of others by engaging in an unacceptable volume of communication. + 6. **Unhelpful communication.** Offering opinions without relevant experience in the topic being discussed, entering into an ongoing discussion without first gaining familiarity with the history of the topic, or making contributions that are off-topic or otherwise distracting. + 7. **Irresponsible messaging.** Presenting content which includes, links, or describes other restricted behaviors without a relevant reason and appropriate prior warnings for consumers of that content. + 8. Other conduct that could reasonably be considered **unprofessional or inappropriate**. + +## Reporting an issue + +Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm. Reporting even minor issues is important, as they can be helpful in identifying patterns of behavior that may not be concerning in isolation, but when viewed collectively may be more significant. + +When an incident does occur, it is important to report it promptly to the BeeWare Code of Conduct Response Team. + +**If you believe you or anyone else is in physical danger, please notify appropriate law enforcement first.** + +To report a possible violation, email the Team at [conduct@beeware.org](mailto:conduct@beeware.org). If necessary, you can reach out to individual team members. On the BeeWare Discord server, you can also direct message anyone on the Response Team, or, if appropriate, mention `@moderators` in a public channel. Team members can be reached by the following usernames on Discord or GitHub, or the provided email addresses: + + * Russell Keith-Magee (@freakboy3742; [russell@beeware.org](mailto:russell@beeware.org)) + * Kattni (@kattni; [kattni@beeware.org](mailto:kattni@beeware.org)) + * Katie McLaughlin (@glasnt; [katie@beeware.org](mailto:katie@beeware.org)) + * Philip James (@phildini; [philip@beeware.org](mailto:philip@beeware.org)) + * Charles Whittington (@HalfWhitt; [charles@beeware.org](mailto:charles@beeware.org)) + +The Response Team takes reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. The Team will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution. If we determine that a public statement needs to be made, the identities of all victims and reporters will remain confidential unless those individuals instruct us otherwise. + +In your report, please include: + + * **Your contact info** so we can get in touch with you if we need to follow up. + * **Names (real, nicknames, or pseudonyms) of any individuals involved.** If there were other witnesses besides you, please try to include them as well. + * **When and where the incident occurred.** Please be as specific as possible. + * **Your account of what occurred.** If there is a publicly available record (e.g. a Discord or GitHub message) please include a link. + * **Any extra context** you believe existed for the incident. + * **If you believe this incident is ongoing.** + * **If you believe any member of the Response Team has a conflict of interest** in adjudicating the incident. + * **What, if any, corrective response** you believe would be appropriate. + * **Any other information** you believe we should have. + +Code of Conduct Response Team members are obligated to maintain confidentiality with regard to the reporter and details of an incident. + +## The Response Team's report followup + +You will receive a response acknowledging receipt of your report. We promise to acknowledge receipt within 24 hours (and will aim for much quicker than that). + +The Response Team will immediately meet to review the incident and determine: + + * What happened. + * Whether this event constitutes a code of conduct violation. + * Who the reported person is. + * Whether this is an ongoing situation, or if there is a threat to anyone's physical safety. + * If this is determined to be an ongoing incident or a threat to physical safety, the Response Team's immediate priority will be to protect everyone involved. This means we may delay an official response until we believe that the situation has concluded and that everyone is physically safe. + * If a member of the Response Team is one of the named parties, they will not be included in any discussions, and will not be provided with any confidential details from the reporter. + +If anyone on the Response Team believes they have a conflict of interest in adjudicating on a reported issue, they will inform the other Response Team members, and recuse themselves from any discussion about the issue. Following this declaration, they will not be provided with any confidential details from the reporter. + +We'll respond within one week to the person who filed the report with either a resolution or an explanation of why the situation is not yet resolved. + +Once we've determined our final action, we'll contact the original reporter to let them know what action (if any) we'll be taking. We'll take into account feedback from the reporter on the appropriateness of our response, but we don't guarantee we'll act on it. + +Finally, to maintain transparency in the reporting and enforcement process, whenever possible, the Response Team will make a public report of the incident on [The Buzz](https://beeware.org/news/buzz), the BeeWare blog. A public report may not be made if the specifics of the incident do not allow us to preserve anonymity, or if there is potential for ongoing harm. + +## Enforcement: addressing and repairing harm + +If an investigation by the Response Team finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped. + + 1. Warning + * Event: A violation involving a single incident or series of incidents. + * Consequence: A private, written warning from the Response Team. + * Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations. + + 2. Temporarily Limited Activities + * Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation. + * Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members. + * Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over. + + 3. Temporary Suspension + * Event: A pattern of repeated violation which the Response Team has tried to address with warnings, or a single serious violation. + * Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions. Suspensions will be based on where the violation occurs, and may be limited to the space in which the violation occurs. In the event of a more serious violation, the suspension may apply to all spaces. + * Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted. + + 4. Permanent Ban + * Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Response Team determines there is no way to keep the community safe with this person as a member. + * Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior. + * Repair: There is no possible repair in cases of this severity. + +This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community. + +## Scope + +This Code of Conduct applies within all community spaces, including GitHub, the BeeWare Discord server, and in-person events, such as conferences, meetups, and sprints. It also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +Behavior outside of official BeeWare spaces may also be considered as supporting evidence for a report if that behavior establishes a pattern, or represents a potential risk to the BeeWare community. + +This Code of Conduct operates in parallel to any Code of Conduct that is in effect in a given context (e.g., the Code of Conduct for a conference). If an incident occurs, we encourage reporting that incident to all relevant conduct groups. Known violations of other Codes of Conduct may be considered as supporting evidence for a report under this Code of Conduct. The BeeWare Code of Conduct Response Team will cooperate with other Code of Conduct teams, but will not disclose any identifying details without the prior consent of the reporting party. +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/). + +Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). + +For answers to common questions about Contributor Covenant, see the [FAQ](https://www.contributor-covenant.org/faq). [Translations](https://www.contributor-covenant.org/translations) are provided. There are [additional enforcement and community guideline resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion). + +## Changes + +Major substantive changes are listed here; for a complete list of changes see the GitHub commit history. + + * **December 15, 2025:** Updated to adapt the Contributor Covenant, version 3.0, with some modifications for BeeWare-specific guidelines and procedures. + + * **July 4, 2016:** Added instructions and guidelines for reporting incidents. + + * **December 5, 2015:** Initial Code of Conduct adopted. diff --git a/Makefile b/Makefile index ec5dcc9..82dde37 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.8 +PYTHON_VERSION=3.13.11 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 f1653c4..dc94847 100644 --- a/patch/Python/Python.patch +++ b/patch/Python/Python.patch @@ -1,6 +1,57 @@ +diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml +index 6347ffb3e11..19259c069d7 100644 +--- a/.pre-commit-config.yaml ++++ b/.pre-commit-config.yaml +@@ -2,6 +2,10 @@ + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.13.2 + hooks: ++ - 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] +@@ -26,6 +30,10 @@ + 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] +--- /dev/null ++++ b/Apple/.ruff.toml +@@ -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 ++] --- /dev/null +++ b/Apple/__main__.py -@@ -0,0 +1,1034 @@ +@@ -0,0 +1,1111 @@ +#!/usr/bin/env python3 +########################################################################## +# Apple XCframework build script @@ -20,23 +71,25 @@ +# which will: +# * Clean any pre-existing build artefacts +# * Configure and make a Python that can be used for the build -+# * Configure and make a Python for each supported iOS/tvOS architecture and ABI ++# * Configure and make a Python for each supported iOS/tvOS/watchOS/visionOS ++# architecture and ABI +# * Combine the outputs of the builds from the previous step into a single +# XCframework, merging binaries into a "fat" binary if necessary +# * Clone a copy of the testbed, configured to use the XCframework +# * Construct a tarball containing the release artefacts +# * Run the test suite using the generated XCframework. +# -+# This is the complete sequence that would be needed in CI to build and test a -+# candidate release artefact. ++# This is the complete sequence that would be needed in CI to build and test ++# a candidate release artefact. +# +# Each individual step can be invoked individually - there are commands to +# clean, configure-build, make-build, configure-host, make-host, package, and +# test. +# +# There is also a build command that can be used to combine the configure and -+# make steps for the build Python, an individual host, all hosts, or all builds. -+# ######################################################################### ++# make steps for the build Python, an individual host, all hosts, or all ++# builds. ++########################################################################## +from __future__ import annotations + +import argparse @@ -50,13 +103,12 @@ +import sys +import sysconfig +import time -+from collections.abc import Sequence ++from collections.abc import Callable, Sequence +from contextlib import contextmanager +from datetime import datetime, timezone +from os.path import basename, relpath +from pathlib import Path +from subprocess import CalledProcessError -+from typing import Callable + +EnvironmentT = dict[str, str] +ArgsT = Sequence[str | Path] @@ -180,17 +232,15 @@ +def apple_env(host: str) -> EnvironmentT: + """Construct an Apple development environment for the given host.""" + env = { -+ "PATH": ":".join( -+ [ -+ str(PYTHON_DIR / f"Apple/{platform_for_host(host)}/Resources/bin"), -+ str(subdir(host) / "prefix"), -+ "/usr/bin", -+ "/bin", -+ "/usr/sbin", -+ "/sbin", -+ "/Library/Apple/usr/bin", -+ ] -+ ), ++ "PATH": ":".join([ ++ str(PYTHON_DIR / f"Apple/{platform_for_host(host)}/Resources/bin"), ++ str(subdir(host) / "prefix"), ++ "/usr/bin", ++ "/bin", ++ "/usr/sbin", ++ "/sbin", ++ "/Library/Apple/usr/bin", ++ ]), + } + + return env @@ -236,12 +286,10 @@ + paths.append(target) + + if target in {"all", "hosts", "test"}: -+ paths.extend( -+ [ -+ path.name -+ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*") -+ ] -+ ) ++ paths.extend([ ++ path.name ++ for path in CROSS_BUILD_DIR.glob(f"{context.platform}-testbed.*") ++ ]) + + for path in paths: + delete_path(path) @@ -254,7 +302,9 @@ + if not binary.is_file(): + binary = binary.with_suffix(".exe") + if not binary.is_file(): -+ raise FileNotFoundError(f"Unable to find `python(.exe)` in {build_dir}") ++ raise FileNotFoundError( ++ f"Unable to find `python(.exe)` in {build_dir}" ++ ) + + return binary + @@ -345,14 +395,21 @@ + Downloads binaries if they aren't already present. Downloads will be stored + in provided cache directory. + -+ On non-macOS platforms, as a safety mechanism, any dynamic libraries will be -+ purged from the unpacked dependencies. ++ On non-macOS platforms, as a safety mechanism, any dynamic libraries will ++ be purged from the unpacked dependencies. + """ ++ # To create new builds of these dependencies, usually all that's necessary ++ # is to push a tag to the cpython-apple-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. + deps_url = "https://github.com/beeware/cpython-apple-source-deps/releases/download" + for name_ver in [ + "BZip2-1.0.8-2", + "libFFI-3.4.7-2", -+ "OpenSSL-3.0.17-1", ++ "OpenSSL-3.0.18-1", + "XZ-5.6.4-2", + "mpdecimal-4.0.0-2", + "zstd-1.5.7-1", @@ -381,18 +438,16 @@ + + out_path = target_path / basename(url) + if not Path(out_path).is_file(): -+ run( -+ [ -+ "curl", -+ "-Lf", -+ "--retry", -+ "5", -+ "--retry-all-errors", -+ "-o", -+ out_path, -+ url, -+ ] -+ ) ++ run([ ++ "curl", ++ "-Lf", ++ "--retry", ++ "5", ++ "--retry-all-errors", ++ "-o", ++ out_path, ++ url, ++ ]) + else: + print(f"Using cached version of {basename(url)}") + return out_path @@ -473,7 +528,8 @@ + """ + return ( + CROSS_BUILD_DIR -+ / f"{host_triple}/Apple/{platform_for_host(host_triple)}/Frameworks/{multiarch}" ++ / f"{host_triple}/Apple/{platform_for_host(host_triple)}" ++ / f"Frameworks/{multiarch}" + ) + + @@ -481,7 +537,9 @@ + """Extract the Python version being built from patchlevel.h.""" + for path in prefix_path.glob("**/patchlevel.h"): + text = path.read_text(encoding="utf-8") -+ if match := re.search(r'\n\s*#define\s+PY_VERSION\s+"(.+)"\s*\n', text): ++ if match := re.search( ++ r'\n\s*#define\s+PY_VERSION\s+"(.+)"\s*\n', text ++ ): + version = match[1] + # If not building against a tagged commit, add a timestamp to the + # version. Follow the PyPA version number rules, as this will make @@ -499,7 +557,7 @@ + + +def lib_platform_files(dirname, names): -+ """A file filter that ignores platform-specific files in the lib directory.""" ++ """A file filter that ignores platform-specific files in lib.""" + path = Path(dirname) + if ( + path.parts[-3] == "lib" @@ -508,7 +566,7 @@ + ): + return names + elif path.parts[-2] == "lib" and path.parts[-1].startswith("python"): -+ ignored_names = set( ++ ignored_names = { + name + for name in names + if ( @@ -516,7 +574,13 @@ + or name.startswith("_sysconfig_vars_") + or name == "build-details.json" + ) -+ ) ++ } ++ elif path.parts[-1] == "lib": ++ ignored_names = { ++ name ++ for name in names ++ if name.startswith("libpython") and name.endswith(".dylib") ++ } + else: + ignored_names = set() + @@ -529,7 +593,9 @@ + """ + path = Path(dirname) + if path.parts[-2] == "lib" and path.parts[-1].startswith("python"): -+ return set(names) - lib_platform_files(dirname, names) - {"lib-dynload"} ++ return ( ++ set(names) - lib_platform_files(dirname, names) - {"lib-dynload"} ++ ) + else: + return set() + @@ -537,14 +603,15 @@ +def create_xcframework(platform: str) -> str: + """Build an XCframework from the component parts for the platform. + -+ :return: The version number of the Python verion that was packaged. ++ :return: The version number of the Python version that was packaged. + """ + package_path = CROSS_BUILD_DIR / platform + try: + package_path.mkdir() + except FileExistsError: + raise RuntimeError( -+ f"{platform} XCframework already exists; do you need to run with --clean?" ++ f"{platform} XCframework already exists; do you need to run " ++ "with --clean?" + ) from None + + frameworks = [] @@ -558,7 +625,8 @@ + # one on the list, as it's as representative as any other. + first_host_triple, first_multiarch = next(iter(slice_parts.items())) + first_framework = ( -+ framework_path(first_host_triple, first_multiarch) / "Python.framework" ++ framework_path(first_host_triple, first_multiarch) ++ / "Python.framework" + ) + + if len(slice_parts) == 1: @@ -587,7 +655,10 @@ + run( + ["lipo", "-create", "-output", slice_framework / "Python"] + + [ -+ (framework_path(host_triple, multiarch) / "Python.framework/Python") ++ ( ++ framework_path(host_triple, multiarch) ++ / "Python.framework/Python" ++ ) + for host_triple, multiarch in slice_parts.items() + ] + ) @@ -633,7 +704,7 @@ + print(f" - {slice_name} binaries") + shutil.copytree(first_path / "bin", slice_path / "bin") + -+ # Copy the include path (this will be a symlink to the framework headers) ++ # Copy the include path (a symlink to the framework headers) + print(f" - {slice_name} include files") + shutil.copytree( + first_path / "include", @@ -647,6 +718,12 @@ + slice_framework / "Headers/pyconfig.h", + ) + ++ print(f" - {slice_name} shared library") ++ # Create a simlink for the fat library ++ shared_lib = slice_path / f"lib/libpython{version_tag}.dylib" ++ shared_lib.parent.mkdir() ++ shared_lib.symlink_to("../Python.framework/Python") ++ + print(f" - {slice_name} architecture-specific files") + for host_triple, multiarch in slice_parts.items(): + print(f" - {multiarch} standard library") @@ -658,6 +735,7 @@ + framework_path(host_triple, multiarch) / "lib", + package_path / "Python.xcframework/lib", + ignore=lib_platform_files, ++ symlinks=True, + ) + has_common_stdlib = True + @@ -665,6 +743,7 @@ + framework_path(host_triple, multiarch) / "lib", + slice_path / f"lib-{arch}", + ignore=lib_non_platform_files, ++ symlinks=True, + ) + + # Copy the host's pyconfig.h to an architecture-specific name. @@ -685,7 +764,8 @@ + # statically link those libraries into a Framework, you become + # responsible for providing a privacy manifest for that framework. + xcprivacy_file = { -+ "OpenSSL": subdir(host_triple) / "prefix/share/OpenSSL.xcprivacy" ++ "OpenSSL": subdir(host_triple) ++ / "prefix/share/OpenSSL.xcprivacy" + } + print(f" - {multiarch} xcprivacy files") + for module, lib in [ @@ -695,7 +775,8 @@ + shutil.copy( + xcprivacy_file[lib], + slice_path -+ / f"lib-{arch}/python{version_tag}/lib-dynload/{module}.xcprivacy", ++ / f"lib-{arch}/python{version_tag}" ++ / f"lib-dynload/{module}.xcprivacy", + ) + + print(" - build tools") @@ -720,18 +801,16 @@ + if context.platform != "watchOS": + # Clone testbed + print() -+ run( -+ [ -+ sys.executable, -+ "Apple/testbed", -+ "clone", -+ "--platform", -+ context.platform, -+ "--framework", -+ CROSS_BUILD_DIR / context.platform / "Python.xcframework", -+ CROSS_BUILD_DIR / context.platform / "testbed", -+ ] -+ ) ++ run([ ++ sys.executable, ++ "Apple/testbed", ++ "clone", ++ "--platform", ++ context.platform, ++ "--framework", ++ CROSS_BUILD_DIR / context.platform / "Python.xcframework", ++ CROSS_BUILD_DIR / context.platform / "testbed", ++ ]) + + # Build the final archive + archive_name = ( @@ -785,7 +864,7 @@ + package(context) + + -+def test(context: argparse.Namespace, host: str | None = None) -> None: ++def test(context: argparse.Namespace, host: str | None = None) -> None: # noqa: PT028 + """The implementation of the "test" command.""" + if host is None: + host = context.host @@ -795,9 +874,13 @@ + + with group(f"Test {'XCframework' if host in {'all', 'hosts'} else host}"): + timestamp = str(time.time_ns())[:-6] -+ testbed_dir = CROSS_BUILD_DIR / f"{context.platform}-testbed.{timestamp}" ++ testbed_dir = ( ++ CROSS_BUILD_DIR / f"{context.platform}-testbed.{timestamp}" ++ ) + if host in {"all", "hosts"}: -+ framework_path = CROSS_BUILD_DIR / context.platform / "Python.xcframework" ++ framework_path = ( ++ CROSS_BUILD_DIR / context.platform / "Python.xcframework" ++ ) + else: + build_arch = platform.machine() + host_arch = host.split("-")[0] @@ -819,18 +902,16 @@ + / f"Frameworks/{apple_multiarch(host)}" + ) + -+ run( -+ [ -+ sys.executable, -+ "Apple/testbed", -+ "clone", -+ "--platform", -+ context.platform, -+ "--framework", -+ framework_path, -+ testbed_dir, -+ ] -+ ) ++ run([ ++ sys.executable, ++ "Apple/testbed", ++ "clone", ++ "--platform", ++ context.platform, ++ "--framework", ++ framework_path, ++ testbed_dir, ++ ]) + + run( + [ @@ -839,16 +920,17 @@ + "run", + "--verbose", + ] -+ + (["--simulator", str(context.simulator)] if context.simulator else []) ++ + ( ++ ["--simulator", str(context.simulator)] ++ if context.simulator ++ else [] ++ ) + + [ + "--", + "test", -+ # "--slow-ci" if context.slow else "--fast-ci", -+ # "--no-randomize", -+ "-uall", -+ "--rerun", -+ "-W", ++ f"--{context.ci_mode}-ci", + "--single-process", ++ "--no-randomize", + # Timeout handling requires subprocesses; explicitly setting + # the timeout to -1 disables the faulthandler. + "--timeout=-1", @@ -859,11 +941,39 @@ + ) + + ++def apple_sim_host(platform_name: str) -> str: ++ """Determine the native simulator target for this platform.""" ++ for _, slice_parts in HOSTS[platform_name].items(): ++ for host_triple in slice_parts: ++ parts = host_triple.split("-") ++ if parts[0] == platform.machine() and parts[-1] == "simulator": ++ return host_triple ++ ++ raise KeyError(platform_name) ++ ++ +def ci(context: argparse.Namespace) -> None: -+ """The implementation of the "ci" command.""" ++ """The implementation of the "ci" command. ++ ++ In "Fast" mode, this compiles the build python, and the simulator for the ++ build machine's architecture; and runs the test suite with `--fast-ci` ++ configuration. ++ ++ In "Slow" mode, it compiles the build python, plus all candidate ++ architectures (both device and simulator); then runs the test suite with ++ `--slow-ci` configuration. ++ """ + clean(context, "all") -+ build(context, host="all") -+ test(context, host="all") ++ if context.ci_mode == "slow": ++ # In slow mode, build and test the full XCframework ++ build(context, host="all") ++ test(context, host="all") ++ else: ++ # In fast mode, just build the simulator platform. ++ sim_host = apple_sim_host(context.platform) ++ build(context, host="build") ++ build(context, host=sim_host) ++ test(context, host=sim_host) + + +def parse_args() -> argparse.Namespace: @@ -885,8 +995,7 @@ + "configure-build", help="Run `configure` for the build Python" + ) + subcommands.add_parser( -+ "make-build", -+ help="Run `make` for the build Python", ++ "make-build", help="Run `make` for the build Python" + ) + configure_host = subcommands.add_parser( + "configure-host", @@ -964,17 +1073,28 @@ + cmd.add_argument( + "--simulator", + help=( -+ "The name of the simulator to use (eg: 'iPhone 16e'). Defaults to " -+ "the most recently released 'entry level' iPhone device. Device " -+ "architecture and OS version can also be specified; e.g., " -+ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would run on " -+ "an ARM64 iPhone 16 Pro simulator running iOS 26.0." ++ "The name of the simulator to use (eg: 'iPhone 16e'). " ++ "Defaults to the most recently released 'entry level' " ++ "iPhone device. Device architecture and OS version can also " ++ "be specified; e.g., " ++ "`--simulator 'iPhone 16 Pro,arch=arm64,OS=26.0'` would " ++ "run on an ARM64 iPhone 16 Pro simulator running iOS 26.0." + ), + ) -+ cmd.add_argument( -+ "--slow", -+ action="store_true", -+ help="Run tests with --slow-ci options.", ++ group = cmd.add_mutually_exclusive_group() ++ 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", + ) + + for subcommand in [configure_build, configure_host, build, ci]: @@ -995,7 +1115,9 @@ + stream.write("\n") + + # shlex uses single quotes, so we surround the command with double quotes. -+ print(f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}') ++ print( ++ f'Command "{join_command(e.cmd)}" returned exit status {e.returncode}' ++ ) + + +def main() -> None: @@ -1034,10 +1156,16 @@ + + +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() --- /dev/null +++ b/Apple/iOS/README.md -@@ -0,0 +1,328 @@ +@@ -0,0 +1,339 @@ +# Python on iOS README + +**iOS support is [tier 3](https://peps.python.org/pep-0011/#tier-3).** @@ -1264,6 +1392,17 @@ + + $ 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 @@ -1687,7 +1826,7 @@ + --- /dev/null +++ b/Apple/testbed/Python.xcframework/build/utils.sh -@@ -0,0 +1,179 @@ +@@ -0,0 +1,180 @@ +# Utility methods for use in an Xcode project. +# +# An iOS XCframework cannot include any content other than the library binary @@ -1765,7 +1904,8 @@ + 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 -+ rsync -au --delete "$PROJECT_DIR/$PYTHON_XCFRAMEWORK_PATH/$SLICE_FOLDER/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" ++ # 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 +} + @@ -1994,7 +2134,7 @@ +process handle SIGXFSZ -n true -p true -s false --- /dev/null +++ b/Apple/testbed/TestbedTests/TestbedTests.m -@@ -0,0 +1,195 @@ +@@ -0,0 +1,198 @@ +#import +#import + @@ -2032,6 +2172,9 @@ + 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` @@ -2192,10 +2335,12 @@ +@end --- /dev/null +++ b/Apple/testbed/__main__.py -@@ -0,0 +1,436 @@ +@@ -0,0 +1,456 @@ +import argparse +import json ++import os +import re ++import shlex +import shutil +import subprocess +import sys @@ -2228,15 +2373,15 @@ + json_data = json.loads(raw_json) + + 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". ++ # 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. ++ # 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"] @@ -2267,7 +2412,7 @@ + ) + simulator = simulators[-1][1] + elif platform == "watchOS": -+ raise NotImplementedError(f"Don't know how to launch watchOS (yet)") ++ raise NotImplementedError("Don't know how to launch watchOS (yet)") + else: + raise ValueError(f"Unknown platform {platform}") + @@ -2294,6 +2439,13 @@ + 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. @@ -2301,6 +2453,7 @@ + ["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 @@ -2459,7 +2612,7 @@ + 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: @@ -2501,7 +2654,8 @@ + + parser = argparse.ArgumentParser( + description=( -+ "Manages the process of testing an Apple Python project through Xcode." ++ "Manages the process of testing an Apple Python project " ++ "through Xcode." + ), + ) + @@ -2542,7 +2696,10 @@ + + run = subcommands.add_parser( + "run", -+ usage="%(prog)s [-h] [--simulator SIMULATOR] -- [ ...]", ++ usage=( ++ "%(prog)s [-h] [--simulator SIMULATOR] -- " ++ " [ ...]" ++ ), + description=( + "Run a testbed project. The arguments provided after `--` will be " + "passed to the running test process as if they were arguments to " @@ -2603,9 +2760,9 @@ + / "bin" + ).is_dir(): + print( -+ f"Testbed does not contain a compiled Python 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) + @@ -2617,7 +2774,8 @@ + ) + 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) @@ -2628,6 +2786,11 @@ + + +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() --- /dev/null +++ b/Apple/testbed/iOSTestbed.xcodeproj/project.pbxproj @@ -6039,20 +6202,20 @@ index 9921fd6114b..4ed18f4938c 100644 +file next to ``mymodule.so``, and the privacy manifest will be installed into +the required location when the binary module is converted into a framework. diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py -index 8261773cef9..6612230265f 100644 +index 80651dc64ce..8a8f3e95505 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py -@@ -347,9 +347,9 @@ - if name: - name = _os.fspath(name) +@@ -380,9 +380,9 @@ + else: + def _load_library(self, name, mode, handle, winmode): - # If the filename that has been provided is an iOS/tvOS/watchOS - # .fwork file, dereference the location to the true origin of the - # binary. + # If the filename that has been provided is an iOS, tvOS, visionOS + # or watchOS .fwork file, dereference the location to the true + # origin of the binary. - if name.endswith(".fwork"): + if name and name.endswith(".fwork"): with open(name) as f: name = _os.path.join( diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py @@ -6069,7 +6232,7 @@ index 117bf06cb01..87611a6d03f 100644 def find_library(name): possible = ['lib%s.dylib' % name, diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py -index c5e641773e6..4260ab6a74a 100644 +index 41f538acb03..02fc9b9daf3 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -52,7 +52,7 @@ @@ -6081,7 +6244,7 @@ index c5e641773e6..4260ab6a74a 100644 _CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) -@@ -1800,7 +1800,7 @@ +@@ -1802,7 +1802,7 @@ """ extension_loaders = [] if hasattr(_imp, 'create_dynamic'): @@ -6247,7 +6410,7 @@ index 8895177e326..9e1e0628671 100755 macos_release = mac_ver()[0] if macos_release: diff --git a/Lib/site.py b/Lib/site.py -index aedf36399c3..72383a0957c 100644 +index 041dca113a5..3dedc828524 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -292,8 +292,8 @@ @@ -6262,7 +6425,7 @@ index aedf36399c3..72383a0957c 100644 def joinuser(*args): diff --git a/Lib/subprocess.py b/Lib/subprocess.py -index ffc3fdf6afb..7df38b4f3d8 100644 +index 885f0092b53..0e04efb25ce 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -75,7 +75,7 @@ @@ -6317,7 +6480,7 @@ index f7bd675bb3b..98ee0cf234c 100644 import _osx_support osname, release, machine = _osx_support.get_platform_osx( diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py -index 44c5b28f455..0b4f874ec15 100644 +index 22ab655507a..368100eeb29 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -6840,9 +6840,9 @@ @@ -6333,10 +6496,18 @@ index 44c5b28f455..0b4f874ec15 100644 else: extension_loader = "ExtensionFileLoader" diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py -index 4605938b875..5f1223e57d6 100644 +index 4605938b875..cc65b3d63dc 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py -@@ -582,7 +582,7 @@ +@@ -64,6 +64,7 @@ + "force_not_colorized_test_class", + "make_clean_env", + "BrokenIter", ++ "reset_code", "on_github_actions" + ] + + +@@ -582,7 +583,7 @@ is_android = sys.platform == "android" @@ -6345,7 +6516,7 @@ index 4605938b875..5f1223e57d6 100644 unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: unix_shell = None -@@ -592,7 +592,7 @@ +@@ -592,7 +593,7 @@ is_emscripten = sys.platform == "emscripten" is_wasi = sys.platform == "wasi" @@ -6354,6 +6525,15 @@ index 4605938b875..5f1223e57d6 100644 is_apple = is_apple_mobile or sys.platform == "darwin" has_fork_support = hasattr(os, "fork") and not ( +@@ -1330,6 +1331,8 @@ + _opcode.ENABLE_SPECIALIZATION, "requires specialization")(test) + + ++on_github_actions = "GITHUB_ACTIONS" in os.environ ++ + #======================================================================= + # Check for the presence of docstrings. + diff --git a/Lib/test/test__interpreters.py b/Lib/test/test__interpreters.py index 6fb2bc24fa3..88cb6691cd8 100644 --- a/Lib/test/test__interpreters.py @@ -6395,6 +6575,27 @@ index ed277276b51..c846a9bc5d7 100644 else: self.assertEqual(res.system, "") self.assertEqual(res.release, "") +diff --git a/Lib/test/test_socketserver.py b/Lib/test/test_socketserver.py +index 0f62f9eb200..2ca356606b2 100644 +--- a/Lib/test/test_socketserver.py ++++ b/Lib/test/test_socketserver.py +@@ -218,12 +218,16 @@ + self.dgram_examine) + + @requires_unix_sockets ++ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, ++ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") + def test_UnixDatagramServer(self): + self.run_server(socketserver.UnixDatagramServer, + socketserver.DatagramRequestHandler, + self.dgram_examine) + + @requires_unix_sockets ++ @unittest.skipIf(test.support.is_apple_mobile and test.support.on_github_actions, ++ "gh-140702: Test fails regularly on iOS simulator on GitHub Actions") + def test_ThreadingUnixDatagramServer(self): + self.run_server(socketserver.ThreadingUnixDatagramServer, + socketserver.DatagramRequestHandler, diff --git a/Lib/test/test_webbrowser.py b/Lib/test/test_webbrowser.py index 4fcbc5c2e59..851bd6e8f3c 100644 --- a/Lib/test/test_webbrowser.py @@ -6437,7 +6638,7 @@ index 2f9555ad60d..249756ba4de 100755 if objc: # If objc exists, we know ctypes is also importable. diff --git a/Makefile.pre.in b/Makefile.pre.in -index a7dc9709d62..a1c3b6f625a 100644 +index a7dc9709d62..304a9f6fc3a 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -202,6 +202,12 @@ @@ -6462,7 +6663,17 @@ index a7dc9709d62..a1c3b6f625a 100644 # Run the testbed project $(PYTHON_FOR_BUILD) "$(XCFOLDER)" run --verbose -- test -uall --single-process --rerun -W -@@ -2976,10 +2982,10 @@ +@@ -2791,6 +2797,9 @@ + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR) + sed 's/%VERSION%/'"`$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import platform; print(platform.python_version())'`"'/g' < $(RESSRCDIR)/Info.plist > $(DESTDIR)$(PYTHONFRAMEWORKINSTALLDIR)/Info.plist + $(INSTALL_SHARED) $(LDLIBRARY) $(DESTDIR)$(PYTHONFRAMEWORKPREFIX)/$(LDLIBRARY) ++ $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(LIBDIR) ++ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(LDVERSION).dylib" ++ $(LN) -fs "../$(LDLIBRARY)" "$(DESTDIR)$(prefix)/lib/libpython$(VERSION).dylib" + $(INSTALL) -d -m $(DIRMODE) $(DESTDIR)$(BINDIR) + for file in $(srcdir)/$(RESSRCDIR)/bin/* ; do \ + $(INSTALL) -m $(EXEMODE) $$file $(DESTDIR)$(BINDIR); \ +@@ -2976,10 +2985,10 @@ -find build -type f -a ! -name '*.gc??' -exec rm -f {} ';' -rm -f Include/pydtrace_probes.h -rm -f profile-gen-stamp @@ -6477,7 +6688,7 @@ index a7dc9709d62..a1c3b6f625a 100644 .PHONY: profile-removal profile-removal: -@@ -3005,7 +3011,7 @@ +@@ -3005,7 +3014,7 @@ config.cache config.log pyconfig.h Modules/config.c -rm -rf build platform -rm -rf $(PYTHONFRAMEWORKDIR)