Python plugin v2 passes shell-quoted python-packages as literal pip arguments
Metadata
Current evaluation
Closed and linked to internal Jira ticket CRAFT-5286. No code fix or merge was documented. The reported shell quoting bug remains unaddressed.
Suggested action: —
No scores available.
Issue body
### Bug Description
### Summary
Python plugin v2 fails when a `python-packages` entry contains version operators that require shell quoting, such as:
```yaml
parts:
app:
plugin: python
source: .
python-packages:
- gunicorn~=26.0
```
The generated build script passes literal quote characters to pip:
```text
PACKAGES="'gunicorn~=26.0'"
pip install ${PACKAGES}
```
This results in:
```text
ERROR: Invalid requirement: "'gunicorn~=26.0'":
Expected package name at the start of dependency specifier
```
Running pip directly with normal shell quoting succeeds:
```bash
pip install 'gunicorn~=26.0'
```
### Environment
- craft-parts: 2.35.0
- Plugin: Python v2
- Build base: Ubuntu 26.04
- Python: 3.14
- Observed through Rockcraft
### Cause
Python plugin v2 applies `shlex.quote()` to each package and joins the results into a scalar:
```python
packages = " ".join(
shlex.quote(pkg) for pkg in self._options.python_packages
)
pip_lines.append(f'PACKAGES="{packages}"')
```
The scalar is later expanded as:
```bash
pip install ${PACKAGES}
```
Shell syntax stored inside a variable is not re-evaluated. Consequently, quotes inserted by `shlex.quote()` become literal characters in the argument passed to pip.
Constraints such as `~=26.0`, `>=26.0,<27.0`, and wildcard pins are affected.
### Expected behavior
Each `python-packages` entry should be passed to pip as one argument without literal quote characters.
For example, the generated script could use Bash arrays:
```bash
REQUIREMENTS=(-r requirements.txt)
PACKAGES=('gunicorn~=26.0')
pip install "${REQUIREMENTS[@]}" "${PACKAGES[@]}"
```
Equivalent Python generation could be:
```python
requirements = " ".join(
f"-r {shlex.quote(req)}"
for req in self._options.python_requirements
)
packages = " ".join(
shlex.quote(pkg)
for pkg in self._options.python_packages
)
pip_lines.append(f"REQUIREMENTS=({requirements})")
pip_lines.append(f"PACKAGES=({packages})")
pip_lines.append('pip install "${REQUIREMENTS[@]}" "${PACKAGES[@]}"')
```
Failing [rockcraft ci](https://github.com/canonical/rockcraft/actions/runs/33177946956/job/98883063908).
### To Reproduce
1. Create a part using Python plugin v2.
2. Add `gunicorn~=26.0` to `python-packages`.
3. Run the build.
4. Observe pip receiving `"'gunicorn~=26.0'"`.
Include the failing build excerpt showing:
```text
PACKAGES="'gunicorn~=26.0'"
pip install "'gunicorn~=26.0'" --break-system-packages
Defaulting to user installation because normal site-packages is not writeable
ERROR: Invalid requirement: "'gunicorn~=26.0'": Expected package name at the start of dependency specifier
'gunicorn~=26.0'
^
```
### part yaml
```shell
parts:
app:
plugin: python
source: .
python-packages:
- gunicorn~=26.0
```
### Relevant log output
```shell
Building django-framework.dependencies
Copying needed target link from the system: /etc/ssl/certs/ca-certificates.crt
Copying needed target link from the system: /usr/share/zoneinfo/Etc/UTC
:: + [[ /root/parts/django-framework.dependencies/install/usr/bin/python3 == /usr/bin/* ]]
:: + REQUIREMENTS='-r requirements.txt'
:: + PACKAGES=''\''gunicorn~=26.0'\'''
:: + '[' -f setup.py -o -f pyproject.toml ']'
:: + pip install -r requirements.txt ''\''gunicorn~=26.0'\'''
:: ERROR: Invalid requirement: "'gunicorn~=26.0'": Expected package name at the start of dependency specifier
:: 'gunicorn~=26.0'
:: ^
Failed to run the build script for part 'django-framework.dependencies'.
Detailed information:
:: + PACKAGES=''\''gunicorn~=26.0'\'''
:: + '[' -f setup.py -o -f pyproject.toml ']'
:: + pip install -r requirements.txt ''\''gunicorn~=26.0'\'''
:: ERROR: Invalid requirement: "'gunicorn~=26.0'": Expected package name at the start of dependency specifier
:: 'gunicorn~=26.0'
:: ^
Recommended resolution: Check the build output and verify the project can work with the 'python' plugin.
For more information, check out: https://documentation.ubuntu.com/rockcraft/latest/reference/plugins/
```
Evaluation history
| Date | Model | Scores | Action | Summary |
|---|---|---|---|---|
| qwen3.6-35b-a3b-mtp-q6 | — | — | Closed and linked to internal Jira ticket CRAFT-5286. No code fix or merge was documented. The reported shell quoting bug remains unaddressed. |
Update history
| Date | Change |
|---|---|
| closed | |
| updated | |
| updated | |
| updated | |
| created |
Related issues
| Issue | Project | State | Summary | Similarity |
|---|---|---|---|---|
| #1884429 python plugin v2 doesn't quote python-packages | snapcraft (launchpad) | closed | Closed without resolution or comments. The python plugin v2 fails to quote python-packages entries, causing pip install failures with spaces and platform markers. The issue appears abandoned. |