Skip to content

Commit 4f47a8e

Browse files
authored
Implement bin/report (#227)
1 parent 6c05352 commit 4f47a8e

File tree

5 files changed

+256
-3
lines changed

5 files changed

+256
-3
lines changed

bin/compile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ ENV_DIR=$3
1414
source $BP_DIR/lib/common.sh
1515
source $BP_DIR/lib/maven.sh
1616
source $BP_DIR/lib/buildpack-stdlib-v7.sh
17+
source $BP_DIR/lib/metadata.sh
18+
19+
# Initialise the buildpack metadata store.
20+
# This is used to track state across builds (for cache invalidation and messaging when build
21+
# configuration changes) and also so that `bin/report` can generate the build report.
22+
meta_init "${CACHE_DIR}" "java"
23+
meta_setup
1724

1825
export_env $ENV_DIR "." "JAVA_OPTS|JAVA_TOOL_OPTIONS"
1926

@@ -23,4 +30,4 @@ install_jdk "${BUILD_DIR}" "${CACHE_DIR}"
2330
[ -n "$(find ${BUILD_DIR} -type f -name "*.groovy")" ] && mcount "groovy.source"
2431

2532
run_mvn "compile" $BUILD_DIR $CACHE_DIR
26-
remove_mvn $BUILD_DIR $CACHE_DIR
33+
remove_mvn $BUILD_DIR $CACHE_DIR

bin/report

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env bash
2+
# Usage: bin/report <build-dir> <cache-dir> <env-dir>
3+
4+
# Produces a build report containing metadata about the build, that's consumed by the build system.
5+
# This script is run for both successful and failing builds, so it should not assume the build ran
6+
# to completion (e.g. OpenJDK may not even have been installed).
7+
#
8+
# Metadata must be emitted to stdout as valid YAML key-value pairs. Any fields that should always
9+
# be typed as a string must be explicitly quoted.
10+
#
11+
# Example valid stdout:
12+
# openjdk_version: 'X.Y.Z'
13+
# openjdk_install_duration: 1.234
14+
#
15+
# Failures in this script don't cause the overall build to fail (and won't appear in user
16+
# facing build logs) to avoid breaking builds unnecessarily / causing confusion. To debug
17+
# issues check the internal build system logs for `buildpack.report.failed` events, or
18+
# when developing run `make run` in this repo locally, which runs `bin/report` too.
19+
20+
set -euo pipefail
21+
shopt -s inherit_errexit
22+
23+
CACHE_DIR="${2}"
24+
25+
# The absolute path to the root of the buildpack.
26+
BUILDPACK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)"
27+
28+
# The build system doesn't source the `export` script before running this script, so we have to do
29+
# so manually (if it exists) so that buildpack java can be found (if the build succeeded).
30+
EXPORT_FILE="${BUILDPACK_DIR}/export"
31+
if [[ -f "${EXPORT_FILE}" ]]; then
32+
# shellcheck source=/dev/null
33+
source "${EXPORT_FILE}"
34+
fi
35+
36+
source "${BUILDPACK_DIR}/lib/metadata.sh"
37+
meta_init "${CACHE_DIR}" "java"
38+
39+
# Emit the key / value pair unquoted to stdout. Skips if the value is empty.
40+
# Based on: https://github.com/heroku/heroku-buildpack-nodejs/blob/main/bin/report
41+
kv_pair() {
42+
local key="${1}"
43+
local value="${2}"
44+
if [[ -n "${value}" ]]; then
45+
echo "${key}: ${value}"
46+
fi
47+
}
48+
49+
# Emit the key / value pair to stdout, safely quoting the string. Skips if the value is empty.
50+
# Based on: https://github.com/heroku/heroku-buildpack-nodejs/blob/main/bin/report
51+
# (Though instead uses single quotes instead of double to avoid escaping issues.)
52+
kv_pair_string() {
53+
local key="${1}"
54+
local value="${2}"
55+
if [[ -n "${value}" ]]; then
56+
# Escape any existing single quotes, which for YAML means replacing `'` with `''`.
57+
value="${value//\'/\'\'}"
58+
echo "${key}: '${value}'"
59+
fi
60+
}
61+
62+
STRING_FIELDS=(
63+
# Fields coming from jvm-common
64+
openjdk_version
65+
openjdk_distribution
66+
openjdk_version_selector
67+
)
68+
69+
# We don't want to quote numeric or boolean fields.
70+
ALL_OTHER_FIELDS=(
71+
# Fields coming from jvm-common
72+
openjdk_install_duration
73+
app_has_jdk_overlay
74+
)
75+
76+
for field in "${STRING_FIELDS[@]}"; do
77+
# shellcheck disable=SC2312 # TODO: Invoke this command separately to avoid masking its return value.
78+
kv_pair_string "${field}" "$(meta_get "${field}")"
79+
done
80+
81+
for field in "${ALL_OTHER_FIELDS[@]}"; do
82+
# shellcheck disable=SC2312 # TODO: Invoke this command separately to avoid masking its return value.
83+
kv_pair "${field}" "$(meta_get "${field}")"
84+
done

