Skip to content

Commit 6fd8079

Browse files
authored
Backport error message/build output improvements from CNB (#1783)
Backports the error message/build output improvements made as part of: - heroku/buildpacks-python#352 - heroku/buildpacks-python#353 - heroku/buildpacks-python#354 - heroku/buildpacks-python#355 Plus: - applies similar changes to equivalent error messages that only exist in the classic buildpack (eg the Pipenv errors) - switches to use of contractions as per the CX team's style guidelines GUS-W-18225347. GUS-W-18421778.
1 parent 3e99fc6 commit 6fd8079

19 files changed

+185
-113
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
- Improved the instructions for migrating from `runtime.txt` to `.python-version`. ([#1783](https://github.com/heroku/heroku-buildpack-python/pull/1783))
6+
- Improved the error message instructions shown when `.python-version`, `runtime.txt` or `Pipfile.lock` contain an invalid Python version. ([#1783](https://github.com/heroku/heroku-buildpack-python/pull/1783))
7+
- Improved the rendering of the error message shown if `.python-version` or `runtime.txt` contain stray invisible characters (such as ASCII control codes). ([#1783](https://github.com/heroku/heroku-buildpack-python/pull/1783))
8+
- Improved the upgrade instructions shown for EOL and unsupported Python versions. ([#1783](https://github.com/heroku/heroku-buildpack-python/pull/1783))
59

610
## [v282] - 2025-05-02
711

bin/compile

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,20 @@ if [[ "${python_version_origin}" == "runtime.txt" ]]; then
152152
Please delete your runtime.txt file and create a new file named:
153153
.python-version
154154
155-
Make sure to include the '.' at the start of the filename.
155+
Make sure to include the '.' character at the start of the
156+
filename. Don't add a file extension such as '.txt'.
156157
157-
In the new file, specify your app's Python version without
158-
quotes or a 'python-' prefix. For example:
158+
In the new file, specify your app's major Python version number
159+
only. Don't include quotes or a 'python-' prefix.
160+
161+
For example, to request the latest version of Python ${python_major_version},
162+
update your .python-version file so it contains exactly:
159163
${python_major_version}
160164
161-
We strongly recommend that you use the major version form
162-
instead of pinning to an exact version, since it will allow
163-
your app to receive Python security updates.
165+
We strongly recommend that you don't specify the Python patch
166+
version number, since it will pin your app to an exact Python
167+
version and so stop your app from receiving security updates
168+
each time it builds.
164169
165170
In the future support for runtime.txt will be removed and
166171
this warning will be made an error.

bin/steps/nltk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ if is_module_available 'nltk'; then
3535
Error: Unable to download NLTK data.
3636
3737
The 'python -m nltk.downloader' command to download NLTK
38-
data did not exit successfully.
38+
data didn't exit successfully.
3939
4040
See the log output above for more information.
4141
EOF

lib/checks.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ function checks::ensure_supported_stack() {
2525
;;
2626
*)
2727
output::error <<-EOF
28-
Error: The '${stack}' stack is not recognised.
28+
Error: The '${stack}' stack isn't recognised.
2929
30-
This buildpack does not recognise or support the '${stack}' stack.
30+
This buildpack doesn't recognise or support the '${stack}' stack.
3131
3232
If '${stack}' is a valid stack, make sure that you are using the latest
33-
version of this buildpack and have not pinned to an older release:
33+
version of this buildpack and haven't pinned to an older release:
3434
https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks
3535
https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references
3636
EOF
@@ -57,7 +57,7 @@ function checks::warn_if_duplicate_python_buildpack() {
5757
from a buildpack run earlier in the build.
5858
5959
This normally means there are duplicate Python buildpacks set
60-
on your app, which is not supported, can cause errors and
60+
on your app, which isn't supported, can cause errors and
6161
slow down builds.
6262
6363
Check the buildpacks set on your app and remove any duplicate
@@ -99,13 +99,13 @@ function checks::warn_if_existing_python_dir_present() {
9999
$(find .heroku/python/ -maxdepth 2 || true)
100100
101101
Writing to internal locations used by the Python buildpack
102-
is not supported and can cause unexpected errors.
102+
isn't supported and can cause unexpected errors.
103103
104104
If you have committed a '.heroku/python/' directory to your
105105
Git repo, you must delete it or use a different location.
106106
107107
Otherwise, check that an earlier buildpack or 'bin/pre_compile'
108-
hook has not created this directory.
108+
hook hasn't created this directory.
109109
110110
If you have a use-case that requires writing to this location,
111111
please comment on:

lib/package_manager.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ function package_manager::determine_package_manager() {
2020
2121
A 'Pipfile' file was found, however, the associated 'Pipfile.lock'
2222
Pipenv lockfile was not. This means your app dependency versions
23-
are not pinned, which means the package versions used on Heroku
23+
aren't pinned, which means the package versions used on Heroku
2424
might not match those installed in other environments.
2525
2626
For now, we will install your dependencies without a lockfile,
2727
however, in the future this warning will become an error.
2828
2929
Run 'pipenv lock' locally to generate the lockfile, and make sure
30-
that 'Pipfile.lock' is not listed in '.gitignore' or '.slugignore'.
30+
that 'Pipfile.lock' isn't listed in '.gitignore' or '.slugignore'.
3131
EOF
3232
package_managers_found+=(pipenv)
3333
package_managers_found_display_text+=("Pipfile (Pipenv)")

lib/pip.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function pip::install_pip_setuptools_wheel() {
6565
6666
Try building again to see if the error resolves itself.
6767
68-
If that does not help, check the status of PyPI here:
68+
If that doesn't help, check the status of PyPI here:
6969
https://status.python.org
7070
EOF
7171
meta_set "failure_reason" "install-package-manager::pip"

lib/pipenv.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function pipenv::install_pipenv() {
3838
3939
Try building again to see if the error resolves itself.
4040
41-
If that does not help, check the status of PyPI here:
41+
If that doesn't help, check the status of PyPI here:
4242
https://status.python.org
4343
EOF
4444
meta_set "failure_reason" "install-package-manager::pipenv"

lib/poetry.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function poetry::install_poetry() {
8585
8686
Try building again to see if the error resolves itself.
8787
88-
If that does not help, check the status of PyPI here:
88+
If that doesn't help, check the status of PyPI here:
8989
https://status.python.org
9090
EOF
9191
meta_set "failure_reason" "install-package-manager::poetry"

lib/python.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function python::install() {
100100
https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks
101101
https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references
102102
103-
We also strongly recommend that you do not pin your app to an
103+
We also strongly recommend that you don't pin your app to an
104104
exact Python version such as ${python_full_version}, and instead only specify
105105
the major Python version of ${python_major_version} in your ${python_version_origin} file.
106106
This will allow your app to receive the latest available Python

lib/python_version.sh

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ function python_version::read_requested_python_version() {
5252

5353
local runtime_txt_path="${build_dir}/runtime.txt"
5454
if [[ -f "${runtime_txt_path}" ]]; then
55-
contents="$(cat "${runtime_txt_path}")"
55+
contents="$(cat --show-nonprinting "${runtime_txt_path}")"
5656
version="$(python_version::parse_runtime_txt "${contents}")"
5757
origin="runtime.txt"
5858
return 0
5959
fi
6060

6161
local python_version_file_path="${build_dir}/.python-version"
6262
if [[ -f "${python_version_file_path}" ]]; then
63-
contents="$(cat "${python_version_file_path}")"
63+
contents="$(cat --show-nonprinting "${python_version_file_path}")"
6464
version="$(python_version::parse_python_version_file "${contents}")"
6565
origin=".python-version"
6666
return 0
@@ -106,23 +106,28 @@ function python_version::parse_runtime_txt() {
106106
The following file contents were found, which aren't valid:
107107
${contents:0:100}
108108
109-
However, the runtime.txt file is deprecated since it has
110-
been replaced by the .python-version file. As such, we
111-
recommend that you switch to using a .python-version file
109+
However, the runtime.txt file is deprecated since it has been
110+
replaced by the more widely supported .python-version file.
111+
As such, we recommend that you switch to using .python-version
112112
instead of fixing your runtime.txt file.
113113
114114
Please delete your runtime.txt file and create a new file named:
115115
.python-version
116116
117-
Make sure to include the '.' at the start of the filename.
117+
Make sure to include the '.' character at the start of the
118+
filename. Don't add a file extension such as '.txt'.
118119
119-
In the new file, specify your app's Python version without
120-
quotes or a 'python-' prefix. For example:
120+
In the new file, specify your app's major Python version number
121+
only. Don't include quotes or a 'python-' prefix.
122+
123+
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
124+
update your .python-version file so it contains exactly:
121125
${DEFAULT_PYTHON_MAJOR_VERSION}
122126
123-
We strongly recommend that you use the major version form
124-
instead of pinning to an exact version, since it will allow
125-
your app to receive Python security updates.
127+
We strongly recommend that you don't specify the Python patch
128+
version number, since it will pin your app to an exact Python
129+
version and so stop your app from receiving security updates
130+
each time it builds.
126131
EOF
127132
meta_set "failure_reason" "runtime-txt::invalid-version"
128133
meta_set "failure_detail" "${contents:0:50}"
@@ -160,19 +165,20 @@ function python_version::parse_python_version_file() {
160165
${line}
161166
162167
However, the Python version must be specified as either:
163-
1. The major version only: 3.X (recommended)
164-
2. An exact patch version: 3.X.Y
168+
1. The major version only, for example: ${DEFAULT_PYTHON_MAJOR_VERSION} (recommended)
169+
2. An exact patch version, for example: ${DEFAULT_PYTHON_MAJOR_VERSION}.999
165170
166-
Don't include quotes or a 'python-' prefix. To include
167-
comments, add them on their own line, prefixed with '#'.
171+
Don't include quotes, a 'python-' prefix or wildcards. Any
172+
code comments must be on a separate line prefixed with '#'.
168173
169174
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
170-
update your .python-version file so it contains:
175+
update your .python-version file so it contains exactly:
171176
${DEFAULT_PYTHON_MAJOR_VERSION}
172177
173-
We strongly recommend that you use the major version form
174-
instead of pinning to an exact version, since it will allow
175-
your app to receive Python security updates.
178+
We strongly recommend that you don't specify the Python patch
179+
version number, since it will pin your app to an exact Python
180+
version and so stop your app from receiving security updates
181+
each time it builds.
176182
EOF
177183
meta_set "failure_reason" "python-version-file::invalid-version"
178184
meta_set "failure_detail" "${line:0:50}"
@@ -185,10 +191,11 @@ function python_version::parse_python_version_file() {
185191
186192
No Python version was found in your .python-version file.
187193
188-
Update the file so that it contains a valid Python version.
194+
Update the file so that it contains your app's major Python
195+
version number. Don't include quotes or a 'python-' prefix.
189196
190197
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
191-
update your .python-version file so it contains:
198+
update your .python-version file so it contains exactly:
192199
${DEFAULT_PYTHON_MAJOR_VERSION}
193200
194201
If the file already contains a version, check the line doesn't
@@ -212,6 +219,10 @@ function python_version::parse_python_version_file() {
212219
213220
Update the file so it contains only one Python version.
214221
222+
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
223+
update your .python-version file so it contains exactly:
224+
${DEFAULT_PYTHON_MAJOR_VERSION}
225+
215226
If you have added comments to the file, make sure that those
216227
lines begin with a '#', so that they are ignored.
217228
EOF
@@ -278,15 +289,18 @@ function python_version::read_pipenv_python_version() {
278289
${version}
279290
280291
However, the Python version must be specified as either:
281-
1. The major version only: 3.X (recommended)
282-
2. An exact patch version: 3.X.Y
292+
1. The major version only, for example: ${DEFAULT_PYTHON_MAJOR_VERSION} (recommended)
293+
2. An exact patch version, for example: ${DEFAULT_PYTHON_MAJOR_VERSION}.999
294+
295+
Wildcards aren't supported.
283296
284297
Please update your Pipfile to use a valid Python version and
285298
then run 'pipenv lock' to regenerate Pipfile.lock.
286299
287-
We strongly recommend that you use the major version form
288-
instead of pinning to an exact version, since it will allow
289-
your app to receive Python security updates.
300+
We strongly recommend that you don't specify the Python patch
301+
version number, since it will pin your app to an exact Python
302+
version and so stop your app from receiving security updates
303+
each time it builds.
290304
291305
For more information, see:
292306
https://pipenv.pypa.io/en/stable/specifiers.html#specifying-versions-of-python
@@ -329,12 +343,21 @@ function python_version::resolve_python_version() {
329343
Please upgrade to at least Python 3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION} by configuring an
330344
explicit Python version for your app.
331345
332-
Create a .python-version file in the root directory of your
333-
app, that contains a Python version like:
334-
3.${NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION}
346+
Create a new file in the root directory of your app named:
347+
.python-version
348+
349+
Make sure to include the '.' character at the start of the
350+
filename. Don't add a file extension such as '.txt'.
351+
352+
In the new file, specify the new major Python version number
353+
only. Don't include quotes or a 'python-' prefix.
335354
336-
When creating this file make sure to include the '.' at the
337-
start of the filename.
355+
For example, to request the latest version of Python 3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION},
356+
update your .python-version file so it contains exactly:
357+
3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION}
358+
359+
If possible, we recommend upgrading all the way to Python ${DEFAULT_PYTHON_MAJOR_VERSION},
360+
since it contains many performance and usability improvements.
338361
EOF
339362
else
340363
output::error <<-EOF
@@ -349,6 +372,9 @@ function python_version::resolve_python_version() {
349372
350373
Please upgrade to at least Python 3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION} by changing the
351374
version in your ${python_version_origin} file.
375+
376+
If possible, we recommend upgrading all the way to Python ${DEFAULT_PYTHON_MAJOR_VERSION},
377+
since it contains many performance and usability improvements.
352378
EOF
353379
fi
354380
meta_set "failure_reason" "python-version::eol"
@@ -464,7 +490,7 @@ function python_version::warn_if_patch_update_available() {
464490
465491
Update your ${python_version_origin} file to use the new version.
466492
467-
We strongly recommend that you do not pin your app to an
493+
We strongly recommend that you don't pin your app to an
468494
exact Python version such as ${python_full_version}, and instead only specify
469495
the major Python version of ${python_major_version} in your ${python_version_origin} file.
470496
This will allow your app to receive the latest available Python

lib/utils.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ function utils::bundled_pip_module_path() {
3535
echo "${bundled_pip_wheel}/pip"
3636
else
3737
output::error <<-EOF
38-
Internal Error: Unable to locate the bundled copy of pip.
38+
Internal Error: Unable to locate the Python stdlib's bundled pip.
3939
40-
The Python buildpack could not locate the copy of pip bundled
41-
inside Python's 'ensurepip' module:
40+
Couldn't find the pip wheel file bundled inside the Python
41+
stdlib's 'ensurepip' module:
4242
4343
$(find "${bundled_wheels_dir}/" 2>&1 || find "${python_home}/" -type d 2>&1 || true)
4444
EOF

spec/fixtures/python_version_file_invalid_version/.python-version

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
# So are empty lines, and leading/trailing whitespace.
55

66

7-
3.12.0invalid
7+
# The version number has a trailing control character.
8+
3.12.0
89

910

1011
# 2.7.18
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
python-3.12.0invalid
1+
python-3.12.0

spec/hatchet/checks_spec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
remote: ! from a buildpack run earlier in the build.
1919
remote: !
2020
remote: ! This normally means there are duplicate Python buildpacks set
21-
remote: ! on your app, which is not supported, can cause errors and
21+
remote: ! on your app, which isn't supported, can cause errors and
2222
remote: ! slow down builds.
2323
remote: !
2424
remote: ! Check the buildpacks set on your app and remove any duplicate
@@ -59,13 +59,13 @@
5959
remote: ! .heroku/python/bin/python
6060
remote: !
6161
remote: ! Writing to internal locations used by the Python buildpack
62-
remote: ! is not supported and can cause unexpected errors.
62+
remote: ! isn't supported and can cause unexpected errors.
6363
remote: !
6464
remote: ! If you have committed a '.heroku/python/' directory to your
6565
remote: ! Git repo, you must delete it or use a different location.
6666
remote: !
6767
remote: ! Otherwise, check that an earlier buildpack or 'bin/pre_compile'
68-
remote: ! hook has not created this directory.
68+
remote: ! hook hasn't created this directory.
6969
remote: !
7070
remote: ! If you have a use-case that requires writing to this location,
7171
remote: ! please comment on:
@@ -76,10 +76,10 @@
7676
remote: -----> Using Python #{DEFAULT_PYTHON_MAJOR_VERSION} specified in .python-version
7777
remote: -----> Using cached install of Python #{DEFAULT_PYTHON_FULL_VERSION}
7878
remote:
79-
remote: ! Internal Error: Unable to locate the bundled copy of pip.
79+
remote: ! Internal Error: Unable to locate the Python stdlib's bundled pip.
8080
remote: !
81-
remote: ! The Python buildpack could not locate the copy of pip bundled
82-
remote: ! inside Python's 'ensurepip' module:
81+
remote: ! Couldn't find the pip wheel file bundled inside the Python
82+
remote: ! stdlib's 'ensurepip' module:
8383
remote: !
8484
remote: ! find: ‘/app/.heroku/python/lib/python3.13/ensurepip/_bundled/’: No such file or directory
8585
remote: ! /app/.heroku/python/

spec/hatchet/nltk_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
remote: ! Error: Unable to download NLTK data.
6969
remote: !
7070
remote: ! The 'python -m nltk.downloader' command to download NLTK
71-
remote: ! data did not exit successfully.
71+
remote: ! data didn't exit successfully.
7272
remote: !
7373
remote: ! See the log output above for more information.
7474
remote:

0 commit comments

Comments
 (0)