general: Remove Python 2.7 support.

Python 2.7 has been EOL since January 2020.

Ubuntu oldoldlts (Focal Fossa, 20.04) has Python 3.8.
Debian oldoldstable (Buster, from 2019) has Python 3.7.
RHEL 8 (from 2019) has Python 3.6.

It's easier than ever to install a modern Python using uv.

Given this, it seems like a fine idea to drop Python 2.7 support.

Even though the build is not tested on Python as old as 3.3, I
left comments stating that "3.3+" is the baseline Python version.
However, it might make sense to bump this to e.g., 3.10, the oldest
Python 3 version used during CI. Or, using uv or another method
actually test on the oldest Python interpreter that is desirable
to support (uv goes back to Python 3.7 easily; in October 2025, the
oldest supported Python interpreter version will be 3.10)

Signed-off-by: Jeff Epler <jepler@gmail.com>
This commit is contained in:
Jeff Epler 2025-08-08 10:09:13 -05:00
parent 593ae04eeb
commit d2817bb168
12 changed files with 35 additions and 93 deletions

View file

@ -15,7 +15,7 @@ concurrency:
jobs: jobs:
test: test:
runs-on: ubuntu-22.04 # use 22.04 to get python2 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install packages - name: Install packages

View file

@ -121,7 +121,7 @@ jobs:
run: tests/run-tests.py --print-failures run: tests/run-tests.py --print-failures
nanbox: nanbox:
runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install packages - name: Install packages
@ -135,7 +135,7 @@ jobs:
run: tests/run-tests.py --print-failures run: tests/run-tests.py --print-failures
longlong: longlong:
runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386 runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install packages - name: Install packages

View file

@ -80,9 +80,8 @@ This repository contains the following components:
- [examples/](examples/) -- a few example Python scripts. - [examples/](examples/) -- a few example Python scripts.
"make" is used to build the components, or "gmake" on BSD-based systems. "make" is used to build the components, or "gmake" on BSD-based systems.
You will also need bash, gcc, and Python 3.3+ available as the command `python3` You will also need bash, gcc, and Python 3.3+ available as the command `python3`.
(if your system only has Python 2.7 then invoke make with the additional option Some ports (rp2 and esp32) additionally use CMake.
`PYTHON=python2`). Some ports (rp2 and esp32) additionally use CMake.
Supported platforms & architectures Supported platforms & architectures
----------------------------------- -----------------------------------

View file

