Skip to content

Commit 8e65b7e

Browse files
committed
Switch to a new output helper for error messages
The existing logging functions in `bin/utils` don't support being passed multi-line output. Upcoming PRs are going to be adding a number of new error messages, so this adds a new output module under `lib/` (that supports multi-line output and also uses colour) and switches the existing buildpack error messages to it. GUS-W-16808943.
1 parent 3da3b3a commit 8e65b7e

File tree

12 files changed

+117
-81
lines changed

12 files changed

+117
-81
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+
- Buildpack error messages are now more consistently formatted and use colour. ([#1639](https://github.com/heroku/heroku-buildpack-python/pull/1639))
56

67
## [v256] - 2024-09-07
78

bin/compile

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ BUILDPACK_DIR=$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)
1515

1616
source "${BUILDPACK_DIR}/bin/utils"
1717
source "${BUILDPACK_DIR}/lib/metadata.sh"
18+
source "${BUILDPACK_DIR}/lib/output.sh"
1819

1920
compile_start_time=$(nowms)
2021

@@ -137,32 +138,30 @@ fi
137138
# TODO: Move this into a new package manager handling implementation when adding Poetry support.
138139
# We intentionally don't mention `setup.py` here since it's being removed soon.
139140
if [[ ! -f requirements.txt && ! -f Pipfile && ! -f setup.py ]]; then
140-
puts-warn
141-
puts-warn "Error: Couldn't find any supported Python package manager files."
142-
puts-warn
143-
puts-warn "A Python app on Heroku must have either a 'requirements.txt' or"
144-
puts-warn "'Pipfile' package manager file in the root directory of its"
145-
puts-warn "source code."
146-
puts-warn
147-
puts-warn "Currently the root directory of your app contains:"
148-
puts-warn
149-
# TODO: Overhaul logging helpers so they can handle prefixing multi-line strings, and switch to them.
150-
# shellcheck disable=SC2012 # Using `ls` instead of `find` is absolutely fine for this use case.
151-
ls -1 --indicator-style=slash "${BUILD_DIR}" | sed 's/^/ ! /'
152-
puts-warn
153-
puts-warn "If your app already has a package manager file, check that it:"
154-
puts-warn
155-
puts-warn "1. Is in the top level directory (not a subdirectory)."
156-
puts-warn "2. Has the correct spelling (the filenames are case-sensitive)."
157-
puts-warn "3. Isn't listed in '.gitignore' or '.slugignore'."
158-
puts-warn
159-
puts-warn "Otherwise, add a package manager file to your app. If your app has"
160-
puts-warn "no dependencies, then create an empty 'requirements.txt' file."
161-
puts-warn
162-
puts-warn "For help with using Python on Heroku, see:"
163-
puts-warn "https://devcenter.heroku.com/articles/getting-started-with-python"
164-
puts-warn "https://devcenter.heroku.com/articles/python-support"
165-
puts-warn
141+
display_error <<-EOF
142+
Error: Couldn't find any supported Python package manager files.
143+
144+
A Python app on Heroku must have either a 'requirements.txt' or
145+
'Pipfile' package manager file in the root directory of its
146+
source code.
147+
148+
Currently the root directory of your app contains:
149+
150+
$(ls -1 --indicator-style=slash "${BUILD_DIR}")
151+
152+
If your app already has a package manager file, check that it:
153+
154+
1. Is in the top level directory (not a subdirectory).
155+
2. Has the correct spelling (the filenames are case-sensitive).
156+
3. Isn't listed in '.gitignore' or '.slugignore'.
157+
158+
Otherwise, add a package manager file to your app. If your app has
159+
no dependencies, then create an empty 'requirements.txt' file.
160+
161+
For help with using Python on Heroku, see:
162+
https://devcenter.heroku.com/articles/getting-started-with-python
163+
https://devcenter.heroku.com/articles/python-support
164+
EOF
166165
meta_set "failure_reason" "package-manager-not-found"
167166
exit 1
168167
fi

bin/detect

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
#!/usr/bin/env bash
2-
# Usage: bin/compile <build-dir>
2+
# Usage: bin/detect <build-dir>
33
# See: https://devcenter.heroku.com/articles/buildpack-api
44

55
set -euo pipefail
66

77
BUILD_DIR="${1}"
88

9+
# The absolute path to the root of the buildpack.
10+
BUILDPACK_DIR=$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)
11+
12+
source "${BUILDPACK_DIR}/lib/output.sh"
13+
914
# Filenames that if found in a project mean it should be treated as a Python project,
1015
# and so pass this buildpack's detection phase.
1116
#
@@ -36,14 +41,10 @@ for filename in "${KNOWN_PYTHON_PROJECT_FILES[@]}"; do
3641
fi
3742
done
3843

