Skip to content

Commit 7e64842

Browse files
authored
Merge pull request SCons#4505 from mwichmann/py313-isabs
Fix isabs() check in NodeInfo for Py 3.13
2 parents 2f57d9d + 693472a commit 7e64842

File tree

4 files changed

+61
-40
lines changed

4 files changed

+61
-40
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
1717
- Dump() with json format selected now recognizes additional compound types
1818
(UserDict and UserList), which improves the detail of the display.
1919
json output is also sorted, to match the default display.
20+
- Python 3.13 (alpha) changes the behavior of isabs() on Windows. Adjust
21+
SCons usage of in NodeInfo classes to match. Fixes #4502, #4504.
2022

2123

2224
RELEASE 4.7.0 - Sun, 17 Mar 2024 17:22:20 -0700

RELEASE.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
2929
- Dump() with json format selected now recognizes additional compound types
3030
(UserDict and UserList), which improves the detail of the display.
3131
json output is also sorted, to match the default display.
32+
- Python 3.13 changes the behavior of isabs() on Windows. Adjust SCons
33+
usage of this in NodeInfo classes to avoid test problems.
3234

3335
FIXES
3436
-----

SCons/Node/FS.py

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -120,43 +120,41 @@ def save_strings(val) -> None:
120120
global Save_Strings
121121
Save_Strings = val
122122

123-
#
124-
# Avoid unnecessary function calls by recording a Boolean value that
125-
# tells us whether or not os.path.splitdrive() actually does anything
126-
# on this system, and therefore whether we need to bother calling it
127-
# when looking up path names in various methods below.
128-
#
129123

130124
do_splitdrive = None
131-
_my_splitdrive =None
125+
_my_splitdrive = None
132126

