Skip to content

Commit a751cda

Browse files
committed
Add CHANGELOG entry
1 parent d2976d4 commit a751cda

File tree

5 files changed

+63
-44
lines changed

5 files changed

+63
-44
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+
* Replace sbt-extras with direct sbt launcher installation. ([#291](https://github.com/heroku/heroku-buildpack-scala/pull/291))
56
* Remove build directory symlinking. Modern sbt versions no longer require a stable build path for caching. ([#290](https://github.com/heroku/heroku-buildpack-scala/pull/290))
67

78
## [v103] - 2025-10-27

bin/compile

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,15 @@ if has_old_preset_sbt_opts; then
131131
EOF
132132
fi
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+
134143
if [[ -n "${SBT_PROJECT}" ]]; then
135144
SBT_TASKS="${SBT_PROJECT}/compile ${SBT_PROJECT}/stage"
136145
else
@@ -147,7 +156,12 @@ fi
147156
# See: https://devcenter.heroku.com/articles/scala-support#clean-builds
148157
[[ "${SBT_CLEAN}" = "true" ]] && SBT_TASKS="clean ${SBT_TASKS}"
149158

150-
159+
# Disable log formatting (which would modify already printed lines...)
160+
util::prepend_to_env "SBT_OPTS" "-Dsbt.log.noformat=true"
161+
# Run sbt in batch mode, disabling interactive prompts
162+
util::prepend_to_env "SBT_OPTS" "-batch"
163+
# Disable colored output
164+
util::prepend_to_env "SBT_OPTS" "-Dsbt.color=false"
151165

152166
ivy_home_dir="${CACHE_DIR}/ivy_home"
153167
util::prepend_to_env "SBT_OPTS" "-Dsbt.ivy.home=${ivy_home_dir}"
@@ -161,24 +175,35 @@ sbt_boot_dir="${CACHE_DIR}/sbt_boot"
161175
util::prepend_to_env "SBT_OPTS" "-Dsbt.boot.directory=${sbt_boot_dir}"
162176
mkdir -p "${sbt_boot_dir}"
163177

164-
# Recreate the sbt global directory on each build with the current Heroku plugins and settings.
165178
# See: https://www.scala-sbt.org/1.x/docs/Command-Line-Reference.html
166179
sbt_global_dir="${CACHE_DIR}/sbt_global"
167180
util::prepend_to_env "SBT_OPTS" "-Dsbt.global.base=${sbt_global_dir}"
168-
rm -rf "${sbt_global_dir}"
169181
mkdir -p "${sbt_global_dir}"
170182

171-
# Install the Heroku sbt plugin
172-
mkdir -p "${sbt_global_dir}/plugins"
183+
# Install the Heroku sbt plugin if the exising version differs (or is missing). We do this conditionally to
184+
# ensure we can cache the compiled class files of the plugin between builds, speeding up the build overall.
173185
case "${sbt_version}" in
174186
1.*)
175-
cp "${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin_sbt1.scala" "${sbt_global_dir}/plugins/HerokuBuildpackPlugin.scala"
187+
plugin_source_path="${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin_sbt1.scala"
176188
;;
177189
*)
178-
cp "${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin.scala" "${sbt_global_dir}/plugins/HerokuBuildpackPlugin.scala"
190+
plugin_source_path="${BUILDPACK_DIR}/opt/HerokuBuildpackPlugin.scala"
179191
;;
180192
esac
181193

194+
plugins_dir="${sbt_global_dir}/plugins"
195+
plugin_destination_path="${plugins_dir}/HerokuBuildpackPlugin.scala"
196+
source_checksum="$(sha256sum "${plugin_source_path}" | awk '{print $1}')"
197+
198+
if [[ ! -f "${plugin_destination_path}" ]] || [[ "${source_checksum}" != "$(sha256sum "${plugin_destination_path}" | awk '{print $1}')" ]]; then
199+
# We remove the whole directory since we also want to remove the (cached) compiled files
200+
# for the older version of the plugin in the ./target directory.
201+
rm -rf "${plugins_dir}"
202+
203+
mkdir -p "${plugins_dir}"
204+
cp "${plugin_source_path}" "${plugin_destination_path}"
205+
fi
206+
182207
sbt::install_sbt_launcher "${sbt_version}" "${CACHE_DIR}/sbt-launcher"
183208

184209
# Collect metrics
@@ -196,36 +221,39 @@ fi
196221

197222
# build app
198223
cd "${BUILD_DIR}"
224+
199225
run_sbt "${SBT_TASKS}"
200226

201227
if [[ -z "${DISABLE_DEPENDENCY_CLASSPATH_LOG:-}" ]]; then
202228
write_sbt_dependency_classpath_log
203229
fi
204230

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
231+
# Copy the target dir back to cache for the next build (see cache restore comment above for context)
232+
target_dir_cache_write_start_time=$(util::nowms)
233+
util::cache_copy "target" "${BUILD_DIR}" "${CACHE_DIR}"
234+
metrics::set_duration "target_dir_cache_write_duration" "${target_dir_cache_write_start_time}"
235+
236+
# When sbt-native-packager is used (either directly or implicitly by the Play framework), a standalone JAR file with
237+
# all code and dependencies is created. This makes the other JAR and class files redundant since they are not used by
238+
# the running application. We remove these files here to reduce the slug size. However, this is a broad assumption that
239+
# is often but not always correct. Users might want to use these files to run the application differently or for other
240+
# purposes. This is legacy behavior that we will not change at this point to avoid users running into slug size limits
241+
# needlessly. Ideally customers would curate their builds to not include extraneous files.
242+
if (is_sbt_native_packager "${BUILD_DIR}" || is_play "${BUILD_DIR}") && [[ "${KEEP_SBT_CACHE:-}" != "true" ]]; then
243+
output::step "Dropping compilation artifacts from the slug"
244+
rm -rf "${BUILD_DIR}/target/scala-"*
245+
rm -rf "${BUILD_DIR}/target/streams"
246+
find "${BUILD_DIR}/target/resolution-cache" -mindepth 1 ! -name "reports" ! -name "*-compile.xml" -exec rm -rf {} + 2>/dev/null || true
217247
fi
218248

219249
# write profile.d script
220250
profile_script="${BUILD_DIR}/.profile.d/scala.sh"
221251
mkdir -p "$(dirname "${profile_script}")"
222252
cat <<-EOF >"${profile_script}"
223-
export SBT_HOME="\$HOME/${SBT_USER_HOME}"
224253
export PATH="\$SBT_HOME/bin:\$PATH"
225254
EOF
226255

227256
# write export script
228257
cat <<-EOF >"${BASE_DIR}/export"
229-
export SBT_HOME="${BUILD_DIR}/${SBT_USER_HOME}"
230258
export PATH="\$SBT_HOME/bin:\$PATH"
231259
EOF

lib/sbt.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function sbt::install_sbt_launcher() {
2121
cat <<-EOF >"${sbt_launcher_dir}/bin/sbt"
2222
#!/usr/bin/env bash
2323
script_dir="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
24-
java -jar "\${script_dir}/../sbt-launch-${sbt_version}.jar" "\$@"
24+
java \${SBT_OPTS:-} -jar "\${script_dir}/../sbt-launch-${sbt_version}.jar" "\$@"
2525
EOF
2626

2727
chmod +x "${sbt_launcher_dir}/bin/sbt"
@@ -99,7 +99,7 @@ function sbt::download_sbt_launcher_jar() {
9999
local sha1_path
100100
sha1_path=$(mktemp)
101101

102-
if ! curl --silent --show-error --location "${sbt_launcher_jar_url}.sha1" > "${sha1_path}" 2>/dev/null; then
102+
if ! curl --silent --show-error --location "${sbt_launcher_jar_url}.sha1" >"${sha1_path}" 2>/dev/null; then
103103
output::error <<-EOF
104104
Error: Unable to download SHA-1 checksum for sbt launcher.
105105
EOF

test/spec/cache_spec.rb

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,14 @@
1010
expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE))
1111
remote: -----> Scala app detected
1212
remote: -----> Installing Azul Zulu OpenJDK 21.0.[0-9]+
13+
remote: -----> Downloading sbt launcher 1.11.7...
14+
remote: -----> Setting up sbt launcher...
1315
remote: -----> Running: sbt compile stage
14-
remote: Downloading sbt launcher for 1.11.7:
15-
remote: From https://repo1.maven.org/maven2/org/scala-sbt/sbt-launch/1.11.7/sbt-launch-1.11.7.jar
16-
remote: To /tmp/[^/]+/.sbt_home/launchers/1.11.7/sbt-launch.jar
17-
remote: Downloading sbt launcher 1.11.7 md5 hash:
18-
remote: From https://repo1.maven.org/maven2/org/scala-sbt/sbt-launch/1.11.7/sbt-launch-1.11.7.jar.md5
19-
remote: To /tmp/[^/]+/.sbt_home/launchers/1.11.7/sbt-launch.jar.md5
20-
remote: /tmp/[^/]+/.sbt_home/launchers/1.11.7/sbt-launch.jar: OK
2116
remote: \\[info\\] \\[launcher\\] getting org.scala-sbt sbt 1.11.7 \\(this may take some time\\)...
2217
remote: \\[info\\] \\[launcher\\] getting Scala 2.12.20 \\(for sbt\\)...
2318
remote: \\[info\\] welcome to sbt 1.11.7 \\(Azul Systems, Inc. Java 21.0.[0-9]+\\)
24-
remote: \\[info\\] loading global plugins from /tmp/[^/]+/.sbt_home/plugins
25-
remote: \\[info\\] compiling 1 Scala source to /tmp/[^/]+/.sbt_home/plugins/target/scala-2.12/sbt-1.0/classes ...
19+
remote: \\[info\\] loading global plugins from .*/plugins
20+
remote: \\[info\\] compiling 1 Scala source to .* ...
2621
remote: \\[info\\] Non-compiled module 'compiler-bridge_2.12' for Scala 2.12.20. Compiling...
2722
remote: \\[info\\] Compilation completed in .*s.
2823
remote: \\[info\\] done compiling
@@ -48,9 +43,6 @@
4843
remote: \\[info\\] Wrote /tmp/[^/]+/target/scala-2.13/sbt-0-11-7-play-3-x-scala-2-13-x_2.13-1.0-SNAPSHOT.pom
4944
remote: \\[success\\] Total time: .*
5045
remote: -----> Collecting dependency information
51-
remote: -----> Dropping ivy cache from the slug
52-
remote: -----> Dropping sbt boot dir from the slug
53-
remote: -----> Dropping sbt cache dir from the slug
5446
remote: -----> Dropping compilation artifacts from the slug
5547
remote: -----> Discovering process types
5648
remote: Procfile declares types -> \\(none\\)
@@ -66,9 +58,10 @@
6658
expect(clean_output(app.output)).to match(Regexp.new(<<~REGEX, Regexp::MULTILINE))
6759
remote: -----> Scala app detected
6860
remote: -----> Installing Azul Zulu OpenJDK 21.0.[0-9]+
61+
remote: -----> Setting up sbt launcher...
6962
remote: -----> Running: sbt compile stage
7063
remote: \\[info\\] welcome to sbt 1.11.7 \\(Azul Systems, Inc. Java 21.0.[0-9]+\\)
71-
remote: \\[info\\] loading global plugins from /tmp/[^/]+/.sbt_home/plugins
64+
remote: \\[info\\] loading global plugins from .*/plugins
7265
remote: \\[info\\] loading settings for project [^\\s]+-build from plugins.sbt...
7366
remote: \\[info\\] loading project definition from /tmp/[^/]+/project
7467
remote: \\[info\\] loading settings for project root from build.sbt...
@@ -89,9 +82,6 @@
8982
remote: \\[info\\] Wrote /tmp/[^/]+/target/scala-2.13/sbt-0-11-7-play-3-x-scala-2-13-x_2.13-1.0-SNAPSHOT.pom
9083
remote: \\[success\\] Total time: .*
9184
remote: -----> Collecting dependency information
92-
remote: -----> Dropping ivy cache from the slug
93-
remote: -----> Dropping sbt boot dir from the slug
94-
remote: -----> Dropping sbt cache dir from the slug
9585
remote: -----> Dropping compilation artifacts from the slug
9686
remote: -----> Discovering process types
9787
remote: Procfile declares types -> \\(none\\)

test/spec/ci_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
\\[info\\] \\[launcher\\] getting org.scala-sbt sbt 1.11.7 \\(this may take some time\\)...
2121
\\[info\\] \\[launcher\\] getting Scala 2.12.20 \\(for sbt\\)...
2222
\\[info\\] welcome to sbt 1.11.7 \\(Azul Systems, Inc. Java 21.0.[0-9]+\\)
23-
\\[info\\] loading global plugins from /app/.sbt_home/plugins
24-
\\[info\\] compiling 1 Scala source to /app/.sbt_home/plugins/target/scala-2.12/sbt-1.0/classes ...
23+
\\[info\\] loading global plugins from .*/plugins
24+
\\[info\\] compiling 1 Scala source to .* ...
2525
\\[info\\] Non-compiled module 'compiler-bridge_2.12' for Scala 2.12.20. Compiling...
2626
\\[info\\] Compilation completed in .*s.
2727
\\[info\\] done compiling
@@ -47,7 +47,7 @@
4747
-----> Running Scala buildpack tests...
4848
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 -XX:MaxRAM=2684354560 -XX:MaxRAMPercentage=80.0
4949
\\[info\\] welcome to sbt 1.11.7 \\(Azul Systems, Inc. Java 21.0.[0-9]+\\)
50-
\\[info\\] loading global plugins from /app/.sbt_home/plugins
50+
\\[info\\] loading global plugins from .*/plugins
5151
\\[info\\] loading project definition from /app/project/project
5252
\\[info\\] loading settings for project app-build from ._plugins.sbt, plugins.sbt...
5353
\\[info\\] loading project definition from /app/project
@@ -78,7 +78,7 @@
7878
-----> Installing Azul Zulu OpenJDK 21.0.[0-9]+
7979
-----> Running: sbt update
8080
\\[info\\] welcome to sbt 1.11.7 \\(Azul Systems, Inc. Java 21.0.[0-9]+\\)
81-
\\[info\\] loading global plugins from /app/.sbt_home/plugins
81+
\\[info\\] loading global plugins from .*/plugins
8282
\\[info\\] loading project definition from /app/project/project
8383
\\[info\\] loading settings for project app-build from ._plugins.sbt, plugins.sbt...
8484
\\[info\\] loading project definition from /app/project
@@ -101,7 +101,7 @@
101101
-----> Running Scala buildpack tests...
102102
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8 -XX:MaxRAM=2684354560 -XX:MaxRAMPercentage=80.0
103103
\\[info\\] welcome to sbt 1.11.7 \\(Azul Systems, Inc. Java 21.0.[0-9]+\\)
104-
\\[info\\] loading global plugins from /app/.sbt_home/plugins
104+
\\[info\\] loading global plugins from .*/plugins
105105
\\[info\\] loading project definition from /app/project/project
106106
\\[info\\] loading settings for project app-build from ._plugins.sbt, plugins.sbt...
107107
\\[info\\] loading project definition from /app/project

0 commit comments

Comments
 (0)