Skip to content

Commit 26762e9

Browse files
committed
Remove Python 3.7 support
1 parent 7877823 commit 26762e9

File tree

15 files changed

+37
-185
lines changed

15 files changed

+37
-185
lines changed

.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.8'},
2525
{os: 'ubuntu-24.04', py: '3.13'},
2626
]
2727

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555

5656
- &coverage_jobs
5757
dist: bionic
58-
python: 3.7
58+
python: 3.8
5959
name: coverage
6060
before_script:
6161
- ./.travis/coverage_setup.sh

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, 8, 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 & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,7 +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),(),()'),
15531552
(3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
15541553
(3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
15551554
(3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
@@ -1730,7 +1729,6 @@ def LocalFunc() -> None:
17301729
pass
17311730

17321731
func_matches = {
1733-
(3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
17341732
(3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
17351733
(3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
17361734
(3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
@@ -1742,7 +1740,6 @@ def LocalFunc() -> None:
17421740
}
17431741

17441742
meth_matches = {
1745-
(3, 7): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
17461743
(3, 8): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
17471744
(3, 9): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
17481745
(3, 10): bytearray(b'1, 1, 0, 0,(),(),(d\x00S\x00),(),()'),
@@ -1983,7 +1980,6 @@ def LocalFunc() -> None:
19831980
pass
19841981

19851982
func_matches = {
1986-
(3, 7): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
19871983
(3, 8): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
19881984
(3, 9): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
19891985
(3, 10): bytearray(b'0, 0, 0, 0,(),(),(d\x00S\x00),(),()'),
@@ -2046,7 +2042,6 @@ def LocalFunc() -> None:
20462042
pass
20472043

20482044
matches = {
2049-
(3, 7): b'd\x00S\x00',
20502045
(3, 8): b'd\x00S\x00',
20512046
(3, 9): b'd\x00S\x00',
20522047
(3, 10): b'd\x00S\x00',
@@ -2249,7 +2244,6 @@ def func1(a, b, c):
22492244
# we need different expected results per version
22502245
# Note unlike the others, this result is a tuple, use assertIn
22512246
expected = {
2252-
(3, 7): (bytearray(b"3, 3, 0, 0,(),(),(|\x00S\x00),(),()"),),
22532247
(3, 8): (bytearray(b"3, 3, 0, 0,(),(),(|\x00S\x00),(),()"),),
22542248
(3, 9): (bytearray(b"3, 3, 0, 0,(),(),(|\x00S\x00),(),()"),),
22552249
(3, 10): ( # 3.10.1, 3.10.2
@@ -2277,9 +2271,6 @@ def test_object_contents(self) -> None:
22772271
# Since the python bytecode has per version differences,
22782272
# we need different expected results per version
22792273
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-
),
22832274
(3, 8): bytearray(
22842275
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}}}"
22852276
),
@@ -2312,9 +2303,6 @@ def test_code_contents(self) -> None:
23122303

23132304
# Since the python bytecode has per version differences, we need different expected results per version
23142305
expected = {
2315-
(3, 7): bytearray(
2316-
b"0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)"
2317-
),
23182306
(3, 8): bytearray(
23192307
b"0, 0, 0, 0,(Hello, World!),(print),(e\x00d\x00\x83\x01\x01\x00d\x01S\x00)"
23202308
),

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, 8, 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: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def get_current_hash_algorithm_used():
253253
"""Returns the current hash algorithm name used.
254254
255255
Where the python version >= 3.9, this is expected to return md5.
256-
If python's version is <= 3.8, this returns md5 on non-FIPS-mode platforms, and
256+
If python's version is == 3.8, this returns md5 on non-FIPS-mode platforms, and
257257
sha1 or sha256 on FIPS-mode Linux platforms.
258258
259259
This function is primarily useful for testing, where one expects a value to be
@@ -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 & 3 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.8"
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,7 +22,6 @@ classifiers = [
2222
"Programming Language :: Python",
2323
"Programming Language :: Python :: 3",
2424
"Programming Language :: Python :: 3 :: Only",
25-
"Programming Language :: Python :: 3.7",
2625
"Programming Language :: Python :: 3.8",
2726
"Programming Language :: Python :: 3.9",
2827
"Programming Language :: Python :: 3.10",
@@ -97,7 +96,7 @@ dist-dir = "build/dist"
9796
dist-dir = "build/dist"
9897

9998
[tool.ruff]
100-
target-version = "py37" # Lowest python version supported
99+
target-version = "py38" # Lowest python version supported
101100
extend-include = ["SConstruct", "SConscript"]
102101
extend-exclude = [
103102
"bench/",

0 commit comments

Comments
 (0)