-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
python
executable file
·126 lines (111 loc) · 5.87 KB
/
python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env bash
# shellcheck disable=SC2154 # TODO: Env var is referenced but not assigned.
# shellcheck disable=SC2250 # TODO: Use braces around variable references even when not strictly required.
set -euo pipefail
# The Python runtime archive filename is of form: 'python-X.Y.Z-ubuntu-22.04-amd64.tar.zst'
# The Ubuntu version is calculated from `STACK` since it's faster than calling `lsb_release`.
UBUNTU_VERSION="${STACK/heroku-/}.04"
ARCH=$(dpkg --print-architecture)
PYTHON_URL="${S3_BASE_URL}/python-${python_full_version}-ubuntu-${UBUNTU_VERSION}-${ARCH}.tar.zst"
# The Python version validation earlier will have filtered out most unsupported versions.
# However, the version might still not be found if either:
# 1. It's a Python major version we've deprecated and so is only available on older stacks (i.e: Python 3.8).
# 2. If an exact Python version was requested and the patch version doesn't exist (e.g. 3.12.999).
# 3. The user has pinned to an older buildpack version and the S3 bucket location or layout has changed since.
# TODO: Update this message to be more specific once Python 3.8 support is dropped.
if ! curl --output /dev/null --silent --head --fail --retry 3 --retry-connrefused --connect-timeout 10 "${PYTHON_URL}"; then
display_error <<-EOF
Error: Python ${python_full_version} is not available for this stack (${STACK}).
For a list of the supported Python versions, see:
https://devcenter.heroku.com/articles/python-support#supported-runtimes
EOF
meta_set "failure_reason" "python-version-not-found"
exit 1
fi
function warn_if_patch_update_available() {
local requested_full_version="${1}"
local requested_major_version="${2}"
local latest_patch_version
latest_patch_version="$(python_version::resolve_python_version "${requested_major_version}" "${python_version_origin}")"
# Extract the patch version component of the version strings (ie: the '5' in '3.10.5').
local requested_patch_number="${requested_full_version##*.}"
local latest_patch_number="${latest_patch_version##*.}"
if ((requested_patch_number < latest_patch_number)); then
puts-warn
puts-warn "A Python security update is available! Upgrade as soon as possible to: Python ${latest_patch_version}"
puts-warn "See: https://devcenter.heroku.com/articles/python-runtimes"
puts-warn
meta_set "python_version_outdated" "true"
else
meta_set "python_version_outdated" "false"
fi
}
# We wait until now to display outdated Python version warnings, since we only want to show them
# if there weren't any errors with the version to avoid adding noise to the error messages.
# TODO: Move this into lib/ as part of the warnings refactor.
if [[ "${python_major_version}" == "3.8" ]]; then
puts-warn
puts-warn "Python 3.8 will reach its upstream end-of-life in October 2024, at which"
puts-warn "point it will no longer receive security updates:"
puts-warn "https://devguide.python.org/versions/#supported-versions"
puts-warn
puts-warn "Support for Python 3.8 will be removed from this buildpack on December 4th, 2024."
puts-warn
puts-warn "Upgrade to a newer Python version as soon as possible to keep your app secure."
puts-warn "See: https://devcenter.heroku.com/articles/python-runtimes"
puts-warn
fi
warn_if_patch_update_available "${python_full_version}" "${python_major_version}"
if [[ "$STACK" != "$CACHED_PYTHON_STACK" ]]; then
puts-step "Stack has changed from $CACHED_PYTHON_STACK to $STACK, clearing cache"
rm -rf .heroku/python-stack .heroku/python-version .heroku/python .heroku/vendor .heroku/python .heroku/python-sqlite3-version
fi
# TODO: Clean this up as part of the cache refactor.
if [[ -f .heroku/python-version ]]; then
if [[ "${cached_python_version}" != "${python_full_version}" ]]; then
puts-step "Python version has changed from ${cached_python_version} to ${python_full_version}, clearing cache"
rm -rf .heroku/python
else
SKIP_INSTALL=1
fi
fi
# If using pip, check if we should reinstall python dependencies given that requirements.txt
# is non-deterministic (not all packages pinned, doesn't handle uninstalls etc). We don't need
# to do this when using Pipenv, since it has a lockfile and syncs the packages for us.
if [[ -f "${BUILD_DIR}/requirements.txt" ]]; then
if [[ ! -f "$CACHE_DIR/.heroku/requirements.txt" ]]; then
# This is a the first build of an app (or the build cache was cleared). Since there
# are no cached packages, we only need to store the requirements file for next time.
cp -R "$BUILD_DIR/requirements.txt" "$CACHE_DIR/.heroku/requirements.txt"
else
# IF there IS a cached directory, check for differences with the new one
if ! diff "$BUILD_DIR/requirements.txt" "$CACHE_DIR/.heroku/requirements.txt" &>/dev/null; then
puts-step "Requirements file has been changed, clearing cached dependencies"
# if there are any differences, clear the Python cache
# Installing Python over again does not take noticably more time
cp -R "$BUILD_DIR/requirements.txt" "$CACHE_DIR/.heroku/requirements.txt"
rm -rf .heroku/python
unset SKIP_INSTALL
else
puts-step "No change in requirements detected, installing from cache"
fi
fi
fi
if [[ "${SKIP_INSTALL:-0}" == "1" ]]; then
puts-step "Using cached install of Python ${python_full_version}"
else
puts-step "Installing Python ${python_full_version}"
# Prepare destination directory.
mkdir -p .heroku/python
if ! curl --silent --show-error --fail --retry 3 --retry-connrefused --connect-timeout 10 "${PYTHON_URL}" | tar --zstd --extract --directory .heroku/python; then
# The Python version was confirmed to exist previously, so any failure here is due to
# a networking issue or archive/buildpack bug rather than the runtime not existing.
display_error "Error: Failed to download/install Python ${python_full_version}."
meta_set "failure_reason" "python-download"
exit 1
fi
# Record for future reference.
echo "python-${python_full_version}" >.heroku/python-version
echo "$STACK" >.heroku/python-stack
hash -r
fi