Skip to content

Commit aef5e69

Browse files
authored
Support major Python version syntax in runtime.txt (#322)
Historically the `runtime.txt` file has only supported specifying an exact Python version, in the form `python-3.X.Y`. This adds support for the `python-3.X` form too, which means the app will automatically receive new Python patch updates during subsequent builds (similar to what's already supported for the `.python-version` file). This means the Python CNB's `runtime.txt` supported syntax now matches that supported by the classic Python buildpack (which gained support for the major version form as part of adding support for the `.python-version` file). The `runtime.txt` file remains deprecated (a deprecation warning will be added shortly), however, in the meantime this improves parity between the classic buildpack and CNB. GUS-W-17660224.
1 parent 1bb68e5 commit aef5e69

File tree

5 files changed

+42
-20
lines changed

5 files changed

+42
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Added `runtime.txt` support for the `python-3.X` major Python version form. ([#322](https://github.com/heroku/buildpacks-python/pull/322))
1213
- Enabled `libcnb`'s `trace` feature. ([#320](https://github.com/heroku/buildpacks-python/pull/320))
1314

1415
## [0.23.0] - 2025-01-13

src/errors.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::layers::python::PythonLayerError;
88
use crate::package_manager::DeterminePackageManagerError;
99
use crate::python_version::{
1010
RequestedPythonVersion, RequestedPythonVersionError, ResolvePythonVersionError,
11-
DEFAULT_PYTHON_FULL_VERSION, DEFAULT_PYTHON_VERSION, NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION,
11+
DEFAULT_PYTHON_VERSION, NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION,
1212
OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION,
1313
};
1414
use crate::python_version_file::ParsePythonVersionFileError;
@@ -205,17 +205,22 @@ fn on_requested_python_version_error(error: RequestedPythonVersionError) {
205205
log_error(
206206
"Invalid Python version in runtime.txt",
207207
formatdoc! {"
208-
The Python version specified in 'runtime.txt' is not in the correct format.
208+
The Python version specified in 'runtime.txt' isn't in
209+
the correct format.
209210
210211
The following file contents were found:
211212
{cleaned_contents}
212213
213-
However, the file contents must begin with a 'python-' prefix, followed by the
214-
version specified as '<major>.<minor>.<patch>'. Comments are not supported.
214+
However, the version must be specified as either:
215+
1. 'python-<major>.<minor>' (recommended, for automatic updates)
216+
2. 'python-<major>.<minor>.<patch>' (to pin to an exact version)
217+
218+
Remember to include the 'python-' prefix. Comments aren't
219+
supported.
215220
216-
For example, to request Python {DEFAULT_PYTHON_FULL_VERSION}, update the 'runtime.txt' file so it
217-
contains exactly:
218-
python-{DEFAULT_PYTHON_FULL_VERSION}
221+
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
222+
update the 'runtime.txt' file so it contains:
223+
python-{DEFAULT_PYTHON_VERSION}
219224
"},
220225
);
221226
}

src/python_version.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub(crate) const DEFAULT_PYTHON_VERSION: RequestedPythonVersion = RequestedPytho
1313
patch: None,
1414
origin: PythonVersionOrigin::BuildpackDefault,
1515
};
16+
17+
#[cfg(test)]
1618
pub(crate) const DEFAULT_PYTHON_FULL_VERSION: PythonVersion = LATEST_PYTHON_3_13;
1719

1820
pub(crate) const OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 9;

src/runtime_txt.rs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::python_version::{PythonVersionOrigin, RequestedPythonVersion};
22

33
/// Parse the contents of a `runtime.txt` file into a [`RequestedPythonVersion`].
44
///
5-
/// The file is expected to contain a string of form `python-X.Y.Z`.
5+
/// The file is expected to contain a string of form `python-X.Y.Z` or `python-X.Y`.
66
/// Any leading or trailing whitespace will be removed.
77
pub(crate) fn parse(contents: &str) -> Result<RequestedPythonVersion, ParseRuntimeTxtError> {
88
// All leading/trailing whitespace is trimmed, since that's what the classic buildpack
@@ -30,6 +30,12 @@ pub(crate) fn parse(contents: &str) -> Result<RequestedPythonVersion, ParseRunti
3030
patch: Some(patch),
3131
origin: PythonVersionOrigin::RuntimeTxt,
3232
}),
33+
[major, minor] => Ok(RequestedPythonVersion {
34+
major,
35+
minor,
36+
patch: None,
37+
origin: PythonVersionOrigin::RuntimeTxt,
38+
}),
3339
_ => Err(ParseRuntimeTxtError {
3440
cleaned_contents: cleaned_contents.clone(),
3541
}),
@@ -48,6 +54,15 @@ mod tests {
4854

4955
#[test]
5056
fn parse_valid() {
57+
assert_eq!(
58+
parse("python-1.2"),
59+
Ok(RequestedPythonVersion {
60+
major: 1,
61+
minor: 2,
62+
patch: None,
63+
origin: PythonVersionOrigin::RuntimeTxt
64+
})
65+
);
5166
assert_eq!(
5267
parse("python-1.2.3"),
5368
Ok(RequestedPythonVersion {
@@ -139,12 +154,6 @@ mod tests {
139154
cleaned_contents: "python-1".to_string(),
140155
})
141156
);
142-
assert_eq!(
143-
parse("python-1.2"),
144-
Err(ParseRuntimeTxtError {
145-
cleaned_contents: "python-1.2".to_string(),
146-
})
147-
);
148157
assert_eq!(
149158
parse("python-1.2.3.4"),
150159
Err(ParseRuntimeTxtError {

tests/python_version_test.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -392,17 +392,22 @@ fn runtime_txt_invalid_version() {
392392
context.pack_stderr,
393393
&formatdoc! {"
394394
[Error: Invalid Python version in runtime.txt]
395-
The Python version specified in 'runtime.txt' is not in the correct format.
395+
The Python version specified in 'runtime.txt' isn't in
396+
the correct format.
396397
397398
The following file contents were found:
398399
python-an.invalid.version
399400
400-
However, the file contents must begin with a 'python-' prefix, followed by the
401-
version specified as '<major>.<minor>.<patch>'. Comments are not supported.
401+
However, the version must be specified as either:
402+
1. 'python-<major>.<minor>' (recommended, for automatic updates)
403+
2. 'python-<major>.<minor>.<patch>' (to pin to an exact version)
404+
405+
Remember to include the 'python-' prefix. Comments aren't
406+
supported.
402407
403-
For example, to request Python {DEFAULT_PYTHON_FULL_VERSION}, update the 'runtime.txt' file so it
404-
contains exactly:
405-
python-{DEFAULT_PYTHON_FULL_VERSION}
408+
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
409+
update the 'runtime.txt' file so it contains:
410+
python-{DEFAULT_PYTHON_VERSION}
406411
"}
407412
);
408413
});

0 commit comments

Comments
 (0)