diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index bbb27c9e0..c4f7468b5 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -2030,7 +2030,10 @@ object Defaults extends BuildCommon { val xapisFiles = xapis.map { (k, v) => converter.toPath(k).toFile() -> v } - val options = sOpts ++ Opts.doc.externalAPI(xapisFiles) + val externalApiOpts = + if (ScalaArtifacts.isScala3(sv)) Opts.doc.externalAPIScala3(xapisFiles) + else Opts.doc.externalAPI(xapisFiles) + val options = sOpts ++ externalApiOpts val scalac = cs.scalac match case ac: AnalyzingCompiler => ac.onArgs(exported(s, "scaladoc")) val docSrcFiles = if ScalaArtifacts.isScala3(sv) then tFiles else srcs diff --git a/main/src/main/scala/sbt/Opts.scala b/main/src/main/scala/sbt/Opts.scala index 10c7417d6..faa54abfa 100644 --- a/main/src/main/scala/sbt/Opts.scala +++ b/main/src/main/scala/sbt/Opts.scala @@ -38,6 +38,37 @@ object Opts { mappings .map { (f, u) => s"${f.getAbsolutePath}#${u.toURL().toExternalForm}" } .mkString("-doc-external-doc:", ",", "") :: Nil + + /** + * Generates Scala 3 scaladoc external mappings option. + * Format: -external-mappings:regex::[scaladoc3|scaladoc|javadoc]::url,... + */ + def externalAPIScala3(mappings: Iterable[(File, URI)]): Seq[String] = + if (mappings.isEmpty) Nil + else + mappings + .map { (f, u) => + val fileName = f.getName + // Escape regex special characters in the filename + val escapedName = fileName.replaceAll("([\\[\\]{}()*+?.\\\\^$|])", "\\\\$1") + // Use a regex pattern that matches the file anywhere in the classpath + val regex = s".*$escapedName" + // Determine if this is javadoc or scaladoc based on the file/URL + val docType = if (isJavaDoc(f, u)) "javadoc" else "scaladoc3" + s"$regex::$docType::${u.toURL().toExternalForm}" + } + .mkString("-external-mappings:", ",", "") :: Nil + + private def isJavaDoc(file: File, uri: URI): Boolean = { + val name = file.getName.toLowerCase + val url = uri.toString.toLowerCase + // Heuristics to detect Java documentation + name.startsWith("rt.jar") || + name.contains("java") && !name.contains("scala") || + url.contains("docs.oracle.com") || + url.contains("javadoc") || + url.contains("/java/") && !url.contains("scala") + } } object resolver { import sbt.io.syntax.* diff --git a/sbt-app/src/sbt-test/actions/doc-external-scala3/build.sbt b/sbt-app/src/sbt-test/actions/doc-external-scala3/build.sbt new file mode 100644 index 000000000..08af6cb8c --- /dev/null +++ b/sbt-app/src/sbt-test/actions/doc-external-scala3/build.sbt @@ -0,0 +1,22 @@ +// Test for issue #6652: Doc task fails with `bad option '-doc-external-doc'` for scala3 project +// This test verifies that autoAPIMappings works correctly with Scala 3's -external-mappings option + +lazy val root = (project in file(".")) + .settings( + scalaVersion := "3.3.4", + autoAPIMappings := true, + libraryDependencies += "org.typelevel" %% "cats-core" % "2.12.0", + TaskKey[Unit]("checkDocGenerated") := { + val docDir = (Compile / doc / target).value + val indexFile = docDir / "index.html" + assert(indexFile.exists(), s"Expected $indexFile to exist after doc generation") + println(s"Documentation generated successfully at $docDir") + }, + TaskKey[Unit]("checkApiMappings") := { + val mappings = (Compile / doc / apiMappings).value + println(s"API Mappings count: ${mappings.size}") + // With autoAPIMappings and cats-core dependency, we should have some mappings + // (cats-core publishes with apiURL) + println(s"API Mappings: ${mappings.take(3).mkString("\n ", "\n ", "")}") + } + ) diff --git a/sbt-app/src/sbt-test/actions/doc-external-scala3/src/main/scala/Example.scala b/sbt-app/src/sbt-test/actions/doc-external-scala3/src/main/scala/Example.scala new file mode 100644 index 000000000..8ca12b898 --- /dev/null +++ b/sbt-app/src/sbt-test/actions/doc-external-scala3/src/main/scala/Example.scala @@ -0,0 +1,7 @@ +package example + +/** An example class to generate documentation for. */ +class Example { + /** Returns a greeting */ + def greet: String = "Hello" +} diff --git a/sbt-app/src/sbt-test/actions/doc-external-scala3/test b/sbt-app/src/sbt-test/actions/doc-external-scala3/test new file mode 100644 index 000000000..81fab5d94 --- /dev/null +++ b/sbt-app/src/sbt-test/actions/doc-external-scala3/test @@ -0,0 +1,12 @@ +# Test for issue #6652: Doc task should use -external-mappings for Scala 3 +# instead of -doc-external-doc which causes "bad option" warnings + +# Show the API mappings that will be used +> checkApiMappings + +# Run the doc task - this should succeed without "bad option" warnings +# Before the fix, this would emit a warning about bad option -doc-external-doc +> doc + +# Verify documentation was actually generated +> checkDocGenerated