Skip to content

Commit

Permalink
Drop support for Python 3.7.
Browse files Browse the repository at this point in the history
3.7 EOLs on 2023-06-27 (in two days time). Dropping it will bring CI/CD
back below the 16 concurrent jobs limit so that it finishes in 2 hours
instead of 4.
  • Loading branch information
bwoodsend committed Jun 25, 2023
1 parent 58b8235 commit 7ab1af5
Show file tree
Hide file tree
Showing 22 changed files with 53 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Expand Up @@ -24,7 +24,7 @@ Welcome to the PyInstaller issue tracker! Before creating an issue, please heed
### Context information (for bug reports)

* Output of `pyinstaller --version`: ```(paste here)```
* Version of Python: <!-- e.g. 3.7 -->
* Version of Python: <!-- e.g. 3.11 -->
* Platform: <!-- e.g GNU/Linux (distribution), Windows (language settings), OS X, FreeBSD -->
* How you installed Python: <!-- e.g. python.org/downloads, conda, brew, pyenv, apt, Windows store -->
* Did you also try this on another platform? Does it work there?
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -30,7 +30,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12-dev']
python-version: [3.8, 3.9, '3.10', '3.11', '3.12-dev']
os: ['windows-latest', 'ubuntu-20.04', 'macos-11']
fail-fast: false
steps:
Expand Down
5 changes: 1 addition & 4 deletions PyInstaller/__init__.py
Expand Up @@ -42,10 +42,7 @@
sys.stderr.write('WARN: failed to parse git revision')
else:
# PyInstaller was installed by `python setup.py install'.
if compat.is_py38:
from importlib.metadata import version
else:
from importlib_metadata import version
from importlib.metadata import version
__version__ = version('PyInstaller')
# Default values of paths where to put files created by PyInstaller. If changing these, do not forget to update the
# help text for corresponding command-line options, defined in build_main.
Expand Down
24 changes: 11 additions & 13 deletions PyInstaller/building/build_main.py
Expand Up @@ -147,19 +147,18 @@ def find_binary_dependencies(binaries, binding_redirects, import_packages):
extra_libdirs.append(compat.base_prefix)

# On Windows, packages' initialization code might register additional DLL search paths, either by modifying the
# `PATH` environment variable, or (on pythonn >= 3.8) by calling `os.add_dll_directory`. Therefore, we import
# `PATH` environment variable, or by calling `os.add_dll_directory`. Therefore, we import
# all collected packages, and track changes made to the environment.
if compat.is_win:
# Track changes made via `os.add_dll_directory`.
if compat.is_py38:
added_dll_directories = []
_orig_add_dll_directory = os.add_dll_directory
added_dll_directories = []
_orig_add_dll_directory = os.add_dll_directory

def _pyi_add_dll_directory(path):
added_dll_directories.append(path)
return _orig_add_dll_directory(path)
def _pyi_add_dll_directory(path):
added_dll_directories.append(path)
return _orig_add_dll_directory(path)

os.add_dll_directory = _pyi_add_dll_directory
os.add_dll_directory = _pyi_add_dll_directory

# Import collected packages to set up environment.
for package in import_packages:
Expand All @@ -170,12 +169,11 @@ def _pyi_add_dll_directory(path):

# Process extra search paths...
# Directories added via os.add_dll_directory() calls.
if compat.is_py38:
logger.info("Extra DLL search directories (AddDllDirectory): %r", added_dll_directories)
extra_libdirs += added_dll_directories
logger.info("Extra DLL search directories (AddDllDirectory): %r", added_dll_directories)
extra_libdirs += added_dll_directories

# Restore original function
os.add_dll_directory = _orig_add_dll_directory
# Restore original function
os.add_dll_directory = _orig_add_dll_directory

# Directories set via PATH environment variable.
# NOTE: PATH might contain empty entries that need to be filtered out.
Expand Down
15 changes: 1 addition & 14 deletions PyInstaller/building/utils.py
Expand Up @@ -652,20 +652,7 @@ def strip_paths_in_code(co, new_filename=None):
for const_co in co.co_consts
)

