Skip to content

Commit e026ea0

Browse files
committed
Remove Python 3.7 & 3.8 support
1 parent 7877823 commit e026ea0

15 files changed

Lines changed: 37 additions & 197 deletions

File tree

.appveyor.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ environment:
3535
SCONS_CACHE_MSVC_CONFIG: "true"
3636
matrix:
3737
# Test oldest and newest supported Pythons, and a subset in between.
38-
# Skipping 3.8, 3.10, 3.12 at this time
38+
# Skipping 3.10, 3.12 at this time
3939
- WINPYTHON: "Python313"
4040
- WINPYTHON: "Python311"
4141
- WINPYTHON: "Python39"
@@ -45,7 +45,7 @@ environment:
4545
# to fine tune the number and platforms tested
4646
matrix:
4747
exclude:
48-
# test python 3.7 on Visual Studio 2017 image
48+
# test python 3.8 on Visual Studio 2017 image
4949
- image: Visual Studio 2017
5050
WINPYTHON: "Python313"
5151
- image: Visual Studio 2017
@@ -59,13 +59,13 @@ matrix:
5959
- image: Visual Studio 2019
6060
WINPYTHON: "Python311"
6161
- image: Visual Studio 2019
62-
WINPYTHON: "Python37"
62+
WINPYTHON: "Python38"
6363

6464
# test python 3.11, 3.13 on Visual Studio 2022 image
6565
- image: Visual Studio 2022
6666
WINPYTHON: "Python39"
6767
- image: Visual Studio 2022
68-
WINPYTHON: "Python37"
68+
WINPYTHON: "Python38"
6969

7070
# Remove some binaries we don't want to be found
7171
# Note this is no longer needed, git-windows bin/ is quite minimal now.

.github/workflows/runtest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
fail-fast: false
2222
matrix:
2323
cfg: [
24-
{os: 'ubuntu-22.04', py: '3.7'},
24+
{os: 'ubuntu-22.04', py: '3.9'},
2525
{os: 'ubuntu-24.04', py: '3.13'},
2626
]
2727

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SCons is an open-source software construction tool (build tool) implemented in P
1313
## Building and Running
1414

1515
### Prerequisites
16-
* Python 3.7 or higher.
16+
* Python 3.9 or higher.
1717
* Development dependencies: `python -m pip install -r requirements-dev.txt`
1818

1919
### Running SCons (Development)

ReleaseConfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ version_tuple = (4, 10, 2, 'a', 0)
3737
# when that version is used. Python versions prior to deprecate_python_version
3838
# cause a warning to be issued (assuming it's not disabled). These values are
3939
# mandatory and must be present in the configuration file.
40-
unsupported_python_version = (3, 7, 0)
40+
unsupported_python_version = (3, 9, 0)
4141
deprecated_python_version = (3, 9, 0)
4242

4343
# If release_date is (yyyy, mm, dd, hh, mm, ss), that is used as the release

SCons/ActionTests.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,8 +1549,6 @@ def LocalFunc() -> None:
15491549

