Skip to content
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

plugins: be more specific in search for payload Python #487

Merged
merged 1 commit into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion craft_parts/plugins/python_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,16 @@ def get_build_commands(self) -> List[str]:
set +e
install_dir="{self._part_info.part_install_dir}/usr/bin"
stage_dir="{self._part_info.stage_dir}/usr/bin"
payload_python=$(find "$install_dir" "$stage_dir" -type f -executable -name "python3.*" -print -quit 2>/dev/null)

# look for the right Python version - if the venv was created with python3.10,
# look for python3.10
basename=$(basename $(readlink -f ${{PARTS_PYTHON_VENV_INTERP_PATH}}))
echo Looking for a Python interpreter called \\"${{basename}}\\" in the payload...
payload_python=$(find "$install_dir" "$stage_dir" -type f -executable -name "${{basename}}" -print -quit 2>/dev/null)
tigarmo marked this conversation as resolved.
Show resolved Hide resolved

if [ -n "$payload_python" ]; then
# We found a provisioned interpreter, use it.
echo Found interpreter in payload: \\"${{payload_python}}\\"
installed_python="${{payload_python##{self._part_info.part_install_dir}}}"
if [ "$installed_python" = "$payload_python" ]; then
# Found a staged interpreter.
Expand All @@ -149,6 +155,7 @@ def get_build_commands(self) -> List[str]:
fi
else
# Otherwise use what _get_system_python_interpreter() told us.
echo "Python interpreter not found in payload."
symlink_target="{python_interpreter}"
fi

Expand Down
89 changes: 89 additions & 0 deletions tests/integration/plugins/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import textwrap
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -259,3 +260,91 @@ def _get_script_interpreter(self) -> str:

primed_script = Path("prime/bin/pip")
assert primed_script.open().readline().rstrip() == "#!/my/script/interpreter"


# A part whose override-build copies the system's Python interpreter into the
# payload
PART_WITH_PAYLOAD_PYTHON = """\
parts:
foo:
plugin: python
source: .
override-build: |
# Put a binary called "{payload_python}" in the payload
mkdir -p ${{CRAFT_PART_INSTALL}}/usr/bin
cp {real_python} ${{CRAFT_PART_INSTALL}}/usr/bin/{payload_python}
craftctl default
"""


def test_find_payload_python_bad_version(new_dir):
"""Test that the build fails if a payload interpreter is needed but it's the
wrong Python version."""

class MyPythonPlugin(craft_parts.plugins.plugins.PythonPlugin):
@override
def _get_system_python_interpreter(self) -> Optional[str]:
# To have the build fail after failing to find the payload interpreter
return None

plugins.register({"python": MyPythonPlugin})

real_python = Path(sys.executable).resolve()
real_basename = real_python.name

# Copy the "real" binary into the payload before calling the plugin's build,
# but name it "python3.3".
parts_yaml = PART_WITH_PAYLOAD_PYTHON.format(
real_python=real_python, payload_python="python3.3"
)
parts = yaml.safe_load(parts_yaml)

lf = LifecycleManager(parts, application_name="test_python", cache_dir=new_dir)
actions = lf.plan(Step.PRIME)

out = Path("out.txt")
with out.open(mode="w") as outfile, pytest.raises(errors.ScriptletRunError):
with lf.action_executor() as ctx:
ctx.execute(actions, stdout=outfile)

output = out.read_text()
expected_text = textwrap.dedent(
f"""\
Looking for a Python interpreter called "{real_basename}" in the payload...
Python interpreter not found in payload.
No suitable Python interpreter found, giving up.
"""
)
assert expected_text in output


def test_find_payload_python_good_version(new_dir):
"""Test that the build succeeds if a payload interpreter is needed, and it's
the right Python version."""

real_python = Path(sys.executable).resolve()
real_basename = real_python.name

# Copy the "real" binary into the payload before calling the plugin's build.
parts_yaml = PART_WITH_PAYLOAD_PYTHON.format(
real_python=real_python, payload_python=real_basename
)
parts = yaml.safe_load(parts_yaml)

lf = LifecycleManager(parts, application_name="test_python", cache_dir=new_dir)
actions = lf.plan(Step.PRIME)

out = Path("out.txt")
with out.open(mode="w") as outfile:
with lf.action_executor() as ctx:
ctx.execute(actions, stdout=outfile)

output = out.read_text()
payload_python = Path(f"parts/foo/install/usr/bin/{real_basename}").resolve()
expected_text = textwrap.dedent(
f"""\
Looking for a Python interpreter called "{real_basename}" in the payload...
Found interpreter in payload: "{payload_python}"
"""
)
assert expected_text in output
9 changes: 8 additions & 1 deletion tests/unit/plugins/test_python_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,16 @@ def get_build_commands(
set +e
install_dir="{new_dir}/parts/p1/install/usr/bin"
stage_dir="{new_dir}/stage/usr/bin"
payload_python=$(find "$install_dir" "$stage_dir" -type f -executable -name "python3.*" -print -quit 2>/dev/null)

# look for the right Python version - if the venv was created with python3.10,
# look for python3.10
basename=$(basename $(readlink -f ${{PARTS_PYTHON_VENV_INTERP_PATH}}))
echo Looking for a Python interpreter called \\"${{basename}}\\" in the payload...
payload_python=$(find "$install_dir" "$stage_dir" -type f -executable -name "${{basename}}" -print -quit 2>/dev/null)

if [ -n "$payload_python" ]; then
# We found a provisioned interpreter, use it.
echo Found interpreter in payload: \\"${{payload_python}}\\"
installed_python="${{payload_python##{new_dir}/parts/p1/install}}"
if [ "$installed_python" = "$payload_python" ]; then
# Found a staged interpreter.
Expand All @@ -94,6 +100,7 @@ def get_build_commands(
fi
else
# Otherwise use what _get_system_python_interpreter() told us.
echo "Python interpreter not found in payload."
symlink_target="$(readlink -f "$(which "${{PARTS_PYTHON_INTERPRETER}}")")"
fi

Expand Down