Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PyPackageDir #4479

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Fix bad typing in Action.py: process() and strfunction().
- Add Pseudo() to global functions, had been omitted. Fixes #4474.
The Pseudo manpage entry was updated to provide more clarity.
- The internal routine which implements the PyPackageDir function
would fail with an exception if called with a module which is
not found. It will now return None. Updated manpage entry and
docstring..


RELEASE 4.6.0 - Sun, 19 Nov 2023 17:22:20 -0700
Expand Down
3 changes: 3 additions & 0 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ FIXES
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.
- PyPackageDir no longer fails if passed a module name which cannot be found,
now returns None.


IMPROVEMENTS
------------
Expand Down
43 changes: 32 additions & 11 deletions SCons/Environment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2880,20 +2880,41 @@ and &f-link-env-Prepend;.
</arguments>
<summary>
<para>
This returns a Directory Node similar to Dir.
The python module / package is looked up and if located
the directory is returned for the location.
<parameter>modulename</parameter>
Is a named python package / module to
lookup the directory for it's location.
</para>
<para>
If
<parameter>modulename</parameter>
is a list, SCons returns a list of Dir nodes.
Finds the location of <parameter>modulename</parameter>,
which can be a string or a sequence of strings,
each representing the name of a &Python; module.
Construction variables are expanded in
<parameter>modulename</parameter>.
Returns a Directory Node (see &f-link-Dir;),
or a list of Directory Nodes if
<parameter>modulename</parameter> is a sequence.
<literal>None</literal> is returned for any module not found.
</para>

<para>
When a Tool module which is installed as a
&Python; module is used, you need
to specify a <parameter>toolpath</parameter> argument to
&f-link-Tool;,
&f-link-Environment;
or &f-link-Clone;,
as tools outside the standard project locations
(<filename>site_scons/site_tools</filename>)
will not be found otherwise.
Using &f-PyPackageDir; allows this path to be
discovered at runtime instead of hardcoding the path.
</para>

<para>
Example:
</para>

<example_commands>
env = Environment(
tools=["default", "ExampleTool"],
toolpath=[PyPackageDir("example_tool")]
)
</example_commands>
</summary>
</scons_function>

Expand Down
31 changes: 17 additions & 14 deletions SCons/Node/FS.py
Original file line number Diff line number Diff line change
Expand Up @@ -1294,7 +1294,7 @@ def get_root(self, drive):
self.Root[''] = root
return root

def _lookup(self, p, directory, fsclass, create: int=1):
def _lookup(self, p, directory, fsclass, create: bool = True):
"""
The generic entry point for Node lookup with user-supplied data.

Expand Down Expand Up @@ -1430,7 +1430,7 @@ def _lookup(self, p, directory, fsclass, create: int=1):

return root._lookup_abs(p, fsclass, create)

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

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

def PyPackageDir(self, modulename):
r"""Locate the directory of a given python module name
def PyPackageDir(self, modulename) -> Optional[Dir]:
r"""Locate the directory of Python module *modulename*.

For example scons might resolve to
Windows: C:\Python27\Lib\site-packages\scons-2.5.1
Linux: /usr/lib/scons
For example 'SCons' might resolve to
Windows: C:\Python311\Lib\site-packages\SCons
Linux: /usr/lib64/python3.11/site-packages/SCons

This can be useful when we want to determine a toolpath based on a python module name"""
Can be used to determine a toolpath based on a Python module name.

dirpath = ''

# Python3 Code
This is the backend called by the public API function
:meth:`~Environment.Base.PyPackageDir`.
"""
modspec = importlib.util.find_spec(modulename)
dirpath = os.path.dirname(modspec.origin)
return self._lookup(dirpath, None, Dir, True)
if modspec:
origin = os.path.dirname(modspec.origin)
return self._lookup(origin, directory=None, fsclass=Dir, create=True)
else:
return None


def variant_dir_target_climb(self, orig, dir, tail):
Expand Down
17 changes: 17 additions & 0 deletions SCons/Node/FSTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4046,6 +4046,23 @@ def test_root_lookup_equivalence(self) -> None:
os.chdir(save_cwd)


class PyPackageDir(unittest.TestCase):
def runTest(self) -> None:
"""Test calling the PyPackageDir() method.

We don't want to mock the positive case here - there's
testing for that in E2E test test/Dir/PyPackageDir.
We're only making sure we don't die in the negative case
(module not found) and instead return None.
"""
fs = SCons.Node.FS.FS('/')
try:
pkdir = fs.PyPackageDir("garglemod")
except AttributeError:
self.fail("non-existent module raised AttributeError")
self.assertIsNone(pkdir)


if __name__ == "__main__":
unittest.main()

Expand Down
Loading