if hasattr(co, 'replace'): # is_py38
return co.replace(co_consts=consts, co_filename=new_filename)
elif hasattr(co, 'co_kwonlyargcount'):
# co_kwonlyargcount was added in some version of Python 3
return code_func(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize, co.co_flags, co.co_code, consts,
co.co_names, co.co_varnames, new_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars,
co.co_cellvars
)
else:
return code_func(
co.co_argcount, co.co_nlocals, co.co_stacksize, co.co_flags, co.co_code, consts, co.co_names,
co.co_varnames, new_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars
)
return co.replace(co_consts=consts, co_filename=new_filename)


def _should_include_system_binary(binary_tuple, exceptions):
Expand Down
33 changes: 8 additions & 25 deletions PyInstaller/compat.py
Expand Up @@ -83,56 +83,42 @@
is_macos_11 = is_macos_11_compat or is_macos_11_native # Big Sur or newer

# On different platforms is different file for dynamic python library.
# TODO: When removing support for is_py37, the "m" variants can be
# removed, see <https://docs.python.org/3/whatsnew/3.8.html#build-and-c-api-changes>
_pyver = sys.version_info[:2]
if is_win or is_cygwin:
PYDYLIB_NAMES = {
'python%d%d.dll' % _pyver,
'libpython%d%d.dll' % _pyver,
'libpython%d%dm.dll' % _pyver,
'libpython%d.%d.dll' % _pyver,
'libpython%d.%dm.dll' % _pyver
} # For MSYS2 environment
elif is_darwin:
# libpython%d.%dm.dylib for Conda virtual environment installations
PYDYLIB_NAMES = {
'Python', '.Python',
'Python',
'.Python',
'Python%d' % _pyver[0],
'libpython%d.%d.dylib' % _pyver,
'libpython%d.%dm.dylib' % _pyver
}
elif is_aix:
# Shared libs on AIX may be archives with shared object members, hence the ".a" suffix. However, starting with
# python 2.7.11 libpython?.?.so and Python3 libpython?.?m.so files are produced.
PYDYLIB_NAMES = {
'libpython%d.%d.a' % _pyver,
'libpython%d.%dm.a' % _pyver,
'libpython%d.%d.so' % _pyver,
'libpython%d.%dm.so' % _pyver
}
elif is_freebsd:
PYDYLIB_NAMES = {
'libpython%d.%d.so.1' % _pyver,
'libpython%d.%dm.so.1' % _pyver,
'libpython%d.%d.so.1.0' % _pyver,
'libpython%d.%dm.so.1.0' % _pyver
}
elif is_openbsd:
PYDYLIB_NAMES = {'libpython%d.%d.so.0.0' % _pyver, 'libpython%d.%dm.so.0.0' % _pyver}
PYDYLIB_NAMES = {'libpython%d.%d.so.0.0' % _pyver}
elif is_hpux:
PYDYLIB_NAMES = {'libpython%d.%d.so' % _pyver}
elif is_unix:
# Other *nix platforms.
# Python 2 .so library on Linux is: libpython2.7.so.1.0
# Python 3 .so library on Linux is: libpython3.2mu.so.1.0, libpython3.3m.so.1.0
PYDYLIB_NAMES = {
'libpython%d.%d.so.1.0' % _pyver,
'libpython%d.%dm.so.1.0' % _pyver,
'libpython%d.%dmu.so.1.0' % _pyver,
'libpython%d.%dm.so' % _pyver,
'libpython%d.%d.so' % _pyver
}
# Python 3 .so library on Linux is: libpython3.3.so.1.0
PYDYLIB_NAMES = {'libpython%d.%d.so.1.0' % _pyver, 'libpython%d.%d.so' % _pyver}
else:
raise SystemExit('Your platform is not yet supported. Please define constant PYDYLIB_NAMES for your platform.')

