This is an automated email from the ASF dual-hosted git repository.

cloud-fan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 5680d1536bdf [SPARK-57485][BUILD] Exclude package-private Scala types 
from the generated Javadoc
5680d1536bdf is described below

commit 5680d1536bdf2d5225086365173dac6f9edf8d31
Author: Wenchen Fan <[email protected]>
AuthorDate: Wed Jun 17 10:19:57 2026 -0700

    [SPARK-57485][BUILD] Exclude package-private Scala types from the generated 
Javadoc
    
    ### What changes were proposed in this pull request?
    
    Spark publishes both a Scaladoc and a Javadoc API site. The Javadoc is 
generated from Scala sources by genjavadoc, and it currently exposes a large 
number of internal types that the Scaladoc correctly hides.
    
    The root cause: a top-level `private[x]` Scala type (e.g. `private[spark] 
trait SupportsDelegationToken`) compiles to a JVM-`public` symbol. genjavadoc 
emits a `public` Java stub for it even with 
`-P:genjavadoc:strictVisibility=true`, and the Javadoc `-public` option can't 
filter it because the stub genuinely is public. Scaladoc, by contrast, honors 
the access qualifier and drops these types.
    
    This PR adds a filter to `JavaUnidoc / unidoc / unidocAllSources` 
(alongside the existing `ignoreUndocumentedPackages`) that drops a generated 
stub `<module>/target/java/<pkg>/<Name>.java` **iff every top-level Scala 
declaration of `<Name>` in that package is `private[...]`**. A public class 
with a `private[...]` companion object (e.g. `SparkConf` — public `class`, 
`private[spark] object`) is kept, since the class itself is public.
    
    ### Why are the changes needed?
    
    The published Javadoc lists ~1.3k internal types (e.g. 
`BarrierCoordinator`, `ContextCleaner`, `ExecutorAllocationManager`, scheduler 
RPC messages, `SupportsDelegationToken`) that are `private[spark]` in source 
and are absent from the Scaladoc. This both misleads Java users about the 
public API surface and makes the two API docs disagree on which types are 
public. Filtering them aligns the Java API doc with the Scala one (format still 
differs, coverage now matches) without touching ge [...]
    
    ### Does this PR introduce _any_ user-facing change?
    
    No code/runtime change. The only user-facing effect is on the generated 
Javadoc site: top-level `private[spark]` (and other qualified-private) Scala 
types no longer appear as public Java classes. Genuinely public APIs — 
including Java-authored ones (`src/main/java`, e.g. the DataSource V2 connector 
interfaces) and Java-friendly wrappers like `org.apache.spark.api.java.JavaRDD` 
— are unaffected.
    
    ### How was this patch tested?
    
    - Validated the filter selects exactly the package-private stubs against 
the already-generated `*/target/java` stubs across `core`, `sql/core`, 
`sql/api`, `sql/catalyst`, `mllib`, `streaming`: it drops the `private[spark]` 
leaks (`SupportsDelegationToken`, `StructuredStreamingIdAwareSchedulerLogging`, 
`InternalAccumulator`, ~1.3k total) while keeping public types and 
public-class-with-private-companion cases (`SparkConf`, `SparkContext`, 
`TaskContext`, `RDD`).
    - Confirmed the build definition compiles via `build/sbt reload`.
    - A full `build/sbt unidoc` run is the end-to-end integration check; 
relying on CI's docs build for that.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Isaac)
    
    This pull request and its description were written by Isaac.
    
    Closes #56538 from cloud-fan/genjavadoc-exclude-package-private.
    
    Authored-by: Wenchen Fan <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
---
 project/SparkBuild.scala | 58 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 56 insertions(+), 2 deletions(-)

diff --git a/project/SparkBuild.scala b/project/SparkBuild.scala
index e55e8c2cea02..a4f92eb4ef07 100644
--- a/project/SparkBuild.scala
+++ b/project/SparkBuild.scala
@@ -1688,6 +1688,59 @@ object Unidoc {
       .map(_.filterNot(_.data.getCanonicalPath.contains("connect-shims")))
   }
 
