Skip to content

Commit 00ef03a

Browse files
committed
Fix PyPackageDir
Passing a module which the active Python (system or virtualenv) cannot locate through the import machinery would cause SCons to fail with an AttributError, because the result of the initial lookup was used without checking for success. Now returns None for not found. Manpage entry and docstring also updated. Signed-off-by: Mats Wichmann <[email protected]>
1 parent ef925ad commit 00ef03a

File tree

5 files changed

+73
-25
lines changed

5 files changed

+73
-25
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
8181
- Fix bad typing in Action.py: process() and strfunction().
8282
- Add Pseudo() to global functions, had been omitted. Fixes #4474.
8383
The Pseudo manpage entry was updated to provide more clarity.
84+
- The internal routine which implements the PyPackageDir function
85+
would fail with an exception if called with a module which is
86+
not found. It will now return None. Updated manpage entry and
87+
docstring..
8488

8589

8690
RELEASE 4.6.0 - Sun, 19 Nov 2023 17:22:20 -0700

RELEASE.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ FIXES
5454
make sure decoding of bytes doesn't fail.
5555
- Documentation indicated that both Pseudo() and env.Pseudo() were usable,
5656
but Pseudo() did not work; is now enabled.
57+
- PyPackageDir no longer fails if passed a module name which cannot be found,
58+
now returns None.
59+
5760

5861
IMPROVEMENTS
5962
------------

SCons/Environment.xml

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2880,20 +2880,41 @@ and &f-link-env-Prepend;.
28802880
</arguments>
28812881
<summary>
28822882
<para>
2883-
This returns a Directory Node similar to Dir.
2884-
The python module / package is looked up and if located
2885-
the directory is returned for the location.
2886-
<parameter>modulename</parameter>
2887-
Is a named python package / module to
2888-
lookup the directory for it's location.
2889-
</para>
2890-
<para>
2891-
If
2892-
<parameter>modulename</parameter>
2893-
is a list, SCons returns a list of Dir nodes.
2883+
Finds the location of <parameter>modulename</parameter>,
2884+
which can be a string or a sequence of strings,
2885+
each representing the name of a &Python; module.
28942886
Construction variables are expanded in
28952887
<parameter>modulename</parameter>.
2888+
Returns a Directory Node (see &f-link-Dir;),
2889+
or a list of Directory Nodes if
2890+
<parameter>modulename</parameter> is a sequence.
2891+
<literal>None</literal> is returned for any module not found.
2892+
</para>
2893+
2894+
<para>
2895+
When a Tool module which is installed as a
2896+
&Python; module is used, you need
2897+
to specify a <parameter>toolpath</parameter> argument to
2898+
&f-link-Tool;,
2899+
&f-link-Environment;
2900+
or &f-link-Clone;,
2901+
as tools outside the standard project locations
2902+
(<filename>site_scons/site_tools</filename>)
2903+
will not be found otherwise.
2904+
Using &f-PyPackageDir; allows this path to be
2905+
discovered at runtime instead of hardcoding the path.
2906+
</para>
2907+
2908+
<para>
2909+
Example:
28962910
</para>
2911+
2912+
<example_commands>
2913+
env = Environment(
2914+
tools=["default", "ExampleTool"],
2915+
toolpath=[PyPackageDir("example_tool")]
2916+
)
2917+
</example_commands>
28972918
</summary>
28982919
</scons_function>
28992920

SCons/Node/FS.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,7 +1294,7 @@ def get_root(self, drive):
12941294
self.Root[''] = root
12951295
return root
12961296

1297-
def _lookup(self, p, directory, fsclass, create: int=1):
1297+
def _lookup(self, p, directory, fsclass, create: bool = True):
12981298
"""
12991299
The generic entry point for Node lookup with user-supplied data.
13001300
@@ -1430,7 +1430,7 @@ def _lookup(self, p, directory, fsclass, create: int=1):
14301430

14311431
return root._lookup_abs(p, fsclass, create)
14321432

1433-
def Entry(self, name, directory = None, create: int = 1):
1433+
def Entry(self, name, directory = None, create: bool = True):
14341434
"""Look up or create a generic Entry node with the specified name.
14351435
If the name is a relative path (begins with ./, ../, or a file
14361436
name), then it is looked up relative to the supplied directory
@@ -1439,7 +1439,7 @@ def Entry(self, name, directory = None, create: int = 1):
14391439
"""
14401440
return self._lookup(name, directory, Entry, create)
14411441

1442-
def File(self, name, directory = None, create: int = 1):
1442+
def File(self, name, directory = None, create: bool = True):
14431443
"""Look up or create a File node with the specified name. If
14441444
the name is a relative path (begins with ./, ../, or a file name),
14451445
then it is looked up relative to the supplied directory node,
@@ -1486,21 +1486,24 @@ def Repository(self, *dirs) -> None:
14861486
d = self.Dir(d)
14871487
self.Top.addRepository(d)
14881488

1489-
def PyPackageDir(self, modulename):
1490-
r"""Locate the directory of a given python module name
1489+
def PyPackageDir(self, modulename) -> Optional[Dir]:
1490+
r"""Locate the directory of Python module *modulename*.
14911491
1492-
For example scons might resolve to
1493-
Windows: C:\Python27\Lib\site-packages\scons-2.5.1
1494-
Linux: /usr/lib/scons
1492+
For example 'SCons' might resolve to
1493+
Windows: C:\Python311\Lib\site-packages\SCons
1494+
Linux: /usr/lib64/python3.11/site-packages/SCons
14951495
1496-
This can be useful when we want to determine a toolpath based on a python module name"""
1496+
Can be used to determine a toolpath based on a Python module name.
14971497
1498-
dirpath = ''
1499-
1500-
# Python3 Code
1498+
This is the backend called by the public API function
1499+
:meth:`~Environment.Base.PyPackageDir`.
1500+
"""
15011501
modspec = importlib.util.find_spec(modulename)
1502-
dirpath = os.path.dirname(modspec.origin)
1503-
return self._lookup(dirpath, None, Dir, True)
1502+
if modspec:
1503+
origin = os.path.dirname(modspec.origin)
1504+
return self._lookup(origin, directory=None, fsclass=Dir, create=True)
1505+
else:
1506+
return None
15041507

15051508

15061509
def variant_dir_target_climb(self, orig, dir, tail):

SCons/Node/FSTests.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4046,6 +4046,23 @@ def test_root_lookup_equivalence(self) -> None:
40464046
os.chdir(save_cwd)
40474047

40484048

4049+
class PyPackageDir(unittest.TestCase):
4050+
def runTest(self) -> None:
4051+
"""Test calling the PyPackageDir() method.
4052+
4053+
We don't want to mock the positive case here - there's
4054+
testing for that in E2E test test/Dir/PyPackageDir.
4055+
We're only making sure we don't die in the negative case
4056+
(module not found) and instead return None.
4057+
"""
4058+
fs = SCons.Node.FS.FS('/')
4059+
try:
4060+
pkdir = fs.PyPackageDir("garglemod")
4061+
except AttributeError:
4062+
self.fail("non-existent module raised AttributeError")
4063+
self.assertIsNone(pkdir)
4064+
4065+
40494066
if __name__ == "__main__":
40504067
unittest.main()
40514068

0 commit comments

Comments
 (0)