Skip to content
Open
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
1 change: 1 addition & 0 deletions newsfragments/3851.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Files listed in ``scripts`` are no longer also auto-discovered as ``py_modules``, so a single-script project is no longer installed into both ``scripts`` and ``purelib``.
22 changes: 22 additions & 0 deletions setuptools/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ def _analyse_src_layout(self) -> bool:
self.dist.package_dir = package_dir # persist eventual modifications
self.dist.packages = PEP420PackageFinder.find(src_dir)
self.dist.py_modules = ModuleFinder.find(src_dir)
self._exclude_script_modules()
log.debug(f"discovered packages -- {self.dist.packages}")
log.debug(f"discovered py_modules -- {self.dist.py_modules}")
return True
Expand All @@ -452,10 +453,31 @@ def _analyse_flat_packages(self) -> bool:

def _analyse_flat_modules(self) -> bool:
self.dist.py_modules = FlatLayoutModuleFinder.find(self._root_dir)
self._exclude_script_modules()
log.debug(f"discovered py_modules -- {self.dist.py_modules}")
self._ensure_no_accidental_inclusion(self.dist.py_modules, "modules")
return bool(self.dist.py_modules)

def _exclude_script_modules(self) -> None:
"""Drop auto-discovered modules that are already listed in ``scripts``.

Otherwise a lone ``script.py`` given via ``scripts=["script.py"]`` is
also treated as a ``py_module`` and installed into both ``scripts`` and
``purelib`` (#3851).
"""
if not self.dist.py_modules:
return
skip = {
os.path.splitext(os.path.basename(script))[0]
for script in self.dist.scripts or ()
if os.path.splitext(script)[1] == ".py"
}
skip.discard("")
if skip:
self.dist.py_modules = [
name for name in self.dist.py_modules if name not in skip
]

def _ensure_no_accidental_inclusion(self, detected: list[str], kind: str):
if len(detected) > 1:
from inspect import cleandoc
Expand Down
15 changes: 15 additions & 0 deletions setuptools/tests/test_config_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ def test_flat_layout_with_multiple_modules(self, tmp_path):
with pytest.raises(PackageDiscoveryError, match="multiple (packages|modules)"):
_get_dist(tmp_path, {})

def test_scripts_not_auto_discovered_as_py_modules(self, tmp_path):
"""A file listed in ``scripts`` must not also become a py_module (#3851)."""
files = ["script.py"]
options = {"scripts": ["script.py"]}
_populate_project_dir(tmp_path, files, options)
dist = _get_dist(tmp_path, options)
assert "script" not in (dist.py_modules or [])

def test_scripts_do_not_hide_other_discovered_modules(self, tmp_path):
files = ["pkg.py", "script.py"]
options = {"scripts": ["script.py"]}
_populate_project_dir(tmp_path, files, options)
dist = _get_dist(tmp_path, options)
assert set(dist.py_modules or []) == {"pkg"}

def test_py_modules_when_wheel_dir_is_cwd(self, tmp_path):
"""Regression for issue 3692"""
from setuptools import build_meta
Expand Down