Expand Down Expand Up @@ -745,16 +731,13 @@ def check_requirements():
Fail hard if any requirement is not met.
"""
# Fail hard if Python does not have minimum required version
if sys.version_info < (3, 7):
raise EnvironmentError('PyInstaller requires at Python 3.7 or newer.')
if sys.version_info < (3, 8):
raise EnvironmentError('PyInstaller requires Python 3.8 or newer.')

# There are some old packages which used to be backports of libraries which are now part of the standard library.
# These backports are now unmaintained and contain only an older subset of features leading to obscure errors like
# "enum has not attribute IntFlag" if installed.
if is_py38:
from importlib.metadata import distribution, PackageNotFoundError
else:
from importlib_metadata import distribution, PackageNotFoundError
from importlib.metadata import distribution, PackageNotFoundError

for name in ["enum34", "typing", "pathlib"]:
try:
Expand Down
12 changes: 1 addition & 11 deletions PyInstaller/depend/bytecode.py
Expand Up @@ -91,17 +91,7 @@ def finditer(pattern: Pattern, string: bytes):
# themselves need to be enclosed in another (non-capturing) group. E.g., "(?:(?:_OPCODES_FUNCTION_GLOBAL).)".
# NOTE2: _OPCODES_EXTENDED_ARG2 is an exception, as it is used as a list of opcodes to exclude, i.e.,
# "[^_OPCODES_EXTENDED_ARG2]". Therefore, multiple opcodes are not separated by the OR operator.
if not compat.is_py37:
_OPCODES_EXTENDED_ARG = rb"`EXTENDED_ARG`"
_OPCODES_EXTENDED_ARG2 = _OPCODES_EXTENDED_ARG
_OPCODES_FUNCTION_GLOBAL = rb"`LOAD_NAME`|`LOAD_GLOBAL`|`LOAD_FAST`"
_OPCODES_FUNCTION_LOAD = rb"`LOAD_ATTR`"
_OPCODES_FUNCTION_ARGS = rb"`LOAD_CONST`"
_OPCODES_FUNCTION_CALL = rb"`CALL_FUNCTION`|`CALL_FUNCTION_EX`"

def _cleanup_bytecode_string(bytecode):
return bytecode # Nothing to do here
elif not compat.is_py311:
if not compat.is_py311:
# Python 3.7 introduced two new function-related opcodes, LOAD_METHOD and CALL_METHOD
_OPCODES_EXTENDED_ARG = rb"`EXTENDED_ARG`"
_OPCODES_EXTENDED_ARG2 = _OPCODES_EXTENDED_ARG
Expand Down
8 changes: 3 additions & 5 deletions PyInstaller/hooks/rthooks/pyi_rth_multiprocessing.py
Expand Up @@ -32,11 +32,9 @@ def _freeze_support():
# Look for those flags and the import statement, then `exec()` the code ourselves.

if (
len(sys.argv) >= 2 and sys.argv[-2] == '-c' and sys.argv[-1].startswith((
'from multiprocessing.semaphore_tracker import main', # Py<3.8
'from multiprocessing.resource_tracker import main', # Py>=3.8
'from multiprocessing.forkserver import main'
)) and set(sys.argv[1:-2]) == set(_args_from_interpreter_flags())
len(sys.argv) >= 2 and sys.argv[-2] == '-c' and sys.argv[-1].startswith(
('from multiprocessing.resource_tracker import main', 'from multiprocessing.forkserver import main')
) and set(sys.argv[1:-2]) == set(_args_from_interpreter_flags())
):
exec(sys.argv[-1])
sys.exit()
Expand Down
19 changes: 2 additions & 17 deletions PyInstaller/lib/modulegraph/modulegraph.py
Expand Up @@ -3260,20 +3260,5 @@ def _replace_paths_in_code(self, co):

code_func = type(co)

if hasattr(co, 'replace'): # is_py38
return co.replace(co_consts=tuple(consts),
co_filename=new_filename)
elif hasattr(co, 'co_kwonlyargcount'):
return code_func(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
co.co_stacksize, co.co_flags, co.co_code,
tuple(consts), co.co_names, co.co_varnames,
new_filename, co.co_name, co.co_firstlineno,
co.co_lnotab, co.co_freevars, co.co_cellvars)
else:
return code_func(
co.co_argcount, co.co_nlocals, co.co_stacksize,
co.co_flags, co.co_code, tuple(consts), co.co_names,
co.co_varnames, new_filename, co.co_name,
co.co_firstlineno, co.co_lnotab,
co.co_freevars, co.co_cellvars)
return co.replace(co_consts=tuple(consts),
co_filename=new_filename)
10 changes: 4 additions & 6 deletions PyInstaller/loader/pyimod04_pywin32.py
Expand Up @@ -41,15 +41,13 @@ def install():
# `pywin32_system32` sub-directory instead of the top-level application directory.
sys.path.append(pywin32_system32_path)

# Add the DLL directory to DLL search path using os.add_dll_directory(), if available (python >= 3.8).
# Add the DLL directory to DLL search path using os.add_dll_directory().
# This allows extensions from win32 directory (e.g., win32api, win32crypt) to be loaded on their own without
# importing pywintypes first. The extensions are linked against pywintypes3X.dll.
if hasattr(os, 'add_dll_directory'):
os.add_dll_directory(pywin32_system32_path)
os.add_dll_directory(pywin32_system32_path)

# Add the DLL directory to PATH.
# This is necessary on python 3.7 that lacks `os.add_dll_directory`, and under certain versions of Anaconda python,
# where `os.add_dll_directory` does not work.
# Add the DLL directory to PATH. This is necessary under certain versions of
# Anaconda python, where `os.add_dll_directory` does not work.
path = os.environ.get('PATH', None)
if not path:
path = pywin32_system32_path
Expand Down
6 changes: 1 addition & 5 deletions PyInstaller/utils/hooks/conda.py
Expand Up @@ -40,15 +40,11 @@
import sys
from pathlib import Path
from typing import Iterable, List
from importlib.metadata import PackagePath as _PackagePath

from PyInstaller import compat
from PyInstaller.log import logger

if compat.is_py38:
from importlib.metadata import PackagePath as _PackagePath
else:
from importlib_metadata import PackagePath as _PackagePath

# Conda virtual environments each get their own copy of `conda-meta` so the use of `sys.prefix` instead of
# `sys.base_prefix`, `sys.real_prefix` or anything from our `compat` module is intentional.
CONDA_ROOT = Path(sys.prefix)
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Expand Up @@ -41,7 +41,7 @@ but is not tested against them as part of the continuous integration tests.
Main Advantages
---------------

- Works out-of-the-box with any Python version 3.7-3.12.
- Works out-of-the-box with any Python version 3.8-3.12.
- Fully multi-platform, and uses the OS support to load the dynamic libraries,
thus ensuring full compatibility.
- Correctly bundles the major Python packages such as numpy, PyQt5,
Expand All @@ -66,7 +66,7 @@ Requirements and Tested Platforms
---------------------------------

- Python:
- 3.7-3.12. Note that Python 3.10.0 contains a bug making it unsupportable by
- 3.8-3.12. Note that Python 3.10.0 contains a bug making it unsupportable by
PyInstaller. PyInstaller will also not work with beta releases of Python
3.13.
- Windows (32bit/64bit/ARM64):
Expand Down
2 changes: 1 addition & 1 deletion doc/index.rst
Expand Up @@ -11,7 +11,7 @@ PyInstaller Manual

PyInstaller bundles a Python application and all its dependencies into a single package.
The user can run the packaged app without installing a Python interpreter or any modules.
PyInstaller supports Python 3.7 and newer, and correctly bundles many major Python packages
PyInstaller supports Python 3.8 and newer, and correctly bundles many major Python packages
such as numpy, matplotlib, PyQt, wxPython, and others.

PyInstaller is tested against Windows, MacOS X, and Linux.
Expand Down
2 changes: 1 addition & 1 deletion doc/man/pyinstaller.rst
Expand Up @@ -23,7 +23,7 @@ PyInstaller is a program that freezes (packages) Python programs into
stand-alone executables, under Windows, GNU/Linux, macOS,
FreeBSD, OpenBSD, Solaris and AIX.
Its main advantages over similar tools are that PyInstaller works with
Python 3.7-3.11, it builds smaller executables thanks to transparent
Python 3.8-3.11, it builds smaller executables thanks to transparent
compression, it is fully multi-platform, and use the OS support to load the
dynamic libraries, thus ensuring full compatibility.

Expand Down
4 changes: 0 additions & 4 deletions doc/spec-files.rst
Expand Up @@ -363,10 +363,6 @@ For example modify the spec file this way::
exclude_binaries=...
)

.. Note:: The unbuffered stdio mode (the ``u`` option) enables unbuffered
binary layer of ``stdout`` and ``stderr`` streams on all supported Python
versions. The unbuffered text layer requires Python 3.7 or later.


.. _spec file options for a macOS bundle:

Expand Down
3 changes: 2 additions & 1 deletion doc/usage.rst
Expand Up @@ -592,7 +592,8 @@ Building 32-bit Apps in macOS
on modern versions of macOS, see :ref:`here <macos multi-arch support>`).
However, PyInstaller still supports building 32-bit bootloader,
and 32-bit/64-bit Python installers are still available from
python.org for (some) versions of Python 3.7.
python.org for (some) versions of Python 3.7 which PyInstaller dropped
support for in v6.0.

Older versions of macOS supported both 32-bit and 64-bit executables.
PyInstaller builds an app using the the word-length of the Python used to execute it.
Expand Down
2 changes: 1 addition & 1 deletion doc/when-things-go-wrong.rst
Expand Up @@ -108,7 +108,7 @@ Build-Time Python Errors
PyInstaller sometimes terminates by raising a Python exception.
In most cases the reason is clear from the exception message,
for example "Your system is not supported", or "Pyinstaller
requires at least Python 3.7".
requires at least Python 3.8".
Others clearly indicate a bug that should be reported.

One of these errors can be puzzling, however:
Expand Down
1 change: 1 addition & 0 deletions news/6475.core.rst
@@ -0,0 +1 @@
Drop support for end of life Python 3.7.
4 changes: 1 addition & 3 deletions setup.cfg
Expand Up @@ -39,7 +39,6 @@ classifiers =
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Expand All @@ -58,7 +57,7 @@ classifiers =
packages = find:
zip_safe = False
include_package_data = False
python_requires = >=3.7, <3.13
python_requires = >=3.8, <3.13
## IMPORTANT: Keep aligned with requirements.txt
install_requires =
setuptools >= 42.0.0
Expand All @@ -67,7 +66,6 @@ install_requires =
pywin32-ctypes >= 0.2.1 ; sys_platform == 'win32'
macholib >= 1.8 ; sys_platform == 'darwin'
pyinstaller-hooks-contrib >= 2021.4
importlib-metadata >= 1.4 ; python_version < '3.8'

[options.packages.find]
include =
Expand Down
11 changes: 4 additions & 7 deletions tests/requirements-libraries.txt
Expand Up @@ -16,7 +16,7 @@ importlib_resources==5.12.0; python_version < '3.9'
# -------
# These packages work with no (known) issues.
babel==2.12.1
Django==4.2.2; python_version >= '3.8'
Django==4.2.2
future==0.18.3
gevent==22.10.2
ipython==8.14.0; python_version >= '3.9'
Expand Down Expand Up @@ -53,10 +53,10 @@ PyQt6-WebEngine==6.5.0
python-dateutil==2.8.2
pytz==2023.3
requests==2.31.0
scipy==1.10.1; python_version >= '3.8'
scipy==1.10.1
# simplejson is used for text_c_extension
simplejson==3.19.1
sphinx==7.0.1; python_version >= '3.8'
sphinx==7.0.1
# Required for test_namespace_package
sqlalchemy==2.0.16
zope.interface==6.0
Expand All @@ -66,10 +66,7 @@ Pillow==9.5.0
# Python versions not supported / supported for older package versions
# -------------------------------------------------------

# numpy dropped support for python 3.7 as of 1.22
numpy==1.21.5; python_version == '3.7' # pyup: ignore
numpy==1.24.3; python_version == '3.8' # pyup: ignore

# ipython dropped support for python 3.7 in v8.0.0 and for python 3.8 in v8.13.0
ipython==7.34.0; python_version == '3.7' # pyup: ignore
# ipython dropped support for python 3.8 in v8.13.0
ipython==8.12.1; python_version == '3.8' # pyup: ignore

0 comments on commit 7ab1af5

Please sign in to comment.