39-
# Cytokine incorrectly indents the first line, so we have to leave it empty.
40-
echo 1>&2
41-
4244
# Note: This error message intentionally doesn't list all of the filetypes above,
4345
# since during compile the build will still require a package manager file, so it
44-
# makes sense to describe the stricter requirements up front.
45-
# TODO: Overhaul logging helpers so they can handle prefixing multi-line strings, and switch to them.
46-
sed 's/^/ ! /' 1>&2 <<EOF
46+
# makes sense to describe the stricter requirements upfront.
47+
display_error <<EOF
4748
Error: Your app is configured to use the Python buildpack,
4849
but we couldn't find any supported Python project files.
4950

bin/steps/python

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@ PYTHON_VERSION="${PYTHON_VERSION%%+([[:space:]])}"
99
function eol_python_version_error() {
1010
local major_version="${1}"
1111
local eol_date="${2}"
12-
puts-warn
13-
puts-warn "Python ${major_version} reached upstream end-of-life on ${eol_date}, and is"
14-
puts-warn "therefore no longer receiving security updates:"
15-
puts-warn "https://devguide.python.org/versions/#supported-versions"
16-
puts-warn
17-
puts-warn "As such, it is no longer supported by this buildpack."
18-
puts-warn
19-
puts-warn "Please upgrade to a newer Python version."
20-
puts-warn
21-
puts-warn "For a list of the supported Python versions, see:"
22-
puts-warn "https://devcenter.heroku.com/articles/python-support#supported-runtimes"
23-
puts-warn
12+
display_error <<-EOF
13+
Error: Python ${major_version} is no longer supported.
14+
15+
Python ${major_version} reached upstream end-of-life on ${eol_date}, and is
16+
therefore no longer receiving security updates:
17+
https://devguide.python.org/versions/#supported-versions
18+
19+
As such, it is no longer supported by this buildpack.
20+
21+
Please upgrade to a newer Python version.
22+
23+
For a list of the supported Python versions, see:
24+
https://devcenter.heroku.com/articles/python-support#supported-runtimes
25+
EOF
2426
meta_set "failure_reason" "python-version-eol"
2527
exit 1
2628
}
@@ -44,12 +46,12 @@ ARCH=$(dpkg --print-architecture)
4446
PYTHON_URL="${S3_BASE_URL}/${PYTHON_VERSION}-ubuntu-${UBUNTU_VERSION}-${ARCH}.tar.zst"
4547

4648
if ! curl --output /dev/null --silent --head --fail --retry 3 --retry-connrefused --connect-timeout 10 "${PYTHON_URL}"; then
47-
puts-warn
48-
puts-warn "Requested runtime '${PYTHON_VERSION}' is not available for this stack (${STACK})."
49-
puts-warn
50-
puts-warn "For a list of the supported Python versions, see:"
51-
puts-warn "https://devcenter.heroku.com/articles/python-support#supported-runtimes"
52-
puts-warn
49+
display_error <<-EOF
50+
Error: Requested runtime '${PYTHON_VERSION}' is not available for this stack (${STACK}).
51+
52+
For a list of the supported Python versions, see:
53+
https://devcenter.heroku.com/articles/python-support#supported-runtimes
54+
EOF
5355
meta_set "failure_reason" "python-version-not-found"
5456
exit 1
5557
fi
@@ -153,7 +155,7 @@ else
153155
if ! curl --silent --show-error --fail --retry 3 --retry-connrefused --connect-timeout 10 "${PYTHON_URL}" | tar --zstd --extract --directory .heroku/python; then
154156
# The Python version was confirmed to exist previously, so any failure here is due to
155157
# a networking issue or archive/buildpack bug rather than the runtime not existing.
156-
puts-warn "Failed to download/install ${PYTHON_VERSION}"
158+
display_error "Error: Failed to download/install ${PYTHON_VERSION}."
157159
meta_set "failure_reason" "python-download"
158160
exit 1
159161
fi
@@ -186,7 +188,7 @@ BUNDLED_PIP_WHEEL_LIST=(.heroku/python/lib/python*/ensurepip/_bundled/pip-*.whl)
186188
BUNDLED_PIP_WHEEL="${BUNDLED_PIP_WHEEL_LIST[0]}"
187189