@ -106,7 +106,7 @@ See the `ARM GCC
toolchain <https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads>`_ toolchain <https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads>`_
for the latest details. for the latest details.
Python is also required. Python 2 is supported for now, but we recommend using Python 3. Python 3 is also required.
Check that you have Python available on your system: Check that you have Python available on your system:
.. code-block:: bash .. code-block:: bash

View file

@ -105,7 +105,7 @@ def compute_pll2(hse, sys, relax_pll48):
# VCO_OUT must be between 192MHz and 432MHz # VCO_OUT must be between 192MHz and 432MHz
if sys * P not in mcu.range_vco_out: if sys * P not in mcu.range_vco_out:
continue continue
NbyM = float(sys * P) / hse # float for Python 2 NbyM = sys * P / hse
# scan M # scan M
M_min = mcu.range_n[0] // int(round(NbyM)) # starting value M_min = mcu.range_n[0] // int(round(NbyM)) # starting value
while mcu.range_vco_in[-1] * M_min < hse: while mcu.range_vco_in[-1] * M_min < hse:
@ -121,7 +121,7 @@ def compute_pll2(hse, sys, relax_pll48):
# N is restricted # N is restricted
if N not in mcu.range_n: if N not in mcu.range_n:
continue continue
Q = float(sys * P) / 48 # float for Python 2 Q = sys * P / 48
# Q must be an integer in a set range # Q must be an integer in a set range
if close_int(Q) and round(Q) in mcu.range_q: if close_int(Q) and round(Q) in mcu.range_q:
# found valid values # found valid values
@ -142,7 +142,6 @@ def compute_pll2(hse, sys, relax_pll48):
def compute_derived(hse, pll): def compute_derived(hse, pll):
hse = float(hse) # float for Python 2
M, N, P, Q = pll M, N, P, Q = pll
vco_in = hse / M vco_in = hse / M
vco_out = hse * N / M vco_out = hse * N / M

View file

@ -9,25 +9,13 @@ from __future__ import print_function
import argparse import argparse
import re import re
# Python 2/3 compatibility
import platform
if platform.python_version_tuple()[0] == "2": def convert_bytes_to_str(b):
try:
def convert_bytes_to_str(b): return str(b, "utf8")
return b except ValueError:
# some files have invalid utf8 bytes, so filter them out
elif platform.python_version_tuple()[0] == "3": return "".join(chr(l) for l in b if l <= 126)
def convert_bytes_to_str(b):
try:
return str(b, "utf8")
except ValueError:
# some files have invalid utf8 bytes, so filter them out
return "".join(chr(l) for l in b if l <= 126)
# end compatibility code
# given a list of (name,regex) pairs, find the first one that matches the given line # given a list of (name,regex) pairs, find the first one that matches the given line

View file

@ -1,7 +1,7 @@
""" """
Process raw qstr file and output qstr data with length, hash and data bytes. Process raw qstr file and output qstr data with length, hash and data bytes.
This script works with Python 2.6, 2.7, 3.3 and 3.4. This script works with Python 3.3+.
""" """
from __future__ import print_function from __future__ import print_function
@ -9,13 +9,7 @@ from __future__ import print_function
import re import re
import sys import sys
# Python 2/3/MicroPython compatibility: bytes_cons = bytes
# - iterating through bytes is different
# - codepoint2name from html.entities is hard-coded
if sys.version_info[0] == 2:
bytes_cons = lambda val, enc=None: bytearray(val)
elif sys.version_info[0] == 3: # Also handles MicroPython
bytes_cons = bytes
# fmt: off # fmt: off
codepoint2name = { codepoint2name = {
@ -57,7 +51,6 @@ codepoint2name = {
253: "yacute", 165: "yen", 255: "yuml", 950: "zeta", 8205: "zwj", 8204: "zwnj" 253: "yacute", 165: "yen", 255: "yuml", 950: "zeta", 8205: "zwj", 8204: "zwnj"
} }
# fmt: on # fmt: on
# end compatibility code
codepoint2name[ord("-")] = "hyphen" codepoint2name[ord("-")] = "hyphen"

View file

@ -2,7 +2,7 @@
This script processes the output from the C preprocessor and extracts all This script processes the output from the C preprocessor and extracts all
qstr. Each qstr is transformed into a qstr definition of the form 'Q(...)'. qstr. Each qstr is transformed into a qstr definition of the form 'Q(...)'.
This script works with Python 2.6, 2.7, 3.3 and 3.4. This script works with Python 3.3+.
""" """
from __future__ import print_function from __future__ import print_function

View file

@ -1,7 +1,7 @@
""" """
Generate header file with macros defining MicroPython version info. Generate header file with macros defining MicroPython version info.
This script works with Python 2.6, 2.7, 3.3 and 3.4. This script works with Python 3.3+.
""" """
from __future__ import print_function from __future__ import print_function
@ -22,12 +22,6 @@ import subprocess
# "vX.Y.Z-preview.N.gHASH.dirty" -- building at any subsequent commit in the cycle # "vX.Y.Z-preview.N.gHASH.dirty" -- building at any subsequent commit in the cycle
# with local changes # with local changes
def get_version_info_from_git(repo_path): def get_version_info_from_git(repo_path):
# Python 2.6 doesn't have check_output, so check for that
try:
subprocess.check_output
except AttributeError:
return None
# Note: git describe doesn't work if no tag is available # Note: git describe doesn't work if no tag is available
try: try:
git_tag = subprocess.check_output( git_tag = subprocess.check_output(
@ -48,12 +42,6 @@ def get_version_info_from_git(repo_path):
def get_hash_from_git(repo_path): def get_hash_from_git(repo_path):
# Python 2.6 doesn't have check_output, so check for that.
try:
subprocess.check_output
except AttributeError:
return None
try: try:
return subprocess.check_output( return subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"], ["git", "rev-parse", "--short", "HEAD"],

View file

@ -127,15 +127,12 @@ function ci_code_size_build {
function ci_mpy_format_setup { function ci_mpy_format_setup {
sudo apt-get update sudo apt-get update
sudo apt-get install python2.7
sudo pip3 install pyelftools sudo pip3 install pyelftools
python2.7 --version
python3 --version python3 --version
} }
function ci_mpy_format_test { function ci_mpy_format_test {
# Test mpy-tool.py dump feature on bytecode # Test mpy-tool.py dump feature on bytecode
python2.7 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy
python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy python3 ./tools/mpy-tool.py -xd tests/frozen/frozentest.mpy
# Build MicroPython # Build MicroPython
@ -666,12 +663,11 @@ function ci_unix_coverage_run_native_mpy_tests {
function ci_unix_32bit_setup { function ci_unix_32bit_setup {
sudo dpkg --add-architecture i386 sudo dpkg --add-architecture i386
sudo apt-get update sudo apt-get update
sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386 python2.7 sudo apt-get install gcc-multilib g++-multilib libffi-dev:i386
sudo pip3 install setuptools sudo pip3 install setuptools
sudo pip3 install pyelftools sudo pip3 install pyelftools
sudo pip3 install ar sudo pip3 install ar
gcc --version gcc --version
python2.7 --version
python3 --version python3 --version
} }
@ -689,13 +685,12 @@ function ci_unix_coverage_32bit_run_native_mpy_tests {
} }
function ci_unix_nanbox_build { function ci_unix_nanbox_build {
# Use Python 2 to check that it can run the build scripts ci_unix_build_helper VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1"
ci_unix_build_helper PYTHON=python2.7 VARIANT=nanbox CFLAGS_EXTRA="-DMICROPY_PY_MATH_CONSTANTS=1"
ci_unix_build_ffi_lib_helper gcc -m32 ci_unix_build_ffi_lib_helper gcc -m32
} }
function ci_unix_nanbox_run_tests { function ci_unix_nanbox_run_tests {
ci_unix_run_tests_full_no_native_helper nanbox PYTHON=python2.7 ci_unix_run_tests_full_no_native_helper nanbox
} }
function ci_unix_longlong_build { function ci_unix_longlong_build {

View file

@ -24,40 +24,20 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
# Python 2/3/MicroPython compatibility code
from __future__ import print_function
import sys
if sys.version_info[0] == 2:
from binascii import hexlify as hexlify_py2
str_cons = lambda val, enc=None: str(val)
bytes_cons = lambda val, enc=None: bytearray(val)
is_str_type = lambda o: isinstance(o, str)
is_bytes_type = lambda o: type(o) is bytearray
is_int_type = lambda o: isinstance(o, int) or isinstance(o, long) # noqa: F821
def hexlify_to_str(b):
x = hexlify_py2(b)
return ":".join(x[i : i + 2] for i in range(0, len(x), 2))
elif sys.version_info[0] == 3: # Also handles MicroPython
from binascii import hexlify
str_cons = str
bytes_cons = bytes
is_str_type = lambda o: isinstance(o, str)
is_bytes_type = lambda o: isinstance(o, bytes)
is_int_type = lambda o: isinstance(o, int)
def hexlify_to_str(b):
return str(hexlify(b, ":"), "ascii")
# end compatibility code
import sys
import struct import struct
import sys
from binascii import hexlify
str_cons = str
bytes_cons = bytes
is_str_type = lambda o: isinstance(o, str)
is_bytes_type = lambda o: isinstance(o, bytes)
is_int_type = lambda o: isinstance(o, int)
def hexlify_to_str(b):
return str(hexlify(b, ":"), "ascii")
sys.path.append(sys.path[0] + "/../py") sys.path.append(sys.path[0] + "/../py")
import makeqstrdata as qstrutil import makeqstrdata as qstrutil

View file

@ -77,7 +77,7 @@ __DFU_INTERFACE = 0
# Python 3 deprecated getargspec in favour of getfullargspec, but # Python 3 deprecated getargspec in favour of getfullargspec, but
# Python 2 doesn't have the latter, so detect which one to use # Python 2 doesn't have the latter, so detect which one to use
getargspec = getattr(inspect, "getfullargspec", getattr(inspect, "getargspec", None)) getargspec = inspect.getfullargspec
if "length" in getargspec(usb.util.get_string).args: if "length" in getargspec(usb.util.get_string).args:
# PyUSB 1.0.0.b1 has the length argument # PyUSB 1.0.0.b1 has the length argument