diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c2f84b3f..4d4630981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] +- Fixed Poetry installation when using outdated patch versions of Python 3.8, 3.9 and 3.10, whose bundled pip doesn't support the `--python` option. ([#1687](https://github.com/heroku/heroku-buildpack-python/pull/1687)) ## [v264] - 2024-11-06 diff --git a/bin/compile b/bin/compile index 9ce9cb60f..da98a59a5 100755 --- a/bin/compile +++ b/bin/compile @@ -168,7 +168,7 @@ case "${package_manager}" in pipenv::install_pipenv ;; poetry) - poetry::install_poetry "${python_home}" "${CACHE_DIR}" "${EXPORT_PATH}" + poetry::install_poetry "${CACHE_DIR}" "${EXPORT_PATH}" ;; *) utils::abort_internal_error "Unhandled package manager: ${package_manager}" diff --git a/lib/poetry.sh b/lib/poetry.sh index bcceccd59..b21d7e583 100644 --- a/lib/poetry.sh +++ b/lib/poetry.sh @@ -7,9 +7,8 @@ set -euo pipefail POETRY_VERSION=$(utils::get_requirement_version 'poetry') function poetry::install_poetry() { - local python_home="${1}" - local cache_dir="${2}" - local export_file="${3}" + local cache_dir="${1}" + local export_file="${2}" # We store Poetry in the build cache, since we only need it during the build. local poetry_root="${cache_dir}/.heroku/python-poetry" @@ -39,19 +38,17 @@ function poetry::install_poetry() { # The Poetry directory will already exist in the relocated cache case mentioned above. rm -rf "${poetry_root}" - python -m venv --without-pip "${poetry_venv_dir}" - - # We use the pip wheel bundled within Python's standard library to install Poetry. - # Whilst Poetry does still require pip for some tasks (such as package uninstalls), - # it bundles its own copy for use as a fallback. As such we don't need to install pip - # into the Poetry venv (and in fact, Poetry wouldn't use this install anyway, since - # it only finds an external pip if it exists in the target venv). - local bundled_pip_module_path - bundled_pip_module_path="$(utils::bundled_pip_module_path "${python_home}")" + # We can't use the pip wheel bundled within Python's standard library to install Poetry + # (which would allow us to use `--without-pip` here to skip the pip install), since it + # requires using the `--python` option, which was only added in pip v22.3. And whilst + # all major Python versions we support now bundled a newer pip than that, some apps + # are still using outdated patch releases of those Python versions, whose bundled pip + # can be older (for example Python 3.9.0 ships with pip v20.2.1). Once Python 3.10 EOLs + # we can switch back to the previous approach since Python 3.11.0 ships with pip v22.3. + python -m venv "${poetry_venv_dir}" if ! { - python "${bundled_pip_module_path}" \ - --python "${poetry_venv_dir}" \ + "${poetry_venv_dir}/bin/pip" \ install \ --disable-pip-version-check \ --no-cache-dir \ diff --git a/spec/fixtures/poetry_oldest_python/.python-version b/spec/fixtures/poetry_oldest_python/.python-version new file mode 100644 index 000000000..a5c4c7633 --- /dev/null +++ b/spec/fixtures/poetry_oldest_python/.python-version @@ -0,0 +1 @@ +3.9.0 diff --git a/spec/fixtures/poetry_oldest_python/poetry.lock b/spec/fixtures/poetry_oldest_python/poetry.lock new file mode 100644 index 000000000..5f96ba1c8 --- /dev/null +++ b/spec/fixtures/poetry_oldest_python/poetry.lock @@ -0,0 +1,17 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "3b8ea94b618b2c3590bcbb224f2e2c723e6c14c8da90ba6917ca4a4fdcb716cf" diff --git a/spec/fixtures/poetry_oldest_python/pyproject.toml b/spec/fixtures/poetry_oldest_python/pyproject.toml new file mode 100644 index 000000000..c69fd9272 --- /dev/null +++ b/spec/fixtures/poetry_oldest_python/pyproject.toml @@ -0,0 +1,6 @@ +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +python = "^3.9" +typing-extensions = "*" diff --git a/spec/hatchet/poetry_spec.rb b/spec/hatchet/poetry_spec.rb index 5aa092068..0476ae811 100644 --- a/spec/hatchet/poetry_spec.rb +++ b/spec/hatchet/poetry_spec.rb @@ -185,6 +185,35 @@ end end + # This checks that the Poetry bootstrap works even with older bundled pip, and that + # our chosen Poetry version also supports our oldest supported Python version. + context 'when using the oldest supported Python version' do + let(:app) { Hatchet::Runner.new('spec/fixtures/poetry_oldest_python') } + + it 'installs successfully' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python 3.9.0 specified in .python-version + remote: -----> Installing Python 3.9.0 + remote: + remote: ! Warning: A Python security update is available! + remote: ! + remote: ! Upgrade as soon as possible to: Python #{LATEST_PYTHON_3_9} + remote: ! See: https://devcenter.heroku.com/articles/python-runtimes + remote: + remote: -----> Installing Poetry #{POETRY_VERSION} + remote: -----> Installing dependencies using 'poetry install --sync --only main' + remote: Installing dependencies from lock file + remote: + remote: Package operations: 1 install, 0 updates, 0 removals + remote: + remote: - Installing typing-extensions (4.12.2) + OUTPUT + end + end + end + context 'when poetry.lock is out of sync with pyproject.toml' do let(:app) { Hatchet::Runner.new('spec/fixtures/poetry_lockfile_out_of_sync', allow_failure: true) }