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

Improve the error message when bundled pip cannot be found #1720

Merged
merged 1 commit into from
Dec 12, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

- Improved the error message shown when pip install fails due to pip rejecting a package with invalid version metadata. ([#1718](https://github.com/heroku/heroku-buildpack-python/pull/1718))
- Improved the error message shown when the copy of pip bundled in the `ensurepip` module cannot be found. ([#1720](https://github.com/heroku/heroku-buildpack-python/pull/1720))

## [v270] - 2024-12-10

Expand Down
2 changes: 1 addition & 1 deletion lib/pip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function pip::install_pip_setuptools_wheel() {
# We use the pip wheel bundled within Python's standard library to install our chosen
# pip version, since it's faster than `ensurepip` followed by an upgrade in place.
local bundled_pip_module_path
bundled_pip_module_path="$(utils::bundled_pip_module_path "${python_home}")"
bundled_pip_module_path="$(utils::bundled_pip_module_path "${python_home}" "${python_major_version}")"

meta_set "pip_version" "${PIP_VERSION}"

Expand Down
33 changes: 20 additions & 13 deletions lib/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,31 @@ function utils::get_requirement_version() {
# pip version from PyPI, saving us from having to download the usual pip bootstrap script.
function utils::bundled_pip_module_path() {
local python_home="${1}"
local python_major_version="${2}"

# We have to use a glob since the bundled wheel filename contains the pip version, which
# differs between Python versions. We also have to handle the case where there are multiple
# matching pip wheels, since in some versions of Python (eg 3.9.0) multiple versions of pip
# were accidentally bundled upstream. Note: This implementation relies upon `nullglob` being
# set, which is the case thanks to the `bin/utils` that was run earlier.
local bundled_pip_wheel_list=("${python_home}"/lib/python*/ensurepip/_bundled/pip-*.whl)
local bundled_pip_wheel="${bundled_pip_wheel_list[0]}"

if [[ -z "${bundled_pip_wheel}" ]]; then
output::error <<-'EOF'
Internal Error: Unable to locate the ensurepip pip wheel file.
local bundled_wheels_dir="${python_home}/lib/python${python_major_version}/ensurepip/_bundled"

# We have to use a glob since the bundled wheel filename contains the pip version, which differs
# between Python versions. We use compgen to avoid having to set nullglob, since there may be no
# matches in the case of a broken Python install. We also have to handle the case where there are
# multiple matching pip wheels, since in some versions of Python (eg 3.9.0) multiple versions of
# pip were accidentally bundled upstream (we use tail since we want the newest pip version).
if bundled_pip_wheel="$(compgen -G "${bundled_wheels_dir}/pip-*.whl" | tail --lines=1)"; then
# The pip module exists inside the pip wheel (which is a zip file), however, Python can load
# it directly by appending the module name to the zip filename, as though it were a path.
echo "${bundled_pip_wheel}/pip"
else
output::error <<-EOF
Internal Error: Unable to locate the bundled copy of pip.
The Python buildpack could not locate the copy of pip bundled
inside Python's 'ensurepip' module:
$(find "${bundled_wheels_dir}/" 2>&1 || find "${python_home}/" -type d 2>&1 || true)
EOF
meta_set "failure_reason" "bundled-pip-not-found"
exit 1
fi

echo "${bundled_pip_wheel}/pip"
}

function utils::abort_internal_error() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

# This file emulates a Python install having been committed to the app's Git repo.
# For example, by downloading a slug, extracting it, and committing the results.

set -euo pipefail

exit 0
1 change: 1 addition & 0 deletions spec/fixtures/python_in_app_source/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
Empty file.
30 changes: 30 additions & 0 deletions spec/hatchet/checks_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require_relative '../spec_helper'

RSpec.describe 'Buildpack validation checks' do
context 'when the app source contains a broken Python install' do
let(:app) { Hatchet::Runner.new('spec/fixtures/python_in_app_source', allow_failure: true) }

it 'fails detection' do
app.deploy do |app|
expect(clean_output(app.output)).to include(<<~OUTPUT)
remote: -----> Python app detected
remote: -----> Using Python #{DEFAULT_PYTHON_MAJOR_VERSION} specified in .python-version
remote: -----> Using cached install of Python #{DEFAULT_PYTHON_FULL_VERSION}
remote:
remote: ! Internal Error: Unable to locate the bundled copy of pip.
remote: !
remote: ! The Python buildpack could not locate the copy of pip bundled
remote: ! inside Python's 'ensurepip' module:
remote: !
remote: ! find: ‘/app/.heroku/python/lib/python3.13/ensurepip/_bundled/’: No such file or directory
remote: ! /app/.heroku/python/
remote: ! /app/.heroku/python/bin
remote:
remote: ! Push rejected, failed to compile Python app.
OUTPUT
end
end
end
end