Skip to content

Commit f943a2a

Browse files
committed
Deprecate support for the runtime.txt file
The `runtime.txt` file is a classic Heroku Python buildpack invention that's not widely supported in the Python ecosystem. Instead, most other tooling (pyenv, package managers, GitHub Actions, dependency update bots etc) support/use the `.python-version` file. As such, we recently added `.python-version` support to both the Python CNB and the classic Python buildpack, and updated all documentation and guides to use it instead of the `runtime.txt` file. eg: https://devcenter.heroku.com/articles/python-runtimes We would prefer apps use the new file, since it helps ensure their deployed app is using the same Python version used locally (via eg pyenv or uv) or in CI. As such this adds a deprecation warning for apps using `runtime.txt`. Closes #1642. GUS-W-16878260.
1 parent b3e0b29 commit f943a2a

File tree

7 files changed

+196
-78
lines changed

7 files changed

+196
-78
lines changed

CHANGELOG.md

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

33
## [Unreleased]
44

5+
- Deprecated support for the `runtime.txt` file. ([#1743](https://github.com/heroku/heroku-buildpack-python/pull/1743))
6+
- Improved the error messages shown when `.python-version`, `runtime.txt` or `Pipfile.lock` contain an invalid Python version. ([#1743](https://github.com/heroku/heroku-buildpack-python/pull/1743))
57

68
## [v275] - 2025-01-13
79

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ For example, to request the latest patch release of Python 3.13, create a `.pyth
2626
the root directory of your app containing:
2727
`3.13`
2828

29+
We strongly recommend that you use the major version form instead of pinning to an exact version,
30+
since it will allow your app to receive Python security updates.
31+
2932
The buildpack will look for a Python version in the following places (in descending order of precedence):
3033

3134
1. `runtime.txt` file (deprecated)

bin/compile

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ meta_set "python_version_reason" "${python_version_origin}"
125125

126126
# TODO: More strongly recommend specifying a Python version (eg switch the messaging to
127127
# be a warning instead, after version resolution, and mention .python-version inline)
128-
# TODO: Add runtime.txt deprecation warning.
129128
case "${python_version_origin}" in
130129
default)
131130
output::step "No Python version was specified. Using the buildpack default: Python ${requested_python_version}"
@@ -145,6 +144,31 @@ python_major_version="${python_full_version%.*}"
145144
meta_set "python_version" "${python_full_version}"
146145
meta_set "python_version_major" "${python_major_version}"
147146

147+
if [[ "${python_version_origin}" == "runtime.txt" ]]; then
148+
output::warning <<-EOF
149+
Warning: The runtime.txt file is deprecated.
150+
151+
The runtime.txt file is deprecated since it has been replaced
152+
by the more widely supported .python-version file.
153+
154+
Please delete your runtime.txt file and create a new file named:
155+
.python-version
156+
157+
Make sure to include the '.' at the start of the filename.
158+
159+
In the new file, specify your app's Python version without
160+
quotes or a 'python-' prefix. For example:
161+
${python_major_version}
162+
163+
We strongly recommend that you use the major version form
164+
instead of pinning to an exact version, since it will allow
165+
your app to receive Python security updates.
166+
167+
In the future support for runtime.txt will be removed and
168+
this warning will be made an error.
169+
EOF
170+
fi
171+
148172
cache::restore "${BUILD_DIR}" "${CACHE_DIR}" "${STACK}" "${cached_python_full_version}" "${python_full_version}" "${package_manager}"
149173

150174
# The directory for the .profile.d scripts.

lib/python_version.sh

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ PYTHON_FULL_VERSION_REGEX="${INT_REGEX}\.${INT_REGEX}\.${INT_REGEX}"
3030
# resolved to an exact Python version.
3131
#
3232
# If an app specifies the Python version via multiple means, then the order of precedence is:
33-
# 1. runtime.txt
34-
# 2. .python-version
35-
# 3. Pipfile.lock (`python_full_version` field)
36-
# 4. Pipfile.lock (`python_version` field)
33+
# 1. `runtime.txt` file (deprecated)
34+
# 2. `.python-version` file (recommended)
35+
# 3. The `python_full_version` field in the `Pipfile.lock` file
36+
# 4. The `python_version` field in the `Pipfile.lock` file
3737
#
3838
# If a version wasn't specified by the app, then new apps/those with an empty cache will use
3939
# a buildpack default version for the first build, and then subsequent cached builds will use
@@ -100,21 +100,29 @@ function python_version::parse_runtime_txt() {
100100
output::error <<-EOF
101101
Error: Invalid Python version in runtime.txt.
102102
103-
The Python version specified in 'runtime.txt' isn't in
104-
the correct format.
103+
The Python version specified in your runtime.txt file isn't
104+
in the correct format.
105105
106-
The following file contents were found:
106+
The following file contents were found, which aren't valid:
107107
${contents}
108108
109-
However, the version must be specified as either:
110-
1. 'python-<major>.<minor>' (recommended, for automatic patch updates)
111-
2. 'python-<major>.<minor>.<patch>' (to pin to an exact patch version)
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
112+
instead of fixing your runtime.txt file.
112113
113-
Remember to include the 'python-' prefix. Comments aren't supported.
114+
Please delete your runtime.txt file and create a new file named:
115+
.python-version
114116
115-
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
116-
update the 'runtime.txt' file so it contains:
117-
python-${DEFAULT_PYTHON_MAJOR_VERSION}
117+
Make sure to include the '.' at the start of the filename.
118+
119+
In the new file, specify your app's Python version without
120+
quotes or a 'python-' prefix. For example:
121+
${DEFAULT_PYTHON_MAJOR_VERSION}
122+
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.
118126
EOF
119127
meta_set "failure_reason" "runtime-txt::invalid-version"
120128
exit 1
@@ -144,22 +152,26 @@ function python_version::parse_python_version_file() {
144152
output::error <<-EOF
145153
Error: Invalid Python version in .python-version.
146154
147-
The Python version specified in '.python-version' isn't in
148-
the correct format.
155+
The Python version specified in your .python-version file
156+
isn't in the correct format.
149157
150158
The following version was found:
151159
${line}
152160
153-
However, the version must be specified as either:
154-
1. '<major>.<minor>' (recommended, for automatic patch updates)
155-
2. '<major>.<minor>.<patch>' (to pin to an exact patch version)
161+
However, the Python version must be specified as either:
162+
1. The major version only: 3.X (recommended)
163+
2. An exact patch version: 3.X.Y
156164
157165
Don't include quotes or a 'python-' prefix. To include
158166
comments, add them on their own line, prefixed with '#'.
159167
160168
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
161-
update the '.python-version' file so it contains:
169+
update your .python-version file so it contains:
162170
${DEFAULT_PYTHON_MAJOR_VERSION}
171+
172+
We strongly recommend that you use the major version form
173+
instead of pinning to an exact version, since it will allow
174+
your app to receive Python security updates.
163175
EOF
164176
meta_set "failure_reason" "python-version-file::invalid-version"
165177
exit 1
@@ -169,10 +181,13 @@ function python_version::parse_python_version_file() {
169181
output::error <<-EOF
170182
Error: Invalid Python version in .python-version.
171183
172-
No Python version was found in the '.python-version' file.
184+
No Python version was found in your .python-version file.
173185
174-
Update the file so that it contains a valid Python version
175-
such as '${DEFAULT_PYTHON_MAJOR_VERSION}'.
186+
Update the file so that it contains a valid Python version.
187+
188+
For example, to request the latest version of Python ${DEFAULT_PYTHON_MAJOR_VERSION},
189+
update your .python-version file so it contains:
190+
${DEFAULT_PYTHON_MAJOR_VERSION}
176191
177192
If the file already contains a version, check the line doesn't
178193
begin with a '#', otherwise it will be treated as a comment.
@@ -184,8 +199,7 @@ function python_version::parse_python_version_file() {
184199
output::error <<-EOF
185200
Error: Invalid Python version in .python-version.
186201
187-
Multiple Python versions were found in the '.python-version'
188-
file:
202+
Multiple versions were found in your .python-version file:
189203
190204
$(
191205
IFS=$'\n'
@@ -194,8 +208,8 @@ function python_version::parse_python_version_file() {
194208
195209
Update the file so it contains only one Python version.
196210
197-
If the additional versions are actually comments, prefix
198-
those lines with '#'.
211+
If you have added comments to the file, make sure that those
212+
lines begin with a '#', so that they are ignored.
199213
EOF
200214
meta_set "failure_reason" "python-version-file::multiple-versions"
201215
exit 1
@@ -245,20 +259,24 @@ function python_version::read_pipenv_python_version() {
245259
echo "${version}"
246260
else
247261
output::error <<-EOF
248-
Error: Invalid Python version in Pipfile / Pipfile.lock.
262+
Error: Invalid Python version in Pipfile.lock.
249263
250-
The Python version specified in Pipfile / Pipfile.lock by the
251-
'python_version' or 'python_full_version' field isn't valid.
264+
The Python version specified in your Pipfile.lock file by the
265+
'python_version' or 'python_full_version' fields isn't valid.
252266
253267
The following version was found:
254268
${version}
255269
256-
However, the version must be specified as either:
257-
1. '<major>.<minor>' (recommended, for automatic patch updates)
258-
2. '<major>.<minor>.<patch>' (to pin to an exact patch version)
270+
However, the Python version must be specified as either:
271+
1. The major version only: 3.X (recommended)
272+
2. An exact patch version: 3.X.Y
273+
274+
Please update your Pipfile to use a valid Python version and
275+
then run 'pipenv lock' to regenerate Pipfile.lock.
259276
260-
Please update your 'Pipfile' to use a valid Python version and
261-
then run 'pipenv lock' to regenerate the lockfile.
277+
We strongly recommend that you use the major version form
278+
instead of pinning to an exact version, since it will allow
279+
your app to receive Python security updates.
262280
263281
For more information, see:
264282
https://pipenv.pypa.io/en/stable/specifiers.html#specifying-versions-of-python
@@ -297,10 +315,15 @@ function python_version::resolve_python_version() {
297315
As such, it's no longer supported by this buildpack:
298316
https://devcenter.heroku.com/articles/python-support#supported-python-versions
299317
300-
Please upgrade to at least Python 3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION} by creating a
301-
.python-version file in the root directory of your app,
302-
that contains a Python version like:
303-
3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION}
318+
Please upgrade to at least Python 3.${OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION} by configuring an
319+
explicit Python version for your app.
320+
321+
Create a .python-version file in the root directory of your
322+
app, that contains a Python version like:
323+
3.${NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION}
324+
325+
When creating this file make sure to include the '.' at the
326+
start of the filename.
304327
EOF
305328
else
306329
output::error <<-EOF

spec/hatchet/pipenv_spec.rb

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -219,20 +219,24 @@
219219
expect(clean_output(app.output)).to include(<<~OUTPUT)
220220
remote: -----> Python app detected
221221
remote:
222-
remote: ! Error: Invalid Python version in Pipfile / Pipfile.lock.
222+
remote: ! Error: Invalid Python version in Pipfile.lock.
223223
remote: !
224-
remote: ! The Python version specified in Pipfile / Pipfile.lock by the
225-
remote: ! 'python_version' or 'python_full_version' field isn't valid.
224+
remote: ! The Python version specified in your Pipfile.lock file by the
225+
remote: ! 'python_version' or 'python_full_version' fields isn't valid.
226226
remote: !
227227
remote: ! The following version was found:
228228
remote: ! ^3.12
229229
remote: !
230-
remote: ! However, the version must be specified as either:
231-
remote: ! 1. '<major>.<minor>' (recommended, for automatic patch updates)
232-
remote: ! 2. '<major>.<minor>.<patch>' (to pin to an exact patch version)
230+
remote: ! However, the Python version must be specified as either:
231+
remote: ! 1. The major version only: 3.X (recommended)
232+
remote: ! 2. An exact patch version: 3.X.Y
233+
remote: !
234+
remote: ! Please update your Pipfile to use a valid Python version and
235+
remote: ! then run 'pipenv lock' to regenerate Pipfile.lock.
233236
remote: !
234-
remote: ! Please update your 'Pipfile' to use a valid Python version and
235-
remote: ! then run 'pipenv lock' to regenerate the lockfile.
237+
remote: ! We strongly recommend that you use the major version form
238+
remote: ! instead of pinning to an exact version, since it will allow
239+
remote: ! your app to receive Python security updates.
236240
remote: !
237241
remote: ! For more information, see:
238242
remote: ! https://pipenv.pypa.io/en/stable/specifiers.html#specifying-versions-of-python
@@ -253,18 +257,22 @@
253257
remote:
254258
remote: ! Error: Invalid Python version in Pipfile / Pipfile.lock.
255259
remote: !
256-
remote: ! The Python version specified in Pipfile / Pipfile.lock by the
257-
remote: ! 'python_version' or 'python_full_version' field isn't valid.
260+
remote: ! The Python version specified in your Pipfile.lock file by the
261+
remote: ! 'python_version' or 'python_full_version' fields isn't valid.
258262
remote: !
259263
remote: ! The following version was found:
260264
remote: ! 3.9.*
261265
remote: !
262-
remote: ! However, the version must be specified as either:
263-
remote: ! 1. '<major>.<minor>' (recommended, for automatic patch updates)
264-
remote: ! 2. '<major>.<minor>.<patch>' (to pin to an exact patch version)
266+
remote: ! However, the Python version must be specified as either:
267+
remote: ! 1. The major version only: 3.X (recommended)
268+
remote: ! 2. An exact patch version: 3.X.Y
269+
remote: !
270+
remote: ! Please update your Pipfile to use a valid Python version and
271+
remote: ! then run 'pipenv lock' to regenerate Pipfile.lock.
265272
remote: !
266-
remote: ! Please update your 'Pipfile' to use a valid Python version and
267-
remote: ! then run 'pipenv lock' to regenerate the lockfile.
273+
remote: ! We strongly recommend that you use the major version form
274+
remote: ! instead of pinning to an exact version, since it will allow
275+
remote: ! your app to receive Python security updates.
268276
remote: !
269277
remote: ! For more information, see:
270278
remote: ! https://pipenv.pypa.io/en/stable/specifiers.html#specifying-versions-of-python

spec/hatchet/python_update_warning_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@
1414
expect(clean_output(app.output)).to include(<<~OUTPUT)
1515
remote: -----> Python app detected
1616
remote: -----> Using Python 3.9.0 specified in runtime.txt
17+
remote:
18+
remote: ! Warning: The runtime.txt file is deprecated.
19+
remote: !
20+
remote: ! The runtime.txt file is deprecated since it has been replaced
21+
remote: ! by the more widely supported .python-version file.
22+
remote: !
23+
remote: ! Please delete your runtime.txt file and create a new file named:
24+
remote: ! .python-version
25+
remote: !
26+
remote: ! Make sure to include the '.' at the start of the filename.
27+
remote: !
28+
remote: ! In the new file, specify your app's Python version without
29+
remote: ! quotes or a 'python-' prefix. For example:
30+
remote: ! 3.9
31+
remote: !
32+
remote: ! We strongly recommend that you use the major version form
33+
remote: ! instead of pinning to an exact version, since it will allow
34+
remote: ! your app to receive Python security updates.
35+
remote: !
36+
remote: ! In the future support for runtime.txt will be removed and
37+
remote: ! this warning will be made an error.
38+
remote:
1739
remote: -----> Installing Python 3.9.0
1840
remote:
1941
remote: ! Warning: Support for Python 3.9 is ending soon!

0 commit comments

Comments
 (0)