feat: auto-cap setuptools when setup.py uses removed APIs - #1264
Conversation
Detect pkg_resources imports and dry_run keyword arguments in setup.py via AST parsing, and automatically append a setuptools version cap to build-system requirements. setuptools 81 removed distutils dry_run parameters, and setuptools 82 removed pkg_resources entirely. Closes python-wheel-build#1263 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Vikash Shaw <vshaw@redhat.com>
📝 WalkthroughWalkthroughBuild-system dependency resolution now parses Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/fromager/dependencies.py`:
- Around line 164-167: Update the AST Call handling in the dependency analysis
so dry_run is recorded only when the call target resolves to a tracked
removed-API import, rather than for every keyword named dry_run. Preserve
existing removed-API detection and add a regression test confirming unrelated
local calls such as copy_assets are ignored.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 74bf20a1-8b86-435a-ba81-799fe50f64d2
📒 Files selected for processing (2)
src/fromager/dependencies.pytests/test_dependencies.py
| elif isinstance(node, ast.Call): | ||
| for kw in node.keywords: | ||
| if kw.arg == "dry_run": | ||
| findings.add("dry_run") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Restrict dry_run detection to removed APIs.
Any local call such as copy_assets(dry_run=True) adds setuptools<81 despite not using setuptools functionality, potentially conflicting with a package’s declared setuptools requirement. Resolve the call target against tracked removed-API imports, and add a regression for an unrelated local dry_run parameter. Setuptools 81 removed setup.py dry-run support, not arbitrary Python keyword arguments. (setuptools.pypa.io)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/fromager/dependencies.py` around lines 164 - 167, Update the AST Call
handling in the dependency analysis so dry_run is recorded only when the call
target resolves to a tracked removed-API import, rather than for every keyword
named dry_run. Preserve existing removed-API detection and add a regression test
confirming unrelated local calls such as copy_assets are ignored.
dhellmann
left a comment
There was a problem hiding this comment.
This is useful, but it feels somewhat specific to the set of packages we happen to be building right now. What sorts of options did you explore for making this a plugin or otherwise making it a more configurable behavior so that a user can choose to enable it or not?
| logger.info( | ||
| "%s: auto-adding %s (setup.py uses removed APIs)", req.name, constraint | ||
| ) | ||
| requires.append(constraint) |
There was a problem hiding this comment.
What if there is already a setuptools requirement in the list? Shouldn't we modify that requirement instead of just adding another one?
There was a problem hiding this comment.
Yes, the code has to carefully merge constraints. If the upstream project or our downstream project overrides set a lower ceiling, then the new code must not raise the ceiling.
| logger.info( | ||
| "%s: auto-adding %s (setup.py uses removed APIs)", req.name, constraint | ||
| ) | ||
| requires.append(constraint) |
There was a problem hiding this comment.
Yes, the code has to carefully merge constraints. If the upstream project or our downstream project overrides set a lower ceiling, then the new code must not raise the ceiling.
| return requires | ||
|
|
||
|
|
||
| def _get_setuptools_constraint(sdist_root_dir: pathlib.Path) -> str | None: |
There was a problem hiding this comment.
The function should look into build_dir, not sdist_root_dir.
|
Closing in favor of a better approach: adding |
Summary
pkg_resourcesimports anddry_runkeyword arguments insetup.pyvia AST parsingsetuptools<82orsetuptools<81to build-system requirements when removed APIs are detectedsetup.pyor cleansetup.pyfilesCloses #1263
Motivation
setuptools 81 removed
distutils.spawn(dry_run=...)anddistutils.dir_util.remove_tree(dry_run=...). setuptools 82 removedpkg_resourcesentirely. Many packages on PyPI still use these in theirsetup.py, causing build failures when Fromager resolves an uncappedsetuptools.Currently every Fromager deployment must handle this per-package, either via a plugin overriding
get_build_system_dependenciesor viaproject_override.update_build_requires. In the AIPCC wheels builder, 22 identical plugins exist solely to add this constraint, and 127 additional packages have no protection at all.Implementation
The change adds a
_get_setuptools_constraint()function todependencies.pythat:setup.pyexists insdist_root_dirast.parse(avoids false positives from comments/strings)pkg_resourcesimports anddry_run=keyword argsdefault_get_build_system_dependenciescalls this function and appends the constraint when needed.Regression analysis
update_build_requires(e.g.,setuptools<80)default_get_build_system_dependenciesinternallysetup.pybuild-system-requirements.txtcacheTest plan
test_pkg_resources_import-- detectsimport pkg_resourcestest_pkg_resources_from_import-- detectsfrom pkg_resources import ...test_pkg_resources_submodule-- detectsimport pkg_resources.externtest_dry_run_keyword-- detectsdry_run=keyword argtest_both_returns_tighter-- returnssetuptools<81when both foundtest_clean_setup_py-- returnsNonefor clean setup.pytest_no_setup_py-- returnsNonewhen no setup.py existstest_syntax_error-- returnsNonegracefully on syntax errorstest_pkg_resources_in_string_not_detected-- no false positive from string literalstest_default_build_system_deps_adds_setuptools_constraint-- integration test through fullget_build_system_dependencies