Skip to content

Commit 01c9257

Browse files
authored
Merge pull request #4743 from mwichmann/tweak/ENVPath
Doc and test tweaks for ENVPath functions
2 parents 6162a4c + 31e18a6 commit 01c9257

File tree

6 files changed

+244
-138
lines changed

6 files changed

+244
-138
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
171171
if existing value already contained more than one of added value.
172172
- Tweak runtest.py and test framework to more reliably get the requested
173173
Python binary (for odd Windows setups + Python launcher)
174+
- Improve the wording of AppendENVPath and PrependENVPath in manpage.
175+
- Add more unit tests to internal AppendPath, PrependPath functions.
174176

175177

176178
RELEASE 4.9.1 - Thu, 27 Mar 2025 11:40:20 -0700

RELEASE.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ DOCUMENTATION
143143

144144
- Improve the wording of Configure methods.
145145

146+
- Improve the wording of AppendENVPath and PrependENVPath in manpage.
147+
146148
DEVELOPMENT
147149
-----------
148150

@@ -193,6 +195,8 @@ DEVELOPMENT
193195
iterative speedup test from 200 to 250. Increasing the workload
194196
should reduce the likelihood that the ninja tests are slower.
195197

198+
- Add more unit tests to internal AppendPath, PrependPath functions.
199+
196200
Thanks to the following contributors listed below for their contributions to this release.
197201
==========================================================================================
198202
.. code-block:: text

