[2.x] dependencyTree: add `--append` for file output in multi-module builds (#8878)

**Summary**
This adds an --append flag to the dependencyTree input task when used with --out. It allows running dependencyTree in multiple subprojects and appending their outputs to the same target file instead of overwriting.

**Solution**
- Add Arg.Append in the CLI parsing
- When --append is set, write using IO.write(..., append = true)
This commit is contained in:
oolokioo7 2026-03-06 16:53:18 -03:00 committed by GitHub
parent 8a77f7fb03
commit b3d6c06731
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 49 additions and 6 deletions

View File

@ -36,6 +36,7 @@ private[sbt] object DependencyTreeSettings:
case Quiet
case Format(format: Fmt)
case Out(out: String)
case Append
case Browse
enum Fmt:
@ -67,6 +68,7 @@ private[sbt] object DependencyTreeSettings:
lazy val ArgOptionParser: Parser[Arg] =
Space ~> (("--quiet" ^^^ Arg.Quiet)
| ("--append" ^^^ Arg.Append)
| ("--browse" ^^^ Arg.Browse)
| ("--out" ~> Space ~> StringBasic)
.map(Arg.Out(_))
@ -97,6 +99,7 @@ SUBCOMMAND
OPTIONS
--quiet Returns the output as task value, replacing asString
--append Append to the output file when used with --out
--out <file> Writes the output to the specified file;
The file extension will influence the default subcommand
--browse Opens the browser when combined with graph or html subcommand
@ -164,6 +167,7 @@ OPTIONS
val args = ArgsParser.parsed.toList
val isHelp = args.contains(Arg.Help)
val isQuiet = args.contains(Arg.Quiet)
val isAppend = args.contains(Arg.Append)
val isBrowse = args.contains(Arg.Browse)
if isHelp then Def.task { s.log.info(usageText); "" }
else
@ -195,19 +199,19 @@ OPTIONS
case Fmt.List => rendering.FlatList.render(_.id.idString)(graph)
case Fmt.Stats => rendering.Statistics.renderModuleStatsList(graph)
case _ => rendering.AsciiTree.asciiTree(graph, asciiGraphWidth.value)
handleOutput(output, outFileOpt, isQuiet, s.log)
handleOutput(output, outFileOpt, isQuiet, isAppend, s.log)
}
case Fmt.Json =>
Def.task {
val graph = dependencyTreeModuleGraph0.value
val output = TreeView.createJson(graph)
handleOutput(output, outFileOpt, isQuiet, s.log)
handleOutput(output, outFileOpt, isQuiet, isAppend, s.log)
}
case Fmt.Xml =>
Def.task {
val graph = dependencyTreeModuleGraph0.value
val output = rendering.GraphML.graphMLAsString(graph)
handleOutput(output, outFileOpt, isQuiet, s.log)
handleOutput(output, outFileOpt, isQuiet, isAppend, s.log)
}
case Fmt.Html =>
Def.task {
@ -227,7 +231,8 @@ OPTIONS
rendering.DOT.HTMLLabelRendering.AngleBrackets,
dependencyDotNodeColors.value
)
if format == Fmt.Graph then handleOutput(output, outFileOpt, isQuiet, s.log)
if format == Fmt.Graph then
handleOutput(output, outFileOpt, isQuiet, isAppend, s.log)
else
val outputFile = DagreHTML.createFile(output, targetDir)
if isBrowse then openBrowser(outputFile.toURI)
@ -281,7 +286,7 @@ OPTIONS
val output = format match
case Fmt.Json => rendering.LicenseInfo.renderJson(graph)
case _ => rendering.LicenseInfo.render(graph)
handleOutput(output, outFileOpt, isQuiet, s.log)
handleOutput(output, outFileOpt, isQuiet, appendToFile = false, s.log)
}
}).evaluated,
)
@ -306,11 +311,15 @@ OPTIONS
content: String,
outputFileOpt: Option[File],
isQuiet: Boolean,
appendToFile: Boolean,
log: Logger,
): String =
outputFileOpt match
case Some(output) =>
IO.write(output, content, IO.utf8)
val toWrite =
if appendToFile && output.exists() && output.length() > 0 then "\n" + content
else content
IO.write(output, toWrite, IO.utf8, append = appendToFile)
if !isQuiet then log.info(s"wrote dependencies to $output")
output.toString
case None =>

View File

@ -27,6 +27,7 @@ object DependencyTreeTest extends verify.BasicTestSuite:
assert(parseArgs(List("help")) == List(Arg.Help))
assert(parseArgs(List("--help")) == List(Arg.Help))
assert(parseArgs(List("--quiet")) == List(Arg.Quiet))
assert(parseArgs(List("--append")) == List(Arg.Append))
assert(parseArgs(List("tree")) == List(Arg.Format(Fmt.Tree)))
assert(parseArgs(List("--out", "/tmp/deps.txt")) == List(Arg.Out("/tmp/deps.txt")))
assert(parseArgs(List("--browse")) == List(Arg.Browse))

View File

@ -0,0 +1,2 @@
object A:
def a: Int = 1

View File

@ -0,0 +1,2 @@
object B:
def b: Int = 2

View File

@ -0,0 +1,26 @@
scalaVersion := "2.12.21"
organization := "org.example"
version := "0.1"
lazy val root = (project in file("."))
.aggregate(a, b)
.settings(
publish / skip := true,
)
lazy val a = (project in file("a")).settings(
name := "a",
libraryDependencies += "org.typelevel" %% "cats-core" % "2.10.0",
)
lazy val b = (project in file("b")).settings(
name := "b",
libraryDependencies += "com.lihaoyi" %% "pprint" % "0.9.0",
)
TaskKey[Unit]("check") := {
val f = new File("target/tree.txt")
val content = IO.read(f)
assert(content.contains("org.typelevel:cats-core_2.12:2.10.0"), s"Missing cats-core in output:\n$content")
assert(content.contains("com.lihaoyi:pprint_2.12:0.9.0"), s"Missing pprint in output:\n$content")
}

View File

@ -0,0 +1,3 @@
> a/dependencyTree --out target/tree.txt
> b/dependencyTree --out target/tree.txt --append
> check