188190
if [[ -z "${BUNDLED_PIP_WHEEL}" ]]; then
189-
puts-warn "Failed to locate the bundled pip wheel"
191+
display_error "Error: Failed to locate the bundled pip wheel."
190192
meta_set "failure_reason" "bundled-pip-not-found"
191193
exit 1
192194
fi

builds/test_python_runtime.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ set -euo pipefail
44

55
ARCHIVE_FILEPATH="${1:?"Error: The filepath of the Python runtime archive must be specified as the first argument."}"
66

7+
function abort() {
8+
echo "Error: ${1}" >&2
9+
exit 1
10+
}
11+
712
# We intentionally extract the Python runtime into a different directory to the one into which it
813
# was originally installed before being packaged, to check that relocation works (since buildpacks
914
# depend on it). Since the Python binary was built in shared mode, `LD_LIBRARY_PATH` must be set
@@ -22,8 +27,7 @@ tar --zstd --extract --verbose --file "${ARCHIVE_FILEPATH}" --directory "${INSTA
2227
# Check that all dynamically linked libraries exist in the run image (since it has fewer packages than the build image).
2328
LDD_OUTPUT=$(find "${INSTALL_DIR}" -type f,l \( -name 'python3' -o -name '*.so*' \) -exec ldd '{}' +)
2429
if grep 'not found' <<<"${LDD_OUTPUT}" | sort --unique; then
25-
echo "The above dynamically linked libraries were not found!"
26-
exit 1
30+
abort "The above dynamically linked libraries were not found!"
2731
fi
2832

2933
# Check that optional and/or system library dependent stdlib modules were built.
@@ -46,6 +50,5 @@ if ! "${INSTALL_DIR}/bin/python3" -c "import $(
4650
IFS=,
4751
echo "${optional_stdlib_modules[*]}"
4852
)"; then
49-
echo "The above optional stdlib module failed to import! Check the compile logs to see if it was skipped due to missing libraries/headers."
50-
exit 1
53+
abort "The above optional stdlib module failed to import! Check the compile logs to see if it was skipped due to missing libraries/headers."
5154
fi