SCons/Environment.xml

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -778,26 +778,41 @@ See also &f-link-env-AppendUnique;,
778778
</arguments>
779779
<summary>
780780
<para>
781-
Append path elements specified by <parameter>newpath</parameter>
782-
to the given search path string or list <parameter>name</parameter>
783-
in mapping <parameter>envname</parameter> in the &consenv;.
784-
Supplying <parameter>envname</parameter> is optional:
785-
the default is the execution environment &cv-link-ENV;.
786-
Optional <parameter>sep</parameter> is used as the search path separator,
787-
the default is the platform's separator (<systemitem>os.pathsep</systemitem>).
788-
A path element will only appear once.
789-
Any duplicates in <parameter>newpath</parameter> are dropped,
790-
keeping the last appearing (to preserve path order).
791-
If <parameter>delete_existing</parameter>
792-
is <constant>False</constant> (the default)
793-
any addition duplicating an existing path element is ignored;
794-
if <parameter>delete_existing</parameter>
795-
is <constant>True</constant> the existing value will
796-
be dropped and the path element will be added at the end.
797-
To help maintain uniqueness all paths are normalized (using
798-
<systemitem>os.path.normpath</systemitem>
799-
and
800-
<systemitem>os.path.normcase</systemitem>).
781+
Append directory paths from
782+
<parameter>newpath</parameter> to
783+
a search-path entry <parameter>name</parameter>
784+
in &consvar; <parameter>envname</parameter>
785+
in the current enviromment (<parameter>env</parameter>).
786+
If <parameter>envname</parameter> is not given,
787+
the default is <literal>"ENV"</literal>
788+
(see &cv-link-ENV;).
789+
<parameter>envname</parameter> is expected
790+
to refer to a dictionary-like object;
791+
if it does not exist in <parameter>env</parameter>
792+
it will be created as an initially empty dict.
793+
<parameter>newpath</parameter>
794+
may be specified as a string,
795+
a directory node, or a list of strings.
796+
If a string, it may contain multiple paths separated
797+
by the system path separator
798+
(<systemitem>os.pathsep</systemitem>),
799+
or, if specified, by the value of <parameter>sep</parameter>.
800+
Top-relative path strings (starting with <literal>#</literal>) are recognized.
801+
The type of the existing value
802+
of <parameter>name</parameter> is preserved.
803+
</para>
804+
805+
<para>
806+
Paths will only appear once.
807+
Duplicate paths in <parameter>newpath</parameter> are removed,
808+
preserving the last occurrence to maintain path order.
809+
If <parameter>delete_existing</parameter> is true (the default),
810+
existing duplicates are removed before appending,
811+
otherwise, new duplicates are skipped.
812+
During comparisons, paths are normalized, to avoid
813+
issues with case differences (on case-insensitive filesystems)
814+
and with relative paths that may refer back to the same directory.
815+
The stored values are not modified by this process.
801816
</para>
802817

803818
<para>
@@ -1119,8 +1134,8 @@ env4 = env.Clone(tools=['msvc', MyTool])
11191134
The
11201135
<parameter>parse_flags</parameter>
11211136
keyword argument is also recognized, to allow merging command-line
1122-
style arguments into the appropriate construction
1123-
variables (see &f-link-env-MergeFlags;).
1137+
style arguments into the appropriate &consvars;
1138+
(see &f-link-env-MergeFlags;).
11241139
</para>
11251140

11261141
<example_commands>
@@ -2863,26 +2878,41 @@ and &f-link-env-PrependUnique;.
28632878
</arguments>
28642879
<summary>
28652880
<para>
2866-
Prepend path elements specified by <parameter>newpath</parameter>
2867-
to the given search path string or list <parameter>name</parameter>
2868-
in mapping <parameter>envname</parameter> in the &consenv;.
2869-
Supplying <parameter>envname</parameter> is optional:
2870-
the default is the execution environment &cv-link-ENV;.
2871-
Optional <parameter>sep</parameter> is used as the search path separator,
2872-
the default is the platform's separator (<systemitem>os.pathsep</systemitem>).
2873-
A path element will only appear once.
2874-
Any duplicates in <parameter>newpath</parameter> are dropped,
2875-
keeping the first appearing (to preserve path order).
2876-
If <parameter>delete_existing</parameter>
2877-
is <constant>False</constant>
2878-
any addition duplicating an existing path element is ignored;
2879-
if <parameter>delete_existing</parameter>
2880-
is <constant>True</constant> (the default) the existing value will
2881-
be dropped and the path element will be inserted at the beginning.
2882-
To help maintain uniqueness all paths are normalized (using
2883-
<systemitem>os.path.normpath</systemitem>
2884-
and
2885-
<systemitem>os.path.normcase</systemitem>).
2881+
Prepend directory paths from
2882+
<parameter>newpath</parameter> to
2883+
a search-path entry <parameter>name</parameter>
2884+
in &consvar; <parameter>envname</parameter>
2885+
in the current enviromment (<parameter>env</parameter>).
2886+
If <parameter>envname</parameter> is not given,
2887+
the default is <literal>"ENV"</literal>
2888+
(see &cv-link-ENV;).
2889+
<parameter>envname</parameter> is expected
2890+
to refer to a dictionary-like object;
2891+
if it does not exist in <parameter>env</parameter>
2892+
it will be created as an initially empty dict.
2893+
<parameter>newpath</parameter>
2894+
may be specified as a string,
2895+
a directory node, or a list of strings.
2896+
If a string, it may contain multiple paths separated
2897+
by the system path separator
2898+
(<systemitem>os.pathsep</systemitem>),
2899+
or, if specified, by the value of <parameter>sep</parameter>.
2900+
Top-relative path strings (starting with <literal>#</literal>) are recognized.
2901+
The type of the existing value
2902+
of <parameter>name</parameter> is preserved.
2903+
</para>
2904+
2905+
<para>
2906+
Paths will only appear once.
2907+
Duplicate paths in <parameter>newpath</parameter> are removed,
2908+
preserving the first occurrence to maintain path order.
2909+
If <parameter>delete_existing</parameter> is true (the default),
2910+
existing duplicates are removed before prepending,
2911+
otherwise, new duplicates are skipped.
2912+
During comparisons, paths are normalized, to avoid
2913+
issues with case differences (on case-insensitive filesystems)
2914+
and with relative paths that may refer back to the same directory.
2915+
The stored values are not modified by this process.
28862916
</para>
28872917

28882918
<para>

SCons/Util/UtilTests.py

Lines changed: 86 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -545,55 +545,95 @@ def test_get_native_path(self) -> None:
545545

546546
def test_PrependPath(self) -> None:
547547
"""Test prepending to a path"""
548-
p1: list | str = r'C:\dir\num\one;C:\dir\num\two'
549-
p2: list | str = r'C:\mydir\num\one;C:\mydir\num\two'
550-
# have to include the pathsep here so that the test will work on UNIX too.
551-
p1 = PrependPath(p1, r'C:\dir\num\two', sep=';')
552-
p1 = PrependPath(p1, r'C:\dir\num\three', sep=';')
553-
assert p1 == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one', p1
554-
555-
p2 = PrependPath(p2, r'C:\mydir\num\three', sep=';')
556-
p2 = PrependPath(p2, r'C:\mydir\num\one', sep=';')
557-
assert p2 == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two', p2
558-
559-
# check (only) first one is kept if there are dupes in new
560-
p3: list | str = r'C:\dir\num\one'
561-
p3 = PrependPath(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\two', sep=';')
562-
assert p3 == r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\one', p3
548+
# have to specify the pathsep when adding so it's cross-platform
549+
# new duplicates existing - "moves to front"
550+
with self.subTest():
551+
p1: list | str = r'C:\dir\num\one;C:\dir\num\two'
552+
p1 = PrependPath(p1, r'C:\dir\num\two', sep=';')
553+
p1 = PrependPath(p1, r'C:\dir\num\three', sep=';')
554+
self.assertEqual(p1, r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one')
555+
556+
# ... except with delete_existing false
557+
with self.subTest():
558+
p2: list | str = r'C:\dir\num\one;C:\dir\num\two'
559+
p2 = PrependPath(p2, r'C:\dir\num\two', sep=';', delete_existing=False)
560+
p2 = PrependPath(p2, r'C:\dir\num\three', sep=';', delete_existing=False)
561+
self.assertEqual(p2, r'C:\dir\num\three;C:\dir\num\one;C:\dir\num\two')
562+
563+
# only last one is kept if there are dupes in new
564+
with self.subTest():
565+
p3: list | str = r'C:\dir\num\one'
566+
p3 = PrependPath(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\two', sep=';')
567+
self.assertEqual(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\one')
568+
569+
# try prepending a Dir Node
570+
with self.subTest():
571+
p4: list | str = r'C:\dir\num\one'
572+
test = TestCmd.TestCmd(workdir='')
573+
test.subdir('sub')
574+
subdir = test.workpath('sub')
575+
p4 = PrependPath(p4, subdir, sep=';')
576+
self.assertEqual(p4, rf'{subdir};C:\dir\num\one')
577+
578+
# try with initial list, adding string (result stays a list)
579+
with self.subTest():
580+
p5: list = [r'C:\dir\num\one', r'C:\dir\num\two']
581+
p5 = PrependPath(p5, r'C:\dir\num\two', sep=';')
582+
self.assertEqual(p5, [r'C:\dir\num\two', r'C:\dir\num\one'])
583+
p5 = PrependPath(p5, r'C:\dir\num\three', sep=';')
584+
self.assertEqual(p5, [r'C:\dir\num\three', r'C:\dir\num\two', r'C:\dir\num\one'])
585+
586+
# try with initial string, adding list (result stays a string)
587+
with self.subTest():
588+
p6: list | str = r'C:\dir\num\one;C:\dir\num\two'
589+
p6 = PrependPath(p6, [r'C:\dir\num\two', r'C:\dir\num\three'], sep=';')
590+
self.assertEqual(p6, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\one')
591+
563592

564593
def test_AppendPath(self) -> None:
565594
"""Test appending to a path."""
566-
p1: list | str = r'C:\dir\num\one;C:\dir\num\two'
567-
p2: list | str = r'C:\mydir\num\one;C:\mydir\num\two'
568-
# have to include the pathsep here so that the test will work on UNIX too.
569-
p1 = AppendPath(p1, r'C:\dir\num\two', sep=';')
570-
p1 = AppendPath(p1, r'C:\dir\num\three', sep=';')
571-
assert p1 == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three', p1
572-
573-
p2 = AppendPath(p2, r'C:\mydir\num\three', sep=';')
574-
p2 = AppendPath(p2, r'C:\mydir\num\one', sep=';')
575-
assert p2 == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one', p2
576-
577-
# check (only) last one is kept if there are dupes in new
578-
p3: list | str = r'C:\dir\num\one'
579-
p3 = AppendPath(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\two', sep=';')
580-
assert p3 == r'C:\dir\num\one;C:\dir\num\three;C:\dir\num\two', p3
581-
582-
def test_PrependPathPreserveOld(self) -> None:
583-
"""Test prepending to a path while preserving old paths"""
584-
p1: list | str = r'C:\dir\num\one;C:\dir\num\two'
585-
# have to include the pathsep here so that the test will work on UNIX too.
586-
p1 = PrependPath(p1, r'C:\dir\num\two', sep=';', delete_existing=False)
587-
p1 = PrependPath(p1, r'C:\dir\num\three', sep=';')
588-
assert p1 == r'C:\dir\num\three;C:\dir\num\one;C:\dir\num\two', p1
589-
590-
def test_AppendPathPreserveOld(self) -> None:
591-
"""Test appending to a path while preserving old paths"""
592-
p1: list | str = r'C:\dir\num\one;C:\dir\num\two'
593-
# have to include the pathsep here so that the test will work on UNIX too.
594-
p1 = AppendPath(p1, r'C:\dir\num\one', sep=';', delete_existing=False)
595-
p1 = AppendPath(p1, r'C:\dir\num\three', sep=';')
596-
assert p1 == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three', p1
595+
# have to specify the pathsep when adding so it's cross-platform
596+
# new duplicates existing - "moves to end"
597+
with self.subTest():
598+
p1: list | str = r'C:\dir\num\one;C:\dir\num\two'
599+
p1 = AppendPath(p1, r'C:\dir\num\two', sep=';')
600+
p1 = AppendPath(p1, r'C:\dir\num\three', sep=';')
601+
self.assertEqual(p1, r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three')
602+
603+
# ... except with delete_existing false
604+
with self.subTest():
605+
p2: list | str = r'C:\dir\num\one;C:\dir\num\two'
606+
p2 = AppendPath(p1, r'C:\dir\num\one', sep=';', delete_existing=False)
607+
p2 = AppendPath(p1, r'C:\dir\num\three', sep=';')
608+
self.assertEqual(p2, r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three')
609+
610+
# only last one is kept if there are dupes in new
611+
with self.subTest():
612+
p3: list | str = r'C:\dir\num\one'
613+
p3 = AppendPath(p3, r'C:\dir\num\two;C:\dir\num\three;C:\dir\num\two', sep=';')
614+
self.assertEqual(p3, r'C:\dir\num\one;C:\dir\num\three;C:\dir\num\two')
615+
616+
# try appending a Dir Node
617+
with self.subTest():
618+
p4: list | str = r'C:\dir\num\one'
619+
test = TestCmd.TestCmd(workdir='')
620+
test.subdir('sub')
621+
subdir = test.workpath('sub')
622+
p4 = AppendPath(p4, subdir, sep=';')
623+
self.assertEqual(p4, rf'C:\dir\num\one;{subdir}')
624+
625+
# try with initial list, adding string (result stays a list)
626+
with self.subTest():
627+
p5: list = [r'C:\dir\num\one', r'C:\dir\num\two']
628+
p5 = AppendPath(p5, r'C:\dir\num\two', sep=';')
629+
p5 = AppendPath(p5, r'C:\dir\num\three', sep=';')
630+
self.assertEqual(p5, [r'C:\dir\num\one', r'C:\dir\num\two', r'C:\dir\num\three'])
631+
632+
# try with initia string, adding list (result stays a string)
633+
with self.subTest():
634+
p6: list | str = r'C:\dir\num\one;C:\dir\num\two'
635+
p6 = AppendPath(p6, [r'C:\dir\num\two', r'C:\dir\num\three'], sep=';')
636+
self.assertEqual(p6, r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three')
597637

598638
def test_addPathIfNotExists(self) -> None:
599639
"""Test the AddPathIfNotExists() function"""

SCons/Util/envs.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ def PrependPath(
3030
3131
Will only add any particular path once (leaving the first one it
3232
encounters and ignoring the rest, to preserve path order), and will
33-
:mod:`os.path.normpath` and :mod:`os.path.normcase` all paths to help
34-
assure this. This can also handle the case where *oldpath*
35-
is a list instead of a string, in which case a list will be returned
36-
instead of a string. For example:
33+
:mod:`os.path.normpath` and :mod:`os.path.normcase` all paths
34+
for comparisons to help assure this. *oldpath* may be a list
35+
instead of a string, in which case a list is returned.
36+
37+
Example:
3738
3839
>>> p = PrependPath("/foo/bar:/foo", "/biz/boom:/foo")
3940
>>> print(p)
@@ -120,10 +121,11 @@ def AppendPath(
120121
121122
Will only add any particular path once (leaving the last one it
122123
encounters and ignoring the rest, to preserve path order), and will
123-
:mod:`os.path.normpath` and :mod:`os.path.normcase` all paths to help
124-
assure this. This can also handle the case where *oldpath*
125-
is a list instead of a string, in which case a list will be returned
126-
instead of a string. For example:
124+
:mod:`os.path.normpath` and :mod:`os.path.normcase` all paths
125+
for comparisons to help assure this. *oldpath* may be a list
126+
instead of a string, in which case a list is returned.
127+
128+
Example:
127129
128130
>>> p = AppendPath("/foo/bar:/foo", "/biz/boom:/foo")
129131
>>> print(p)

0 commit comments

Comments
 (0)