lib/kvstore.sh

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env bash
2+
3+
# This is technically redundant, since all consumers of this lib will have enabled these,
4+
# however, it helps Shellcheck realise the options under which these functions will run.
5+
set -eo pipefail
6+
7+
# Taken from: https://github.com/heroku/heroku-buildpack-nodejs/blob/main/lib/kvstore.sh
8+
9+
kv_create() {
10+
local f="${1}"
11+
mkdir -p "$(dirname "${f}")"
12+
touch "${f}"
13+
}
14+
15+
kv_clear() {
16+
local f="${1}"
17+
echo "" >"${f}"
18+
}
19+
20+
kv_set() {
21+
# TODO: Stop ignoring an incorrect number of passed arguments.
22+
if [[ $# -eq 3 ]]; then
23+
local f="${1}"
24+
if [[ -f "${f}" ]]; then
25+
echo "${2}=${3}" >>"${f}"
26+
fi
27+
fi
28+
}
29+
30+
# Returns a key from the key-value store file, or else the empty string
31+
# if the file doesn't exist or the key wasn't found.
32+
kv_get() {
33+
# TODO: Stop ignoring an incorrect number of passed arguments.
34+
if [[ $# -eq 2 ]]; then
35+
local f="${1}"
36+
if [[ -f "${f}" ]]; then
37+
# shellcheck disable=SC2310 # This function is invoked in an || condition so set -e will be disabled.
38+
grep "^${2}=" "${f}" | sed -e "s/^${2}=//" | tail -n 1 || true
39+
fi
40+
fi
41+
}
42+
43+
# get the value, but wrap it in quotes if it contains a space
44+
kv_get_escaped() {
45+
local value
46+
value=$(kv_get "${1}" "${2}")
47+
if [[ "${value}" =~ [[:space:]]+ ]]; then
48+
echo "\"${value}\""
49+
else
50+
echo "${value}"
51+
fi
52+
}
53+
54+
kv_keys() {
55+
local f="${1}"
56+
local keys=()
57+
58+
if [[ -f "${f}" ]]; then
59+
# Iterate over each line, splitting on the '=' character
60+
#
61+
# The || [[ -n "${key}" ]] statement addresses an issue with reading the last line
62+
# of a file when there is no newline at the end. This will not happen if the file
63+
# is created with this module, but can happen if it is written by hand.
64+
# See: https://stackoverflow.com/questions/12916352/shell-script-read-missing-last-line
65+
while IFS="=" read -r key value || [[ -n "${key}" ]]; do
66+
# if there are any empty lines in the store, skip them
67+
if [[ -n "${key}" ]]; then
68+
keys+=("${key}")
69+
fi
70+
done <"${f}"
71+
72+
echo "${keys[@]}" | tr ' ' '\n' | sort -u
73+
fi
74+
}
75+
76+
kv_list() {
77+
local f="${1}"
78+
79+
kv_keys "${f}" | tr ' ' '\n' | while read -r key; do
80+
if [[ -n "${key}" ]]; then
81+
# shellcheck disable=SC2312 # TODO: Invoke this command separately to avoid masking its return value.
82+
echo "${key}=$(kv_get_escaped "${f}" "${key}")"
83+
fi
84+
done
85+
}

lib/maven.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,8 @@ run_mvn() {
108108

109109
local cache_status="$(get_cache_status ${mavenInstallDir})"
110110
let start=$(nowms)
111-
${mavenExe} -DoutputFile=target/mvn-dependency-list.log -B ${mvn_settings_opt} ${mvnOpts} | indent
112111

113-
if [ "${PIPESTATUS[*]}" != "0 0" ]; then
112+
if ! ${mavenExe} -DoutputFile=target/mvn-dependency-list.log -B ${mvn_settings_opt} ${mvnOpts} | indent; then
114113
error "Failed to build app with Maven
115114
We're sorry this build is failing! If you can't find the issue in application code,
116115
please submit a ticket so we can help: https://help.heroku.com/"

lib/metadata.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env bash
2+
3+
# This is technically redundant, since all consumers of this lib will have enabled these,
4+
# however, it helps Shellcheck realise the options under which these functions will run.
5+
set -eo pipefail
6+
7+
# Based on: https://github.com/heroku/heroku-buildpack-nodejs/blob/main/lib/metadata.sh
8+
9+
JAVA_BUILDPACK_DIR="${JAVA_BUILDPACK_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)}"
10+
11+
source "${JAVA_BUILDPACK_DIR:?}/lib/kvstore.sh"
12+
13+
# Variables shared by this whole module
14+
BUILD_DATA_FILE=""
15+
PREVIOUS_BUILD_DATA_FILE=""
16+
17+
# Must be called before you can use any other methods
18+
meta_init() {
19+
local cache_dir="${1}"
20+
local buildpack_name="${2}"
21+
BUILD_DATA_FILE="${cache_dir}/build-data/${buildpack_name}"
22+
PREVIOUS_BUILD_DATA_FILE="${cache_dir}/build-data/${buildpack_name}-prev"
23+
}
24+
25+
# Moves the data from the last build into the correct place, and clears the store
26+
# This should be called after meta_init in bin/compile
27+
meta_setup() {
28+
# if the file already exists because it's from the last build, save it
29+
if [[ -f "${BUILD_DATA_FILE}" ]]; then
30+
cp "${BUILD_DATA_FILE}" "${PREVIOUS_BUILD_DATA_FILE}"
31+
fi
32+
33+
kv_create "${BUILD_DATA_FILE}"
34+
kv_clear "${BUILD_DATA_FILE}"
35+
}
36+
37+
# Force removal of exiting data file state. This is mostly useful during testing and not
38+
# expected to be used during buildpack execution.
39+
meta_force_clear() {
40+
[[ -f "${BUILD_DATA_FILE}" ]] && rm "${BUILD_DATA_FILE}"
41+
[[ -f "${PREVIOUS_BUILD_DATA_FILE}" ]] && rm "${PREVIOUS_BUILD_DATA_FILE}"
42+
}
43+
44+
meta_get() {
45+
kv_get "${BUILD_DATA_FILE}" "${1}"
46+
}
47+
48+
meta_set() {
49+
kv_set "${BUILD_DATA_FILE}" "${1}" "${2}"
50+
}
51+
52+
# Similar to mtime from buildpack-stdlib
53+
meta_time() {
54+
local key="${1}"
55+
local start="${2}"
56+
local end="${3:-$(nowms)}"
57+
local time
58+
time="$(echo "${start}" "${end}" | awk '{ printf "%.3f", ($2 - $1)/1000 }')"
59+
kv_set "${BUILD_DATA_FILE}" "${key}" "${time}"
60+
}
61+
62+
# Retrieve a value from a previous build if it exists
63+
# This is useful to give the user context about what changed if the
64+
# build has failed. Ex:
65+
# - changed stacks
66+
# - deployed with a new major version of Node
67+
# - etc
68+
meta_prev_get() {
69+
kv_get "${PREVIOUS_BUILD_DATA_FILE}" "${1}"
70+
}
71+
72+
log_meta_data() {
73+
# print all values on one line in logfmt format
74+
# https://brandur.org/logfmt
75+
# the echo call ensures that all values are printed on a single line
76+
# shellcheck disable=SC2005,SC2046,SC2312
77+
echo $(kv_list "${BUILD_DATA_FILE}")
78+
}

0 commit comments

Comments
 (0)