diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36516e291..0dc854444 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: fail-fast: false matrix: platform: [ "macos-12", "macos-14", "windows-latest", "ubuntu-latest" ] - python-version: [ "3.8", "3.12", "3.13-dev" ] + python-version: [ "3.9", "3.12", "3.13-dev" ] include: - experimental: false - platform: "ubuntu-latest" @@ -80,10 +80,6 @@ jobs: # Allow dev Python to fail without failing entire job - python-version: "3.13-dev" experimental: true - exclude: - # macos-14 (i.e. arm64) does not support Python 3.8 - - platform: "macos-14" - python-version: "3.8" steps: - name: Checkout uses: actions/checkout@v4.1.7 @@ -145,7 +141,7 @@ jobs: with: # Use minimum version of python for coverage to avoid phantom branches # https://github.com/nedbat/coveragepy/issues/1572#issuecomment-1522546425 - python-version: "3.8" + python-version: "3.9" - name: Install Tox uses: beeware/.github/.github/actions/install-requirement@main diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15ce66508..6d0037927 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: rev: v3.17.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/PyCQA/isort rev: 5.13.2 hooks: diff --git a/changes/1934.removal.1.rst b/changes/1934.removal.1.rst new file mode 100644 index 000000000..e61e69e27 --- /dev/null +++ b/changes/1934.removal.1.rst @@ -0,0 +1 @@ +Support for Python 3.8 was dropped. diff --git a/changes/1934.removal.2.rst b/changes/1934.removal.2.rst new file mode 100644 index 000000000..76ed35bc1 --- /dev/null +++ b/changes/1934.removal.2.rst @@ -0,0 +1 @@ +macOS and iOS templates have both had an epoch increase. macOS and iOS projects created with previous versions of Briefcase will need to be re-generated. diff --git a/docs/background/faq.rst b/docs/background/faq.rst index 79485ba59..373350fe6 100644 --- a/docs/background/faq.rst +++ b/docs/background/faq.rst @@ -4,7 +4,7 @@ Frequently Asked Questions What version of Python does Briefcase support? ---------------------------------------------- -Python 3.8 or higher. +Python 3.9 or higher. What platforms does Briefcase support? -------------------------------------- diff --git a/docs/reference/platforms/linux/system.rst b/docs/reference/platforms/linux/system.rst index ac1120a98..66cb62312 100644 --- a/docs/reference/platforms/linux/system.rst +++ b/docs/reference/platforms/linux/system.rst @@ -104,7 +104,7 @@ normalization of code name and version is performed, so ``ubuntu:jammy`` and same version). You can specify any identifier you want, provided the distribution is still -supported by the vendor, and system Python is Python 3.8 or later. +supported by the vendor, and system Python is Python 3.9 or later. The following Linux vendors are known to work as Docker targets: diff --git a/pyproject.toml b/pyproject.toml index 653e5c1fa..cfdb8bd65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dynamic = ["version"] name = "briefcase" description = "Tools to support converting a Python project into a standalone native application." readme = "README.rst" -requires-python = ">= 3.8" +requires-python = ">= 3.9" license.text = "New BSD" authors = [ {name="Russell Keith-Magee", email="russell@keith-magee.com"}, @@ -48,7 +48,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -98,9 +97,7 @@ dependencies = [ dev = [ "coverage[toml] == 7.6.1", "coverage-conditional-plugin == 0.9.0", - # Pre-commit 3.6.0 deprecated support for Python 3.8 - "pre-commit == 3.5.0 ; python_version < '3.9'", - "pre-commit == 3.8.0 ; python_version >= '3.9'", + "pre-commit == 3.8.0", "pytest == 8.3.2", "pytest-xdist == 3.6.1", "setuptools_scm == 8.1.0", diff --git a/src/briefcase/commands/new.py b/src/briefcase/commands/new.py index c74ab79a6..4f5ce70d1 100644 --- a/src/briefcase/commands/new.py +++ b/src/briefcase/commands/new.py @@ -4,9 +4,8 @@ import sys import unicodedata from collections import OrderedDict -from collections.abc import Sequence +from collections.abc import Iterable, Sequence from email.utils import parseaddr -from typing import Iterable if sys.version_info >= (3, 10): # pragma: no-cover-if-lt-py310 from importlib.metadata import entry_points diff --git a/src/briefcase/console.py b/src/briefcase/console.py index 06f1563a6..cdc7af3bc 100644 --- a/src/briefcase/console.py +++ b/src/briefcase/console.py @@ -10,11 +10,12 @@ import textwrap import time import traceback +from collections.abc import Iterable from contextlib import contextmanager from datetime import datetime from enum import IntEnum from pathlib import Path -from typing import Callable, Iterable +from typing import Callable from rich.console import Console as RichConsole from rich.control import strip_control_codes diff --git a/src/briefcase/integrations/docker.py b/src/briefcase/integrations/docker.py index 50127c8ae..3dd3ff734 100644 --- a/src/briefcase/integrations/docker.py +++ b/src/briefcase/integrations/docker.py @@ -5,9 +5,9 @@ import socket import subprocess import sys +from collections.abc import Iterable, Mapping from functools import lru_cache from pathlib import Path, PosixPath, PurePosixPath -from typing import Iterable, Mapping from packaging.version import InvalidVersion, Version diff --git a/src/briefcase/integrations/file.py b/src/briefcase/integrations/file.py index afdf71123..047c48acc 100644 --- a/src/briefcase/integrations/file.py +++ b/src/briefcase/integrations/file.py @@ -5,10 +5,10 @@ import shutil import sys import tempfile +from collections.abc import Iterable, Sequence from contextlib import suppress from email.message import Message from pathlib import Path -from typing import TYPE_CHECKING from urllib.parse import urlparse import requests.exceptions as requests_exceptions @@ -21,9 +21,6 @@ ) from briefcase.integrations.base import Tool, ToolCache -if TYPE_CHECKING: - from typing import Iterable, Sequence - class File(Tool): name = "file" diff --git a/src/briefcase/integrations/subprocess.py b/src/briefcase/integrations/subprocess.py index 31c751db4..8d0fb099c 100644 --- a/src/briefcase/integrations/subprocess.py +++ b/src/briefcase/integrations/subprocess.py @@ -10,11 +10,11 @@ import sys import threading import time -from collections.abc import Callable +from collections.abc import Callable, Iterator, Mapping, Sequence from functools import wraps from pathlib import Path from subprocess import CompletedProcess -from typing import Iterator, Mapping, Sequence, TypeVar, Union +from typing import TypeVar, Union import psutil diff --git a/src/briefcase/integrations/windows_sdk.py b/src/briefcase/integrations/windows_sdk.py index 88f89a004..a527193f2 100644 --- a/src/briefcase/integrations/windows_sdk.py +++ b/src/briefcase/integrations/windows_sdk.py @@ -1,8 +1,8 @@ from __future__ import annotations import subprocess +from collections.abc import Iterator from pathlib import Path -from typing import Iterator # winreg can only be imported on Windows try: diff --git a/src/briefcase/platforms/iOS/__init__.py b/src/briefcase/platforms/iOS/__init__.py index 71e8d5372..9e68e6b1d 100644 --- a/src/briefcase/platforms/iOS/__init__.py +++ b/src/briefcase/platforms/iOS/__init__.py @@ -9,8 +9,8 @@ class iOSMixin: supported_host_os_reason = ( "iOS applications require Xcode, which is only available on macOS." ) - # 0.3.16 introduced new-style dylib support. - platform_target_version = "0.3.16" + # 0.3.20 introduced PEP 730-style dynamic libraries. + platform_target_version = "0.3.20" def verify_tools(self): Xcode.verify(self.tools, min_version=(13, 0, 0)) diff --git a/src/briefcase/platforms/iOS/xcode.py b/src/briefcase/platforms/iOS/xcode.py index 31da81f47..e0cab0257 100644 --- a/src/briefcase/platforms/iOS/xcode.py +++ b/src/briefcase/platforms/iOS/xcode.py @@ -514,11 +514,14 @@ def run_app( # Try to uninstall the app first. If the app hasn't been installed # before, this will still succeed. self.logger.info(f"Installing {label}...", prefix=app.app_name) - with self.input.wait_bar( - "Uninstalling any existing app version..." - ) as keep_alive, self.tools.subprocess.Popen( - ["xcrun", "simctl", "uninstall", udid, app.bundle_identifier] - ) as uninstall_popen: + with ( + self.input.wait_bar( + "Uninstalling any existing app version..." + ) as keep_alive, + self.tools.subprocess.Popen( + ["xcrun", "simctl", "uninstall", udid, app.bundle_identifier] + ) as uninstall_popen, + ): while (ret_code := uninstall_popen.poll()) is None: keep_alive.update() time.sleep(0.25) @@ -529,11 +532,12 @@ def run_app( ) # Install the app. - with self.input.wait_bar( - f"Installing new {label} version..." - ) as keep_alive, self.tools.subprocess.Popen( - ["xcrun", "simctl", "install", udid, self.binary_path(app)] - ) as install_popen: + with ( + self.input.wait_bar(f"Installing new {label} version...") as keep_alive, + self.tools.subprocess.Popen( + ["xcrun", "simctl", "install", udid, self.binary_path(app)] + ) as install_popen, + ): while (ret_code := install_popen.poll()) is None: keep_alive.update() time.sleep(0.25) diff --git a/src/briefcase/platforms/linux/__init__.py b/src/briefcase/platforms/linux/__init__.py index f1265ead1..ee467475c 100644 --- a/src/briefcase/platforms/linux/__init__.py +++ b/src/briefcase/platforms/linux/__init__.py @@ -3,7 +3,6 @@ import subprocess import sys from pathlib import Path -from typing import List from briefcase.commands.create import _is_local_requirement from briefcase.commands.open import OpenCommand @@ -134,7 +133,7 @@ def local_requirements_path(self, app): def _install_app_requirements( self, app: AppConfig, - requires: List[str], + requires: list[str], app_packages_path: Path, ): """Install requirements for the app with pip. @@ -197,7 +196,7 @@ def _install_app_requirements( app_packages_path=app_packages_path, ) - def _pip_requires(self, app: AppConfig, requires: List[str]): + def _pip_requires(self, app: AppConfig, requires: list[str]): """Convert the requirements list to an .deb project compatible format. Any local file requirements are converted into a reference to the file generated diff --git a/src/briefcase/platforms/macOS/__init__.py b/src/briefcase/platforms/macOS/__init__.py index fbe9d58e0..f31b3271f 100644 --- a/src/briefcase/platforms/macOS/__init__.py +++ b/src/briefcase/platforms/macOS/__init__.py @@ -72,6 +72,8 @@ class macOSMixin: platform = "macOS" supported_host_os = {"Darwin"} supported_host_os_reason = "macOS applications can only be built on macOS." + # 0.3.20 introduced a framework-based support package. + platform_target_version = "0.3.20" class macOSCreateMixin(AppPackagesMergeMixin): diff --git a/src/briefcase/platforms/web/static.py b/src/briefcase/platforms/web/static.py index a80aa39fa..5e09a94a9 100644 --- a/src/briefcase/platforms/web/static.py +++ b/src/briefcase/platforms/web/static.py @@ -4,7 +4,7 @@ import webbrowser from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path -from typing import Any, List +from typing import Any from zipfile import ZipFile from briefcase.console import Log @@ -297,7 +297,7 @@ def run_app( self, app: AppConfig, test_mode: bool, - passthrough: List[str], + passthrough: list[str], host, port, open_browser, diff --git a/src/briefcase/platforms/windows/__init__.py b/src/briefcase/platforms/windows/__init__.py index c720b7f1d..b12724eb9 100644 --- a/src/briefcase/platforms/windows/__init__.py +++ b/src/briefcase/platforms/windows/__init__.py @@ -2,7 +2,6 @@ import subprocess import uuid from pathlib import Path, PurePath -from typing import List from zipfile import ZIP_DEFLATED, ZipFile from briefcase.commands import CreateCommand, PackageCommand, RunCommand @@ -131,7 +130,7 @@ def run_app( self, app: AppConfig, test_mode: bool, - passthrough: List[str], + passthrough: list[str], **kwargs, ): """Start the application. diff --git a/tests/commands/base/test_properties.py b/tests/commands/base/test_properties.py index 398f19075..4514dcb8e 100644 --- a/tests/commands/base/test_properties.py +++ b/tests/commands/base/test_properties.py @@ -2,7 +2,7 @@ def test_briefcase_required_python_version(base_command): - assert base_command.briefcase_required_python_version == (3, 8) + assert base_command.briefcase_required_python_version == (3, 9) def test_bundle_path(base_command, my_app, tmp_path): diff --git a/tests/integrations/base/test_tool_registry.py b/tests/integrations/base/test_tool_registry.py index 4fbe4e930..4dbb13186 100644 --- a/tests/integrations/base/test_tool_registry.py +++ b/tests/integrations/base/test_tool_registry.py @@ -1,7 +1,6 @@ import inspect import pkgutil import sys -from typing import Dict, Set, Type import pytest @@ -9,7 +8,7 @@ from briefcase.integrations.base import ManagedTool, Tool, tool_registry -def integrations_modules() -> Set[str]: +def integrations_modules() -> set[str]: """All modules in ``briefcase.integrations`` irrespective of whether they are defined in ``briefcase.integrations.__all__``""" return { @@ -19,7 +18,7 @@ def integrations_modules() -> Set[str]: } -def tools_for_module(tool_module_name: str) -> Dict[str, Type[Tool]]: +def tools_for_module(tool_module_name: str) -> dict[str, type[Tool]]: """Return classes that subclass Tool in a module in ``briefcase.integrations``, e.g. {"android_sdk": AndroidSDK}.""" return dict( @@ -27,15 +26,15 @@ def tools_for_module(tool_module_name: str) -> Dict[str, Type[Tool]]: sys.modules[f"briefcase.integrations.{tool_module_name}"], lambda klass: ( inspect.isclass(klass) + and not inspect.isabstract(klass) and issubclass(klass, (Tool, ManagedTool)) - and klass not in {Tool, ManagedTool} ), ) ) @pytest.fixture -def all_defined_tools() -> Set[Type[Tool]]: +def all_defined_tools() -> set[type[Tool]]: """All classes under ``briefcase.integrations`` that subclass Tool.""" return { tool diff --git a/tests/platforms/linux/system/test_mixin__verify_python.py b/tests/platforms/linux/system/test_mixin__verify_python.py index bc9954085..01b672713 100644 --- a/tests/platforms/linux/system/test_mixin__verify_python.py +++ b/tests/platforms/linux/system/test_mixin__verify_python.py @@ -105,7 +105,7 @@ def test_target_too_old(create_command, first_app_config): with pytest.raises( BriefcaseCommandError, match=r"The system python3 version provided by somevendor:surprising " - r"is 3\.7\.16; Briefcase requires a minimum Python3 version of 3\.8\.", + r"is 3\.7\.16; Briefcase requires a minimum Python3 version of 3\.9\.", ): create_command.verify_python(first_app_config) diff --git a/tests/platforms/macOS/app/conftest.py b/tests/platforms/macOS/app/conftest.py index d29df38a6..c627c6bde 100644 --- a/tests/platforms/macOS/app/conftest.py +++ b/tests/platforms/macOS/app/conftest.py @@ -31,6 +31,9 @@ def first_app_templated(first_app_config, tmp_path): create_file( tmp_path / "base_path/build/first-app/macos/app/briefcase.toml", """ +[briefcase] +target_version = "0.3.20" + [paths] app_packages_path="First App.app/Contents/Resources/app_packages" support_path="First App.app/Contents/Frameworks" diff --git a/tox.ini b/tox.ini index 9e5976f46..00acabf0f 100644 --- a/tox.ini +++ b/tox.ini @@ -16,18 +16,17 @@ extend-ignore = E203, [tox] -envlist = towncrier-check,docs-lint,pre-commit,py{38,39,310,311,312,313}-cov,coverage +envlist = towncrier-check,docs-lint,pre-commit,py{39,310,311,312,313}-cov,coverage labels = - test = py{38,39,310,311,312,313}-cov,coverage - test38 = py38-cov,coverage38 + test = py{39,310,311,312,313}-cov,coverage test39 = py39-cov,coverage39 test310 = py310-cov,coverage310 test311 = py311-cov,coverage311 test312 = py312-cov,coverage312 test313 = py313-cov,coverage313 - test-fast = py{38,39,310,311,312,313}-fast - test-platform = py{38,39,310,311,312,313}-cov,coverage-platform - ci = towncrier-check,docs-lint,pre-commit,py{38,39,310,311,312,313}-cov,coverage-platform + test-fast = py{39,310,311,312,313}-fast + test-platform = py{39,310,311,312,313}-cov,coverage-platform + ci = towncrier-check,docs-lint,pre-commit,py{39,310,311,312,313}-cov,coverage-platform skip_missing_interpreters = True [testenv:pre-commit] @@ -36,7 +35,7 @@ wheel_build_env = .pkg extras = dev commands = pre-commit run --all-files --show-diff-on-failure --color=always -[testenv:py{,38,39,310,311,312,313}{,-fast,-cov}] +[testenv:py{,39,310,311,312,313}{,-fast,-cov}] package = wheel wheel_build_env = .pkg depends: pre-commit @@ -50,15 +49,14 @@ commands = cov : python -X warn_default_encoding -m coverage run -m pytest {posargs:-vv --color yes} fast : python -m pytest {posargs:-vv --color yes -n auto} -[testenv:coverage{,38,39,310,311,312,313}{,-ci}{,-platform,-platform-linux,-platform-macos,-platform-windows,-project}{,-keep}{,-html}] +[testenv:coverage{,39,310,311,312,313}{,-ci}{,-platform,-platform-linux,-platform-macos,-platform-windows,-project}{,-keep}{,-html}] package = wheel wheel_build_env = .pkg -depends = pre-commit,py{,38,39,310,311,312,313}{,-cov} +depends = pre-commit,py{,39,310,311,312,313}{,-cov} # by default, coverage should run on oldest supported Python for testing platform coverage. # however, coverage for a particular Python version should match the version used for pytest. base_python = - coverage: py38,py39,py310,py311,py312,py313 - coverage38: py38 + coverage: py39,py310,py311,py312,py313 coverage39: py39 coverage310: py310 coverage311: py311