From c3e4bc53c4a04e6bdefd68ac91a5660234186826 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 14 Dec 2024 01:42:48 -0500 Subject: [PATCH 01/24] Giter8 0.17.0 **Problem/Solution** Bump to Giter8 0.17.0. --- main/src/main/scala/sbt/plugins/Giter8TemplatePlugin.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/plugins/Giter8TemplatePlugin.scala b/main/src/main/scala/sbt/plugins/Giter8TemplatePlugin.scala index 51e60575b..4aedac6aa 100644 --- a/main/src/main/scala/sbt/plugins/Giter8TemplatePlugin.scala +++ b/main/src/main/scala/sbt/plugins/Giter8TemplatePlugin.scala @@ -27,7 +27,7 @@ object Giter8TemplatePlugin extends AutoPlugin { ModuleID( "org.scala-sbt.sbt-giter8-resolver", "sbt-giter8-resolver", - "0.16.2" + "0.17.0" ) cross CrossVersion.binary, "sbtgiter8resolver.Giter8TemplateResolver" ) From 3bdfc34c6b51b63c298fe633c5127609b65005ee Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:37:44 -0800 Subject: [PATCH 02/24] Bump JLine 2 to 9a88bc --- project/Dependencies.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index d34375260..8587c489e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -84,16 +84,13 @@ object Dependencies { val sjsonNewScalaJson = sjsonNew("sjson-new-scalajson") val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash") - // JLine 3 version must be coordinated together with JAnsi version - // and the JLine 2 fork version, which uses the same JAnsi - val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-9c3b6aca11c57e339441442bbf58e550cdfecb79" + val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-9a88bc413e2b34a4580c001c654d1a7f4f65bf18" val jline3Version = "3.27.1" val jline3Terminal = "org.jline" % "jline-terminal" % jline3Version val jline3JNI = "org.jline" % "jline-terminal-jni" % jline3Version val jline3Native = "org.jline" % "jline-native" % jline3Version val jline3Reader = "org.jline" % "jline-reader" % jline3Version val jline3Builtins = "org.jline" % "jline-builtins" % jline3Version - val jansi = "org.fusesource.jansi" % "jansi" % "2.4.1" val scalatest = "org.scalatest" %% "scalatest" % "3.2.10" val scalacheck = "org.scalacheck" %% "scalacheck" % "1.15.4" val junit = "junit" % "junit" % "4.13.1" From e606b1f7945d64b26e9461e0d551e9f3e15cd700 Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:42:34 -0800 Subject: [PATCH 03/24] Use new Scala CLA checker --- .github/workflows/cla.yml | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 91f51361e..c7ec83e66 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -4,21 +4,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Check CLA - env: - AUTHOR: ${{ github.event.pull_request.user.login }} - run: | - echo "Pull request submitted by $AUTHOR"; - signed=$(curl -s "https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR" | jq -r ".signed"); - if [ "$signed" = "true" ] ; then - echo "CLA check for $AUTHOR successful"; - else - echo "CLA check for $AUTHOR failed"; - echo "Please sign the Scala CLA to contribute to the Scala compiler."; - echo "Go to https://contribute.akka.io/contribute/cla/scala and then"; - echo "comment on the pull request to ask for a new check."; - echo ""; - echo "Check if CLA is signed: https://contribute.akka.io/contribute/cla/scala/check/$AUTHOR"; - exit 1; - fi; + - name: Check CLA + uses: scala/cla-checker@v1 + with: + author: ${{ github.event.pull_request.user.login }} From 30c7be8d0d54b8867fac92507b211d44355bedc6 Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:26:32 -0800 Subject: [PATCH 04/24] Prepare for sbt 1.10.7 --- .github/workflows/ci.yml | 2 +- build.sbt | 2 +- project/build.properties | 2 +- sbt-app/src/sbt-test/project/scripted13/test | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f6f5de34..1fbfb72a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: JVM_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 SCALA_212: 2.12.20 UTIL_TESTS: "utilCache/test utilControl/test utilInterface/test utilLogging/test utilPosition/test utilRelation/test utilScripted/test utilTracking/test" - TEST_SBT_VER: 1.10.5 + TEST_SBT_VER: 1.10.6 SBT_ETC_FILE: $HOME/etc/sbt/sbtopts JDK11: adopt@1.11.0-9 SPARK_LOCAL_IP: "127.0.0.1" diff --git a/build.sbt b/build.sbt index 81949a48c..c81b5e5d7 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ import scala.util.Try // ThisBuild settings take lower precedence, // but can be shared across the multi projects. ThisBuild / version := { - val v = "1.10.6-SNAPSHOT" + val v = "1.10.7-SNAPSHOT" nightlyVersion.getOrElse(v) } ThisBuild / version2_13 := "2.0.0-SNAPSHOT" diff --git a/project/build.properties b/project/build.properties index db1723b08..e88a0d817 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.5 +sbt.version=1.10.6 diff --git a/sbt-app/src/sbt-test/project/scripted13/test b/sbt-app/src/sbt-test/project/scripted13/test index b321a8554..d40ea3d07 100644 --- a/sbt-app/src/sbt-test/project/scripted13/test +++ b/sbt-app/src/sbt-test/project/scripted13/test @@ -1,6 +1,6 @@ # This tests that this sbt scripted plugin can launch the previous one -> ^^1.10.5 +> ^^1.10.6 $ copy-file changes/A.scala src/sbt-test/a/b/A.scala > scripted From 73123986951324fb4ee99ddb00ca0811a04dfc2d Mon Sep 17 00:00:00 2001 From: friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:30:20 -0800 Subject: [PATCH 05/24] Restore Multirepo integration test --- .github/workflows/ci.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fbfb72a8..66c7de106 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,18 +142,18 @@ jobs: shell: bash run: | ./sbt -v "++2.13.x; all utilControl/test utilRelation/test utilPosition/test" -# - name: Multirepo integration test -# if: ${{ matrix.jobtype == 6 }} -# shell: bash -# run: | -# # build from fresh IO, LM, and Zinc -# BUILD_VERSION="${TEST_SBT_VER}-SNAPSHOT" -# cd io -# sbt -v -Dsbt.build.version=${BUILD_VERSION} +publishLocal -# cd ../ -# sbt -Dsbtlm.path=$HOME/work/sbt/sbt/librarymanagement -Dsbtzinc.path=$HOME/work/sbt/sbt/zinc -Dsbt.build.version=$BUILD_VERSION -Dsbt.build.fatal=false "+lowerUtils/publishLocal; {librarymanagement}/publishLocal; {zinc}/publishLocal; upperModules/publishLocal" -# rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true -# sbt -v -Dsbt.version=$BUILD_VERSION "++2.13.x; all $UTIL_TESTS; ++$SCALA_212; all $UTIL_TESTS; scripted actions/* source-dependencies/*1of3 dependency-management/*1of4 java/*" + - name: Multirepo integration test + if: ${{ matrix.jobtype == 6 }} + shell: bash + run: | + # build from fresh IO, LM, and Zinc + BUILD_VERSION="${TEST_SBT_VER}-SNAPSHOT" + cd io + sbt -v -Dsbt.build.version=${BUILD_VERSION} +publishLocal + cd ../ + sbt -Dsbtlm.path=$HOME/work/sbt/sbt/librarymanagement -Dsbtzinc.path=$HOME/work/sbt/sbt/zinc -Dsbt.build.version=$BUILD_VERSION -Dsbt.build.fatal=false "+lowerUtils/publishLocal; {librarymanagement}/publishLocal; {zinc}/publishLocal; upperModules/publishLocal" + rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true + sbt -v -Dsbt.version=$BUILD_VERSION "++2.13.x; all $UTIL_TESTS; ++$SCALA_212; all $UTIL_TESTS; scripted actions/* source-dependencies/*1of3 dependency-management/*1of4 java/*" - name: Build and test (7) if: ${{ matrix.jobtype == 7 }} shell: bash From 72c061a2a25f3bd39e942591437e64d226bc82f9 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 22 Dec 2024 01:32:16 -0500 Subject: [PATCH 06/24] fix: Fixes glob in scripted **Problem** Absolute path handling regressed. **Solution** Create an absolute glob for expression starting with /. --- .../sbt/internal/scripted/FileCommands.scala | 16 ++++++++++------ sbt-app/src/sbt-test/actions/add-alias/test | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/util-scripted/src/main/scala/sbt/internal/scripted/FileCommands.scala b/internal/util-scripted/src/main/scala/sbt/internal/scripted/FileCommands.scala index a2aa32db7..a8bd97544 100644 --- a/internal/util-scripted/src/main/scala/sbt/internal/scripted/FileCommands.scala +++ b/internal/util-scripted/src/main/scala/sbt/internal/scripted/FileCommands.scala @@ -56,6 +56,11 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler { def fromStrings(paths: List[String]) = paths.map(fromString) def fromString(path: String) = new File(baseDirectory, path) def filterFromStrings(exprs: List[String]): List[PathFilter] = { + def globs(exprs: List[String]): List[PathFilter] = + exprs.map { g => + if (g.startsWith("/")) (Glob(g): PathFilter) + else (Glob(baseDirectory, g): PathFilter) + } def orGlobs = { val exprs1 = exprs .mkString("") @@ -63,19 +68,18 @@ class FileCommands(baseDirectory: File) extends BasicStatementHandler { .filter(_ != OR) .toList .map(_.trim) - val combined = exprs1.map(Glob(baseDirectory, _)) match { + val combined = globs(exprs1) match { case Nil => sys.error("unexpected Nil") - case g :: Nil => (g: PathFilter) + case g :: Nil => g case g :: gs => - gs.foldLeft(g: PathFilter) { - case (acc, g) => - acc || (g: PathFilter) + gs.foldLeft(g) { + case (acc, g) => acc || g } } List(combined) } if (exprs.contains("||")) orGlobs - else exprs.map(Glob(baseDirectory, _): PathFilter) + else globs(exprs) } def touch(paths: List[String]): Unit = IO.touch(fromStrings(paths)) diff --git a/sbt-app/src/sbt-test/actions/add-alias/test b/sbt-app/src/sbt-test/actions/add-alias/test index 0269ab4f8..9a7db3782 100644 --- a/sbt-app/src/sbt-test/actions/add-alias/test +++ b/sbt-app/src/sbt-test/actions/add-alias/test @@ -2,3 +2,5 @@ -> demo-failure > +z > z + +$ absent /tmp/non-existent From cd58481811689c676d448c87f2ec17e1f2497929 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 20 Dec 2024 03:02:26 -0500 Subject: [PATCH 07/24] --allow-empty fixes **Problem** 1. Currently users are automatically opted into -create-sbt, somewhat implicitly. 2. When somehow they are not, the check mechanism currently blocks for input. **Solution** 1. Support a new location for sbtopts file under XSG_CONFIG_HOME/sbt 2. Rename -create-sbt to --allow-empty, and don't opt everyone in 3. Exit 1 instead of blocking for input --- .../src/test/scala/RunnerTest.scala | 26 ++++++++ launcher-package/src/universal/bin/sbt.bat | 47 ++++++-------- launcher-package/src/universal/conf/sbtopts | 6 +- sbt | 65 ++++++++++++------- 4 files changed, 88 insertions(+), 56 deletions(-) diff --git a/launcher-package/integration-test/src/test/scala/RunnerTest.scala b/launcher-package/integration-test/src/test/scala/RunnerTest.scala index f3d406502..382b259d7 100755 --- a/launcher-package/integration-test/src/test/scala/RunnerTest.scala +++ b/launcher-package/integration-test/src/test/scala/RunnerTest.scala @@ -4,6 +4,7 @@ import minitest._ import scala.sys.process._ import java.io.File import java.util.Locale +import sbt.io.IO object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { // 1.3.0, 1.3.0-M4 @@ -20,6 +21,10 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { sbt.internal.Process(Seq(sbtScript.getAbsolutePath) ++ args, new File("citest"), "JAVA_OPTS" -> javaOpts, "SBT_OPTS" -> sbtOpts) + def sbtProcessInDir(dir: File)(args: String*) = + sbt.internal.Process(Seq(sbtScript.getAbsolutePath) ++ args, dir, + "JAVA_OPTS" -> "", + "SBT_OPTS" -> "") test("sbt runs") { assert(sbtScript.exists) @@ -68,6 +73,27 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { } } + test("sbt in empty directory") { + IO.withTemporaryDirectory { tmp => + val out = sbtProcessInDir(tmp)("about").! + assert(out == 1) + } + IO.withTemporaryDirectory { tmp => + val out = sbtProcessInDir(tmp)("about", "--allow-empty").! + assert(out == 0) + } + () + } + + test("sbt --script-version in empty directory") { + IO.withTemporaryDirectory { tmp => + val out = sbtProcessInDir(tmp)("--script-version").!!.trim + val expectedVersion = "^"+versionRegEx+"$" + assert(out.matches(expectedVersion)) + } + () + } + /* test("sbt --client") { val out = sbtProcess("--client", "--no-colors", "compile").!!.linesIterator.toList diff --git a/launcher-package/src/universal/bin/sbt.bat b/launcher-package/src/universal/bin/sbt.bat index f9d6ce6fd..2bd4dd49e 100755 --- a/launcher-package/src/universal/bin/sbt.bat +++ b/launcher-package/src/universal/bin/sbt.bat @@ -45,7 +45,7 @@ set sbt_args_timings= set sbt_args_traces= set sbt_args_sbt_boot= set sbt_args_sbt_cache= -set sbt_args_sbt_create= +set sbt_args_allow_empty= set sbt_args_sbt_dir= set sbt_args_sbt_version= set sbt_args_mem= @@ -72,11 +72,13 @@ rem TODO: remove/deprecate sbtconfig.txt and parse the sbtopts files rem FIRST we load the config file of extra options. set SBT_CONFIG=!SBT_HOME!\conf\sbtconfig.txt set SBT_CFG_OPTS= -for /F "tokens=* eol=# usebackq delims=" %%i in ("!SBT_CONFIG!") do ( - set DO_NOT_REUSE_ME=%%i - rem ZOMG (Part #2) WE use !! here to delay the expansion of - rem SBT_CFG_OPTS, otherwise it remains "" for this loop. - set SBT_CFG_OPTS=!SBT_CFG_OPTS! !DO_NOT_REUSE_ME! +if exist "!SBT_CONFIG!" ( + for /F "tokens=* eol=# usebackq delims=" %%i in ("!SBT_CONFIG!") do ( + set DO_NOT_REUSE_ME=%%i + rem ZOMG (Part #2) WE use !! here to delay the expansion of + rem SBT_CFG_OPTS, otherwise it remains "" for this loop. + set SBT_CFG_OPTS=!SBT_CFG_OPTS! !DO_NOT_REUSE_ME! + ) ) rem poor man's jenv (which is not available on Windows) @@ -235,12 +237,14 @@ if defined _traces_arg ( goto args_loop ) -if "%~0" == "-sbt-create" set _sbt_create_arg=true -if "%~0" == "--sbt-create" set _sbt_create_arg=true +if "%~0" == "-sbt-create" set _allow_empty_arg=true +if "%~0" == "--sbt-create" set _allow_empty_arg=true +if "%~0" == "-allow-empty" set _allow_empty_arg=true +if "%~0" == "--allow-empty" set _allow_empty_arg=true -if defined _sbt_create_arg ( - set _sbt_create_arg= - set sbt_args_sbt_create=1 +if defined _allow_empty_arg ( + set _allow_empty_arg= + set sbt_args_allow_empty=1 goto args_loop ) @@ -526,25 +530,12 @@ goto args_loop rem Confirm a user's intent if the current directory does not look like an sbt rem top-level directory and the "new" command was not given. -if not defined sbt_args_sbt_create if not defined sbt_args_print_version if not defined sbt_args_print_sbt_version if not defined sbt_args_print_sbt_script_version if not defined shutdownall if not exist build.sbt ( +if not defined sbt_args_allow_empty if not defined sbt_args_print_version if not defined sbt_args_print_sbt_version if not defined sbt_args_print_sbt_script_version if not defined shutdownall if not exist build.sbt ( if not exist project\ ( if not defined sbt_new ( - echo [warn] Neither build.sbt nor a 'project' directory in the current directory: "%CD%" - setlocal -:confirm - echo c^) continue - echo q^) quit - - set /P reply=^? - if /I "!reply!" == "c" ( - goto confirm_end - ) else if /I "!reply!" == "q" ( - exit /B 1 - ) - - goto confirm -:confirm_end - endlocal + echo [error] Neither build.sbt nor a 'project' directory in the current directory: "%CD%" + echo [error] run 'sbt new', touch build.sbt, or run 'sbt --allow-empty'. + goto error ) ) ) diff --git a/launcher-package/src/universal/conf/sbtopts b/launcher-package/src/universal/conf/sbtopts index c6f4e7bec..5990223b7 100644 --- a/launcher-package/src/universal/conf/sbtopts +++ b/launcher-package/src/universal/conf/sbtopts @@ -9,7 +9,7 @@ # Starts sbt even if the current directory contains no sbt project. # --sbt-create +#--allow-empty # Path to global settings/plugins directory (default: ~/.sbt) # @@ -31,11 +31,11 @@ # #-no-share -# Put SBT in offline mode. +# Put sbt in offline mode. # #-offline -# Sets the SBT version to use. +# Sets the sbt version to use. #-sbt-version 0.11.3 # Scala version (default: latest release) diff --git a/sbt b/sbt index e25690211..9a1a4a0b4 100755 --- a/sbt +++ b/sbt @@ -25,6 +25,7 @@ declare use_sbtn= declare no_server= declare sbtn_command="$SBTN_CMD" declare sbtn_version="1.10.5" +declare use_colors=1 ### ------------------------------- ### ### Helper methods for BASH scripts ### @@ -104,6 +105,15 @@ declare -r sbt_home="$(dirname "$sbt_bin_dir")" echoerr () { echo 1>&2 "$@" } +RED='\033[0;31m' +NC='\033[0m' # No Color +echoerr_error () { + if [[ $use_colors == "1" ]]; then + echoerr -e "[${RED}error${NC}] $@" + else + echoerr "[error] $@" + fi +} vlog () { [[ $sbt_verbose || $sbt_debug ]] && echoerr "$@" } @@ -483,6 +493,21 @@ copyRt() { fi } +# Confirm a user's intent if the current directory does not look like an sbt +# top-level directory and neither the --allow-empty option nor the "new" command was given. +checkWorkingDirectory() { + if [[ ! -n "$allow_empty" ]]; then + [[ -f ./build.sbt || -d ./project || -n "$sbt_new" ]] || { + echoerr_error "Neither build.sbt nor a 'project' directory in the current directory: $(pwd)" + echoerr_error "run 'sbt new', touch build.sbt, or run 'sbt --allow-empty'." + echoerr_error "" + echoerr_error "To opt out of this check, create ${config_home}/sbtopts with:" + echoerr_error "--allow-empty" + exit 1 + } + fi +} + run() { # Copy preloaded repo to user's preloaded directory syncPreloaded @@ -519,6 +544,7 @@ run() { done echo "shutdown ${#sbt_processes[@]} sbt processes" else + checkWorkingDirectory # run sbt execRunner "$java_cmd" \ "${java_args[@]}" \ @@ -543,7 +569,10 @@ declare -r sbt_opts_file=".sbtopts" declare -r build_props_file="$(pwd)/project/build.properties" declare -r etc_sbt_opts_file="/etc/sbt/sbtopts" # this allows /etc/sbt/sbtopts location to be changed -declare -r etc_file="${SBT_ETC_FILE:-$etc_sbt_opts_file}" +declare machine_sbt_opts_file="${etc_sbt_opts_file}" +declare config_home="${XDG_CONFIG_HOME:-$HOME/.config}/sbt" +[[ -f "${config_home}/sbtopts" ]] && machine_sbt_opts_file="${config_home}/sbtopts" +[[ -f "$SBT_ETC_FILE" ]] && machine_sbt_opts_file="$SBT_ETC_FILE" declare -r dist_sbt_opts_file="${sbt_home}/conf/sbtopts" declare -r win_sbt_opts_file="${sbt_home}/conf/sbtconfig.txt" declare sbt_jar="$(jar_file)" @@ -568,7 +597,7 @@ Usage: `basename "$0"` [options] enable or disable supershell (sbt 1.3 and above) --traces generate Trace Event report on shutdown (sbt 1.3 and above) --timings display task timings report on shutdown - --sbt-create start sbt even if current directory contains no sbt project + --allow-empty start sbt even if current directory contains no sbt project --sbt-dir path to global settings/plugins directory (default: ~/.sbt) --sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11 series) --sbt-cache path to global cache directory (default: operating system specific) @@ -607,7 +636,7 @@ process_my_args () { case "$1" in -batch|--batch) exec - -sbt-create|--sbt-create) sbt_create=true && shift ;; + -allow-empty|--allow-empty|-sbt-create|--sbt-create) allow_empty=true && shift ;; new) sbt_new=true && addResidual "$1" && shift ;; @@ -617,23 +646,6 @@ process_my_args () { # Now, ensure sbt version is used. [[ "${sbt_version}XXX" != "XXX" ]] && addJava "-Dsbt.version=$sbt_version" - - # Confirm a user's intent if the current directory does not look like an sbt - # top-level directory and neither the -sbt-create option nor the "new" - # command was given. - [[ -f ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_new" ]] || { - echo "[warn] Neither build.sbt nor a 'project' directory in the current directory: $(pwd)" - while true; do - echo 'c) continue' - echo 'q) quit' - - read -p '? ' || exit 1 - case "$REPLY" in - c|C) break ;; - q|Q) exit 1 ;; - esac - done - } } ## map over argument array. this is used to process both command line arguments and SBT_OPTS @@ -694,6 +706,7 @@ process_args () { export PATH="$2/bin:$PATH" && shift 2 ;; + -Dsbt.color=never|-Dsbt.log.noformat=true) addJava "$1" && use_colors=0 && shift ;; "-D*"|-D*) addJava "$1" && shift ;; -J*) addJava "${1:2}" && shift ;; *) addResidual "$1" && shift ;; @@ -785,11 +798,13 @@ runNativeClient() { original_args=("$@") -# Here we pull in the default settings configuration. -[[ -f "$dist_sbt_opts_file" ]] && set -- $(loadConfigFile "$dist_sbt_opts_file") "$@" - -# Here we pull in the global settings configuration. -[[ -f "$etc_file" ]] && set -- $(loadConfigFile "$etc_file") "$@" +# Pull in the machine-wide settings configuration. +if [[ -f "$machine_sbt_opts_file" ]]; then + set -- $(loadConfigFile "$machine_sbt_opts_file") "$@" +else + # Otherwise pull in the default settings configuration. + [[ -f "$dist_sbt_opts_file" ]] && set -- $(loadConfigFile "$dist_sbt_opts_file") "$@" +fi # Pull in the project-level config file, if it exists. [[ -f "$sbt_opts_file" ]] && set -- $(loadConfigFile "$sbt_opts_file") "$@" From de04f1f84719b802aec30992a2973d8cc481acf7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 22 Dec 2024 19:14:12 -0500 Subject: [PATCH 08/24] Update lm-coursier to 2.1.7 **Problem** BOM support current has perf issues. **Solution** This bumps Coursier to 2.1.22. This also adds a new setting `csrMavenDependencyOverride`, which will default to false. --- main/src/main/scala/sbt/Defaults.scala | 1 + main/src/main/scala/sbt/Keys.scala | 1 + .../scala/sbt/coursierint/LMCoursier.scala | 61 +++++++++++++++++++ project/Dependencies.scala | 6 +- .../ignoreScalaLibrary/build.sbt | 1 + .../ignoreScalaLibrary/{pending => test} | 0 6 files changed, 67 insertions(+), 3 deletions(-) rename sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/{pending => test} (100%) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index b8c329dd8..942fccd55 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -265,6 +265,7 @@ object Defaults extends BuildCommon { csrLogger := LMCoursier.coursierLoggerTask.value, csrMavenProfiles :== Set.empty, csrReconciliations :== LMCoursier.relaxedForAllModules, + csrMavenDependencyOverride :== false, csrSameVersions := Seq( ScalaArtifacts.Artifacts.map(a => InclExclRule(scalaOrganization.value, a)).toSet ) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index fb428c33b..9349773d7 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -454,6 +454,7 @@ object Keys { val csrPublications = taskKey[Seq[(lmcoursier.definitions.Configuration, lmcoursier.definitions.Publication)]]("") val csrReconciliations = settingKey[Seq[(ModuleMatchers, Reconciliation)]]("Strategy to reconcile version conflicts.") val csrSameVersions = settingKey[Seq[Set[InclExclRule]]]("Modules to keep at the same version.") + val csrMavenDependencyOverride = settingKey[Boolean]("Enables Maven dependency override (bill of materials) support") val internalConfigurationMap = settingKey[Configuration => Configuration]("Maps configurations to the actual configuration used to define the classpath.").withRank(CSetting) val classpathConfiguration = taskKey[Configuration]("The configuration used to define the classpath.").withRank(CTask) diff --git a/main/src/main/scala/sbt/coursierint/LMCoursier.scala b/main/src/main/scala/sbt/coursierint/LMCoursier.scala index 84ed5339d..fd7ebb546 100644 --- a/main/src/main/scala/sbt/coursierint/LMCoursier.scala +++ b/main/src/main/scala/sbt/coursierint/LMCoursier.scala @@ -121,6 +121,7 @@ object LMCoursier { depsOverrides, None, Nil, + None, log ) @@ -172,6 +173,60 @@ object LMCoursier { depsOverrides, updateConfig, Nil, + None, + log + ) + + // For binary compatibility / MiMa + def coursierConfiguration( + rs: Seq[Resolver], + interProjectDependencies: Seq[CProject], + extraProjects: Seq[CProject], + fallbackDeps: Seq[FallbackDependency], + appConfig: AppConfiguration, + classifiers: Option[Seq[Classifier]], + profiles: Set[String], + scalaOrg: String, + scalaVer: String, + scalaBinaryVer: String, + autoScalaLib: Boolean, + scalaModInfo: Option[ScalaModuleInfo], + excludeDeps: Seq[InclExclRule], + credentials: Seq[Credentials], + createLogger: Option[CacheLogger], + cacheDirectory: File, + reconciliation: Seq[(ModuleMatchers, Reconciliation)], + ivyHome: Option[File], + strict: Option[CStrict], + depsOverrides: Seq[ModuleID], + updateConfig: Option[UpdateConfiguration], + sameVersions: Seq[Set[InclExclRule]], + log: Logger + ): CoursierConfiguration = + coursierConfiguration( + rs, + interProjectDependencies, + extraProjects, + fallbackDeps, + appConfig, + classifiers, + profiles, + scalaOrg, + scalaVer, + scalaBinaryVer, + autoScalaLib, + scalaModInfo, + excludeDeps, + credentials, + createLogger, + cacheDirectory, + reconciliation, + ivyHome, + strict, + depsOverrides, + updateConfig, + sameVersions, + None, log ) @@ -198,6 +253,7 @@ object LMCoursier { depsOverrides: Seq[ModuleID], updateConfig: Option[UpdateConfiguration], sameVersions: Seq[Set[InclExclRule]], + enableDependencyOverrides: Option[Boolean], log: Logger ): CoursierConfiguration = { val coursierExcludeDeps = Inputs @@ -252,6 +308,7 @@ object LMCoursier { .withForceVersions(userForceVersions.toVector) .withMissingOk(missingOk) .withSameVersions(sameVersions) + .withEnableDependencyOverrides(enableDependencyOverrides) } def coursierConfigurationTask: Def.Initialize[Task[CoursierConfiguration]] = Def.task { @@ -279,6 +336,7 @@ object LMCoursier { dependencyOverrides.value, Some(updateConfiguration.value), csrSameVersions.value, + Some(csrMavenDependencyOverride.value), streams.value.log ) } @@ -308,6 +366,7 @@ object LMCoursier { dependencyOverrides.value, Some(updateConfiguration.value), csrSameVersions.value, + Some(csrMavenDependencyOverride.value), streams.value.log ) } @@ -337,6 +396,7 @@ object LMCoursier { dependencyOverrides.value, Some(updateConfiguration.value), csrSameVersions.value, + Some(csrMavenDependencyOverride.value), streams.value.log ) } @@ -366,6 +426,7 @@ object LMCoursier { dependencyOverrides.value, Some(updateConfiguration.value), csrSameVersions.value, + Some(csrMavenDependencyOverride.value), streams.value.log ) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 8587c489e..9e4b893be 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -12,10 +12,10 @@ object Dependencies { sys.env.get("BUILD_VERSION") orElse sys.props.get("sbt.build.version") // sbt modules - private val ioVersion = nightlyVersion.getOrElse("1.10.2") + private val ioVersion = nightlyVersion.getOrElse("1.10.3") private val lmVersion = sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.10.3") - val zincVersion = nightlyVersion.getOrElse("1.10.5") + val zincVersion = nightlyVersion.getOrElse("1.10.7") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion @@ -77,7 +77,7 @@ object Dependencies { def addSbtZincCompile = addSbtModule(sbtZincPath, "zincCompile", zincCompile) def addSbtZincCompileCore = addSbtModule(sbtZincPath, "zincCompileCore", zincCompileCore) - val lmCoursierShaded = "io.get-coursier" %% "lm-coursier-shaded" % "2.1.6" + val lmCoursierShaded = "io.get-coursier" %% "lm-coursier-shaded" % "2.1.7" def sjsonNew(n: String) = Def.setting("com.eed3si9n" %% n % "0.10.1") // contrabandSjsonNewVersion.value diff --git a/sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/build.sbt b/sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/build.sbt index fae8cacab..c05b42e71 100644 --- a/sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/build.sbt +++ b/sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/build.sbt @@ -4,6 +4,7 @@ libraryDependencies ++= Seq( "org.slf4j" % "slf4j-api" % "1.7.2", "ch.qos.logback" % "logback-classic" % "1.0.7" ) +csrMavenDependencyOverride := false TaskKey[Unit]("check") := { val report = updateFull.value diff --git a/sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/pending b/sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/test similarity index 100% rename from sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/pending rename to sbt-app/src/sbt-test/dependency-graph/ignoreScalaLibrary/test From 3529e202799abc6a381a1f02cd527d7b8ff88034 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 22 Dec 2024 21:26:54 -0500 Subject: [PATCH 09/24] sbt 1.10.7 --- .github/workflows/ci.yml | 2 +- build.sbt | 2 +- sbt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66c7de106..846327e2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: JVM_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 SCALA_212: 2.12.20 UTIL_TESTS: "utilCache/test utilControl/test utilInterface/test utilLogging/test utilPosition/test utilRelation/test utilScripted/test utilTracking/test" - TEST_SBT_VER: 1.10.6 + TEST_SBT_VER: 1.10.7 SBT_ETC_FILE: $HOME/etc/sbt/sbtopts JDK11: adopt@1.11.0-9 SPARK_LOCAL_IP: "127.0.0.1" diff --git a/build.sbt b/build.sbt index c81b5e5d7..eebbfdd3c 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ import scala.util.Try // ThisBuild settings take lower precedence, // but can be shared across the multi projects. ThisBuild / version := { - val v = "1.10.7-SNAPSHOT" + val v = "1.10.8-SNAPSHOT" nightlyVersion.getOrElse(v) } ThisBuild / version2_13 := "2.0.0-SNAPSHOT" diff --git a/sbt b/sbt index 9a1a4a0b4..d178f16c3 100755 --- a/sbt +++ b/sbt @@ -1,7 +1,7 @@ #!/usr/bin/env bash set +e -declare builtin_sbt_version="1.10.6" +declare builtin_sbt_version="1.10.7" declare -a residual_args declare -a java_args declare -a scalac_args From ad0ab07e99c98f0bb40fcafc22358cdabf17b86a Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:38:12 -0800 Subject: [PATCH 10/24] Return jvmBuildTarget for workspace/buildTargets --- .../internal/server/BuildServerProtocol.scala | 36 +++++++++++++- .../sbt/internal/bsp/JvmBuildTarget.scala | 48 +++++++++++++++++++ .../sbt/internal/bsp/ScalaBuildTarget.scala | 23 ++++++--- .../sbt/internal/bsp/codec/JsonProtocol.scala | 1 + .../bsp/codec/JvmBuildTargetFormats.scala | 29 +++++++++++ .../bsp/codec/SbtBuildTargetFormats.scala | 2 +- .../bsp/codec/ScalaBuildTargetFormats.scala | 6 ++- protocol/src/main/contraband/bsp.contra | 14 ++++++ 8 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala create mode 100644 protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 88e07d77d..b063cfa28 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -34,6 +34,7 @@ import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser => import xsbti.CompileFailed import java.io.File +import java.nio.file.Paths import java.util.concurrent.atomic.AtomicBoolean import scala.collection.mutable @@ -614,12 +615,19 @@ object BuildServerProtocol { val thisProjectRef = Keys.thisProjectRef.value val thisConfig = Keys.configuration.value val scalaJars = Keys.scalaInstance.value.allJars.map(_.toURI.toString) + val (javaHomeForTarget, isForkedJava) = javaHome.value match { + case Some(forkedJava) => (Some(forkedJava.toURI), true) + case None => (sys.props.get("java.home").map(Paths.get(_)).map(_.toUri), false) + } + val javaVersionForTarget = extractJavaVersion(javacOptions.value, isForkedJava) + val jvmBuildTarget = JvmBuildTarget(javaHomeForTarget, javaVersionForTarget) val compileData = ScalaBuildTarget( scalaOrganization = scalaOrganization.value, scalaVersion = scalaVersion.value, scalaBinaryVersion = scalaBinaryVersion.value, platform = ScalaPlatform.JVM, - jars = scalaJars.toVector + jars = scalaJars.toVector, + jvmBuildTarget = jvmBuildTarget, ) val configuration = Keys.configuration.value val displayName = BuildTargetName.fromScope(thisProject.id, configuration.name) @@ -659,7 +667,11 @@ object BuildServerProtocol { scalaVersion = scalaProvider.version(), scalaBinaryVersion = binaryScalaVersion(scalaProvider.version()), platform = ScalaPlatform.JVM, - jars = scalaJars.toVector.map(_.toURI.toString) + jars = scalaJars.toVector.map(_.toURI.toString), + jvmBuildTarget = JvmBuildTarget( + sys.props.get("java.home").map(Paths.get(_)).map(_.toUri), + sys.props.get("java.version") + ), ) val sbtVersionValue = sbtVersion.value val sbtData = SbtBuildTarget( @@ -985,6 +997,26 @@ object BuildServerProtocol { ) } + private def extractJavaVersion( + javacOptions: Seq[String], + isForkedJava: Boolean + ): Option[String] = { + def getVersionAfterFlag(flag: String): Option[String] = { + val index = javacOptions.indexOf(flag) + if (index >= 0) javacOptions.lift(index + 1) + else None + } + + val versionFromJavacOption = getVersionAfterFlag("--release") + .orElse(getVersionAfterFlag("--target")) + .orElse(getVersionAfterFlag("-target")) + + versionFromJavacOption.orElse { + // TODO: extract java version from forked javac + if (isForkedJava) None else sys.props.get("java.version") + } + } + // naming convention still seems like the only reliable way to get IntelliJ to import this correctly // https://github.com/JetBrains/intellij-scala/blob/a54c2a7c157236f35957049cbfd8c10587c9e60c/scala/scala-impl/src/org/jetbrains/sbt/language/SbtFileImpl.scala#L82-L84 private def toSbtTargetIdName(ref: LoadedBuildUnit): String = { diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala new file mode 100644 index 000000000..488b8b8e6 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/JvmBuildTarget.scala @@ -0,0 +1,48 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Contains jvm-specific metadata, specifically JDK reference + * @param javaHome Uri representing absolute path to jdk + * @param javaVersion The java version this target is supposed to use (can be set using javac `-target` flag) + */ +final class JvmBuildTarget private ( + val javaHome: Option[java.net.URI], + val javaVersion: Option[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { + case x: JvmBuildTarget => (this.javaHome == x.javaHome) && (this.javaVersion == x.javaVersion) + case _ => false + }) + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.JvmBuildTarget".##) + javaHome.##) + javaVersion.##) + } + override def toString: String = { + "JvmBuildTarget(" + javaHome + ", " + javaVersion + ")" + } + private[this] def copy(javaHome: Option[java.net.URI] = javaHome, javaVersion: Option[String] = javaVersion): JvmBuildTarget = { + new JvmBuildTarget(javaHome, javaVersion) + } + def withJavaHome(javaHome: Option[java.net.URI]): JvmBuildTarget = { + copy(javaHome = javaHome) + } + def withJavaHome(javaHome: java.net.URI): JvmBuildTarget = { + copy(javaHome = Option(javaHome)) + } + def withJavaVersion(javaVersion: Option[String]): JvmBuildTarget = { + copy(javaVersion = javaVersion) + } + def withJavaVersion(javaVersion: String): JvmBuildTarget = { + copy(javaVersion = Option(javaVersion)) + } +} +object JvmBuildTarget { + + def apply(javaHome: Option[java.net.URI], javaVersion: Option[String]): JvmBuildTarget = new JvmBuildTarget(javaHome, javaVersion) + def apply(javaHome: java.net.URI, javaVersion: String): JvmBuildTarget = new JvmBuildTarget(Option(javaHome), Option(javaVersion)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala index 32e5885a9..e686d84dd 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaBuildTarget.scala @@ -14,28 +14,30 @@ package sbt.internal.bsp For example, 2.12 if scalaVersion is 2.12.4. * @param platform The target platform for this target * @param jars A sequence of Scala jars such as scala-library, scala-compiler and scala-reflect. + * @param jvmBuildTarget The jvm build target describing jdk to be used */ final class ScalaBuildTarget private ( val scalaOrganization: String, val scalaVersion: String, val scalaBinaryVersion: String, val platform: Int, - val jars: Vector[String]) extends Serializable { + val jars: Vector[String], + val jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget]) extends Serializable { override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match { - case x: ScalaBuildTarget => (this.scalaOrganization == x.scalaOrganization) && (this.scalaVersion == x.scalaVersion) && (this.scalaBinaryVersion == x.scalaBinaryVersion) && (this.platform == x.platform) && (this.jars == x.jars) + case x: ScalaBuildTarget => (this.scalaOrganization == x.scalaOrganization) && (this.scalaVersion == x.scalaVersion) && (this.scalaBinaryVersion == x.scalaBinaryVersion) && (this.platform == x.platform) && (this.jars == x.jars) && (this.jvmBuildTarget == x.jvmBuildTarget) case _ => false }) override def hashCode: Int = { - 37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaBuildTarget".##) + scalaOrganization.##) + scalaVersion.##) + scalaBinaryVersion.##) + platform.##) + jars.##) + 37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaBuildTarget".##) + scalaOrganization.##) + scalaVersion.##) + scalaBinaryVersion.##) + platform.##) + jars.##) + jvmBuildTarget.##) } override def toString: String = { - "ScalaBuildTarget(" + scalaOrganization + ", " + scalaVersion + ", " + scalaBinaryVersion + ", " + platform + ", " + jars + ")" + "ScalaBuildTarget(" + scalaOrganization + ", " + scalaVersion + ", " + scalaBinaryVersion + ", " + platform + ", " + jars + ", " + jvmBuildTarget + ")" } - private[this] def copy(scalaOrganization: String = scalaOrganization, scalaVersion: String = scalaVersion, scalaBinaryVersion: String = scalaBinaryVersion, platform: Int = platform, jars: Vector[String] = jars): ScalaBuildTarget = { - new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars) + private[this] def copy(scalaOrganization: String = scalaOrganization, scalaVersion: String = scalaVersion, scalaBinaryVersion: String = scalaBinaryVersion, platform: Int = platform, jars: Vector[String] = jars, jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget] = jvmBuildTarget): ScalaBuildTarget = { + new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, jvmBuildTarget) } def withScalaOrganization(scalaOrganization: String): ScalaBuildTarget = { copy(scalaOrganization = scalaOrganization) @@ -52,8 +54,15 @@ final class ScalaBuildTarget private ( def withJars(jars: Vector[String]): ScalaBuildTarget = { copy(jars = jars) } + def withJvmBuildTarget(jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget]): ScalaBuildTarget = { + copy(jvmBuildTarget = jvmBuildTarget) + } + def withJvmBuildTarget(jvmBuildTarget: sbt.internal.bsp.JvmBuildTarget): ScalaBuildTarget = { + copy(jvmBuildTarget = Option(jvmBuildTarget)) + } } object ScalaBuildTarget { - def apply(scalaOrganization: String, scalaVersion: String, scalaBinaryVersion: String, platform: Int, jars: Vector[String]): ScalaBuildTarget = new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars) + def apply(scalaOrganization: String, scalaVersion: String, scalaBinaryVersion: String, platform: Int, jars: Vector[String], jvmBuildTarget: Option[sbt.internal.bsp.JvmBuildTarget]): ScalaBuildTarget = new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, jvmBuildTarget) + def apply(scalaOrganization: String, scalaVersion: String, scalaBinaryVersion: String, platform: Int, jars: Vector[String], jvmBuildTarget: sbt.internal.bsp.JvmBuildTarget): ScalaBuildTarget = new ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, Option(jvmBuildTarget)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala index f6cae48d8..c98c92a87 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala @@ -55,6 +55,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.TestResultFormats with sbt.internal.bsp.codec.RunParamsFormats with sbt.internal.bsp.codec.RunResultFormats + with sbt.internal.bsp.codec.JvmBuildTargetFormats with sbt.internal.bsp.codec.ScalaBuildTargetFormats with sbt.internal.bsp.codec.ScalacOptionsParamsFormats with sbt.internal.bsp.codec.ScalacOptionsItemFormats diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala new file mode 100644 index 000000000..301e778ba --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JvmBuildTargetFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait JvmBuildTargetFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val JvmBuildTargetFormat: JsonFormat[sbt.internal.bsp.JvmBuildTarget] = new JsonFormat[sbt.internal.bsp.JvmBuildTarget] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.JvmBuildTarget = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val javaHome = unbuilder.readField[Option[java.net.URI]]("javaHome") + val javaVersion = unbuilder.readField[Option[String]]("javaVersion") + unbuilder.endObject() + sbt.internal.bsp.JvmBuildTarget(javaHome, javaVersion) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.JvmBuildTarget, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("javaHome", obj.javaHome) + builder.addField("javaVersion", obj.javaVersion) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala index ff96c4211..4e057a969 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/SbtBuildTargetFormats.scala @@ -5,7 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.internal.bsp.codec import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -trait SbtBuildTargetFormats { self: sbt.internal.bsp.codec.ScalaBuildTargetFormats with sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.BuildTargetIdentifierFormats => +trait SbtBuildTargetFormats { self: sbt.internal.bsp.codec.ScalaBuildTargetFormats with sbt.internal.bsp.codec.JvmBuildTargetFormats with sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.BuildTargetIdentifierFormats => implicit lazy val SbtBuildTargetFormat: JsonFormat[sbt.internal.bsp.SbtBuildTarget] = new JsonFormat[sbt.internal.bsp.SbtBuildTarget] { override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.SbtBuildTarget = { __jsOpt match { diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala index 900994c4e..67b1389a3 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaBuildTargetFormats.scala @@ -5,7 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.internal.bsp.codec import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } -trait ScalaBuildTargetFormats { self: sjsonnew.BasicJsonProtocol => +trait ScalaBuildTargetFormats { self: sbt.internal.bsp.codec.JvmBuildTargetFormats with sjsonnew.BasicJsonProtocol => implicit lazy val ScalaBuildTargetFormat: JsonFormat[sbt.internal.bsp.ScalaBuildTarget] = new JsonFormat[sbt.internal.bsp.ScalaBuildTarget] { override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalaBuildTarget = { __jsOpt match { @@ -16,8 +16,9 @@ implicit lazy val ScalaBuildTargetFormat: JsonFormat[sbt.internal.bsp.ScalaBuild val scalaBinaryVersion = unbuilder.readField[String]("scalaBinaryVersion") val platform = unbuilder.readField[Int]("platform") val jars = unbuilder.readField[Vector[String]]("jars") + val jvmBuildTarget = unbuilder.readField[Option[sbt.internal.bsp.JvmBuildTarget]]("jvmBuildTarget") unbuilder.endObject() - sbt.internal.bsp.ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars) + sbt.internal.bsp.ScalaBuildTarget(scalaOrganization, scalaVersion, scalaBinaryVersion, platform, jars, jvmBuildTarget) case None => deserializationError("Expected JsObject but found None") } @@ -29,6 +30,7 @@ implicit lazy val ScalaBuildTargetFormat: JsonFormat[sbt.internal.bsp.ScalaBuild builder.addField("scalaBinaryVersion", obj.scalaBinaryVersion) builder.addField("platform", obj.platform) builder.addField("jars", obj.jars) + builder.addField("jvmBuildTarget", obj.jvmBuildTarget) builder.endObject() } } diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index a931a8fb6..1956cf440 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -605,6 +605,17 @@ type RunResult { } +# JVM extension + +## Contains jvm-specific metadata, specifically JDK reference +type JvmBuildTarget { + ## Uri representing absolute path to jdk + javaHome: java.net.URI + + ## The java version this target is supposed to use (can be set using javac `-target` flag) + javaVersion: String +} + # Scala Extension ## Contains scala-specific metadata for compiling a target containing Scala sources. @@ -626,6 +637,9 @@ type ScalaBuildTarget { ## A sequence of Scala jars such as scala-library, scala-compiler and scala-reflect. jars: [String]! + + ## The jvm build target describing jdk to be used + jvmBuildTarget: sbt.internal.bsp.JvmBuildTarget } ## Scalac options From 655310061f2fd7cc6d201f046f87286dc16961dc Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:56:18 -0800 Subject: [PATCH 11/24] Add unit test --- .../src/test/scala/testpkg/BuildServerTest.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 15ec8a1da..c24694587 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -53,6 +53,16 @@ object BuildServerTest extends AbstractServerTest { result.targets.find(_.displayName.contains("buildserver-build")).get assert(buildServerBuildTarget.id.uri.toString.endsWith("#buildserver-build")) assert(!result.targets.exists(_.displayName.contains("badBuildTarget"))) + // Check for JVM based Scala Project, built target should contain Java version information + val scalaBuildTarget = + Converter.fromJsonOptionUnsafe[ScalaBuildTarget](utilTarget.data) + val javaTarget = scalaBuildTarget.jvmBuildTarget + (javaTarget.flatMap(_.javaVersion), javaTarget.flatMap(_.javaHome)) match { + case (Some(javaVersion), Some(javaHome)) => + assert(javaVersion.equals(sys.props("java.version"))) + assert(javaHome.equals(Paths.get(sys.props("java.home")).toUri)) + case _ => fail("JVM build target should contain javaVersion and javaHome") + } } test("buildTarget/sources") { _ => From 838eee97cdac8d7e3c04859301a51fae4dc83525 Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Thu, 26 Dec 2024 15:05:00 -0800 Subject: [PATCH 12/24] Fix CI --- project/build.properties | 2 +- sbt-app/src/sbt-test/project/scripted13/test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project/build.properties b/project/build.properties index e88a0d817..73df629ac 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.6 +sbt.version=1.10.7 diff --git a/sbt-app/src/sbt-test/project/scripted13/test b/sbt-app/src/sbt-test/project/scripted13/test index d40ea3d07..2fd6077fb 100644 --- a/sbt-app/src/sbt-test/project/scripted13/test +++ b/sbt-app/src/sbt-test/project/scripted13/test @@ -1,6 +1,6 @@ # This tests that this sbt scripted plugin can launch the previous one -> ^^1.10.6 +> ^^1.10.7 $ copy-file changes/A.scala src/sbt-test/a/b/A.scala > scripted From e23419efedc8243776b643a0e80b67701b5fb404 Mon Sep 17 00:00:00 2001 From: friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Sun, 22 Dec 2024 01:12:49 -0800 Subject: [PATCH 13/24] Clean Zinc Cache for 'Compile / clean', 'Test / clean' --- main/src/main/scala/sbt/Defaults.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 942fccd55..6400d4080 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -928,7 +928,20 @@ object Defaults extends BuildCommon { tastyFiles.map(_.getAbsoluteFile) } else Nil }.value, - clean := (compileOutputs / clean).value, + clean := { + val _ = (compileOutputs / clean).value + val setup = compileIncSetup.value + try { + val store = AnalysisUtil.staticCachedStore( + analysisFile = setup.cacheFile.toPath, + useTextAnalysis = !enableBinaryCompileAnalysis.value, + useConsistent = enableConsistentCompileAnalysis.value, + ) + store.clearCache() + } catch { + case NonFatal(_) => () + } + }, earlyOutputPing := Def.promise[Boolean], compileProgress := { val s = streams.value From 13373415b3b49fb51e2c4a480042b6b16ff2cb4e Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Thu, 26 Dec 2024 18:43:04 -0800 Subject: [PATCH 14/24] Fix CI --- main/src/main/scala/sbt/Defaults.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 6400d4080..71c1a4912 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -930,10 +930,10 @@ object Defaults extends BuildCommon { }.value, clean := { val _ = (compileOutputs / clean).value - val setup = compileIncSetup.value + val analysisFile = compileAnalysisFile.value try { val store = AnalysisUtil.staticCachedStore( - analysisFile = setup.cacheFile.toPath, + analysisFile = analysisFile.toPath, useTextAnalysis = !enableBinaryCompileAnalysis.value, useConsistent = enableConsistentCompileAnalysis.value, ) From 1a8fa65af388c298f8eaa7dfec84c04f78862e38 Mon Sep 17 00:00:00 2001 From: Friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:55:42 -0800 Subject: [PATCH 15/24] Avoid upstream compilation when calling previousCompile --- main/src/main/scala/sbt/Defaults.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 71c1a4912..70987ad57 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2580,9 +2580,9 @@ object Defaults extends BuildCommon { private[sbt] def jnone[A]: Optional[A] = none[A].toOptional def compileAnalysisSettings: Seq[Setting[_]] = Seq( previousCompile := { - val setup = compileIncSetup.value + val analysisFile = compileAnalysisFile.value val store = AnalysisUtil.staticCachedStore( - analysisFile = setup.cacheFile.toPath, + analysisFile = analysisFile.toPath, useTextAnalysis = !enableBinaryCompileAnalysis.value, useConsistent = enableConsistentCompileAnalysis.value, ) From c834f500b91d36155a3542bbb4b8fb47a965f3f7 Mon Sep 17 00:00:00 2001 From: friendseeker <66892505+Friendseeker@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:30:15 -0800 Subject: [PATCH 16/24] Add comment --- main/src/main/scala/sbt/Defaults.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 70987ad57..a5e114b81 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2580,6 +2580,7 @@ object Defaults extends BuildCommon { private[sbt] def jnone[A]: Optional[A] = none[A].toOptional def compileAnalysisSettings: Seq[Setting[_]] = Seq( previousCompile := { + // Avoid compileIncSetup since it would trigger upstream compilation val analysisFile = compileAnalysisFile.value val store = AnalysisUtil.staticCachedStore( analysisFile = analysisFile.toPath, From a13bfd3ef96f1e7ac8906bb8f3fe70575caf6677 Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Sat, 11 Jan 2025 12:46:35 -0800 Subject: [PATCH 17/24] fix race condition in NetworkChannel --- main/src/main/scala/sbt/internal/server/NetworkChannel.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 95c0e16c0..dfe167cf4 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -241,7 +241,6 @@ final class NetworkChannel( } } } - thread.start() private[sbt] def isLanguageServerProtocol: Boolean = true @@ -365,7 +364,6 @@ final class NetworkChannel( impl() }, s"sbt-$name-write-thread") writeThread.setDaemon(true) - writeThread.start() def publishBytes(event: Array[Byte], delimit: Boolean): Unit = try pendingWrites.put(event -> delimit) @@ -914,6 +912,9 @@ final class NetworkChannel( } } private[sbt] def isAttached: Boolean = attached.get + + thread.start() + writeThread.start() } object NetworkChannel { From f0afda3dd0ad477a42beb2d51c63f23f151f173b Mon Sep 17 00:00:00 2001 From: Derek Wickern Date: Sat, 11 Jan 2025 13:08:18 -0800 Subject: [PATCH 18/24] make NetworkChannel#thread private --- main/src/main/scala/sbt/internal/server/NetworkChannel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index dfe167cf4..6bf3558c6 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -163,7 +163,7 @@ final class NetworkChannel( } } - val thread = new Thread(s"sbt-networkchannel-${connection.getPort}") { + private[this] val thread = new Thread(s"sbt-networkchannel-${connection.getPort}") { private val ct = "Content-Type: " private val x1 = "application/sbt-x1" override def run(): Unit = { From e46843bfd9b87c3d733c6e4105ee8dc714cf3252 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 17 Jan 2025 15:53:24 +0100 Subject: [PATCH 19/24] Add setting to allow demoting the SIP-51 build failure Add a `allowUnsafeScalaLibUpgrade` setting (default is `false`) to demote the SIP-51 build failure to a warning. If the scalaVersion is 2.13.12 but some dependency pulls in scala-library 2.13.13, the compiler will stay at 2.13.12, but the dependency classpath will contain scala-library 2.13.13. This usually works, the compiler can run fine with a newer scala-library on its dependency classpath. Macro expansion may fail, if the macro uses some library class / method that doesn't exist in the old version. The macro itself is loaded from the dependency classpath into the class loader running the compiler, where the older Scala library is on the runtime classpath. Using the Scala REPL in sbt may also fail in a similar fashion. --- main/src/main/scala/sbt/Defaults.scala | 52 ++++++++++++------- main/src/main/scala/sbt/Keys.scala | 1 + .../main/scala/sbt/internal/LintUnused.scala | 1 + .../stdlib-unfreeze-warn/a/A.scala | 27 ++++++++++ .../stdlib-unfreeze-warn/b/B.scala | 7 +++ .../stdlib-unfreeze-warn/build.sbt | 39 ++++++++++++++ .../stdlib-unfreeze-warn/c/C.scala | 7 +++ .../stdlib-unfreeze-warn/test | 11 ++++ 8 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala create mode 100644 sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index a5e114b81..fa9b6f509 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -180,6 +180,7 @@ object Defaults extends BuildCommon { apiMappings := Map.empty, autoScalaLibrary :== true, managedScalaInstance :== true, + allowUnsafeScalaLibUpgrade :== false, classpathEntryDefinesClass := { (file: File) => sys.error("use classpathEntryDefinesClassVF instead") }, @@ -1178,6 +1179,7 @@ object Defaults extends BuildCommon { def scalaInstanceFromUpdate: Initialize[Task[ScalaInstance]] = Def.task { val sv = scalaVersion.value val fullReport = update.value + val s = streams.value // For Scala 3, update scala-library.jar in `scala-tool` and `scala-doc-tool` in case a newer version // is present in the `compile` configuration. This is needed once forwards binary compatibility is dropped @@ -1204,24 +1206,38 @@ object Defaults extends BuildCommon { ) if (Classpaths.isScala213(sv)) { - for { - compileReport <- fullReport.configuration(Configurations.Compile) - libName <- ScalaArtifacts.Artifacts - } { - for (lib <- compileReport.modules.find(_.module.name == libName)) { - val libVer = lib.module.revision - val n = name.value - if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer"))) - sys.error( - s"""expected `$n/scalaVersion` to be "$libVer" or later, - |but found "$sv"; upgrade scalaVersion to fix the build. - | - |to support backwards-only binary compatibility (SIP-51), - |the Scala 2.13 compiler cannot be older than $libName on the - |dependency classpath. - |see `$n/evicted` to know why $libName $libVer is getting pulled in. - |""".stripMargin - ) + val scalaDeps = for { + compileReport <- fullReport.configuration(Configurations.Compile).iterator + libName <- ScalaArtifacts.Artifacts.iterator + lib <- compileReport.modules.find(_.module.name == libName) + } yield lib + for (lib <- scalaDeps.take(1)) { + val libVer = lib.module.revision + val libName = lib.module.name + val n = name.value + if (VersionNumber(sv).matchesSemVer(SemanticSelector(s"<$libVer"))) { + val err = !allowUnsafeScalaLibUpgrade.value + val fix = + if (err) + """Upgrade the `scalaVersion` to fix the build. If upgrading the Scala compiler version is + |not possible (for example due to a regression in the compiler or a missing dependency), + |this error can be demoted by setting `allowUnsafeScalaLibUpgrade := true`.""".stripMargin + else + s"""Note that the dependency classpath and the runtime classpath of your project + |contain the newer $libName $libVer, even if the scalaVersion is $sv. + |Compilation (macro expansion) or using the Scala REPL in sbt may fail with a LinkageError.""".stripMargin + + val msg = + s"""Expected `$n/scalaVersion` to be $libVer or later, but found $sv. + |To support backwards-only binary compatibility (SIP-51), the Scala 2.13 compiler + |should not be older than $libName on the dependency classpath. + | + |$fix + | + |See `$n/evicted` to know why $libName $libVer is getting pulled in. + |""".stripMargin + if (err) sys.error(msg) + else s.log.warn(msg) } } } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 9349773d7..94b1f7def 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -571,6 +571,7 @@ object Keys { val conflictManager = settingKey[ConflictManager]("Selects the conflict manager to use for dependency management.").withRank(CSetting) val autoScalaLibrary = settingKey[Boolean]("Adds a dependency on scala-library if true.").withRank(ASetting) val managedScalaInstance = settingKey[Boolean]("Automatically obtains Scala tools as managed dependencies if true.").withRank(BSetting) + val allowUnsafeScalaLibUpgrade = settingKey[Boolean]("Allow the Scala library on the compilation classpath to be newer than the scalaVersion (see Scala SIP-51).").withRank(CSetting) val sbtResolver = settingKey[Resolver]("Provides a resolver for obtaining sbt as a dependency.").withRank(BMinusSetting) val sbtResolvers = settingKey[Seq[Resolver]]("The external resolvers for sbt and plugin dependencies.").withRank(BMinusSetting) val sbtDependency = settingKey[ModuleID]("Provides a definition for declaring the current version of sbt.").withRank(BMinusSetting) diff --git a/main/src/main/scala/sbt/internal/LintUnused.scala b/main/src/main/scala/sbt/internal/LintUnused.scala index c2d2db006..8df675c15 100644 --- a/main/src/main/scala/sbt/internal/LintUnused.scala +++ b/main/src/main/scala/sbt/internal/LintUnused.scala @@ -34,6 +34,7 @@ object LintUnused { commands, crossScalaVersions, crossSbtVersions, + allowUnsafeScalaLibUpgrade, initialize, lintUnusedKeysOnLoad, onLoad, diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala new file mode 100644 index 000000000..c66551e1b --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/a/A.scala @@ -0,0 +1,27 @@ +import scala.language.reflectiveCalls + + +package scala.collection.immutable { + object Exp { + // Access RedBlackTree.validate added in Scala 2.13.13 + def v = RedBlackTree.validate(null)(null) + } +} + + +object A extends App { + println(scala.util.Properties.versionString) +} + +object AMacro { + import scala.language.experimental.macros + import scala.reflect.macros.blackbox.Context + + def m(x: Int): Int = macro impl + + def impl(c: Context)(x: c.Expr[Int]): c.Expr[Int] = { + import c.universe._ + println(scala.collection.immutable.Exp.v) + c.Expr(q"2 + $x") + } +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala new file mode 100644 index 000000000..f75b7905e --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/b/B.scala @@ -0,0 +1,7 @@ +import java.nio.file.{Paths, Files} +import java.nio.charset.StandardCharsets + +object B extends App { + println(AMacro.m(33)) // fails + Files.write(Paths.get(s"s${scala.util.Properties.versionNumberString}.txt"), "nix".getBytes) +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt new file mode 100644 index 000000000..c757f2648 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/build.sbt @@ -0,0 +1,39 @@ +import sbt.librarymanagement.InclExclRule + +lazy val a = project.settings( + scalaVersion := "2.13.13", + libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, + TaskKey[Unit]("checkLibs") := checkLibs("2.13.13", (Compile/dependencyClasspath).value, ".*scala-(library|reflect).*"), +) + +lazy val b = project.dependsOn(a).settings( + allowUnsafeScalaLibUpgrade := true, + scalaVersion := "2.13.12", + + // dependencies are upgraded to 2.13.13 + TaskKey[Unit]("checkLibs") := checkLibs("2.13.13", (Compile/dependencyClasspath).value, ".*scala-(library|reflect).*"), + + // check the compiler uses the 2.13.12 library on its runtime classpath + TaskKey[Unit]("checkScala") := { + val i = scalaInstance.value + i.libraryJars.filter(_.toString.contains("scala-library")).toList match { + case List(l) => assert(l.toString.contains("2.13.12"), i.toString) + } + assert(i.compilerJars.filter(_.toString.contains("scala-library")).isEmpty, i.toString) + assert(i.otherJars.filter(_.toString.contains("scala-library")).isEmpty, i.toString) + }, +) + +lazy val c = project.dependsOn(a).settings( + allowUnsafeScalaLibUpgrade := true, + scalaVersion := "2.13.12", + TaskKey[Unit]("checkLibs") := checkLibs("2.13.13", (Compile/dependencyClasspath).value, ".*scala-(library|reflect).*"), +) + +def checkLibs(v: String, cp: Classpath, filter: String): Unit = { + for (p <- cp) + if (p.toString.matches(filter)) { + println(s"$p -- $v") + assert(p.toString.contains(v), p) + } +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala new file mode 100644 index 000000000..de0f7c084 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/c/C.scala @@ -0,0 +1,7 @@ +import java.nio.file.{Paths, Files} +import java.nio.charset.StandardCharsets + +object C extends App { + assert(scala.collection.immutable.Exp.v == null) + Files.write(Paths.get(s"s${scala.util.Properties.versionNumberString}.txt"), "nix".getBytes) +} diff --git a/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test new file mode 100644 index 000000000..f347b3534 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/stdlib-unfreeze-warn/test @@ -0,0 +1,11 @@ +> a/checkLibs +> b/checkLibs +> b/checkScala +> c/checkLibs + +# macro expansion fails +-> b/compile + +> c/run +$ exists s2.13.13.txt +$ delete s2.13.13.txt From 55b1fdeddbc1b58373b1caf0946d2e900615169c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 28 Jan 2025 00:08:28 -0500 Subject: [PATCH 20/24] fix: Fix Chrome tracing file **Problem** We changed the content of Chrome tracing file incorrectly and renamed tid to tname. **Solution** 1. This renames tname back to to tid. 2. To retain the fix to avoid Thread#getId, this calls either the JDK 8 way or the JDK 19 way reflectively. --- .../main/scala/sbt/internal/util/Util.scala | 37 +++++++++++++++++++ .../sbt/internal/AbstractTaskProgress.scala | 2 + .../scala/sbt/internal/TaskTraceEvent.scala | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala index 324c62dfb..b78ae61c8 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala @@ -12,6 +12,8 @@ import java.util.Locale import scala.reflect.macros.blackbox import scala.language.experimental.macros +import scala.language.reflectiveCalls +import scala.util.control.NonFatal object Util { def makeList[T](size: Int, value: T): List[T] = List.fill(size)(value) @@ -77,4 +79,39 @@ object Util { class Macro(val c: blackbox.Context) { def ignore(f: c.Tree): c.Expr[Unit] = c.universe.reify({ c.Expr[Any](f).splice; () }) } + + lazy val majorJavaVersion: Int = + try { + val javaVersion = sys.props.get("java.version").getOrElse("1.0") + if (javaVersion.startsWith("1.")) { + javaVersion.split("\\.")(1).toInt + } else { + javaVersion.split("\\.")(0).toInt + } + } catch { + case NonFatal(_) => 0 + } + + private type GetId = { + def getId: Long + } + private type ThreadId = { + def threadId: Long + } + + /** + * Returns current thread id. + * Thread.threadId was added in JDK 19, and deprecated Thread#getId + * https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#threadId() + */ + def threadId: Long = + if (majorJavaVersion < 19) { + (Thread.currentThread(): AnyRef) match { + case g: GetId @unchecked => g.getId + } + } else { + (Thread.currentThread(): AnyRef) match { + case g: ThreadId @unchecked => g.threadId + } + } } diff --git a/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala b/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala index aabeb8401..bce7bb2ba 100644 --- a/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala +++ b/main/src/main/scala/sbt/internal/AbstractTaskProgress.scala @@ -9,6 +9,7 @@ package sbt package internal +import sbt.internal.util.Util import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong import scala.collection.JavaConverters._ @@ -123,6 +124,7 @@ object AbstractTaskExecuteProgress { private[sbt] class Timer() { val startNanos: Long = System.nanoTime() val threadName: String = Thread.currentThread().getName + val threadId: Long = Util.threadId var endNanos: Long = 0L def stop(): Unit = { endNanos = System.nanoTime() diff --git a/main/src/main/scala/sbt/internal/TaskTraceEvent.scala b/main/src/main/scala/sbt/internal/TaskTraceEvent.scala index 37f6ecdb6..914ee84a1 100644 --- a/main/src/main/scala/sbt/internal/TaskTraceEvent.scala +++ b/main/src/main/scala/sbt/internal/TaskTraceEvent.scala @@ -61,7 +61,7 @@ private[sbt] final class TaskTraceEvent def durationEvent(name: String, cat: String, t: Timer): String = { val sb = new java.lang.StringBuilder(name.length + 2) CompactPrinter.print(new JString(name), sb) - s"""{"name": ${sb.toString}, "cat": "$cat", "ph": "X", "ts": ${(t.startMicros)}, "dur": ${(t.durationMicros)}, "pid": 0, "tname": "${t.threadName}"}""" + s"""{"name": ${sb.toString}, "cat": "$cat", "ph": "X", "ts": ${(t.startMicros)}, "dur": ${(t.durationMicros)}, "pid": 0, "tid": "${t.threadId}"}""" } val entryIterator = currentTimings while (entryIterator.hasNext) { From 2ee5eb7fa7740cc65f92537a282b2b064c13b0d0 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:34:26 -0500 Subject: [PATCH 21/24] Make timing outputs consistently show hours and hint at time format Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .../sbt/internal/client/NetworkClient.scala | 8 ++++---- .../scala/sbt/internal/AggregationSpec.scala | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 45ec9b52c..72f3983ba 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -1130,13 +1130,13 @@ object NetworkClient { val totalString = s"$total s" + (if (total <= 60) "" else { - val maybeHours = total / 3600 match { - case 0 => "" - case h => f"$h%02d:" + val hours = total / 3600 match { + case 0 => "0" + case h => f"$h%02d" } val mins = f"${total % 3600 / 60}%02d" val secs = f"${total % 60}%02d" - s" ($maybeHours$mins:$secs)" + s" ($hours:$mins:$secs.0)" }) s"Total time: $totalString, completed $nowString" } diff --git a/main/src/test/scala/sbt/internal/AggregationSpec.scala b/main/src/test/scala/sbt/internal/AggregationSpec.scala index 8ac2d8f8a..b6cbdb681 100644 --- a/main/src/test/scala/sbt/internal/AggregationSpec.scala +++ b/main/src/test/scala/sbt/internal/AggregationSpec.scala @@ -12,15 +12,15 @@ object AggregationSpec extends verify.BasicTestSuite { val timing = Aggregation.timing(Aggregation.defaultFormat, 0, _: Long) test("timing should format total time properly") { - assert(timing(101).startsWith("Total time: 0 s,")) - assert(timing(1000).startsWith("Total time: 1 s,")) - assert(timing(3000).startsWith("Total time: 3 s,")) - assert(timing(30399).startsWith("Total time: 30 s,")) - assert(timing(60399).startsWith("Total time: 60 s,")) - assert(timing(60699).startsWith("Total time: 61 s (01:01),")) - assert(timing(303099).startsWith("Total time: 303 s (05:03),")) - assert(timing(6003099).startsWith("Total time: 6003 s (01:40:03),")) - assert(timing(96003099).startsWith("Total time: 96003 s (26:40:03),")) + assert(timing(101).startsWith("Total time: 0 s")) + assert(timing(1000).startsWith("Total time: 1 s")) + assert(timing(3000).startsWith("Total time: 3 s")) + assert(timing(30399).startsWith("Total time: 30 s")) + assert(timing(60399).startsWith("Total time: 60 s")) + assert(timing(60699).startsWith("Total time: 61 s (0:01:01.0)")) + assert(timing(303099).startsWith("Total time: 303 s (0:05:03.0)")) + assert(timing(6003099).startsWith("Total time: 6003 s (01:40:03.0)")) + assert(timing(96003099).startsWith("Total time: 96003 s (26:40:03.0)")) } test("timing should not emit special space characters") { From a7d862a08be0f940042a3f2bd72c03f873f64667 Mon Sep 17 00:00:00 2001 From: Dmitrii Naumenko Date: Tue, 4 Feb 2025 18:11:06 +0100 Subject: [PATCH 22/24] detect user-specific jdk installations on macOs (fixes #8031) User-specific JDKs are installed, for example, by IntelliJ IDEA --- main/src/main/scala/sbt/internal/CrossJava.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/internal/CrossJava.scala b/main/src/main/scala/sbt/internal/CrossJava.scala index 38765232c..6761b30f1 100644 --- a/main/src/main/scala/sbt/internal/CrossJava.scala +++ b/main/src/main/scala/sbt/internal/CrossJava.scala @@ -412,13 +412,19 @@ private[sbt] object CrossJava { class MacOsDiscoverConfig extends JavaDiscoverConf { val base: File = file("/Library") / "Java" / "JavaVirtualMachines" + // User-specific JDKs are installed, for example, by IntelliJ IDEA + private val baseInUserHome: File = Path.userHome / "Library" / "Java" / "JavaVirtualMachines" def javaHomes: Vector[(String, File)] = - wrapNull(base.list()) - .collect { - case dir @ JavaHomeDir(version) => - version -> (base / dir / "Contents" / "Home") - } + findAllHomes(base) ++ + findAllHomes(baseInUserHome) + + private def findAllHomes(root: File): Vector[(String, File)] = { + wrapNull(root.list()).collect { + case dir @ JavaHomeDir(version) => + version -> (root / dir / "Contents" / "Home") + } + } } class JabbaDiscoverConfig extends JavaDiscoverConf { From 06acd261d5353cd7e27e6927467aeb3821b288a5 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 9 Feb 2025 17:33:23 -0500 Subject: [PATCH 23/24] Scala 2.13.16 --- .github/workflows/ci.yml | 8 ++++---- project/Dependencies.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 846327e2a..dbc83f1d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,10 +32,10 @@ jobs: java: 21 distribution: temurin jobtype: 5 - - os: ubuntu-latest - java: 8 - distribution: adopt - jobtype: 6 + # - os: ubuntu-latest + # java: 8 + # distribution: adopt + # jobtype: 6 - os: ubuntu-latest java: 8 distribution: adopt diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 9e4b893be..4c35c591d 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -5,7 +5,7 @@ import sbt.contraband.ContrabandPlugin.autoImport._ object Dependencies { // WARNING: Please Scala update versions in PluginCross.scala too val scala212 = "2.12.20" - val scala213 = "2.13.15" + val scala213 = "2.13.16" val checkPluginCross = settingKey[Unit]("Make sure scalaVersion match up") val baseScalaVersion = scala212 def nightlyVersion: Option[String] = From a75e847a08f9729cf26a0c87123c039ec05a95cd Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 9 Feb 2025 17:07:48 -0500 Subject: [PATCH 24/24] refactor: Refactor response handler **Problem** The sbtn response handling code is relatively stragightforward, but it's a bit messy. **Solution** This cleans it up a bit, similar to the style used by Unfiltered back then (not sure how Unfiltered plans are written nowadays) by expressing each event handling as a partial function, and composing them together using `orElse`. --- .../main/scala/sbt/internal/util/Util.scala | 7 ++ .../sbt/internal/client/NetworkClient.scala | 108 ++++++++++-------- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala index b78ae61c8..c182bad16 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala @@ -80,6 +80,13 @@ object Util { def ignore(f: c.Tree): c.Expr[Unit] = c.universe.reify({ c.Expr[Any](f).splice; () }) } + /** + * Given a list of event handlers expressed partial functions, combine them + * together using orElse from the left. + */ + def reduceIntents[A1, A2](intents: PartialFunction[A1, A2]*): PartialFunction[A1, A2] = + intents.toList.reduceLeft(_ orElse _) + lazy val majorJavaVersion: Int = try { val javaVersion = sys.props.get("java.version").getOrElse("1.0") diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 72f3983ba..160291f31 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -529,61 +529,77 @@ class NetworkClient( .getOrElse(1) case _ => 1 } - private def completeExec(execId: String, exitCode: => Int): Unit = + + private val onAttachResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if attachUUID.get == msg.id => + attachUUID.set(null) + attached.set(true) + Option(inputThread.get).foreach(_.drain()) + () + } + def completeExec(execId: String, exitCode: Int) = { pendingResults.remove(execId) match { - case null => + case null => () case (q, startTime, name) => val now = System.currentTimeMillis val message = NetworkClient.timing(startTime, now) - val ec = exitCode if (batchMode.get || !attached.get) { - if (ec == 0) console.success(message) + if (exitCode == 0) console.success(message) else console.appendLog(Level.Error, message) } - Util.ignoreResult(q.offer(ec)) - } - def onResponse(msg: JsonRpcResponseMessage): Unit = { - completeExec(msg.id, getExitCode(msg.result)) - pendingCancellations.remove(msg.id) match { - case null => - case q => q.offer(msg.toString.contains("Task cancelled")) - } - msg.id match { - case execId => - if (attachUUID.get == msg.id) { - attachUUID.set(null) - attached.set(true) - Option(inputThread.get).foreach(_.drain()) - } - pendingCompletions.remove(execId) match { - case null => - case completions => - completions(msg.result match { - case Some(o: JObject) => - o.value - .foldLeft(CompletionResponse(Vector.empty[String])) { - case (resp, i) => - if (i.field == "items") - resp.withItems( - Converter - .fromJson[Vector[String]](i.value) - .getOrElse(Vector.empty[String]) - ) - else if (i.field == "cachedTestNames") - resp.withCachedTestNames( - Converter.fromJson[Boolean](i.value).getOrElse(true) - ) - else if (i.field == "cachedMainClassNames") - resp.withCachedMainClassNames( - Converter.fromJson[Boolean](i.value).getOrElse(true) - ) - else resp - } - case _ => CompletionResponse(Vector.empty[String]) - }) - } + Util.ignoreResult(q.offer(exitCode)) } } + private val onExecResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if pendingResults.containsKey(msg.id) => + completeExec(msg.id, getExitCode(msg.result)) + } + private val onCancellationResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if pendingCancellations.containsKey(msg.id) => + pendingCancellations.remove(msg.id) match { + case null => () + case q => Util.ignoreResult(q.offer(msg.toString.contains("Task cancelled"))) + } + } + private val onCompletionResponse: PartialFunction[JsonRpcResponseMessage, Unit] = { + case msg if pendingCompletions.containsKey(msg.id) => + pendingCompletions.remove(msg.id) match { + case null => () + case completions => + completions(msg.result match { + case Some(o: JObject) => + o.value + .foldLeft(CompletionResponse(Vector.empty[String])) { + case (resp, i) => + if (i.field == "items") + resp.withItems( + Converter + .fromJson[Vector[String]](i.value) + .getOrElse(Vector.empty[String]) + ) + else if (i.field == "cachedTestNames") + resp.withCachedTestNames( + Converter.fromJson[Boolean](i.value).getOrElse(true) + ) + else if (i.field == "cachedMainClassNames") + resp.withCachedMainClassNames( + Converter.fromJson[Boolean](i.value).getOrElse(true) + ) + else resp + } + case _ => CompletionResponse(Vector.empty[String]) + }) + } + } + // cache the composed plan + private val responsePlan = Util.reduceIntents[JsonRpcResponseMessage, Unit]( + onExecResponse, + onCancellationResponse, + onAttachResponse, + onCompletionResponse, + { case _ => () }, + ) + def onResponse(msg: JsonRpcResponseMessage): Unit = responsePlan(msg) def onNotification(msg: JsonRpcNotificationMessage): Unit = { def splitToMessage: Vector[(Level.Value, String)] =