Skip to content

Commit

Permalink
Fix docjar context classloader conflict in scala 3 (#4331)
Browse files Browse the repository at this point in the history
Fixes #4316

This isn't a general solution to make context classloaders behave
properly in the presence of long-lived worker objects instantiated from
classloaders. Rather, we just hard-code support in the Scala3 docjar
callsite to properly set the context classloader there, since these
context-classloader-sensitive pieces of code are pretty rare in Mill and
so we can just deal with them individually as they pop up

CC @joan38
  • Loading branch information
lihaoyi authored Jan 15, 2025
1 parent c3faa7c commit 2a40b17
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 15 deletions.
6 changes: 2 additions & 4 deletions example/extending/jvmcode/3-worker/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ trait GroovyGenerateJavaModule extends JavaModule {
def groovyScript = Task.Source(millSourcePath / "generate.groovy")

def groovyGeneratedResources = Task {
val oldCl = Thread.currentThread().getContextClassLoader
Thread.currentThread().setContextClassLoader(groovyWorker())
try {
mill.api.ClassLoader.withContextClassLoader(groovyWorker()) {
groovyWorker()
.loadClass("groovy.ui.GroovyMain")
.getMethod("main", classOf[Array[String]])
Expand All @@ -41,7 +39,7 @@ trait GroovyGenerateJavaModule extends JavaModule {
(Task.dest / "groovy-generated.html").toString
)
)
} finally Thread.currentThread().setContextClassLoader(oldCl)
}
PathRef(Task.dest)
}

Expand Down
8 changes: 8 additions & 0 deletions main/api/src/mill/api/ClassLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import java.net.{URL, URLClassLoader}
*/
object ClassLoader {

def withContextClassLoader[T](cl: java.lang.ClassLoader)(t: => T): T = {
val thread = Thread.currentThread()
val oldCl = thread.getContextClassLoader()
try {
thread.setContextClassLoader(cl)
t
} finally thread.setContextClassLoader(oldCl)
}
def java9OrAbove: Boolean = !System.getProperty("java.specification.version").startsWith("1.")

def create(
Expand Down
6 changes: 2 additions & 4 deletions runner/src/mill/runner/MillBuildBootstrap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -399,17 +399,15 @@ object MillBuildBootstrap {
selectiveExecution: Boolean
): (Either[String, Seq[Any]], Seq[Watchable], Seq[Watchable]) = {
rootModule.evalWatchedValues.clear()
val previousClassloader = Thread.currentThread().getContextClassLoader
val evalTaskResult =
try {
Thread.currentThread().setContextClassLoader(rootModule.getClass.getClassLoader)
mill.api.ClassLoader.withContextClassLoader(rootModule.getClass.getClassLoader) {
RunScript.evaluateTasksNamed(
evaluator,
targetsAndParams,
SelectMode.Separated,
selectiveExecution = selectiveExecution
)
} finally Thread.currentThread().setContextClassLoader(previousClassloader)
}

val moduleWatched = rootModule.watchedValues.toVector
val addedEvalWatched = rootModule.evalWatchedValues.toVector
Expand Down
18 changes: 11 additions & 7 deletions scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,17 @@ class ZincWorkerImpl(
val hasErrorsMethod = reporter.getClass().getMethod("hasErrors")
!hasErrorsMethod.invoke(reporter).asInstanceOf[Boolean]
} else if (ZincWorkerUtil.isScala3(scalaVersion)) {
val scaladocClass =
compilers.scalac().scalaInstance().loader().loadClass("dotty.tools.scaladoc.Main")
val scaladocMethod = scaladocClass.getMethod("run", classOf[Array[String]])
val reporter =
scaladocMethod.invoke(scaladocClass.getConstructor().newInstance(), args.toArray)
val hasErrorsMethod = reporter.getClass().getMethod("hasErrors")
!hasErrorsMethod.invoke(reporter).asInstanceOf[Boolean]
// DottyDoc makes use of `com.fasterxml.jackson.databind.Module` which
// requires the ContextClassLoader to be set appropriately
mill.api.ClassLoader.withContextClassLoader(getClass.getClassLoader) {
val scaladocClass =
compilers.scalac().scalaInstance().loader().loadClass("dotty.tools.scaladoc.Main")
val scaladocMethod = scaladocClass.getMethod("run", classOf[Array[String]])
val reporter =
scaladocMethod.invoke(scaladocClass.getConstructor().newInstance(), args.toArray)
val hasErrorsMethod = reporter.getClass().getMethod("hasErrors")
!hasErrorsMethod.invoke(reporter).asInstanceOf[Boolean]
}
} else {
val scaladocClass =
compilers.scalac().scalaInstance().loader().loadClass("scala.tools.nsc.ScalaDoc")
Expand Down

0 comments on commit 2a40b17

Please sign in to comment.