Skip to content

Commit b54bba6

Browse files
committed
🔥 Drop EOL Python 3.7/3.8/3.9 support
Bump python_requires to >=3.10.0, drop the matching classifiers and CI matrix entries. Align MIN_PYTHON_MINOR_VERSION to 10 and drop the now-unreachable Python 2 branch in check_python_version(). Remove the Python 2 compatibility shims anchored to the pre-3.10 floor (urlparse import, load_source Py3.3/3.4 fallback, FileNotFoundError shims, virtualenv branch, to_unicode wrapper, time.monotonic wrapper, path_type TypeError fallback, add_parser argparse aliases wrapper) and the permanently-inert Python 2 test markers. Drop the `from __future__ import annotations` workaround in the msgspec recipe (PEP 585 generics are native since 3.9) and the PEP 508 `python_version<"3.3"` / `<"3.5"` markers in the ifaddr and zeroconf recipes (never satisfied under the new floor). Modernize the remaining `typing.List` annotation to PEP 585 builtin generics in scipy/wrapper.py. The aiohttp recipe is left untouched on purpose: editing it would trigger the rebuild_updated_recipes CI, which fails because aiohttp 3.8.3's pregenerated Cython C is incompatible with Python 3.14 hostpython -- fixing that needs a recipe bump and is out of scope here.
1 parent 5f5a46d commit b54bba6

13 files changed

Lines changed: 50 additions & 154 deletions

File tree

.github/workflows/push.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
strategy:
5555
fail-fast: false
5656
matrix:
57-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
57+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
5858
os: [ubuntu-latest, macos-latest]
5959
steps:
6060
- name: Checkout python-for-android

pythonforandroid/pythonpackage.py

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535

3636
import functools
37-
from io import open # needed for python 2
3837
import os
3938
import shutil
4039
import subprocess
@@ -153,17 +152,13 @@ def binary_is_usable(python_bin):
153152
return
154153
# We should check file not found anyway trying to run it,
155154
# since it might be a dead symlink:
156-
try:
157-
filenotfounderror = FileNotFoundError
158-
except NameError: # Python 2
159-
filenotfounderror = OSError
160155
try:
161156
# Run it and see if version output works with no error:
162157
subprocess.check_output([
163158
os.path.join(path, python_bin), "--version"
164159
], stderr=subprocess.STDOUT)
165160
return True
166-
except (subprocess.CalledProcessError, filenotfounderror):
161+
except (subprocess.CalledProcessError, FileNotFoundError):
167162
return False
168163

