Skip to content

Commit f03ed3f

Browse files
authored
Improve output utils for warnings, steps and indented output (#1666)
* Switches warnings to the same approach used for errors (which also adds colour) * Switches to the new style utils for steps + indentation * Removes usage of the `exec 1>&2` pattern since it breaks the output returned by Bash functions, amongst other issues. GUS-W-17048951.
1 parent 99684a6 commit f03ed3f

22 files changed

+216
-135
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## [Unreleased]
44

5+
- Updated buildpack-generated warning messages to use colour and be more consistently formatted. ([#1666](https://github.com/heroku/heroku-buildpack-python/pull/1666))
56

67
## [v261] - 2024-10-14
78

bin/compile

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,6 @@ fi
142142
package_manager="$(package_manager::determine_package_manager "${BUILD_DIR}")"
143143
meta_set "package_manager" "${package_manager}"
144144

145-
# TODO: Move this warning to lib/package_manager.sh once `output::warning()` exists
146-
# (puts-warn outputs to stdout, which would break `determine_package_manager()` as is).
147-
# TODO: Adjust this warning to mention support for missing Pipfile.lock will be removed soon.
148-
if [[ "${package_manager}" == "pipenv" && ! -f "${BUILD_DIR}/Pipfile.lock" ]]; then
149-
puts-warn "No 'Pipfile.lock' found! We recommend you commit this into your repository."
150-
fi
151-
152145
# We use the Bash 4.3+ `nameref` feature to pass back multiple values from this function
153146
# without having to hardcode globals. See: https://stackoverflow.com/a/38997681
154147
python_version::read_requested_python_version "${BUILD_DIR}" "${package_manager}" "${cached_python_version}" requested_python_version python_version_origin
@@ -159,15 +152,15 @@ meta_set "python_version_reason" "${python_version_origin}"
159152
# TODO: Add runtime.txt deprecation warning.
160153
case "${python_version_origin}" in
161154
default)
162-
puts-step "No Python version was specified. Using the buildpack default: Python ${requested_python_version}"
155+
output::step "No Python version was specified. Using the buildpack default: Python ${requested_python_version}"
163156
echo " To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes"
164157
;;
165158
cached)
166-
puts-step "No Python version was specified. Using the same version as the last build: Python ${requested_python_version}"
159+
output::step "No Python version was specified. Using the same version as the last build: Python ${requested_python_version}"
167160
echo " To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes"
168161
;;
169162
*)
170-
puts-step "Using Python ${requested_python_version} specified in ${python_version_origin}"
163+
output::step "Using Python ${requested_python_version} specified in ${python_version_origin}"
171164
;;
172165
esac
173166

bin/detect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ done
4545
# Note: This error message intentionally doesn't list all of the filetypes above,
4646
# since during compile the build will still require a package manager file, so it
4747
# makes sense to describe the stricter requirements upfront.
48-
display_error <<EOF
48+
output::error <<EOF
4949
Error: Your app is configured to use the Python buildpack,
5050
but we couldn't find any supported Python project files.
5151

bin/steps/collectstatic

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@
1616
set -euo pipefail
1717
BUILDPACK_DIR=$(cd "$(dirname "$(dirname "$(dirname "${BASH_SOURCE[0]}")")")" && pwd)
1818
source "${BUILDPACK_DIR}/bin/utils"
19+
source "${BUILDPACK_DIR}/lib/output.sh"
1920

2021
# Required for `meta_set`.
2122
source "${BUILDPACK_DIR}/lib/metadata.sh"
2223
meta_init "${CACHE_DIR:?}" "python"
2324

2425
if [[ -f .heroku/collectstatic_disabled ]]; then
25-
puts-step "Skipping Django collectstatic since the file '.heroku/collectstatic_disabled' exists."
26-
puts-warn "This approach is deprecated, please set the env var DISABLE_COLLECTSTATIC=1 instead."
26+
output::step "Skipping Django collectstatic since the file '.heroku/collectstatic_disabled' exists."
27+
output::warning <<-'EOF'
28+
Warning: The .heroku/collectstatic_disabled file is deprecated.
29+
30+
Please remove the file and set the env var DISABLE_COLLECTSTATIC=1 instead.
31+
EOF
2732
meta_set "django_collectstatic" "disabled-file"
2833
exit 0
2934
fi
3035

