diff --git a/.circleci/config.yml b/.circleci/config.yml index 3db1a9c62..0757851c9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,7 @@ jobs: parameters: stack: type: enum - enum: ["heroku-18", "heroku-20"] + enum: ["heroku-18", "heroku-20", "heroku-22"] docker: - image: cimg/ruby:2.7 working_directory: /mnt/ramdisk/project @@ -87,4 +87,4 @@ workflows: - hatchet: matrix: parameters: - stack: ["heroku-18", "heroku-20"] + stack: ["heroku-18", "heroku-20", "heroku-22"] diff --git a/CHANGELOG.md b/CHANGELOG.md index a46da74c4..29ef7b717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add support for Heroku-22 ([#1299](https://github.com/heroku/heroku-buildpack-python/pull/1299)). ## v211 (2022-05-17) diff --git a/Makefile b/Makefile index ffec65e7a..721ba0341 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ # These targets are not files .PHONY: lint lint-scripts lint-ruby compile builder-image buildenv deploy-runtimes publish -STACK ?= heroku-20 -STACKS ?= heroku-18 heroku-20 +STACK ?= heroku-22 +STACKS ?= heroku-18 heroku-20 heroku-22 PLATFORM := linux/amd64 FIXTURE ?= spec/fixtures/python_version_unspecified ENV_FILE ?= builds/dockerenv.default diff --git a/README.md b/README.md index 686a8ac00..aeb69e26a 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Specify a Python Runtime Supported runtime options include: -- `python-3.10.4` -- `python-3.9.13` -- `python-3.8.13` -- `python-3.7.13` +- `python-3.10.4` on all [supported stacks](https://devcenter.heroku.com/articles/stack#stack-support-details) +- `python-3.9.13` on all [supported stacks](https://devcenter.heroku.com/articles/stack#stack-support-details) +- `python-3.8.13` on Heroku-18 and Heroku-20 only +- `python-3.7.13` on Heroku-18 and Heroku-20 only diff --git a/builds/README.md b/builds/README.md index 0093b4192..a9094cf41 100644 --- a/builds/README.md +++ b/builds/README.md @@ -74,7 +74,7 @@ make deploy-runtimes RUNTIMES='python-X.Y.Z' STACKS='heroku-18' Multiple runtimes can also be specified (useful for when adding a new stack), like so: ```bash -make deploy-runtimes RUNTIMES='python-A.B.C python-X.Y.Z' STACKS='heroku-20' +make deploy-runtimes RUNTIMES='python-A.B.C python-X.Y.Z' STACKS='heroku-22' ``` Note: Both `RUNTIMES` and `STACKS` are space delimited. diff --git a/builds/heroku-22.Dockerfile b/builds/heroku-22.Dockerfile new file mode 100644 index 000000000..9ab6b484c --- /dev/null +++ b/builds/heroku-22.Dockerfile @@ -0,0 +1,20 @@ +FROM heroku/heroku:22-build + +ENV WORKSPACE_DIR="/app/builds" \ + S3_BUCKET="heroku-buildpack-python" \ + S3_PREFIX="heroku-22/" \ + STACK="heroku-22" + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + libsqlite3-dev \ + python3-pip \ + python3-setuptools \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY requirements.txt /app/ +RUN pip3 install --disable-pip-version-check --no-cache-dir -r /app/requirements.txt + +COPY . /app diff --git a/builds/runtimes/python b/builds/runtimes/python index 58d4633bf..11de1c8b4 100755 --- a/builds/runtimes/python +++ b/builds/runtimes/python @@ -12,6 +12,12 @@ VERSION=$(echo "${FORMULA_FILENAME}" | cut --delimiter '-' --fields 2) echo "Building Python ${VERSION}..." +if [[ "${STACK}" != "heroku-18" && "${STACK}" != "heroku-20" && "${VERSION}" == 3.[7-8].* ]]; then + echo "Python 3.7 and 3.8 are only supported on Heroku-20 and older!" >&2 + echo "Override the default stacks list using: STACKS='heroku-18 heroku-20'" >&2 + exit 1 +fi + # See: https://www.python.org/downloads/ -> "OpenPGP Public Keys" case "${VERSION}" in 3.10.*) diff --git a/spec/fixtures/python_3.9_outdated/runtime.txt b/spec/fixtures/python_3.9_outdated/runtime.txt index f72c5111f..540296197 100644 --- a/spec/fixtures/python_3.9_outdated/runtime.txt +++ b/spec/fixtures/python_3.9_outdated/runtime.txt @@ -1 +1 @@ -python-3.9.0 +python-3.9.12 diff --git a/spec/hatchet/getting_started_spec.rb b/spec/hatchet/getting_started_spec.rb index 1014dab89..a350561f1 100644 --- a/spec/hatchet/getting_started_spec.rb +++ b/spec/hatchet/getting_started_spec.rb @@ -3,12 +3,8 @@ require_relative '../spec_helper' RSpec.describe 'Python getting started project' do - it 'getting started app has no relative paths' do - buildpacks = [ - :default, - 'https://github.com/sharpstone/force_absolute_paths_buildpack' - ] - Hatchet::Runner.new('python-getting-started', buildpacks: buildpacks).deploy do |app| + it 'builds successfully' do + Hatchet::Runner.new('python-getting-started').deploy do |app| # Deploy works end end diff --git a/spec/hatchet/pipenv_spec.rb b/spec/hatchet/pipenv_spec.rb index 6eadefaae..ffee9e1e3 100644 --- a/spec/hatchet/pipenv_spec.rb +++ b/spec/hatchet/pipenv_spec.rb @@ -20,6 +20,19 @@ end end +RSpec.shared_examples 'aborts the build with a runtime not available message (Pipenv)' do |requested_version| + it 'aborts the build with a runtime not available message' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python version specified in Pipfile.lock + remote: ! Requested runtime (python-#{requested_version}) is not available for this stack (#{app.stack}). + remote: ! Aborting. More info: https://devcenter.heroku.com/articles/python-support + OUTPUT + end + end +end + RSpec.describe 'Pipenv support' do context 'without a Pipfile.lock' do let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_no_lockfile') } @@ -85,20 +98,11 @@ end end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } - it 'fails the build' do - # Python 2.7 is EOL, so it has not been built for Heroku-20. - app.deploy do |app| - expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: -----> Python app detected - remote: -----> Using Python version specified in Pipfile.lock - remote: ! Requested runtime (python-#{LATEST_PYTHON_2_7}) is not available for this stack (#{app.stack}). - remote: ! Aborting. More info: https://devcenter.heroku.com/articles/python-support - OUTPUT - end - end + # Python 2.7 is EOL, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message (Pipenv)', LATEST_PYTHON_2_7 end end @@ -112,34 +116,64 @@ end context 'with a Pipfile.lock containing python_version 3.6' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_3.6') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_3.6', allow_failure: allow_failure) } - it 'builds with the latest Python 3.6' do - app.deploy do |app| - expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) - remote: -----> Python app detected - remote: -----> Using Python version specified in Pipfile.lock - remote: cp: cannot stat '/tmp/build_.*/requirements.txt': No such file or directory - remote: -----> Installing python-#{LATEST_PYTHON_3_6} - remote: -----> Installing pip 21.3.1, setuptools 59.6.0 and wheel 0.37.1 - remote: -----> Installing dependencies with Pipenv 2020.11.15 - remote: Installing dependencies from Pipfile.lock \\(.*\\)... - remote: -----> Installing SQLite3 - REGEX + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'builds with the latest Python 3.6' do + app.deploy do |app| + expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) + remote: -----> Python app detected + remote: -----> Using Python version specified in Pipfile.lock + remote: cp: cannot stat '/tmp/build_.*/requirements.txt': No such file or directory + remote: -----> Installing python-#{LATEST_PYTHON_3_6} + remote: -----> Installing pip 21.3.1, setuptools 59.6.0 and wheel 0.37.1 + remote: -----> Installing dependencies with Pipenv 2020.11.15 + remote: Installing dependencies from Pipfile.lock \\(.*\\)... + remote: -----> Installing SQLite3 + REGEX + end end end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # Python 3.6 is EOL, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message (Pipenv)', LATEST_PYTHON_3_6 + end end context 'with a Pipfile.lock containing python_version 3.7' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_3.7') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_3.7', allow_failure: allow_failure) } - include_examples 'builds using Pipenv with the requested Python version', LATEST_PYTHON_3_7 + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'builds using Pipenv with the requested Python version', LATEST_PYTHON_3_7 + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # Python 3.7 is in the security fix only stage of its lifecycle, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message (Pipenv)', LATEST_PYTHON_3_7 + end end context 'with a Pipfile.lock containing python_version 3.8' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_3.8') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_3.8', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'builds using Pipenv with the requested Python version', LATEST_PYTHON_3_8 + end - include_examples 'builds using Pipenv with the requested Python version', LATEST_PYTHON_3_8 + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # Python 3.8 is in the security fix only stage of its lifecycle, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message (Pipenv)', LATEST_PYTHON_3_8 + end end context 'with a Pipfile.lock containing python_version 3.9' do @@ -155,24 +189,36 @@ end context 'with a Pipfile.lock containing python_full_version 3.10.0' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_full_version') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pipenv_python_full_version', allow_failure: allow_failure) } - it 'builds with the outdated Python version specified' do - app.deploy do |app| - expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) - remote: -----> Python app detected - remote: -----> Using Python version specified in Pipfile.lock - remote: ! Python has released a security update! Please consider upgrading to python-#{LATEST_PYTHON_3_10} - remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes - remote: cp: cannot stat '/tmp/build_.*/requirements.txt': No such file or directory - remote: -----> Installing python-3.10.0 - remote: -----> Installing pip 22.0.4, setuptools 60.10.0 and wheel 0.37.1 - remote: -----> Installing dependencies with Pipenv 2020.11.15 - remote: Installing dependencies from Pipfile.lock \\(99d8c9\\)... - remote: -----> Installing SQLite3 - REGEX + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'builds with the outdated Python version specified' do + app.deploy do |app| + expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX)) + remote: -----> Python app detected + remote: -----> Using Python version specified in Pipfile.lock + remote: ! Python has released a security update! Please consider upgrading to python-#{LATEST_PYTHON_3_10} + remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes + remote: cp: cannot stat '/tmp/build_.*/requirements.txt': No such file or directory + remote: -----> Installing python-3.10.0 + remote: -----> Installing pip 22.0.4, setuptools 60.10.0 and wheel 0.37.1 + remote: -----> Installing dependencies with Pipenv 2020.11.15 + remote: Installing dependencies from Pipfile.lock \\(99d8c9\\)... + remote: -----> Installing SQLite3 + REGEX + end end end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # Whilst Python 3.10 is supported on Heroku-22, only the latest version (3.10.4) has been built. + # TODO: Once newer Python 3.10 versions are released, adjust this test to use 3.10.4, + # which will work for all stacks. + include_examples 'aborts the build with a runtime not available message (Pipenv)', '3.10.0' + end end context 'with a Pipfile.lock containing an invalid python_version', diff --git a/spec/hatchet/python_update_warning_spec.rb b/spec/hatchet/python_update_warning_spec.rb index bd213ebed..6bde91480 100644 --- a/spec/hatchet/python_update_warning_spec.rb +++ b/spec/hatchet/python_update_warning_spec.rb @@ -50,7 +50,7 @@ end end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } include_examples 'aborts the build without showing an update warning', '2.7.17' @@ -65,7 +65,7 @@ include_examples 'warns there is a Python update available', '3.4.9', LATEST_PYTHON_3_4 end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } include_examples 'aborts the build without showing an update warning', '3.4.9' @@ -80,7 +80,7 @@ include_examples 'warns there is a Python update available', '3.5.9', LATEST_PYTHON_3_5 end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } include_examples 'aborts the build without showing an update warning', '3.5.9' @@ -88,59 +88,134 @@ end context 'with a runtime.txt containing python-3.6.11' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.6_outdated') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.6_outdated', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'warns there is a Python update available', '3.6.11', LATEST_PYTHON_3_6 + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } - include_examples 'warns there is a Python update available', '3.6.11', LATEST_PYTHON_3_6 + include_examples 'aborts the build without showing an update warning', '3.6.11' + end end context 'with a runtime.txt containing python-3.7.8' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.7_outdated') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.7_outdated', allow_failure: allow_failure) } - include_examples 'warns there is a Python update available', '3.7.8', LATEST_PYTHON_3_7 + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'warns there is a Python update available', '3.7.8', LATEST_PYTHON_3_7 + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + include_examples 'aborts the build without showing an update warning', '3.7.8' + end end context 'with a runtime.txt containing python-3.8.6' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.8_outdated') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.8_outdated', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'warns there is a Python update available', '3.8.6', LATEST_PYTHON_3_8 + end - include_examples 'warns there is a Python update available', '3.8.6', LATEST_PYTHON_3_8 + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + include_examples 'aborts the build without showing an update warning', '3.8.6' + end end - context 'with a runtime.txt containing python-3.9.0' do + context 'with a runtime.txt containing python-3.9.12' do let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.9_outdated') } - include_examples 'warns there is a Python update available', '3.9.0', LATEST_PYTHON_3_9 + include_examples 'warns there is a Python update available', '3.9.12', LATEST_PYTHON_3_9 end context 'with a runtime.txt containing python-3.10.0' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.10_outdated') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.10_outdated', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'warns there is a Python update available', '3.10.0', LATEST_PYTHON_3_10 + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } - include_examples 'warns there is a Python update available', '3.10.0', LATEST_PYTHON_3_10 + # Whilst Python 3.10 is supported on Heroku-22, only the latest version (3.10.4) has been built. + # TODO: Once newer Python 3.10 versions are released, adjust this test to use 3.10.4, + # which will work for all stacks. + include_examples 'aborts the build without showing an update warning', '3.10.0' + end end context 'with a runtime.txt containing pypy2.7-7.3.1' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_2.7_outdated') } - - it 'warns there is a PyPy update available' do - app.deploy do |app| - expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: ! The PyPy project has released a security update! Please consider upgrading to pypy2.7-#{LATEST_PYPY_2_7} - remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes - remote: -----> Installing pypy2.7-7.3.1 - OUTPUT + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_2.7_outdated', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'warns there is a PyPy update available' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: ! The PyPy project has released a security update! Please consider upgrading to pypy2.7-#{LATEST_PYPY_2_7} + remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes + remote: -----> Installing pypy2.7-7.3.1 + OUTPUT + end + end + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + it 'aborts the build without showing an update warning' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python version specified in runtime.txt + remote: ! Requested runtime (pypy2.7-7.3.1) is not available for this stack (#{app.stack}). + remote: ! Aborting. More info: https://devcenter.heroku.com/articles/python-support + OUTPUT + end end end end context 'with a runtime.txt containing pypy3.6-7.3.1' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_3.6_outdated') } - - it 'warns there is a PyPy update available' do - app.deploy do |app| - expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: ! The PyPy project has released a security update! Please consider upgrading to pypy3.6-#{LATEST_PYPY_3_6} - remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes - remote: -----> Installing pypy3.6-7.3.1 - OUTPUT + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_3.6_outdated', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'warns there is a PyPy update available' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: ! The PyPy project has released a security update! Please consider upgrading to pypy3.6-#{LATEST_PYPY_3_6} + remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes + remote: -----> Installing pypy3.6-7.3.1 + OUTPUT + end + end + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + it 'aborts the build without showing an update warning' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python version specified in runtime.txt + remote: ! Requested runtime (pypy3.6-7.3.1) is not available for this stack (#{app.stack}). + remote: ! Aborting. More info: https://devcenter.heroku.com/articles/python-support + OUTPUT + end end end end diff --git a/spec/hatchet/python_version_spec.rb b/spec/hatchet/python_version_spec.rb index 878e51c64..6f62bb286 100644 --- a/spec/hatchet/python_version_spec.rb +++ b/spec/hatchet/python_version_spec.rb @@ -19,13 +19,13 @@ end end -RSpec.shared_examples 'aborts the build with a runtime not available message' do |requested_version| +RSpec.shared_examples 'aborts the build with a runtime not available message' do |requested_runtime| it 'aborts the build with a runtime not available message' do app.deploy do |app| expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Python app detected remote: -----> Using Python version specified in runtime.txt - remote: ! Requested runtime (python-#{requested_version}) is not available for this stack (#{app.stack}). + remote: ! Requested runtime (#{requested_runtime}) is not available for this stack (#{app.stack}). remote: ! Aborting. More info: https://devcenter.heroku.com/articles/python-support OUTPUT end @@ -54,7 +54,7 @@ # This test performs an initial build using an older buildpack version, followed # by a build using the current version. This ensures that the current buildpack # can successfully read the version metadata written to the build cache in the past. - let(:buildpacks) { ['https://github.com/heroku/heroku-buildpack-python#v189'] } + let(:buildpacks) { ['https://github.com/heroku/heroku-buildpack-python#v209'] } it 'builds with the same Python version as the last build' do app.deploy do |app| @@ -63,14 +63,12 @@ app.push! expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Python app detected - remote: -----> No Python version was specified. Using the same version as the last build: python-3.6.12 + remote: -----> No Python version was specified. Using the same version as the last build: python-3.10.4 remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes - remote: ! Python has released a security update! Please consider upgrading to python-#{LATEST_PYTHON_3_6} - remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes remote: -----> No change in requirements detected, installing from cache - remote: -----> Using cached install of python-3.6.12 + remote: -----> Using cached install of python-3.10.4 OUTPUT - expect(app.run('python -V')).to include('Python 3.6.12') + expect(app.run('python -V')).to include('Python 3.10.4') end end end @@ -99,11 +97,11 @@ end end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } - # Python 2.7 is EOL, so it has not been built for Heroku-20. - include_examples 'aborts the build with a runtime not available message', LATEST_PYTHON_2_7 + # Python 2.7 is EOL, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message', "python-#{LATEST_PYTHON_2_7}" end end @@ -132,11 +130,11 @@ end end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } - # Python 3.4 is EOL, so it has not been built for Heroku-20. - include_examples 'aborts the build with a runtime not available message', LATEST_PYTHON_3_4 + # Python 3.4 is EOL, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message', "python-#{LATEST_PYTHON_3_4}" end end @@ -161,43 +159,73 @@ end end - context 'when using Heroku-20', stacks: %w[heroku-20] do + context 'when using Heroku-20 or newer', stacks: %w[heroku-20 heroku-22] do let(:allow_failure) { true } - # Python 3.5 is EOL, so it has not been built for Heroku-20. - include_examples 'aborts the build with a runtime not available message', LATEST_PYTHON_3_5 + # Python 3.5 is EOL, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message', "python-#{LATEST_PYTHON_3_5}" end end context 'when runtime.txt contains python-3.6.15' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.6') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.6', allow_failure: allow_failure) } - it 'builds with Python 3.6.15' do - app.deploy do |app| - expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: -----> Python app detected - remote: -----> Using Python version specified in runtime.txt - remote: -----> Installing python-#{LATEST_PYTHON_3_6} - remote: -----> Installing pip 21.3.1, setuptools 59.6.0 and wheel 0.37.1 - remote: -----> Installing SQLite3 - remote: -----> Installing requirements with pip - remote: Collecting urllib3 - OUTPUT - expect(app.run('python -V')).to include("Python #{LATEST_PYTHON_3_6}") + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'builds with Python 3.6.15' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python version specified in runtime.txt + remote: -----> Installing python-#{LATEST_PYTHON_3_6} + remote: -----> Installing pip 21.3.1, setuptools 59.6.0 and wheel 0.37.1 + remote: -----> Installing SQLite3 + remote: -----> Installing requirements with pip + remote: Collecting urllib3 + OUTPUT + expect(app.run('python -V')).to include("Python #{LATEST_PYTHON_3_6}") + end end end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # Python 3.6 is EOL, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message', "python-#{LATEST_PYTHON_3_6}" + end end context 'when runtime.txt contains python-3.7.13' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.7') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.7', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'builds with the requested Python version', LATEST_PYTHON_3_7 + end - include_examples 'builds with the requested Python version', LATEST_PYTHON_3_7 + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # Python 3.7 is in the security fix only stage of its lifecycle, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message', "python-#{LATEST_PYTHON_3_7}" + end end context 'when runtime.txt contains python-3.8.13' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.8') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.8', allow_failure: allow_failure) } + + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + include_examples 'builds with the requested Python version', LATEST_PYTHON_3_8 + end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } - include_examples 'builds with the requested Python version', LATEST_PYTHON_3_8 + # Python 3.8 is in the security fix only stage of its lifecycle, so has not been built for newer stacks. + include_examples 'aborts the build with a runtime not available message', "python-#{LATEST_PYTHON_3_8}" + end end context 'when runtime.txt contains python-3.9.13' do @@ -213,47 +241,67 @@ end context 'when runtime.txt contains pypy2.7-7.3.2' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_2.7') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_2.7', allow_failure: allow_failure) } - it 'builds with PyPy2.7 v7.3.2' do - app.deploy do |app| - expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: -----> Python app detected - remote: -----> Using Python version specified in runtime.txt - remote: -----> Installing pypy2.7-#{LATEST_PYPY_2_7} - remote: -----> Installing pip 20.3.4, setuptools 44.1.1 and wheel 0.37.1 - remote: -----> Installing SQLite3 - remote: -----> Installing requirements with pip - remote: Collecting urllib3 - OUTPUT - expect(app.run('python -V')).to include('Python 2.7', "PyPy #{LATEST_PYPY_2_7}") + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'builds with PyPy2.7 v7.3.2' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python version specified in runtime.txt + remote: -----> Installing pypy2.7-#{LATEST_PYPY_2_7} + remote: -----> Installing pip 20.3.4, setuptools 44.1.1 and wheel 0.37.1 + remote: -----> Installing SQLite3 + remote: -----> Installing requirements with pip + remote: Collecting urllib3 + OUTPUT + expect(app.run('python -V')).to include('Python 2.7', "PyPy #{LATEST_PYPY_2_7}") + end end end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # The beta PyPy support is deprecated and so not being made available for new stacks. + include_examples 'aborts the build with a runtime not available message', "pypy2.7-#{LATEST_PYPY_2_7}" + end end context 'when runtime.txt contains pypy3.6-7.3.2' do - let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_3.6') } + let(:allow_failure) { false } + let(:app) { Hatchet::Runner.new('spec/fixtures/pypy_3.6', allow_failure: allow_failure) } - it 'builds with PyPy3.6 v7.3.2' do - app.deploy do |app| - expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: -----> Python app detected - remote: -----> Using Python version specified in runtime.txt - remote: -----> Installing pypy3.6-#{LATEST_PYPY_3_6} - remote: -----> Installing pip 21.3.1, setuptools 59.6.0 and wheel 0.37.1 - remote: -----> Installing SQLite3 - remote: -----> Installing requirements with pip - remote: Collecting urllib3 - OUTPUT - expect(app.run('python -V')).to include('Python 3.6', "PyPy #{LATEST_PYPY_3_6}") + context 'when using Heroku-18 or Heroku-20', stacks: %w[heroku-18 heroku-20] do + it 'builds with PyPy3.6 v7.3.2' do + app.deploy do |app| + expect(clean_output(app.output)).to include(<<~OUTPUT) + remote: -----> Python app detected + remote: -----> Using Python version specified in runtime.txt + remote: -----> Installing pypy3.6-#{LATEST_PYPY_3_6} + remote: -----> Installing pip 21.3.1, setuptools 59.6.0 and wheel 0.37.1 + remote: -----> Installing SQLite3 + remote: -----> Installing requirements with pip + remote: Collecting urllib3 + OUTPUT + expect(app.run('python -V')).to include('Python 3.6', "PyPy #{LATEST_PYPY_3_6}") + end end end + + context 'when using Heroku-22', stacks: %w[heroku-22] do + let(:allow_failure) { true } + + # The beta PyPy support is deprecated and so not being made available for new stacks. + include_examples 'aborts the build with a runtime not available message', "pypy3.6-#{LATEST_PYPY_3_6}" + end end context 'when runtime.txt contains an invalid python version string' do let(:app) { Hatchet::Runner.new('spec/fixtures/python_version_invalid', allow_failure: true) } - include_examples 'aborts the build with a runtime not available message', 'X.Y.Z' + include_examples 'aborts the build with a runtime not available message', 'python-X.Y.Z' end context 'when runtime.txt contains stray whitespace' do @@ -269,7 +317,7 @@ end context 'when the requested Python version has changed since the last build' do - let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.6') } + let(:app) { Hatchet::Runner.new('spec/fixtures/python_3.9') } it 'builds with the new Python version after removing the old install' do app.deploy do |app| @@ -280,7 +328,7 @@ expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Python app detected remote: -----> Using Python version specified in runtime.txt - remote: -----> Python version has changed from python-#{LATEST_PYTHON_3_6} to python-#{LATEST_PYTHON_3_10}, clearing cache + remote: -----> Python version has changed from python-#{LATEST_PYTHON_3_9} to python-#{LATEST_PYTHON_3_10}, clearing cache remote: -----> No change in requirements detected, installing from cache remote: -----> Installing python-#{LATEST_PYTHON_3_10} remote: -----> Installing pip 22.0.4, setuptools 60.10.0 and wheel 0.37.1 diff --git a/spec/hatchet/stack_spec.rb b/spec/hatchet/stack_spec.rb index da7b9fbaf..961523be2 100644 --- a/spec/hatchet/stack_spec.rb +++ b/spec/hatchet/stack_spec.rb @@ -3,32 +3,30 @@ require_relative '../spec_helper' RSpec.describe 'Stack changes' do - context 'when the stack is upgraded from Heroku-18 to Heroku-20', stacks: %w[heroku-18] do + context 'when the stack is upgraded from Heroku-20 to Heroku-22', stacks: %w[heroku-20] do # This test performs an initial build using an older buildpack version, followed # by a build using the current version. This ensures that the current buildpack # can successfully read the stack metadata written to the build cache in the past. # The buildpack version chosen is one which had an older default Python version, so # we can also prove that clearing the cache didn't lose the Python version metadata. - let(:buildpacks) { ['https://github.com/heroku/heroku-buildpack-python#v208'] } + let(:buildpacks) { ['https://github.com/heroku/heroku-buildpack-python#v209'] } let(:app) { Hatchet::Runner.new('spec/fixtures/python_version_unspecified', buildpacks: buildpacks) } it 'clears the cache before installing again whilst preserving the sticky Python version' do app.deploy do |app| - expect(app.output).to include('Building on the Heroku-18 stack') - app.update_stack('heroku-20') + expect(app.output).to include('Building on the Heroku-20 stack') + app.update_stack('heroku-22') update_buildpacks(app, [:default]) app.commit! app.push! # TODO: The requirements output shouldn't say "installing from cache", since it's not. expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Python app detected - remote: -----> No Python version was specified. Using the same version as the last build: python-3.10.3 + remote: -----> No Python version was specified. Using the same version as the last build: python-3.10.4 remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes - remote: ! Python has released a security update! Please consider upgrading to python-#{LATEST_PYTHON_3_10} - remote: Learn More: https://devcenter.heroku.com/articles/python-runtimes - remote: -----> Stack has changed from heroku-18 to heroku-20, clearing cache + remote: -----> Stack has changed from heroku-20 to heroku-22, clearing cache remote: -----> No change in requirements detected, installing from cache - remote: -----> Installing python-3.10.3 + remote: -----> Installing python-3.10.4 remote: -----> Installing pip 22.0.4, setuptools 60.10.0 and wheel 0.37.1 remote: -----> Installing SQLite3 remote: -----> Installing requirements with pip @@ -38,13 +36,13 @@ end end - context 'when the stack is downgraded from Heroku-20 to Heroku-18', stacks: %w[heroku-20] do + context 'when the stack is downgraded from Heroku-22 to Heroku-20', stacks: %w[heroku-22] do let(:app) { Hatchet::Runner.new('spec/fixtures/python_version_unspecified') } it 'clears the cache before installing again' do app.deploy do |app| - expect(app.output).to include('Building on the Heroku-20 stack') - app.update_stack('heroku-18') + expect(app.output).to include('Building on the Heroku-22 stack') + app.update_stack('heroku-20') app.commit! app.push! # TODO: Stop using Python scripts before Python is installed (or else ensure system @@ -53,10 +51,12 @@ remote: -----> Python app detected remote: -----> No Python version was specified. Using the same version as the last build: python-#{DEFAULT_PYTHON_VERSION} remote: To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes - remote: python: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by python) - remote: python: /lib/x86_64-linux-gnu/libpthread.so.0: version `GLIBC_2.30' not found (required by python) - remote: python: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by python) - remote: -----> Stack has changed from heroku-20 to heroku-18, clearing cache + remote: python: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by python) + remote: python: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.35' not found (required by /app/.heroku/python/lib/libpython3.10.so.1.0) + remote: python: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by /app/.heroku/python/lib/libpython3.10.so.1.0) + remote: python: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by /app/.heroku/python/lib/libpython3.10.so.1.0) + remote: python: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by /app/.heroku/python/lib/libpython3.10.so.1.0) + remote: -----> Stack has changed from heroku-22 to heroku-20, clearing cache remote: -----> No change in requirements detected, installing from cache remote: -----> Installing python-#{DEFAULT_PYTHON_VERSION} remote: -----> Installing pip 22.0.4, setuptools 60.10.0 and wheel 0.37.1 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9569d8556..e478ba9c8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true ENV['HATCHET_BUILDPACK_BASE'] ||= 'https://github.com/heroku/heroku-buildpack-python.git' -ENV['HATCHET_DEFAULT_STACK'] ||= 'heroku-20' +ENV['HATCHET_DEFAULT_STACK'] ||= 'heroku-22' require 'rspec/core' require 'hatchet'