Skip to content

Commit

Permalink
Dependency directive: produce BOM notation, modern Gradle (#462)
Browse files Browse the repository at this point in the history
  • Loading branch information
ennru authored Dec 4, 2020
1 parent e39f348 commit 81ecd30
Show file tree
Hide file tree
Showing 5 changed files with 480 additions and 95 deletions.
8 changes: 2 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: scala

env:
matrix:
- [email protected].0-222
- [email protected]-0

before_install: curl -Ls https://git.io/jabba | bash && . ~/.jabba/jabba.sh
install: jabba install "$TRAVIS_JDK" && jabba use $_ && java -Xmx32m -version
Expand All @@ -19,7 +19,7 @@ jobs:

- script: sbt verify
name: "Compile, test and build docs with JDK11"
env: [email protected].0-4
env: [email protected]-0

- stage: publish
script: sbt ci-release
Expand Down Expand Up @@ -48,7 +48,3 @@ cache:
before_cache:
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
- find $HOME/.sbt -name "*.lock" -print -delete

notifications:
email:
on_success: never
10 changes: 4 additions & 6 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
Paradox [![scaladex-badge][]][scaladex] [![travis-badge][]][travis] [![gitter-badge][]][gitter]
Paradox [![scaladex-badge][]][scaladex] [![travis-badge][]][travis]
=======

[scaladex]: https://index.scala-lang.org/lightbend/paradox
[scaladex-badge]: https://index.scala-lang.org/lightbend/paradox/paradox/latest.svg
[travis]: https://travis-ci.org/lightbend/paradox
[travis-badge]: https://travis-ci.org/lightbend/paradox.svg?branch=master
[gitter]: https://gitter.im/lightbend/paradox
[gitter-badge]: https://badges.gitter.im/lightbend/paradox.svg
[travis]: https://travis-ci.com/lightbend/paradox
[travis-badge]: https://travis-ci.com/lightbend/paradox.svg?branch=master

Paradox is a markdown documentation tool for software projects. See [Paradox docs](http://developer.lightbend.com/docs/paradox/latest/) for details.

Expand All @@ -18,4 +16,4 @@ This software is licensed under the Apache 2 license.

**Paradox is NOT supported under the Lightbend subscription.**

The project is maintained by the [Paradox Team](https://github.com/orgs/lightbend/teams/paradox) and all [the contributors](https://github.com/lightbend/paradox/graphs/contributors). Pull requests are very welcome–thanks in advance!
The project is community maintained by [the contributors](https://github.com/lightbend/paradox/graphs/contributors). Pull requests are very welcome–thanks in advance!
139 changes: 103 additions & 36 deletions core/src/main/scala/com/lightbend/paradox/markdown/Directive.scala
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ case class InlineGroupDirective(groups: Seq[String]) extends InlineDirective(gro
* Dependency directive.
*/
case class DependencyDirective(ctx: Writer.Context) extends LeafBlockDirective("dependency") {
val BomVersionSymbols = "bomVersionSymbols"
val VersionSymbol = "symbol"
val VersionValue = "value"
val ScalaBinaryVersionVar = "scala.binary.version"
Expand All @@ -714,6 +715,9 @@ case class DependencyDirective(ctx: Writer.Context) extends LeafBlockDirective("
def renderDependency(tools: String, node: DirectiveNode, printer: Printer): Unit = {
val classes = Seq("dependency", node.attributes.classesString).filter(_.nonEmpty)

val bomPostfixes = node.attributes.keys().asScala.toSeq
.filter(_.startsWith(BomVersionSymbols)).sorted.map(_.replace(BomVersionSymbols, ""))

val symbolPostfixes = node.attributes.keys().asScala.toSeq
.filter(_.startsWith(VersionSymbol)).sorted.map(_.replace(VersionSymbol, ""))

Expand Down Expand Up @@ -775,35 +779,61 @@ case class DependencyDirective(ctx: Writer.Context) extends LeafBlockDirective("
}
}

def gradle(group: String, artifact: String, rawArtifact: String, version: String, scope: Option[String], classifier: Option[String]): String = {
val artifactName = ScalaBinaryVersion match {
/**
* Replace Scala bin version in artifact postfix with the property.
*/
def artifactNameWithScalaBin(artifact: String, rawArtifact: String, property: String) = {
ScalaBinaryVersion match {
case Some(v) if (rawArtifact.endsWith(scalaBinaryVersionVarUse)) =>
"\"" + artifact.substring(0, artifact.length - v.length) + "${versions.ScalaBinary}\""
case _ => s"'$artifact'"
artifact.substring(0, artifact.length - v.length) + property
case _ => artifact
}
val conf = scope.getOrElse("compile")
val extra = classifier.map(c => s", classifier: '$c'").getOrElse("")
val v = if (symbols.contains(version)) s"versions.$version" else s"'$version'"
s"""$conf group: '$group', name: $artifactName, version: $v$extra""".stripMargin
}

def mvn(group: String, artifact: String, rawArtifact: String, version: String, scope: Option[String], classifier: Option[String]): String = {
val artifactName = ScalaBinaryVersion match {
case Some(v) if rawArtifact.endsWith(scalaBinaryVersionVarUse) =>
artifact.substring(0, artifact.length - v.length) + "${scala.binary.version}"
case _ => artifact
def gradle(group: String, artifact: String, rawArtifact: String, version: Option[String], scope: Option[String], classifier: Option[String]): String = {
val artifactName = artifactNameWithScalaBin(artifact, rawArtifact, "${versions.ScalaBinary}")
val conf = scope match {
case None => "implementation"
case Some("test") => "testImplementation"
case Some(other) => other
}
val ver = version.map(v => if (symbols.contains(v)) s":$${versions.$v}" else s":$v").getOrElse("")
val extra = classifier.map(c => s":$c").getOrElse("")
s"""$conf "$group:$artifactName$ver$extra""""
}

def gradleBom(group: String, artifact: String, rawArtifact: String, version: String): String = {
val artifactName = artifactNameWithScalaBin(artifact, rawArtifact, "${versions.ScalaBinary}")
val ver = if (symbols.contains(version)) s"versions.$version" else version
s""" implementation platform("$group:$artifactName:$ver")"""
}

def mvn(group: String, artifact: String, rawArtifact: String, version: Option[String], `type`: Option[String], scope: Option[String], classifier: Option[String], indent: String): String = {
val artifactName = artifactNameWithScalaBin(artifact, rawArtifact, "${scala.binary.version}")

val elements =
Seq("groupId" -> group, "artifactId" -> artifactName, "version" -> {
if (symbols.contains(version)) s"$${${dotted(version)}}" else version
}) ++
classifier.map("classifier" -> _) ++ scope.map("scope" -> _)
Seq("groupId" -> group, "artifactId" -> artifactName) ++
version.map(v => "version" -> {
if (symbols.contains(v)) s"$${${dotted(v)}}" else v
}) ++ classifier.map("classifier" -> _) ++ `type`.map("type" -> _) ++ scope.map("scope" -> _)
elements.map {
case (element, value) => s" <$element>$value</$element>"
}.mkString("<dependency>\n", "\n", "\n</dependency>").replace("<", "&lt;").replace(">", "&gt;")
case (element, value) => s"$indent &lt;$element&gt;$value&lt;/$element&gt;"
}.mkString(s"$indent&lt;dependency&gt;\n", "\n", s"\n$indent&lt;/dependency&gt\n")
}

val boms = bomPostfixes.map { p =>
(
requiredCoordinate(s"bomGroup$p"),
requiredCoordinate(s"bomArtifact$p"),
requiredCoordinateRaw(s"bomArtifact$p"),
requiredCoordinate(s"bomVersionSymbols$p").split(",").toSeq
)
}
val bomSymbols = boms.flatMap(_._4.toSet).toSet

val symbolVersions = symbolPostfixes
.map(sp => requiredCoordinate(VersionSymbol + sp) -> requiredCoordinate(VersionValue + sp))

printer.print(s"""<dl class="${classes.mkString(" ")}">""")
tools.split("[,]").map(_.trim).filter(_.nonEmpty).foreach { tool =>
val (lang, code) = tool match {
Expand Down Expand Up @@ -831,54 +861,91 @@ case class DependencyDirective(ctx: Writer.Context) extends LeafBlockDirective("

case "gradle" | "Gradle" =>
val scalaBinaryVersionProperties =
if (showSymbolScalaBinary) ScalaBinaryVersion.flatMap { v =>
Some(s""" ScalaBinary: "$v"""")
}
if (showSymbolScalaBinary) ScalaBinaryVersion.map(v => s""" ScalaBinary: "$v"""")
else None
val symbolProperties = if (scalaBinaryVersionProperties.isEmpty && symbols.isEmpty) "" else
(symbolPostfixes.map { sp =>
s""" ${requiredCoordinate(VersionSymbol + sp)}: "${requiredCoordinate(VersionValue + sp)}""""
} ++ scalaBinaryVersionProperties).mkString("versions += [\n", ",\n", "\n]\n")
(symbolVersions
.filter(sp => !bomSymbols.contains(sp._1))
.map {
case (symbol, version) => s""" $symbol: "$version""""
} ++ scalaBinaryVersionProperties).mkString("def versions = [\n", ",\n", "\n]\n")
val bomArtifacts =
if (boms.nonEmpty) {
boms.map {
case (group, artifact, artifactRaw, versionSymbol) =>
gradleBom(
group,
artifact,
artifactRaw,
version = symbolVersions.find(_._1 == versionSymbol.head).map(_._2).getOrElse(sys.error(s"No version found for ${versionSymbol.head}")),
)
}.mkString("", "\n", "\n\n")
} else ""
val artifacts = dependencyPostfixes.map { dp =>
val versionCoordinate = requiredCoordinate(s"version$dp")
gradle(
requiredCoordinate(s"group$dp"),
requiredCoordinate(s"artifact$dp"),
requiredCoordinateRaw(s"artifact$dp"),
requiredCoordinate(s"version$dp"),
if (bomSymbols.contains(versionCoordinate)) None else Some(versionCoordinate),
coordinate(s"scope$dp"),
coordinate(s"classifier$dp")
)
}

val libraryDependencies =
Seq("dependencies {", artifacts.map(a => s" $a").mkString(",\n"), "}").mkString("\n")
Seq("dependencies {", bomArtifacts ++ artifacts.map(a => s" $a").mkString("\n"), "}").mkString("\n")

("gradle", symbolProperties + libraryDependencies)

case "maven" | "Maven" | "mvn" =>
val scalaBinaryVersionProperties =
if (showSymbolScalaBinary) ScalaBinaryVersion.flatMap { v =>
Some(s""" &lt;scala.binary.version&gt;$v&lt;/scala.binary.version&gt;""")
if (showSymbolScalaBinary) ScalaBinaryVersion.map { v =>
s""" &lt;scala.binary.version&gt;$v&lt;/scala.binary.version&gt;"""
}
else None
val symbolProperties = if (scalaBinaryVersionProperties.isEmpty && symbols.isEmpty) "" else
(symbolPostfixes.map { sp =>
val symb = s"""${dotted(requiredCoordinate(VersionSymbol + sp))}"""
s""" &lt;$symb&gt;${requiredCoordinate(VersionValue + sp)}&lt;/$symb&gt;"""
} ++ scalaBinaryVersionProperties)
(symbolVersions
.filter(sp => !bomSymbols.contains(sp._1))
.map {
case (symbol, version) =>
val symb = dotted(symbol)
s""" &lt;$symb&gt;$version&lt;/$symb&gt;"""
} ++ scalaBinaryVersionProperties)
.mkString("&lt;properties&gt;\n", "\n", "\n&lt;/properties&gt;\n")
val bomArtifacts =
if (boms.nonEmpty) {
boms.map {
case (group, artifact, artifactRaw, versionSymbol) =>
val version = symbolVersions.find(_._1 == versionSymbol.head).map(_._2)
if (version.isEmpty) sys.error(s"No version found for ${versionSymbol.head}")
mvn(
group,
artifact,
artifactRaw,
version,
`type` = Some("pom"),
scope = Some("import"),
classifier = None,
indent = " "
)
}.mkString("&lt;dependencyManagement&gt;\n &lt;dependencies&gt;\n", "", " &lt;/dependencies&gt;\n&lt;/dependencyManagement&gt;\n")
} else ""
val artifacts = dependencyPostfixes.map { dp =>
val versionCoordinate = requiredCoordinate(s"version$dp")
mvn(
requiredCoordinate(s"group$dp"),
requiredCoordinate(s"artifact$dp"),
requiredCoordinateRaw(s"artifact$dp"),
requiredCoordinate(s"version$dp"),
if (bomSymbols.contains(versionCoordinate)) None else Some(versionCoordinate),
`type` = None,
coordinate(s"scope$dp"),
coordinate(s"classifier$dp")
coordinate(s"classifier$dp"),
indent = " "
)
}

("xml", symbolProperties + artifacts.mkString("\n"))
("xml", symbolProperties + bomArtifacts ++ artifacts.mkString("&lt;dependencies&gt\n", "", "&lt;/dependencies&gt;"))
}

printer.print(s"""<dt>$tool</dt>""")
Expand Down
50 changes: 50 additions & 0 deletions docs/src/main/paradox/directives/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,53 @@ will be rendered as:
artifact3="akka-http_$scala.binary.version$"
version3="AkkaHttpVersion"
}

## Bill of Materials (BOM) for Maven and Gradle

Similar to the symbolic version names above, [Maven BOMs](http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#bill-of-materials-bom-poms) can point to a BOM which specifies versions for dependencies and frees the local build from setting library versions for those dependencies.

By specifying a `bomGroup`, `bomArtifact` and `bomVersionSymbols` the Maven and Gradle notations will show the notation to import the BOM and leave out the versions for the libraries using the listed symbols. If the BOM covers multiple different versions, list all symbols separated by commas.

Note that the notation works just fine for sbt.

This example (with empty lines for readability)

```
@@dependency[Maven,Gradle,sbt] {
bomGroup=com.typesafe.akka
bomArtifact=akka-with-http-bom_$scala.binary.version$
bomVersionSymbols=AkkaVersion,AkkaHttpVersion
symbol1=AkkaVersion
value1="2.6.13"
symbol2="AkkaHttpVersion"
value2="10.2.2"
group1="com.typesafe.akka"
artifact1="akka-stream_$scala.binary.version$"
version1="AkkaVersion"
group2="com.typesafe.akka"
artifact2="akka-http_$scala.binary.version$"
version2="AkkaHttpVersion"
}
```

will be rendered as:

@@dependency[Maven,Gradle,sbt] {
bomGroup=com.typesafe.akka
bomArtifact=akka-with-http-bom_$scala.binary.version$
bomVersionSymbols=AkkaVersion,AkkaHttpVersion
symbol1=AkkaVersion
value1="2.6.13"
symbol2="AkkaHttpVersion"
value2="10.2.2"
group1="com.typesafe.akka"
artifact1="akka-stream_$scala.binary.version$"
version1="AkkaVersion"
group2="com.typesafe.akka"
artifact2="akka-http_$scala.binary.version$"
version2="AkkaHttpVersion"
}
Loading

0 comments on commit 81ecd30

Please sign in to comment.