project/Doc.scala (163 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* license agreements; and to You under the Apache License, version 2.0:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* This file is part of the Apache Pekko project, which was derived from Akka.
*/
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
import sbt._
import sbtunidoc.BaseUnidocPlugin.autoImport.{ unidoc, unidocAllSources, unidocProjectFilter }
import sbtunidoc.JavaUnidocPlugin.autoImport.JavaUnidoc
import sbtunidoc.ScalaUnidocPlugin.autoImport.ScalaUnidoc
import sbtunidoc.GenJavadocPlugin.autoImport._
import sbt.Keys._
import sbt.File
import scala.annotation.tailrec
import sbt.ScopeFilter.ProjectFilter
object Scaladoc extends AutoPlugin {
object CliOptions {
val scaladocDiagramsEnabled = CliOption("pekko.scaladoc.diagrams", true)
val scaladocAutoAPI = CliOption("pekko.scaladoc.autoapi", true)
}
override def trigger = allRequirements
override def requires = plugins.JvmPlugin
val validateDiagrams = settingKey[Boolean]("Validate generated scaladoc diagrams")
override lazy val projectSettings = {
inTask(doc)(
Seq(
Compile / scalacOptions ++= scaladocOptions(version.value, (ThisBuild / baseDirectory).value),
// -release caused build failures when generating javadoc:
Compile / scalacOptions --= Seq("-release", "8"),
autoAPIMappings := CliOptions.scaladocAutoAPI.get)) ++
Seq(Compile / validateDiagrams := true) ++
CliOptions.scaladocDiagramsEnabled.ifTrue(Compile / doc := {
val docs = (Compile / doc).value
if ((Compile / validateDiagrams).value)
scaladocVerifier(docs)
docs
})
}
def scaladocOptions(ver: String, base: File): List[String] = {
val urlString = GitHub.url(ver) + "/€{FILE_PATH_EXT}#L€{FILE_LINE}"
val opts = List(
"-implicits",
"-groups",
"-doc-source-url",
urlString,
"-sourcepath",
base.getAbsolutePath,
"-doc-title",
"Apache Pekko",
"-doc-version",
ver,
"-doc-canonical-base-url",
"https://pekko.apache.org/api/pekko/current/")
CliOptions.scaladocDiagramsEnabled.ifTrue("-diagrams").toList ::: opts
}
def scaladocVerifier(file: File): File = {
@tailrec
def findHTMLFileWithDiagram(dirs: Seq[File]): Boolean = {
if (dirs.isEmpty) false
else {
val curr = dirs.head
val (newDirs, files) = curr.listFiles.partition(_.isDirectory)
val rest = dirs.tail ++ newDirs
val hasDiagram = files.exists { f =>
val name = f.getName
if (name.endsWith(".html") && !name.startsWith("index-") &&
!name.equals("index.html") && !name.equals("package.html")) {
val source = scala.io.Source.fromFile(f)(scala.io.Codec.UTF8)
val hd =
try source
.getLines()
.exists(lines =>
lines.contains(
"<div class=\"toggleContainer block diagram-container\" id=\"inheritance-diagram-container\">") ||
lines.contains("<svg id=\"graph"))
catch {
case e: Exception =>
throw new IllegalStateException("Scaladoc verification failed for file '" + f + "'", e)
} finally source.close()
hd
} else false
}
hasDiagram || findHTMLFileWithDiagram(rest)
}
}
// if we have generated scaladoc and none of the files have a diagram then fail
if (file.exists() && !findHTMLFileWithDiagram(List(file)))
sys.error("ScalaDoc diagrams not generated!")
else
file
}
}
/**
* For projects with few (one) classes there might not be any diagrams.
*/
object ScaladocNoVerificationOfDiagrams extends AutoPlugin {
override def trigger = noTrigger
override def requires = Scaladoc
override lazy val projectSettings = Seq(Compile / Scaladoc.validateDiagrams := false)
}
/**
* Unidoc settings for root project. Adds unidoc command.
*/
object UnidocRoot extends AutoPlugin {
object CliOptions {
val genjavadocEnabled = CliOption("pekko.genjavadoc.enabled", false)
}
object autoImport {
val unidocRootIgnoreProjects = settingKey[Seq[ProjectReference]]("Projects to ignore when generating unidoc")
}
import autoImport._
override def trigger = noTrigger
override def requires =
UnidocRoot.CliOptions.genjavadocEnabled
.ifTrue(sbtunidoc.ScalaUnidocPlugin && sbtunidoc.JavaUnidocPlugin && sbtunidoc.GenJavadocPlugin)
.getOrElse(sbtunidoc.ScalaUnidocPlugin)
val pekkoSettings = UnidocRoot.CliOptions.genjavadocEnabled
.ifTrue(Seq(
JavaUnidoc / unidoc / javacOptions := {
if (JdkOptions.isJdk8) Seq("-Xdoclint:none")
else Seq("-Xdoclint:none", "--ignore-source-errors", "--no-module-directories")
}))
.getOrElse(Nil)
override lazy val projectSettings = {
def unidocRootProjectFilter(ignoreProjects: Seq[ProjectReference]): ProjectFilter =
ignoreProjects.foldLeft(inAnyProject) { _ -- inProjects(_) }
inTask(unidoc)(
Seq(
ScalaUnidoc / unidocProjectFilter := unidocRootProjectFilter(unidocRootIgnoreProjects.value),
JavaUnidoc / unidocProjectFilter := unidocRootProjectFilter(unidocRootIgnoreProjects.value),
Compile / doc / apiMappings ++= {
val entries: Seq[Attributed[File]] = (LocalProject("slf4j") / Compile / fullClasspath).value
def mappingsFor(organization: String, names: List[String], location: String,
revision: String => String = identity): Seq[(File, URL)] = {
for {
entry: Attributed[File] <- entries
module: ModuleID <- entry.get(moduleID.key)
if module.organization == organization
if names.exists(module.name.startsWith)
} yield entry.data -> url(location.format(module.revision))
}
val mappings: Seq[(File, URL)] =
mappingsFor("org.slf4j", List("slf4j-api"), "https://www.javadoc.io/doc/org.slf4j/slf4j-api/%s/")
mappings.toMap
},
ScalaUnidoc / apiMappings := (Compile / doc / apiMappings).value) ++
UnidocRoot.CliOptions.genjavadocEnabled
.ifTrue(Seq(JavaUnidoc / unidocAllSources ~= { v =>
v.map(
_.filterNot(s =>
// org.apache.pekko.stream.scaladsl.GraphDSL.Implicits.ReversePortsOps
// contains code that genjavadoc turns into (probably
// incorrect) Java code that in turn confuses the javadoc
// tool.
s.getAbsolutePath.endsWith("scaladsl/GraphDSL.java") ||
// Since adding -P:genjavadoc:strictVisibility=true,
// the javadoc tool would NullPointerException while
// determining the upper bound for some generics:
s.getAbsolutePath.endsWith("TopicImpl.java") ||
s.getAbsolutePath.endsWith("PersistencePlugin.java") ||
s.getAbsolutePath.endsWith("GraphDelegate.java") ||
s.getAbsolutePath.contains("/impl/")))
}))
.getOrElse(Nil))
}
}
/**
* Unidoc settings for every multi-project. Adds genjavadoc specific settings.
*/
object BootstrapGenjavadoc extends AutoPlugin {
override def trigger = allRequirements
override def requires =
UnidocRoot.CliOptions.genjavadocEnabled
.ifTrue {
// require 11, fail fast for 8, 9, 10
require(JdkOptions.isJdk11orHigher, "Javadoc generation requires at least jdk 11")
sbtunidoc.GenJavadocPlugin
}
.getOrElse(plugins.JvmPlugin)
override lazy val projectSettings = UnidocRoot.CliOptions.genjavadocEnabled
.ifTrue(Seq(
unidocGenjavadocVersion := "0.18",
Compile / scalacOptions ++= Seq(
"-P:genjavadoc:fabricateParams=false",
"-P:genjavadoc:suppressSynthetic=false",
"-P:genjavadoc:strictVisibility=true")))
.getOrElse(Nil)
}