lib/output.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env bash
2+
3+
ANSI_RED='\033[1;31m'
4+
ANSI_RESET='\033[0m'
5+
6+
# shellcheck disable=SC2120 # Prevent warnings about unused arguments due to the split args vs stdin API.
7+
function display_error() {
8+
# Send all output to stderr
9+
exec 1>&2
10+
# If arguments are given, redirect them to stdin. This allows the function
11+
# to be invoked with either a string argument or stdin (e.g. via <<-EOF).
12+
(($#)) && exec <<<"${@}"
13+
echo
14+
while IFS= read -r line; do
15+
echo -e "${ANSI_RED} ! ${line}${ANSI_RESET}"
16+
done
17+
echo
18+
}

spec/hatchet/detect_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
remote: ! https://devcenter.heroku.com/articles/getting-started-with-python
4040
remote: ! https://devcenter.heroku.com/articles/python-support
4141
remote:
42+
remote:
4243
remote: More info: https://devcenter.heroku.com/articles/buildpacks#detection-failure
4344
OUTPUT
4445
end

spec/hatchet/package_manager_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
app.deploy do |app|
1111
expect(clean_output(app.output)).to include(<<~OUTPUT)
1212
remote: -----> Python app detected
13-
remote: !
13+
remote:
1414
remote: ! Error: Couldn't find any supported Python package manager files.
1515
remote: !
1616
remote: ! A Python app on Heroku must have either a 'requirements.txt' or
@@ -34,7 +34,7 @@
3434
remote: ! For help with using Python on Heroku, see:
3535
remote: ! https://devcenter.heroku.com/articles/getting-started-with-python
3636
remote: ! https://devcenter.heroku.com/articles/python-support
37-
remote: !
37+
remote:
3838
remote: ! Push rejected, failed to compile Python app.
3939
OUTPUT
4040
end

spec/hatchet/pipenv_spec.rb

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
expect(clean_output(app.output)).to include(<<~OUTPUT)
2525
remote: -----> Python app detected
2626
remote: -----> Using Python version specified in Pipfile.lock
27-
remote: !
28-
remote: ! Requested runtime 'python-#{requested_version}' is not available for this stack (#{app.stack}).
27+
remote:
28+
remote: ! Error: Requested runtime 'python-#{requested_version}' is not available for this stack (#{app.stack}).
2929
remote: !
3030
remote: ! For a list of the supported Python versions, see:
3131
remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes
32-
remote: !
32+
remote:
3333
remote: ! Push rejected, failed to compile Python app.
3434
OUTPUT
3535
end
@@ -125,6 +125,8 @@
125125
expect(clean_output(app.output)).to match(Regexp.new(<<~OUTPUT))
126126
remote: -----> Python app detected
127127
remote: -----> Using Python version specified in Pipfile.lock
128+
remote:
129+
remote: ! Error: Python 3.6 is no longer supported.
128130
remote: !
129131
remote: ! Python 3.6 reached upstream end-of-life on December 23rd, 2021, and is
130132
remote: ! therefore no longer receiving security updates:
@@ -136,7 +138,7 @@
136138
remote: !
137139
remote: ! For a list of the supported Python versions, see:
138140
remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes
139-
remote: !
141+
remote:
140142
remote: ! Push rejected, failed to compile Python app.
141143
OUTPUT
142144
end
@@ -151,6 +153,8 @@
151153
expect(clean_output(app.output)).to match(Regexp.new(<<~OUTPUT))
152154
remote: -----> Python app detected
153155
remote: -----> Using Python version specified in Pipfile.lock
156+
remote:
157+
remote: ! Error: Python 3.7 is no longer supported.
154158
remote: !
155159
remote: ! Python 3.7 reached upstream end-of-life on June 27th, 2023, and is
156160
remote: ! therefore no longer receiving security updates:
@@ -162,7 +166,7 @@
162166
remote: !
163167
remote: ! For a list of the supported Python versions, see:
164168
remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes
165-
remote: !
169+
remote:
166170
remote: ! Push rejected, failed to compile Python app.
167171
OUTPUT
168172
end
@@ -273,12 +277,12 @@
273277
expect(clean_output(app.output)).to include(<<~OUTPUT)
274278
remote: -----> Python app detected
275279
remote: -----> Using Python version specified in Pipfile.lock
276-
remote: !
277-
remote: ! Requested runtime '^3.12' is not available for this stack (#{app.stack}).
280+
remote:
281+
remote: ! Error: Requested runtime '^3.12' is not available for this stack (#{app.stack}).
278282
remote: !
279283
remote: ! For a list of the supported Python versions, see:
280284
remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes
281-
remote: !
285+
remote:
282286
remote: ! Push rejected, failed to compile Python app.
283287
OUTPUT
284288
end
@@ -293,12 +297,12 @@
293297
expect(clean_output(app.output)).to include(<<~OUTPUT)
294298
remote: -----> Python app detected
295299
remote: -----> Using Python version specified in Pipfile.lock
296-
remote: !
297-
remote: ! Requested runtime 'python-X.Y.Z' is not available for this stack (#{app.stack}).
300+
remote:
301+
remote: ! Error: Requested runtime 'python-X.Y.Z' is not available for this stack (#{app.stack}).
298302
remote: !
299303
remote: ! For a list of the supported Python versions, see:
300304
remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes
301-
remote: !
305+
remote:
302306
remote: ! Push rejected, failed to compile Python app.
303307
OUTPUT
304308
end

spec/hatchet/python_update_warning_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
expect(clean_output(app.output)).to include(<<~OUTPUT)
2525
remote: -----> Python app detected
2626
remote: -----> Using Python version specified in runtime.txt
27-
remote: !
28-
remote: ! Requested runtime 'python-#{requested_version}' is not available for this stack (#{app.stack}).
27+
remote:
28+
remote: ! Error: Requested runtime 'python-#{requested_version}' is not available for this stack (#{app.stack}).
2929
remote: !
3030
remote: ! For a list of the supported Python versions, see:
3131
remote: ! https://devcenter.heroku.com/articles/python-support#supported-runtimes
32-
remote: !
32+
remote:
3333
remote: ! Push rejected, failed to compile Python app.
3434
OUTPUT
3535
end

0 commit comments

Comments
 (0)