133127
def initialize_do_splitdrive() -> None:
134-
global do_splitdrive
135-
global has_unc
136-
drive, path = os.path.splitdrive('X:/foo')
137-
# splitunc is removed from python 3.7 and newer
138-
# so we can also just test if splitdrive works with UNC
139-
has_unc = (hasattr(os.path, 'splitunc')
140-
or os.path.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive')
141-
142-
do_splitdrive = not not drive or has_unc
143-
144-
global _my_splitdrive
145-
if has_unc:
146-
def splitdrive(p):
128+
"""Set up splitdrive usage.
129+
130+
Avoid unnecessary function calls by recording a flag that tells us whether
131+
or not :func:`os.path.splitdrive` actually does anything on this system,
132+
and therefore whether we need to bother calling it when looking up path
133+
names in various methods below.
134+
135+
If :data:`do_splitdrive` is True, :func:`_my_splitdrive` will be a real
136+
function which we can call. As all supported Python versions' ntpath module
137+
now handle UNC paths correctly, we no longer special-case that.
138+
139+
Deferring the setup of ``_my_splitdrive`` also lets unit tests do
140+
their thing and test UNC path handling on a POSIX host.
141+
"""
142+
global do_splitdrive, _my_splitdrive
143+
144+
do_splitdrive = bool(os.path.splitdrive('X:/foo')[0])
145+
146+
if do_splitdrive:
147+
def _my_splitdrive(p):
147148
if p[1:2] == ':':
148149
return p[:2], p[2:]
149150
if p[0:2] == '//':
150151
# Note that we leave a leading slash in the path
151152
# because UNC paths are always absolute.
152153
return '//', p[1:]
153154
return '', p
154-
else:
155-
def splitdrive(p):
156-
if p[1:2] == ':':
157-
return p[:2], p[2:]
158-
return '', p
159-
_my_splitdrive = splitdrive
155+
# TODO: the os routine should work and be better debugged than ours,
156+
# but unit test test_unc_path fails on POSIX platforms. Resolve someday.
157+
# _my_splitdrive = os.path.splitdrive
160158

161159
# Keep some commonly used values in global variables to skip to
162160
# module look-up costs.
@@ -1238,7 +1236,10 @@ def __init__(self, path = None) -> None:
12381236
self.pathTop = os.getcwd()
12391237
else:
12401238
self.pathTop = path
1241-
self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1239+
if do_splitdrive:
1240+
self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
1241+
else:
1242+
self.defaultDrive = ""
12421243

12431244
self.Top = self.Dir(self.pathTop)
12441245
self.Top._path = '.'
@@ -1554,11 +1555,15 @@ class DirNodeInfo(SCons.Node.NodeInfoBase):
15541555
def str_to_node(self, s):
15551556
top = self.fs.Top
15561557
root = top.root
1558+
# Python 3.13/Win changed isabs() - after you split C:/foo/bar,
1559+
# the path part is no longer considerd absolute. Save the passed
1560+
# path for the isabs check so we can get the right answer.
1561+
path = s
15571562
if do_splitdrive:
15581563
drive, s = _my_splitdrive(s)
15591564
if drive:
15601565
root = self.fs.get_root(drive)
1561-
if not os.path.isabs(s):
1566+
if not os.path.isabs(path):
15621567
s = top.get_labspath() + '/' + s
15631568
return root._lookup_abs(s, Entry)
15641569

@@ -2380,7 +2385,7 @@ def __init__(self, drive, fs) -> None:
23802385
# The // entry is necessary because os.path.normpath()
23812386
# preserves double slashes at the beginning of a path on Posix
23822387
# platforms.
2383-
if not has_unc:
2388+
if not do_splitdrive:
23842389
self._lookupDict['//'] = self
23852390

23862391
def _morph(self) -> None:
@@ -2511,11 +2516,15 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
25112516
def str_to_node(self, s):
25122517
top = self.fs.Top
25132518
root = top.root
2519+
# Python 3.13/Win changed isabs() - after you split C:/foo/bar,
2520+
# the path part is no longer considerd absolute. Save the passed
2521+
# path for the isabs check so we can get the right answer.
2522+
path = s
25142523
if do_splitdrive:
25152524
drive, s = _my_splitdrive(s)
25162525
if drive:
25172526
root = self.fs.get_root(drive)
2518-
if not os.path.isabs(s):
2527+
if not os.path.isabs(path):
25192528
s = top.get_labspath() + '/' + s
25202529
return root._lookup_abs(s, Entry)
25212530

@@ -3534,7 +3543,7 @@ def is_up_to_date(self) -> bool:
35343543
35353544
In all cases self is the target we're checking to see if it's up to date
35363545
"""
3537-
T = 0
3546+
T = False
35383547
if T: Trace('is_up_to_date(%s):' % self)
35393548
if not self.exists():
35403549
if T: Trace(' not self.exists():')
@@ -3730,7 +3739,10 @@ def filedir_lookup(self, p, fd=None):
37303739
if fd is None:
37313740
fd = self.default_filedir
37323741
dir, name = os.path.split(fd)
3733-
drive, d = _my_splitdrive(dir)
3742+
if do_splitdrive:
3743+
drive, d = _my_splitdrive(dir)
3744+
else:
3745+
drive, d = "", dir
37343746
if not name and d[:1] in ('/', OS_SEP):
37353747
#return p.fs.get_root(drive).dir_on_disk(name)
37363748
return p.fs.get_root(drive)

SCons/Node/__init__.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,7 @@ def scan(self) -> None:
10631063
# Don't bother scanning non-derived files, because we don't
10641064
# care what their dependencies are.
10651065
# Don't scan again, if we already have scanned.
1066+
T = False
10661067
if self.implicit is not None:
10671068
return
10681069
self.implicit = []
@@ -1087,7 +1088,12 @@ def scan(self) -> None:
10871088
# essentially short-circuits an N*M scan of the
10881089
# sources for each individual target, which is a hell
10891090
# of a lot more efficient.
1091+
def print_nodelist(n):
1092+
tgts = [f"{t.path!r}" for t in n]
1093+
return f"[{', '.join(tgts)}]"
1094+
10901095
for tgt in executor.get_all_targets():
1096+
if T: Trace(f"adding implicit {print_nodelist(implicit)} to {tgt!s}\n")
10911097
tgt.add_to_implicit(implicit)
10921098

10931099
if implicit_deps_unchanged or self.is_up_to_date():
@@ -1472,8 +1478,8 @@ def changed(self, node=None, allowcache: bool=False):
14721478
14731479
@see: FS.File.changed(), FS.File.release_target_info()
14741480
"""
1475-
t = 0
1476-
if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node))
1481+
T = False
1482+
if T: Trace('changed(%s [%s], %s)' % (self, classname(self), node))
14771483
if node is None:
14781484
node = self
14791485

@@ -1491,25 +1497,24 @@ def changed(self, node=None, allowcache: bool=False):
14911497
# entries to equal the new dependency list, for the benefit
14921498
# of the loop below that updates node information.
14931499
then.extend([None] * diff)
1494-
if t: Trace(': old %s new %s' % (len(then), len(children)))
1500+
if T: Trace(': old %s new %s' % (len(then), len(children)))
14951501
result = True
14961502

14971503
for child, prev_ni in zip(children, then):
14981504
if _decider_map[child.changed_since_last_build](child, self, prev_ni, node):
1499-
if t: Trace(': %s changed' % child)
1505+
if T: Trace(f": '{child!s}' changed")
15001506
result = True
15011507

15021508
if self.has_builder():
15031509
contents = self.get_executor().get_contents()
15041510
newsig = hash_signature(contents)
15051511
if bi.bactsig != newsig:
1506-
if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
1512+
if T: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
15071513
result = True
15081514

15091515
if not result:
1510-
if t: Trace(': up to date')
1511-
1512-
if t: Trace('\n')
1516+
if T: Trace(': up to date')
1517+
if T: Trace('\n')
15131518

15141519
return result
15151520

0 commit comments

Comments
 (0)