+  // genjavadoc emits top-level package-private Scala types (`private` or 
`private[x]`, e.g.
+  // `private[spark] trait Foo` or a bare `private class Bar`) as *public* 
Java stubs even with
+  // `-P:genjavadoc:strictVisibility=true`, because a top-level 
package-private type compiles to a
+  // JVM-public symbol, and the Javadoc `-public` option cannot drop a stub 
that really is public.
+  // ScalaDoc honors the qualifier and hides such types, so we drop their 
stubs here to keep the
+  // published Java API doc aligned with the Scala one. A stub 
`<module>/target/java/<pkg>/<Name>.java`
+  // is dropped iff EVERY top-level Scala declaration of `<Name>` in that 
package is `private` or
+  // `private[...]`; a public class with a private companion object (e.g. 
`SparkConf`) is kept, since
+  // the class itself is public. The private regex tolerates other modifiers 
around the access
+  // qualifier (e.g. `final private[x] class`).
+  private val publicTopTypeRe =
+    
"""(?m)^(?:@\w+(?:\([^\n)]*\))?\s+)*(?:(?:final|sealed|abstract|implicit|case)\s+)*(?:class|trait|object)\s+(\w+)""".r
+  private val privateTopTypeRe =
+    
"""(?m)^(?:@\w+(?:\([^\n)]*\))?\s+)*(?:(?:final|sealed|abstract|implicit|case)\s+)*private(?:\[[^\]]+\])?\s+(?:(?:final|sealed|abstract|implicit|case)\s+)*(?:class|trait|object)\s+(\w+)""".r
+
+  private def dropPackagePrivateJavaStubs(sources: Seq[Seq[File]]): 
Seq[Seq[File]] = {
+    val cache = scala.collection.mutable.Map.empty[String, (Set[String], 
Set[String])]
+    def scanPkg(dir: String): (Set[String], Set[String]) = 
cache.getOrElseUpdate(dir, {
+      val d = new File(dir)
+      val scalaFiles =
+        if (d.isDirectory) d.listFiles.filter(_.getName.endsWith(".scala")) 
else Array.empty[File]
+      val text = scalaFiles
+        .map(f => new String(java.nio.file.Files.readAllBytes(f.toPath), 
"UTF-8"))
+        .mkString("\n")
+      (publicTopTypeRe.findAllMatchIn(text).map(_.group(1)).toSet,
+        privateTopTypeRe.findAllMatchIn(text).map(_.group(1)).toSet)
+    })
+    val marker = "/target/java/"
+    sources.map(_.filterNot { f =>
+      val path = f.getCanonicalPath.replace('\\', '/')
+      val idx = path.indexOf(marker)
+      if (idx < 0 || !path.endsWith(".java")) {
+        false
+      } else {
+        val rel = path.substring(idx + marker.length) // <pkg>/<Name>.java
+        val slash = rel.lastIndexOf('/')
+        if (slash < 0) {
+          false
+        } else {
+          val moduleRoot = path.substring(0, idx)
+          val pkgPath = rel.substring(0, slash)
+          val name = f.getName.stripSuffix(".java")
+          val (pub, priv) = Seq("src/main/scala", "src/main/scala-2.13")
+            .map(d => scanPkg(s"$moduleRoot/$d/$pkgPath"))
+            .foldLeft((Set.empty[String], Set.empty[String])) {
+              case ((p, q), (a, b)) => (p ++ a, q ++ b)
+            }
+          priv.contains(name) && !pub.contains(name)
+        }
+      }
+    })
+  }
+
   val unidocSourceBase = settingKey[String]("Base URL of source links in 
Scaladoc.")
 
   lazy val settings = BaseUnidocPlugin.projectSettings ++
@@ -1712,8 +1765,9 @@ object Unidoc {
 
     // Skip class names containing $ and some internal packages in Javadocs
     (JavaUnidoc / unidoc / unidocAllSources) := {
-      ignoreUndocumentedPackages((JavaUnidoc / unidoc / 
unidocAllSources).value)
-        .map(_.filterNot(_.getCanonicalPath.contains("org/apache/hadoop")))
+      dropPackagePrivateJavaStubs(
+        ignoreUndocumentedPackages((JavaUnidoc / unidoc / 
unidocAllSources).value)
+          .map(_.filterNot(_.getCanonicalPath.contains("org/apache/hadoop"))))
     },
 
     (JavaUnidoc / unidoc / javacOptions) := {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to