169164
python_name = "python" + sys.version
@@ -271,21 +266,10 @@ def get_package_as_folder(dependency):
271266
try:
272267
# Create a venv to install into:
273268
try:
274-
if int(sys.version.partition(".")[0]) < 3:
275-
# Python 2.x has no venv.
276-
subprocess.check_output([
277-
sys.executable, # no venv conflict possible,
278-
# -> no need to use system python
279-
"-m", "virtualenv",
280-
"--python=" + _get_system_python_executable(),
281-
os.path.join(venv_parent, 'venv')
282-
], cwd=venv_parent)
283-
else:
284-
# On modern Python 3, use venv.
285-
subprocess.check_output([
286-
_get_system_python_executable(), "-m", "venv",
287-
os.path.join(venv_parent, 'venv')
288-
], cwd=venv_parent)
269+
subprocess.check_output([
270+
_get_system_python_executable(), "-m", "venv",
271+
os.path.join(venv_parent, 'venv')
272+
], cwd=venv_parent)
289273
except subprocess.CalledProcessError as e:
290274
output = e.output.decode('utf-8', 'replace')
291275
raise ValueError(
@@ -295,16 +279,12 @@ def get_package_as_folder(dependency):
295279
venv_path = os.path.join(venv_parent, "venv")
296280

297281
# Update pip and wheel in venv for latest feature support:
298-
try:
299-
filenotfounderror = FileNotFoundError
300-
except NameError: # Python 2.
301-
filenotfounderror = OSError
302282
try:
303283
subprocess.check_output([
304284
os.path.join(venv_path, "bin", "pip"),
305285
"install", "-U", "pip", "wheel",
306286
])
307-
except filenotfounderror:
287+
except FileNotFoundError:
308288
raise RuntimeError(
309289
"venv appears to be missing pip. "
310290
"did we fail to use a proper system python??\n"
@@ -322,12 +302,7 @@ def get_package_as_folder(dependency):
322302
with open(os.path.join(venv_path, "requirements.txt"),
323303
"w", encoding="utf-8"
324304
) as f:
325-
def to_unicode(s): # Needed for Python 2.
326-
try:
327-
return s.decode("utf-8")
328-
except AttributeError:
329-
return s
330-
f.write(to_unicode(transform_dep_for_pip(dependency)))
305+
f.write(transform_dep_for_pip(dependency))
331306
try:
332307
subprocess.check_output(
333308
[
@@ -451,10 +426,7 @@ def _extract_metainfo_files_from_package_unsafe(
451426
# distribution, and others get_package_as_folder() may support
452427
# in the future.
453428
with open(os.path.join(output_path, "metadata_source"), "w") as f:
454-
try:
455-
f.write(path_type)
456-
except TypeError: # in python 2 path_type may be str/bytes:
457-
f.write(path_type.decode("utf-8", "replace"))
429+
f.write(path_type)
458430

459431
# Copy the metadata file:
460432
shutil.copyfile(metadata_path, os.path.join(output_path, "METADATA"))
@@ -585,19 +557,14 @@ def _extract_info_from_package(dependency,
585557

586558
def get_package_name(dependency,
587559
use_cache=True):
588-
def timestamp():
589-
try:
590-
return time.monotonic()
591-
except AttributeError:
592-
return time.time() # Python 2.
593560
try:
594561
value = package_name_cache[dependency]
595-
if value[0] + 600.0 > timestamp() and use_cache:
562+
if value[0] + 600.0 > time.monotonic() and use_cache:
596563
return value[1]
597564
except KeyError:
598565
pass
599566
result = _extract_info_from_package(dependency, extract_type="name")
600-
package_name_cache[dependency] = (timestamp(), result)
567+
package_name_cache[dependency] = (time.monotonic(), result)
601568
return result
602569

603570

pythonforandroid/recipe.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@
1616
from packaging.version import Version
1717
from multiprocessing import cpu_count
1818
import time
19-
try:
20-
from urlparse import urlparse
21-
except ImportError:
22-
from urllib.parse import urlparse
19+
from urllib.parse import urlparse
2320

2421
import packaging.version
2522

pythonforandroid/recipes/ifaddr/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class IfaddrRecipe(PythonRecipe):
55
name = 'ifaddr'
66
version = '0.1.7'
77
url = 'https://pypi.python.org/packages/source/i/ifaddr/ifaddr-{version}.tar.gz'
8-
depends = ['setuptools', 'ifaddrs', 'ipaddress;python_version<"3.3"']
8+
depends = ['setuptools', 'ifaddrs']
99
call_hostpython_via_targetpython = False
1010
patches = ["getifaddrs.patch"]
1111

pythonforandroid/recipes/msgspec/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import annotations
2-
31
from typing import Any
42

53
from pythonforandroid.archs import Arch

pythonforandroid/recipes/scipy/wrapper.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
import subprocess
77
import sys
8-
import typing
98

109
"""
1110
This wrapper is used to ignore or replace some unsupported flags for flang-new.
@@ -30,7 +29,7 @@
3029
COMPLIER_PATH = "@COMPILER@"
3130

3231

33-
def main(argv: typing.List[str]):
32+
def main(argv: list[str]):
3433
cwd = os.getcwd()
3534
argv_new = []
3635
i = 0

pythonforandroid/recipes/zeroconf/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class ZeroconfRecipe(PythonRecipe):
55
name = 'zeroconf'
66
version = '0.24.5'
77
url = 'https://pypi.python.org/packages/source/z/zeroconf/zeroconf-{version}.tar.gz'
8-
depends = ['setuptools', 'ifaddr', 'typing;python_version<"3.5"']
8+
depends = ['setuptools', 'ifaddr']
99
call_hostpython_via_targetpython = False
1010

1111

pythonforandroid/recommendations.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,10 @@ def check_ndk_api(ndk_api, android_api):
186186

187187

188188
MIN_PYTHON_MAJOR_VERSION = 3
189-
MIN_PYTHON_MINOR_VERSION = 6
189+
MIN_PYTHON_MINOR_VERSION = 10
190190
MIN_PYTHON_VERSION = packaging.version.Version(
191191
f"{MIN_PYTHON_MAJOR_VERSION}.{MIN_PYTHON_MINOR_VERSION}"
192192
)
193-
PY2_ERROR_TEXT = (
194-
'python-for-android no longer supports running under Python 2. Either upgrade to '
195-
'Python {min_version} or higher (recommended), or revert to python-for-android 2019.07.08.'
196-
).format(min_version=MIN_PYTHON_VERSION)
197193

198194
PY_VERSION_ERROR_TEXT = (
199195
'Your Python version {user_major}.{user_minor} is not supported by python-for-android, '
@@ -205,11 +201,6 @@ def check_ndk_api(ndk_api, android_api):
205201

206202

207203
def check_python_version():
208-
# Python 2 special cased because it's a major transition. In the
209-
# future the major or minor versions can increment more quietly.
210-
if sys.version_info.major == 2:
211-
raise BuildInterruptingException(PY2_ERROR_TEXT)
212-
213204
if (
214205
sys.version_info.major < MIN_PYTHON_MAJOR_VERSION or
215206
sys.version_info.minor < MIN_PYTHON_MINOR_VERSION

pythonforandroid/toolchain.py

Lines changed: 28 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -393,56 +393,45 @@ def __init__(self):
393393
subparsers = parser.add_subparsers(dest='subparser_name',
394394
help='The command to run')
395395

396-
def add_parser(subparsers, *args, **kwargs):
397-
"""
398-
argparse in python2 doesn't support the aliases option,
399-
so we just don't provide the aliases there.
400-
"""
401-
if 'aliases' in kwargs and sys.version_info.major < 3:
402-
kwargs.pop('aliases')
403-
return subparsers.add_parser(*args, **kwargs)
404-
405-
add_parser(
406-
subparsers,
396+
subparsers.add_parser(
407397
'recommendations',
408398
parents=[generic_parser],
409399
help='List recommended p4a dependencies')
410-
parser_recipes = add_parser(
411-
subparsers,
400+
parser_recipes = subparsers.add_parser(
412401
'recipes',
413402
parents=[generic_parser],
414403
help='List the available recipes')
415404
parser_recipes.add_argument(
416405
"--compact",
417406
action="store_true", default=False,
418407
help="Produce a compact list suitable for scripting")
419-
add_parser(
420-
subparsers, 'bootstraps',
408+
subparsers.add_parser(
409+
'bootstraps',
421410
help='List the available bootstraps',
422411
parents=[generic_parser])
423-
add_parser(
424-
subparsers, 'clean_all',
412+
subparsers.add_parser(
413+
'clean_all',
425414
aliases=['clean-all'],
426415
help='Delete all builds, dists and caches',
427416
parents=[generic_parser])
428-
add_parser(
429-
subparsers, 'clean_dists',
417+
subparsers.add_parser(
418+
'clean_dists',
430419
aliases=['clean-dists'],
431420
help='Delete all dists',
432421
parents=[generic_parser])
433-
add_parser(
434-
subparsers, 'clean_bootstrap_builds',
422+
subparsers.add_parser(
423+
'clean_bootstrap_builds',
435424
aliases=['clean-bootstrap-builds'],
436425
help='Delete all bootstrap builds',
437426
parents=[generic_parser])
438-
add_parser(
439-
subparsers, 'clean_builds',
427+
subparsers.add_parser(
428+
'clean_builds',
440429
aliases=['clean-builds'],
441430
help='Delete all builds',
442431
parents=[generic_parser])
443432

444-
parser_clean = add_parser(
445-
subparsers, 'clean',
433+
parser_clean = subparsers.add_parser(
434+
'clean',
446435
help='Delete build components.',
447436
parents=[generic_parser])
448437
parser_clean.add_argument(
@@ -451,8 +440,7 @@ def add_parser(subparsers, *args, **kwargs):
451440
'number of arguments from "all", "builds", "dists", '
452441
'"distributions", "bootstrap_builds", "downloads".'))
453442

454-
parser_clean_recipe_build = add_parser(
455-
subparsers,
443+
parser_clean_recipe_build = subparsers.add_parser(
456444
'clean_recipe_build', aliases=['clean-recipe-build'],
457445
help=('Delete the build components of the given recipe. '
458446
'By default this will also delete built dists'),
@@ -465,8 +453,7 @@ def add_parser(subparsers, *args, **kwargs):
465453
action='store_true',
466454
help='If passed, do not delete existing dists')
467455

468-
parser_clean_download_cache = add_parser(
469-
subparsers,
456+
parser_clean_download_cache = subparsers.add_parser(
470457
'clean_download_cache', aliases=['clean-download-cache'],
471458
help='Delete cached downloads for requirement builds',
472459
parents=[generic_parser])
@@ -476,8 +463,7 @@ def add_parser(subparsers, *args, **kwargs):
476463
help='The recipes to clean (space-separated). If no recipe name is'
477464
' provided, the entire cache is cleared.')
478465

479-
parser_export_dist = add_parser(
480-
subparsers,
466+
parser_export_dist = subparsers.add_parser(
481467
'export_dist', aliases=['export-dist'],
482468
help='Copy the named dist to the given path',
483469
parents=[generic_parser])
@@ -547,57 +533,46 @@ def add_parser(subparsers, *args, **kwargs):
547533
'--signkeypw', dest='signkeypw', action='store', default=None,
548534
help='Password for key alias')
549535

550-
add_parser(
551-
subparsers,
536+
subparsers.add_parser(
552537
'aar', help='Build an AAR',
553538
parents=[parser_packaging])
554539

555-
add_parser(
556-
subparsers,
540+
subparsers.add_parser(
557541
'apk', help='Build an APK',
558542
parents=[parser_packaging])
559543

560-
add_parser(
561-
subparsers,
544+
subparsers.add_parser(
562545
'aab', help='Build an AAB',
563546
parents=[parser_packaging])
564547

565-
add_parser(
566-
subparsers,
548+
subparsers.add_parser(
567549
'create', help='Compile a set of requirements into a dist',
568550
parents=[generic_parser])
569-
add_parser(
570-
subparsers,
551+
subparsers.add_parser(
571552
'archs', help='List the available target architectures',
572553
parents=[generic_parser])
573-
add_parser(
574-
subparsers,
554+
subparsers.add_parser(
575555
'distributions', aliases=['dists'],
576556
help='List the currently available (compiled) dists',
577557
parents=[generic_parser])
578-
add_parser(
579-
subparsers,
558+
subparsers.add_parser(
580559
'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
581560
parents=[generic_parser])
582561

583-
parser_sdk_tools = add_parser(
584-
subparsers,
562+
parser_sdk_tools = subparsers.add_parser(
585563
'sdk_tools', aliases=['sdk-tools'],
586564
help='Run the given binary from the SDK tools dis',
587565
parents=[generic_parser])
588566
parser_sdk_tools.add_argument(
589567
'tool', help='The binary tool name to run')
590568

591-
add_parser(
592-
subparsers,
569+
subparsers.add_parser(
593570
'adb', help='Run adb from the given SDK',
594571
parents=[generic_parser])
595-
add_parser(
596-
subparsers,
572+
subparsers.add_parser(
597573
'logcat', help='Run logcat from the given SDK',
598574
parents=[generic_parser])
599-
add_parser(
600-
subparsers,
575+
subparsers.add_parser(
601576
'build_status', aliases=['build-status'],
602577
help='Print some debug information about current built components',
603578
parents=[generic_parser])

0 commit comments

Comments
 (0)