mirror of https://github.com/sbt/sbt.git
[2.x] feat: dependencyLicenseInfo (#8506)
* Add JSON output support to dependencyLicenseInfo - Add LicenseInfo rendering object with text and JSON output - Add dependencyLicenseInfo input task key - Implement dependencyLicenseInfo task with JSON format support - Supports --out option for file output - Auto-detects JSON format from .json file extension - Follows same pattern as dependencyTree task Resolves #7771
This commit is contained in:
parent
847703cd5e
commit
aeabb90d2d
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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
|
||||
package internal
|
||||
package graph
|
||||
package rendering
|
||||
|
||||
import sbt.internal.graph.*
|
||||
|
||||
object LicenseInfo {
|
||||
def render(graph: ModuleGraph): String =
|
||||
graph.nodes
|
||||
.filter(_.isUsed)
|
||||
.groupBy(_.license)
|
||||
.toSeq
|
||||
.sortBy(_._1)
|
||||
.map { case (license, modules) =>
|
||||
license.getOrElse("No license specified") + "\n" +
|
||||
modules.map(m => s"\t ${m.id.idString}").mkString("\n")
|
||||
}
|
||||
.mkString("\n\n")
|
||||
|
||||
def renderJson(graph: ModuleGraph): String = {
|
||||
// Create JSON array manually: [{license: "...", modules: [...]}, ...]
|
||||
def escapeJson(str: String): String =
|
||||
str.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r")
|
||||
|
||||
def formatModuleList(modules: Vector[String]): String =
|
||||
modules.map(m => s""""${escapeJson(m)}"""").mkString("[", ",", "]")
|
||||
|
||||
val groups = graph.nodes
|
||||
.filter(_.isUsed)
|
||||
.groupBy(_.license)
|
||||
.toSeq
|
||||
.sortBy(_._1)
|
||||
.map { case (license, modules) =>
|
||||
val licenseStr = license.getOrElse("No license specified")
|
||||
val moduleList = formatModuleList(modules.map(_.id.idString).toVector.sorted)
|
||||
s"""{"license":"${escapeJson(licenseStr)}","modules":$moduleList}"""
|
||||
}
|
||||
|
||||
groups.mkString("[", ",", "]")
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,8 @@ abstract class DependencyTreeKeys:
|
|||
private[sbt] val dependencyTreeModuleGraphStore =
|
||||
taskKey[ModuleGraph]("The stored module-graph from the last run")
|
||||
val whatDependsOn = inputKey[String]("Shows information about what depends on the given module")
|
||||
val dependencyLicenseInfo =
|
||||
inputKey[String]("Displays license information for dependencies in text or JSON format")
|
||||
private[sbt] val dependencyTreeCrossProjectId = settingKey[ModuleID]("")
|
||||
|
||||
// 0 was added to avoid conflict with sbt-dependency-tree
|
||||
|
|
|
|||
|
|
@ -254,8 +254,52 @@ OPTIONS
|
|||
}
|
||||
output
|
||||
},
|
||||
dependencyLicenseInfo := (Def.inputTaskDyn {
|
||||
val s = streams.value
|
||||
val args = ArgsParser.parsed.toList
|
||||
val isHelp = args.contains(Arg.Help)
|
||||
val isQuiet = args.contains(Arg.Quiet)
|
||||
if isHelp then Def.task { s.log.info(licenseInfoUsageText); "" }
|
||||
else
|
||||
val formatOpt = (args
|
||||
.collect { case Arg.Format(fmt) => fmt })
|
||||
.reverse
|
||||
.headOption
|
||||
val outFileNameOpt = (args
|
||||
.collect { case Arg.Out(out) => out })
|
||||
.reverse
|
||||
.headOption
|
||||
val outFileOpt = outFileNameOpt.map(new File(_))
|
||||
val format = (formatOpt, outFileNameOpt) match
|
||||
case (None, Some(out)) if out.endsWith(".json") => Fmt.Json
|
||||
case (Some(fmt), _) => fmt
|
||||
case _ => Fmt.Tree
|
||||
Def.task {
|
||||
val graph = dependencyTreeModuleGraph0.value
|
||||
val output = format match
|
||||
case Fmt.Json => rendering.LicenseInfo.renderJson(graph)
|
||||
case _ => rendering.LicenseInfo.render(graph)
|
||||
handleOutput(output, outFileOpt, isQuiet, s.log)
|
||||
}
|
||||
}).evaluated,
|
||||
)
|
||||
|
||||
def licenseInfoUsageText: String =
|
||||
s"""dependencyLicenseInfo task displays license information for dependencies.
|
||||
|
||||
USAGE
|
||||
dependencyLicenseInfo [subcommand] [options]
|
||||
|
||||
SUBCOMMAND
|
||||
json Prints JSON (default is text)
|
||||
help Prints this help
|
||||
|
||||
OPTIONS
|
||||
--quiet Returns the output as task value
|
||||
--out <file> Writes the output to the specified file;
|
||||
The file extension will influence the default subcommand
|
||||
"""
|
||||
|
||||
private def handleOutput(
|
||||
content: String,
|
||||
outputFileOpt: Option[File],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
name := "license-info-test"
|
||||
scalaVersion := "3.3.1"
|
||||
|
||||
libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value
|
||||
|
||||
TaskKey[Unit]("check") := {
|
||||
import java.io.File
|
||||
import sbt.io.IO
|
||||
|
||||
// Check text file
|
||||
val textFile = new File("target/licenses.txt")
|
||||
require(textFile.exists(), s"Text file ${textFile.getPath} does not exist")
|
||||
val textContent = IO.read(textFile)
|
||||
require(textContent.nonEmpty, "Text file is empty")
|
||||
require(textContent.contains("scala-library") || textContent.contains("No license specified"),
|
||||
"Text file should contain license information")
|
||||
|
||||
// Check JSON file
|
||||
val jsonFile = new File("target/licenses.json")
|
||||
require(jsonFile.exists(), s"JSON file ${jsonFile.getPath} does not exist")
|
||||
val jsonContent = IO.read(jsonFile)
|
||||
require(jsonContent.nonEmpty, "JSON file is empty")
|
||||
require(jsonContent.trim.startsWith("[") && jsonContent.trim.endsWith("]"),
|
||||
"JSON file should be a valid JSON array")
|
||||
require(jsonContent.contains("\"license\"") && jsonContent.contains("\"modules\""),
|
||||
"JSON file should contain license and modules fields")
|
||||
|
||||
()
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# Test dependencyLicenseInfo text output
|
||||
> dependencyLicenseInfo --out target/licenses.txt
|
||||
$ exists target/licenses.txt
|
||||
# Test dependencyLicenseInfo JSON output
|
||||
> dependencyLicenseInfo json --out target/licenses.json
|
||||
$ exists target/licenses.json
|
||||
> check
|
||||
|
||||
Loading…
Reference in New Issue