3136
if [[ "${DISABLE_COLLECTSTATIC:-0}" != "0" ]]; then
32-
puts-step "Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set."
37+
output::step "Skipping Django collectstatic since the env var DISABLE_COLLECTSTATIC is set."
3338
meta_set "django_collectstatic" "disabled-env-var"
3439
exit 0
3540
fi
@@ -45,21 +50,21 @@ MANAGE_FILE=$(find . -maxdepth 3 -type f -name 'manage.py' -printf '%d\t%P\n' |
4550
MANAGE_FILE=${MANAGE_FILE:-fakepath}
4651

4752
if [[ ! -f "${MANAGE_FILE}" ]]; then
48-
puts-step "Skipping Django collectstatic since no manage.py file found."
53+
output::step "Skipping Django collectstatic since no manage.py file found."
4954
meta_set "django_collectstatic" "skipped-no-manage-py"
5055
exit 0
5156
fi
5257

5358
meta_set "django_collectstatic" "enabled"
5459

55-
puts-step "$ python $MANAGE_FILE collectstatic --noinput"
60+
output::step "$ python $MANAGE_FILE collectstatic --noinput"
5661

5762
PYTHONPATH=${PYTHONPATH:-.}
5863
export PYTHONPATH
5964
COLLECTSTATIC_LOG=$(mktemp)
6065

6166
set +e
62-
python "$MANAGE_FILE" collectstatic --noinput --traceback 2>&1 | tee "$COLLECTSTATIC_LOG" | sed '/^Post-processed/d;/^Copying/d;/^$/d' | indent
67+
python "$MANAGE_FILE" collectstatic --noinput --traceback 2>&1 | tee "$COLLECTSTATIC_LOG" | sed '/^Post-processed/d;/^Copying/d;/^$/d' | output::indent
6368
COLLECTSTATIC_STATUS="${PIPESTATUS[0]}"
6469
set -e
6570

@@ -82,22 +87,28 @@ else
8287
meta_set "failure_reason" "collectstatic-other"
8388
fi
8489

85-
echo " ! Error while running '$ python $MANAGE_FILE collectstatic --noinput'."
86-
echo " See traceback above for details."
87-
echo
88-
echo " You may need to update application code to resolve this error."
89-
echo " Or, you can disable collectstatic for this application:"
90-
echo
91-
echo " $ heroku config:set DISABLE_COLLECTSTATIC=1"
92-
echo
93-
echo " https://devcenter.heroku.com/articles/django-assets"
90+
output::error <<-EOF
91+
Error: Unable to generate Django static files.
92+
93+
The 'python ${MANAGE_FILE} collectstatic --noinput' Django
94+
management command to generate static files failed.
95+
96+
See the traceback above for details.
97+
98+
You may need to update application code to resolve this error.
99+
Or, you can disable collectstatic for this application:
100+
101+
$ heroku config:set DISABLE_COLLECTSTATIC=1
102+
103+
https://devcenter.heroku.com/articles/django-assets
104+
EOF
94105

95106
# Additionally, dump out the environment, if debug mode is on.
96107
if [[ "${DEBUG_COLLECTSTATIC:-0}" == "1" ]]; then
97108
echo
98109
echo "****** Collectstatic environment variables:"
99110
echo
100-
env | indent
111+
env | output::indent
101112
fi
102113

103114
exit 1

bin/steps/nltk

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
set -euo pipefail
77
BUILDPACK_DIR=$(cd "$(dirname "$(dirname "$(dirname "${BASH_SOURCE[0]}")")")" && pwd)
88
source "${BUILDPACK_DIR}/bin/utils"
9+
source "${BUILDPACK_DIR}/lib/output.sh"
910

1011
# Required for `meta_set`.
1112
source "${BUILDPACK_DIR}/lib/metadata.sh"
@@ -18,22 +19,22 @@ EXPORT_PATH="${BUILDPACK_DIR}/export"
1819
# Check that nltk was installed by pip, otherwise obviously not needed
1920
# shellcheck disable=SC2310 # TODO: This function is invoked in an 'if' condition so set -e will be disabled.
2021
if is_module_available 'nltk'; then
21-
puts-step "Downloading NLTK corpora..."
22+
output::step "Downloading NLTK corpora..."
2223

2324
nltk_packages_definition="$BUILD_DIR/nltk.txt"
2425

2526
if [[ -f "$nltk_packages_definition" ]]; then
2627
meta_set "nltk_downloader" "enabled"
2728

2829
readarray -t nltk_packages <"$nltk_packages_definition"
29-
puts-step "Downloading NLTK packages: ${nltk_packages[*]}"
30+
output::step "Downloading NLTK packages: ${nltk_packages[*]}"
3031

31-
python -m nltk.downloader -d "$BUILD_DIR/.heroku/python/nltk_data" "${nltk_packages[@]}" | indent
32+
python -m nltk.downloader -d "$BUILD_DIR/.heroku/python/nltk_data" "${nltk_packages[@]}" | output::indent
3233
set_env NLTK_DATA "/app/.heroku/python/nltk_data"
3334

3435
else
3536
meta_set "nltk_downloader" "skipped-no-nltk-file"
36-
puts-warn "'nltk.txt' not found, not downloading any corpora"
37-
puts-warn "Learn more: https://devcenter.heroku.com/articles/python-nltk"
37+
echo " 'nltk.txt' not found, not downloading any corpora"
38+
echo " Learn more: https://devcenter.heroku.com/articles/python-nltk"
3839
fi
3940
fi

bin/steps/python

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ PYTHON_URL="${S3_BASE_URL}/python-${python_full_version}-ubuntu-${UBUNTU_VERSION
1717
# 3. The user has pinned to an older buildpack version and the S3 bucket location or layout has changed since.
1818
# TODO: Update this message to be more specific once Python 3.8 support is dropped.
1919
if ! curl --output /dev/null --silent --head --fail --retry 3 --retry-connrefused --connect-timeout 10 "${PYTHON_URL}"; then
20-
display_error <<-EOF
20+
output::error <<-EOF
2121
Error: Python ${python_full_version} isn't available for this stack (${STACK}).
2222
2323
For a list of the supported Python versions, see:
@@ -38,10 +38,12 @@ function warn_if_patch_update_available() {
3838
# TODO: Update this message to suggest using the .python-version major version syntax to stay up to date,
3939
# once runtime.txt is deprecated and sticky-versioning only pins to the major version.
4040
if ((requested_patch_number < latest_patch_number)); then
41-
puts-warn
42-
puts-warn "A Python security update is available! Upgrade as soon as possible to: Python ${latest_patch_version}"
43-
puts-warn "See: https://devcenter.heroku.com/articles/python-runtimes"
44-
puts-warn
41+
output::warning <<-EOF
42+
Warning: A Python security update is available!
43+
44+
Upgrade as soon as possible to: Python ${latest_patch_version}
45+
See: https://devcenter.heroku.com/articles/python-runtimes
46+
EOF
4547
meta_set "python_version_outdated" "true"
4648
else
4749
meta_set "python_version_outdated" "false"
@@ -52,29 +54,31 @@ function warn_if_patch_update_available() {
5254
# if there weren't any errors with the version to avoid adding noise to the error messages.
5355
# TODO: Move this into lib/ as part of the warnings refactor.
5456
if [[ "${python_major_version}" == "3.8" ]]; then
55-
puts-warn
56-
puts-warn "Python 3.8 will reach its upstream end-of-life in October 2024, at which"
57-
puts-warn "point it will no longer receive security updates:"
58-
puts-warn "https://devguide.python.org/versions/#supported-versions"
59-
puts-warn
60-
puts-warn "Support for Python 3.8 will be removed from this buildpack on December 4th, 2024."
61-
puts-warn
62-
puts-warn "Upgrade to a newer Python version as soon as possible to keep your app secure."
63-
puts-warn "See: https://devcenter.heroku.com/articles/python-runtimes"
64-
puts-warn
57+
output::warning <<-'EOF'
58+
Warning: Support for Python 3.8 is ending soon!
59+
60+
Python 3.8 will reach its upstream end-of-life in October 2024, at which
61+
point it will no longer receive security updates:
62+
https://devguide.python.org/versions/#supported-versions
63+
64+
Support for Python 3.8 will be removed from this buildpack on December 4th, 2024.
65+
66+
Upgrade to a newer Python version as soon as possible to keep your app secure.
67+
See: https://devcenter.heroku.com/articles/python-runtimes
68+
EOF
6569
fi
6670

6771
warn_if_patch_update_available "${python_full_version}" "${python_major_version}"
6872

6973
if [[ "$STACK" != "$CACHED_PYTHON_STACK" ]]; then
70-
puts-step "Stack has changed from $CACHED_PYTHON_STACK to $STACK, clearing cache"
74+
output::step "Stack has changed from $CACHED_PYTHON_STACK to $STACK, clearing cache"
7175
rm -rf .heroku/python-stack .heroku/python-version .heroku/python .heroku/vendor .heroku/python .heroku/python-sqlite3-version
7276
fi
7377

7478
# TODO: Clean this up as part of the cache refactor.
7579
if [[ -f .heroku/python-version ]]; then
7680
if [[ "${cached_python_version}" != "${python_full_version}" ]]; then
77-
puts-step "Python version has changed from ${cached_python_version} to ${python_full_version}, clearing cache"
81+
output::step "Python version has changed from ${cached_python_version} to ${python_full_version}, clearing cache"
7882
rm -rf .heroku/python
7983
else
8084
SKIP_INSTALL=1
@@ -92,30 +96,35 @@ if [[ -f "${BUILD_DIR}/requirements.txt" ]]; then
9296
else
9397
# IF there IS a cached directory, check for differences with the new one
9498
if ! diff "$BUILD_DIR/requirements.txt" "$CACHE_DIR/.heroku/requirements.txt" &>/dev/null; then
95-
puts-step "Requirements file has been changed, clearing cached dependencies"
99+
output::step "Requirements file has been changed, clearing cached dependencies"
96100
# if there are any differences, clear the Python cache
97101
# Installing Python over again does not take noticably more time
98102
cp -R "$BUILD_DIR/requirements.txt" "$CACHE_DIR/.heroku/requirements.txt"
99103
rm -rf .heroku/python
100104
unset SKIP_INSTALL
101105
else
102-
puts-step "No change in requirements detected, installing from cache"
106+
output::step "No change in requirements detected, installing from cache"
103107
fi
104108
fi
105109
fi
106110

107111
if [[ "${SKIP_INSTALL:-0}" == "1" ]]; then
108-
puts-step "Using cached install of Python ${python_full_version}"
112+
output::step "Using cached install of Python ${python_full_version}"
109113
else
110-
puts-step "Installing Python ${python_full_version}"
114+
output::step "Installing Python ${python_full_version}"
111115

112116
# Prepare destination directory.
113117
mkdir -p .heroku/python
114118

115119
if ! curl --silent --show-error --fail --retry 3 --retry-connrefused --connect-timeout 10 "${PYTHON_URL}" | tar --zstd --extract --directory .heroku/python; then
116120
# The Python version was confirmed to exist previously, so any failure here is due to
117121
# a networking issue or archive/buildpack bug rather than the runtime not existing.
118-
display_error "Error: Failed to download/install Python ${python_full_version}."
122+
output::error <<-EOF
123+
Error: Failed to download/install Python ${python_full_version}.
124+
125+
In some cases, this happens due to an unstable network connection.
126+
Please try again and to see if the error resolves itself.
127+
EOF
119128
meta_set "failure_reason" "python-download"
120129
exit 1
121130
fi

bin/steps/sqlite3

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ sqlite3_install() {
6363
}
6464

6565
buildpack_sqlite3_install() {
66-
puts-step "Installing SQLite3"
66+
output::step "Installing SQLite3"
6767

6868
# TODO: This never actual prints failure or even aborts the build, since
6969
# the conditional disables `set -e` inside the called function:

bin/utils

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,11 @@ source "${BUILDPACK_DIR:?}/vendor/buildpack-stdlib_v8.sh"
1010

1111
sed() { command sed -u "$@"; }
1212

13-
# Syntax sugar.
14-
indent() {
15-
sed "s/^/ /"
16-
}
17-
1813
# Clean up pip output
1914
cleanup() {
2015
sed -e 's/\.\.\.\+/.../g' | sed -e '/already satisfied/Id' | sed -e '/No files were found to uninstall/Id' | sed -e '/Overwriting/Id' | sed -e '/python executable/Id' | sed -e '/no previously-included files/Id'
2116
}
2217

23-
# Buildpack Steps.
24-
puts-step() {
25-
echo "-----> $*"
26-
}
27-
28-
# Buildpack Warnings.
29-
puts-warn() {
30-
echo " ! $*"
31-
}
32-
3318
# Does some serious copying.
3419
deep-cp() {
3520
declare source="$1" target="$2"

bin/warnings

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
gdal-missing() {
44
# shellcheck disable=SC2154 # TODO: Env var is referenced but not assigned.
55
if grep -qi 'Could not find gdal-config' "${WARNINGS_LOG}"; then
6-
echo
7-
puts-warn "Hello! Package installation failed since the GDAL library was not found."
8-
puts-warn "For GDAL, GEOS and PROJ support, use the Geo buildpack alongside the Python buildpack:"
9-
puts-warn "https://github.com/heroku/heroku-geo-buildpack"
6+
output::error <<-'EOF'
7+
Error: Package installation failed since the GDAL library was not found.
8+
9+
For GDAL, GEOS and PROJ support, use the Geo buildpack alongside the Python buildpack:
10+
https://github.com/heroku/heroku-geo-buildpack
11+
EOF
1012
fi
1113
}
1214

0 commit comments

Comments
 (0)