Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Updated uv from 0.9.7 to 0.9.9. ([#1961](https://github.com/heroku/heroku-buildpack-python/pull/1961))
- Improved the error message shown for `.python-version` files that contain unexpected ASCII control code characters. ([#1962](https://github.com/heroku/heroku-buildpack-python/pull/1962))
- Fixed Bash command substitution warnings from being shown if `runtime.txt` contains null byte characters. ([#1962](https://github.com/heroku/heroku-buildpack-python/pull/1962))
- Improved the error message shown if the buildpack's build data file is deleted by a pre/post-compile hook. ([#1963](https://github.com/heroku/heroku-buildpack-python/pull/1963))

## [v318] - 2025-11-12

Expand Down
25 changes: 21 additions & 4 deletions lib/build_data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,25 @@ function build_data::_set() {
local jq_args=(--argjson value "${value}")
fi

local new_data_file_contents
new_data_file_contents="$(jq --arg key "${key}" "${jq_args[@]}" '. + { ($key): ($value) }' "${BUILD_DATA_FILE}")"
echo "${new_data_file_contents}" >"${BUILD_DATA_FILE}"
if [[ -f "${BUILD_DATA_FILE}" ]]; then
local new_data_file_contents
new_data_file_contents="$(jq --exit-status --arg key "${key}" "${jq_args[@]}" '. + { ($key): ($value) }' "${BUILD_DATA_FILE}")"
echo "${new_data_file_contents}" >"${BUILD_DATA_FILE}"
else
output::error <<-EOF
Error: Can't find the buildpack's build data file.

The Python buildpack's internal build data file is missing:
${BUILD_DATA_FILE}

This file is required for the buildpack to work correctly,
and so you must not delete it when removing files from the
build cache or /tmp directories.
EOF
build_data::setup
build_data::set_string "failure_reason" "build-data::data-file-deleted"
exit 1
fi
}

# Check whether an entry exists in the build data store for the current build.
Expand Down Expand Up @@ -143,6 +159,7 @@ function build_data::get_previous() {
tac "${LEGACY_BUILD_DATA_FILE}" | { grep --perl-regexp --only-matching --max-count=1 "^${key}=\K.*$" || true; }
elif [[ -f "${PREVIOUS_BUILD_DATA_FILE}" ]]; then
# The `// empty` ensures we return the empty string rather than `null` if the key doesn't exist.
# We don't use `--exit-status` since `false` is a valid value for us to retrieve.
jq --raw-output ".${key} // empty" "${PREVIOUS_BUILD_DATA_FILE}"
fi
}
Expand Down Expand Up @@ -170,5 +187,5 @@ function build_data::current_unix_realtime() {
# build_data::print_bin_report_json
# ```
function build_data::print_bin_report_json() {
jq --sort-keys '.' "${BUILD_DATA_FILE}"
jq --exit-status --sort-keys '.' "${BUILD_DATA_FILE}"
}
6 changes: 6 additions & 0 deletions spec/fixtures/hooks_delete_cache_dir/bin/post_compile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -euo pipefail

set -x
rm -rf "${CACHE_DIR:?}"
Empty file.
24 changes: 24 additions & 0 deletions spec/hatchet/hooks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,28 @@
end
end
end

context 'when an app tries to delete the whole cache directory including the build data file' do
let(:app) { Hatchet::Runner.new('spec/fixtures/hooks_delete_cache_dir', allow_failure: true) }

it 'aborts the build with a suitable error message' do
app.deploy do |app|
expect(clean_output(app.output)).to include(<<~OUTPUT)
remote: -----> Running bin/post_compile hook
remote: + rm -rf /tmp/codon/tmp/cache
remote:
remote: ! Error: Can't find the buildpack's build data file.
remote: !
remote: ! The Python buildpack's internal build data file is missing:
remote: ! /tmp/codon/tmp/cache/build-data/python.json
remote: !
remote: ! This file is required for the buildpack to work correctly,
remote: ! and so you must not delete it when removing files from the
remote: ! build cache or /tmp directories.
remote:
remote: ! Push rejected, failed to compile Python app.
OUTPUT
end
end
end
end