15501550
# Since the python bytecode has per version differences, we need different expected results per version
15511551
func_matches = {
1552-
(3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1553-
(3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
15541552
(3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
15551553
(3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
15561554
(3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
@@ -1730,8 +1728,6 @@ def LocalFunc() -> None:
17301728
pass
17311729

17321730
func_matches = {
1733-
(3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1734-
(3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
17351731
(3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
17361732
(3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
17371733
(3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
@@ -1742,8 +1738,6 @@ def LocalFunc() -> None:
17421738
}
17431739

17441740
meth_matches = {
1745-
(3, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
1746-
(3, 8): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
17471741
(3, 9): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
17481742
(3, 10): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
17491743
(3, 11): bytearray(b'1, 1, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
@@ -1983,8 +1977,6 @@ def LocalFunc() -> None:
19831977
pass
19841978

19851979
func_matches = {
1986-
(3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
1987-
(3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
19881980
(3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
19891981
(3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
19901982
(3, 11): bytearray(b'0, 0, 0, 0,(),(),(\x97\x00d\x00S\x00),(),()'),
@@ -2046,8 +2038,6 @@ def LocalFunc() -> None:
20462038
pass
20472039

20482040
matches = {
2049-
(3, 7): b'd\x00S\x00',
2050-
(3, 8): b'd\x00S\x00',
20512041
(3, 9): b'd\x00S\x00',
20522042
(3, 10): b'd\x00S\x00',
20532043
(3, 11): b'\x97\x00d\x00S\x00',
@@ -2249,8 +2239,6 @@ def func1(a, b, c):
22492239
# we need different expected results per version
22502240
# Note unlike the others, this result is a tuple, use assertIn
22512241
expected = {
2252-
(3, 7): (bytearray(b"3, 3, 0, 0,(),(),(|\x00S\x00),(),()"),),
2253-
(3, 8): (bytearray(b"3, 3, 0, 0,(),(),(|\x00S\x00),(),()"),),
22542242
(3, 9): (bytearray(b"3, 3, 0, 0,(),(),(|\x00S\x00),(),()"),),
22552243
(3, 10): ( # 3.10.1, 3.10.2
22562244
bytearray(b"3, 3, 0, 0,(N.),(),(|\x00S\x00),(),()"),
@@ -2277,12 +2265,6 @@ def test_object_contents(self) -> None:
22772265
# Since the python bytecode has per version differences,
22782266
# we need different expected results per version
22792267
expected = {
2280-
(3, 7): bytearray(
2281-
b"{TestClass:__main__}[[[(<class 'object'>, ()), [(<class '__main__.TestClass'>, (<class 'object'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2282-
),
2283-
(3, 8): bytearray(
2284-
b"{TestClass:__main__}[[[(<class 'object'>, ()), [(<class '__main__.TestClass'>, (<class 'object'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
2285-
),
22862268
(3, 9): bytearray(
22872269
b"{TestClass:__main__}[[[(<class 'object'>, ()), [(<class '__main__.TestClass'>, (<class 'object'>,))]]]]{{1, 1, 0, 0,(a,b),(a,b),(d\x01|\x00_\x00d\x02|\x00_\x01d\x00S\x00),(),(),2, 2, 0, 0,(),(),(d\x00S\x00),(),()}}{{{a=a,b=b}}}"
22882270
),
@@ -2312,12 +2294,6 @@ def test_code_contents(self) -> None:
23122294

23132295
# Since the python bytecode has per version differences, we need different expected results per version
23142296
expected = {
2315-
(3, 7): bytearray(
2316-
b"0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)"
2317-
),
2318-
(3, 8): bytearray(
2319-
b"0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)"
2320-
),
23212297
(3, 9): bytearray(
23222298
b"0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)"
23232299
),

SCons/CacheDir.py

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -210,44 +210,22 @@ def _mkdir_atomic(self, path: str) -> bool:
210210
return False
211211

212212
try:
213-
# TODO: Python 3.7. See comment below.
214-
# tempdir = tempfile.TemporaryDirectory(dir=os.path.dirname(directory))
215-
tempdir = tempfile.mkdtemp(dir=os.path.dirname(directory))
213+
tempdir = tempfile.TemporaryDirectory(dir=os.path.dirname(directory))
216214
except OSError as e:
217215
msg = "Failed to create cache directory " + path
218216
raise SCons.Errors.SConsEnvironmentError(msg) from e
219217

220-
# TODO: Python 3.7: the context manager raises exception on cleanup
221-
# if the temporary was moved successfully (File Not Found).
222-
# Fixed in 3.8+. In the replacement below we manually clean up if
223-
# the move failed as mkdtemp() does not. TemporaryDirectory's
224-
# cleanup is more sophisitcated so prefer when we can use it.
225-
# self._add_config(tempdir.name)
226-
# with tempdir:
227-
# try:
228-
# os.replace(tempdir.name, directory)
229-
# return True
230-
# except OSError as e:
231-
# # did someone else get there first?
232-
# if os.path.isdir(directory):
233-
# return False # context manager cleans up
234-
# msg = "Failed to create cache directory " + path
235-
# raise SCons.Errors.SConsEnvironmentError(msg) from e
236-
237-
self._add_config(tempdir)
238-
try:
239-
os.replace(tempdir, directory)
240-
return True
241-
except OSError as e:
242-
# did someone else get there first? attempt cleanup.
243-
if os.path.isdir(directory):
244-
try:
245-
shutil.rmtree(tempdir)
246-
except Exception: # we tried, don't worry about it
247-
pass
248-
return False
249-
msg = "Failed to create cache directory " + path
250-
raise SCons.Errors.SConsEnvironmentError(msg) from e
218+
self._add_config(tempdir.name)
219+
with tempdir:
220+
try:
221+
os.replace(tempdir.name, directory)
222+
return True
223+
except OSError as e:
224+
# did someone else get there first?
225+
if os.path.isdir(directory):
226+
return False # context manager cleans up
227+
msg = "Failed to create cache directory " + path
228+
raise SCons.Errors.SConsEnvironmentError(msg) from e
251229

252230
def _readconfig(self, path: str) -> None:
253231
"""Read the cache config from *path*.

SCons/Script/Main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
from SCons import __version__ as SConsVersion
6868

6969
# these define the range of versions SCons supports
70-
minimum_python_version = (3, 7, 0)
70+
minimum_python_version = (3, 9, 0)
7171
deprecated_python_version = (3, 9, 0)
7272

7373
# ordered list of SConstruct names to look for if there is no -f flag

SCons/Tool/install.py

Lines changed: 2 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
import os
3333
import stat
34-
from shutil import copy2, copystat
34+
from shutil import copy2, copytree
3535

3636
import SCons.Action
3737
import SCons.Tool
@@ -50,109 +50,6 @@
5050
class CopytreeError(OSError):
5151
pass
5252

53-
54-
def scons_copytree(src, dst, symlinks: bool=False, ignore=None, copy_function=copy2,
55-
ignore_dangling_symlinks: bool=False, dirs_exist_ok: bool=False):
56-
"""Recursively copy a directory tree, SCons version.
57-
58-
This is a modified copy of the Python 3.7 shutil.copytree function.
59-
SCons update: dirs_exist_ok dictates whether to raise an
60-
exception in case dst or any missing parent directory already
61-
exists. Implementation depends on os.makedirs having a similar
62-
flag, which it has since Python 3.2. This version also raises an
63-
SCons-defined exception rather than the one defined locally to shtuil.
64-
This version uses a change from Python 3.8.
65-
TODO: we can remove this forked copy once the minimum Py version is 3.8.
66-
67-
If exception(s) occur, an Error is raised with a list of reasons.
68-
69-
If the optional symlinks flag is true, symbolic links in the
70-
source tree result in symbolic links in the destination tree; if
71-
it is false, the contents of the files pointed to by symbolic
72-
links are copied. If the file pointed by the symlink doesn't
73-
exist, an exception will be added in the list of errors raised in
74-
an Error exception at the end of the copy process.
75-
76-
You can set the optional ignore_dangling_symlinks flag to true if you
77-
want to silence this exception. Notice that this has no effect on
78-
platforms that don't support os.symlink.
79-
80-
The optional ignore argument is a callable. If given, it
81-
is called with the `src` parameter, which is the directory
82-
being visited by copytree(), and `names` which is the list of
83-
`src` contents, as returned by os.listdir():
84-
85-
callable(src, names) -> ignored_names
86-
87-
Since copytree() is called recursively, the callable will be
88-
called once for each directory that is copied. It returns a
89-
list of names relative to the `src` directory that should
90-
not be copied.
91-
92-
The optional copy_function argument is a callable that will be used
93-
to copy each file. It will be called with the source path and the
94-
destination path as arguments. By default, copy2() is used, but any
95-
function that supports the same signature (like copy()) can be used.
96-
97-
"""
98-
names = os.listdir(src)
99-
if ignore is not None:
100-
ignored_names = ignore(src, names)
101-
else:
102-
ignored_names = set()
103-
104-
os.makedirs(dst, exist_ok=dirs_exist_ok)
105-
errors = []
106-
for name in names:
107-
if name in ignored_names:
108-
continue
109-
srcname = os.path.join(src, name)
110-
dstname = os.path.join(dst, name)
111-
try:
112-
if os.path.islink(srcname):
113-
linkto = os.readlink(srcname)
114-
if symlinks:
115-
# We can't just leave it to `copy_function` because legacy
116-
# code with a custom `copy_function` may rely on copytree
117-
# doing the right thing.
118-
os.symlink(linkto, dstname)
119-
copystat(srcname, dstname, follow_symlinks=not symlinks)
120-
else:
121-
# ignore dangling symlink if the flag is on
122-
if not os.path.exists(linkto) and ignore_dangling_symlinks:
123-
continue
124-
# otherwise let the copy occurs. copy2 will raise an error
125-
if os.path.isdir(srcname):
126-
scons_copytree(srcname, dstname, symlinks=symlinks,
127-
ignore=ignore, copy_function=copy_function,
128-
ignore_dangling_symlinks=ignore_dangling_symlinks,
129-
dirs_exist_ok=dirs_exist_ok)
130-
else:
131-
copy_function(srcname, dstname)
132-
elif os.path.isdir(srcname):
133-
scons_copytree(srcname, dstname, symlinks=symlinks,
134-
ignore=ignore, copy_function=copy_function,
135-
ignore_dangling_symlinks=ignore_dangling_symlinks,
136-
dirs_exist_ok=dirs_exist_ok)
137-
else:
138-
# Will raise a SpecialFileError for unsupported file types
139-
copy_function(srcname, dstname)
140-
# catch the Error from the recursive copytree so that we can
141-
# continue with other files
142-
except CopytreeError as err: # SCons change
143-
errors.extend(err.args[0])
144-
except OSError as why:
145-
errors.append((srcname, dstname, str(why)))
146-
try:
147-
copystat(src, dst)
148-
except OSError as why:
149-
# Copying file access times may fail on Windows
150-
if getattr(why, 'winerror', None) is None:
151-
errors.append((src, dst, str(why)))
152-
if errors:
153-
raise CopytreeError(errors) # SCons change
154-
return dst
155-
15653
#
15754
# Functions doing the actual work of the Install Builder.
15855
#
@@ -173,7 +70,7 @@ def copyFunc(dest, source, env) -> int:
17370
parent = os.path.split(dest)[0]
17471
if not os.path.exists(parent):
17572
os.makedirs(parent)
176-
scons_copytree(source, dest, dirs_exist_ok=True)
73+
copytree(source, dest, dirs_exist_ok=True)
17774
else:
17875
copy2(source, dest)
17976
st = os.stat(source)

SCons/Util/hashes.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,14 +330,8 @@ def hash_file_signature(fname: str, chunksize: int=65536, hash_format=None) -> s
330330
"""
331331
m = _get_hash_object(hash_format)
332332
with open(fname, "rb") as f:
333-
while True:
334-
blck = f.read(chunksize)
335-
if not blck:
336-
break
337-
m.update(to_bytes(blck))
338-
# TODO: can use this when base is Python 3.8+
339-
# while (blk := f.read(chunksize)) != b'':
340-
# m.update(to_bytes(blk))
333+
while (blk := f.read(chunksize)):
334+
m.update(to_bytes(blk))
341335

342336
return m.hexdigest()
343337

pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ requires = ["setuptools"]
55
[project]
66
name = "SCons"
77
description = "Open Source next-generation build tool."
8-
requires-python = ">=3.7"
8+
requires-python = ">=3.9"
99
license = "MIT" # PEP 639 form (new - setuptools >= 77.0)
1010
# Should include docbook license, but this fails:
1111
# license = "MIT AND DocBook-stylesheet"
@@ -22,8 +22,6 @@ classifiers = [
2222
"Programming Language :: Python",
2323
"Programming Language :: Python :: 3",
2424
"Programming Language :: Python :: 3 :: Only",
25-
"Programming Language :: Python :: 3.7",
26-
"Programming Language :: Python :: 3.8",
2725
"Programming Language :: Python :: 3.9",
2826
"Programming Language :: Python :: 3.10",
2927
"Programming Language :: Python :: 3.11",
@@ -97,7 +95,7 @@ dist-dir = "build/dist"
9795
dist-dir = "build/dist"
9896

9997
[tool.ruff]
100-
target-version = "py37" # Lowest python version supported
98+
target-version = "py38" # Lowest python version supported
10199
extend-include = ["SConstruct", "SConscript"]
102100
extend-exclude = [
103101
"bench/",

0 commit comments

Comments
 (0)