Skip to content

Commit c1f8424

Browse files
authored
Fix multiple issues with UnidocModule (#5308)
Originally started because Scaladoc links to the `.scala` files were broken (#5221). Fixed multiple issues along the way: - `UnidocModule` did not support Scala 3. The appropriate code was in mill's build, moved that code to `UnidocModule`. - `UnidocModule` did not collect transitive dependencies, all modules had to be listed. Now it does that by default and you can still override `unidocModuleDeps` to bet the old behaviour back. - The source links option changed it's semantics from Scala 2 to Scala 3, updated it to work properly. - Added tests for this functionality. Breaking API changes: - `unidocDocumentTitle` had a default of "Mill", now we force the user to specify it.
1 parent b6a7611 commit c1f8424

File tree

6 files changed

+141
-20
lines changed

6 files changed

+141
-20
lines changed

example/scalalib/module/15-unidoc/build.mill

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ object foo extends ScalaModule with UnidocModule {
1414
def moduleDeps = Seq(bar)
1515
}
1616

17+
def unidocDocumentTitle = Task { "foo docs" }
1718
def unidocVersion = Some("0.1.0")
1819
def unidocSourceUrl = Some("https://github.com/lihaoyi/test/blob/master")
1920
}

libs/scalalib/src/mill/scalalib/UnidocModule.scala

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,67 @@
11
package mill.scalalib
22
import mill.*
33
import mill.define.BuildCtx
4+
import mill.scalalib.api.JvmWorkerUtil
45

56
/**
67
* Mix this in to any [[ScalaModule]] to provide a [[unidocSite]] target that
78
* can be used to build a unified scaladoc site for this module and all of
89
* its transitive dependencies
910
*/
1011
trait UnidocModule extends ScalaModule {
12+
13+
/** The URL of the source code of this module. */
1114
def unidocSourceUrl: T[Option[String]] = None
1215

16+
/** Passed as `-doc-version` to scaladoc. */
1317
def unidocVersion: T[Option[String]] = None
1418

1519
def unidocCompileClasspath = Task {
1620
Seq(compile().classes) ++ Task.traverse(moduleDeps)(_.compileClasspath)().flatten
1721
}
1822

23+
/**
24+
* Which module dependencies to include in the scaladoc site.
25+
*
26+
* By default, all transitive module dependencies are included.
27+
*/
28+
def unidocModuleDeps: Seq[JavaModule] = transitiveModuleDeps
29+
1930
def unidocSourceFiles = Task {
20-
allSourceFiles() ++ Task.traverse(moduleDeps)(_.allSourceFiles)().flatten
31+
if (JvmWorkerUtil.isScala3(scalaVersion())) {
32+
// On Scala 3 scaladoc only accepts .tasty files and .jar files
33+
Task.traverse(unidocModuleDeps)(_.compile)().map(_.classes)
34+
.filter(pr => os.exists(pr.path))
35+
.flatMap(pr => os.walk(pr.path))
36+
.filter(path => path.ext == "tasty" || path.ext == "jar")
37+
.map(PathRef(_))
38+
} else
39+
Task.traverse(unidocModuleDeps)(_.allSourceFiles)().flatten
2140
}
2241

2342
/** The title of the scaladoc site. */
24-
def unidocDocumentTitle: T[String] = Task { "Mill" }
43+
def unidocDocumentTitle: T[String]
2544

2645
/** Extra options passed to scaladoc. */
2746
def unidocOptions: T[Seq[String]] = Task { Seq.empty[String] }
2847

2948
/**
30-
* @param local whether to use 'file://' as the `-doc-source-url`.
49+
* @param local whether to use 'file://' as the `-doc-source-url`/`-source-links`.
3150
*/
3251
def unidocCommon(local: Boolean) = Task.Anon {
52+
val scalaVersion0 = scalaVersion()
53+
val onScala3 = JvmWorkerUtil.isScala3(scalaVersion0)
3354

55+
val scalaOrganization0 = scalaOrganization()
56+
val scalaDocClasspath0 = scalaDocClasspath()
57+
val scalacPluginClasspath0 = scalacPluginClasspath()
3458
val unidocSourceFiles0 = unidocSourceFiles()
3559

3660
Task.log.info(s"Staging scaladoc for ${unidocSourceFiles0.length} files")
3761

3862
// the details of the options and jvmWorker call are significantly
39-
// different between scala-2 scaladoc and scala-3 scaladoc
40-
// below is for scala-2 variant
63+
// different between scala-2 scaladoc and scala-3 scaladoc, so make sure to
64+
// use the correct options for the correct version
4165
val options: Seq[String] = Seq(
4266
"-doc-title",
4367
unidocDocumentTitle(),
@@ -48,18 +72,35 @@ trait UnidocModule extends ScalaModule {
4872
) ++
4973
unidocVersion().toSeq.flatMap(Seq("-doc-version", _)) ++
5074
unidocSourceUrl().toSeq.flatMap { url =>
75+
val sourceLinksOption = if (onScala3) "-source-links" else "-doc-source-url"
76+
5177
if (local) Seq(
52-
"-doc-source-url",
53-
"file://€{FILE_PATH}.scala"
54-
)
55-
else Seq(
56-
"-doc-source-url",
57-
url + "€{FILE_PATH}.scala",
58-
"-sourcepath",
59-
BuildCtx.workspaceRoot.toString
78+
sourceLinksOption,
79+
"file://€{FILE_PATH_EXT}"
6080
)
81+
else {
82+
val workspaceRoot = BuildCtx.workspaceRoot
83+
Seq(
84+
sourceLinksOption,
85+
// Relative path to the workspace
86+
if (onScala3) s"$workspaceRoot=$url€{FILE_PATH_EXT}" else s"$url€{FILE_PATH_EXT}",
87+
"-sourcepath",
88+
workspaceRoot.toString
89+
)
90+
}
6191
} ++ unidocOptions()
6292

93+
Task.log.info(
94+
s"""|Running Unidoc with:
95+
| scalaVersion: ${scalaVersion0}
96+
| scalaOrganization: ${scalaOrganization0}
97+
| options: $options
98+
| scalaDocClasspath: ${scalaDocClasspath0.map(_.path)}
99+
| scalacPluginClasspath: ${scalacPluginClasspath0.map(_.path)}
100+
| unidocSourceFiles: ${unidocSourceFiles0.map(_.path)}
101+
|""".stripMargin
102+
)
103+
63104
jvmWorker().worker().docJar(
64105
scalaVersion(),
65106
scalaOrganization(),
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package bar
2+
3+
class Bar(foo: _root_.foo.Foo)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package foo
2+
3+
class Foo
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package mill.scalalib
2+
3+
import mill.*
4+
import mill.define.Discover
5+
import utest.*
6+
import mill.testkit.UnitTester
7+
import mill.testkit.TestRootModule
8+
import scala.util.Properties
9+
10+
object UnidocTests extends TestSuite {
11+
trait UnidocTestRootModule(scalaVersion: String) extends TestRootModule { self =>
12+
object foo extends ScalaModule {
13+
def scalaVersion = self.scalaVersion
14+
}
15+
16+
object bar extends ScalaModule {
17+
def scalaVersion = self.scalaVersion
18+
19+
override def moduleDeps = Seq(foo)
20+
}
21+
22+
object docs extends UnidocModule {
23+
def scalaVersion = self.scalaVersion
24+
25+
def unidocDocumentTitle = Task { "MyApp" }
26+
27+
def unidocSourceUrl = Task { Some(s"https://github.com/test-org/my-app/blob/main") }
28+
29+
def moduleDeps = Seq(bar)
30+
}
31+
}
32+
33+
object Scala2 extends UnidocTestRootModule("2.13.16") {
34+
lazy val millDiscover = Discover[this.type]
35+
}
36+
37+
object Scala3 extends UnidocTestRootModule("3.7.0") {
38+
lazy val millDiscover = Discover[this.type]
39+
}
40+
41+
val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "unidoc"
42+
43+
def tests: Tests = Tests {
44+
def doTest(module: UnidocTestRootModule, site: Boolean, isScala3: Boolean) =
45+
UnitTester(module, resourcePath).scoped { eval =>
46+
val task = if (site) module.docs.unidocSite else module.docs.unidocLocal
47+
val Right(_) = eval.apply(task): @unchecked
48+
val dest = eval.outPath / "docs" / (if (site) "unidocSite.dest" else "unidocLocal.dest")
49+
val sandbox = os.pwd
50+
51+
def fixWindowsPath(path: String) =
52+
if (Properties.isWin) path.replace("\\", "/")
53+
else path
54+
55+
val baseUrl =
56+
if (site) "https://github.com/test-org/my-app/blob/main"
57+
else {
58+
fixWindowsPath(
59+
if (isScala3) s"file://${module.moduleDir.toString.replace(sandbox.toString, "")}"
60+
else module.moduleDir.toString
61+
)
62+
}
63+
assert(
64+
// both modules should be present
65+
os.exists(dest / "foo" / "Foo.html"),
66+
os.exists(dest / "bar" / "Bar.html"),
67+
// the source links should point to the correct files
68+
os.read(dest / "foo" / "Foo.html").contains(s"$baseUrl/foo/src/foo/Foo.scala"),
69+
os.read(dest / "bar" / "Bar.html").contains(s"$baseUrl/bar/src/bar/Bar.scala")
70+
)
71+
}
72+
73+
test("scala2 site") - doTest(Scala2, site = true, isScala3 = false)
74+
test("scala2 local") - doTest(Scala2, site = false, isScala3 = false)
75+
test("scala3 site") - doTest(Scala3, site = true, isScala3 = true)
76+
test("scala3 local") - doTest(Scala3, site = false, isScala3 = true)
77+
}
78+
}

website/package.mill

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,8 @@ object `package` extends mill.Module {
1414
// This module isn't really a ScalaModule, but we use it to generate
1515
// consolidated documentation using the Scaladoc tool.
1616
object site extends UnidocModule {
17-
def unidocSourceFiles = Task {
18-
(Seq(compile().classes) ++ Task.traverse(moduleDeps)(_.compile)().map(_.classes))
19-
.filter(pr => os.exists(pr.path))
20-
.flatMap(pr => os.walk(pr.path))
21-
.filter(_.ext == "tasty")
22-
.map(PathRef(_))
23-
}
17+
def unidocDocumentTitle = Task { "Mill" }
18+
2419
def unidocCompileClasspath =
2520
super.unidocCompileClasspath().filter { ref =>
2621
// Workaround for https://github.com/scala/bug/issues/10028

0 commit comments

Comments
 (0)