diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index d9de297ecf..f5910e9f38 100644 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -582,7 +582,7 @@ def add_defaults(self) -> None: sdist.add_defaults(self) self.filelist.append(self.template) self.filelist.append(self.manifest) - rcfiles = list(walk_revctrl()) + rcfiles = list(walk_revctrl(distribution=self.distribution)) if rcfiles: self.filelist.extend(rcfiles) elif os.path.exists(self.manifest): diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index 17279ac421..7ffbeebedf 100644 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +import inspect import os import re from collections.abc import Iterator @@ -17,10 +18,47 @@ _default_revctrl = list -def walk_revctrl(dirname='') -> Iterator: +def _call_finder_with_distribution_support(finder_func, dirname='', distribution=None): + """ + Call a file finder function with distribution support if available. + + This helper function inspects the finder function's signature to determine + if it accepts a distribution parameter. If it does, the distribution is passed; + otherwise, only the dirname is passed for backward compatibility. + + Args: + finder_func: The file finder function to call + dirname: Directory name to search (default: '') + distribution: Distribution object to pass if supported (default: None) + + Returns: + Iterator of file paths from the finder function + + Raises: + Any exception raised by the finder function itself + """ + try: + sig = inspect.signature(finder_func) + params = list(sig.parameters.keys()) + + # If function accepts distribution parameter, pass it + if len(params) > 1 and 'distribution' in params: + return finder_func(dirname, distribution=distribution) + else: + # Fall back to dirname-only for backward compatibility + return finder_func(dirname) + except (ValueError, TypeError): + # If signature inspection fails, fall back to dirname-only + return finder_func(dirname) + + +def walk_revctrl(dirname='', distribution=None) -> Iterator: """Find all files under revision control""" for ep in metadata.entry_points(group='setuptools.file_finders'): - yield from ep.load()(dirname) + finder_func = ep.load() + yield from _call_finder_with_distribution_support( + finder_func, dirname, distribution + ) class sdist(orig.sdist): diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py index 19d8ddf6da..20fc438fb1 100644 --- a/setuptools/tests/test_sdist.py +++ b/setuptools/tests/test_sdist.py @@ -19,7 +19,7 @@ from setuptools import Command, SetuptoolsDeprecationWarning from setuptools._importlib import metadata from setuptools.command.egg_info import manifest_maker -from setuptools.command.sdist import sdist +from setuptools.command.sdist import _call_finder_with_distribution_support, sdist from setuptools.dist import Distribution from setuptools.extension import Extension from setuptools.tests import fail_on_ascii @@ -982,3 +982,109 @@ def test_sanity_check_setuptools_own_sdist(setuptools_sdist): # setuptools sdist should not include the .tox folder tox_files = [name for name in files if ".tox" in name] assert len(tox_files) == 0, f"not empty {tox_files}" + + +class TestCallFinderWithDistributionSupport: + """Tests for the _call_finder_with_distribution_support helper function""" + + def test_finder_with_distribution_parameter(self): + """Test that finder functions accepting distribution parameter receive it""" + + def finder_with_distribution(dirname, distribution=None): + return [f"file_in_{dirname}_with_dist_{distribution}"] + + result = list( + _call_finder_with_distribution_support( + finder_with_distribution, dirname="testdir", distribution="test_dist" + ) + ) + + assert result == ["file_in_testdir_with_dist_test_dist"] + + def test_finder_without_distribution_parameter(self): + """Test that finder functions not accepting distribution parameter work normally""" + + def finder_without_distribution(dirname): + return [f"file_in_{dirname}"] + + result = list( + _call_finder_with_distribution_support( + finder_without_distribution, dirname="testdir", distribution="test_dist" + ) + ) + + assert result == ["file_in_testdir"] + + def test_finder_with_single_parameter_only(self): + """Test that finder functions with only dirname parameter work""" + + def finder_single_param(dirname): + return [f"single_{dirname}"] + + result = list( + _call_finder_with_distribution_support(finder_single_param, dirname="test") + ) + + assert result == ["single_test"] + + def test_finder_with_distribution_named_differently(self): + """Test that only functions with 'distribution' parameter name get it""" + + def finder_with_other_param(dirname, other_param=None): + return [f"other_{dirname}_{other_param}"] + + # Should not pass distribution even though function has 2+ params + result = list( + _call_finder_with_distribution_support( + finder_with_other_param, dirname="test", distribution="dist_obj" + ) + ) + + assert result == ["other_test_None"] + + def test_finder_with_signature_inspection_failure(self): + """Test fallback behavior when signature inspection fails""" + + # Create a mock function that raises an error during signature inspection + def problematic_finder(dirname): + return [f"fallback_{dirname}"] + + # Mock inspect.signature to raise ValueError + with mock.patch( + 'setuptools.command.sdist.inspect.signature', + side_effect=ValueError("test error"), + ): + result = list( + _call_finder_with_distribution_support( + problematic_finder, dirname="test", distribution="dist_obj" + ) + ) + + assert result == ["fallback_test"] + + def test_finder_with_multiple_parameters_including_distribution(self): + """Test finder with multiple parameters where one is distribution""" + + def complex_finder(dirname, extra_param=None, distribution=None): + return [f"complex_{dirname}_{extra_param}_{distribution}"] + + result = list( + _call_finder_with_distribution_support( + complex_finder, dirname="test", distribution="dist_obj" + ) + ) + + assert result == ["complex_test_None_dist_obj"] + + def test_finder_function_raises_exception(self): + """Test that exceptions from finder functions are properly propagated""" + + def failing_finder(dirname, distribution=None): + raise RuntimeError("Finder failed") + + with pytest.raises(RuntimeError, match="Finder failed"): + list( + _call_finder_with_distribution_support( + failing_finder, dirname="test", distribution="dist_obj" + ) + )