Repository: SCons/scons (language: python)

Issue:
Pseudo() global function missing
Initiated from discord discussion https://discord.com/channels/571796279483564041/571796280146133047/1204494883369263154

The documentation indicates both `Pseudo` and `env.Pseudo` work; in practice, the global function form does not work, generating an `AttributeError`.

A quick examination shows the table of environment methods (in `SCons/Script/__init__.py`) which should be made into global functions does not contain `Pseudo`, looks like an oversight.


Discussion / hints:

Fix patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index 44a458a8f7..b3a0d6ec4e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -74,6 +74,7 @@ RELEASE  VERSION/DATE TO BE FILLED IN LATER
       Fixes #3529.
     - Clarify/fix documentation of Scanners in User Guide and Manpage.
       Fixes #4468.
+    - Add Pseudo() to global functions, had been omitted. Fixes #4474.
 
 
 RELEASE 4.6.0 -  Sun, 19 Nov 2023 17:22:20 -0700
diff --git a/RELEASE.txt b/RELEASE.txt
index 2952d4f396..28b6c6ac8b 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -52,6 +52,8 @@ FIXES
   build of the project file fails.
 - On Windows platform, when collecting command output (Configure checks),
   make sure decoding of bytes doesn't fail.
+- Documentation indicated that both Pseudo() and env.Pseudo() were usable,
+  but Pseudo() did not work; is now enabled.
 
 IMPROVEMENTS
 ------------
diff --git a/SCons/Script/__init__.py b/SCons/Script/__init__.py
index 0d2940c6ae..a62650f7f6 100644
--- a/SCons/Script/__init__.py
+++ b/SCons/Script/__init__.py
@@ -343,6 +343,7 @@ def Variables(files=None, args=ARGUMENTS):
     'Local',
     'ParseDepends',
     'Precious',
+    'Pseudo',
     'PyPackageDir',
     'Repository',
     'Requires',


Test patch:
diff --git a/test/Pseudo.py b/test/Pseudo.py
index db3c30c05b..ec953f7b2a 100644
--- a/test/Pseudo.py
+++ b/test/Pseudo.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python
 #
-# __COPYRIGHT__
+# MIT License
+#
+# Copyright The SCons Foundation
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -20,41 +22,66 @@
 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
 
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+"""
+Test the Pseudo method
+"""
 
 import TestSCons
 
 test = TestSCons.TestSCons()
 
-# Firstly, build a pseudo target and make sure we get no warnings it
-# doesn't exist under any circumstances
-test.write('SConstruct', """
+test.write('SConstruct', """\
 env = Environment()
-env.Pseudo(env.Command('foo.out', [], '@echo boo'))
-""")
-
-test.run(arguments='-Q', stdout = 'boo\n')
+foo = env.Command('foo.out', [], '@echo boo')
+bar = env.Command('bar.out', [], Touch('$TARGET'))
+env.Pseudo(foo, bar)
 
-test.run(arguments='-Q --warning=target-not-built', stdout = "boo\n")
-
-# Now do the same thing again but create the target and check we get an
-# error if it exists after the build
-test.write('SConstruct', """
-env = Environment()
-env.Pseudo(env.Command('foo.out', [], Touch('$TARGET')))
+gfoo = Command('foo.glb', [], '@echo boo')
+gbar = Command('bar.glb', [], Touch('$TARGET'))
+Pseudo(gfoo, gbar)
 """)
 
-test.run(arguments='-Q', stdout = 'Touch("foo.out")\n', stderr = None,
-         status = 2)
-test.must_contain_all_lines(test.stderr(),
-                            'scons:  *** Pseudo target foo.out must not exist')
-test.run(arguments='-Q --warning=target-not-built',
-         stdout = 'Touch("foo.out")\n',
-         stderr = None, status = 2)
-test.must_contain_all_lines(test.stderr(),
-                            'scons:  *** Pseudo target foo.out must not exist')
+# foo.out build does not create file, should generate no errors
+test.run(arguments='-Q foo.out', stdout='boo\n')
+# missing target warning triggers if requested
+test.run(arguments='-Q foo.out --warning=target-not-built', stdout="boo\n")
+# bar.out build creates file, error if it exists after the build
+test.run(arguments='-Q bar.out', stdout='Touch("bar.out")\n', stderr=None, status=2)
+test.must_contain_all_lines(
+    test.stderr(),
+    'scons:  *** Pseudo target bar.out must not exist',
+)
+# warning must not appear since target created
+test.run(
+    arguments='-Q bar.out --warning=target-not-built',
+    stdout='Touch("bar.out")\n',
+    stderr=None,
+    status=2,
+)
+test.must_contain_all_lines(
+    test.stderr(),
+    'scons:  *** Pseudo target bar.out must not exist',
+)
+
+# repeat the process for the global function form (was missing initially)
+test.run(arguments='-Q foo.glb', stdout='boo\n')
+test.run(arguments='-Q foo.glb --warning=target-not-built', stdout="boo\n")
+test.run(arguments='-Q bar.glb', stdout='Touch("bar.glb")\n', stderr=None, status=2)
+test.must_contain_all_lines(
+    test.stderr(),
+    'scons:  *** Pseudo target bar.glb must not exist',
+)
+test.run(
+    arguments='-Q bar.glb --warning=target-not-built',
+    stdout='Touch("bar.glb")\n',
+    stderr=None,
+    status=2,
+)
+test.must_contain_all_lines(
+    test.stderr(),
+    'scons:  *** Pseudo target bar.glb must not exist',
+)
 
 test.pass_test()