From 7655884b2e8ef641e7ca9bab7916c06a1b90d6ae Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Thu, 31 Jul 2025 12:29:32 +0200 Subject: [PATCH 1/2] WIP --- bin/compile | 10 +- bin/detect | 34 ++-- bin/release | 7 +- bin/test | 12 +- bin/test-compile | 22 +- lib/common.sh | 93 --------- lib/frameworks.sh | 45 +++++ lib/java_properties.sh | 21 ++ lib/maven.sh | 343 ++++++++++++++++++++++---------- lib/util.sh | 28 +++ test/spec/customization_spec.rb | 4 +- test/spec/misc_spec.rb | 20 +- test/spec/settings_xml_spec.rb | 25 ++- test/spec/versions_spec.rb | 50 ++++- 14 files changed, 467 insertions(+), 247 deletions(-) delete mode 100644 lib/common.sh create mode 100644 lib/java_properties.sh diff --git a/bin/compile b/bin/compile index 4ffc6268..93678c84 100755 --- a/bin/compile +++ b/bin/compile @@ -10,7 +10,7 @@ BUILDPACK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)" source "${BUILDPACK_DIR}/lib/output.sh" source "${BUILDPACK_DIR}/lib/util.sh" -source "${BUILDPACK_DIR}/lib/common.sh" +source "${BUILDPACK_DIR}/lib/java_properties.sh" source "${BUILDPACK_DIR}/lib/maven.sh" source "${BUILDPACK_DIR}/lib/metrics.sh" source "${BUILDPACK_DIR}/lib/openjdk.sh" @@ -22,5 +22,9 @@ util::export_env_dir "${ENV_DIR}" "." "JAVA_OPTS|JAVA_TOOL_OPTIONS" openjdk::install_openjdk_via_jvm_common_buildpack "${BUILD_DIR}" "${BUILDPACK_DIR}" -maven::run_mvn "compile" "${BUILD_DIR}" "${CACHE_DIR}" -maven::remove_mvn "${BUILD_DIR}" "${CACHE_DIR}" +maven::setup_maven_and_build_app \ + "${BUILD_DIR}" \ + "${CACHE_DIR}" \ + "${MAVEN_JAVA_OPTS:-""}" \ + "${MAVEN_CUSTOM_OPTS:-"-DskipTests"}" \ + "${MAVEN_CUSTOM_GOALS:-"clean dependency:list install"}" diff --git a/bin/detect b/bin/detect index 7a4f37a6..63bb7511 100755 --- a/bin/detect +++ b/bin/detect @@ -4,17 +4,23 @@ set -euo pipefail BUILD_DIR="${1}" -if [[ -f "${BUILD_DIR}/pom.xml" ]] || - [[ -f "${BUILD_DIR}/pom.atom" ]] || - [[ -f "${BUILD_DIR}/pom.clj" ]] || - [[ -f "${BUILD_DIR}/pom.groovy" ]] || - [[ -f "${BUILD_DIR}/pom.rb" ]] || - [[ -f "${BUILD_DIR}/pom.scala" ]] || - [[ -f "${BUILD_DIR}/pom.yaml" ]] || - [[ -f "${BUILD_DIR}/pom.yml" ]]; then - echo "Java" - exit 0 -else - (>&2 echo "Could not find a pom.xml file! Please check that it exists and is committed to Git.") - exit 1 -fi +extensions=( + "xml" + "atom" + "clj" + "groovy" + "rb" + "scala" + "yaml" + "yml" +) + +for extension in "${extensions[@]}"; do + if [[ -f "${BUILD_DIR}/pom.${extension}" ]]; then + echo "Java" + exit 0 + fi +done + +(>&2 echo "Could not find a pom.xml file or other supported POM format!") +exit 1 diff --git a/bin/release b/bin/release index bb4e2685..815950e8 100755 --- a/bin/release +++ b/bin/release @@ -12,10 +12,8 @@ source "${BUILDPACK_DIR}/lib/frameworks.sh" echo "---" if frameworks::has_postgres "${BUILD_DIR}"; then - cat <"${BUILD_DIR}/.profile.d/maven.sh" + export M2_HOME="\$HOME/.maven" + export MAVEN_OPTS="-Xmx1024m -Duser.home=\$HOME -Dmaven.repo.local=\$HOME/.m2/repository" + export PATH="\$M2_HOME/bin:\$PATH" +EOF + +util::cache_copy ".m2" "${BUILD_DIR}" "${CACHE_DIR}" diff --git a/lib/common.sh b/lib/common.sh deleted file mode 100644 index 0ff95dfa..00000000 --- a/lib/common.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash - -# This is technically redundant, since all consumers of this lib will have enabled these, -# however, it helps Shellcheck realise the options under which these functions will run. -set -euo pipefail - -export DEFAULT_MAVEN_VERSION="3.9.4" - -common::install_maven() { - local install_dir="${1}" - local build_dir="${2}" - local maven_home="${install_dir}/.maven" - - local defined_maven_version - defined_maven_version=$(common::detect_maven_version "${build_dir}") - - local maven_version="${defined_maven_version:-${DEFAULT_MAVEN_VERSION}}" - - output::step "Installing Maven ${maven_version}..." - local maven_url="https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${maven_version}/apache-maven-${maven_version}-bin.tar.gz" - if common::is_supported_maven_version "${maven_version}" "${maven_url}"; then - common::download_maven "${maven_url}" "${maven_home}" - else - output::error <<-EOF - ERROR: You have defined an unsupported Maven version in the system.properties file. - - The default supported version is ${DEFAULT_MAVEN_VERSION} - EOF - return 1 - fi -} - -common::download_maven() { - local maven_url="${1}" - local install_dir="${2}" - - rm -rf "${install_dir}" - mkdir -p "${install_dir}" - curl --fail --retry 3 --retry-connrefused --connect-timeout 5 --silent --max-time 60 --location "${maven_url}" | tar -xzm --strip-components 1 -C "${install_dir}" - chmod +x "${install_dir}/bin/mvn" -} - -common::is_supported_maven_version() { - local maven_version="${1}" - local maven_url="${2:?}" - if [[ "${maven_version}" = "${DEFAULT_MAVEN_VERSION}" ]]; then - return 0 - elif curl -I --retry 3 --retry-connrefused --connect-timeout 5 --fail --silent --max-time 5 --location "${maven_url}" >/dev/null; then - return 0 - else - return 1 - fi -} - -common::detect_maven_version() { - local base_dir="${1}" - if [[ -f "${base_dir}/system.properties" ]]; then - local maven_version - maven_version=$(common::get_app_system_value "${base_dir}/system.properties" "maven.version") - if [[ -n "${maven_version}" ]]; then - echo "${maven_version}" - else - echo "" - fi - else - echo "" - fi -} - -common::get_app_system_value() { - local file="${1?No file specified}" - local key="${2?No key specified}" - - # escape for regex - local escaped_key - # shellcheck disable=SC2001 - escaped_key="$(echo "${key}" | sed "s/\./\\\./g")" - - [[ -f "${file}" ]] && - grep -E "^${escaped_key}[[:space:]=]+" "${file}" | - sed -E -e "s/${escaped_key}([\ \t]*=[\ \t]*|[\ \t]+)([A-Za-z0-9\.-]*).*/\2/g" -} - -common::cache_copy() { - local rel_dir="${1}" - local from_dir="${2}" - local to_dir="${3}" - rm -rf "${to_dir:?}/${rel_dir:?}" - if [[ -d "${from_dir}/${rel_dir}" ]]; then - mkdir -p "${to_dir}/${rel_dir}" - cp -pr "${from_dir}/${rel_dir}"/. "${to_dir}/${rel_dir}" - fi -} diff --git a/lib/frameworks.sh b/lib/frameworks.sh index 9e0bebdd..4210f730 100644 --- a/lib/frameworks.sh +++ b/lib/frameworks.sh @@ -4,6 +4,15 @@ # however, it helps Shellcheck realise the options under which these functions will run. set -euo pipefail +# Detects if the application is a Spring Boot project by checking for Spring Boot +# group and artifact IDs in the pom.xml file. +# +# Usage: +# ``` +# if frameworks::is_spring_boot "${BUILD_DIR}"; then +# echo "Spring Boot application detected" +# fi +# ``` frameworks::is_spring_boot() { local build_dir="${1}" @@ -11,21 +20,57 @@ frameworks::is_spring_boot() { grep -qs "spring-boot" "${build_dir}/pom.xml" } +# Detects if the application uses WildFly Swarm by checking for WildFly Swarm +# group ID in the pom.xml file. +# +# Usage: +# ``` +# if frameworks::is_wildfly_swarm "${BUILD_DIR}"; then +# echo "WildFly Swarm application detected" +# fi +# ``` frameworks::is_wildfly_swarm() { local build_dir="${1}" grep -qs "org.wildfly.swarm" "${build_dir}/pom.xml" } +# Detects if the application uses Micronaut by checking for Micronaut +# group ID in the pom.xml file. +# +# Usage: +# ``` +# if frameworks::is_micronaut "${BUILD_DIR}"; then +# echo "Micronaut application detected" +# fi +# ``` frameworks::is_micronaut() { local build_dir="${1}" grep -qs "io.micronaut" "${build_dir}/pom.xml" } +# Detects if the application uses Quarkus by checking for Quarkus +# group ID in the pom.xml file. +# +# Usage: +# ``` +# if frameworks::is_quarkus "${BUILD_DIR}"; then +# echo "Quarkus application detected" +# fi +# ``` frameworks::is_quarkus() { local build_dir="${1}" grep -qs "io.quarkus" "${build_dir}/pom.xml" } +# Detects if the application has PostgreSQL dependencies by checking for +# PostgreSQL-related group IDs in the pom.xml file. +# +# Usage: +# ``` +# if frameworks::has_postgres "${BUILD_DIR}"; then +# echo "PostgreSQL dependency detected" +# fi +# ``` frameworks::has_postgres() { local build_dir="${1}" diff --git a/lib/java_properties.sh b/lib/java_properties.sh new file mode 100644 index 00000000..bd383a71 --- /dev/null +++ b/lib/java_properties.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Reads the value of a key from a Java properties file +# +# ``` +# java_properties:get "system.properties" "java.runtime.version" +# ``` +java_properties::get() { + local file=${1:?} + local key=${2:?} + + if [ -f "${file}" ]; then + local escaped_key + escaped_key="${key//\./\\.}" + + grep -E "^${escaped_key}[[:space:]=]+" "${file}" | + sed -E -e "s/${escaped_key}([\ \t]*=[\ \t]*|[\ \t]+)([_A-Za-z0-9\.-]*).*/\2/g" + else + echo "" + fi +} diff --git a/lib/maven.sh b/lib/maven.sh index 14023da2..0e5dffc8 100644 --- a/lib/maven.sh +++ b/lib/maven.sh @@ -4,134 +4,275 @@ # however, it helps Shellcheck realise the options under which these functions will run. set -euo pipefail -maven::mvn_java_opts() { - local scope="${1}" - local home="${2}" - local cache="${3}" - - echo -n "-Xmx1024m" - if [[ "${scope}" = "compile" ]]; then - echo -n " ${MAVEN_JAVA_OPTS:-""}" - elif [[ "${scope}" = "test-compile" ]]; then - echo -n "" - fi +BUILDPACK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)" +source "${BUILDPACK_DIR}/lib/java_properties.sh" +source "${BUILDPACK_DIR}/lib/util.sh" - echo -n " -Duser.home=${home} -Dmaven.repo.local=${cache}/.m2/repository" -} +export DEFAULT_MAVEN_VERSION="3.9.4" + +# Sets up Maven environment and builds the application. +# +# This function handles the complete Maven build process including: +# - Detecting and configuring Maven Wrapper (mvnw) or downloading specified Maven version +# - Setting up Maven repository caching and wrapper caching for faster subsequent builds +# - Configuring MAVEN_OPTS with memory limits and repository locations +# - Resolving settings.xml from MAVEN_SETTINGS_PATH, MAVEN_SETTINGS_URL, or local file +# - Executing the build with the specified Maven goals and options +# +# Usage: +# ``` +# maven::setup_maven_and_build_app "${BUILD_DIR}" "${CACHE_DIR}" "${MAVEN_JAVA_OPTS}" "${MAVEN_OPTS}" "${MAVEN_GOALS}" +# ``` +maven::setup_maven_and_build_app() { + local build_dir="${1}" + local cache_dir="${2}" + local maven_java_opts="${3}" + local maven_opts="${4}" + local maven_goals="${5}" -maven::mvn_cmd_opts() { - local scope="${1}" + # See: https://maven.apache.org/configure.html#maven_opts-environment-variable + export MAVEN_OPTS="-Xmx1024m${maven_java_opts:+ ${maven_java_opts}} -Duser.home=${build_dir} -Dmaven.repo.local=${cache_dir}/.m2/repository" - if [[ "${scope}" = "compile" ]]; then - echo -n "${MAVEN_CUSTOM_OPTS:-"-DskipTests"}" - echo -n " ${MAVEN_CUSTOM_GOALS:-"clean dependency:list install"}" - elif [[ "${scope}" = "test-compile" ]]; then - echo -n "${MAVEN_CUSTOM_GOALS:-"clean dependency:resolve-plugins test-compile"}" + if maven::should_use_wrapper "${build_dir}"; then + util::cache_copy ".m2/wrapper" "${cache_dir}" "${build_dir}" + chmod +x "${build_dir}/mvnw" + local maven_exe="./mvnw" else - echo -n "" + local maven_version + maven_version="$(java_properties::get "${build_dir}/system.properties" "maven.version")" + maven::install_maven "${maven_version:-${DEFAULT_MAVEN_VERSION}}" "${cache_dir}/.maven" + local maven_exe="mvn" fi -} -maven::mvn_settings_opt() { - local home="${1}" - local maven_install_dir="${2}" + maven::install_settings_xml "${build_dir}" "${build_dir}/.m2/settings.xml" - if [[ -n "${MAVEN_SETTINGS_PATH:-}" ]]; then - echo -n "-s ${MAVEN_SETTINGS_PATH}" - elif [[ -n "${MAVEN_SETTINGS_URL:-}" ]]; then - local settings_xml="${maven_install_dir}/.m2/settings.xml" - mkdir -p "$(dirname "${settings_xml}")" - curl --retry 3 --retry-connrefused --connect-timeout 5 --silent --max-time 10 --location "${MAVEN_SETTINGS_URL}" --output "${settings_xml}" - if [[ -f "${settings_xml}" ]]; then - echo -n "-s ${settings_xml}" - else - output::error <<-EOF - ERROR: Could not download settings.xml from the URL defined in MAVEN_SETTINGS_URL! - EOF - return 1 - fi - elif [[ -f "${home}/settings.xml" ]]; then - echo -n "-s ${home}/settings.xml" - else - echo -n "" - fi -} + output::step "Executing Maven" + + cd "${build_dir}" + echo "$ ${maven_exe} ${maven_opts} ${maven_goals}" | output::indent + + # We rely on word splitting for settings_xml_opts, maven_opts, and maven_goals: + # Intentional word splitting needed for Maven command arguments + # shellcheck disable=SC2086 + if ! ${maven_exe} -DoutputFile=target/mvn-dependency-list.log -B ${maven_opts} ${maven_goals} | output::indent; then + output::error <<-EOF + Error: Maven build failed. + + An error occurred during the Maven build process. This usually + indicates an issue with your application's dependencies, configuration, + or source code. + + First, check the build output above for specific error messages + from Maven that might indicate what went wrong. Common issues include: + + - Missing or incompatible dependencies in your POM + - Compilation errors in your application source code + - Test failures (if tests are enabled during the build) + - Invalid Maven configuration or settings + - Using an incompatible OpenJDK version for your project + + If you're unable to determine the cause from the Maven output, + try building your application locally with the same Maven command + to reproduce and debug the issue. + EOF -maven::has_maven_wrapper() { - local home="${1}" - if [[ -f "${home}/mvnw" ]] && - [[ -f "${home}/.mvn/wrapper/maven-wrapper.properties" ]] && - [[ -z "$(common::detect_maven_version "${home}")" ]]; then - return 0 - else return 1 fi + + util::cache_copy ".m2/wrapper" "${build_dir}" "${cache_dir}" + rm -rf "${build_dir}/.m2/wrapper" } -maven::run_mvn() { - local scope="${1}" - local home="${2}" - local maven_install_dir="${3}" +# Downloads and installs the specified Maven version to the given directory. +# Sets up PATH to include the Maven bin directory. +# +# Usage: +# ``` +# maven::install_maven "3.9.4" "/path/to/maven/home" +# ``` +maven::install_maven() { + local maven_version="${1}" + local maven_home="${2}" - mkdir -p "${maven_install_dir}" - if maven::has_maven_wrapper "${home}"; then - common::cache_copy ".m2/wrapper" "${maven_install_dir}" "${home}" - chmod +x "${home}/mvnw" - local maven_exe="./mvnw" - else - # shellcheck disable=SC2164 - cd "${maven_install_dir}" + output::step "Installing Maven ${maven_version}..." - common::install_maven "${maven_install_dir}" "${home}" - PATH="${maven_install_dir}/.maven/bin:${PATH}" - local maven_exe="mvn" - # shellcheck disable=SC2164 - cd "${home}" - fi + local maven_url="https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${maven_version}/apache-maven-${maven_version}-bin.tar.gz" - local mvn_settings_opt - mvn_settings_opt="$(maven::mvn_settings_opt "${home}" "${maven_install_dir}")" + mkdir -p "${maven_home}" - MAVEN_OPTS="$(maven::mvn_java_opts "${scope}" "${home}" "${maven_install_dir}")" - export MAVEN_OPTS + local tarball_path + tarball_path=$(mktemp) - # shellcheck disable=SC2164 - cd "${home}" - local mvn_opts - mvn_opts="$(maven::mvn_cmd_opts "${scope}")" + local http_status_code + http_status_code=$(curl \ + --retry 3 \ + --retry-connrefused \ + --connect-timeout 5 \ + --silent \ + --show-error \ + --max-time 60 \ + --location \ + --write-out "%{http_code}" \ + --output "${tarball_path}" \ + "${maven_url}") - output::step "Executing Maven" - echo "$ ${maven_exe} ${mvn_opts}" | output::indent + local curl_exit_code=$? - # We rely on word splitting for mvn_settings_opt and mvn_opts: - # shellcheck disable=SC2086 - if ! ${maven_exe} -DoutputFile=target/mvn-dependency-list.log -B ${mvn_settings_opt} ${mvn_opts} | output::indent; then + if [[ "${http_status_code}" == "404" ]]; then + output::error <<-EOF + Error: The requested Maven version isn't available. + + Your app's system.properties file specifies a Maven version + of ${maven_version}, however, we couldn't find that version in the + Maven repository. + + Check that this Maven version has been released upstream: + https://maven.apache.org/docs/history.html + + If it has, make sure that you are using the latest version + of this buildpack, and haven't pinned to an older release: + https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks + https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references + + We also strongly recommend using the Maven Wrapper instead of + pinning to an exact Maven version such as ${maven_version}. + Remove the maven.version property from your system.properties file + and set up Maven Wrapper in your project, which will automatically + download and use the correct Maven version. + + Learn more about Maven Wrapper: + https://maven.apache.org/wrapper/ + + The default supported version is ${DEFAULT_MAVEN_VERSION}. + EOF + + exit 1 + elif [[ "${curl_exit_code}" -ne 0 || "${http_status_code}" != "200" ]]; then output::error <<-EOF - ERROR: Failed to build app with Maven + Error: Unable to download Maven. + + An error occurred while downloading the Maven archive from: + ${maven_url} + + In some cases, this happens due to a temporary issue with + the network connection or server. + + First, make sure that you are using the latest version + of this buildpack, and haven't pinned to an older release: + https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks + https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references + + Then try building again to see if the error resolves itself. - We're sorry this build is failing! If you can't find the issue in application code, - please submit a ticket so we can help: https://help.heroku.com/ + HTTP status code: ${http_status_code}, curl exit code: ${curl_exit_code} EOF - return 1 + + exit 1 + fi + + local error_log + error_log=$(mktemp) + + if ! tar -xzm --strip-components 1 -C "${maven_home}" -f "${tarball_path}" 2>&1 | tee "${error_log}"; then + output::error <<-EOF + Error: Unable to extract Maven. + + An error occurred while extracting the Maven archive: + ${maven_url} + + In some cases, this happens due to a corrupted download + or a temporary issue with the archive format. + + First, make sure that you are using the latest version + of this buildpack, and haven't pinned to an older release: + https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks + https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references + + Then try building again to see if the error resolves itself. + + Error details: $(head --lines=1 "${error_log}" || true) + EOF + + exit 1 fi + + PATH="${maven_home}/bin:${PATH}" + chmod +x "${maven_home}/bin/mvn" } -maven::write_mvn_profile() { - local home="${1}" +# Determines if Maven Wrapper should be used for the given build directory. +# Returns 0 (true) if mvnw exists and no maven.version is specified in system.properties. +# +# Usage: +# ``` +# if maven::should_use_wrapper "${BUILD_DIR}"; then +# echo "Using Maven Wrapper" +# fi +# ``` +maven::should_use_wrapper() { + local build_dir="${1}" - mkdir -p "${home}/.profile.d" - cat <<-EOF >"${home}/.profile.d/maven.sh" - export M2_HOME="\$HOME/.maven" - export MAVEN_OPTS="$(maven::mvn_java_opts "test" "\$HOME" "\$HOME")" - export PATH="\$M2_HOME/bin:\$PATH" - EOF + [[ -f "${build_dir}/mvnw" ]] && [[ -z "$(java_properties::get "${build_dir}/system.properties" "maven.version")" ]] } -maven::remove_mvn() { - local home="${1}" - local maven_install_dir="${2}" - if maven::has_maven_wrapper "${home}"; then - common::cache_copy ".m2/wrapper" "${home}" "${maven_install_dir}" - rm -rf "${home}/.m2" +# Installs Maven settings.xml to the specified destination. +# +# This is an optional feature - if no settings are found, this function is a no-op. +# Checks for settings in priority order and symlinks local files or downloads remote ones: +# 1. MAVEN_SETTINGS_PATH environment variable (symlinked) +# 2. MAVEN_SETTINGS_URL environment variable (downloaded) +# 3. Local settings.xml file in project directory (symlinked) +# +# Usage: +# ``` +# maven::install_settings_xml "${BUILD_DIR}" "${BUILD_DIR}/.m2/settings.xml" +# ``` +maven::install_settings_xml() { + local build_dir="${1}" + local settings_destination="${2}" + + mkdir -p "$(dirname "${settings_destination}")" + + if [[ -n "${MAVEN_SETTINGS_PATH:-}" ]]; then + output::step "Using settings.xml from ${MAVEN_SETTINGS_PATH}" + ln -sf "${MAVEN_SETTINGS_PATH}" "${settings_destination}" + elif [[ -n "${MAVEN_SETTINGS_URL:-}" ]]; then + output::step "Using settings.xml from ${MAVEN_SETTINGS_URL}" + + if ! curl \ + --silent \ + --show-error \ + --fail \ + --retry 3 \ + --retry-connrefused \ + --connect-timeout 5 \ + --max-time 10 \ + --location \ + --output "${settings_destination}" \ + "${MAVEN_SETTINGS_URL}"; then + output::error <<-EOF + Error: Unable to download Maven settings.xml. + + An error occurred while downloading the Maven settings file from: + ${MAVEN_SETTINGS_URL} + + In some cases, this happens due to a temporary issue with + the network connection or server, or because the URL is + inaccessible or requires authentication. + + Check that the URL in your MAVEN_SETTINGS_URL environment + variable is correct and publicly accessible. If the settings file + is not needed, you can remove the MAVEN_SETTINGS_URL environment variable + to use default Maven settings. + + Learn more about Maven settings configuration: + https://devcenter.heroku.com/articles/using-a-custom-maven-settings-xml + EOF + + exit 1 + fi + elif [[ -f "${build_dir}/settings.xml" ]]; then + output::step "Using settings.xml from project directory" + ln -sf "${build_dir}/settings.xml" "${settings_destination}" fi } diff --git a/lib/util.sh b/lib/util.sh index 37e4c393..b80b0480 100644 --- a/lib/util.sh +++ b/lib/util.sh @@ -30,6 +30,34 @@ util::export_env_dir() { done } +# Returns the current time in milliseconds since epoch. +# +# Usage: +# ``` +# timestamp=$(util::nowms) +# ``` util::nowms() { date +%s%3N } + +# Copies a subdirectory from source to destination, replacing any existing content. +# +# Usage: +# ``` +# util::cache_copy ".m2/wrapper" "${CACHE_DIR}" "${BUILD_DIR}" +# ``` +util::cache_copy() { + local subdirectory="${1}" + local source_dir="${2}" + local destination_dir="${3}" + + local destination_path="${destination_dir}/${subdirectory}" + local source_path="${source_dir}/${subdirectory}" + + rm -rf "${destination_path:?}" + + if [[ -d "${source_path}" ]]; then + mkdir -p "${destination_path}" + cp -pr "${source_path}"/. "${destination_path}" + fi +} diff --git a/test/spec/customization_spec.rb b/test/spec/customization_spec.rb index 212b4334..ca5c8ae8 100644 --- a/test/spec/customization_spec.rb +++ b/test/spec/customization_spec.rb @@ -6,7 +6,7 @@ it 'allows to set custom Maven goals via MAVEN_CUSTOM_GOALS' do app = Hatchet::Runner.new('simple-http-service', config: { MAVEN_CUSTOM_GOALS: 'site' }) app.deploy do - expect(app.output).to include('./mvnw -DskipTests site') + expect(app.output).to include('$ ./mvnw -DskipTests site') expect(app.output).to include('[INFO] --- maven-site-plugin:3.7.1:site (default-site) @ simple-http-service ---') # The dependency list is implemented by using the dependency:list goal. We need to @@ -40,7 +40,7 @@ it 'allows to set custom Maven goals via MAVEN_CUSTOM_OPTS' do app = Hatchet::Runner.new('simple-http-service', config: { MAVEN_CUSTOM_OPTS: '-X' }) app.deploy do - expect(app.output).to include('./mvnw -X clean dependency:list install') + expect(app.output).to include('$ ./mvnw -X clean dependency:list install') expect(app.output).to include('[DEBUG] -- end configuration --') # -DskipTests is part of the default Maven options. We expect it to be overridden by MAVEN_CUSTOM_OPTS and diff --git a/test/spec/misc_spec.rb b/test/spec/misc_spec.rb index f1ddaa90..780eb80c 100644 --- a/test/spec/misc_spec.rb +++ b/test/spec/misc_spec.rb @@ -73,10 +73,24 @@ remote: \\[ERROR\\] For more information about the errors and possible solutions, please read the following articles: remote: \\[ERROR\\] \\[Help 1\\] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException remote: - remote: ! ERROR: Failed to build app with Maven + remote: ! Error: Maven build failed. remote: ! - remote: ! We're sorry this build is failing! If you can't find the issue in application code, - remote: ! please submit a ticket so we can help: https://help.heroku.com/ + remote: ! An error occurred during the Maven build process. This usually + remote: ! indicates an issue with your application's dependencies, configuration, + remote: ! or source code. + remote: ! + remote: ! First, check the build output above for specific error messages + remote: ! from Maven that might indicate what went wrong. Common issues include: + remote: ! + remote: ! - Missing or incompatible dependencies in your POM + remote: ! - Compilation errors in your application source code + remote: ! - Test failures \\(if tests are enabled during the build\\) + remote: ! - Invalid Maven configuration or settings + remote: ! - Using an incompatible OpenJDK version for your project + remote: ! + remote: ! If you're unable to determine the cause from the Maven output, + remote: ! try building your application locally with the same Maven command + remote: ! to reproduce and debug the issue. remote: remote: ! Push rejected, failed to compile Java app. REGEX diff --git a/test/spec/settings_xml_spec.rb b/test/spec/settings_xml_spec.rb index be3c33dd..4f890d9b 100644 --- a/test/spec/settings_xml_spec.rb +++ b/test/spec/settings_xml_spec.rb @@ -17,17 +17,22 @@ config: { MAVEN_SETTINGS_URL: SETTINGS_XML_URL_404 }) app.deploy do expect(clean_output(app.output)).to include(<<~OUTPUT) - remote: -----> Executing Maven - remote: $ ./mvnw -DskipTests clean dependency:list install - remote: [ERROR] Error executing Maven. - remote: [ERROR] 1 problem was encountered while building the effective settings - remote: [FATAL] Non-parseable settings /tmp/codon/tmp/cache/.m2/settings.xml: only whitespace content allowed before start tag and not N (position: START_DOCUMENT seen N... @1:1) @ /tmp/codon/tmp/cache/.m2/settings.xml, line 1, column 1 - remote: - remote: - remote: ! ERROR: Failed to build app with Maven + remote: ! Error: Unable to download Maven settings.xml. + remote: ! + remote: ! An error occurred while downloading the Maven settings file from: + remote: ! #{SETTINGS_XML_URL_404} + remote: ! + remote: ! In some cases, this happens due to a temporary issue with + remote: ! the network connection or server, or because the URL is + remote: ! inaccessible or requires authentication. + remote: ! + remote: ! Check that the URL in your MAVEN_SETTINGS_URL environment + remote: ! variable is correct and publicly accessible. If the settings file + remote: ! is not needed, you can remove the MAVEN_SETTINGS_URL environment variable + remote: ! to use default Maven settings. remote: ! - remote: ! We're sorry this build is failing! If you can't find the issue in application code, - remote: ! please submit a ticket so we can help: https://help.heroku.com/ + remote: ! Learn more about Maven settings configuration: + remote: ! https://devcenter.heroku.com/articles/using-a-custom-maven-settings-xml remote: remote: ! Push rejected, failed to compile Java app. OUTPUT diff --git a/test/spec/versions_spec.rb b/test/spec/versions_spec.rb index 412d4cf0..2a09c3d5 100644 --- a/test/spec/versions_spec.rb +++ b/test/spec/versions_spec.rb @@ -38,9 +38,30 @@ expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Installing Maven #{UNKNOWN_MAVEN_VERSION}... remote: - remote: ! ERROR: You have defined an unsupported Maven version in the system.properties file. + remote: ! Error: The requested Maven version isn't available. remote: ! - remote: ! The default supported version is #{DEFAULT_MAVEN_VERSION} + remote: ! Your app's system.properties file specifies a Maven version + remote: ! of #{UNKNOWN_MAVEN_VERSION}, however, we couldn't find that version in the + remote: ! Maven repository. + remote: ! + remote: ! Check that this Maven version has been released upstream: + remote: ! https://maven.apache.org/docs/history.html + remote: ! + remote: ! If it has, make sure that you are using the latest version + remote: ! of this buildpack, and haven't pinned to an older release: + remote: ! https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks + remote: ! https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references + remote: ! + remote: ! We also strongly recommend using the Maven Wrapper instead of + remote: ! pinning to an exact Maven version such as #{UNKNOWN_MAVEN_VERSION}. + remote: ! Remove the maven.version property from your system.properties file + remote: ! and set up Maven Wrapper in your project, which will automatically + remote: ! download and use the correct Maven version. + remote: ! + remote: ! Learn more about Maven Wrapper: + remote: ! https://maven.apache.org/wrapper/ + remote: ! + remote: ! The default supported version is #{DEFAULT_MAVEN_VERSION}. remote: remote: ! Push rejected, failed to compile Java app. OUTPUT @@ -73,9 +94,30 @@ expect(clean_output(app.output)).to include(<<~OUTPUT) remote: -----> Installing Maven #{UNKNOWN_MAVEN_VERSION}... remote: - remote: ! ERROR: You have defined an unsupported Maven version in the system.properties file. + remote: ! Error: The requested Maven version isn't available. + remote: ! + remote: ! Your app's system.properties file specifies a Maven version + remote: ! of #{UNKNOWN_MAVEN_VERSION}, however, we couldn't find that version in the + remote: ! Maven repository. + remote: ! + remote: ! Check that this Maven version has been released upstream: + remote: ! https://maven.apache.org/docs/history.html + remote: ! + remote: ! If it has, make sure that you are using the latest version + remote: ! of this buildpack, and haven't pinned to an older release: + remote: ! https://devcenter.heroku.com/articles/managing-buildpacks#view-your-buildpacks + remote: ! https://devcenter.heroku.com/articles/managing-buildpacks#classic-buildpacks-references + remote: ! + remote: ! We also strongly recommend using the Maven Wrapper instead of + remote: ! pinning to an exact Maven version such as #{UNKNOWN_MAVEN_VERSION}. + remote: ! Remove the maven.version property from your system.properties file + remote: ! and set up Maven Wrapper in your project, which will automatically + remote: ! download and use the correct Maven version. + remote: ! + remote: ! Learn more about Maven Wrapper: + remote: ! https://maven.apache.org/wrapper/ remote: ! - remote: ! The default supported version is #{DEFAULT_MAVEN_VERSION} + remote: ! The default supported version is #{DEFAULT_MAVEN_VERSION}. remote: remote: ! Push rejected, failed to compile Java app. OUTPUT From 5c979c1a8d972eea09c8aa2e0180ca07368889b0 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Thu, 31 Jul 2025 13:57:10 +0200 Subject: [PATCH 2/2] Fix CI --- lib/maven.sh | 11 +++++++++-- test/spec/versions_spec.rb | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/maven.sh b/lib/maven.sh index 0e5dffc8..852d7305 100644 --- a/lib/maven.sh +++ b/lib/maven.sh @@ -196,7 +196,7 @@ maven::install_maven() { exit 1 fi - PATH="${maven_home}/bin:${PATH}" + export PATH="${maven_home}/bin:${PATH}" chmod +x "${maven_home}/bin/mvn" } @@ -234,8 +234,15 @@ maven::install_settings_xml() { mkdir -p "$(dirname "${settings_destination}")" if [[ -n "${MAVEN_SETTINGS_PATH:-}" ]]; then + local settings_source + if [[ "${MAVEN_SETTINGS_PATH}" = /* ]]; then + settings_source="${MAVEN_SETTINGS_PATH}" + else + settings_source="${build_dir}/${MAVEN_SETTINGS_PATH}" + fi + output::step "Using settings.xml from ${MAVEN_SETTINGS_PATH}" - ln -sf "${MAVEN_SETTINGS_PATH}" "${settings_destination}" + ln -sf "${settings_source}" "${settings_destination}" elif [[ -n "${MAVEN_SETTINGS_URL:-}" ]]; then output::step "Using settings.xml from ${MAVEN_SETTINGS_URL}" diff --git a/test/spec/versions_spec.rb b/test/spec/versions_spec.rb index 2a09c3d5..022ce7ee 100644 --- a/test/spec/versions_spec.rb +++ b/test/spec/versions_spec.rb @@ -69,7 +69,7 @@ end it 'installs the default Maven version when no wrapper is present and no version is explicitly configured' do - app = Hatchet::Runner.new('simple-http-service', allow_failure: true) + app = Hatchet::Runner.new('simple-http-service') app.before_deploy do `rm -r mvnw` end