← Back to issue list

Python plugin v2 passes shell-quoted python-packages as literal pip arguments

View original Github issue

Metadata

Project
craft-parts
Number
#1702
Type
issue
State
closed
Author
alithethird
Labels
Status: Triaged
Created
Updated
Closed

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.
73%