mirror of https://github.com/sbt/sbt.git
Merge branch 'develop' into fix/5647-dynamic-all-lint-unused
This commit is contained in:
commit
5666b2b2d1
|
|
@ -4,6 +4,20 @@ package example.test
|
|||
* RunnerScriptTest is used to test the sbt shell script, for both macOS/Linux and Windows.
|
||||
*/
|
||||
object RunnerScriptTest extends verify.BasicTestSuite with ShellScriptUtil:
|
||||
private val versionPattern = "\\d(\\.\\d+){2}(-\\w+)?"
|
||||
|
||||
private def assertScriptVersion(out: List[String]): Unit =
|
||||
assert(out.mkString(System.lineSeparator()).trim.matches("^" + versionPattern + "$"))
|
||||
|
||||
private def assertVersionOutput(out: List[String]): Unit =
|
||||
val lines =
|
||||
out.mkString(System.lineSeparator()).linesIterator.map(_.stripPrefix("[0J").trim).toList
|
||||
assert(
|
||||
lines.exists(_.matches("^sbt version in this project: " + versionPattern + "\\r?$")) ||
|
||||
lines.contains("sbtVersion")
|
||||
)
|
||||
assert(lines.exists(_.matches("^sbt runner version: " + versionPattern + "\\r?$")))
|
||||
assert(!lines.exists(_.contains("failed to connect to server")))
|
||||
|
||||
testOutput("sbt -no-colors")("compile", "-no-colors", "-v"): (out: List[String]) =>
|
||||
assert(out.contains[String]("-Dsbt.log.noformat=true"))
|
||||
|
|
@ -117,16 +131,28 @@ object RunnerScriptTest extends verify.BasicTestSuite with ShellScriptUtil:
|
|||
"sbt --script-version should print sbtVersion (sbt 1.x project)",
|
||||
citestVariant = "citest",
|
||||
)("--script-version"): (out: List[String]) =>
|
||||
val expectedVersion = "^" + ExtendedRunnerTest.versionRegEx + "$"
|
||||
assert(out.mkString(System.lineSeparator()).trim.matches(expectedVersion))
|
||||
assertScriptVersion(out)
|
||||
()
|
||||
|
||||
testOutput(
|
||||
"sbt --script-version should print sbtVersion (sbt 2.x project)",
|
||||
citestVariant = "citest2",
|
||||
)("--script-version"): (out: List[String]) =>
|
||||
val expectedVersion = "^" + ExtendedRunnerTest.versionRegEx + "$"
|
||||
assert(out.mkString(System.lineSeparator()).trim.matches(expectedVersion))
|
||||
assertScriptVersion(out)
|
||||
()
|
||||
|
||||
testOutput(
|
||||
"sbt --version should work (sbt 1.x project)",
|
||||
citestVariant = "citest",
|
||||
)("--version"): (out: List[String]) =>
|
||||
assertVersionOutput(out)
|
||||
()
|
||||
|
||||
testOutput(
|
||||
"sbt --version should work (sbt 2.x project)",
|
||||
citestVariant = "citest2",
|
||||
)("--version"): (out: List[String]) =>
|
||||
assertVersionOutput(out)
|
||||
()
|
||||
|
||||
testOutput("--sbt-cache")("--sbt-cache", "./cachePath"): (out: List[String]) =>
|
||||
|
|
@ -227,6 +253,30 @@ object RunnerScriptTest extends verify.BasicTestSuite with ShellScriptUtil:
|
|||
s"sbtopts options should appear before CLI memory settings. g1Index=$g1Index, xmxCliIndex=$xmxCliIndex"
|
||||
)
|
||||
|
||||
// Test for issue #7333: JVM parameters with spaces in .sbtopts
|
||||
testOutput(
|
||||
"sbt with -J--add-modules jdk.incubator.concurrent in .sbtopts (args with spaces)",
|
||||
sbtOptsFileContents = "-J--add-modules jdk.incubator.concurrent",
|
||||
windowsSupport = false,
|
||||
)("-v"): (out: List[String]) =>
|
||||
assert(out.contains[String]("--add-modules"))
|
||||
assert(out.contains[String]("jdk.incubator.concurrent"))
|
||||
|
||||
// Test for issue #7333: -D with spaces in .jvmopts
|
||||
testOutput(
|
||||
"sbt with -Dkey=\"value with spaces\" in .jvmopts",
|
||||
jvmoptsFileContents = """-Dtest.7333="value with spaces"""",
|
||||
windowsSupport = false,
|
||||
)("-v"): (out: List[String]) =>
|
||||
assert(
|
||||
out.exists(_.contains("test.7333")),
|
||||
s"Expected -Dtest.7333= in output, got: ${out.filter(_.contains("test.7333")).mkString(", ")}"
|
||||
)
|
||||
assert(
|
||||
out.exists(_.contains("value with spaces")),
|
||||
"Expected 'value with spaces' in -D value"
|
||||
)
|
||||
|
||||
// Test for issue #7289: Special characters in .jvmopts should not cause shell expansion
|
||||
testOutput(
|
||||
"sbt with special characters in .jvmopts (pipes, wildcards, ampersands)",
|
||||
|
|
|
|||
|
|
@ -617,7 +617,7 @@ if !sbt_args_print_sbt_script_version! equ 1 (
|
|||
goto :eof
|
||||
)
|
||||
|
||||
if !run_native_client! equ 1 (
|
||||
if !run_native_client! equ 1 if not defined sbt_args_print_version (
|
||||
goto :runnative !SBT_ARGS!
|
||||
goto :eof
|
||||
)
|
||||
|
|
@ -1122,8 +1122,16 @@ echo.
|
|||
exit /B 1
|
||||
|
||||
:set_sbt_version
|
||||
rem set project sbtVersion
|
||||
for /F "usebackq tokens=2" %%G in (`CALL "!_JAVACMD!" -jar "!sbt_jar!" "sbtVersion" 2^>^&1`) do set "sbt_version=%%G"
|
||||
set "sbt_version="
|
||||
for /F "usebackq tokens=1,2 delims= " %%a in (`CALL "!_JAVACMD!" -jar "!sbt_jar!" "sbtVersion" 2^>^&1`) do (
|
||||
if "%%a" == "[info]" (
|
||||
set "_version_candidate=%%b"
|
||||
) else (
|
||||
set "_version_candidate=%%a"
|
||||
)
|
||||
echo !_version_candidate!| findstr /R "^[0-9][0-9.]*[-+0-9A-Za-z._]*$" >nul && set "sbt_version=!_version_candidate!"
|
||||
)
|
||||
if not defined sbt_version if defined build_props_sbt_version set "sbt_version=!build_props_sbt_version!"
|
||||
exit /B 0
|
||||
|
||||
:error
|
||||
|
|
|
|||
|
|
@ -133,6 +133,9 @@ object Tests {
|
|||
/** Test execution will be ordered by the position of the matching filter. */
|
||||
final case class Filters(filterTest: Seq[String => Boolean]) extends TestOption
|
||||
|
||||
/** Names explicitly requested (e.g. testOnly com.example.MySuite). Used to set explicitlySpecified on TaskDef. */
|
||||
final case class ExplicitlyRequestedNames(names: Seq[String]) extends TestOption
|
||||
|
||||
/** Defines a TestOption that passes arguments `args` to all test frameworks. */
|
||||
def Argument(args: String*): Argument = Argument(None, args.toList)
|
||||
|
||||
|
|
@ -247,10 +250,14 @@ object Tests {
|
|||
val testFilters = new ListBuffer[String => Boolean]
|
||||
var orderedFilters = Seq[String => Boolean]()
|
||||
val excludeTestsSet = new HashSet[String]
|
||||
var explicitlyRequestedNames = Set.empty[String]
|
||||
val setup, cleanup = new ListBuffer[ClassLoader => Unit]
|
||||
val testListeners = new ListBuffer[TestReportListener]
|
||||
val undefinedFrameworks = new ListBuffer[String]
|
||||
|
||||
def isExplicitFqn(s: String): Boolean =
|
||||
!s.contains('*') && !s.contains('?') && !s.contains("...")
|
||||
|
||||
for (option <- config.options) {
|
||||
option match {
|
||||
case Filter(include) => testFilters += include; ()
|
||||
|
|
@ -258,6 +265,9 @@ object Tests {
|
|||
if (orderedFilters.nonEmpty) sys.error("Cannot define multiple ordered test filters.")
|
||||
else orderedFilters = includes
|
||||
()
|
||||
case ExplicitlyRequestedNames(names) =>
|
||||
explicitlyRequestedNames = names.filter(isExplicitFqn).toSet
|
||||
()
|
||||
case Exclude(exclude) => excludeTestsSet ++= exclude; ()
|
||||
case Listeners(listeners) => testListeners ++= listeners; ()
|
||||
case Setup(setupFunction, _) => setup += setupFunction; ()
|
||||
|
|
@ -281,8 +291,18 @@ object Tests {
|
|||
if (orderedFilters.isEmpty) filtered0
|
||||
else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct
|
||||
val uniqueTests = distinctBy(tests)(_.name)
|
||||
// Per TaskDef: explicitlySpecified=true only when user supplied a complete FQN (e.g. testOnly com.example.MySuite),
|
||||
// not for patterns (testOnly *Spec) or plain "test". So only mark when test.name is in explicitlyRequestedNames.
|
||||
val testsToUse = uniqueTests.map(t =>
|
||||
new TestDefinition(
|
||||
t.name,
|
||||
t.fingerprint,
|
||||
explicitlySpecified = explicitlyRequestedNames.contains(t.name),
|
||||
t.selectors
|
||||
)
|
||||
)
|
||||
new ProcessedOptions(
|
||||
uniqueTests.toVector,
|
||||
testsToUse.toVector,
|
||||
setup.toVector,
|
||||
cleanup.toVector,
|
||||
testListeners.toVector
|
||||
|
|
@ -555,7 +575,6 @@ object Tests {
|
|||
c.topLevel
|
||||
case _ => false
|
||||
})
|
||||
// TODO: To pass in correct explicitlySpecified and selectors
|
||||
val tests =
|
||||
for {
|
||||
(df, di) <- discovered
|
||||
|
|
|
|||
|
|
@ -1354,7 +1354,8 @@ object Defaults extends BuildCommon {
|
|||
val st = state.value
|
||||
given display: Show[ScopedKey[?]] = Project.showContextKey(st)
|
||||
val modifiedOpts =
|
||||
Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions*) +: config.options
|
||||
Tests.ExplicitlyRequestedNames(selected) +: Tests.Filters(filter(selected)) +:
|
||||
Tests.Argument(frameworkOptions*) +: config.options
|
||||
val newConfig = config.copy(options = modifiedOpts)
|
||||
val output = allTestGroupsTask(
|
||||
s,
|
||||
|
|
@ -3633,6 +3634,7 @@ object Classpaths {
|
|||
.withArtifactFilter(
|
||||
updateConfig0.artifactFilter.map(af => af.withInverted(!af.inverted))
|
||||
)
|
||||
.withMissingOk(true)
|
||||
val app = appConfiguration.value
|
||||
val srcTypes = sourceArtifactTypes.value
|
||||
val docTypes = docArtifactTypes.value
|
||||
|
|
@ -3718,11 +3720,14 @@ object Classpaths {
|
|||
val ref = thisProjectRef.value
|
||||
val unit = loadedBuild.value.units(ref.build).unit
|
||||
val converter = unit.converter
|
||||
val pluginClasspath = unit.plugins.pluginData.dependencyClasspath.toVector
|
||||
val pluginData = unit.plugins.pluginData
|
||||
val pluginInternalCp = pluginData.internalDependencyClasspath.toVector
|
||||
val pluginClasspath = pluginData.dependencyClasspath.toVector
|
||||
val cp = pluginClasspath.diff(pluginInternalCp)
|
||||
// Exclude directories: an approximation to whether they've been published
|
||||
// Note: it might be a redundant legacy from sbt 0.13/1.x times where the classpath contained directories
|
||||
// but it's left just in case
|
||||
val pluginJars = pluginClasspath.filter: x =>
|
||||
val pluginJars = cp.filter: x =>
|
||||
!Files.isDirectory(converter.toPath(x.data))
|
||||
val pluginIDs: Vector[ModuleID] = pluginJars.flatMap(_.get(moduleIDStr).map: str =>
|
||||
moduleIdJsonKeyFormat.read(str))
|
||||
|
|
|
|||
|
|
@ -142,6 +142,10 @@ object EvaluateTaskConfig {
|
|||
) extends EvaluateTaskConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* @param internalDependencyClasspath internal classpath entries from the metabuild that are used to exclude
|
||||
* them when resolving/retrieving classifiers for sbt.
|
||||
*/
|
||||
final case class PluginData(
|
||||
dependencyClasspath: Def.Classpath,
|
||||
definitionClasspath: Def.Classpath,
|
||||
|
|
@ -155,13 +159,28 @@ final case class PluginData(
|
|||
managedSources: Seq[File],
|
||||
buildTarget: Option[BuildTargetIdentifier],
|
||||
converter: FileConverter,
|
||||
internalDependencyClasspath: Def.Classpath,
|
||||
) {
|
||||
val classpath: Def.Classpath = definitionClasspath ++ dependencyClasspath
|
||||
}
|
||||
|
||||
object PluginData {
|
||||
private[sbt] def apply(dependencyClasspath: Def.Classpath, converter: FileConverter): PluginData =
|
||||
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil, Nil, None, converter)
|
||||
PluginData(
|
||||
dependencyClasspath,
|
||||
Nil,
|
||||
None,
|
||||
None,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
None,
|
||||
converter,
|
||||
Nil
|
||||
)
|
||||
}
|
||||
|
||||
object EvaluateTask {
|
||||
|
|
|
|||
|
|
@ -172,6 +172,11 @@ private[sbt] object LibraryManagement {
|
|||
.apply(updateInputs)
|
||||
if isCached then markAsCached(report) else report
|
||||
catch
|
||||
case r: ResolveException
|
||||
if r.failed.exists(isMissingScalaLib) &&
|
||||
module.scalaModuleInfo.exists(_.scalaBinaryVersion == "2.13") =>
|
||||
informSandwich()
|
||||
throw r
|
||||
case t: (NullPointerException | OutOfMemoryError) =>
|
||||
val resolvedAgain = resolve
|
||||
val culprit = t.getClass.getSimpleName
|
||||
|
|
@ -185,6 +190,26 @@ private[sbt] object LibraryManagement {
|
|||
Tracked.inputChanged(cacheStoreFactory.make("inputs"))(doCachedResolve)
|
||||
}
|
||||
|
||||
def informSandwich(): Unit =
|
||||
log.warn("[sbt-8728] Smorrebrod - the end of Scala 2.13-3.x sandwich")
|
||||
log.warn("")
|
||||
log.warn("Scala 3.8+ cannot be used in a Scala 2.13 subproject.")
|
||||
log.warn(
|
||||
"Dependency resolution failed because scala-reflect or -compiler 3.x does not exist."
|
||||
)
|
||||
log.warn(
|
||||
"This happens when a Scala 2.13 subproject depends on Scala 3.8+ directly or transitively."
|
||||
)
|
||||
log.warn("To fix this, either")
|
||||
log.warn(" - Keep Scala 3 subproject or transitive dependency to 3.7 or below, or")
|
||||
log.warn(" - Migrate the Scala 2.13 subproject to Scala 3.x")
|
||||
log.warn("See https://github.com/sbt/sbt/discussions/8728")
|
||||
|
||||
def isMissingScalaLib(m: ModuleID): Boolean =
|
||||
m.organization == "org.scala-lang" &&
|
||||
(m.name == "scala-compiler" || m.name == "scala-reflect") &&
|
||||
(m.revision.startsWith("3."))
|
||||
|
||||
// Get the handler to use and feed it in the inputs
|
||||
// This is lm-engine specific input hashed into Long
|
||||
val extraInputHash = module.extraInputHash
|
||||
|
|
|
|||
|
|
@ -1381,6 +1381,7 @@ private[sbt] object Load {
|
|||
isMetaBuild :== true,
|
||||
pluginData := Def.uncached {
|
||||
val prod = (Configurations.Runtime / exportedProducts).value
|
||||
val internalCp = (Configurations.Runtime / internalDependencyClasspath).value
|
||||
val cp = (Configurations.Runtime / fullClasspath).value
|
||||
val opts = (Configurations.Compile / scalacOptions).value
|
||||
val javaOpts = (Configurations.Compile / javacOptions).value
|
||||
|
|
@ -1403,6 +1404,7 @@ private[sbt] object Load {
|
|||
managedSrcs,
|
||||
Some(buildTarget),
|
||||
converter,
|
||||
internalCp,
|
||||
)
|
||||
},
|
||||
onLoadMessage := ("loading project definition from " + baseDirectory.value)
|
||||
|
|
@ -1670,6 +1672,7 @@ final case class LoadBuildConfiguration(
|
|||
Nil,
|
||||
None,
|
||||
converter,
|
||||
Nil
|
||||
)
|
||||
case None => PluginData(globalPluginClasspath, converter)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import Aggregation.{ KeyValue, Values }
|
|||
import Types.idFun
|
||||
import Highlight.{ bold, showMatches }
|
||||
import annotation.tailrec
|
||||
import sbt.internal.util.EscHelpers
|
||||
|
||||
import sbt.io.IO
|
||||
|
||||
|
|
@ -43,7 +44,12 @@ object Output {
|
|||
printLines: Seq[String] => Unit
|
||||
)(using display: Show[ScopedKey[?]]): Unit = {
|
||||
val pattern = Pattern.compile(patternString)
|
||||
val lines = flatLines(lastLines(keys, streams))(_ flatMap showMatches(pattern))
|
||||
val lines = flatLines(lastLines(keys, streams)) { rawLines =>
|
||||
rawLines.flatMap { line =>
|
||||
val stripped = EscHelpers.stripColorsAndMoves(line)
|
||||
showMatches(pattern)(stripped)
|
||||
}
|
||||
}
|
||||
printLines(lines)
|
||||
}
|
||||
|
||||
|
|
@ -55,8 +61,13 @@ object Output {
|
|||
): Unit =
|
||||
printLines(grep(tailLines(file, tailDelim), patternString))
|
||||
|
||||
def grep(lines: Seq[String], patternString: String): Seq[String] =
|
||||
lines.flatMap(showMatches(Pattern.compile(patternString)))
|
||||
def grep(lines: Seq[String], patternString: String): Seq[String] = {
|
||||
val pattern = Pattern.compile(patternString)
|
||||
lines.flatMap { line =>
|
||||
val stripped = EscHelpers.stripColorsAndMoves(line)
|
||||
showMatches(pattern)(stripped)
|
||||
}
|
||||
}
|
||||
|
||||
def flatLines(outputs: Values[Seq[String]])(f: Seq[String] => Seq[String])(using
|
||||
display: Show[ScopedKey[?]]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2023, Scala center
|
||||
* Copyright 2011 - 2022, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal
|
||||
|
||||
import scala.Console.{ RED, RESET }
|
||||
import verify.BasicTestSuite
|
||||
import sbt.internal.Output.grep
|
||||
|
||||
object OutputSpec extends BasicTestSuite {
|
||||
|
||||
test(
|
||||
"grep should match pattern against visible text when lines contain ANSI escape sequences (#4840)"
|
||||
) {
|
||||
// Line with ANSI color around "error" - user searching for "error" should find it (strip before match)
|
||||
val lineWithAnsi = s"${RED}error${RESET}: something failed"
|
||||
val lines = Seq("info: ok", lineWithAnsi, "warn: deprecated")
|
||||
val result = grep(lines, "error")
|
||||
assert(result.size == 1, s"expected 1 match, got ${result.size}: $result")
|
||||
// Pattern matched the visible "error" (ANSI was stripped before matching); result may have highlight from showMatches
|
||||
assert(result.head.contains("error"), s"result should contain 'error': ${result.head}")
|
||||
}
|
||||
|
||||
test("grep should not match when pattern appears only inside ANSI sequence") {
|
||||
// Line where "error" is not in the visible text (only in escape code - unrealistic but ensures we strip first)
|
||||
val lines = Seq("info: ok", "something failed")
|
||||
val result = grep(lines, "error")
|
||||
assert(result.isEmpty, s"expected no match, got: $result")
|
||||
}
|
||||
}
|
||||
67
sbt
67
sbt
|
|
@ -766,6 +766,60 @@ process_args () {
|
|||
}
|
||||
}
|
||||
|
||||
# Parse a line into words respecting double and single quotes.
|
||||
# Outputs one word per line. Used for .sbtopts and .jvmopts to handle args with spaces (#7333).
|
||||
parseLineIntoWords() {
|
||||
local line="$1"
|
||||
local word=""
|
||||
local i=0
|
||||
local len=${#line}
|
||||
local in_dq=0 in_sq=0
|
||||
while (( i < len )); do
|
||||
local c="${line:$i:1}"
|
||||
if (( in_dq )); then
|
||||
word+="$c"
|
||||
[[ "$c" == '"' ]] && in_dq=0
|
||||
elif (( in_sq )); then
|
||||
word+="$c"
|
||||
[[ "$c" == "'" ]] && in_sq=0
|
||||
else
|
||||
case "$c" in
|
||||
'"') in_dq=1; word+="$c" ;;
|
||||
"'") in_sq=1; word+="$c" ;;
|
||||
' '|$'\t')
|
||||
[[ -n "$word" ]] && printf '%s\n' "$word"
|
||||
word=""
|
||||
;;
|
||||
*) word+="$c" ;;
|
||||
esac
|
||||
fi
|
||||
((i++))
|
||||
done
|
||||
[[ -n "$word" ]] && printf '%s\n' "$word"
|
||||
}
|
||||
|
||||
# Output config file tokens one per line. For -J lines, each token is prefixed with -J.
|
||||
# No eval; caller appends via: while IFS= read -r t; do [[ -n "$t" ]] && arr+=("$t"); done < <(outputConfigFileTokens "$file")
|
||||
# Fixes #7333; Bash 3.x compatible.
|
||||
outputConfigFileTokens() {
|
||||
local file="$1"
|
||||
[[ ! -f "$file" ]] && return
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
line=$(printf '%s' "$line" | sed $'/^\#/d;s/\r$//')
|
||||
[[ -z "$line" ]] && continue
|
||||
if [[ "$line" == -J* ]]; then
|
||||
local rest="${line#-J}"
|
||||
while IFS= read -r token; do
|
||||
[[ -n "$token" ]] && printf '%s\n' "-J$token"
|
||||
done < <(parseLineIntoWords "$rest")
|
||||
else
|
||||
while IFS= read -r token; do
|
||||
[[ -n "$token" ]] && printf '%s\n' "$token"
|
||||
done < <(parseLineIntoWords "$line")
|
||||
fi
|
||||
done < <(cat "$file")
|
||||
}
|
||||
|
||||
loadConfigFile() {
|
||||
# Make sure the last line is read even if it doesn't have a terminating \n
|
||||
# Output lines literally without shell expansion to handle special characters safely
|
||||
|
|
@ -861,14 +915,14 @@ sbt_file_opts=()
|
|||
|
||||
# Pull in the machine-wide settings configuration.
|
||||
if [[ -f "$machine_sbt_opts_file" ]]; then
|
||||
sbt_file_opts+=($(loadConfigFile "$machine_sbt_opts_file"))
|
||||
while IFS= read -r t; do [[ -n "$t" ]] && sbt_file_opts+=("$t"); done < <(outputConfigFileTokens "$machine_sbt_opts_file")
|
||||
else
|
||||
# Otherwise pull in the default settings configuration.
|
||||
[[ -f "$dist_sbt_opts_file" ]] && sbt_file_opts+=($(loadConfigFile "$dist_sbt_opts_file"))
|
||||
[[ -f "$dist_sbt_opts_file" ]] && while IFS= read -r t; do [[ -n "$t" ]] && sbt_file_opts+=("$t"); done < <(outputConfigFileTokens "$dist_sbt_opts_file")
|
||||
fi
|
||||
|
||||
# Pull in the project-level config file, if it exists (highest priority, overrides machine/dist).
|
||||
[[ -f "$sbt_opts_file" ]] && sbt_file_opts+=($(loadConfigFile "$sbt_opts_file"))
|
||||
[[ -f "$sbt_opts_file" ]] && while IFS= read -r t; do [[ -n "$t" ]] && sbt_file_opts+=("$t"); done < <(outputConfigFileTokens "$sbt_opts_file")
|
||||
|
||||
# Prepend sbtopts so command line args appear last and win for duplicate properties.
|
||||
if (( ${#sbt_file_opts[@]} > 0 )); then
|
||||
|
|
@ -876,14 +930,15 @@ if (( ${#sbt_file_opts[@]} > 0 )); then
|
|||
fi
|
||||
|
||||
# Pull in the project-level java config, if it exists.
|
||||
[[ -f ".jvmopts" ]] && export JAVA_OPTS="$JAVA_OPTS $(loadConfigFile .jvmopts)"
|
||||
jvmopts_args=()
|
||||
[[ -f ".jvmopts" ]] && while IFS= read -r t; do [[ -n "$t" ]] && jvmopts_args+=("$t"); done < <(outputConfigFileTokens ".jvmopts")
|
||||
|
||||
# Pull in default JAVA_OPTS
|
||||
[[ -z "${JAVA_OPTS// }" ]] && export JAVA_OPTS="$default_java_opts"
|
||||
|
||||
[[ -f "$build_props_file" ]] && loadPropFile "$build_props_file"
|
||||
|
||||
java_args=($JAVA_OPTS)
|
||||
java_args=($JAVA_OPTS "${jvmopts_args[@]}")
|
||||
sbt_options0=(${SBT_OPTS:-$default_sbt_opts})
|
||||
java_tool_options=($JAVA_TOOL_OPTIONS)
|
||||
jdk_java_options=($JDK_JAVA_OPTIONS)
|
||||
|
|
@ -909,7 +964,7 @@ if [[ $print_sbt_script_version ]]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$(isRunNativeClient)" == "true" ]]; then
|
||||
if [[ "$(isRunNativeClient)" == "true" ]] && [[ -z "$print_version" ]]; then
|
||||
set -- "${residual_args[@]}"
|
||||
argumentCount=$#
|
||||
runNativeClient
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
// Verify that local plugins work
|
||||
TaskKey[Unit]("checkLocalPlugins") := Def.uncached {
|
||||
val localResult = localPluginCheck.value
|
||||
val customResult = customPluginCheck.value
|
||||
assert(localResult == "local-plugin-active", s"Expected local plugin to be active, got: $localResult")
|
||||
assert(customResult == "custom plugin", s"Expected custom plugin to be active, got: $customResult")
|
||||
},
|
||||
|
||||
// Verify that the dependencies in updateSbtClassifiers / classifiersModule do not include the local plugins but do include
|
||||
// other declared dependencies
|
||||
TaskKey[Unit]("checkClassifiersModule") := Def.uncached {
|
||||
val mod = (updateSbtClassifiers / classifiersModule).value
|
||||
val deps = mod.dependencies
|
||||
val actual = deps.map(m => s"${m.organization}:${m.name}").sorted.toSet
|
||||
|
||||
val expected = Set(
|
||||
"org.scala-sbt:sbt",
|
||||
"junit:junit",
|
||||
"com.eed3si9n:sbt-buildinfo_sbt2_3",
|
||||
"org.hamcrest:hamcrest-core",
|
||||
"com.eed3si9n.manifesto:manifesto_3",
|
||||
"org.scala-lang:scala3-library_3",
|
||||
"org.scala-lang:scala-library",
|
||||
"org.typelevel:cats-core_3",
|
||||
"org.typelevel:cats-kernel_3"
|
||||
)
|
||||
|
||||
assert(
|
||||
actual == expected,
|
||||
s"""
|
||||
|ClassifiersModule dependencies mismatch.
|
||||
|Expected: ${expected.mkString(", ")}
|
||||
|Actual: ${actual.mkString(", ")}
|
||||
""".stripMargin
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import sbt.*
|
||||
import sbt.Keys.*
|
||||
|
||||
object LocalPlugin extends AutoPlugin {
|
||||
override def requires: Plugins = plugins.JvmPlugin
|
||||
override def trigger: PluginTrigger = allRequirements
|
||||
|
||||
object autoImport {
|
||||
val localPluginCheck = taskKey[String]("A task provided by the local plugin")
|
||||
}
|
||||
|
||||
import autoImport.*
|
||||
|
||||
override lazy val projectSettings: Seq[Setting[?]] = Seq(
|
||||
localPluginCheck := "local-plugin-active"
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
lazy val meta = (project in file("."))
|
||||
.settings(
|
||||
// Just to make the test more comprehensive and check whether the additional libraries/plugins
|
||||
// are present in updateSbtClassifiers/classifiersModule.
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1"),
|
||||
libraryDependencies += "junit" % "junit" % "4.13.2"
|
||||
)
|
||||
.dependsOn(customPlugin)
|
||||
|
||||
lazy val customPlugin = (project in file("custom"))
|
||||
.enablePlugins(SbtPlugin)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %% "cats-core" % "2.13.0",
|
||||
)
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import sbt.*
|
||||
import sbt.Keys.*
|
||||
|
||||
object CustomPlugin extends AutoPlugin {
|
||||
override def requires: Plugins = plugins.JvmPlugin
|
||||
override def trigger: PluginTrigger = allRequirements
|
||||
|
||||
object autoImport {
|
||||
val customPluginCheck = taskKey[String]("A task provided by the custom plugin")
|
||||
}
|
||||
|
||||
import autoImport.*
|
||||
|
||||
override lazy val projectSettings: Seq[Setting[?]] = Seq(
|
||||
customPluginCheck := {
|
||||
import cats.implicits.*
|
||||
List("custom", " ", "plugin").combineAll
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Verify the local plugins are loaded and active
|
||||
> checkLocalPlugins
|
||||
|
||||
# Verify the local plugins are not included in the classifiersModule dependencies
|
||||
> checkClassifiersModule
|
||||
|
||||
# Also verify updateSbtClassifiers itself succeeds
|
||||
> updateSbtClassifiers
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
// Inject a non-existent module into the classifiers module dependencies
|
||||
// to simulate a scenario where classifier artifacts can't be downloaded.
|
||||
// With missingOk=true, updateSbtClassifiers should still succeed.
|
||||
updateSbtClassifiers / classifiersModule := {
|
||||
val mod = (updateSbtClassifiers / classifiersModule).value
|
||||
val fakeModule = "com.example.nonexistent" % "fake-library" % "0.0.1"
|
||||
mod.withDependencies(mod.dependencies :+ fakeModule)
|
||||
},
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
|
||||
|
|
@ -0,0 +1 @@
|
|||
> updateSbtClassifiers
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
val scalatest = "org.scalatest" %% "scalatest" % "3.0.5"
|
||||
|
||||
ThisBuild / scalaVersion := "2.12.21"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.settings(
|
||||
libraryDependencies += scalatest,
|
||||
Test / testOptions += Tests.Argument("-C", "custom.CustomReporter")
|
||||
)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package custom
|
||||
|
||||
import java.io._
|
||||
import org.scalatest._
|
||||
import events._
|
||||
|
||||
class CustomReporter extends Reporter {
|
||||
|
||||
private def writeFile(filePath: String, content: String): Unit = {
|
||||
val file = new File(filePath)
|
||||
val writer =
|
||||
if (!file.exists)
|
||||
new FileWriter(new File(filePath))
|
||||
else
|
||||
new FileWriter(new File(filePath + "-2"))
|
||||
writer.write(content)
|
||||
writer.flush()
|
||||
writer.close()
|
||||
}
|
||||
|
||||
def apply(event: Event): Unit = {
|
||||
event match {
|
||||
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
|
||||
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
|
||||
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
|
||||
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.test
|
||||
|
||||
import org.scalatest.Spec
|
||||
|
||||
class TestSpec extends Spec {
|
||||
|
||||
def `TestSpec-test-1 ` {}
|
||||
|
||||
def `TestSpec-test-2 ` {}
|
||||
|
||||
def `TestSpec-test-3 ` {}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.test
|
||||
|
||||
import org.scalatest._
|
||||
|
||||
@DoNotDiscover
|
||||
class TestSpec2 extends Spec {
|
||||
|
||||
def `TestSpec2-test-1 ` {}
|
||||
|
||||
def `TestSpec2-test-2 ` {}
|
||||
|
||||
def `TestSpec2-test-3 ` {}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# #5609: When explicitly requested via testOnly, @DoNotDiscover suite should run.
|
||||
# First: full test run must exclude @DoNotDiscover (TestSpec2).
|
||||
# Second: testOnly with explicit FQN must run TestSpec2.
|
||||
|
||||
> clean
|
||||
> testFull
|
||||
$ exists target/SuiteStarting-TestSpec
|
||||
$ exists target/SuiteCompleted-TestSpec
|
||||
$ absent target/SuiteStarting-TestSpec2
|
||||
$ absent target/SuiteCompleted-TestSpec2
|
||||
|
||||
> clean
|
||||
> testOnly com.test.TestSpec2
|
||||
$ exists target/SuiteStarting-TestSpec2
|
||||
$ exists target/SuiteCompleted-TestSpec2
|
||||
|
||||
$ delete target/SuiteStarting-TestSpec
|
||||
$ delete target/SuiteCompleted-TestSpec
|
||||
$ delete target/SuiteStarting-TestSpec2
|
||||
$ delete target/SuiteCompleted-TestSpec2
|
||||
> testOnly com.test...
|
||||
$ exists target/SuiteStarting-TestSpec
|
||||
$ exists target/SuiteCompleted-TestSpec
|
||||
$ absent target/SuiteStarting-TestSpec2
|
||||
$ absent target/SuiteCompleted-TestSpec2
|
||||
|
|
@ -117,7 +117,7 @@ class ClientTest extends AbstractServerTest {
|
|||
assert(client("willFail;willSucceed") == 1)
|
||||
}
|
||||
test("three commands") {
|
||||
assert(client("compile;clean;willSucceed") == 0)
|
||||
assert(client("compile;willSucceed;willSucceed") == 0)
|
||||
}
|
||||
test("three commands with middle failure") {
|
||||
assert(client("compile;willFail;willSucceed") == 1)
|
||||
|
|
|
|||
Loading…
Reference in New Issue