-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Enhanced setup.py with dual setup.py and pyproject.toml package support #44810
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
base: main
Are you sure you want to change the base?
Changes from all commits
f249b3f
cb8ff0b
881eb2f
9bf77bf
3d2b7a8
9f9c4ea
377e4f1
46fb773
aa0c7ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,13 +11,41 @@ | |||||||||||||||||||||||||||
| import copy | ||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||
| import runpy | ||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Use tomllib for Python 3.11+, fallback to tomli for older versions | ||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| import tomllib as toml | ||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||
| import tomli as toml | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| root_folder = os.path.abspath(os.path.dirname(__file__)) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # pull in any packages that exist in the root directory | ||||||||||||||||||||||||||||
| # Helper function to check if pyproject.toml has [project] section | ||||||||||||||||||||||||||||
| def has_project_section(pyproject_path): | ||||||||||||||||||||||||||||
| """Check if a pyproject.toml file has a [project] section.""" | ||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| with open(pyproject_path, 'rb') as f: | ||||||||||||||||||||||||||||
| pyproject_data = toml.load(f) | ||||||||||||||||||||||||||||
| return 'project' in pyproject_data | ||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| except Exception: | |
| except (OSError, toml.TOMLDecodeError): |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is misleading. During discovery (lines 39-47), packages are only added if their pyproject.toml has a [project] section. However, a package could also be added via setup.py discovery (lines 35-36). So when checking during installation, a pyproject.toml file might exist without a [project] section, or it might have a [project] section but the package was discovered via setup.py. The comment should be clarified or removed.
| # Determine which file to use: pyproject.toml with [project] or setup.py | |
| # Since we already filtered during discovery, if pyproject.toml exists it has [project] | |
| # Determine which file to use: prefer pyproject.toml when it exists and has a [project] section, | |
| # otherwise fall back to setup.py or skip if neither is usable. |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The has_project_section() function is called redundantly during installation. Packages are already filtered to have a [project] section during discovery (lines 41, 46), but the function is called again here. This is inefficient and causes double file I/O. Since packages were already validated during discovery, you can simplify this to just use_pyproject = os.path.exists(pkg_pyproject_path) or remove the redundant check entirely if setup.py should take precedence when both files exist.
| use_pyproject = os.path.exists(pkg_pyproject_path) and has_project_section(pkg_pyproject_path) | |
| use_pyproject = os.path.exists(pkg_pyproject_path) |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code assumes pip is available by calling sys.executable -m pip. If pip is not installed or not available as a module, this will fail with a non-zero exit code. While the code prints a warning (line 105), it doesn't provide any guidance to the user about installing pip. Consider adding a more helpful error message when pip is not available, or checking for pip availability before attempting to use it.
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command mapping logic checks for "develop" in sys.argv but also checks for '-e' and '--editable' flags. However, when using the root setup.py (e.g., python setup.py develop), sys.argv will contain "develop" but not '-e' or '--editable'. The check for these flags appears redundant and potentially confusing since they are pip-specific flags that wouldn't be passed to setup.py. Consider removing the editable flag checks or clarifying when they would be used.
| elif "develop" in sys.argv or any(arg in sys.argv for arg in ['-e', '--editable']): | |
| elif "develop" in sys.argv: |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When commands other than "install" or "develop" are used, pyproject.toml packages are skipped entirely (line 101). This means commands like bdist_wheel, sdist, or other setup.py commands won't work for pyproject.toml packages. If these commands need to be supported, consider mapping them to equivalent pip/build commands or documenting this limitation clearly.
| # For other commands like --version, --help, etc., skip pyproject.toml packages | |
| # These commands are meant for the root setup.py, not individual packages | |
| # For other commands (e.g., sdist, bdist_wheel, --version, --help, etc.), | |
| # pyproject.toml-based packages are not handled per-package by this script. | |
| # These commands are meant for the root setup.py, not individual packages. | |
| print( | |
| f"Skipping pyproject.toml-based package '{pkg_name}' for setup.py " | |
| f"command(s) {sys.argv[1:]}. Only 'install' and 'develop' (or " | |
| f"editable variants) are supported for per-package operations. " | |
| "Use 'pip install' or 'python -m build' in the package directory " | |
| "to build distribution artifacts for this package.", | |
| file=sys.stderr, | |
| ) |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subprocess.run call uses capture_output=False, which means stdout and stderr will be printed to the console. However, the function doesn't check result.returncode to propagate failures. If a package installation fails, the script continues installing other packages and only prints a warning. This could lead to incomplete installations going unnoticed. Consider either:
- Raising an exception on non-zero return codes to halt the installation
- Collecting failures and reporting them at the end
- At minimum, using a more severe logging mechanism than just a warning
| print(f"Warning: Package {pkg_name} installation returned non-zero exit code", file=sys.stderr) | |
| print(f"Error: Package {pkg_name} installation returned non-zero exit code {result.returncode}", file=sys.stderr) | |
| sys.exit(result.returncode) |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for handling packages with both setup.py and pyproject.toml is unclear. According to the PR description, "Packages with both → prefers setup.py for backward compatibility", but the code at line 87 prefers pyproject.toml over setup.py (using use_pyproject as the first condition). The conditions should be reordered to check for setup.py first if backward compatibility is the goal, or the PR description should be updated to match the actual behavior.
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The broad exception handler catches all exceptions (including KeyboardInterrupt and SystemExit) and only prints them to stderr without re-raising. This means if a user tries to cancel installation with Ctrl+C, the script will continue processing other packages. Consider either:
- Catching specific exceptions (e.g.,
subprocess.CalledProcessError,OSError) - Re-raising KeyboardInterrupt and SystemExit
- Using
except Exception as e:instead of bareexceptto exclude system-exiting exceptions
| print(f"Warning: No setup.py or pyproject.toml found for {pkg_name}", file=sys.stderr) | |
| print(f"Warning: No setup.py or pyproject.toml found for {pkg_name}", file=sys.stderr) | |
| except KeyboardInterrupt: | |
| # Allow user-initiated interruption (e.g., Ctrl+C) to stop the script | |
| raise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback import of
tomliwill fail if the package is not installed. For Python versions 3.8-3.10, thetomlipackage needs to be available as a dependency or the script will crash with an ImportError. Consider either:tomlito a requirements file or as a conditional dependency (for Python < 3.11)