@@ -131,6 +131,15 @@ if has_old_preset_sbt_opts; then
131131 EOF
132132fi
133133
134+ # Copy the target dir from cache to speed up compilation. This is legacy buildpack
135+ # behavior that other JVM buildpacks don't implement. It can cause cache bloat, stale artifacts,
136+ # and reduced build reproducibility. We've observed customers using SBT_CLEAN (which will remove these files)
137+ # much more than expected (~10% of builds), presumably to work around issues.
138+ # This will be removed in a future version.
139+ target_dir_cache_restore_start_time=$(util::nowms)
140+ util::cache_copy "target" "${CACHE_DIR}" "${BUILD_DIR}"
141+ metrics::set_duration "target_dir_cache_restore_duration" "${target_dir_cache_restore_start_time}"
142+
134143if [[ -n "${SBT_PROJECT}" ]]; then
135144 SBT_TASKS="${SBT_PROJECT}/compile ${SBT_PROJECT}/stage"
136145else
147156# See: https://devcenter.heroku.com/articles/scala-support#clean-builds
148157[[ "${SBT_CLEAN}" = "true" ]] && SBT_TASKS="clean ${SBT_TASKS}"
149158
150-
151-
152159ivy_home_dir="${CACHE_DIR}/ivy_home"
153160util::prepend_to_env "SBT_OPTS" "-Dsbt.ivy.home=${ivy_home_dir}"
154161mkdir -p "${ivy_home_dir}"
@@ -161,24 +168,35 @@ sbt_boot_dir="${CACHE_DIR}/sbt_boot"
161168util::prepend_to_env "SBT_OPTS" "-Dsbt.boot.directory=${sbt_boot_dir}"
162169mkdir -p "${sbt_boot_dir}"
163170
164- # Recreate the sbt global directory on each build with the current Heroku plugins and settings.
165171# See: https://www.scala-sbt.org/1.x/docs/Command-Line-Reference.html
166172sbt_global_dir="${CACHE_DIR}/sbt_global"
167173util::prepend_to_env "SBT_OPTS" "-Dsbt.global.base=${sbt_global_dir}"
168- rm -rf "${sbt_global_dir}"
169174mkdir -p "${sbt_global_dir}"
170175
171- # Install the Heroku sbt plugin
172- mkdir -p "${sbt_global_dir}/plugins"
176+ # Install the Heroku sbt plugin if the exising version differs (or is missing). We do this conditionally to
177+ # ensure we can cache the compiled class files of the plugin between builds, speeding up the build overall.
173178case "${sbt_version}" in
1741791.*)
175- cp "${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin_sbt1.scala" "${sbt_global_dir}/plugins/HerokuBuildpackPlugin .scala"
180+ plugin_source_path= "${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin_sbt1.scala"
176181 ;;
177182*)
178- cp "${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin.scala" "${sbt_global_dir}/plugins /HerokuBuildpackPlugin.scala"
183+ plugin_source_path= "${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin.scala"
179184 ;;
180185esac
181186
187+ plugins_dir="${sbt_global_dir}/plugins"
188+ plugin_destination_path="${plugins_dir}/HerokuBuildpackPlugin.scala"
189+ source_checksum="$(sha256sum "${plugin_source_path}" | awk '{print $1}')"
190+
191+ if [[ ! -f "${plugin_destination_path}" ]] || [[ "${source_checksum}" != "$(sha256sum "${plugin_destination_path}" | awk '{print $1}')" ]]; then
192+ # We remove the whole directory since we also want to remove the (cached) compiled files
193+ # for the older version of the plugin in the ./target directory.
194+ rm -rf "${plugins_dir}"
195+
196+ mkdir -p "${plugins_dir}"
197+ cp "${plugin_source_path}" "${plugin_destination_path}"
198+ fi
199+
182200sbt::install_sbt_launcher "${sbt_version}" "${CACHE_DIR}/sbt-launcher"
183201
184202# Collect metrics
196214
197215# build app
198216cd "${BUILD_DIR}"
217+
199218run_sbt "${SBT_TASKS}"
200219
201220if [[ -z "${DISABLE_DEPENDENCY_CLASSPATH_LOG:-}" ]]; then
202221 write_sbt_dependency_classpath_log
203222fi
204223
205- # drop useless directories from slug for play and sbt-native-launcher only
206- if is_sbt_native_packager "${BUILD_DIR}" || is_play "${BUILD_DIR}"; then
207- if [[ "${KEEP_SBT_CACHE:-}" != "true" ]]; then
208- if [[ -d "${BUILD_DIR}/target" ]]; then
209- output::step "Dropping compilation artifacts from the slug"
210- rm -rf "${BUILD_DIR}/target/scala-"*
211- rm -rf "${BUILD_DIR}/target/streams"
212- if [[ -d "${BUILD_DIR}/target/resolution-cache" ]]; then
213- find "${BUILD_DIR}/target/resolution-cache"/* ! -name "reports" ! -name "*-compile.xml" -print0 | xargs -0 rm -rf --
214- fi
215- fi
216- fi
224+ # Copy the target dir back to cache for the next build (see cache restore comment above for context)
225+ target_dir_cache_write_start_time=$(util::nowms)
226+ util::cache_copy "target" "${BUILD_DIR}" "${CACHE_DIR}"
227+ metrics::set_duration "target_dir_cache_write_duration" "${target_dir_cache_write_start_time}"
228+
229+ # When sbt-native-packager is used (either directly or implicitly by the Play framework), a standalone JAR file with
230+ # all code and dependencies is created. This makes the other JAR and class files redundant since they are not used by
231+ # the running application. We remove these files here to reduce the slug size. However, this is a broad assumption that
232+ # is often but not always correct. Users might want to use these files to run the application differently or for other
233+ # purposes. This is legacy behavior that we will not change at this point to avoid users running into slug size limits
234+ # needlessly. Ideally customers would curate their builds to not include extraneous files.
235+ if (is_sbt_native_packager "${BUILD_DIR}" || is_play "${BUILD_DIR}") && [[ "${KEEP_SBT_CACHE:-}" != "true" ]]; then
236+ output::step "Dropping compilation artifacts from the slug"
237+ rm -rf "${BUILD_DIR}/target/scala-"*
238+ rm -rf "${BUILD_DIR}/target/streams"
239+ find "${BUILD_DIR}/target/resolution-cache" -mindepth 1 ! -name "reports" ! -name "*-compile.xml" -exec rm -rf {} + 2>/dev/null || true
217240fi
218241
219242# write profile.d script
220243profile_script="${BUILD_DIR}/.profile.d/scala.sh"
221244mkdir -p "$(dirname "${profile_script}")"
222245cat <<-EOF >"${profile_script}"
223- export SBT_HOME="\$HOME/${SBT_USER_HOME}"
224246 export PATH="\$SBT_HOME/bin:\$PATH"
225247EOF
226248
227249# write export script
228250cat <<-EOF >"${BASE_DIR}/export"
229- export SBT_HOME="${BUILD_DIR}/${SBT_USER_HOME}"
230251 export PATH="\$SBT_HOME/bin:\$PATH"
231252EOF
0 commit comments