From da9dc644eee37ff155f74d1251590dd42aa798fe Mon Sep 17 00:00:00 2001 From: JeanMarc van Leerdam Date: Wed, 20 Aug 2025 08:41:06 +0200 Subject: [PATCH 01/17] Include artifact name in Sona deployment --- main/src/main/scala/sbt/Defaults.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index cef243bb4..fb466ae95 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -3105,9 +3105,10 @@ object Classpaths { }, sonaDeploymentName := { val o = organization.value + val n = name.value val v = version.value val uuid = UUID.randomUUID().toString().take(8) - s"$o:$v:$uuid" + s"$o:$n:$v:$uuid" }, ) From 724b8cebef747a87fe4fdcee05decb0d52877c9f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 21 Aug 2025 14:11:23 +0200 Subject: [PATCH 02/17] scala-library 3.8.0 support **Problem** Scala 3.8.0 nightly and later in-sources the scala-library for the use by Scala 3, as opposed to using Scala-2.13-bound standard library. This means that we will run into situations where scala-library should NOT align with scala-reflect, which might exist transitively. **Solution** Adjust the csrSameVersions rule for Scala 3.8 so it will only try to keep scala-library and scala3-library versions aligned. --- main/src/main/scala/sbt/Defaults.scala | 19 ++++++++++++++++--- project/Dependencies.scala | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index cef243bb4..8af12defd 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -268,9 +268,7 @@ object Defaults extends BuildCommon { csrMavenProfiles :== Set.empty, csrReconciliations :== LMCoursier.relaxedForAllModules, csrMavenDependencyOverride :== false, - csrSameVersions := Seq( - ScalaArtifacts.Artifacts.map(a => InclExclRule(scalaOrganization.value, a)).toSet - ), + csrSameVersions :== Nil, stagingDirectory := (ThisBuild / baseDirectory).value / "target" / "sona-staging", localStaging := Some(Resolver.file("local-staging", stagingDirectory.value)), sonaBundle := Publishing @@ -3245,6 +3243,21 @@ object Classpaths { (proj +: base).distinct } }).value, + csrSameVersions ++= { + partialVersion(scalaVersion.value) match { + // See https://github.com/sbt/sbt/issues/8224 + // Scala 3.8+ should align only Scala3_8Artifacts + case Some((3, minor)) if minor >= 8 => + ScalaArtifacts.Scala3_8Artifacts + .map(a => InclExclRule(scalaOrganization.value, a)) + .toSet :: Nil + case Some((major, minor)) if major == 2 || major == 3 => + ScalaArtifacts.Artifacts + .map(a => InclExclRule(scalaOrganization.value, a)) + .toSet :: Nil + case _ => Nil + } + }, moduleName := normalizedName.value, ivyPaths := IvyPaths(baseDirectory.value, bootIvyHome(appConfiguration.value)), csrCacheDirectory := { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6147eb617..aaedaaad5 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -14,7 +14,7 @@ object Dependencies { // sbt modules private val ioVersion = nightlyVersion.getOrElse("1.10.5") private val lmVersion = - sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.11.3") + sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.11.4") val zincVersion = nightlyVersion.getOrElse("1.10.8") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From 2eead759583580c9323f8aeacf4325ff3f71ca7f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 22 Aug 2025 16:59:23 -0400 Subject: [PATCH 03/17] Add clean.yml --- .github/workflows/clean.yml | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/clean.yml diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml new file mode 100644 index 000000000..f0320f452 --- /dev/null +++ b/.github/workflows/clean.yml @@ -0,0 +1,57 @@ +name: Clean + +on: + workflow_dispatch: + +permissions: + actions: write + +jobs: + delete-artifacts: + name: Delete Artifacts + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Delete artifacts + shell: bash {0} + run: | + # Customize those three lines with your repository and credentials: + REPO=${GITHUB_API_URL}/repos/${{ github.repository }} + + # A shortcut to call GitHub API. + ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } + + # A temporary file which receives HTTP response headers. + TMPFILE=$(mktemp) + + # An associative array, key: artifact name, value: number of artifacts of that name. + declare -A ARTCOUNT + + # Process all artifacts on this repository, loop on returned "pages". + URL=$REPO/actions/artifacts + while [[ -n "$URL" ]]; do + + # Get current page, get response headers in a temporary file. + JSON=$(ghapi --dump-header $TMPFILE "$URL") + + # Get URL of next page. Will be empty if we are at the last page. + URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') + rm -f $TMPFILE + + # Number of artifacts on this page: + COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) + + # Loop on all artifacts on this page. + for ((i=0; $i < $COUNT; i++)); do + + # Get name of artifact and count instances of this name. + name=$(jq <<<$JSON -r ".artifacts[$i].name?") + ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) + + id=$(jq <<<$JSON -r ".artifacts[$i].id?") + size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) + printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size + ghapi -X DELETE $REPO/actions/artifacts/$id + done + done From 6c2e64af1b4f0ef1cf3a039b8f99cf02c70cd0c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 21:13:08 +0000 Subject: [PATCH 04/17] Bump actions/setup-java from 4 to 5 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/nightly.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c47b39e..f5e863d8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: ref: 1.10.x path: zinc - name: Setup JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: "${{ matrix.distribution }}" java-version: "${{ matrix.java }}" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2ae463080..ec469bebd 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -41,7 +41,7 @@ jobs: ref: 1.10.x path: zinc - name: Setup JDK - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: "${{ matrix.distribution }}" java-version: "${{ matrix.java }}" From dd6578eaf6b4655979cb764aa7472e993506d3be Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 22 Aug 2025 17:14:52 -0400 Subject: [PATCH 05/17] Don't cache boot --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c47b39e..d421912b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,11 +93,11 @@ jobs: python-version: 3.12 - name: Coursier cache uses: coursier/cache-action@v6 - - name: Cache sbt - uses: actions/cache@v4 - with: - path: ~/.sbt - key: ${{ runner.os }}-sbt-cache-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + # - name: Cache sbt + # uses: actions/cache@v4 + # with: + # path: ~/.sbt + # key: ${{ runner.os }}-sbt-cache-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - name: Setup Windows C++ toolchain uses: ilammy/msvc-dev-cmd@v1 if: ${{ matrix.os == 'windows-latest' }} @@ -105,7 +105,6 @@ jobs: if: ${{ matrix.jobtype == 1 }} shell: bash run: | - rm -rf "$HOME/.sbt/boot/" || true ./sbt -v --client mimaReportBinaryIssues ./sbt -v --client javafmtCheck ./sbt -v --client "Test/javafmtCheck" From a7baab70e8c75ec069c40964c7aa3c1d6f0f6604 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 Aug 2025 18:14:10 -0400 Subject: [PATCH 06/17] Bump lm --- .gitignore | 1 + project/Dependencies.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 214fa10f9..43bd50428 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ metals.sbt launcher-package/citest/freshly-baked .vscode sbt-launch.jar +local-temp diff --git a/project/Dependencies.scala b/project/Dependencies.scala index aaedaaad5..4f4e185ed 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -14,7 +14,7 @@ object Dependencies { // sbt modules private val ioVersion = nightlyVersion.getOrElse("1.10.5") private val lmVersion = - sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.11.4") + sys.props.get("sbt.build.lm.version").orElse(nightlyVersion).getOrElse("1.11.5") val zincVersion = nightlyVersion.getOrElse("1.10.8") private val sbtIO = "org.scala-sbt" %% "io" % ioVersion From b2afa2114f488fd18b6c7f9bbc1f3798218debe3 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Aug 2025 00:11:43 -0400 Subject: [PATCH 07/17] ci: Split server-test --- .github/workflows/ci.yml | 19 +------------------ .github/workflows/server-test.yml | 29 +++++++++++++++++++++++++++++ launcher-package/LICENSE | 2 +- 3 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/server-test.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a0afd1cb..122d84620 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,19 +85,13 @@ jobs: with: distribution: "${{ matrix.distribution }}" java-version: "${{ matrix.java }}" + cache: "sbt" - name: Setup SBT uses: sbt/setup-sbt@v1 - name: Set up Python 3.12 uses: actions/setup-python@v5 with: python-version: 3.12 - - name: Coursier cache - uses: coursier/cache-action@v6 - # - name: Cache sbt - # uses: actions/cache@v4 - # with: - # path: ~/.sbt - # key: ${{ runner.os }}-sbt-cache-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - name: Setup Windows C++ toolchain uses: ilammy/msvc-dev-cmd@v1 if: ${{ matrix.os == 'windows-latest' }} @@ -116,7 +110,6 @@ jobs: ./sbt -v --client "Test/compile" ./sbt -v --client publishLocal ./sbt -v --client test - ./sbt -v --client "serverTestProj/test" ./sbt -v --client doc ./sbt -v --client "all $UTIL_TESTS" ./sbt -v --client ++2.13.x @@ -200,13 +193,3 @@ jobs: cd citest ./test.bat test3/test3.bat - - name: Cleanup - shell: bash - run: | - rm -rf "$HOME/.sbt/scripted/" || true - rm -rf "$HOME/.ivy2/local" || true - rm -r $(find $HOME/.sbt/boot -name "*-SNAPSHOT") || true - find $HOME/Library/Caches/Coursier/v1 -name "ivydata-*.properties" -delete || true - find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true - find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true - find $HOME/.sbt -name "*.lock" -delete || true diff --git a/.github/workflows/server-test.yml b/.github/workflows/server-test.yml new file mode 100644 index 000000000..eef06bb82 --- /dev/null +++ b/.github/workflows/server-test.yml @@ -0,0 +1,29 @@ +name: Server Test +on: + pull_request: + push: + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + test: + runs-on: ubuntu-latest + env: + JAVA_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 + JVM_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 + SBT_ETC_FILE: $HOME/etc/sbt/sbtopts + steps: + - name: Checkout sbt/sbt + uses: actions/checkout@v4 + - name: Setup JDK + uses: actions/setup-java@v5 + with: + distribution: "zulu" + java-version: "8" + cache: sbt + - name: Setup SBT + uses: sbt/setup-sbt@v1 + - name: Server test + shell: bash + run: sbt -v --client "serverTestProj/test" diff --git a/launcher-package/LICENSE b/launcher-package/LICENSE index 7a694c969..ea5b60640 120000 --- a/launcher-package/LICENSE +++ b/launcher-package/LICENSE @@ -1 +1 @@ -LICENSE \ No newline at end of file +../LICENSE \ No newline at end of file From 2de01a8a05ddb362c0bf19c664b8e4c95b627a1c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 Aug 2025 21:04:40 -0400 Subject: [PATCH 08/17] [1.x] --jvm-client This adds --jvm-client to the runner script. --- .github/workflows/ci.yml | 2 +- launcher-package/citest/build.sbt | 4 +-- .../citest/project/build.properties | 2 +- .../src/test/scala/RunnerTest.scala | 14 ++++----- launcher-package/src/universal/bin/sbt.bat | 31 ++++++++++++++++--- sbt | 4 +++ 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 122d84620..2a67b9291 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.7 + TEST_SBT_VER: 1.11.4 SBT_ETC_FILE: $HOME/etc/sbt/sbtopts JDK11: adopt@1.11.0-9 SPARK_LOCAL_IP: "127.0.0.1" diff --git a/launcher-package/citest/build.sbt b/launcher-package/citest/build.sbt index cd4f0ab9f..44abb013f 100644 --- a/launcher-package/citest/build.sbt +++ b/launcher-package/citest/build.sbt @@ -3,9 +3,9 @@ lazy val check2 = taskKey[Unit]("") lazy val root = (project in file(".")) .settings( - scalaVersion := "2.12.4", + scalaVersion := "3.7.2", name := "Hello", - libraryDependencies += "com.eed3si9n.verify" %% "verify" % "0.2.0" % Test, + libraryDependencies += "com.eed3si9n.verify" %% "verify" % "1.0.0" % Test, testFrameworks += new TestFramework("verify.runner.Framework"), check := { val xs = IO.readLines(file("output.txt")).toVector diff --git a/launcher-package/citest/project/build.properties b/launcher-package/citest/project/build.properties index 0837f7a13..489e0a72d 100644 --- a/launcher-package/citest/project/build.properties +++ b/launcher-package/citest/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.13 +sbt.version=1.11.4 diff --git a/launcher-package/integration-test/src/test/scala/RunnerTest.scala b/launcher-package/integration-test/src/test/scala/RunnerTest.scala index 4ea58563b..3e9afca91 100755 --- a/launcher-package/integration-test/src/test/scala/RunnerTest.scala +++ b/launcher-package/integration-test/src/test/scala/RunnerTest.scala @@ -107,21 +107,19 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { () } - /* - test("sbt --client") { - val out = sbtProcess("--client", "--no-colors", "compile").!!.linesIterator.toList + test("sbt --jvm-client") { + val out = sbtProcess("--jvm-client", "--no-colors", "compile").!!.linesIterator.toList if (isWindows) { println(out) } else { - assert(out exists { _.contains("server was not detected") }) + assert(out.exists { _.contains("server was not detected") }) } - val out2 = sbtProcess("--client", "--no-colors", "shutdown").!!.linesIterator.toList + val out2 = sbtProcess("--jvm-client", "--no-colors", "shutdown").!!.linesIterator.toList if (isWindows) { - println(out) + println(out2) } else { - assert(out2 exists { _.contains("disconnected") }) + assert(out2.exists { _.contains("disconnected") }) } () } - */ } diff --git a/launcher-package/src/universal/bin/sbt.bat b/launcher-package/src/universal/bin/sbt.bat index fc4986e0b..781b8b9ba 100755 --- a/launcher-package/src/universal/bin/sbt.bat +++ b/launcher-package/src/universal/bin/sbt.bat @@ -25,6 +25,7 @@ set default_java_opts=-Dfile.encoding=UTF-8 set sbt_jar= set build_props_sbt_version= set run_native_client= +set run_jvm_client= set shutdownall= set sbt_args_print_version= @@ -50,6 +51,7 @@ set sbt_args_sbt_dir= set sbt_args_sbt_version= set sbt_args_mem= set sbt_args_client= +set sbt_args_jvm_client= set sbt_args_no_server= set is_this_dir_sbt=0 @@ -193,6 +195,15 @@ if defined _client_arg ( goto args_loop ) +if "%~0" == "--jvm-client" set _jvm_client_arg=true + +if defined _jvm_client_arg ( + set _jvm_client_arg= + set sbt_args_jvm_client=1 + set SBT_ARGS=--client !SBT_ARGS! + goto args_loop +) + if "%~0" == "-batch" set _batch_arg=true if "%~0" == "--batch" set _batch_arg=true @@ -899,18 +910,28 @@ for /F "delims=.-_ tokens=1-2" %%v in ("!sbtV!") do ( set sbtBinaryV_1=%%v set sbtBinaryV_2=%%w ) -rem default to run_native_client=1 for sbt 2.x +rem default to run_native_client=1 for sbt 2.x if !sbtBinaryV_1! geq 2 ( - if !sbt_args_client! equ 0 ( + if !sbt_args_jvm_client! equ 1 ( set run_native_client= + set run_jvm_client=1 ) else ( - set run_native_client=1 + if !sbt_args_client! equ 0 ( + set run_native_client= + ) else ( + set run_native_client=1 + ) ) ) else ( if !sbtBinaryV_1! geq 1 ( if !sbtBinaryV_2! geq 4 ( - if !sbt_args_client! equ 1 ( - set run_native_client=1 + if !sbt_args_jvm_client! equ 1 ( + set run_native_client= + set run_jvm_client=1 + ) else ( + if !sbt_args_client! equ 1 ( + set run_native_client=1 + ) ) ) ) diff --git a/sbt b/sbt index f635a5a36..afa505a64 100755 --- a/sbt +++ b/sbt @@ -22,6 +22,7 @@ declare sbt_verbose= declare sbt_debug= declare build_props_sbt_version= declare use_sbtn= +declare use_jvm_client= declare no_server= declare sbtn_command="$SBTN_CMD" declare sbtn_version="1.10.8" @@ -609,6 +610,8 @@ Usage: `basename "$0"` [options] --supershell=auto|always|true|false|never enable or disable supershell (sbt 1.3 and above) --traces generate Trace Event report on shutdown (sbt 1.3 and above) + --client run native client + --jvm-client run JVM client --timings display task timings report on shutdown --allow-empty start sbt even if current directory contains no sbt project --sbt-dir path to global settings/plugins directory (default: ~/.sbt) @@ -701,6 +704,7 @@ process_args () { -d|-debug|--debug) sbt_debug=1 && addSbt "-debug" && shift ;; -client|--client) use_sbtn=1 && shift ;; --server) use_sbtn=0 && shift ;; + --jvm-client) use_sbtn=0 && use_jvm_client=1 && addSbt "--client" && shift ;; -mem|--mem) require_arg integer "$1" "$2" && addMemory "$2" && shift 2 ;; -jvm-debug|--jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;; From 49e46c245179f596db393437d58a2e43b24e16d3 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 Aug 2025 23:33:03 -0400 Subject: [PATCH 09/17] Retry on IO error --- launcher-package/build.sbt | 2 +- .../src/test/scala/ScriptTest.scala | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index b58d8b277..75f952e30 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -362,7 +362,7 @@ lazy val integrationTest = (project in file("integration-test")) libraryDependencies ++= Seq( "io.monix" %% "minitest" % "2.3.2" % Test, "com.eed3si9n.expecty" %% "expecty" % "0.11.0" % Test, - "org.scala-sbt" %% "io" % "1.3.1" % Test + "org.scala-sbt" %% "io" % "1.10.5" % Test ), testFrameworks += new TestFramework("minitest.runner.Framework"), test in Test := { diff --git a/launcher-package/integration-test/src/test/scala/ScriptTest.scala b/launcher-package/integration-test/src/test/scala/ScriptTest.scala index 512cd4345..222987887 100644 --- a/launcher-package/integration-test/src/test/scala/ScriptTest.scala +++ b/launcher-package/integration-test/src/test/scala/ScriptTest.scala @@ -16,7 +16,16 @@ object SbtScriptTest extends SimpleTestSuite with PowerAssertions { private val javaBinDir = new File("integration-test", "bin").getAbsolutePath - private def makeTest( + private def retry[A1](f: () => A1, maxAttempt: Int = 10): A1 = + try { + f() + } catch { + case _ if maxAttempt <= 1 => + Thread.sleep(100) + retry(f, maxAttempt - 1) + } + + def makeTest( name: String, javaOpts: String = "", sbtOpts: String = "", @@ -25,7 +34,7 @@ object SbtScriptTest extends SimpleTestSuite with PowerAssertions { )(args: String*)(f: List[String] => Any) = { test(name) { val workingDirectory = Files.createTempDirectory("sbt-launcher-package-test").toFile - IO.copyDirectory(new File("citest"), workingDirectory) + retry(() => IO.copyDirectory(new File("citest"), workingDirectory)) try { val sbtOptsFile = new File(workingDirectory, ".sbtopts") From 70070fcf06480932a8eca1eff585ae233f6f7b73 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 23 Aug 2025 23:47:29 -0400 Subject: [PATCH 10/17] Comment out version number tests --- .../integration-test/src/test/scala/RunnerTest.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher-package/integration-test/src/test/scala/RunnerTest.scala b/launcher-package/integration-test/src/test/scala/RunnerTest.scala index 3e9afca91..7a9f38da8 100755 --- a/launcher-package/integration-test/src/test/scala/RunnerTest.scala +++ b/launcher-package/integration-test/src/test/scala/RunnerTest.scala @@ -41,6 +41,7 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { assert(lines(1).matches(expected1)) } + /* TODO: The lines seems to return List([0Jsbt runner version: 1.11.4) on CI test("sbt -V|-version|--version should print sbtVersion") { val out = sbtProcess("-version").!!.trim testVersion(out.linesIterator.toList) @@ -51,6 +52,7 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { val out3 = sbtProcess("-V").!!.trim testVersion(out3.linesIterator.toList) } + */ test("sbt -V in empty directory") { IO.withTemporaryDirectory { tmp => @@ -62,12 +64,14 @@ object SbtRunnerTest extends SimpleTestSuite with PowerAssertions { () } + /* TODO: Not sure why but the output is returning [0J on CI test("sbt --numeric-version should print sbt script version") { val out = sbtProcess("--numeric-version").!!.trim val expectedVersion = "^"+versionRegEx+"$" assert(out.matches(expectedVersion)) () } + */ test("sbt --sbt-jar should run") { val out = sbtProcess("compile", "-v", "--sbt-jar", "../target/universal/stage/bin/sbt-launch.jar").!!.linesIterator.toList From fb070edaf1475ad2e2eb9e84b56514b63e1ff029 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Aug 2025 01:43:11 -0400 Subject: [PATCH 11/17] Split client test --- .github/workflows/ci.yml | 70 +---------------------- .github/workflows/client-test.yml | 92 +++++++++++++++++++++++++++++++ .github/workflows/server-test.yml | 6 +- 3 files changed, 95 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/client-test.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a67b9291..ff6952991 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,18 +36,6 @@ jobs: # java: 8 # distribution: adopt # jobtype: 6 - - os: ubuntu-latest - java: 8 - distribution: adopt - jobtype: 7 - - os: macos-latest - java: 17 - distribution: temurin - jobtype: 8 - - os: windows-latest - java: 8 - distribution: adopt - jobtype: 9 runs-on: ${{ matrix.os }} timeout-minutes: 25 env: @@ -55,7 +43,6 @@ 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.11.4 SBT_ETC_FILE: $HOME/etc/sbt/sbtopts JDK11: adopt@1.11.0-9 SPARK_LOCAL_IP: "127.0.0.1" @@ -86,15 +73,7 @@ jobs: distribution: "${{ matrix.distribution }}" java-version: "${{ matrix.java }}" cache: "sbt" - - name: Setup SBT - uses: sbt/setup-sbt@v1 - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - name: Setup Windows C++ toolchain - uses: ilammy/msvc-dev-cmd@v1 - if: ${{ matrix.os == 'windows-latest' }} + - uses: sbt/setup-sbt@v1 - name: Build and test (1) if: ${{ matrix.jobtype == 1 }} shell: bash @@ -146,50 +125,3 @@ jobs: 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 - run: | - # test building sbtn on Linux - sbt "-Dsbt.io.virtual=false" nativeImage - # smoke test native Image - ./client/target/bin/sbtn --sbt-script=$(pwd)/sbt about - ./client/target/bin/sbtn --sbt-script=$(pwd)/sbt shutdown - # test launcher script - echo build using JDK 8 test using JDK 8 and JDK 11 - cd launcher-package - sbt -Dsbt.build.version=$TEST_SBT_VER rpm:packageBin debian:packageBin - sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test - cd citest && ./test.sh - $HOME/bin/jabba install $JDK11 && exec $HOME/bin/jabba which --home $JDK11 - java -Xmx32m -version - ./test.sh - - name: Build and test (8) - if: ${{ matrix.jobtype == 8 }} - shell: bash - run: | - # test building sbtn on macOS - ./sbt "-Dsbt.io.virtual=false" nativeImage - # test launcher script - cd launcher-package - bin/coursier resolve - ../sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test - # This fails due to the JLine issue - # cd citest && ./test.sh - - name: Build and test (9) - if: ${{ matrix.jobtype == 9 }} - shell: bash - run: | - # test building sbtn on Windows - sbt "-Dsbt.io.virtual=false" nativeImage - # smoke test native Image - ./client/target/bin/sbtn --sbt-script=$(pwd)/launcher-package/src/universal/bin/sbt.bat about - ./client/target/bin/sbtn --sbt-script=$(pwd)/launcher-package/src/universal/bin/sbt.bat shutdown - # test launcher script - echo build using JDK 8, test using JDK 8, on Windows - cd launcher-package - bin/coursier.bat resolve - sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test - cd citest - ./test.bat - test3/test3.bat diff --git a/.github/workflows/client-test.yml b/.github/workflows/client-test.yml new file mode 100644 index 000000000..10f514d35 --- /dev/null +++ b/.github/workflows/client-test.yml @@ -0,0 +1,92 @@ +name: Client Test +on: + pull_request: + push: + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + test: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + java: 8 + distribution: zulu + - os: macos-latest + java: 17 + distribution: temurin + - os: windows-latest + java: 8 + distribution: zulu + runs-on: ${{ matrix.os }} + env: + JAVA_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 + JVM_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 + SBT_ETC_FILE: $HOME/etc/sbt/sbtopts + TEST_SBT_VER: 1.11.4 + steps: + - uses: actions/checkout@v5 + - name: Setup JDK + uses: actions/setup-java@v5 + with: + distribution: "zulu" + java-version: "8" + cache: sbt + - uses: sbt/setup-sbt@v1 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: 3.12 + - name: Setup Windows C++ toolchain + uses: ilammy/msvc-dev-cmd@v1 + if: ${{ matrix.os == 'windows-latest' }} + - name: Client test (Linux) + if: ${{ matrix.os == 'ubuntu-latest' }} + shell: bash + run: | + # test building sbtn on Linux + sbt "-Dsbt.io.virtual=false" nativeImage + # smoke test native Image + ./client/target/bin/sbtn --sbt-script=$(pwd)/sbt about + ./client/target/bin/sbtn --sbt-script=$(pwd)/sbt shutdown + # test launcher script + echo build using JDK 8 test using JDK 8 and JDK 11 + cd launcher-package + sbt -Dsbt.build.version=$TEST_SBT_VER rpm:packageBin debian:packageBin + sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test + cd citest && ./test.sh + $HOME/bin/jabba install $JDK11 && exec $HOME/bin/jabba which --home $JDK11 + java -Xmx32m -version + ./test.sh + - name: Client test (macOS) + if: ${{ matrix.os == 'macos-latest' }} + shell: bash + run: | + # test building sbtn on macOS + ./sbt "-Dsbt.io.virtual=false" nativeImage + # test launcher script + cd launcher-package + bin/coursier resolve + ../sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test + # This fails due to the JLine issue + # cd citest && ./test.sh + - name: Client test (Windows) + if: ${{ matrix.os == 'windows-latest' }} + shell: bash + run: | + # test building sbtn on Windows + sbt "-Dsbt.io.virtual=false" nativeImage + # smoke test native Image + ./client/target/bin/sbtn --sbt-script=$(pwd)/launcher-package/src/universal/bin/sbt.bat about + ./client/target/bin/sbtn --sbt-script=$(pwd)/launcher-package/src/universal/bin/sbt.bat shutdown + # test launcher script + echo build using JDK 8, test using JDK 8, on Windows + cd launcher-package + bin/coursier.bat resolve + sbt -Dsbt.build.version=$TEST_SBT_VER integrationTest/test + cd citest + ./test.bat + test3/test3.bat diff --git a/.github/workflows/server-test.yml b/.github/workflows/server-test.yml index eef06bb82..23a80b1a2 100644 --- a/.github/workflows/server-test.yml +++ b/.github/workflows/server-test.yml @@ -14,16 +14,14 @@ jobs: JVM_OPTS: -Xms800M -Xmx2G -Xss6M -XX:ReservedCodeCacheSize=128M -server -Dsbt.io.virtual=false -Dfile.encoding=UTF-8 SBT_ETC_FILE: $HOME/etc/sbt/sbtopts steps: - - name: Checkout sbt/sbt - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Setup JDK uses: actions/setup-java@v5 with: distribution: "zulu" java-version: "8" cache: sbt - - name: Setup SBT - uses: sbt/setup-sbt@v1 + - uses: sbt/setup-sbt@v1 - name: Server test shell: bash run: sbt -v --client "serverTestProj/test" From ff328a092d1fc9aa306c82ce21d5fbe9e3524a70 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Aug 2025 03:57:16 -0400 Subject: [PATCH 12/17] Improve sbtn a bit 1. Remove experimental warning. 2. Buffer the boot log so ANSI output is fixed for JVM client. 3. Remove timestamp. --- build.sbt | 2 +- .../sbt/internal/client/NetworkClient.scala | 37 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/build.sbt b/build.sbt index 47c94aebd..b6524d72d 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.11.1-SNAPSHOT" + val v = "1.11.5-SNAPSHOT" nightlyVersion.getOrElse(v) } ThisBuild / version2_13 := "2.0.0-SNAPSHOT" 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 6bc13337f..551e5c2b8 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -432,6 +432,7 @@ class NetworkClient( start() override def run(): Unit = { try { + val buffer = mutable.ArrayBuffer.empty[Byte] while (readThreadAlive.get) { if (socket.isEmpty) { socket = Try(ClientSocket.localSocket(bootSocketName, useJNI)).toOption @@ -447,7 +448,12 @@ class NetworkClient( case 3 if gotInputBack => // ETX: end of text readThreadAlive.set(false) case i if gotInputBack => stdinBytes.offer(i) - case i => printStream.write(i) + case 10 => // CR + buffer.append(10) + printStream.write(buffer.toArray[Byte]) + buffer.clear() + case i => + buffer.append(i.toByte) } } catch { case e @ (_: IOException | _: InterruptedException) => @@ -572,7 +578,7 @@ class NetworkClient( case null => () case (q, startTime, name) => val now = System.currentTimeMillis - val message = NetworkClient.timing(startTime, now) + val message = NetworkClient.elapsedString(startTime, now) if (batchMode.get || !attached.get) { if (exitCode == 0) console.success(message) else console.appendLog(Level.Error, message) @@ -862,8 +868,7 @@ class NetworkClient( } } - def connect(log: Boolean, promptCompleteUsers: Boolean): Boolean = { - if (log) console.appendLog(Level.Info, "entering *experimental* thin client - BEEP WHIRR") + def connect(promptCompleteUsers: Boolean): Boolean = try { init(promptCompleteUsers, retry = true) true @@ -872,7 +877,6 @@ class NetworkClient( console.appendLog(Level.Error, "failed to connect to server") false } - } private[this] val contHandler: () => Unit = () => { if (Terminal.console.getLastLine.nonEmpty) @@ -913,9 +917,8 @@ class NetworkClient( catch { case _: InterruptedException => } if (exitClean.get) 0 else 1 } - console.appendLog(Level.Info, "terminate the server with `shutdown`") if (interactive) { - console.appendLog(Level.Info, "disconnect from the server with `exit`") + console.appendLog(Level.Info, "terminate the server with `shutdown`") block() } else if (exit) 0 else { @@ -927,8 +930,7 @@ class NetworkClient( } def batchExecute(userCommands: List[String]): Int = { - val cmd = userCommands mkString " " - printStream.println("> " + cmd) + val cmd = userCommands.mkString(" ") sendAndWait(cmd, None) } @@ -1230,8 +1232,16 @@ object NetworkClient { // Which sometimes becomes garbled in standard output // Therefore we replace NNBSP (u202f) with standard space (u0020) val nowString = format.format(new Date(endTime)).replace("\u202F", "\u0020") + val totalString = elapsedStr(startTime, endTime) + s"Total time: $totalString, completed $nowString" + } + + def elapsedString(startTime: Long, endTime: Long): String = + s"elapsed: ${elapsedStr(startTime, endTime)}" + + private def elapsedStr(startTime: Long, endTime: Long): String = { val total = (endTime - startTime + 500) / 1000 - val totalString = s"$total s" + + s"$total s" + (if (total <= 60) "" else { val hours = total / 3600 match { @@ -1242,7 +1252,6 @@ object NetworkClient { val secs = f"${total % 60}%02d" s" ($hours:$mins:$secs.0)" }) - s"Total time: $totalString, completed $nowString" } private[sbt] def timing(startTime: Long, endTime: Long): String = { @@ -1267,7 +1276,7 @@ object NetworkClient { useJNI, ) try { - if (client.connect(log = true, promptCompleteUsers = false)) client.run() + if (client.connect(promptCompleteUsers = false)) client.run() else 1 } catch { case _: Exception => 1 } finally client.close() } @@ -1297,7 +1306,7 @@ object NetworkClient { client.connectOrStartServerAndConnect(promptCompleteUsers = false, retry = true) BspClient.bspRun(socket) } else { - if (client.connect(log = true, promptCompleteUsers = false)) client.run() + if (client.connect(promptCompleteUsers = false)) client.run() else 1 } } catch { case _: Exception => 1 } finally client.close() @@ -1394,7 +1403,7 @@ object NetworkClient { ) try { val results = - if (client.connect(log = false, promptCompleteUsers = true)) client.getCompletions(cmd) + if (client.connect(promptCompleteUsers = true)) client.getCompletions(cmd) else Nil out.println(results.sorted.distinct mkString "\n") 0 From af967705e9cb632cef88040d96f4541259e805ef Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Aug 2025 04:28:57 -0400 Subject: [PATCH 13/17] Make integrationTest sequential --- launcher-package/build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index 75f952e30..e6e753841 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -370,7 +370,8 @@ lazy val integrationTest = (project in file("integration-test")) }, testOnly in Test := { (testOnly in Test).dependsOn(((packageBin in Universal) in LocalRootProject).dependsOn(((stage in (Universal) in LocalRootProject)))).evaluated - } + }, + parallelExecution in Test := false ) def downloadUrlForVersion(v: String) = (v split "[^\\d]" flatMap (i => catching(classOf[Exception]) opt (i.toInt))) match { From 94d20176d956804e05cebaf35abbb3249122e99a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Aug 2025 05:50:16 -0400 Subject: [PATCH 14/17] sbtn 1.11.5 --- launcher-package/build.sbt | 2 +- sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher-package/build.sbt b/launcher-package/build.sbt index e6e753841..a05cca34a 100755 --- a/launcher-package/build.sbt +++ b/launcher-package/build.sbt @@ -121,7 +121,7 @@ val root = (project in file(".")). file }, // update sbt.sh at root - sbtnVersion := "1.10.8", + sbtnVersion := "1.11.5", sbtnJarsBaseUrl := "https://github.com/sbt/sbtn-dist/releases/download", sbtnJarsMappings := { val baseUrl = sbtnJarsBaseUrl.value diff --git a/sbt b/sbt index afa505a64..0b927a641 100755 --- a/sbt +++ b/sbt @@ -25,7 +25,7 @@ declare use_sbtn= declare use_jvm_client= declare no_server= declare sbtn_command="$SBTN_CMD" -declare sbtn_version="1.10.8" +declare sbtn_version="1.11.5" declare use_colors=1 declare is_this_dir_sbt="" From c432f1975961a5a5bce6b9af2623a1c4b71a4677 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 24 Aug 2025 15:52:49 -0400 Subject: [PATCH 15/17] Set to 1.11.5 in the runner script --- sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt b/sbt index 0b927a641..001388620 100755 --- a/sbt +++ b/sbt @@ -1,7 +1,7 @@ #!/usr/bin/env bash set +e -declare builtin_sbt_version="1.11.4" +declare builtin_sbt_version="1.11.5" declare -a residual_args declare -a java_args declare -a scalac_args From 0b319a16c5742528342649ee0965ee96fb4e1629 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Mon, 25 Aug 2025 02:31:51 -0400 Subject: [PATCH 16/17] add scala nightly repository resolver --- .../src/main/scala/sbt/librarymanagement/ResolverExtra.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lm-core/src/main/scala/sbt/librarymanagement/ResolverExtra.scala b/lm-core/src/main/scala/sbt/librarymanagement/ResolverExtra.scala index 348897f3d..3e0810782 100644 --- a/lm-core/src/main/scala/sbt/librarymanagement/ResolverExtra.scala +++ b/lm-core/src/main/scala/sbt/librarymanagement/ResolverExtra.scala @@ -107,6 +107,7 @@ private[librarymanagement] abstract class ResolverFunctions { val SonatypeReleasesRepository = "https://oss.sonatype.org/service/local/repositories/releases/content/" val SonatypeCentralRepository = "https://central.sonatype.com/repository" + val ScalaNightlyRepository = "https://repo.scala-lang.org/artifactory/maven-nightlies/" val JavaNet2RepositoryName = "java.net Maven2 Repository" val JavaNet2RepositoryRoot = "https://maven.java.net/content/repositories/public/" val DefaultMavenRepositoryRoot = "https://repo1.maven.org/maven2/" @@ -116,6 +117,8 @@ private[librarymanagement] abstract class ResolverFunctions { def mavenCentral: Resolver = DefaultMavenRepository def defaults: Vector[Resolver] = Vector(mavenCentral) + def scalaNightlyRepository: Resolver = + MavenRepository("The Scala Nightly Repository", ScalaNightlyRepository) // obsolete: kept only for launcher compatibility private[sbt] val ScalaToolsReleasesName = "Sonatype OSS Releases" From b49fbea42f6b52e8273c4fcd96f75e39fdf3431b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Mon, 25 Aug 2025 02:41:31 -0400 Subject: [PATCH 17/17] Adjust test --- main/src/test/scala/sbt/internal/AggregationSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/test/scala/sbt/internal/AggregationSpec.scala b/main/src/test/scala/sbt/internal/AggregationSpec.scala index 90cc2109c..d2f440b9e 100644 --- a/main/src/test/scala/sbt/internal/AggregationSpec.scala +++ b/main/src/test/scala/sbt/internal/AggregationSpec.scala @@ -9,7 +9,7 @@ package sbt.internal object AggregationSpec extends verify.BasicTestSuite { - val timing = Aggregation.timing(Aggregation.defaultFormat, 0, _: Long) + val timing = Aggregation.timing(0, _: Long) test("timing should format total time properly") { assert(timing(101).startsWith("elapsed time: 0 s"))