mirror of https://github.com/sbt/sbt.git
[2.x] fix: Fix whatDependsOn RuntimeException (#8462)
Filters out empty versions during parser construction to prevent RuntimeException when creating token parsers. Includes comprehensive test coverage for edge cases. DependencyTreePlugin is an AutoPlugin with trigger = AllRequirements, so it loads automatically in scripted tests without requiring explicit plugin configuration.
This commit is contained in:
parent
1ef5823a49
commit
4a36171138
|
|
@ -86,6 +86,14 @@ jobs:
|
|||
repository: sbt/zinc
|
||||
ref: develop
|
||||
path: zinc
|
||||
- name: Cleanup SBT server and named pipes (Windows only)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Kill sbt processes if any are running
|
||||
Get-Process sbt -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
# Remove all SBT named pipes, suppress errors if none exist
|
||||
Remove-Item "\\.\pipe\sbt-*" -ErrorAction SilentlyContinue
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -324,6 +324,41 @@ OPTIONS
|
|||
|
||||
case class ArtifactPattern(organization: String, name: String, version: Option[String])
|
||||
|
||||
private[plugins] def createArtifactPatternParser(
|
||||
graph: ModuleGraph
|
||||
): Parser[ArtifactPattern] =
|
||||
graph.nodes
|
||||
.map(_.id)
|
||||
.groupBy(m => (m.organization, m.name))
|
||||
.map { case ((org, name), modules) =>
|
||||
// Empty versions cause parser token creation to fail
|
||||
val versionParsers: Seq[Parser[Option[String]]] =
|
||||
modules
|
||||
.filter(_.version.nonEmpty)
|
||||
.map { id =>
|
||||
token(Space ~> id.version).?
|
||||
}
|
||||
|
||||
// Handle modules with only empty versions
|
||||
val effectiveVersionParser =
|
||||
if versionParsers.isEmpty then success(None)
|
||||
else oneOf(versionParsers)
|
||||
|
||||
(Space ~> token(org) ~ token(Space ~> name) ~ effectiveVersionParser).map {
|
||||
case ((org, name), version) => ArtifactPattern(org, name, version)
|
||||
}
|
||||
}
|
||||
.reduceOption(_ | _)
|
||||
.getOrElse {
|
||||
// If the dependencyTreeModuleGraphStore couldn't be loaded because no dependency tree command was run before, we should still provide a parser for the command.
|
||||
((Space ~> token(StringBasic, "<organization>")) ~ (Space ~> token(
|
||||
StringBasic,
|
||||
"<module>"
|
||||
)) ~ (Space ~> token(StringBasic, "<version?>")).?).map { case ((org, mod), version) =>
|
||||
ArtifactPattern(org, mod, version)
|
||||
}
|
||||
}
|
||||
|
||||
val artifactPatternParser: Def.Initialize[State => Parser[ArtifactPattern]] =
|
||||
Keys.resolvedScoped { ctx => (state: State) =>
|
||||
val graph =
|
||||
|
|
@ -331,31 +366,9 @@ OPTIONS
|
|||
Nil,
|
||||
Nil
|
||||
)
|
||||
|
||||
graph.nodes
|
||||
.map(_.id)
|
||||
.groupBy(m => (m.organization, m.name))
|
||||
.map { case ((org, name), modules) =>
|
||||
val versionParsers: Seq[Parser[Option[String]]] =
|
||||
modules.map { id =>
|
||||
token(Space ~> id.version).?
|
||||
}
|
||||
|
||||
(Space ~> token(org) ~ token(Space ~> name) ~ oneOf(versionParsers)).map {
|
||||
case ((org, name), version) => ArtifactPattern(org, name, version)
|
||||
}
|
||||
}
|
||||
.reduceOption(_ | _)
|
||||
.getOrElse {
|
||||
// If the dependencyTreeModuleGraphStore couldn't be loaded because no dependency tree command was run before, we should still provide a parser for the command.
|
||||
((Space ~> token(StringBasic, "<organization>")) ~ (Space ~> token(
|
||||
StringBasic,
|
||||
"<module>"
|
||||
)) ~ (Space ~> token(StringBasic, "<version?>")).?).map { case ((org, mod), version) =>
|
||||
ArtifactPattern(org, mod, version)
|
||||
}
|
||||
}
|
||||
createArtifactPatternParser(graph)
|
||||
}
|
||||
|
||||
val shouldForceParser: Parser[Boolean] =
|
||||
(Space ~> (Parser.literal("-f") | "--force")).?.map(_.isDefined)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,17 @@ package sbt
|
|||
package plugins
|
||||
|
||||
import sbt.internal.util.complete.Parser
|
||||
import DependencyTreeSettings.{ Arg, ArgsParser, Fmt, FmtParser }
|
||||
import DependencyTreeSettings.{
|
||||
Arg,
|
||||
ArgsParser,
|
||||
Fmt,
|
||||
FmtParser,
|
||||
ArtifactPattern,
|
||||
createArtifactPatternParser
|
||||
}
|
||||
import sbt.internal.graph.ModuleGraph
|
||||
import sbt.internal.graph.GraphModuleId
|
||||
import sbt.internal.graph.Module
|
||||
|
||||
object DependencyTreeTest extends verify.BasicTestSuite:
|
||||
test("Parse args") {
|
||||
|
|
@ -31,6 +41,77 @@ object DependencyTreeTest extends verify.BasicTestSuite:
|
|||
assert(parseFormat("graph") == Fmt.Graph)
|
||||
}
|
||||
|
||||
test("ArtifactPatternParser with normal modules") {
|
||||
val graph = ModuleGraph(
|
||||
Seq(
|
||||
node("org1", "name1", "1.0"),
|
||||
node("org1", "name1", "2.0")
|
||||
),
|
||||
Nil
|
||||
)
|
||||
val parser = createArtifactPatternParser(graph)
|
||||
|
||||
// Test matching
|
||||
assert(
|
||||
Parser.parse(" org1 name1 1.0", parser) == Right(
|
||||
ArtifactPattern("org1", "name1", Some("1.0"))
|
||||
)
|
||||
)
|
||||
assert(
|
||||
Parser.parse(" org1 name1 2.0", parser) == Right(
|
||||
ArtifactPattern("org1", "name1", Some("2.0"))
|
||||
)
|
||||
)
|
||||
assert(Parser.parse(" org1 name1", parser) == Right(ArtifactPattern("org1", "name1", None)))
|
||||
}
|
||||
|
||||
test("ArtifactPatternParser with completely empty graph") {
|
||||
val graph = ModuleGraph.empty
|
||||
val parser = createArtifactPatternParser(graph)
|
||||
|
||||
// Should fallback to generic parser
|
||||
assert(
|
||||
Parser.parse(" org1 name1 1.0", parser) == Right(
|
||||
ArtifactPattern("org1", "name1", Some("1.0"))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
test("ArtifactPatternParser should not throw RuntimeException on empty version") {
|
||||
val graph = ModuleGraph(
|
||||
Seq(
|
||||
node("org1", "name1", "")
|
||||
),
|
||||
Nil
|
||||
)
|
||||
// This previously threw RuntimeException: String literal cannot be empty
|
||||
val parser = createArtifactPatternParser(graph)
|
||||
|
||||
// Should parse org and name, version is None
|
||||
assert(Parser.parse(" org1 name1", parser) == Right(ArtifactPattern("org1", "name1", None)))
|
||||
}
|
||||
|
||||
test("ArtifactPatternParser mixed valid and empty versions") {
|
||||
val graph = ModuleGraph(
|
||||
Seq(
|
||||
node("org1", "name1", ""),
|
||||
node("org1", "name1", "1.0")
|
||||
),
|
||||
Nil
|
||||
)
|
||||
val parser = createArtifactPatternParser(graph)
|
||||
|
||||
// Valid version should be selectable
|
||||
assert(
|
||||
Parser.parse(" org1 name1 1.0", parser) == Right(
|
||||
ArtifactPattern("org1", "name1", Some("1.0"))
|
||||
)
|
||||
)
|
||||
|
||||
// No version should be selectable
|
||||
assert(Parser.parse(" org1 name1", parser) == Right(ArtifactPattern("org1", "name1", None)))
|
||||
}
|
||||
|
||||
def parseArgs(args: List[String]): Seq[Arg] =
|
||||
Parser.parse(" " + args.mkString(" "), ArgsParser) match
|
||||
case Right(args) => args
|
||||
|
|
@ -40,4 +121,8 @@ object DependencyTreeTest extends verify.BasicTestSuite:
|
|||
Parser.parse(fmt, FmtParser) match
|
||||
case Right(x) => x
|
||||
case Left(err) => sys.error(err)
|
||||
|
||||
def node(org: String, name: String, version: String): Module =
|
||||
Module(GraphModuleId(org, name, version), None, "", None, None, None)
|
||||
|
||||
end DependencyTreeTest
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ default:sbt_8ae1da13_2.12:0.1.0-SNAPSHOT [S]
|
|||
*/
|
||||
|
||||
val expectedGraph =
|
||||
"""default:default-e95e05_2.12:0.1-SNAPSHOT [S]
|
||||
"""foo:foo_2.12:0.1.0-SNAPSHOT [S]
|
||||
| +-ch.qos.logback:logback-classic:1.0.7
|
||||
| | +-ch.qos.logback:logback-core:1.0.7
|
||||
| | +-org.slf4j:slf4j-api:1.6.6 (evicted by: 1.7.2)
|
||||
| | +-org.slf4j:slf4j-api:1.7.2
|
||||
| |
|
||||
| +-org.slf4j:slf4j-api:1.7.2
|
||||
| """.stripMargin
|
||||
|
||||
|
||||
// IO.writeLines(file("/tmp/blib"), sanitize(graph).split("\n"))
|
||||
// IO.writeLines(file("/tmp/blub"), sanitize(expectedGraph).split("\n"))
|
||||
require(sanitize(graph) == sanitize(expectedGraph), "Graph for report %s was '\n%s' but should have been '\n%s'" format (report, sanitize(graph), sanitize(expectedGraph)))
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
addDependencyTreePlugin
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# to initialize parser with deps
|
||||
> Compile/dependencyTreeModuleGraph0
|
||||
> check
|
||||
> check
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
addDependencyTreePlugin
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue