From 1b91e3e6104442f2723e1102a017b65fa3f297ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 29 Aug 2025 13:46:33 +0300 Subject: [PATCH 01/17] WIP: I don't think this approach will work --- .../internal/SemanticDbJavaModuleApi.scala | 2 + .../constants/src/mill/constants/EnvVars.java | 6 + .../src/mill/constants/OutFiles.java | 20 +- .../bsp-util/src/BspServerTestUtil.scala | 3 +- .../server/src/mill/server/Server.scala | 284 ++++++++++-------- .../mill/javalib/api/CompilationResult.scala | 2 +- .../src/mill/javalib/api/JvmWorkerApi.scala | 16 +- .../javalib/api/internal/JvmWorkerApi.scala | 93 +----- .../api/internal/zinc_operations.scala | 2 + .../MillBackgroundWrapper.java | 2 +- libs/javalib/package.mill | 10 +- .../src/mill/javalib/CompileArgs.scala | 24 ++ .../javalib/src/mill/javalib/CompileFor.scala | 10 - .../javalib/src/mill/javalib/JavaModule.scala | 68 +---- .../src/mill/javalib/JvmWorkerModule.scala | 1 + .../mill/javalib/SemanticDbJavaModule.scala | 175 ++++++++--- .../src/mill/javalib/UnresolvedPath.scala | 15 +- .../src/mill/javalib/zinc/ZincWorker.scala | 12 +- .../src/mill/scalalib/ScalaModule.scala | 2 + libs/util/src/mill/util/Tasks.scala | 3 +- 20 files changed, 401 insertions(+), 349 deletions(-) create mode 100644 libs/javalib/src/mill/javalib/CompileArgs.scala delete mode 100644 libs/javalib/src/mill/javalib/CompileFor.scala diff --git a/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala b/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala index 2744b851acca..6966816e4fc3 100644 --- a/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala +++ b/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala @@ -17,6 +17,8 @@ object SemanticDbJavaModuleApi { protected override def initialValue(): Option[String] = None.asInstanceOf[Option[String]] } + private[mill] def clientNeedsSemanticDb(): Boolean = contextSemanticDbVersion.get().isDefined + private[mill] val contextJavaSemanticDbVersion: InheritableThreadLocal[Option[String]] = new InheritableThreadLocal[Option[String]] { protected override def initialValue(): Option[String] = None.asInstanceOf[Option[String]] diff --git a/core/constants/src/mill/constants/EnvVars.java b/core/constants/src/mill/constants/EnvVars.java index 4c9de777f736..5a03be01254d 100644 --- a/core/constants/src/mill/constants/EnvVars.java +++ b/core/constants/src/mill/constants/EnvVars.java @@ -27,6 +27,12 @@ public class EnvVars { */ public static final String MILL_OUTPUT_DIR = "MILL_OUTPUT_DIR"; + /** + * Output directory where Mill workers' state and Mill tasks output should be + * written to for the Mill instances running in BSP mode. + */ + public static final String MILL_BSP_OUTPUT_DIR = "MILL_BSP_OUTPUT_DIR"; + /** * If set to "1", Mill will re-use the regular @{Link OutFiles#out} folder instead of * using a separate one for BSP output. diff --git a/core/constants/src/mill/constants/OutFiles.java b/core/constants/src/mill/constants/OutFiles.java index 89ede26e9e42..64c795b0e4fa 100644 --- a/core/constants/src/mill/constants/OutFiles.java +++ b/core/constants/src/mill/constants/OutFiles.java @@ -12,6 +12,12 @@ public class OutFiles { */ private static final String envOutOrNull = System.getenv(EnvVars.MILL_OUTPUT_DIR); + /** + * Allows us to override the `out/mill-bsp-out` folder from the environment via the + * {@link EnvVars#MILL_BSP_OUTPUT_DIR} variable. + */ + private static final String envBspOutOrNull = System.getenv(EnvVars.MILL_BSP_OUTPUT_DIR); + /** @see EnvVars#MILL_NO_SEPARATE_BSP_OUTPUT_DIR */ public static final boolean mergeBspOut = "1".equals(System.getenv(EnvVars.MILL_NO_SEPARATE_BSP_OUTPUT_DIR)); @@ -28,11 +34,17 @@ public class OutFiles { */ public static final String out = envOutOrNull == null ? defaultOut : envOutOrNull; + /** + * Default hard-coded value for the Mill `out/` folder path when Mill is running in BSP mode. Unless you know + * what you are doing, you should favor using {@link #outFor} instead. + */ + public static final String defaultBspOut = "out/mill-bsp-out"; + /** * Path of the Mill `out/` folder when Mill is running in BSP mode. Unless you know * what you are doing, you should favor using {@link #outFor} instead. */ - public static final String bspOut = "out/mill-bsp-out"; + public static final String bspOut = envBspOutOrNull == null ? defaultBspOut : envBspOutOrNull; /** * Path of the Mill {@link #out} folder. @@ -40,12 +52,12 @@ public class OutFiles { * @param outMode If {@link #envOutOrNull} is set, this parameter is ignored. */ public static String outFor(OutFolderMode outMode) { - if (envOutOrNull != null) return envOutOrNull; switch (outMode) { case REGULAR: - return out; + return envOutOrNull != null ? envOutOrNull : out; case BSP: - return mergeBspOut ? out : bspOut; + if (envBspOutOrNull != null) return envBspOutOrNull; + return mergeBspOut ? outFor(OutFolderMode.REGULAR) : bspOut; default: throw new IllegalArgumentException("Unknown out folder mode: " + outMode); } diff --git a/integration/bsp-util/src/BspServerTestUtil.scala b/integration/bsp-util/src/BspServerTestUtil.scala index c7ac09eec051..548dd6cdc041 100644 --- a/integration/bsp-util/src/BspServerTestUtil.scala +++ b/integration/bsp-util/src/BspServerTestUtil.scala @@ -11,6 +11,7 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonRequest import java.io.ByteArrayOutputStream import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.{CompletableFuture, ExecutorService, Executors, ThreadFactory} +import scala.annotation.unused import scala.jdk.CollectionConverters.* import scala.reflect.ClassTag @@ -226,7 +227,7 @@ object BspServerTestUtil { workspacePath: os.Path, coursierCache: os.Path = os.Path(CacheDefaults.location), javaHome: os.Path = os.Path(sys.props("java.home")), - javaVersion: String = sys.props("java.version") + @unused javaVersion: String = sys.props("java.version") ): Seq[(String, String)] = Seq( workspacePath.toURI.toASCIIString.stripSuffix("/") -> "file:///workspace", diff --git a/libs/daemon/server/src/mill/server/Server.scala b/libs/daemon/server/src/mill/server/Server.scala index 96e9ee29275b..e15cdb335e7b 100644 --- a/libs/daemon/server/src/mill/server/Server.scala +++ b/libs/daemon/server/src/mill/server/Server.scala @@ -18,6 +18,7 @@ import scala.util.control.NonFatal * connections. */ abstract class Server(args: Server.Args) { + import args.* val processId: Long = Server.computeProcessId() @@ -126,148 +127,178 @@ abstract class Server(args: Server.Args) { afterClose = () => { serverLog("daemonLock released") } - ) { locked => - serverLog("server file locked") - val serverSocket = new java.net.ServerSocket(0, 0, InetAddress.getByName(null)) - Server.watchProcessIdFile( - daemonDir / DaemonFiles.processId, - processId, - running = () => !serverSocket.isClosed, - exit = msg => { - serverLog(s"watchProcessIdFile: $msg") - serverSocket.close() - }, - log = serverLog + )(whenServerLockAcquired(_, socketPortFile, initialSystemProperties)).getOrElse { + val serverLog = Try(os.read(daemonDir / DaemonFiles.serverLog)).toOption + val processIdLog = Try(os.read(daemonDir / DaemonFiles.processId)).toOption + val socketPort = Try(os.read(daemonDir / DaemonFiles.socketPort)).toOption + val stdout = Try(os.read(daemonDir / DaemonFiles.stdout)).toOption + val stderr = Try(os.read(daemonDir / DaemonFiles.stderr)).toOption + + def render(opt: Option[String]) = opt match { + case Some(s) => "\n" + s.linesIterator.map(" " + _).mkString("\n") + "\n" + case None => "" + } + + throw IllegalStateException( + s"""[${timestampStr()}] Cannot launch Mill server (pid:$processId): + | the daemon lock is already taken by another process. + | + |Currently running server information: + | + | Process ID: ${render(processIdLog)} + | TCP socket port: ${render(socketPort)} + | Daemon STDOUT: ${render(stdout)} + | Daemon STDERR: ${render(stderr)} + | Server log: ${render(serverLog)} + """.stripMargin ) + } + } catch { + case e: Throwable => + serverLog("server loop error: " + e) + serverLog("server loop stack trace: " + e.getStackTrace.mkString("\n")) + throw e + } finally { + serverLog("exiting server") + } + } + + private def whenServerLockAcquired( + locked: AutoCloseable, + socketPortFile: os.Path, + initialSystemProperties: Map[String, String] + ): Unit = { + serverLog("server file locked") + val serverSocket = new java.net.ServerSocket(0, 0, InetAddress.getByName(null)) + Server.watchProcessIdFile( + daemonDir / DaemonFiles.processId, + processId, + running = () => !serverSocket.isClosed, + exit = msg => { + serverLog(s"watchProcessIdFile: $msg") + serverSocket.close() + }, + log = serverLog + ) - // Wrapper object to encapsulate `activeConnections` and `inactiveTimestampOpt`, - // ensuring they get incremented and decremented together across multiple threads - // and never get out of sync - object connectionTracker { - private var activeConnections = 0 - private var inactiveTimestampOpt: Option[Long] = None + // Wrapper object to encapsulate `activeConnections` and `inactiveTimestampOpt`, + // ensuring they get incremented and decremented together across multiple threads + // and never get out of sync + object connectionTracker { + private var activeConnections = 0 + private var inactiveTimestampOpt: Option[Long] = None - def wrap(t: => Unit): Unit = synchronized { - if (!serverSocket.isClosed) { - t - } - } + def wrap(t: => Unit): Unit = synchronized { + if (!serverSocket.isClosed) { + t + } + } - def increment(): Unit = wrap { - activeConnections += 1 - serverLog(s"$activeConnections active connections") - inactiveTimestampOpt = None - } + def increment(): Unit = wrap { + activeConnections += 1 + serverLog(s"$activeConnections active connections") + inactiveTimestampOpt = None + } - def decrement(): Unit = wrap { - activeConnections -= 1 - serverLog(s"$activeConnections active connections") - if (activeConnections == 0) { - inactiveTimestampOpt = Some(System.currentTimeMillis()) - } - } + def decrement(): Unit = wrap { + activeConnections -= 1 + serverLog(s"$activeConnections active connections") + if (activeConnections == 0) { + inactiveTimestampOpt = Some(System.currentTimeMillis()) + } + } - def closeIfTimedOut(): Unit = wrap { - // Explicit matching as we're doing this every 1ms. - acceptTimeoutMillis match { + def closeIfTimedOut(): Unit = wrap { + // Explicit matching as we're doing this every 1ms. + acceptTimeoutMillis match { + case None => // Do nothing + case Some(acceptTimeoutMillis) => + inactiveTimestampOpt match { case None => // Do nothing - case Some(acceptTimeoutMillis) => - inactiveTimestampOpt match { - case None => // Do nothing - case Some(inactiveTimestamp) => - if (System.currentTimeMillis() - inactiveTimestamp > acceptTimeoutMillis) { - serverLog(s"shutting down due inactivity") - serverSocket.close() - } + case Some(inactiveTimestamp) => + if (System.currentTimeMillis() - inactiveTimestamp > acceptTimeoutMillis) { + serverLog(s"shutting down due inactivity") + serverSocket.close() } } - } } + } + } - try { - os.write.over(socketPortFile, serverSocket.getLocalPort.toString) - serverLog("listening on port " + serverSocket.getLocalPort) - - def systemExit(reason: String, exitCode: Int) = { - serverLog( - s"`systemExit` invoked (reason: $reason), shutting down with exit code $exitCode" - ) + try { + os.write.over(socketPortFile, serverSocket.getLocalPort.toString) + serverLog("listening on port " + serverSocket.getLocalPort) - // Explicitly close serverSocket before exiting otherwise it can keep the - // server alive 500-1000ms before letting it exit properly - serverSocket.close() - serverLog("serverSocket closed") + def systemExit(reason: String, exitCode: Int) = { + serverLog( + s"`systemExit` invoked (reason: $reason), shutting down with exit code $exitCode" + ) - // Explicitly release process lock to indicate this server will not be - // taking any more requests, and a new server should be spawned if necessary. - // Otherwise, launchers may continue trying to connect to the server and - // failing since the socket is closed. - locked.close() + // Explicitly close serverSocket before exiting otherwise it can keep the + // server alive 500-1000ms before letting it exit properly + serverSocket.close() + serverLog("serverSocket closed") - sys.exit(exitCode) - } + // Explicitly release process lock to indicate this server will not be + // taking any more requests, and a new server should be spawned if necessary. + // Otherwise, launchers may continue trying to connect to the server and + // failing since the socket is closed. + locked.close() - val timeoutThread = new Thread( - () => { - while (!serverSocket.isClosed) { - Thread.sleep(1) - connectionTracker.closeIfTimedOut() - } - }, - "MillServerTimeoutThread" - ) - timeoutThread.start() + sys.exit(exitCode) + } + val timeoutThread = new Thread( + () => { while (!serverSocket.isClosed) { - val socketOpt = - try Some(serverSocket.accept()) - catch { - case _: java.net.SocketException => None - } + Thread.sleep(1) + connectionTracker.closeIfTimedOut() + } + }, + "MillServerTimeoutThread" + ) + timeoutThread.start() - socketOpt match { - case Some(sock) => - val socketInfo = Server.SocketInfo(sock) - serverLog(s"handling run for $socketInfo") - new Thread( - () => - try { - connectionTracker.increment() - runForSocket( - systemExit, - sock, - socketInfo, - initialSystemProperties, - () => serverSocket.close() - ) - } catch { - case e: Throwable => - serverLog( - s"""$socketInfo error: $e - | - |${e.getStackTrace.mkString("\n")} - |""".stripMargin - ) - } finally { - connectionTracker.decrement() - sock.close() - }, - s"HandleRunThread-$socketInfo" - ).start() - case None => - } + while (!serverSocket.isClosed) { + val socketOpt = + try Some(serverSocket.accept()) + catch { + case _: java.net.SocketException => None } - } finally serverSocket.close() - }.getOrElse(throw new Exception("Mill server process already present")) - } catch { - case e: Throwable => - serverLog("server loop error: " + e) - serverLog("server loop stack trace: " + e.getStackTrace.mkString("\n")) - throw e - } finally { - serverLog("exiting server") - } + socketOpt match { + case Some(sock) => + val socketInfo = Server.SocketInfo(sock) + serverLog(s"handling run for $socketInfo") + new Thread( + () => + try { + connectionTracker.increment() + runForSocket( + systemExit, + sock, + socketInfo, + initialSystemProperties, + () => serverSocket.close() + ) + } catch { + case e: Throwable => + serverLog( + s"""$socketInfo error: $e + | + |${e.getStackTrace.mkString("\n")} + |""".stripMargin + ) + } finally { + connectionTracker.decrement() + sock.close() + }, + s"HandleRunThread-$socketInfo" + ).start() + case None => + } + } + } finally serverSocket.close() } /** @@ -315,6 +346,7 @@ abstract class Server(args: Server.Args) { @volatile var lastClientAlive = true val stopServerFromCheckClientAlive: Server.StopServer = (reason, exitCode) => stopServer("checkClientAlive", reason, exitCode, Some(data)) + def checkClientAlive() = { val result = try checkIfClientAlive(connectionData, stopServerFromCheckClientAlive, data) @@ -421,12 +453,13 @@ abstract class Server(args: Server.Args) { } } } + object Server { /** - * @param daemonDir directory used for exchanging pre-TCP data with a client + * @param daemonDir directory used for exchanging pre-TCP data with a client * @param acceptTimeout shuts down after this timeout if no clients are connected - * @param bufferSize size of the buffer used to read/write from/to the client + * @param bufferSize size of the buffer used to read/write from/to the client */ case class Args( daemonDir: os.Path, @@ -438,11 +471,12 @@ object Server { /** * @param remote the address of the client - * @param local the address of the server + * @param local the address of the server */ case class SocketInfo(remote: SocketAddress, local: SocketAddress) { override def toString: String = s"SocketInfo(remote=$remote, local=$local)" } + object SocketInfo { def apply(socket: Socket): SocketInfo = apply(socket.getRemoteSocketAddress, socket.getLocalSocketAddress) diff --git a/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala b/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala index bb9325625344..22f7b5c5daef 100644 --- a/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala +++ b/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala @@ -4,7 +4,7 @@ import mill.api.PathRef import mill.api.JsonFormatters._ // analysisFile is represented by os.Path, so we won't break caches after file changes -case class CompilationResult(analysisFile: os.Path, classes: PathRef) +case class CompilationResult(analysisFile: os.Path, classes: PathRef, semanticDbFiles: Option[PathRef]) object CompilationResult { implicit val jsonFormatter: upickle.default.ReadWriter[CompilationResult] = diff --git a/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala index 806cfc46e659..158252324f36 100644 --- a/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala @@ -6,9 +6,10 @@ import mill.api.daemon.internal.CompileProblemReporter object JvmWorkerApi { type Ctx = mill.api.TaskCtx.Dest & mill.api.TaskCtx.Log & mill.api.TaskCtx.Env } +@deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") trait JvmWorkerApi { - /** Compile a Java-only project */ + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") def compileJava( upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], @@ -18,9 +19,10 @@ trait JvmWorkerApi { reporter: Option[CompileProblemReporter], reportCachedProblems: Boolean, incrementalCompilation: Boolean - )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] + )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] = + throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") - /** Compile a mixed Scala/Java or Scala-only project */ + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") def compileMixed( upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], @@ -36,9 +38,10 @@ trait JvmWorkerApi { reportCachedProblems: Boolean, incrementalCompilation: Boolean, auxiliaryClassFileExtensions: Seq[String] - )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] + )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] = + throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") - /** Compiles a Scaladoc jar. */ + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") def docJar( scalaVersion: String, scalaOrganization: String, @@ -46,5 +49,6 @@ trait JvmWorkerApi { scalacPluginClasspath: Seq[PathRef], javaHome: Option[os.Path], args: Seq[String] - )(using ctx: JvmWorkerApi.Ctx): Boolean + )(using ctx: JvmWorkerApi.Ctx): Boolean = + throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") } diff --git a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala index 8b4d41d536a9..f92bb0eaa990 100644 --- a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala @@ -4,9 +4,11 @@ import mill.api.{PathRef, Result} import mill.api.daemon.internal.CompileProblemReporter import mill.javalib.api.CompilationResult import mill.javalib.api.JvmWorkerApi as PublicJvmWorkerApi -import mill.javalib.api.JvmWorkerApi.Ctx -import os.Path +import scala.annotation.nowarn + +//noinspection ScalaDeprecation +@nowarn("cat=deprecation") trait JvmWorkerApi extends PublicJvmWorkerApi { /** Compile a Java-only project. */ @@ -32,93 +34,6 @@ trait JvmWorkerApi extends PublicJvmWorkerApi { op: ZincScaladocJar, javaHome: Option[os.Path] )(using context: JvmWorkerApi.Ctx): Boolean - - // public API forwarder - override def compileJava( - upstreamCompileOutput: Seq[CompilationResult], - sources: Seq[Path], - compileClasspath: Seq[Path], - javaHome: Option[Path], - javacOptions: Seq[String], - reporter: Option[CompileProblemReporter], - reportCachedProblems: Boolean, - incrementalCompilation: Boolean - )(using ctx: Ctx): Result[CompilationResult] = { - val jOpts = JavaCompilerOptions(javacOptions) - compileJava( - ZincCompileJava( - upstreamCompileOutput = upstreamCompileOutput, - sources = sources, - compileClasspath = compileClasspath, - javacOptions = jOpts.compiler, - incrementalCompilation = incrementalCompilation - ), - javaHome = javaHome, - javaRuntimeOptions = jOpts.runtime, - reporter = reporter, - reportCachedProblems = reportCachedProblems - ) - } - - // public API forwarder - override def compileMixed( - upstreamCompileOutput: Seq[CompilationResult], - sources: Seq[Path], - compileClasspath: Seq[Path], - javaHome: Option[Path], - javacOptions: Seq[String], - scalaVersion: String, - scalaOrganization: String, - scalacOptions: Seq[String], - compilerClasspath: Seq[PathRef], - scalacPluginClasspath: Seq[PathRef], - reporter: Option[CompileProblemReporter], - reportCachedProblems: Boolean, - incrementalCompilation: Boolean, - auxiliaryClassFileExtensions: Seq[String] - )(using ctx: Ctx): Result[CompilationResult] = { - val jOpts = JavaCompilerOptions(javacOptions) - compileMixed( - ZincCompileMixed( - upstreamCompileOutput = upstreamCompileOutput, - sources = sources, - compileClasspath = compileClasspath, - javacOptions = jOpts.compiler, - scalaVersion = scalaVersion, - scalaOrganization = scalaOrganization, - scalacOptions = scalacOptions, - compilerClasspath = compilerClasspath, - scalacPluginClasspath = scalacPluginClasspath, - incrementalCompilation = incrementalCompilation, - auxiliaryClassFileExtensions = auxiliaryClassFileExtensions - ), - javaHome = javaHome, - javaRuntimeOptions = jOpts.runtime, - reporter = reporter, - reportCachedProblems = reportCachedProblems - ) - } - - // public API forwarder - override def docJar( - scalaVersion: String, - scalaOrganization: String, - compilerClasspath: Seq[PathRef], - scalacPluginClasspath: Seq[PathRef], - javaHome: Option[Path], - args: Seq[String] - )(using ctx: Ctx): Boolean = { - scaladocJar( - ZincScaladocJar( - scalaVersion = scalaVersion, - scalaOrganization = scalaOrganization, - compilerClasspath = compilerClasspath, - scalacPluginClasspath = scalacPluginClasspath, - args = args - ), - javaHome = javaHome - ) - } } object JvmWorkerApi { type Ctx = PublicJvmWorkerApi.Ctx diff --git a/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala b/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala index bac965eaa556..83455e423f55 100644 --- a/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala +++ b/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala @@ -6,6 +6,7 @@ import mill.api.JsonFormatters.* /** Compiles Java-only sources. */ case class ZincCompileJava( + compileTo: os.Path, upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], compileClasspath: Seq[os.Path], @@ -15,6 +16,7 @@ case class ZincCompileJava( /** Compiles Java and Scala sources. */ case class ZincCompileMixed( + compileTo: os.Path, upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], compileClasspath: Seq[os.Path], diff --git a/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java b/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java index 76bddf93b28a..4b064d1563ef 100644 --- a/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java +++ b/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java @@ -173,7 +173,7 @@ private static void checkIfWeStillNeedToBeRunning( } } - static Optional readPreviousPid(Path pidFilePath) { + public static Optional readPreviousPid(Path pidFilePath) { try { var pidStr = Files.readString(pidFilePath); return Optional.of(Long.parseLong(pidStr)); diff --git a/libs/javalib/package.mill b/libs/javalib/package.mill index 8b237ec8e91e..04c8cd3b4901 100644 --- a/libs/javalib/package.mill +++ b/libs/javalib/package.mill @@ -18,8 +18,14 @@ import millbuild.* object `package` extends MillStableScalaModule { - def moduleDeps = - Seq(build.libs.util, build.libs.rpc, build.libs.javalib.api, build.libs.javalib.testrunner) + def moduleDeps = Seq( + build.libs.util, + build.libs.rpc, + build.libs.javalib.api, + build.libs.javalib.testrunner, + build.libs.javalib.backgroundwrapper + ) + def mvnDeps = Seq(Deps.scalaXml) ++ // despite compiling with Scala 3, we need to include scala-reflect diff --git a/libs/javalib/src/mill/javalib/CompileArgs.scala b/libs/javalib/src/mill/javalib/CompileArgs.scala new file mode 100644 index 000000000000..f20052470399 --- /dev/null +++ b/libs/javalib/src/mill/javalib/CompileArgs.scala @@ -0,0 +1,24 @@ +package mill.javalib + +import mill.api.Task + +/** + * @param compileTo the directory to which the classes will be compiled. We need this because we can't use the task's + * destination folder as we need that folder to be persistent and persistent tasks can not take + * arguments. + * @param forceSemanticDb if true then generates semanticdb files even if [[semanticDbWillBeNeeded]] returns false. + * This is useful when you have tasks like scalafix which need to generate semanticdb files + * even if the Mill BSP client doesn't need them. + */ +private[mill] case class CompileArgs( + compileTo: os.Path, + forceSemanticDb: Boolean +) +private[mill] object CompileArgs { + // TODO review: this is a terrible hack + def compileTaskPath(thisTaskDest: os.Path, compileTask: Task.Named[?]): os.Path = + thisTaskDest / ".." / s"${compileTask.ctx.segments.last.value}.dest" + + /** Arguments for the default compilation. */ + def default(compileTo: os.Path): CompileArgs = CompileArgs(compileTo, forceSemanticDb = false) +} diff --git a/libs/javalib/src/mill/javalib/CompileFor.scala b/libs/javalib/src/mill/javalib/CompileFor.scala deleted file mode 100644 index 24f6cc7d44a3..000000000000 --- a/libs/javalib/src/mill/javalib/CompileFor.scala +++ /dev/null @@ -1,10 +0,0 @@ -package mill.javalib - -private[mill] enum CompileFor { - - /** This is a regular compilation, for example for `compile`. */ - case Regular - - /** This is a compilation for SemanticDB, for example for `semanticDbData`. */ - case SemanticDb -} diff --git a/libs/javalib/src/mill/javalib/JavaModule.scala b/libs/javalib/src/mill/javalib/JavaModule.scala index 430d33f03ac7..387bd593d061 100644 --- a/libs/javalib/src/mill/javalib/JavaModule.scala +++ b/libs/javalib/src/mill/javalib/JavaModule.scala @@ -708,13 +708,6 @@ trait JavaModule Seq(internalDependenciesRepository()) } - /** - * The upstream compilation output of all this module's upstream modules - */ - def upstreamCompileOutput: T[Seq[CompilationResult]] = Task { - Task.traverse(transitiveModuleCompileModuleDeps)(_.compile)() - } - /** * The transitive version of `localClasspath` */ @@ -733,16 +726,19 @@ trait JavaModule * The transitive version of [[compileClasspath]] */ def transitiveCompileClasspath: T[Seq[PathRef]] = Task { - transitiveCompileClasspathTask(CompileFor.Regular)() + transitiveCompileClasspathTask(CompileArgs.default)() } /** * The transitive version of [[compileClasspathTask]] */ - private[mill] def transitiveCompileClasspathTask(compileFor: CompileFor): Task[Seq[PathRef]] = + private[mill] def transitiveCompileClasspathTask(compileArgs: CompileArgs): Task[Seq[PathRef]] = Task.Anon { Task.traverse(transitiveModuleCompileModuleDeps)(m => - Task.Anon { m.localCompileClasspath() ++ Seq(m.compileFor(compileFor)().classes) } + Task.Anon { + val compileResult = m.compileWithArgs.apply()(compileArgs) + compileResult.map(res => m.localCompileClasspath() :+ res.classes) + } )().flatten } @@ -806,13 +802,7 @@ trait JavaModule */ def generatedSources: T[Seq[PathRef]] = Task { Seq.empty[PathRef] } - /** - * Path to sources generated as part of the `compile` step, eg. by Java annotation - * processors which often generate source code alongside classfiles during compilation. - * - * Typically these do not need to be compiled again, and are only used by IDEs - */ - def compileGeneratedSources: T[os.Path] = Task(persistent = true) { Task.dest } + override def compileGeneratedSources: T[os.Path] = Task(persistent = true) { Task.dest } /** * The folders containing all source files fed into the compiler @@ -858,40 +848,13 @@ trait JavaModule os.makeDir.all(compileGenSources) } - val jOpts = JavaCompilerOptions(Seq( - "-s", - compileGenSources.toString - ) ++ javacOptions() ++ mandatoryJavacOptions()) - - jvmWorker() - .internalWorker() - .compileJava( - ZincCompileJava( - upstreamCompileOutput = upstreamCompileOutput(), - sources = allSourceFiles().map(_.path), - compileClasspath = compileClasspath().map(_.path), - javacOptions = jOpts.compiler, - incrementalCompilation = zincIncrementalCompilation() - ), - javaHome = javaHome().map(_.path), - javaRuntimeOptions = jOpts.runtime, - reporter = Task.reporter.apply(hashCode), - reportCachedProblems = zincReportCachedProblems() - ) + ??? } - /** Resolves paths relative to the `out` folder. */ - @internal - private[mill] def resolveRelativeToOut( - task: Task.Named[?], - mkPath: os.SubPath => os.SubPath = identity - ): UnresolvedPath.DestPath = - UnresolvedPath.DestPath(mkPath(os.sub), task.ctx.segments) - /** The path where the compiled classes produced by [[compile]] are stored. */ @internal private[mill] def compileClassesPath: UnresolvedPath.DestPath = - resolveRelativeToOut(compile, _ / "classes") + UnresolvedPath.resolveRelativeToOut(compile, _ / "classes") /** * The path to the compiled classes by [[compile]] without forcing to actually run the compilation. @@ -916,14 +879,15 @@ trait JavaModule needsToMergeResourcesIntoCompileDest: Boolean ): Task[UnresolvedPath] = Task.Anon { - if (needsToMergeResourcesIntoCompileDest) resolveRelativeToOut(bspBuildTargetCompileMerged) + if (needsToMergeResourcesIntoCompileDest) + UnresolvedPath.resolveRelativeToOut(bspBuildTargetCompileMerged) else compileClassesPath } /** * The part of the [[localClasspath]] which is available "after compilation". * - * Keep in sync with [[bspLocalRunClasspath]] + * Keep the return value in sync with [[bspLocalRunClasspath]] */ override def localRunClasspath: T[Seq[PathRef]] = Task { super.localRunClasspath() ++ resources() ++ Seq(compile().classes) @@ -977,15 +941,15 @@ trait JavaModule * * Keep return value in sync with [[bspCompileClasspath]]. */ - def compileClasspath: T[Seq[PathRef]] = Task { compileClasspathTask(CompileFor.Regular)() } + def compileClasspath: T[Seq[PathRef]] = Task { compileClasspathTask(CompileArgs.default)() } /** * All classfiles and resources from upstream modules and dependencies * necessary to compile this module. */ - override private[mill] def compileClasspathTask(compileFor: CompileFor): Task[Seq[PathRef]] = + override private[mill] def compileClasspathTask(compileArgs: CompileArgs): Task[Seq[PathRef]] = Task.Anon { - resolvedMvnDeps() ++ transitiveCompileClasspathTask(compileFor)() ++ localCompileClasspath() + resolvedMvnDeps() ++ transitiveCompileClasspathTask(compileArgs)() ++ localCompileClasspath() } /** @@ -1577,7 +1541,7 @@ trait BomModule extends JavaModule { val sources = allSourceFiles() if (sources.nonEmpty) throw new Exception("A BomModule cannot have sources") - CompilationResult(Task.dest / "zinc", PathRef(Task.dest / "classes")) + CompilationResult(Task.dest / "zinc", PathRef(Task.dest / "classes"), semanticDbFiles = None) } abstract override def resources: T[Seq[PathRef]] = Task { diff --git a/libs/javalib/src/mill/javalib/JvmWorkerModule.scala b/libs/javalib/src/mill/javalib/JvmWorkerModule.scala index 4214d7ab234b..bf725a9d0a0b 100644 --- a/libs/javalib/src/mill/javalib/JvmWorkerModule.scala +++ b/libs/javalib/src/mill/javalib/JvmWorkerModule.scala @@ -48,6 +48,7 @@ trait JvmWorkerModule extends OfflineSupportModule with CoursierModule { /** Whether Zinc debug logging is enabled. */ def zincLogDebug: T[Boolean] = Task.Input(Task.ctx().log.debugEnabled) + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") def worker: Worker[JvmWorkerApi] = internalWorker private[mill] def internalWorker: Worker[InternalJvmWorkerApi] = Task.Worker { diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 0f0e46825d4e..c505ea1a3be6 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -1,8 +1,8 @@ package mill.javalib import mill.api.{BuildCtx, Discover, ExternalModule, ModuleRef, PathRef, Result, experimental} -import mill.api.daemon.internal.SemanticDbJavaModuleApi -import mill.constants.CodeGenConstants +import mill.api.daemon.internal.{EvaluatorApi, SemanticDbJavaModuleApi} +import mill.constants.{CodeGenConstants, OutFiles, OutFolderMode} import mill.util.BuildInfo import mill.javalib.api.{CompilationResult, JvmWorkerUtil} import mill.util.Version @@ -12,12 +12,36 @@ import scala.jdk.CollectionConverters.* import scala.util.Properties import mill.api.daemon.internal.bsp.BspBuildTarget import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} +import mill.javalib.backgroundwrapper.MillBackgroundWrapper @experimental trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi with WithJvmWorkerModule { def jvmWorker: ModuleRef[JvmWorkerModule] + /** + * The upstream compilation output of all this module's upstream modules + */ + def upstreamCompileOutput: T[Seq[CompilationResult]] = Task { + val compileTo = CompileArgs.compileTaskPath(Task.dest, compile) + upstreamCompileOutput(CompileArgs.default(compileTo))() + } + + /** + * The upstream compilation output of all this module's upstream modules + */ + private[mill] def upstreamCompileOutput(compileArgs: CompileArgs): Task[Seq[CompilationResult]] = + Task.Anon { + Task.traverse(transitiveModuleCompileModuleDeps) { module => + val actualCompileArgs = compileArgs.copy(compileTo = + CompileArgs.compileTaskPath(thisTaskDest = ???, module.compile) + ) + module.compileWithArgs.map(fn => + fn(actualCompileArgs) + ) + }() + } + def upstreamSemanticDbDatas: Task[Seq[SemanticDbJavaModule.SemanticDbData]] = Task.sequence(transitiveModuleCompileModuleDeps.map(_.semanticDbDataDetailed)) @@ -25,18 +49,98 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi def zincReportCachedProblems: T[Boolean] def zincIncrementalCompilation: T[Boolean] def allSourceFiles: T[Seq[PathRef]] - def compile: T[mill.javalib.api.CompilationResult] - private[mill] def compileFor(compileFor: CompileFor): Task[mill.javalib.api.CompilationResult] = - compileFor match { - case CompileFor.Regular => compile - case CompileFor.SemanticDb => semanticDbDataDetailed.map(_.compilationResult) + def compile: Task.Simple[mill.javalib.api.CompilationResult] = Task(persistent = true) { + val fn = compileWithArgs() + fn(CompileArgs(compileTo = Task.dest, semanticDbWillBeNeeded())) + } + + /** + * Path to sources generated as part of the `compile` step, eg. by Java annotation + * processors which often generate source code alongside classfiles during compilation. + * + * Typically these do not need to be compiled again, and are only used by IDEs + */ + def compileGeneratedSources: T[os.Path] + + /** + * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using + * the same `out/` directory. + */ + private[mill] def semanticDbWillBeNeeded: T[Boolean] = Task.Input { + // TODO review: read from the files and document why +// MillBackgroundWrapper.readPreviousPid() +// val root = os.list( +// BuildCtx.workspaceRoot / OutFiles.outFor(OutFolderMode.REGULAR) / "bsp-semanticdb-sessions" +// ).exists() + + SemanticDbJavaModuleApi.clientNeedsSemanticDb() + } + + private[mill] def compileWithArgs + : Task[CompileArgs => Result[mill.javalib.api.CompilationResult]] = + Task.Anon { (args: CompileArgs) => + val compileSemanticDb = args.forceSemanticDb || semanticDbWillBeNeeded() + Task.log.debug(s"compileSemanticDb: $compileSemanticDb") + + val jOpts = JavaCompilerOptions { + val opts = + Seq("-s", compileGeneratedSources().toString) ++ javacOptions() ++ mandatoryJavacOptions() + + if (compileSemanticDb) SemanticDbJavaModule.javacOptionsTask(opts, semanticDbJavaVersion()) + else opts + } + + Task.log.debug(s"compiling to: ${args.compileTo}") + Task.log.debug(s"effective javac options: ${jOpts.compiler}") + Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") + + val sources = allSourceFiles().map(_.path) + + val compileClasspathSemanticDbJavaPlugin = + if (compileSemanticDb) resolvedSemanticDbJavaPluginMvnDeps() else Seq.empty + + val compileJavaOp = ZincCompileJava( + compileTo = args.compileTo, + upstreamCompileOutput = upstreamCompileOutput(args)(), + sources = sources, + compileClasspath = + (compileClasspathTask(args)() ++ compileClasspathSemanticDbJavaPlugin).map(_.path), + javacOptions = jOpts.compiler, + incrementalCompilation = zincIncrementalCompilation() + ) + + val compileJavaResult = jvmWorker() + .internalWorker() + .compileJava( + compileJavaOp, + javaHome = javaHome().map(_.path), + javaRuntimeOptions = jOpts.runtime, + reporter = Task.reporter.apply(hashCode), + reportCachedProblems = zincReportCachedProblems() + ) + + compileJavaResult.map { compilationResult => + if (compileSemanticDb) { + val semanticDbFiles = BuildCtx.withFilesystemCheckerDisabled { + SemanticDbJavaModule.copySemanticdbFiles( + classesDir = compilationResult.classes.path, + sourceroot = BuildCtx.workspaceRoot, + targetDir = Task.dest / "semanticdb-data", + workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), + sources = sources + ) + } + + compilationResult.copy(semanticDbFiles = Some(semanticDbFiles)) + } else compilationResult + } } private[mill] def bspBuildTarget: BspBuildTarget def javacOptions: T[Seq[String]] def mandatoryJavacOptions: T[Seq[String]] - private[mill] def compileClasspathTask(compileFor: CompileFor): Task[Seq[PathRef]] + private[mill] def compileClasspathTask(compileArgs: CompileArgs): Task[Seq[PathRef]] def moduleDeps: Seq[JavaModule] def semanticDbVersion: T[String] = Task.Input { @@ -114,48 +218,19 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi defaultResolver().classpath(semanticDbJavaPluginMvnDeps()) } - def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task(persistent = true) { - val javacOpts = SemanticDbJavaModule.javacOptionsTask( - javacOptions() ++ mandatoryJavacOptions(), - semanticDbJavaVersion() - ) - - Task.log.debug(s"effective javac options: ${javacOpts}") - - val jOpts = JavaCompilerOptions(javacOpts) - - jvmWorker().internalWorker() - .compileJava( - ZincCompileJava( - upstreamCompileOutput = upstreamSemanticDbDatas().map(_.compilationResult), - sources = allSourceFiles().map(_.path), - compileClasspath = - (compileClasspathTask( - CompileFor.SemanticDb - )() ++ resolvedSemanticDbJavaPluginMvnDeps()).map( - _.path - ), - javacOptions = jOpts.compiler, - incrementalCompilation = zincIncrementalCompilation() - ), - javaHome = javaHome().map(_.path), - javaRuntimeOptions = jOpts.runtime, - reporter = Task.reporter.apply(hashCode), - reportCachedProblems = zincReportCachedProblems() - ) - .map { compilationResult => - val semanticDbFiles = BuildCtx.withFilesystemCheckerDisabled { - SemanticDbJavaModule.copySemanticdbFiles( - compilationResult.classes.path, - BuildCtx.workspaceRoot, - Task.dest / "data", - SemanticDbJavaModule.workerClasspath().map(_.path), - allSourceFiles().map(_.path) - ) - } - - SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbFiles) - } + // TODO review: evaluator API? + def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task { + val compileTo = CompileArgs.compileTaskPath(Task.dest, compile) +// val compileTo = +// UnresolvedPath.resolveRelativeToOut(compile).resolve(os.Path(???)) + val result = compileWithArgs.apply()(CompileArgs(compileTo, forceSemanticDb = true)) + result.map { compilationResult => + val semanticDbData = + compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( + "SemanticDB files were not produced, this is a bug in Mill." + )) + SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbData) + } } def semanticDbData: T[PathRef] = Task { diff --git a/libs/javalib/src/mill/javalib/UnresolvedPath.scala b/libs/javalib/src/mill/javalib/UnresolvedPath.scala index 7f7de13be3e1..21cb3d3d6ace 100644 --- a/libs/javalib/src/mill/javalib/UnresolvedPath.scala +++ b/libs/javalib/src/mill/javalib/UnresolvedPath.scala @@ -1,12 +1,14 @@ package mill.javalib -import mill.api.daemon.internal.UnresolvedPathApi -import mill.api.{ExecutionPaths, Segment, Segments} +import mill.api.daemon.internal.{UnresolvedPathApi, internal} +import mill.api.{ExecutionPaths, Segment, Segments, Task} import upickle.default.{ReadWriter, macroRW} /** * An unresolved path is relative to some unspecified destination * which depends on the actual configuration at evaluation time. + * + * TODO REVIEW: outdated comment * Hence, you need to call [[#resolve]] with an instance of * [[ExecutionPathsResolver]] to get the final [[os.Path]]. */ @@ -14,6 +16,15 @@ sealed trait UnresolvedPath extends UnresolvedPathApi[os.Path] { def resolve(outPath: os.Path): os.Path } object UnresolvedPath { + + /** Resolves paths relative to the `out` folder. */ + @internal + private[mill] def resolveRelativeToOut( + task: Task.Named[?], + mkPath: os.SubPath => os.SubPath = identity + ): UnresolvedPath.DestPath = + UnresolvedPath.DestPath(mkPath(os.sub), task.ctx.segments) + case class ResolvedPath private (path: String) extends UnresolvedPath { override def resolve(outPath: os.Path): os.Path = os.Path(path) } diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala index bf09f9f574ee..73f65ffef1c8 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala @@ -182,6 +182,7 @@ class ZincWorker( val cacheKey = JavaCompilerCacheKey(javacOptions) javaOnlyCompilerCache.withValue(cacheKey) { compilers => compileInternal( + compileTo = compileTo, upstreamCompileOutput = upstreamCompileOutput, sources = sources, compileClasspath = compileClasspath, @@ -215,6 +216,7 @@ class ZincWorker( deps.compilerBridge ) { compilers => compileInternal( + compileTo = compileTo, upstreamCompileOutput = upstreamCompileOutput, sources = sources, compileClasspath = compileClasspath, @@ -333,6 +335,7 @@ class ZincWorker( } private def compileInternal( + compileTo: os.Path, upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], compileClasspath: Seq[os.Path], @@ -361,9 +364,9 @@ class ZincWorker( |""".stripMargin ) - os.makeDir.all(ctx.dest) + os.makeDir.all(compileTo) - val classesDir = ctx.dest / "classes" + val classesDir = compileTo / "classes" if (ctx.logDebugEnabled) { deps.log.debug( @@ -417,7 +420,7 @@ class ZincWorker( val lookup = MockedLookup(analysisMap) - val store = fileAnalysisStore(ctx.dest / zincCache) + val store = fileAnalysisStore(compileTo / zincCache) // Fix jdk classes marked as binary dependencies, see https://github.com/com-lihaoyi/mill/pull/1904 val converter = MappedFileConverter.empty @@ -516,7 +519,7 @@ class ZincWorker( newResult.setup() ) ) - Result.Success(CompilationResult(ctx.dest / zincCache, PathRef(classesDir))) + Result.Success(CompilationResult(compileTo / zincCache, PathRef(classesDir))) } catch { case e: CompileFailed => Result.Failure(e.toString) @@ -605,7 +608,6 @@ object ZincWorker { /** The invocation context, always comes from the Mill's process. */ case class InvocationContext( env: Map[String, String], - dest: os.Path, logDebugEnabled: Boolean, logPromptColored: Boolean, zincLogDebug: Boolean diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 6ea9f91f4da9..86ef570a3360 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -600,6 +600,8 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase override def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task(persistent = true) { + compile + val sv = scalaVersion() val additionalScalacOptions = if (JvmWorkerUtil.isScala3(sv)) { diff --git a/libs/util/src/mill/util/Tasks.scala b/libs/util/src/mill/util/Tasks.scala index ef8aea5ed3c3..162c606f1745 100644 --- a/libs/util/src/mill/util/Tasks.scala +++ b/libs/util/src/mill/util/Tasks.scala @@ -1,6 +1,7 @@ package mill.util -import mill.api.{Evaluator, SelectMode} +import mill.api.daemon.internal.internal +import mill.api.{Evaluator, SelectMode, Task} /** * Used in the signature of [[Task.Command]]s to allow them to take one or more tasks selectors From 50d1428f643e9c9f25eb28689f6036bf8ceb141b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 29 Aug 2025 14:56:39 +0300 Subject: [PATCH 02/17] WIP: this compiles but doesn't work --- .../mill/playlib/RouteCompilerWorker.scala | 2 +- .../src/mill/twirllib/TwirlWorker.scala | 2 +- .../internal/SemanticDbJavaModuleApi.scala | 4 +- core/api/src/mill/api/Task.scala | 26 ++ .../src/mill/androidlib/AndroidModule.scala | 2 + .../androidlib/hilt/AndroidHiltSupport.scala | 2 +- .../mill/javalib/api/CompilationResult.scala | 12 +- .../javalib/api/internal/JvmWorkerApi.scala | 2 +- .../src/mill/javalib/CompileArgs.scala | 5 - .../javalib/src/mill/javalib/JavaModule.scala | 54 +--- .../mill/javalib/SemanticDbJavaModule.scala | 231 ++++++++++-------- .../mill/javalib/worker/JvmWorkerImpl.scala | 1 - .../src/mill/javalib/zinc/ZincWorker.scala | 6 +- .../src/mill/kotlinlib/KotlinModule.scala | 3 +- .../mill/kotlinlib/js/KotlinJsModule.scala | 3 +- .../src/mill/scalalib/ScalaModule.scala | 148 +++++------ libs/util/src/mill/util/Tasks.scala | 3 +- .../src/mill/meta/MillBuildRootModule.scala | 1 + 18 files changed, 264 insertions(+), 243 deletions(-) diff --git a/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala b/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala index 1a80d05483df..20d51a92e69a 100644 --- a/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala +++ b/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala @@ -44,7 +44,7 @@ private[playlib] class RouteCompilerWorker { dest.toIO ) match { case null => - Result.Success(CompilationResult(Task.dest / "zinc", PathRef(Task.dest))) + Result.Success(CompilationResult(Task.dest / "zinc", PathRef(Task.dest), semanticDbFiles = None)) case err => Result.Failure(err) } } diff --git a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala index 38e08dceda17..9f98fe9ac78c 100644 --- a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala +++ b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala @@ -164,7 +164,7 @@ object TwirlWorker { val zincFile = ctx.dest / "zinc" val classesDir = ctx.dest - mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir))) + mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir), semanticDbFiles = None)) } private def twirlExtensionClass(name: String, formats: Map[String, String]) = diff --git a/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala b/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala index 6966816e4fc3..2dff98105d6d 100644 --- a/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala +++ b/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala @@ -14,14 +14,14 @@ object SemanticDbJavaModuleApi { private[mill] val contextSemanticDbVersion: InheritableThreadLocal[Option[String]] = new InheritableThreadLocal[Option[String]] { - protected override def initialValue(): Option[String] = None.asInstanceOf[Option[String]] + protected override def initialValue(): Option[String] = None } private[mill] def clientNeedsSemanticDb(): Boolean = contextSemanticDbVersion.get().isDefined private[mill] val contextJavaSemanticDbVersion: InheritableThreadLocal[Option[String]] = new InheritableThreadLocal[Option[String]] { - protected override def initialValue(): Option[String] = None.asInstanceOf[Option[String]] + protected override def initialValue(): Option[String] = None } private[mill] def resetContext(): Unit = { diff --git a/core/api/src/mill/api/Task.scala b/core/api/src/mill/api/Task.scala index 0b47938e827b..d12e79370587 100644 --- a/core/api/src/mill/api/Task.scala +++ b/core/api/src/mill/api/Task.scala @@ -295,6 +295,11 @@ object Task { def withFilter(f: T => Boolean): Task[T] = this def zip[V](other: Task[V]): Task[(T, V)] = new Task.Zipped(this, other) + /** Runs `before` function before the task and then `after` funciton after the task. */ + def wrap[IntermediateData, TaskResult]( + before: => IntermediateData + )(after: (IntermediateData, T) => TaskResult): Task[TaskResult] = + Task.Wrapped(this, () => before, after) } private[api] class Sequence[+T](inputs0: Seq[Task[T]]) extends Task[Seq[T]] { @@ -310,6 +315,27 @@ object Task { val inputs: Seq[Task[?]] = List(source) } + private[api] class Wrapped[Input, IntermediateData, TaskResult]( + source: Task[Input], + before: () => IntermediateData, + after: (IntermediateData, Input) => TaskResult + ) extends Task[TaskResult] { + + def evaluate(ctx: mill.api.TaskCtx): Result[TaskResult] = { + println("before()") + val intermediateData = before() + println(s"intermediateData=$intermediateData") + val input = ctx.arg(0).asInstanceOf[Input] + println(s"input=$input") + println("after()") + val result = after(intermediateData, input) + println(s"result=$result") + result + } + + val inputs: Seq[Task[?]] = List(source) + } + private[api] class Zipped[+T, +V](source1: Task[T], source2: Task[V]) extends Task[(T, V)] { def evaluate(ctx: mill.api.TaskCtx): Result[(T, V)] = (ctx.arg(0), ctx.arg(1)) val inputs: Seq[Task[?]] = List(source1, source2) diff --git a/libs/androidlib/src/mill/androidlib/AndroidModule.scala b/libs/androidlib/src/mill/androidlib/AndroidModule.scala index 81a447320b48..4c9a7ca9735b 100644 --- a/libs/androidlib/src/mill/androidlib/AndroidModule.scala +++ b/libs/androidlib/src/mill/androidlib/AndroidModule.scala @@ -480,6 +480,7 @@ trait AndroidModule extends JavaModule { outer => .internalWorker() .compileJava( ZincCompileJava( + compileTo = Task.dest, upstreamCompileOutput = upstreamCompileOutput(), sources = androidLibsRClasses().map(_.path), compileClasspath = Seq.empty, @@ -645,6 +646,7 @@ trait AndroidModule extends JavaModule { outer => .internalWorker() .compileJava( ZincCompileJava( + compileTo = Task.dest, upstreamCompileOutput = upstreamCompileOutput(), sources = sources.map(_.path), compileClasspath = androidTransitiveLibRClasspath().map(_.path), diff --git a/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala b/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala index 2dc8f8b51314..cda4a4adbfd9 100644 --- a/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala +++ b/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala @@ -51,7 +51,7 @@ trait AndroidHiltSupport extends KspBaseModule, AndroidKotlinModule { val analysisFile = Task.dest / "kotlin.analysis.dummy" os.write(target = analysisFile, data = "", createFolders = true) - CompilationResult(analysisFile, transformClasses) + CompilationResult(analysisFile, transformClasses, semanticDbFiles = None) } def hiltProcessorClasspath: T[Seq[PathRef]] = Task { diff --git a/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala b/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala index 22f7b5c5daef..a9e98f69714f 100644 --- a/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala +++ b/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala @@ -3,8 +3,16 @@ package mill.javalib.api import mill.api.PathRef import mill.api.JsonFormatters._ -// analysisFile is represented by os.Path, so we won't break caches after file changes -case class CompilationResult(analysisFile: os.Path, classes: PathRef, semanticDbFiles: Option[PathRef]) +/** + * @param analysisFile represented by os.Path, so we won't break caches after file changes + * @param classes path to the compilation classes + * @param semanticDbFiles path to semanticdb files, if they were produced + */ +case class CompilationResult( + analysisFile: os.Path, + classes: PathRef, + semanticDbFiles: Option[PathRef] +) object CompilationResult { implicit val jsonFormatter: upickle.default.ReadWriter[CompilationResult] = diff --git a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala index f92bb0eaa990..7c655a0ac049 100644 --- a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala @@ -1,6 +1,6 @@ package mill.javalib.api.internal -import mill.api.{PathRef, Result} +import mill.api.Result import mill.api.daemon.internal.CompileProblemReporter import mill.javalib.api.CompilationResult import mill.javalib.api.JvmWorkerApi as PublicJvmWorkerApi diff --git a/libs/javalib/src/mill/javalib/CompileArgs.scala b/libs/javalib/src/mill/javalib/CompileArgs.scala index f20052470399..992029698f56 100644 --- a/libs/javalib/src/mill/javalib/CompileArgs.scala +++ b/libs/javalib/src/mill/javalib/CompileArgs.scala @@ -1,7 +1,5 @@ package mill.javalib -import mill.api.Task - /** * @param compileTo the directory to which the classes will be compiled. We need this because we can't use the task's * destination folder as we need that folder to be persistent and persistent tasks can not take @@ -15,9 +13,6 @@ private[mill] case class CompileArgs( forceSemanticDb: Boolean ) private[mill] object CompileArgs { - // TODO review: this is a terrible hack - def compileTaskPath(thisTaskDest: os.Path, compileTask: Task.Named[?]): os.Path = - thisTaskDest / ".." / s"${compileTask.ctx.segments.last.value}.dest" /** Arguments for the default compilation. */ def default(compileTo: os.Path): CompileArgs = CompileArgs(compileTo, forceSemanticDb = false) diff --git a/libs/javalib/src/mill/javalib/JavaModule.scala b/libs/javalib/src/mill/javalib/JavaModule.scala index 387bd593d061..aff8785b5582 100644 --- a/libs/javalib/src/mill/javalib/JavaModule.scala +++ b/libs/javalib/src/mill/javalib/JavaModule.scala @@ -21,7 +21,6 @@ import mill.javalib.* import mill.api.daemon.internal.idea.GenIdeaInternalApi import mill.api.{DefaultTaskModule, ModuleRef, PathRef, Segment, Task, TaskCtx} import mill.javalib.api.CompilationResult -import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} import mill.javalib.bsp.{BspJavaModule, BspModule} import mill.javalib.internal.ModuleUtils import mill.javalib.publish.Artifact @@ -726,22 +725,11 @@ trait JavaModule * The transitive version of [[compileClasspath]] */ def transitiveCompileClasspath: T[Seq[PathRef]] = Task { - transitiveCompileClasspathTask(CompileArgs.default)() + Task.traverse(transitiveModuleCompileModuleDeps)(m => + Task.Anon(m.localCompileClasspath() :+ m.compile().classes) + )().flatten } - /** - * The transitive version of [[compileClasspathTask]] - */ - private[mill] def transitiveCompileClasspathTask(compileArgs: CompileArgs): Task[Seq[PathRef]] = - Task.Anon { - Task.traverse(transitiveModuleCompileModuleDeps)(m => - Task.Anon { - val compileResult = m.compileWithArgs.apply()(compileArgs) - compileResult.map(res => m.localCompileClasspath() :+ res.classes) - } - )().flatten - } - /** * Same as [[transitiveCompileClasspath]], but with all dependencies on [[compile]] * replaced by their non-compiling [[bspCompileClassesPath]] variants. @@ -831,26 +819,6 @@ trait JavaModule true } - /** - * Compiles the current module to generate compiled classfiles/bytecode. - * - * When you override this, you probably also want/need to override [[bspCompileClassesPath]], - * as that needs to point to the same compilation output path. - * - * Keep in sync with [[bspCompileClassesPath]] - */ - def compile: T[mill.javalib.api.CompilationResult] = Task(persistent = true) { - // Prepare an empty `compileGeneratedSources` folder for java annotation processors - // to write generated sources into, that can then be picked up by IDEs like IntelliJ - val compileGenSources = compileGeneratedSources() - mill.api.BuildCtx.withFilesystemCheckerDisabled { - os.remove.all(compileGenSources) - os.makeDir.all(compileGenSources) - } - - ??? - } - /** The path where the compiled classes produced by [[compile]] are stored. */ @internal private[mill] def compileClassesPath: UnresolvedPath.DestPath = @@ -936,21 +904,15 @@ trait JavaModule bspLocalRunClasspath(needsToMergeResourcesIntoCompileDest)() } - /** - * [[compileClasspathTask]] for regular compilations. - * - * Keep return value in sync with [[bspCompileClasspath]]. - */ - def compileClasspath: T[Seq[PathRef]] = Task { compileClasspathTask(CompileArgs.default)() } - /** * All classfiles and resources from upstream modules and dependencies * necessary to compile this module. + * + * Keep return value in sync with [[bspCompileClasspath]]. */ - override private[mill] def compileClasspathTask(compileArgs: CompileArgs): Task[Seq[PathRef]] = - Task.Anon { - resolvedMvnDeps() ++ transitiveCompileClasspathTask(compileArgs)() ++ localCompileClasspath() - } + def compileClasspath: T[Seq[PathRef]] = Task { + resolvedMvnDeps() ++ transitiveCompileClasspath() ++ localCompileClasspath() + } /** * Same as [[compileClasspath]], but does not trigger compilation targets, if possible. diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index c505ea1a3be6..251ac66e87c9 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -1,8 +1,8 @@ package mill.javalib import mill.api.{BuildCtx, Discover, ExternalModule, ModuleRef, PathRef, Result, experimental} -import mill.api.daemon.internal.{EvaluatorApi, SemanticDbJavaModuleApi} -import mill.constants.{CodeGenConstants, OutFiles, OutFolderMode} +import mill.api.daemon.internal.SemanticDbJavaModuleApi +import mill.constants.CodeGenConstants import mill.util.BuildInfo import mill.javalib.api.{CompilationResult, JvmWorkerUtil} import mill.util.Version @@ -12,7 +12,6 @@ import scala.jdk.CollectionConverters.* import scala.util.Properties import mill.api.daemon.internal.bsp.BspBuildTarget import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} -import mill.javalib.backgroundwrapper.MillBackgroundWrapper @experimental trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi @@ -23,25 +22,9 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi * The upstream compilation output of all this module's upstream modules */ def upstreamCompileOutput: T[Seq[CompilationResult]] = Task { - val compileTo = CompileArgs.compileTaskPath(Task.dest, compile) - upstreamCompileOutput(CompileArgs.default(compileTo))() + Task.traverse(transitiveModuleCompileModuleDeps)(_.compile)() } - /** - * The upstream compilation output of all this module's upstream modules - */ - private[mill] def upstreamCompileOutput(compileArgs: CompileArgs): Task[Seq[CompilationResult]] = - Task.Anon { - Task.traverse(transitiveModuleCompileModuleDeps) { module => - val actualCompileArgs = compileArgs.copy(compileTo = - CompileArgs.compileTaskPath(thisTaskDest = ???, module.compile) - ) - module.compileWithArgs.map(fn => - fn(actualCompileArgs) - ) - }() - } - def upstreamSemanticDbDatas: Task[Seq[SemanticDbJavaModule.SemanticDbData]] = Task.sequence(transitiveModuleCompileModuleDeps.map(_.semanticDbDataDetailed)) @@ -50,9 +33,77 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi def zincIncrementalCompilation: T[Boolean] def allSourceFiles: T[Seq[PathRef]] + /** + * Compiles the current module to generate compiled classfiles/bytecode. + * + * When you override this, you probably also want/need to override [[JavaModule.bspCompileClassesPath]], + * as that needs to point to the same compilation output path. + * + * Keep the paths in sync with [[JavaModule.bspCompileClassesPath]]. + */ def compile: Task.Simple[mill.javalib.api.CompilationResult] = Task(persistent = true) { - val fn = compileWithArgs() - fn(CompileArgs(compileTo = Task.dest, semanticDbWillBeNeeded())) + println("compile() start") + // Prepare an empty `compileGeneratedSources` folder for java annotation processors + // to write generated sources into, that can then be picked up by IDEs like IntelliJ + val compileGenSources = compileGeneratedSources() + mill.api.BuildCtx.withFilesystemCheckerDisabled { + os.remove.all(compileGenSources) + os.makeDir.all(compileGenSources) + } + + println("compile() check") + val compileSemanticDb = + SemanticDbJavaModule.forceSemanticDbCompilation() || semanticDbWillBeNeeded() + Task.log.info(s"compileSemanticDb: $compileSemanticDb") + + val jOpts = JavaCompilerOptions { + val opts = + Seq("-s", compileGeneratedSources().toString) ++ javacOptions() ++ mandatoryJavacOptions() + + if (compileSemanticDb) opts ++ SemanticDbJavaModule.javacOptionsTask(semanticDbJavaVersion()) + else opts + } + + val compileTo = Task.dest + + Task.log.debug(s"compiling to: $compileTo") + Task.log.debug(s"effective javac options: ${jOpts.compiler}") + Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") + + val sources = allSourceFiles().map(_.path) + + val compileClasspathSemanticDbJavaPlugin = + if (compileSemanticDb) resolvedSemanticDbJavaPluginMvnDeps() else Seq.empty + + val compileJavaOp = ZincCompileJava( + compileTo = Task.dest, + upstreamCompileOutput = upstreamCompileOutput(), + sources = sources, + compileClasspath = + (compileClasspath() ++ compileClasspathSemanticDbJavaPlugin).map(_.path), + javacOptions = jOpts.compiler, + incrementalCompilation = zincIncrementalCompilation() + ) + + val compileJavaResult = jvmWorker() + .internalWorker() + .compileJava( + compileJavaOp, + javaHome = javaHome().map(_.path), + javaRuntimeOptions = jOpts.runtime, + reporter = Task.reporter.apply(hashCode), + reportCachedProblems = zincReportCachedProblems() + ) + + compileJavaResult.map { compilationResult => + if (compileSemanticDb) SemanticDbJavaModule.enhanceCompilationResultWithSemanticDb( + compileTo = compileJavaOp.compileTo, + sources = sources, + workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), + compilationResult = compilationResult + ) + else compilationResult + } } /** @@ -74,73 +125,19 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi // BuildCtx.workspaceRoot / OutFiles.outFor(OutFolderMode.REGULAR) / "bsp-semanticdb-sessions" // ).exists() - SemanticDbJavaModuleApi.clientNeedsSemanticDb() - } - - private[mill] def compileWithArgs - : Task[CompileArgs => Result[mill.javalib.api.CompilationResult]] = - Task.Anon { (args: CompileArgs) => - val compileSemanticDb = args.forceSemanticDb || semanticDbWillBeNeeded() - Task.log.debug(s"compileSemanticDb: $compileSemanticDb") + val forced = SemanticDbJavaModule.forceSemanticDbCompilation() + val neededByClient = SemanticDbJavaModuleApi.clientNeedsSemanticDb() - val jOpts = JavaCompilerOptions { - val opts = - Seq("-s", compileGeneratedSources().toString) ++ javacOptions() ++ mandatoryJavacOptions() + // TODO review: change to debug + Task.log.info(s"semanticDbWillBeNeeded: forced=$forced, neededByClient=$neededByClient") - if (compileSemanticDb) SemanticDbJavaModule.javacOptionsTask(opts, semanticDbJavaVersion()) - else opts - } - - Task.log.debug(s"compiling to: ${args.compileTo}") - Task.log.debug(s"effective javac options: ${jOpts.compiler}") - Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") - - val sources = allSourceFiles().map(_.path) - - val compileClasspathSemanticDbJavaPlugin = - if (compileSemanticDb) resolvedSemanticDbJavaPluginMvnDeps() else Seq.empty - - val compileJavaOp = ZincCompileJava( - compileTo = args.compileTo, - upstreamCompileOutput = upstreamCompileOutput(args)(), - sources = sources, - compileClasspath = - (compileClasspathTask(args)() ++ compileClasspathSemanticDbJavaPlugin).map(_.path), - javacOptions = jOpts.compiler, - incrementalCompilation = zincIncrementalCompilation() - ) - - val compileJavaResult = jvmWorker() - .internalWorker() - .compileJava( - compileJavaOp, - javaHome = javaHome().map(_.path), - javaRuntimeOptions = jOpts.runtime, - reporter = Task.reporter.apply(hashCode), - reportCachedProblems = zincReportCachedProblems() - ) - - compileJavaResult.map { compilationResult => - if (compileSemanticDb) { - val semanticDbFiles = BuildCtx.withFilesystemCheckerDisabled { - SemanticDbJavaModule.copySemanticdbFiles( - classesDir = compilationResult.classes.path, - sourceroot = BuildCtx.workspaceRoot, - targetDir = Task.dest / "semanticdb-data", - workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), - sources = sources - ) - } - - compilationResult.copy(semanticDbFiles = Some(semanticDbFiles)) - } else compilationResult - } - } + forced || neededByClient + } private[mill] def bspBuildTarget: BspBuildTarget def javacOptions: T[Seq[String]] def mandatoryJavacOptions: T[Seq[String]] - private[mill] def compileClasspathTask(compileArgs: CompileArgs): Task[Seq[PathRef]] + def compileClasspath: Task[Seq[PathRef]] def moduleDeps: Seq[JavaModule] def semanticDbVersion: T[String] = Task.Input { @@ -218,19 +215,13 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi defaultResolver().classpath(semanticDbJavaPluginMvnDeps()) } - // TODO review: evaluator API? def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task { - val compileTo = CompileArgs.compileTaskPath(Task.dest, compile) -// val compileTo = -// UnresolvedPath.resolveRelativeToOut(compile).resolve(os.Path(???)) - val result = compileWithArgs.apply()(CompileArgs(compileTo, forceSemanticDb = true)) - result.map { compilationResult => - val semanticDbData = - compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( - "SemanticDB files were not produced, this is a bug in Mill." - )) - SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbData) - } + val compilationResult = SemanticDbJavaModule.withForcedSemanticDbCompilation(compile)() + val semanticDbData = + compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( + "SemanticDB files were not produced, this is a bug in Mill." + )) + SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbData) } def semanticDbData: T[PathRef] = Task { @@ -299,9 +290,38 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { )) } + private val forceSemanticDbCompilationThreadLocal: InheritableThreadLocal[Boolean] = + new InheritableThreadLocal[Boolean] { + protected override def initialValue(): Boolean = false + } + + private[mill] def forceSemanticDbCompilation(): Boolean = + forceSemanticDbCompilationThreadLocal.get() + + private[mill] def withForcedSemanticDbCompilation[T](task: Task[T]): Task[T] = { + task.wrap { + val previous = forceSemanticDbCompilationThreadLocal.get() + println("set the flag") + forceSemanticDbCompilationThreadLocal.set(true) + previous + } { (previous, result) => + println("reset the flag") + forceSemanticDbCompilationThreadLocal.set(previous) + result + } + } + + /** + * This overload just prepends the given `javacOptions`, so it's kind of pointless, but it's already there, so we + * have to keep it. + */ def javacOptionsTask(javacOptions: Seq[String], semanticDbJavaVersion: String)(implicit ctx: mill.api.TaskCtx ): Seq[String] = { + javacOptions ++ javacOptionsTask(semanticDbJavaVersion) + } + + def javacOptionsTask(semanticDbJavaVersion: String)(using ctx: mill.api.TaskCtx): Seq[String] = { // these are only needed for Java 17+ val extracJavacExports = if (Properties.isJavaAtLeast(17)) List( @@ -322,7 +342,7 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { Version.isAtLeast(semanticDbJavaVersion, "0.8.10")(using Version.IgnoreQualifierOrdering) val buildTool = s" -build-tool:${if (isNewEnough) "mill" else "sbt"}" val verbose = if (ctx.log.debugEnabled) " -verbose" else "" - javacOptions ++ Seq( + Seq( s"-Xplugin:semanticdb -sourceroot:${ctx.workspace} -targetroot:${ctx.dest / "classes"}${buildTool}${verbose}" ) ++ extracJavacExports } @@ -377,6 +397,25 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { } } + private[mill] def enhanceCompilationResultWithSemanticDb( + compileTo: os.Path, + sources: Seq[os.Path], + workerClasspath: Seq[os.Path], + compilationResult: CompilationResult + ) = { + val semanticDbFiles = BuildCtx.withFilesystemCheckerDisabled { + copySemanticdbFiles( + classesDir = compilationResult.classes.path, + sourceroot = BuildCtx.workspaceRoot, + targetDir = compileTo / "semanticdb-data", + workerClasspath = workerClasspath, + sources = sources + ) + } + + compilationResult.copy(semanticDbFiles = Some(semanticDbFiles)) + } + // The semanticdb-javac plugin has issues with the -sourceroot setting, so we correct this on the fly private[mill] def copySemanticdbFiles( classesDir: os.Path, diff --git a/libs/javalib/worker/src/mill/javalib/worker/JvmWorkerImpl.scala b/libs/javalib/worker/src/mill/javalib/worker/JvmWorkerImpl.scala index 9df339c3244e..4fd04264e432 100644 --- a/libs/javalib/worker/src/mill/javalib/worker/JvmWorkerImpl.scala +++ b/libs/javalib/worker/src/mill/javalib/worker/JvmWorkerImpl.scala @@ -106,7 +106,6 @@ class JvmWorkerImpl(args: JvmWorkerArgs) extends JvmWorkerApi with AutoCloseable val log = ctx.log val zincCtx = ZincWorker.InvocationContext( env = ctx.env, - dest = ctx.dest, logDebugEnabled = log.debugEnabled, logPromptColored = log.prompt.colored, zincLogDebug = zincLogDebug diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala index 73f65ffef1c8..431a4138b85d 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala @@ -519,7 +519,11 @@ class ZincWorker( newResult.setup() ) ) - Result.Success(CompilationResult(compileTo / zincCache, PathRef(classesDir))) + Result.Success(CompilationResult( + compileTo / zincCache, + PathRef(classesDir), + semanticDbFiles = None + )) } catch { case e: CompileFailed => Result.Failure(e.toString) diff --git a/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala index aaad1feb5232..4b24b8e26ae4 100644 --- a/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala +++ b/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala @@ -359,7 +359,7 @@ trait KotlinModule extends JavaModule with KotlinModuleApi { outer => workerResult match { case Result.Success(_) => - val cr = CompilationResult(analysisFile, PathRef(classes)) + val cr = CompilationResult(analysisFile, PathRef(classes), semanticDbFiles = None) if (!isJava) { // pure Kotlin project cr @@ -416,6 +416,7 @@ trait KotlinModule extends JavaModule with KotlinModuleApi { outer => val jOpts = JavaCompilerOptions(javacOptions) worker.compileJava( ZincCompileJava( + compileTo = Task.dest, upstreamCompileOutput = upstreamCompileOutput, sources = javaSourceFiles, compileClasspath = compileCp, diff --git a/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala b/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala index 852368f437c1..cffb235a4530 100644 --- a/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala +++ b/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala @@ -425,7 +425,8 @@ trait KotlinJsModule extends KotlinModule { outer => } workerResult match { - case Result.Success(_) => CompilationResult(analysisFile, PathRef(artifactLocation)) + case Result.Success(_) => + CompilationResult(analysisFile, PathRef(artifactLocation), semanticDbFiles = None) case Result.Failure(reason) => Result.Failure(reason) } } diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 86ef570a3360..7f89fbef0118 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -10,7 +10,7 @@ import mainargs.Flag import mill.api.daemon.internal.bsp.{BspBuildTarget, BspModuleApi, ScalaBuildTarget} import mill.api.daemon.internal.{ScalaModuleApi, ScalaPlatform, internal} import mill.javalib.dependency.versions.{ValidVersion, Version} -import mill.javalib.{CompileFor, SemanticDbJavaModule} +import mill.javalib.SemanticDbJavaModule import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileMixed, ZincScaladocJar} // this import requires scala-reflect library to be on the classpath @@ -269,7 +269,9 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase ) } - // Keep in sync with [[bspCompileClassesPath]] + /** + * Keep the return paths in sync with [[bspCompileClassesPath]]. + */ override def compile: T[CompilationResult] = Task(persistent = true) { val sv = scalaVersion() if (sv == "2.12.4") Task.log.warn( @@ -278,29 +280,75 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase |For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin ) - val jOpts = JavaCompilerOptions(javacOptions() ++ mandatoryJavacOptions()) + val compileSemanticDb = semanticDbWillBeNeeded() + + val jOpts = JavaCompilerOptions { + val baseOpts = javacOptions() ++ mandatoryJavacOptions() + + if (compileSemanticDb) + baseOpts ++ SemanticDbJavaModule.javacOptionsTask(semanticDbJavaVersion()) + else baseOpts + } + + val scalacOptions = { + def semanticDbOptions = + if (JvmWorkerUtil.isScala3(sv)) + Seq("-Xsemanticdb", s"-sourceroot:${BuildCtx.workspaceRoot}") + else Seq("-Yrangepos", s"-P:semanticdb:sourceroot:${BuildCtx.workspaceRoot}") + + if (compileSemanticDb) { + // Filter out -Xfatal-warnings to avoid semanticdb from failing the build. + allScalacOptions().filterNot(_ == "-Xfatal-warnings") ++ + semanticDbEnablePluginScalacOptions() ++ semanticDbOptions + } else allScalacOptions() + } - jvmWorker() + val compileClasspath = { + val baseClasspath = this.compileClasspath() + + if (compileSemanticDb) baseClasspath ++ resolvedSemanticDbJavaPluginMvnDeps() + else baseClasspath + } + + Task.log.debug(s"effective scalac options: $scalacOptions") + Task.log.debug(s"effective javac options: ${jOpts.compiler}") + Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") + + val sources = allSourceFiles().map(_.path) + val compileMixedOp = ZincCompileMixed( + compileTo = Task.dest, + upstreamCompileOutput = upstreamCompileOutput(), + sources = sources, + compileClasspath = compileClasspath.map(_.path), + javacOptions = jOpts.compiler, + scalaVersion = sv, + scalaOrganization = scalaOrganization(), + scalacOptions = allScalacOptions(), + compilerClasspath = scalaCompilerClasspath(), + scalacPluginClasspath = scalacPluginClasspath(), + incrementalCompilation = zincIncrementalCompilation(), + auxiliaryClassFileExtensions = zincAuxiliaryClassFileExtensions() + ) + + val compileMixedResult = jvmWorker() .internalWorker() .compileMixed( - ZincCompileMixed( - upstreamCompileOutput = upstreamCompileOutput(), - sources = allSourceFiles().map(_.path), - compileClasspath = compileClasspath().map(_.path), - javacOptions = jOpts.compiler, - scalaVersion = sv, - scalaOrganization = scalaOrganization(), - scalacOptions = allScalacOptions(), - compilerClasspath = scalaCompilerClasspath(), - scalacPluginClasspath = scalacPluginClasspath(), - incrementalCompilation = zincIncrementalCompilation(), - auxiliaryClassFileExtensions = zincAuxiliaryClassFileExtensions() - ), + compileMixedOp, javaHome = javaHome().map(_.path), javaRuntimeOptions = jOpts.runtime, reporter = Task.reporter.apply(hashCode), reportCachedProblems = zincReportCachedProblems() ) + + compileMixedResult.map { compilationResult => + if (compileSemanticDb) SemanticDbJavaModule.enhanceCompilationResultWithSemanticDb( + compileTo = compileMixedOp.compileTo, + sources = sources, + workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), + compilationResult = compilationResult + ) + else compilationResult + } } override def docSources: T[Seq[PathRef]] = Task { @@ -592,76 +640,12 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase override def semanticDbScalaVersion: T[String] = scalaVersion() - override protected def semanticDbPluginClasspath = Task { + override protected def semanticDbPluginClasspath: T[Seq[PathRef]] = Task { defaultResolver().classpath( scalacPluginMvnDeps() ++ semanticDbPluginMvnDeps() ) } - override def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = - Task(persistent = true) { - compile - - val sv = scalaVersion() - - val additionalScalacOptions = if (JvmWorkerUtil.isScala3(sv)) { - Seq("-Xsemanticdb", s"-sourceroot:${BuildCtx.workspaceRoot}") - } else { - Seq("-Yrangepos", s"-P:semanticdb:sourceroot:${BuildCtx.workspaceRoot}") - } - - val scalacOptions = ( - allScalacOptions() ++ - semanticDbEnablePluginScalacOptions() ++ - additionalScalacOptions - ) - .filterNot(_ == "-Xfatal-warnings") - - val javacOpts = SemanticDbJavaModule.javacOptionsTask(javacOptions(), semanticDbJavaVersion()) - - Task.log.debug(s"effective scalac options: ${scalacOptions}") - Task.log.debug(s"effective javac options: ${javacOpts}") - - val jOpts = JavaCompilerOptions(javacOpts) - - jvmWorker().internalWorker() - .compileMixed( - ZincCompileMixed( - upstreamCompileOutput = upstreamSemanticDbDatas().map(_.compilationResult), - sources = allSourceFiles().map(_.path), - compileClasspath = - (compileClasspathTask( - CompileFor.SemanticDb - )() ++ resolvedSemanticDbJavaPluginMvnDeps()).map(_.path), - javacOptions = jOpts.compiler, - scalaVersion = sv, - scalaOrganization = scalaOrganization(), - scalacOptions = scalacOptions, - compilerClasspath = scalaCompilerClasspath(), - scalacPluginClasspath = semanticDbPluginClasspath(), - incrementalCompilation = zincIncrementalCompilation(), - auxiliaryClassFileExtensions = zincAuxiliaryClassFileExtensions() - ), - javaHome = javaHome().map(_.path), - javaRuntimeOptions = jOpts.runtime, - reporter = Task.reporter.apply(hashCode), - reportCachedProblems = zincReportCachedProblems() - ) - .map { compilationResult => - val semanticDbFiles = BuildCtx.withFilesystemCheckerDisabled { - SemanticDbJavaModule.copySemanticdbFiles( - compilationResult.classes.path, - BuildCtx.workspaceRoot, - Task.dest / "data", - SemanticDbJavaModule.workerClasspath().map(_.path), - allSourceFiles().map(_.path) - ) - } - - SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbFiles) - } - } - // binary compatibility forwarder override def semanticDbData: T[PathRef] = // This is the same as `super.semanticDbData()`, but we can't call it directly diff --git a/libs/util/src/mill/util/Tasks.scala b/libs/util/src/mill/util/Tasks.scala index 162c606f1745..ef8aea5ed3c3 100644 --- a/libs/util/src/mill/util/Tasks.scala +++ b/libs/util/src/mill/util/Tasks.scala @@ -1,7 +1,6 @@ package mill.util -import mill.api.daemon.internal.internal -import mill.api.{Evaluator, SelectMode, Task} +import mill.api.{Evaluator, SelectMode} /** * Used in the signature of [[Task.Command]]s to allow them to take one or more tasks selectors diff --git a/runner/meta/src/mill/meta/MillBuildRootModule.scala b/runner/meta/src/mill/meta/MillBuildRootModule.scala index 874217976cce..aff741399354 100644 --- a/runner/meta/src/mill/meta/MillBuildRootModule.scala +++ b/runner/meta/src/mill/meta/MillBuildRootModule.scala @@ -288,6 +288,7 @@ trait MillBuildRootModule()(implicit .internalWorker() .compileMixed( ZincCompileMixed( + compileTo = Task.dest, upstreamCompileOutput = upstreamCompileOutput(), sources = Seq.from(allSourceFiles().map(_.path)), compileClasspath = compileClasspath().map(_.path), From 1f02621ec41f0a91d808f775d2735e78a7350038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 29 Aug 2025 15:14:03 +0300 Subject: [PATCH 03/17] WIP: yeah, I'm stuck --- .../mill/javalib/SemanticDbJavaModule.scala | 74 ++++++++++++------- .../src/mill/scalalib/ScalaModule.scala | 3 +- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 251ac66e87c9..2d85ce363780 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -52,8 +52,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi } println("compile() check") - val compileSemanticDb = - SemanticDbJavaModule.forceSemanticDbCompilation() || semanticDbWillBeNeeded() + val compileSemanticDb = semanticDbWillBeNeeded().apply(Task.dest) Task.log.info(s"compileSemanticDb: $compileSemanticDb") val jOpts = JavaCompilerOptions { @@ -118,20 +117,21 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using * the same `out/` directory. */ - private[mill] def semanticDbWillBeNeeded: T[Boolean] = Task.Input { - // TODO review: read from the files and document why + private[mill] def semanticDbWillBeNeeded: Task[os.Path => Boolean] = Task.Anon { + (taskDest: os.Path) => + // TODO review: read from the files and document why // MillBackgroundWrapper.readPreviousPid() // val root = os.list( // BuildCtx.workspaceRoot / OutFiles.outFor(OutFolderMode.REGULAR) / "bsp-semanticdb-sessions" // ).exists() - val forced = SemanticDbJavaModule.forceSemanticDbCompilation() - val neededByClient = SemanticDbJavaModuleApi.clientNeedsSemanticDb() + val forced = SemanticDbJavaModule.forceSemanticDbCompilation(taskDest) + val neededByClient = SemanticDbJavaModuleApi.clientNeedsSemanticDb() - // TODO review: change to debug - Task.log.info(s"semanticDbWillBeNeeded: forced=$forced, neededByClient=$neededByClient") + // TODO review: change to debug + Task.log.info(s"semanticDbWillBeNeeded: forced=$forced, neededByClient=$neededByClient") - forced || neededByClient + forced || neededByClient } private[mill] def bspBuildTarget: BspBuildTarget @@ -215,8 +215,21 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi defaultResolver().classpath(semanticDbJavaPluginMvnDeps()) } - def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task { - val compilationResult = SemanticDbJavaModule.withForcedSemanticDbCompilation(compile)() + def semanticDbDataDetailed: Task[SemanticDbJavaModule.SemanticDbData] = Task.Anon { + // TODO review: this is an ugly hack + val compileTo = Task.dest / ".." / "compile.Dest" + + println("set the flag") + os.write.over(compileTo / SemanticDbJavaModule.forceSemanticDbCompilationFilename, "") + val compilationResult = + try compile() + finally { + println("reset the flag") + os.remove(compileTo / SemanticDbJavaModule.forceSemanticDbCompilationFilename) + } + // TODO review: how to do this? +// val compileWithSemanticDb = SemanticDbJavaModule.withForcedSemanticDbCompilation(compile) +// val compilationResult = compileWithSemanticDb.apply().apply(compileTo) val semanticDbData = compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( "SemanticDB files were not produced, this is a bug in Mill." @@ -290,24 +303,29 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { )) } - private val forceSemanticDbCompilationThreadLocal: InheritableThreadLocal[Boolean] = - new InheritableThreadLocal[Boolean] { - protected override def initialValue(): Boolean = false - } - - private[mill] def forceSemanticDbCompilation(): Boolean = - forceSemanticDbCompilationThreadLocal.get() - - private[mill] def withForcedSemanticDbCompilation[T](task: Task[T]): Task[T] = { - task.wrap { - val previous = forceSemanticDbCompilationThreadLocal.get() - println("set the flag") - forceSemanticDbCompilationThreadLocal.set(true) - previous - } { (previous, result) => + private val forceSemanticDbCompilationFilename = "forceSemanticDbCompilation" + + private[mill] def forceSemanticDbCompilation(taskDest: os.Path): Boolean = + os.exists(taskDest / forceSemanticDbCompilationFilename) + + private[mill] def withForcedSemanticDbCompilation[T]( + task: Task[T] + ): Task[os.Path => T] = Task.Anon { (taskDest: os.Path) => +// task.wrap { +// println("set the flag") +// os.write.over(taskDest / forceSemanticDbCompilationFilename, "") +// } { (_, result) => +// println("reset the flag") +// os.remove(taskDest / forceSemanticDbCompilationFilename) +// result +// } + + println("set the flag") + os.write.over(taskDest / forceSemanticDbCompilationFilename, "") + try task() + finally { println("reset the flag") - forceSemanticDbCompilationThreadLocal.set(previous) - result + os.remove(taskDest / forceSemanticDbCompilationFilename) } } diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 7f89fbef0118..c2b5f6736aa5 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -280,7 +280,8 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase |For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin ) - val compileSemanticDb = semanticDbWillBeNeeded() + // TODO review: uncomment + val compileSemanticDb = false // semanticDbWillBeNeeded() val jOpts = JavaCompilerOptions { val baseOpts = javacOptions() ++ mandatoryJavacOptions() From b77f709c332eacc4427865cedf89a3867e0695fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Thu, 11 Sep 2025 08:48:32 +0300 Subject: [PATCH 04/17] WIP: force to always use semantic db --- libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 2d85ce363780..d63b7f5b8217 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -131,7 +131,10 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi // TODO review: change to debug Task.log.info(s"semanticDbWillBeNeeded: forced=$forced, neededByClient=$neededByClient") - forced || neededByClient + // TODO review: actually use this + val _ = forced || neededByClient + + true } private[mill] def bspBuildTarget: BspBuildTarget From 1ac358d34d5d83791d20ffa86158d98aa228a5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Thu, 11 Sep 2025 09:01:22 +0300 Subject: [PATCH 05/17] WIP: force to always use semantic db --- libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala | 4 +--- libs/scalalib/src/mill/scalalib/ScalaModule.scala | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 7dab412ed64f..d4792d12aad4 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -50,9 +50,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi os.makeDir.all(compileGenSources) } - println("compile() check") - val compileSemanticDb = semanticDbWillBeNeeded().apply(Task.dest) - Task.log.info(s"compileSemanticDb: $compileSemanticDb") + val compileSemanticDb = semanticDbWillBeNeeded.apply().apply(Task.dest) val jOpts = JavaCompilerOptions { val opts = diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 286b8346fe56..65a25a7c4d3b 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -280,8 +280,7 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase |For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin ) - // TODO review: uncomment - val compileSemanticDb = false // semanticDbWillBeNeeded() + val compileSemanticDb = semanticDbWillBeNeeded.apply().apply(Task.dest) val jOpts = JavaCompilerOptions { val baseOpts = javacOptions() ++ mandatoryJavacOptions() From 31e0309e9f5e59c07da222b6797d4a591d1d6a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Thu, 11 Sep 2025 13:06:56 +0300 Subject: [PATCH 06/17] WIP: works? --- .../internal/SemanticDbJavaModuleApi.scala | 6 +- core/api/src/mill/api/BuildCtx.scala | 8 +- core/api/src/mill/api/Task.scala | 60 ++++---- core/exec/src/mill/exec/GroupExecution.scala | 2 +- .../mill/javalib/SemanticDbJavaModule.scala | 128 ++++++++---------- .../src/mill/scalalib/ScalaModule.scala | 6 +- .../src/mill/bsp/worker/MillBuildServer.scala | 6 + 7 files changed, 110 insertions(+), 106 deletions(-) diff --git a/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala b/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala index 2dff98105d6d..efbb0695ad25 100644 --- a/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala +++ b/core/api/daemon/src/mill/api/daemon/internal/SemanticDbJavaModuleApi.scala @@ -9,16 +9,14 @@ trait SemanticDbJavaModuleApi { private[mill] def bspCompiledClassesAndSemanticDbFiles: TaskApi[UnresolvedPathApi[?]] } object SemanticDbJavaModuleApi { - val buildTimeJavaSemanticDbVersion = BuildInfo.semanticDbJavaVersion - val buildTimeSemanticDbVersion = BuildInfo.semanticDBVersion + val buildTimeJavaSemanticDbVersion: String = BuildInfo.semanticDbJavaVersion + val buildTimeSemanticDbVersion: String = BuildInfo.semanticDBVersion private[mill] val contextSemanticDbVersion: InheritableThreadLocal[Option[String]] = new InheritableThreadLocal[Option[String]] { protected override def initialValue(): Option[String] = None } - private[mill] def clientNeedsSemanticDb(): Boolean = contextSemanticDbVersion.get().isDefined - private[mill] val contextJavaSemanticDbVersion: InheritableThreadLocal[Option[String]] = new InheritableThreadLocal[Option[String]] { protected override def initialValue(): Option[String] = None diff --git a/core/api/src/mill/api/BuildCtx.scala b/core/api/src/mill/api/BuildCtx.scala index a0de31aee164..3289b6e178ed 100644 --- a/core/api/src/mill/api/BuildCtx.scala +++ b/core/api/src/mill/api/BuildCtx.scala @@ -1,7 +1,8 @@ package mill.api import collection.mutable import mill.api.Watchable -import mill.constants.EnvVars +import mill.constants.{EnvVars, OutFiles, OutFolderMode} + import scala.util.DynamicVariable /** @@ -50,6 +51,7 @@ object BuildCtx { } } + /** As [[watchValue]] but watches a file path. */ def watch(p: os.Path): os.Path = { val watchable = Watchable.Path(p.toNIO, false, PathRef(p).sig) watchedValues.append(watchable) @@ -59,4 +61,8 @@ object BuildCtx { def watch0(w: Watchable): Unit = watchedValues.append(w) def evalWatch0(w: Watchable): Unit = evalWatchedValues.append(w) + + // TODO review: doc me + private[mill] def bspSemanticDbSesssionsFolder = + workspaceRoot / os.SubPath(OutFiles.outFor(OutFolderMode.BSP)) / "semanticdb-sessions" } diff --git a/core/api/src/mill/api/Task.scala b/core/api/src/mill/api/Task.scala index d018e0bc9bf9..cbe669140ae2 100644 --- a/core/api/src/mill/api/Task.scala +++ b/core/api/src/mill/api/Task.scala @@ -295,11 +295,11 @@ object Task { def withFilter(f: T => Boolean): Task[T] = this def zip[V](other: Task[V]): Task[(T, V)] = new Task.Zipped(this, other) - /** Runs `before` function before the task and then `after` funciton after the task. */ - def wrap[IntermediateData, TaskResult]( - before: => IntermediateData - )(after: (IntermediateData, T) => TaskResult): Task[TaskResult] = - Task.Wrapped(this, () => before, after) +// /** Runs `before` function before the task and then `after` funciton after the task. */ +// def wrap[IntermediateData, TaskResult]( +// before: => IntermediateData +// )(after: (IntermediateData, T) => TaskResult): Task[TaskResult] = +// Task.Wrapped(this, () => before, after) } private[api] class Sequence[+T](inputs0: Seq[Task[T]]) extends Task[Seq[T]] { @@ -315,26 +315,36 @@ object Task { val inputs: Seq[Task[?]] = List(source) } - private[api] class Wrapped[Input, IntermediateData, TaskResult]( - source: Task[Input], - before: () => IntermediateData, - after: (IntermediateData, Input) => TaskResult - ) extends Task[TaskResult] { - - def evaluate(ctx: mill.api.TaskCtx): Result[TaskResult] = { - println("before()") - val intermediateData = before() - println(s"intermediateData=$intermediateData") - val input = ctx.arg(0).asInstanceOf[Input] - println(s"input=$input") - println("after()") - val result = after(intermediateData, input) - println(s"result=$result") - result - } - - val inputs: Seq[Task[?]] = List(source) - } +// private[api] class Wrapped[Input, IntermediateData, TaskResult]( +// source: Task[Input], +// before: () => IntermediateData, +// after: (IntermediateData, Input) => TaskResult +// ) extends Task[TaskResult] { +// val inputs: Seq[Task[?]] = source.inputs +// +// def evaluate(ctx: mill.api.TaskCtx): Result[TaskResult] = { +// println("before()") +// val intermediateData = before() +// println(s"intermediateData=$intermediateData") +//// val input = ctx.arg(0).asInstanceOf[Input] +// val input = source.evaluate(ctx) +// println(s"input=$input") +// println("after()") +// val result = after(intermediateData, input) +// println(s"result=$result") +// result +// } +// +// override def sideHash: Int = source.sideHash +// override def persistent: Boolean = source.persistent +// override private[mill] def isExclusiveCommand = source.isExclusiveCommand +// +// override private[mill] def asSimple = source.asSimple.map { simple => ??? } +// +// override private[mill] def asCommand = source.asCommand.map { command => ??? } +// +// override private[mill] def asWorker = source.asWorker.map { worker => ??? } +// } private[api] class Zipped[+T, +V](source1: Task[T], source2: Task[V]) extends Task[(T, V)] { def evaluate(ctx: mill.api.TaskCtx): Result[(T, V)] = (ctx.arg(0), ctx.arg(1)) diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index f32c8d6feed6..768d51534b04 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -568,7 +568,7 @@ object GroupExecution { if (path.startsWith(workspace) && !validReadDests.exists(path.startsWith(_))) { sys.error( s"Reading from ${path.relativeTo(workspace)} not allowed during execution of `$terminal`.\n" + - "You can only read files referenced by `Task.Source` or `Task.Sources`, or within a `Task.Input" + "You can only read files referenced by `Task.Source` or `Task.Sources`, or within a `Task.Input`" ) } } diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index d4792d12aad4..f4e8b96fc4d9 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -12,6 +12,8 @@ import scala.jdk.CollectionConverters.* import mill.api.daemon.internal.bsp.BspBuildTarget import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} +import java.nio.file.NoSuchFileException + @experimental trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi with WithJvmWorkerModule { @@ -41,7 +43,12 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi * Keep the paths in sync with [[JavaModule.bspCompileClassesPath]]. */ def compile: Task.Simple[mill.javalib.api.CompilationResult] = Task(persistent = true) { - println("compile() start") + val compileSemanticDb = bspAnyClientNeedsSemanticDb() + compileInternal.apply().apply(compileSemanticDb) + } + + private[mill] def compileInternal = Task.Anon { (compileSemanticDb: Boolean) => + println(s"compile(compileSemanticDb=$compileSemanticDb) start") // Prepare an empty `compileGeneratedSources` folder for java annotation processors // to write generated sources into, that can then be picked up by IDEs like IntelliJ val compileGenSources = compileGeneratedSources() @@ -50,8 +57,6 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi os.makeDir.all(compileGenSources) } - val compileSemanticDb = semanticDbWillBeNeeded.apply().apply(Task.dest) - val jOpts = JavaCompilerOptions { val opts = Seq("-s", compileGeneratedSources().toString) ++ javacOptions() ++ mandatoryJavacOptions() @@ -110,30 +115,6 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi */ def compileGeneratedSources: T[os.Path] - /** - * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using - * the same `out/` directory. - */ - private[mill] def semanticDbWillBeNeeded: Task[os.Path => Boolean] = Task.Anon { - (taskDest: os.Path) => - // TODO review: read from the files and document why -// MillBackgroundWrapper.readPreviousPid() -// val root = os.list( -// BuildCtx.workspaceRoot / OutFiles.outFor(OutFolderMode.REGULAR) / "bsp-semanticdb-sessions" -// ).exists() - - val forced = SemanticDbJavaModule.forceSemanticDbCompilation(taskDest) - val neededByClient = SemanticDbJavaModuleApi.clientNeedsSemanticDb() - - // TODO review: change to debug - Task.log.info(s"semanticDbWillBeNeeded: forced=$forced, neededByClient=$neededByClient") - - // TODO review: actually use this - val _ = forced || neededByClient - - true - } - private[mill] def bspBuildTarget: BspBuildTarget def javacOptions: T[Seq[String]] def mandatoryJavacOptions: T[Seq[String]] @@ -215,26 +196,55 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi defaultResolver().classpath(semanticDbJavaPluginMvnDeps()) } - def semanticDbDataDetailed: Task[SemanticDbJavaModule.SemanticDbData] = Task.Anon { - // TODO review: this is an ugly hack - val compileTo = Task.dest / ".." / "compile.Dest" - - println("set the flag") - os.write.over(compileTo / SemanticDbJavaModule.forceSemanticDbCompilationFilename, "") - val compilationResult = - try compile() - finally { - println("reset the flag") - os.remove(compileTo / SemanticDbJavaModule.forceSemanticDbCompilationFilename) + // TODO review: document + private lazy val semanticDbSessionsDirWatch = + BuildCtx.watch(BuildCtx.bspSemanticDbSesssionsFolder) + + /** + * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using + * the same `out/` directory. + */ + private[mill] def bspAnyClientNeedsSemanticDb(): Boolean = { + val directory = semanticDbSessionsDirWatch + + val bspHasClientsThatNeedSemanticDb = BuildCtx.withFilesystemCheckerDisabled { + try { + os.list(directory).exists { path => + path.last.toLongOption match { + case None => + // malformatted pid + false + case Some(pid) => + ProcessHandle.of(pid).isPresent + } + } + } catch { + case _: NoSuchFileException => false } - // TODO review: how to do this? -// val compileWithSemanticDb = SemanticDbJavaModule.withForcedSemanticDbCompilation(compile) -// val compilationResult = compileWithSemanticDb.apply().apply(compileTo) - val semanticDbData = - compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( - "SemanticDB files were not produced, this is a bug in Mill." - )) - SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbData) + } + + println(s"bspHasClientsThatNeedSemanticDb=$bspHasClientsThatNeedSemanticDb") + + bspHasClientsThatNeedSemanticDb + } + + def semanticDbDataDetailed: Task[SemanticDbJavaModule.SemanticDbData] = { + val task = + if (bspAnyClientNeedsSemanticDb()) compile.map(Result.Success(_)) + else compileInternal.map(run => run(true)) + + Task.Anon { + task().map { compilationResult => + // TODO review: how to do this? + // val compileWithSemanticDb = SemanticDbJavaModule.withForcedSemanticDbCompilation(compile) + // val compilationResult = compileWithSemanticDb.apply().apply(compileTo) + val semanticDbData = + compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( + "SemanticDB files were not produced, this is a bug in Mill." + )) + SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbData) + } + } } def semanticDbData: T[PathRef] = Task { @@ -303,32 +313,6 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { )) } - private val forceSemanticDbCompilationFilename = "forceSemanticDbCompilation" - - private[mill] def forceSemanticDbCompilation(taskDest: os.Path): Boolean = - os.exists(taskDest / forceSemanticDbCompilationFilename) - - private[mill] def withForcedSemanticDbCompilation[T]( - task: Task[T] - ): Task[os.Path => T] = Task.Anon { (taskDest: os.Path) => -// task.wrap { -// println("set the flag") -// os.write.over(taskDest / forceSemanticDbCompilationFilename, "") -// } { (_, result) => -// println("reset the flag") -// os.remove(taskDest / forceSemanticDbCompilationFilename) -// result -// } - - println("set the flag") - os.write.over(taskDest / forceSemanticDbCompilationFilename, "") - try task() - finally { - println("reset the flag") - os.remove(taskDest / forceSemanticDbCompilationFilename) - } - } - /** * This overload just prepends the given `javacOptions`, so it's kind of pointless, but it's already there, so we * have to keep it. diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 65a25a7c4d3b..969614fba991 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -272,7 +272,9 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase /** * Keep the return paths in sync with [[bspCompileClassesPath]]. */ - override def compile: T[CompilationResult] = Task(persistent = true) { + override private[mill] def compileInternal = Task.Anon { (compileSemanticDb: Boolean) => + println(s"compileScala(compileSemanticDb=$compileSemanticDb) start") + val sv = scalaVersion() if (sv == "2.12.4") Task.log.warn( """Attention: Zinc is known to not work properly for Scala version 2.12.4. @@ -280,8 +282,6 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase |For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin ) - val compileSemanticDb = semanticDbWillBeNeeded.apply().apply(Task.dest) - val jOpts = JavaCompilerOptions { val baseOpts = javacOptions() ++ mandatoryJavacOptions() diff --git a/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala index 3b8276932fa4..343fe4f56dc1 100644 --- a/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala +++ b/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala @@ -264,6 +264,12 @@ private class MillBuildServer( ) clientWantsSemanticDb = true SemanticDbJavaModuleApi.contextSemanticDbVersion.set(Option(version)) + + // Inform other BSP clients that we want to use SemanticDB + val pid = ProcessHandle.current().pid() + val pidFile = BuildCtx.bspSemanticDbSesssionsFolder / pid.toString + os.write.over(pidFile, "", createFolders = true) + pidFile.toNIO.toFile.deleteOnExit() } readVersion(d, "javaSemanticdbVersion").foreach { version => SemanticDbJavaModuleApi.contextJavaSemanticDbVersion.set(Option(version)) From 8ed1ef3f16ddf954ca2fd9f77b17763fa894f0fb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 10:50:43 +0000 Subject: [PATCH 07/17] [autofix.ci] apply automated fixes --- contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala | 6 +++++- contrib/twirllib/src/mill/twirllib/TwirlWorker.scala | 6 +++++- libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala | 1 - 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala b/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala index 20d51a92e69a..a8416cdbfa6d 100644 --- a/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala +++ b/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala @@ -44,7 +44,11 @@ private[playlib] class RouteCompilerWorker { dest.toIO ) match { case null => - Result.Success(CompilationResult(Task.dest / "zinc", PathRef(Task.dest), semanticDbFiles = None)) + Result.Success(CompilationResult( + Task.dest / "zinc", + PathRef(Task.dest), + semanticDbFiles = None + )) case err => Result.Failure(err) } } diff --git a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala index 9f98fe9ac78c..7a50bff2f2b5 100644 --- a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala +++ b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala @@ -164,7 +164,11 @@ object TwirlWorker { val zincFile = ctx.dest / "zinc" val classesDir = ctx.dest - mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir), semanticDbFiles = None)) + mill.api.Result.Success(CompilationResult( + zincFile, + PathRef(classesDir), + semanticDbFiles = None + )) } private def twirlExtensionClass(name: String, formats: Map[String, String]) = diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala index c19a4c9cda4c..1ce498b6989e 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala @@ -1,6 +1,5 @@ package mill.javalib.zinc -import mill.api.JsonFormatters.* import mill.api.PathRef import mill.api.daemon.internal.CompileProblemReporter import mill.api.daemon.{Logger, Result} From f60ae90e9125cb4b22b7cc3eb9f90bfacf528751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Thu, 11 Sep 2025 17:20:30 +0300 Subject: [PATCH 08/17] Fix `BuildCtx.withFilesystemCheckerDisabled` --- .../mill/javalib/SemanticDbJavaModule.scala | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index f4e8b96fc4d9..f17721cffa7a 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -204,29 +204,29 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using * the same `out/` directory. */ - private[mill] def bspAnyClientNeedsSemanticDb(): Boolean = { - val directory = semanticDbSessionsDirWatch - - val bspHasClientsThatNeedSemanticDb = BuildCtx.withFilesystemCheckerDisabled { - try { - os.list(directory).exists { path => - path.last.toLongOption match { - case None => - // malformatted pid - false - case Some(pid) => - ProcessHandle.of(pid).isPresent + private[mill] def bspAnyClientNeedsSemanticDb(): Boolean = + BuildCtx.withFilesystemCheckerDisabled { + val directory = semanticDbSessionsDirWatch + + val bspHasClientsThatNeedSemanticDb = + try { + os.list(directory).exists { path => + path.last.toLongOption match { + case None => + // malformatted pid + false + case Some(pid) => + ProcessHandle.of(pid).isPresent + } } + } catch { + case _: NoSuchFileException => false } - } catch { - case _: NoSuchFileException => false - } - } - println(s"bspHasClientsThatNeedSemanticDb=$bspHasClientsThatNeedSemanticDb") + println(s"bspHasClientsThatNeedSemanticDb=$bspHasClientsThatNeedSemanticDb") - bspHasClientsThatNeedSemanticDb - } + bspHasClientsThatNeedSemanticDb + } def semanticDbDataDetailed: Task[SemanticDbJavaModule.SemanticDbData] = { val task = From f7e6b00923e8f6acaca79b6883e32d8d8929980d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 3 Oct 2025 11:32:57 +0300 Subject: [PATCH 09/17] Binary compatibility and code review fixes. --- .../mill/playlib/RouteCompilerWorker.scala | 6 +- .../src/mill/twirllib/TwirlWorker.scala | 6 +- core/api/src/mill/api/BuildCtx.scala | 12 ++- core/api/src/mill/api/Task.scala | 36 ------- .../androidlib/hilt/AndroidHiltSupport.scala | 2 +- .../server/src/mill/server/Server.scala | 1 - .../mill/javalib/api/CompilationResult.scala | 5 +- .../src/mill/javalib/api/JvmWorkerApi.scala | 17 ++-- .../javalib/api/internal/JvmWorkerApi.scala | 45 ++++++++- .../MillBackgroundWrapper.java | 2 +- libs/javalib/package.mill | 24 +++-- .../src/mill/javalib/CompileArgs.scala | 19 ---- .../javalib/src/mill/javalib/JavaModule.scala | 12 ++- .../src/mill/javalib/JvmWorkerModule.scala | 2 +- .../mill/javalib/SemanticDbJavaModule.scala | 97 +++++++++++-------- .../src/mill/javalib/UnresolvedPath.scala | 5 +- .../src/mill/javalib/zinc/ZincWorker.scala | 6 +- .../src/mill/kotlinlib/KotlinModule.scala | 2 +- .../mill/kotlinlib/js/KotlinJsModule.scala | 3 +- libs/scalalib/package.mill | 22 ++++- .../src/mill/scalalib/ScalaModule.scala | 25 +++-- .../src/mill/bsp/worker/MillBuildServer.scala | 2 +- 22 files changed, 192 insertions(+), 159 deletions(-) delete mode 100644 libs/javalib/src/mill/javalib/CompileArgs.scala diff --git a/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala b/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala index 226cee04cf9b..330d5585ee91 100644 --- a/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala +++ b/contrib/playlib/src/mill/playlib/RouteCompilerWorker.scala @@ -44,11 +44,7 @@ private[playlib] class RouteCompilerWorker { dest.toIO ) match { case null => - Result.Success(CompilationResult( - Task.dest / "zinc", - PathRef(Task.dest), - semanticDbFiles = None - )) + Result.Success(CompilationResult(Task.dest / "zinc", PathRef(Task.dest))) case err => Result.Failure(err) } } diff --git a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala index 81d976818f87..0899bf184752 100644 --- a/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala +++ b/contrib/twirllib/src/mill/twirllib/TwirlWorker.scala @@ -164,11 +164,7 @@ object TwirlWorker { val zincFile = ctx.dest / "zinc" val classesDir = ctx.dest - mill.api.Result.Success(CompilationResult( - zincFile, - PathRef(classesDir), - semanticDbFiles = None - )) + mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir))) } private def twirlExtensionClass(name: String, formats: Map[String, String]) = diff --git a/core/api/src/mill/api/BuildCtx.scala b/core/api/src/mill/api/BuildCtx.scala index 0ec34207056a..cf665e58a6d3 100644 --- a/core/api/src/mill/api/BuildCtx.scala +++ b/core/api/src/mill/api/BuildCtx.scala @@ -62,7 +62,15 @@ object BuildCtx { def evalWatch0(w: Watchable): Unit = evalWatchedValues.append(w) - // TODO review: doc me - private[mill] def bspSemanticDbSesssionsFolder = + /** + * Folder in the filesystem where Mill's BSP sessions that require semanticdb store an indicator file (name = + * process PID, contents are irrelevant) to communicate to main Mill daemon and other BSP sessions that there is at + * least one Mill session that will need the semanticdb. + * + * The reasoning is that if at least one of Mill's clients requests semanticdb, then there is no point in running + * regular `compile` without semanticdb, as eventually we will have to rerun it with semanticdb, and thus we should + * compile with semanticdb upfront to avoid paying the price of compling twice (without semanticdb and then with it). + */ + private[mill] def bspSemanticDbSessionsFolder: os.Path = workspaceRoot / os.SubPath(OutFiles.outFor(OutFolderMode.BSP)) / "semanticdb-sessions" } diff --git a/core/api/src/mill/api/Task.scala b/core/api/src/mill/api/Task.scala index 525afd99021f..f2d40492e084 100644 --- a/core/api/src/mill/api/Task.scala +++ b/core/api/src/mill/api/Task.scala @@ -295,11 +295,6 @@ object Task { def withFilter(f: T => Boolean): Task[T] = this def zip[V](other: Task[V]): Task[(T, V)] = new Task.Zipped(this, other) -// /** Runs `before` function before the task and then `after` funciton after the task. */ -// def wrap[IntermediateData, TaskResult]( -// before: => IntermediateData -// )(after: (IntermediateData, T) => TaskResult): Task[TaskResult] = -// Task.Wrapped(this, () => before, after) } private[api] class Sequence[+T](inputs0: Seq[Task[T]]) extends Task[Seq[T]] { @@ -315,37 +310,6 @@ object Task { val inputs: Seq[Task[?]] = List(source) } -// private[api] class Wrapped[Input, IntermediateData, TaskResult]( -// source: Task[Input], -// before: () => IntermediateData, -// after: (IntermediateData, Input) => TaskResult -// ) extends Task[TaskResult] { -// val inputs: Seq[Task[?]] = source.inputs -// -// def evaluate(ctx: mill.api.TaskCtx): Result[TaskResult] = { -// println("before()") -// val intermediateData = before() -// println(s"intermediateData=$intermediateData") -//// val input = ctx.arg(0).asInstanceOf[Input] -// val input = source.evaluate(ctx) -// println(s"input=$input") -// println("after()") -// val result = after(intermediateData, input) -// println(s"result=$result") -// result -// } -// -// override def sideHash: Int = source.sideHash -// override def persistent: Boolean = source.persistent -// override private[mill] def isExclusiveCommand = source.isExclusiveCommand -// -// override private[mill] def asSimple = source.asSimple.map { simple => ??? } -// -// override private[mill] def asCommand = source.asCommand.map { command => ??? } -// -// override private[mill] def asWorker = source.asWorker.map { worker => ??? } -// } - private[api] class Zipped[+T, +V](source1: Task[T], source2: Task[V]) extends Task[(T, V)] { def evaluate(ctx: mill.api.TaskCtx): Result[(T, V)] = (ctx.arg(0), ctx.arg(1)) val inputs: Seq[Task[?]] = List(source1, source2) diff --git a/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala b/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala index 31cce7643255..506dee24e9d7 100644 --- a/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala +++ b/libs/androidlib/src/mill/androidlib/hilt/AndroidHiltSupport.scala @@ -46,7 +46,7 @@ trait AndroidHiltSupport extends KspModule, AndroidKotlinModule { val analysisFile = Task.dest / "kotlin.analysis.dummy" os.write(target = analysisFile, data = "", createFolders = true) - CompilationResult(analysisFile, transformClasses, semanticDbFiles = None) + CompilationResult(analysisFile, transformClasses) } def hiltProcessorClasspath: T[Seq[PathRef]] = Task { diff --git a/libs/daemon/server/src/mill/server/Server.scala b/libs/daemon/server/src/mill/server/Server.scala index 5b749299356b..37484af87c11 100644 --- a/libs/daemon/server/src/mill/server/Server.scala +++ b/libs/daemon/server/src/mill/server/Server.scala @@ -18,7 +18,6 @@ import scala.util.control.NonFatal * connections. */ abstract class Server(args: Server.Args) { - import args.* val processId: Long = Server.computeProcessId() diff --git a/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala b/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala index 055b4584f00b..92857bc0ad59 100644 --- a/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala +++ b/libs/javalib/api/src/mill/javalib/api/CompilationResult.scala @@ -1,7 +1,8 @@ package mill.javalib.api +import com.lihaoyi.unroll import mill.api.PathRef -import mill.api.JsonFormatters._ +import mill.api.JsonFormatters.* /** * @param analysisFile represented by os.Path, so we won't break caches after file changes @@ -11,7 +12,7 @@ import mill.api.JsonFormatters._ case class CompilationResult( analysisFile: os.Path, classes: PathRef, - semanticDbFiles: Option[PathRef] + @unroll semanticDbFiles: Option[PathRef] = None ) object CompilationResult { diff --git a/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala index 158252324f36..8079ffdabfd3 100644 --- a/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala @@ -6,10 +6,10 @@ import mill.api.daemon.internal.CompileProblemReporter object JvmWorkerApi { type Ctx = mill.api.TaskCtx.Dest & mill.api.TaskCtx.Log & mill.api.TaskCtx.Env } -@deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") +@deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") trait JvmWorkerApi { - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def compileJava( upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], @@ -19,10 +19,9 @@ trait JvmWorkerApi { reporter: Option[CompileProblemReporter], reportCachedProblems: Boolean, incrementalCompilation: Boolean - )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] = - throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def compileMixed( upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], @@ -38,10 +37,9 @@ trait JvmWorkerApi { reportCachedProblems: Boolean, incrementalCompilation: Boolean, auxiliaryClassFileExtensions: Seq[String] - )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] = - throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def docJar( scalaVersion: String, scalaOrganization: String, @@ -49,6 +47,5 @@ trait JvmWorkerApi { scalacPluginClasspath: Seq[PathRef], javaHome: Option[os.Path], args: Seq[String] - )(using ctx: JvmWorkerApi.Ctx): Boolean = - throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + )(using ctx: JvmWorkerApi.Ctx): Boolean } diff --git a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala index 7c655a0ac049..2d1d7cdefc1f 100644 --- a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala @@ -1,6 +1,6 @@ package mill.javalib.api.internal -import mill.api.Result +import mill.api.{PathRef, Result} import mill.api.daemon.internal.CompileProblemReporter import mill.javalib.api.CompilationResult import mill.javalib.api.JvmWorkerApi as PublicJvmWorkerApi @@ -34,6 +34,49 @@ trait JvmWorkerApi extends PublicJvmWorkerApi { op: ZincScaladocJar, javaHome: Option[os.Path] )(using context: JvmWorkerApi.Ctx): Boolean + + // public API forwarder + override def compileJava( + upstreamCompileOutput: Seq[CompilationResult], + sources: Seq[os.Path], + compileClasspath: Seq[os.Path], + javaHome: Option[os.Path], + javacOptions: Seq[String], + reporter: Option[CompileProblemReporter], + reportCachedProblems: Boolean, + incrementalCompilation: Boolean + )(using ctx: PublicJvmWorkerApi.Ctx): Result[CompilationResult] = + throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + + // public API forwarder + override def compileMixed( + upstreamCompileOutput: Seq[CompilationResult], + sources: Seq[os.Path], + compileClasspath: Seq[os.Path], + javaHome: Option[os.Path], + javacOptions: Seq[String], + scalaVersion: String, + scalaOrganization: String, + scalacOptions: Seq[String], + compilerClasspath: Seq[PathRef], + scalacPluginClasspath: Seq[PathRef], + reporter: Option[CompileProblemReporter], + reportCachedProblems: Boolean, + incrementalCompilation: Boolean, + auxiliaryClassFileExtensions: Seq[String] + )(using ctx: PublicJvmWorkerApi.Ctx): Result[CompilationResult] = + throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + + // public API forwarder + override def docJar( + scalaVersion: String, + scalaOrganization: String, + compilerClasspath: Seq[PathRef], + scalacPluginClasspath: Seq[PathRef], + javaHome: Option[os.Path], + args: Seq[String] + )(using ctx: PublicJvmWorkerApi.Ctx): Boolean = + throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") } object JvmWorkerApi { type Ctx = PublicJvmWorkerApi.Ctx diff --git a/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java b/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java index 4b064d1563ef..76bddf93b28a 100644 --- a/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java +++ b/libs/javalib/backgroundwrapper/src/mill/javalib/backgroundwrapper/MillBackgroundWrapper.java @@ -173,7 +173,7 @@ private static void checkIfWeStillNeedToBeRunning( } } - public static Optional readPreviousPid(Path pidFilePath) { + static Optional readPreviousPid(Path pidFilePath) { try { var pidStr = Files.readString(pidFilePath); return Optional.of(Long.parseLong(pidStr)); diff --git a/libs/javalib/package.mill b/libs/javalib/package.mill index 17f286dcec45..5d02af3f72ba 100644 --- a/libs/javalib/package.mill +++ b/libs/javalib/package.mill @@ -1,5 +1,6 @@ package build.libs.javalib -import com.github.lolgab.mill.mima.{Problem, ProblemFilter} + +import com.github.lolgab.mill.mima.{DirectMissingMethodProblem, MissingClassProblem, Problem, ProblemFilter} import scala.util.Properties import scala.util.chaining.* @@ -18,14 +19,8 @@ import millbuild.* object `package` extends MillStableScalaModule { - def moduleDeps = Seq( - build.libs.util, - build.libs.rpc, - build.libs.javalib.api, - build.libs.javalib.testrunner, - build.libs.javalib.backgroundwrapper - ) - + def moduleDeps = + Seq(build.libs.util, build.libs.rpc, build.libs.javalib.api, build.libs.javalib.testrunner) def mvnDeps = Seq(Deps.scalaXml) ++ // despite compiling with Scala 3, we need to include scala-reflect @@ -58,7 +53,10 @@ object `package` extends MillStableScalaModule { // This was `private[mill]`, package private doesn't have the JVM bytecode equivalent, so mima can't check it. ProblemFilter.exclude[Problem]("mill.javalib.PublishModule.checkSonatypeCreds"), ProblemFilter.exclude[Problem]("mill.javalib.publish.SonatypeHelpers.getArtifactMappings"), - ProblemFilter.exclude[Problem]("mill.javalib.publish.PublishInfo.parseFromFile") + ProblemFilter.exclude[Problem]("mill.javalib.publish.PublishInfo.parseFromFile"), + ProblemFilter.exclude[DirectMissingMethodProblem]("mill.javalib.JavaModule.resolveRelativeToOut$default$2$"), + ProblemFilter.exclude[MissingClassProblem]("mill.javalib.CompileFor"), + ProblemFilter.exclude[MissingClassProblem]("mill.javalib.CompileFor$") ) object backgroundwrapper extends MillPublishJavaModule with MillJavaModule { @@ -125,6 +123,12 @@ object `package` extends MillStableScalaModule { BuildInfo.Value("pmdVersion", Deps.RuntimeDeps.pmdDist.version), BuildInfo.Value("comLihaoyiSourcecodeVersion", Deps.sourcecode.version) ) + + override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = + super.mimaBinaryIssueFilters() ++ Seq( + // Ignore internal namespace. + ProblemFilter.exclude[Problem]("mill.javalib.api.internal.*"), + ) } object worker extends MillPublishScalaModule with BuildInfo { diff --git a/libs/javalib/src/mill/javalib/CompileArgs.scala b/libs/javalib/src/mill/javalib/CompileArgs.scala deleted file mode 100644 index 992029698f56..000000000000 --- a/libs/javalib/src/mill/javalib/CompileArgs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package mill.javalib - -/** - * @param compileTo the directory to which the classes will be compiled. We need this because we can't use the task's - * destination folder as we need that folder to be persistent and persistent tasks can not take - * arguments. - * @param forceSemanticDb if true then generates semanticdb files even if [[semanticDbWillBeNeeded]] returns false. - * This is useful when you have tasks like scalafix which need to generate semanticdb files - * even if the Mill BSP client doesn't need them. - */ -private[mill] case class CompileArgs( - compileTo: os.Path, - forceSemanticDb: Boolean -) -private[mill] object CompileArgs { - - /** Arguments for the default compilation. */ - def default(compileTo: os.Path): CompileArgs = CompileArgs(compileTo, forceSemanticDb = false) -} diff --git a/libs/javalib/src/mill/javalib/JavaModule.scala b/libs/javalib/src/mill/javalib/JavaModule.scala index 500299f7a795..92a3e21e3fb1 100644 --- a/libs/javalib/src/mill/javalib/JavaModule.scala +++ b/libs/javalib/src/mill/javalib/JavaModule.scala @@ -727,6 +727,11 @@ trait JavaModule Seq(internalDependenciesRepository()) } + /** See [[SemanticDbJavaModule.upstreamCompileOutput]] for documentation. */ + override def upstreamCompileOutput: T[Seq[CompilationResult]] = Task { + Task.traverse(transitiveModuleCompileModuleDeps)(_.compile)() + } + /** * The transitive version of `localClasspath` */ @@ -839,6 +844,11 @@ trait JavaModule true } + /** See [[SemanticDbJavaModule.compile]] for documentation. */ + override def compile: T[mill.javalib.api.CompilationResult] = Task(persistent = true) { + SemanticDbJavaModule.compile(this)() + } + /** The path where the compiled classes produced by [[compile]] are stored. */ @internal private[mill] def compileClassesPath: UnresolvedPath.DestPath = @@ -1523,7 +1533,7 @@ trait BomModule extends JavaModule { val sources = allSourceFiles() if (sources.nonEmpty) throw new Exception("A BomModule cannot have sources") - CompilationResult(Task.dest / "zinc", PathRef(Task.dest / "classes"), semanticDbFiles = None) + CompilationResult(Task.dest / "zinc", PathRef(Task.dest / "classes")) } abstract override def resources: T[Seq[PathRef]] = Task { diff --git a/libs/javalib/src/mill/javalib/JvmWorkerModule.scala b/libs/javalib/src/mill/javalib/JvmWorkerModule.scala index 31044b11d545..18b1a6a629b9 100644 --- a/libs/javalib/src/mill/javalib/JvmWorkerModule.scala +++ b/libs/javalib/src/mill/javalib/JvmWorkerModule.scala @@ -48,7 +48,7 @@ trait JvmWorkerModule extends OfflineSupportModule with CoursierModule { /** Whether Zinc debug logging is enabled. */ def zincLogDebug: T[Boolean] = Task.Input(Task.ctx().log.debugEnabled) - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.5") + @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def worker: Worker[JvmWorkerApi] = internalWorker private[mill] def internalWorker: Worker[InternalJvmWorkerApi] = Task.Worker { diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 9326e5de9f28..93fc4ddc1725 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -22,10 +22,10 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi /** * The upstream compilation output of all this module's upstream modules + * + * @note the implementation is in [[JavaModule.upstreamCompileOutput]] for binary compatibility reasons. */ - def upstreamCompileOutput: T[Seq[CompilationResult]] = Task { - Task.traverse(transitiveModuleCompileModuleDeps)(_.compile)() - } + def upstreamCompileOutput: T[Seq[CompilationResult]] def upstreamSemanticDbDatas: Task[Seq[SemanticDbJavaModule.SemanticDbData]] = Task.sequence(transitiveModuleCompileModuleDeps.map(_.semanticDbDataDetailed)) @@ -42,14 +42,12 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi * as that needs to point to the same compilation output path. * * Keep the paths in sync with [[JavaModule.bspCompileClassesPath]]. + * + * @note the implementation is in [[JavaModule.compile]] for binary compatibility reasons. */ - def compile: Task.Simple[mill.javalib.api.CompilationResult] = Task(persistent = true) { - val compileSemanticDb = bspAnyClientNeedsSemanticDb() - compileInternal.apply().apply(compileSemanticDb) - } + def compile: Task.Simple[mill.javalib.api.CompilationResult] private[mill] def compileInternal = Task.Anon { (compileSemanticDb: Boolean) => - println(s"compile(compileSemanticDb=$compileSemanticDb) start") // Prepare an empty `compileGeneratedSources` folder for java annotation processors // to write generated sources into, that can then be picked up by IDEs like IntelliJ val compileGenSources = compileGeneratedSources() @@ -69,6 +67,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi val compileTo = Task.dest Task.log.debug(s"compiling to: $compileTo") + Task.log.debug(s"semantic db enabled: $compileSemanticDb") Task.log.debug(s"effective javac options: ${jOpts.compiler}") Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") @@ -109,10 +108,10 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi } /** - * Path to sources generated as part of the `compile` step, eg. by Java annotation + * Path to sources generated as part of the `compile` step, e.g. by Java annotation * processors which often generate source code alongside classfiles during compilation. * - * Typically these do not need to be compiled again, and are only used by IDEs + * Typically, these do not need to be compiled again, and are only used by IDEs */ def compileGeneratedSources: T[os.Path] @@ -197,59 +196,47 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi defaultResolver().classpath(semanticDbJavaPluginMvnDeps()) } - // TODO review: document - private lazy val semanticDbSessionsDirWatch = - BuildCtx.watch(BuildCtx.bspSemanticDbSesssionsFolder) + /** + * Initializes the filesystem watcher for the semanticdb sessions directory. + * + * This is `lazy val` because we don't want to initialize the watcher until it's actually needed. + */ + private lazy val semanticDbSessionsDirWatch: os.Path = + BuildCtx.watch(BuildCtx.bspSemanticDbSessionsFolder) /** * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using * the same `out/` directory. */ - private[mill] def bspAnyClientNeedsSemanticDb(): Boolean = + private[mill] def bspAnyClientNeedsSemanticDb(): Boolean = { + // Allows accessing files outside of normal task scope. BuildCtx.withFilesystemCheckerDisabled { val directory = semanticDbSessionsDirWatch val bspHasClientsThatNeedSemanticDb = try { os.list(directory).exists { path => - path.last.toLongOption match { - case None => - // malformatted pid - false - case Some(pid) => - ProcessHandle.of(pid).isPresent + // Check if the sessions are not stale. + val maybePid = path.last.toLongOption + maybePid match { + case None => false // malformatted pid + case Some(pid) => ProcessHandle.of(pid).isPresent } } } catch { case _: NoSuchFileException => false } - println(s"bspHasClientsThatNeedSemanticDb=$bspHasClientsThatNeedSemanticDb") - bspHasClientsThatNeedSemanticDb } + } - def semanticDbDataDetailed: Task[SemanticDbJavaModule.SemanticDbData] = { - val task = - if (bspAnyClientNeedsSemanticDb()) compile.map(Result.Success(_)) - else compileInternal.map(run => run(true)) - - Task.Anon { - task().map { compilationResult => - // TODO review: how to do this? - // val compileWithSemanticDb = SemanticDbJavaModule.withForcedSemanticDbCompilation(compile) - // val compilationResult = compileWithSemanticDb.apply().apply(compileTo) - val semanticDbData = - compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( - "SemanticDB files were not produced, this is a bug in Mill." - )) - SemanticDbJavaModule.SemanticDbData(compilationResult, semanticDbData) - } - } + def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task { + SemanticDbJavaModule.semanticDbDataDetailed(this)() } def semanticDbData: T[PathRef] = Task { - semanticDbDataDetailed().semanticDbFiles + SemanticDbJavaModule.semanticDbData(this)() } /** @@ -308,6 +295,36 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { semanticDbFiles: PathRef ) derives upickle.ReadWriter + /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ + private[mill] def compile(mod: SemanticDbJavaModule): Task[mill.javalib.api.CompilationResult] = + Task.Anon { + val compileSemanticDb = mod.bspAnyClientNeedsSemanticDb() + mod.compileInternal.apply().apply(compileSemanticDb) + } + + /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ + private[mill] def semanticDbDataDetailed(mod: SemanticDbJavaModule): Task[SemanticDbData] = { + /** If any of the clients needs semanticdb, regular [[compile]] will produce that. */ + val task = + if (mod.bspAnyClientNeedsSemanticDb()) mod.compile.map(Result.Success(_)) + else mod.compileInternal.map(run => run(/* compileSemanticDb */ true)) + + Task.Anon { + task().map { compilationResult => + val semanticDbData = + compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( + "SemanticDB files were not produced, this is a bug in Mill." + )) + SemanticDbData(compilationResult, semanticDbData) + } + } + } + + /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ + private[mill] def semanticDbData(mod: SemanticDbJavaModule): Task[PathRef] = Task.Anon { + mod.semanticDbDataDetailed().semanticDbFiles + } + private[mill] def workerClasspath: T[Seq[PathRef]] = Task { defaultResolver().classpath(Seq( Dep.millProjectModule("mill-libs-javalib-scalameta-worker") diff --git a/libs/javalib/src/mill/javalib/UnresolvedPath.scala b/libs/javalib/src/mill/javalib/UnresolvedPath.scala index fe2da7fdaaaf..fce08d0642c2 100644 --- a/libs/javalib/src/mill/javalib/UnresolvedPath.scala +++ b/libs/javalib/src/mill/javalib/UnresolvedPath.scala @@ -8,9 +8,8 @@ import upickle.{ReadWriter, macroRW} * An unresolved path is relative to some unspecified destination * which depends on the actual configuration at evaluation time. * - * TODO REVIEW: outdated comment - * Hence, you need to call [[#resolve]] with an instance of - * [[ExecutionPathsResolver]] to get the final [[os.Path]]. + * Hence, you need to call [[resolve]] with the Mill's 'out/' path (for example from `EvaluatorApi.outPathJava` to + * get the final [[os.Path]]. */ sealed trait UnresolvedPath extends UnresolvedPathApi[os.Path] { def resolve(outPath: os.Path): os.Path diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala index 1ce498b6989e..1205465b8069 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala @@ -518,11 +518,7 @@ class ZincWorker( newResult.setup() ) ) - Result.Success(CompilationResult( - compileTo / zincCache, - PathRef(classesDir), - semanticDbFiles = None - )) + Result.Success(CompilationResult(compileTo / zincCache, PathRef(classesDir))) } catch { case e: CompileFailed => Result.Failure(e.toString) diff --git a/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala b/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala index 25fd5d71538b..dd02c9701212 100644 --- a/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala +++ b/libs/kotlinlib/src/mill/kotlinlib/KotlinModule.scala @@ -372,7 +372,7 @@ trait KotlinModule extends JavaModule with KotlinModuleApi { outer => workerResult match { case Result.Success(_) => - val cr = CompilationResult(analysisFile, PathRef(classes), semanticDbFiles = None) + val cr = CompilationResult(analysisFile, PathRef(classes)) if (!isJava) { // pure Kotlin project cr diff --git a/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala b/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala index b07dd24e14be..88c721e855af 100644 --- a/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala +++ b/libs/kotlinlib/src/mill/kotlinlib/js/KotlinJsModule.scala @@ -417,8 +417,7 @@ trait KotlinJsModule extends KotlinModule { outer => } workerResult match { - case Result.Success(_) => - CompilationResult(analysisFile, PathRef(artifactLocation), semanticDbFiles = None) + case Result.Success(_) => CompilationResult(analysisFile, PathRef(artifactLocation)) case Result.Failure(reason) => Result.Failure(reason) } } diff --git a/libs/scalalib/package.mill b/libs/scalalib/package.mill index acb0ed5daa80..bdcb6fdcdced 100644 --- a/libs/scalalib/package.mill +++ b/libs/scalalib/package.mill @@ -1,12 +1,14 @@ package build.libs.scalalib -import scala.util.Properties -import scala.util.chaining._ +import com.github.lolgab.mill.mima.{DirectMissingMethodProblem, ProblemFilter} + +import scala.util.Properties +import scala.util.chaining.* import coursier.maven.MavenRepository -import mill._ +import mill.* import mill.util.Tasks -import mill.scalalib._ -import mill.scalalib.publish._ +import mill.scalalib.* +import mill.scalalib.publish.* import mill.util.Jvm import mill.api.SelectMode import mill.contrib.buildinfo.BuildInfo @@ -32,4 +34,14 @@ object `package` extends MillStableScalaModule { val locale = if (Properties.isMac) "en_US.UTF-8" else "C.utf8" super.testForkEnv() ++ Map("LC_ALL" -> locale) } + + override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = + super.mimaBinaryIssueFilters() ++ Seq( + // This was `private[mill]`, package private doesn't have the JVM bytecode equivalent, so mima can't check it. + ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.resolveRelativeToOut"), + ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.resolveRelativeToOut$default$2"), + ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.compileClasspathTask"), + ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.compileFor"), + ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.transitiveCompileClasspathTask") + ) } diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 969614fba991..37c0cac46c27 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -269,12 +269,15 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase ) } + /** See [[SemanticDbJavaModule.compile]] for documentation. */ + override def compile: T[mill.javalib.api.CompilationResult] = Task(persistent = true) { + SemanticDbJavaModule.compile(this)() + } + /** * Keep the return paths in sync with [[bspCompileClassesPath]]. */ override private[mill] def compileInternal = Task.Anon { (compileSemanticDb: Boolean) => - println(s"compileScala(compileSemanticDb=$compileSemanticDb) start") - val sv = scalaVersion() if (sv == "2.12.4") Task.log.warn( """Attention: Zinc is known to not work properly for Scala version 2.12.4. @@ -310,13 +313,17 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase else baseClasspath } + val compileTo = Task.dest + + Task.log.debug(s"compiling to: $compileTo") + Task.log.debug(s"semantic db enabled: $compileSemanticDb") Task.log.debug(s"effective scalac options: $scalacOptions") Task.log.debug(s"effective javac options: ${jOpts.compiler}") Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") val sources = allSourceFiles().map(_.path) val compileMixedOp = ZincCompileMixed( - compileTo = Task.dest, + compileTo = compileTo, upstreamCompileOutput = upstreamCompileOutput(), sources = sources, compileClasspath = compileClasspath.map(_.path), @@ -647,8 +654,12 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase } // binary compatibility forwarder - override def semanticDbData: T[PathRef] = - // This is the same as `super.semanticDbData()`, but we can't call it directly - // because then it generates a forwarder which breaks binary compatibility. - Task { semanticDbDataDetailed().semanticDbFiles } + override def semanticDbDataDetailed: T[SemanticDbJavaModule.SemanticDbData] = Task { + SemanticDbJavaModule.semanticDbDataDetailed(this)() + } + + // binary compatibility forwarder + override def semanticDbData: T[PathRef] = Task { + SemanticDbJavaModule.semanticDbData(this)() + } } diff --git a/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala index 91e3e8e95552..0b667d6267c3 100644 --- a/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala +++ b/runner/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala @@ -267,7 +267,7 @@ private class MillBuildServer( // Inform other BSP clients that we want to use SemanticDB val pid = ProcessHandle.current().pid() - val pidFile = BuildCtx.bspSemanticDbSesssionsFolder / pid.toString + val pidFile = BuildCtx.bspSemanticDbSessionsFolder / pid.toString os.write.over(pidFile, "", createFolders = true) pidFile.toNIO.toFile.deleteOnExit() } From 317f5fc9c4f373d5e62034990ce2027c82751bcc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:58:24 +0000 Subject: [PATCH 10/17] [autofix.ci] apply automated fixes --- .../server/src/mill/server/Server.scala | 2 +- libs/javalib/package.mill | 13 +++++++++--- .../mill/javalib/SemanticDbJavaModule.scala | 3 ++- libs/scalalib/package.mill | 20 ++++++++++++++----- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/libs/daemon/server/src/mill/server/Server.scala b/libs/daemon/server/src/mill/server/Server.scala index 37484af87c11..b79b0871f39b 100644 --- a/libs/daemon/server/src/mill/server/Server.scala +++ b/libs/daemon/server/src/mill/server/Server.scala @@ -176,7 +176,7 @@ abstract class Server(args: Server.Args) { exit = msg => { serverLog(s"watchProcessIdFile: $msg") serverSocket.close() - } + } ) // Wrapper object to encapsulate `activeConnections` and `inactiveTimestampOpt`, diff --git a/libs/javalib/package.mill b/libs/javalib/package.mill index 5d02af3f72ba..9ecf91f93b01 100644 --- a/libs/javalib/package.mill +++ b/libs/javalib/package.mill @@ -1,6 +1,11 @@ package build.libs.javalib -import com.github.lolgab.mill.mima.{DirectMissingMethodProblem, MissingClassProblem, Problem, ProblemFilter} +import com.github.lolgab.mill.mima.{ + DirectMissingMethodProblem, + MissingClassProblem, + Problem, + ProblemFilter +} import scala.util.Properties import scala.util.chaining.* @@ -54,7 +59,9 @@ object `package` extends MillStableScalaModule { ProblemFilter.exclude[Problem]("mill.javalib.PublishModule.checkSonatypeCreds"), ProblemFilter.exclude[Problem]("mill.javalib.publish.SonatypeHelpers.getArtifactMappings"), ProblemFilter.exclude[Problem]("mill.javalib.publish.PublishInfo.parseFromFile"), - ProblemFilter.exclude[DirectMissingMethodProblem]("mill.javalib.JavaModule.resolveRelativeToOut$default$2$"), + ProblemFilter.exclude[DirectMissingMethodProblem]( + "mill.javalib.JavaModule.resolveRelativeToOut$default$2$" + ), ProblemFilter.exclude[MissingClassProblem]("mill.javalib.CompileFor"), ProblemFilter.exclude[MissingClassProblem]("mill.javalib.CompileFor$") ) @@ -127,7 +134,7 @@ object `package` extends MillStableScalaModule { override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = super.mimaBinaryIssueFilters() ++ Seq( // Ignore internal namespace. - ProblemFilter.exclude[Problem]("mill.javalib.api.internal.*"), + ProblemFilter.exclude[Problem]("mill.javalib.api.internal.*") ) } diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 93fc4ddc1725..dc8d8661f058 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -304,10 +304,11 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ private[mill] def semanticDbDataDetailed(mod: SemanticDbJavaModule): Task[SemanticDbData] = { + /** If any of the clients needs semanticdb, regular [[compile]] will produce that. */ val task = if (mod.bspAnyClientNeedsSemanticDb()) mod.compile.map(Result.Success(_)) - else mod.compileInternal.map(run => run(/* compileSemanticDb */ true)) + else mod.compileInternal.map(run => run( /* compileSemanticDb */ true)) Task.Anon { task().map { compilationResult => diff --git a/libs/scalalib/package.mill b/libs/scalalib/package.mill index bdcb6fdcdced..2827bb5cb4e8 100644 --- a/libs/scalalib/package.mill +++ b/libs/scalalib/package.mill @@ -38,10 +38,20 @@ object `package` extends MillStableScalaModule { override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = super.mimaBinaryIssueFilters() ++ Seq( // This was `private[mill]`, package private doesn't have the JVM bytecode equivalent, so mima can't check it. - ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.resolveRelativeToOut"), - ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.resolveRelativeToOut$default$2"), - ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.compileClasspathTask"), - ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.compileFor"), - ProblemFilter.exclude[DirectMissingMethodProblem]("mill.scalalib.scalafmt.ScalafmtWorkerModule.transitiveCompileClasspathTask") + ProblemFilter.exclude[DirectMissingMethodProblem]( + "mill.scalalib.scalafmt.ScalafmtWorkerModule.resolveRelativeToOut" + ), + ProblemFilter.exclude[DirectMissingMethodProblem]( + "mill.scalalib.scalafmt.ScalafmtWorkerModule.resolveRelativeToOut$default$2" + ), + ProblemFilter.exclude[DirectMissingMethodProblem]( + "mill.scalalib.scalafmt.ScalafmtWorkerModule.compileClasspathTask" + ), + ProblemFilter.exclude[DirectMissingMethodProblem]( + "mill.scalalib.scalafmt.ScalafmtWorkerModule.compileFor" + ), + ProblemFilter.exclude[DirectMissingMethodProblem]( + "mill.scalalib.scalafmt.ScalafmtWorkerModule.transitiveCompileClasspathTask" + ) ) } From 93777e26a8bbb0d206d17e701711d06a044de23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 3 Oct 2025 12:28:52 +0300 Subject: [PATCH 11/17] Docs. --- libs/javalib/src/mill/javalib/JavaModule.scala | 1 - libs/scalalib/src/mill/scalalib/ScalaModule.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/libs/javalib/src/mill/javalib/JavaModule.scala b/libs/javalib/src/mill/javalib/JavaModule.scala index 92a3e21e3fb1..f5fd06b5f2bc 100644 --- a/libs/javalib/src/mill/javalib/JavaModule.scala +++ b/libs/javalib/src/mill/javalib/JavaModule.scala @@ -844,7 +844,6 @@ trait JavaModule true } - /** See [[SemanticDbJavaModule.compile]] for documentation. */ override def compile: T[mill.javalib.api.CompilationResult] = Task(persistent = true) { SemanticDbJavaModule.compile(this)() } diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index 37c0cac46c27..da6fbd5827de 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -269,7 +269,6 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase ) } - /** See [[SemanticDbJavaModule.compile]] for documentation. */ override def compile: T[mill.javalib.api.CompilationResult] = Task(persistent = true) { SemanticDbJavaModule.compile(this)() } From 73a724e864283d658e179d329df1c63af6477d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 3 Oct 2025 14:48:12 +0300 Subject: [PATCH 12/17] Undeprecate public JVM worker API --- .../src/mill/javalib/api/JvmWorkerApi.scala | 5 -- .../javalib/api/internal/JvmWorkerApi.scala | 62 ++++++++++++++++--- .../mill/javalib/SemanticDbJavaModule.scala | 12 ++-- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala index 8079ffdabfd3..d4d804af2350 100644 --- a/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/JvmWorkerApi.scala @@ -6,10 +6,7 @@ import mill.api.daemon.internal.CompileProblemReporter object JvmWorkerApi { type Ctx = mill.api.TaskCtx.Dest & mill.api.TaskCtx.Log & mill.api.TaskCtx.Env } -@deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") trait JvmWorkerApi { - - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def compileJava( upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], @@ -21,7 +18,6 @@ trait JvmWorkerApi { incrementalCompilation: Boolean )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def compileMixed( upstreamCompileOutput: Seq[CompilationResult], sources: Seq[os.Path], @@ -39,7 +35,6 @@ trait JvmWorkerApi { auxiliaryClassFileExtensions: Seq[String] )(using ctx: JvmWorkerApi.Ctx): mill.api.Result[CompilationResult] - @deprecated("Public API of JvmWorkerApi is deprecated.", "1.0.7") def docJar( scalaVersion: String, scalaOrganization: String, diff --git a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala index 2d1d7cdefc1f..719087552b9f 100644 --- a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala +++ b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala @@ -5,10 +5,6 @@ import mill.api.daemon.internal.CompileProblemReporter import mill.javalib.api.CompilationResult import mill.javalib.api.JvmWorkerApi as PublicJvmWorkerApi -import scala.annotation.nowarn - -//noinspection ScalaDeprecation -@nowarn("cat=deprecation") trait JvmWorkerApi extends PublicJvmWorkerApi { /** Compile a Java-only project. */ @@ -45,8 +41,23 @@ trait JvmWorkerApi extends PublicJvmWorkerApi { reporter: Option[CompileProblemReporter], reportCachedProblems: Boolean, incrementalCompilation: Boolean - )(using ctx: PublicJvmWorkerApi.Ctx): Result[CompilationResult] = - throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + )(using ctx: PublicJvmWorkerApi.Ctx): Result[CompilationResult] = { + val jOpts = JavaCompilerOptions(javacOptions) + compileJava( + ZincCompileJava( + compileTo = ctx.dest, + upstreamCompileOutput = upstreamCompileOutput, + sources = sources, + compileClasspath = compileClasspath, + javacOptions = jOpts.compiler, + incrementalCompilation = incrementalCompilation + ), + javaHome = javaHome, + javaRuntimeOptions = jOpts.runtime, + reporter = reporter, + reportCachedProblems = reportCachedProblems + ) + } // public API forwarder override def compileMixed( @@ -64,8 +75,29 @@ trait JvmWorkerApi extends PublicJvmWorkerApi { reportCachedProblems: Boolean, incrementalCompilation: Boolean, auxiliaryClassFileExtensions: Seq[String] - )(using ctx: PublicJvmWorkerApi.Ctx): Result[CompilationResult] = - throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + )(using ctx: PublicJvmWorkerApi.Ctx): Result[CompilationResult] = { + val jOpts = JavaCompilerOptions(javacOptions) + compileMixed( + ZincCompileMixed( + compileTo = ctx.dest, + upstreamCompileOutput = upstreamCompileOutput, + sources = sources, + compileClasspath = compileClasspath, + javacOptions = jOpts.compiler, + scalaVersion = scalaVersion, + scalaOrganization = scalaOrganization, + scalacOptions = scalacOptions, + compilerClasspath = compilerClasspath, + scalacPluginClasspath = scalacPluginClasspath, + incrementalCompilation = incrementalCompilation, + auxiliaryClassFileExtensions = auxiliaryClassFileExtensions + ), + javaHome = javaHome, + javaRuntimeOptions = jOpts.runtime, + reporter = reporter, + reportCachedProblems = reportCachedProblems + ) + } // public API forwarder override def docJar( @@ -75,8 +107,18 @@ trait JvmWorkerApi extends PublicJvmWorkerApi { scalacPluginClasspath: Seq[PathRef], javaHome: Option[os.Path], args: Seq[String] - )(using ctx: PublicJvmWorkerApi.Ctx): Boolean = - throw UnsupportedOperationException("Public API of JvmWorkerApi is deprecated.") + )(using ctx: PublicJvmWorkerApi.Ctx): Boolean = { + scaladocJar( + ZincScaladocJar( + scalaVersion = scalaVersion, + scalaOrganization = scalaOrganization, + compilerClasspath = compilerClasspath, + scalacPluginClasspath = scalacPluginClasspath, + args = args + ), + javaHome = javaHome + ) + } } object JvmWorkerApi { type Ctx = PublicJvmWorkerApi.Ctx diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index dc8d8661f058..94afcc45c239 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -64,13 +64,6 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi else opts } - val compileTo = Task.dest - - Task.log.debug(s"compiling to: $compileTo") - Task.log.debug(s"semantic db enabled: $compileSemanticDb") - Task.log.debug(s"effective javac options: ${jOpts.compiler}") - Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") - val sources = allSourceFiles().map(_.path) val compileClasspathSemanticDbJavaPlugin = @@ -86,6 +79,11 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi incrementalCompilation = zincIncrementalCompilation() ) + Task.log.debug(s"compiling to: ${compileJavaOp.compileTo}") + Task.log.debug(s"semantic db enabled: $compileSemanticDb") + Task.log.debug(s"effective javac options: ${jOpts.compiler}") + Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") + val compileJavaResult = jvmWorker() .internalWorker() .compileJava( From 9d66aa2e29a87a9b4591f3ebf7f96b52b8babb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 3 Oct 2025 14:49:05 +0300 Subject: [PATCH 13/17] docs --- .../src/mill/javalib/SemanticDbJavaModule.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 94afcc45c239..b374ceadb7f9 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -1,18 +1,16 @@ package mill.javalib -import mill.api.{BuildCtx, Discover, ExternalModule, ModuleRef, PathRef, Result, experimental} import mill.api.daemon.internal.SemanticDbJavaModuleApi +import mill.api.daemon.internal.bsp.BspBuildTarget +import mill.api.* import mill.constants.CodeGenConstants -import mill.util.BuildInfo +import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} import mill.javalib.api.{CompilationResult, JvmWorkerUtil} -import mill.util.Version +import mill.util.{BuildInfo, Version} import mill.{T, Task} -import scala.jdk.CollectionConverters.* -import mill.api.daemon.internal.bsp.BspBuildTarget -import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} - import java.nio.file.NoSuchFileException +import scala.jdk.CollectionConverters.* @experimental trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi @@ -303,7 +301,10 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ private[mill] def semanticDbDataDetailed(mod: SemanticDbJavaModule): Task[SemanticDbData] = { - /** If any of the clients needs semanticdb, regular [[compile]] will produce that. */ + /** + * If any of the clients needs semanticdb, regular [[compile]] will produce that, so let's reuse the tasks output + * to save resources. + */ val task = if (mod.bspAnyClientNeedsSemanticDb()) mod.compile.map(Result.Success(_)) else mod.compileInternal.map(run => run( /* compileSemanticDb */ true)) From ee7f50ed2e02788b78f1532d815cc4d581a7894d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 3 Oct 2025 16:32:54 +0300 Subject: [PATCH 14/17] test fixes --- .../test/src/mill/playlib/PlayModuleTests.scala | 1 + .../mill/playlib/PlaySingleApiModuleTests.scala | 2 +- .../src/mill/javalib/SemanticDbJavaModule.scala | 1 + .../src/mill/scalalib/CrossVersionTests.scala | 1 + .../test/src/mill/scalalib/HelloWorldTests.scala | 16 +++++++++++----- .../mill/scalalib/LargeAssemblyExeTests.scala | 1 + .../mill/scalalib/ScalaCrossVersionTests.scala | 2 +- .../src/mill/scalalib/ScalaDotty213Tests.scala | 4 ++++ .../ScalaMixedProjectSemanticDbTests.scala | 4 ++-- .../ScalaMultiModuleClasspathsTests.scala | 4 +++- .../src/mill/scalalib/ScalaSemanticDbTests.scala | 8 ++++---- .../src/mill/scalalib/ScalaTypeLevelTests.scala | 4 ++++ .../mill/scalalib/ScalaVersionsRangesTests.scala | 6 ++++++ mill-build/src/millbuild/Deps.scala | 2 +- 14 files changed, 41 insertions(+), 15 deletions(-) diff --git a/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala b/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala index 44238b5393c2..562a3fdbbe15 100644 --- a/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala +++ b/contrib/playlib/test/src/mill/playlib/PlayModuleTests.scala @@ -14,6 +14,7 @@ object PlayModuleTests extends TestSuite with PlayTestSuite { val (crossScalaVersion, crossPlayVersion) = (crossValue, crossValue2) override def playVersion = crossPlayVersion override def scalaVersion = crossScalaVersion + object test extends PlayTests override def mvnDeps = Task { super.mvnDeps() ++ Seq(ws()) } } diff --git a/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala b/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala index 553838139c01..588ebe77865d 100644 --- a/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala +++ b/contrib/playlib/test/src/mill/playlib/PlaySingleApiModuleTests.scala @@ -10,7 +10,7 @@ object PlaySingleApiModuleTests extends TestSuite with PlayTestSuite { object playsingleapi extends TestRootModule with PlayApiModule { override val moduleDir = os.temp() // workaround problem in `SingleModule` override def playVersion = Task { testPlay28 } - override def scalaVersion = Task { "2.13.12" } + override def scalaVersion = Task { "2.13.17" } object test extends PlayTests lazy val millDiscover = Discover[this.type] diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index b374ceadb7f9..104933241811 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -117,6 +117,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi def compileClasspath: Task[Seq[PathRef]] def moduleDeps: Seq[JavaModule] + /** The version of SemanticDB plugin. */ def semanticDbVersion: T[String] = Task.Input { val builtin = SemanticDbJavaModuleApi.buildTimeSemanticDbVersion val requested = Task.env.getOrElse[String]( diff --git a/libs/scalalib/test/src/mill/scalalib/CrossVersionTests.scala b/libs/scalalib/test/src/mill/scalalib/CrossVersionTests.scala index f275de7b2078..2f0f0d9f11df 100644 --- a/libs/scalalib/test/src/mill/scalalib/CrossVersionTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/CrossVersionTests.scala @@ -25,6 +25,7 @@ object CrossVersionTests extends TestSuite { |└─ org.scala-lang:scala-library:2.13.10 |""".stripMargin override def scalaVersion = "2.13.10" + override def semanticDbVersion = "4.9.3" // last version to support Scala 2.13.10 override def mvnDeps = Seq(mvn"com.lihaoyi::upickle:1.4.0") } diff --git a/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala b/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala index 9255d0cc6cd3..70d476821585 100644 --- a/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala @@ -23,10 +23,6 @@ object HelloWorldTests extends TestSuite { trait HelloWorldModule extends scalalib.ScalaModule { def scalaVersion = scala212Version - override def semanticDbVersion: T[String] = Task { - // The latest semanticDB release for Scala 2.12.6 - "4.1.9" - } } trait SemanticModule extends scalalib.ScalaModule { def scalaVersion = scala213Version @@ -42,6 +38,9 @@ object HelloWorldTests extends TestSuite { object HelloWorldNonPrecompiledBridge extends TestRootModule { object core extends HelloWorldModule { override def scalaVersion = "2.12.1" + + // Hack to disable semanticdb, because there's no semanticdb for this ancient scala version. + override protected def semanticDbEnablePluginScalacOptions = Seq.empty } lazy val millDiscover = Discover[this.type] @@ -52,7 +51,14 @@ object HelloWorldTests extends TestSuite { scala212Version, scala213Version ) - trait HelloWorldCross extends CrossScalaModule + + trait HelloWorldCross extends CrossScalaModule { + override def semanticDbVersion = Task { scalaVersion() match { + case `scala2123Version` => "2.1.2" + case _ => super.semanticDbVersion() + } } + } + lazy val millDiscover = Discover[this.type] } diff --git a/libs/scalalib/test/src/mill/scalalib/LargeAssemblyExeTests.scala b/libs/scalalib/test/src/mill/scalalib/LargeAssemblyExeTests.scala index f1756212fe1e..ef75d1bfc0b1 100644 --- a/libs/scalalib/test/src/mill/scalalib/LargeAssemblyExeTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/LargeAssemblyExeTests.scala @@ -19,6 +19,7 @@ object LargeAssemblyExeTests extends TestSuite { trait ExtraDeps extends ScalaModule { def scalaVersion = "2.13.11" + def semanticDbVersion = "4.9.9" // last version to support this Scala def sources = Task.Sources(mill.api.BuildCtx.workspaceRoot / "src") diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaCrossVersionTests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaCrossVersionTests.scala index d91007c783f5..b30695c38f3e 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaCrossVersionTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaCrossVersionTests.scala @@ -30,7 +30,7 @@ object ScalaCrossVersionTests extends TestSuite { CrossModuleDeps.cuttingEdge(scala213Version).moduleDeps ).getMessage assert( - message == s"Unable to find compatible cross version between ${scala213Version} and 2.12.6,3.2.0" + message == s"Unable to find compatible cross version between ${scala213Version} and 2.12.20,3.2.0" ) } } diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaDotty213Tests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaDotty213Tests.scala index d1f5f1f096d6..2a0f85387cc0 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaDotty213Tests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaDotty213Tests.scala @@ -9,6 +9,10 @@ object ScalaDotty213Tests extends TestSuite { object Dotty213 extends TestRootModule { object foo extends ScalaModule { def scalaVersion = "0.18.1-RC1" + + // Hack to disable semanticdb, because there's no semanticdb for this ancient scala version. + override protected def semanticDbEnablePluginScalacOptions = Seq.empty + override def mvnDeps = Seq( mvn"org.scala-lang.modules::scala-xml:1.2.0".withDottyCompat(scalaVersion()) ) diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaMixedProjectSemanticDbTests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaMixedProjectSemanticDbTests.scala index e9aaeeb14415..00a87ec12003 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaMixedProjectSemanticDbTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaMixedProjectSemanticDbTests.scala @@ -31,7 +31,7 @@ object ScalaMixedProjectSemanticDbTests extends TestSuite { println("first - expected full compile") val Right(result) = eval.apply(SemanticWorld.core.semanticDbData): @unchecked - val dataPath = eval.outPath / "core/semanticDbDataDetailed.dest/data" + val dataPath = eval.outPath / "core/semanticDbDataDetailed.dest/semanticdb-data" val outputFiles = os.walk(result.value.path).filter(os.isFile).map(_.relativeTo(result.value.path)) @@ -39,7 +39,7 @@ object ScalaMixedProjectSemanticDbTests extends TestSuite { assert( result.value.path == dataPath, outputFiles.nonEmpty, - outputFiles.toSet == expectedSemFiles, + outputFiles.toVector.sorted == expectedSemFiles.toVector.sorted, result.evalCount > 0, os.exists(dataPath / os.up / "zinc") ) diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaMultiModuleClasspathsTests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaMultiModuleClasspathsTests.scala index 57366c58dc48..c7c4d91ae517 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaMultiModuleClasspathsTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaMultiModuleClasspathsTests.scala @@ -12,7 +12,7 @@ object ScalaMultiModuleClasspathsTests extends TestSuite { object MultiModuleClasspaths extends TestRootModule { trait FooModule extends ScalaModule { def scalaVersion = "2.13.12" - + def semanticDbVersion = "4.8.4" // Last version that supports 2.13.12 def mvnDeps = Seq(mvn"com.lihaoyi::sourcecode:0.2.2") def compileMvnDeps = Seq(mvn"com.lihaoyi::geny:0.4.2") def runMvnDeps = Seq(mvn"com.lihaoyi::utest:0.8.5") @@ -20,6 +20,7 @@ object ScalaMultiModuleClasspathsTests extends TestSuite { } trait BarModule extends ScalaModule { def scalaVersion = "2.13.12" + def semanticDbVersion = "4.8.4" // Last version that supports 2.13.12 def mvnDeps = Seq(mvn"com.lihaoyi::sourcecode:0.2.1") def compileMvnDeps = Seq(mvn"com.lihaoyi::geny:0.4.1") @@ -28,6 +29,7 @@ object ScalaMultiModuleClasspathsTests extends TestSuite { } trait QuxModule extends ScalaModule { def scalaVersion = "2.13.12" + def semanticDbVersion = "4.8.4" // Last version that supports 2.13.12 def mvnDeps = Seq(mvn"com.lihaoyi::sourcecode:0.2.0") def compileMvnDeps = Seq(mvn"com.lihaoyi::geny:0.4.0") diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaSemanticDbTests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaSemanticDbTests.scala index 85af62315b6e..ca102a1ebfef 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaSemanticDbTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaSemanticDbTests.scala @@ -34,7 +34,7 @@ object ScalaSemanticDbTests extends TestSuite { println("first - expected full compile") val Right(result) = eval.apply(SemanticWorld.core.semanticDbData): @unchecked - val dataPath = eval.outPath / "core/semanticDbDataDetailed.dest/data" + val dataPath = eval.outPath / "core/semanticDbDataDetailed.dest/semanticdb-data" val outputFiles = os.walk(result.value.path).filter(os.isFile).map(_.relativeTo(result.value.path)) @@ -42,7 +42,7 @@ object ScalaSemanticDbTests extends TestSuite { assert( result.value.path == dataPath, outputFiles.nonEmpty, - outputFiles.toSet == expectedSemFiles, + outputFiles.toVector.sorted == expectedSemFiles.toVector.sorted, result.evalCount > 0, os.exists(dataPath / os.up / "zinc") ) @@ -79,7 +79,7 @@ object ScalaSemanticDbTests extends TestSuite { println("first - expected full compile") val Right(result) = eval.apply(SemanticWorld.core.semanticDbData): @unchecked - val dataPath = eval.outPath / "core/semanticDbDataDetailed.dest/data" + val dataPath = eval.outPath / "core/semanticDbDataDetailed.dest/semanticdb-data" val outputFiles = os.walk(result.value.path).filter(os.isFile).map(_.relativeTo(result.value.path)) @@ -87,7 +87,7 @@ object ScalaSemanticDbTests extends TestSuite { val filteredOutputFiles = outputFiles.toSet.filter(_.ext != "class") assert( result.value.path == dataPath, - filteredOutputFiles == expectedSemFiles, + filteredOutputFiles.toVector.sorted == expectedSemFiles.toVector.sorted, result.evalCount > 0 ) } diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaTypeLevelTests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaTypeLevelTests.scala index 0f1bdbc4512c..41713f6923a7 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaTypeLevelTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaTypeLevelTests.scala @@ -12,6 +12,10 @@ object ScalaTypeLevelTests extends TestSuite { object foo extends ScalaModule { override def scalaVersion = "2.11.8" override def scalaOrganization = "org.typelevel" + + // Hack to disable semanticdb, because there's no semanticdb for this ancient scala version. + override protected def semanticDbEnablePluginScalacOptions = Seq.empty + override def ammoniteVersion = "1.6.7" override def mvnDeps = Seq( diff --git a/libs/scalalib/test/src/mill/scalalib/ScalaVersionsRangesTests.scala b/libs/scalalib/test/src/mill/scalalib/ScalaVersionsRangesTests.scala index 60a2134bd378..855c79b82755 100644 --- a/libs/scalalib/test/src/mill/scalalib/ScalaVersionsRangesTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/ScalaVersionsRangesTests.scala @@ -10,10 +10,16 @@ object ScalaVersionsRangesTests extends TestSuite { object ScalaVersionsRanges extends TestRootModule { object core extends Cross[CoreCrossModule]("2.12.13", "2.13.5", "3.3.3") + trait CoreCrossModule extends CrossScalaModule with CrossScalaVersionRanges { + + override def semanticDbVersion = "4.8.4" // last version to support these Scala versions + object test extends ScalaTests with TestModule.Utest { override def utestVersion = "0.8.5" + + override def semanticDbVersion = "4.8.4" // last version to support these Scala versions } } diff --git a/mill-build/src/millbuild/Deps.scala b/mill-build/src/millbuild/Deps.scala index bd0afb0bbbc8..1f7890664fb2 100644 --- a/mill-build/src/millbuild/Deps.scala +++ b/mill-build/src/millbuild/Deps.scala @@ -17,7 +17,7 @@ object Deps { val testScala213Version = "2.13.16" // Scala Native 4.2 will not get releases for new Scala version val testScala213VersionForScalaNative42 = "2.13.16" - val testScala212Version = "2.12.6" + val testScala212Version = "2.12.20" val testScala32Version = "3.2.0" val testScala33Version = "3.3.1" From e24a0b2eda2d26fccb3827019a446f69a0d59ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 3 Oct 2025 17:36:15 +0300 Subject: [PATCH 15/17] test fixes --- example/scalalib/linting/3-acyclic/build.mill | 1 + .../scalalib/spark/1-hello-spark/build.mill | 2 + .../spark/3-semi-realistic/build.mill | 2 + .../feature/scoverage/resources/build.mill | 11 +++++ .../scoverage/src/ScoverageTests.scala | 5 ++- .../mill/javalib/SemanticDbJavaModule.scala | 41 ++++++++++++++++--- .../scalajslib/FullOptESModuleTests.scala | 1 + .../mill/scalanativelib/FeaturesTests.scala | 1 + .../src/mill/testkit/IntegrationTester.scala | 5 +++ 9 files changed, 61 insertions(+), 8 deletions(-) diff --git a/example/scalalib/linting/3-acyclic/build.mill b/example/scalalib/linting/3-acyclic/build.mill index a2b03695cdcd..3d0b93adf86f 100644 --- a/example/scalalib/linting/3-acyclic/build.mill +++ b/example/scalalib/linting/3-acyclic/build.mill @@ -19,6 +19,7 @@ import mill.*, scalalib.* object `package` extends ScalaModule { def scalaVersion = "2.13.11" + def semanticDbVersion = "4.9.9" // last version to support this Scala def compileMvnDeps = Seq(mvn"com.lihaoyi:::acyclic:0.3.15") def scalacPluginMvnDeps = Seq(mvn"com.lihaoyi:::acyclic:0.3.15") def scalacOptions = Seq("-P:acyclic:force") diff --git a/example/scalalib/spark/1-hello-spark/build.mill b/example/scalalib/spark/1-hello-spark/build.mill index 2d5b96e88fca..252ac1c91611 100644 --- a/example/scalalib/spark/1-hello-spark/build.mill +++ b/example/scalalib/spark/1-hello-spark/build.mill @@ -3,6 +3,8 @@ import mill.*, scalalib.* object foo extends ScalaModule { def scalaVersion = "2.12.15" + def semanticDbVersion = "4.9.0" // last version to support this Scala + def mvnDeps = Seq( mvn"org.apache.spark::spark-core:3.5.4", mvn"org.apache.spark::spark-sql:3.5.4" diff --git a/example/scalalib/spark/3-semi-realistic/build.mill b/example/scalalib/spark/3-semi-realistic/build.mill index 5bcd51d68101..15de8948dea0 100644 --- a/example/scalalib/spark/3-semi-realistic/build.mill +++ b/example/scalalib/spark/3-semi-realistic/build.mill @@ -3,6 +3,8 @@ import mill.*, scalalib.* object `package` extends ScalaModule { def scalaVersion = "2.12.15" + def semanticDbVersion = "4.9.0" // last version to support this Scala + def mvnDeps = Seq( mvn"org.apache.spark::spark-core:3.5.6", mvn"org.apache.spark::spark-sql:3.5.6" diff --git a/integration/feature/scoverage/resources/build.mill b/integration/feature/scoverage/resources/build.mill index db0ebca789da..797038148103 100644 --- a/integration/feature/scoverage/resources/build.mill +++ b/integration/feature/scoverage/resources/build.mill @@ -17,7 +17,15 @@ object Deps { object core extends Cross[CoreCross]("2.13.11") trait CoreCross extends CrossScalaModule with ScoverageModule { override def scoverageVersion = "2.0.11" + override def semanticDbVersion = "4.9.9" // last version to support this Scala + + // customized scoverage data + override lazy val scoverage: ScoverageData = new ScoverageData { + override def semanticDbVersion = "4.9.9" // last version to support this Scala + } + object test extends ScoverageTests with TestModule.ScalaTest { + override def semanticDbVersion = "4.9.9" // last version to support this Scala override def mvnDeps = Seq(Deps.scalaTest, Deps.millMain) } } @@ -25,8 +33,11 @@ trait CoreCross extends CrossScalaModule with ScoverageModule { object extra extends ScalaModule with ScoverageModule { override def scoverageVersion = "2.0.11" override def scalaVersion = "2.13.11" + override def semanticDbVersion = "4.9.9" // last version to support this Scala + // customized scoverage data override lazy val scoverage: ScoverageData = new ScoverageData { // some customizations + override def semanticDbVersion = "4.9.9" // last version to support this Scala } } diff --git a/integration/feature/scoverage/src/ScoverageTests.scala b/integration/feature/scoverage/src/ScoverageTests.scala index b6f19364cd2a..7d3c0886ec24 100644 --- a/integration/feature/scoverage/src/ScoverageTests.scala +++ b/integration/feature/scoverage/src/ScoverageTests.scala @@ -9,8 +9,9 @@ object ScoverageTests extends UtestIntegrationTestSuite { test("test") - retry(3) { integrationTest { tester => import tester._ - assert(eval("__.compile").isSuccess) - assert(eval("core[2.13.11].scoverage.xmlReport").isSuccess) + + prepEval("__.compile").runWithClues(r => assert(r.isSuccess)) + prepEval("core[2.13.11].scoverage.xmlReport").runWithClues(r => assert(r.isSuccess)) } } } diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 104933241811..3c518739247b 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -138,6 +138,14 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi def semanticDbScalaVersion: T[String] = BuildInfo.scalaVersion + private def semanticDbScalaArtifactOrganization: String = "org.scalameta" + + private def semanticDbScalaArtifactName(scalaVersion: String): String = + s"semanticdb-scalac_$scalaVersion" + + private def semanticDbScalaArtifact(scalaVersion: String, semanticDbVersion: String): Dep = + mvn"$semanticDbScalaArtifactOrganization:${semanticDbScalaArtifactName(scalaVersion)}:$semanticDbVersion" + protected def semanticDbPluginMvnDeps: T[Seq[Dep]] = Task { val sv = semanticDbScalaVersion() val semDbVersion = semanticDbVersion() @@ -152,9 +160,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi } else if (JvmWorkerUtil.isScala3(sv)) { Seq.empty[Dep] } else { - Seq( - mvn"org.scalameta:semanticdb-scalac_${sv}:${semDbVersion}" - ) + Seq(semanticDbScalaArtifact(sv, semDbVersion)) } } @@ -179,9 +185,32 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi * Scalac options to activate the compiler plugins. */ protected def semanticDbEnablePluginScalacOptions: T[Seq[String]] = Task { - val resolvedJars = defaultResolver().classpath( - semanticDbPluginMvnDeps().map(_.exclude("*" -> "*")) - ) + val resolvedJars = + try { + defaultResolver().classpath( + semanticDbPluginMvnDeps().map(_.exclude("*" -> "*")) + ) + } catch { + case t + if t.getMessage.contains(s"$semanticDbScalaArtifactOrganization:${semanticDbScalaArtifactName("")}") => + Task.log.error( + s"""!!! It seems that your SemanticDB version is not compatible with your Scala version !!! + | + |Specify the version that is compatible with your scala version in your build: + | + | ``` + | object myScalaApp extends ScalaModule { + | def semanticDbVersion = "" + | } + | ``` + | + |One option to find the last SemanticDB version that is compatible with your Scala version is to visit + |https://mvnrepository.com/artifact/org.scalameta/semanticdb-scalac and find the value in "Version" + |column that has your Scala version next to it in the "Scala" column. + |""".stripMargin + ) + throw t + } resolvedJars.iterator.map(jar => s"-Xplugin:${jar.path}").toSeq } diff --git a/libs/scalajslib/test/src/mill/scalajslib/FullOptESModuleTests.scala b/libs/scalajslib/test/src/mill/scalajslib/FullOptESModuleTests.scala index 462b2f5b1c5e..d21ffe673865 100644 --- a/libs/scalajslib/test/src/mill/scalajslib/FullOptESModuleTests.scala +++ b/libs/scalajslib/test/src/mill/scalajslib/FullOptESModuleTests.scala @@ -12,6 +12,7 @@ object FullOptESModuleTests extends TestSuite { object fullOptESModuleModule extends ScalaJSModule { override def scalaVersion = "2.13.4" + override def semanticDbVersion = "4.8.4" // last compatible version override def scalaJSVersion = "1.7.0" override def moduleKind = ModuleKind.ESModule } diff --git a/libs/scalanativelib/test/src/mill/scalanativelib/FeaturesTests.scala b/libs/scalanativelib/test/src/mill/scalanativelib/FeaturesTests.scala index 2205225a34c9..c6549b8a23d0 100644 --- a/libs/scalanativelib/test/src/mill/scalanativelib/FeaturesTests.scala +++ b/libs/scalanativelib/test/src/mill/scalanativelib/FeaturesTests.scala @@ -10,6 +10,7 @@ object FeaturesTests extends TestSuite { object Features extends TestRootModule with ScalaNativeModule { def scalaNativeVersion = "0.5.0" def scalaVersion = "2.13.10" + def semanticDbVersion = "4.9.3" // last compatible version def nativeIncrementalCompilation = true override lazy val millDiscover = Discover[this.type] } diff --git a/testkit/src/mill/testkit/IntegrationTester.scala b/testkit/src/mill/testkit/IntegrationTester.scala index 196c9a1b22a0..f38db37d8906 100644 --- a/testkit/src/mill/testkit/IntegrationTester.scala +++ b/testkit/src/mill/testkit/IntegrationTester.scala @@ -77,6 +77,11 @@ object IntegrationTester { asTestValue(propagateEnv), asTestValue(shutdownGracePeriod) ) + + /** Enhances the test with clues and then runs the evaluation. */ + def runWithClues[A](f: EvalResult => A): A = { + withTestClues(clues*)(f(run())) + } } trait Impl extends AutoCloseable with IntegrationTesterBase { From de7fa17d326f37c1774c8f74a36ad3cf999c1d5f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:44:57 +0000 Subject: [PATCH 16/17] [autofix.ci] apply automated fixes --- .../src/mill/javalib/SemanticDbJavaModule.scala | 4 +++- .../test/src/mill/scalalib/HelloWorldTests.scala | 10 ++++++---- testkit/src/mill/testkit/IntegrationTester.scala | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 3c518739247b..d4911bd2c705 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -192,7 +192,9 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi ) } catch { case t - if t.getMessage.contains(s"$semanticDbScalaArtifactOrganization:${semanticDbScalaArtifactName("")}") => + if t.getMessage.contains( + s"$semanticDbScalaArtifactOrganization:${semanticDbScalaArtifactName("")}" + ) => Task.log.error( s"""!!! It seems that your SemanticDB version is not compatible with your Scala version !!! | diff --git a/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala b/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala index 70d476821585..27686a993471 100644 --- a/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala @@ -53,10 +53,12 @@ object HelloWorldTests extends TestSuite { ) trait HelloWorldCross extends CrossScalaModule { - override def semanticDbVersion = Task { scalaVersion() match { - case `scala2123Version` => "2.1.2" - case _ => super.semanticDbVersion() - } } + override def semanticDbVersion = Task { + scalaVersion() match { + case `scala2123Version` => "2.1.2" + case _ => super.semanticDbVersion() + } + } } lazy val millDiscover = Discover[this.type] diff --git a/testkit/src/mill/testkit/IntegrationTester.scala b/testkit/src/mill/testkit/IntegrationTester.scala index f38db37d8906..7e9a4fd3043d 100644 --- a/testkit/src/mill/testkit/IntegrationTester.scala +++ b/testkit/src/mill/testkit/IntegrationTester.scala @@ -77,7 +77,7 @@ object IntegrationTester { asTestValue(propagateEnv), asTestValue(shutdownGracePeriod) ) - + /** Enhances the test with clues and then runs the evaluation. */ def runWithClues[A](f: EvalResult => A): A = { withTestClues(clues*)(f(run())) From 08e281970d31df50cc29f999d18db5e6bf0364d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C5=ABras=20=C5=A0lajus?= Date: Fri, 17 Oct 2025 09:56:27 +0300 Subject: [PATCH 17/17] Refactor to dynamically construct a different task graph based on a bspClientsNeedSemanticDb runtime value --- .../mill/javalib/SemanticDbJavaModule.scala | 145 +++++++++-------- .../src/mill/scalalib/ScalaModule.scala | 154 +++++++++--------- 2 files changed, 152 insertions(+), 147 deletions(-) diff --git a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala index 3c518739247b..4e13565942f5 100644 --- a/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala +++ b/libs/javalib/src/mill/javalib/SemanticDbJavaModule.scala @@ -1,8 +1,8 @@ package mill.javalib +import mill.api.* import mill.api.daemon.internal.SemanticDbJavaModuleApi import mill.api.daemon.internal.bsp.BspBuildTarget -import mill.api.* import mill.constants.CodeGenConstants import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileJava} import mill.javalib.api.{CompilationResult, JvmWorkerUtil} @@ -45,61 +45,63 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi */ def compile: Task.Simple[mill.javalib.api.CompilationResult] - private[mill] def compileInternal = Task.Anon { (compileSemanticDb: Boolean) => - // Prepare an empty `compileGeneratedSources` folder for java annotation processors - // to write generated sources into, that can then be picked up by IDEs like IntelliJ - val compileGenSources = compileGeneratedSources() - mill.api.BuildCtx.withFilesystemCheckerDisabled { - os.remove.all(compileGenSources) - os.makeDir.all(compileGenSources) - } - - val jOpts = JavaCompilerOptions { - val opts = - Seq("-s", compileGeneratedSources().toString) ++ javacOptions() ++ mandatoryJavacOptions() - - if (compileSemanticDb) opts ++ SemanticDbJavaModule.javacOptionsTask(semanticDbJavaVersion()) - else opts - } - - val sources = allSourceFiles().map(_.path) + private[mill] def compileInternal(compileSemanticDb: Boolean) = { + val (semanticDbJavacOptionsTask, semanticDbJavaPluginMvnDepsTask) = + if (compileSemanticDb) ( + Task.Anon { SemanticDbJavaModule.javacOptionsTask(semanticDbJavaVersion()) }, + resolvedSemanticDbJavaPluginMvnDeps + ) + else (Task.Anon(Seq.empty), Task.Anon(Seq.empty)) - val compileClasspathSemanticDbJavaPlugin = - if (compileSemanticDb) resolvedSemanticDbJavaPluginMvnDeps() else Seq.empty + Task.Anon { + // Prepare an empty `compileGeneratedSources` folder for java annotation processors + // to write generated sources into, that can then be picked up by IDEs like IntelliJ + val compileGenSources = compileGeneratedSources() + mill.api.BuildCtx.withFilesystemCheckerDisabled { + os.remove.all(compileGenSources) + os.makeDir.all(compileGenSources) + } - val compileJavaOp = ZincCompileJava( - compileTo = Task.dest, - upstreamCompileOutput = upstreamCompileOutput(), - sources = sources, - compileClasspath = - (compileClasspath() ++ compileClasspathSemanticDbJavaPlugin).map(_.path), - javacOptions = jOpts.compiler, - incrementalCompilation = zincIncrementalCompilation() - ) + val jOpts = JavaCompilerOptions(Seq( + "-s", + compileGeneratedSources().toString + ) ++ javacOptions() ++ mandatoryJavacOptions() ++ semanticDbJavacOptionsTask()) - Task.log.debug(s"compiling to: ${compileJavaOp.compileTo}") - Task.log.debug(s"semantic db enabled: $compileSemanticDb") - Task.log.debug(s"effective javac options: ${jOpts.compiler}") - Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") - - val compileJavaResult = jvmWorker() - .internalWorker() - .compileJava( - compileJavaOp, - javaHome = javaHome().map(_.path), - javaRuntimeOptions = jOpts.runtime, - reporter = Task.reporter.apply(hashCode), - reportCachedProblems = zincReportCachedProblems() - ) + val sources = allSourceFiles().map(_.path) - compileJavaResult.map { compilationResult => - if (compileSemanticDb) SemanticDbJavaModule.enhanceCompilationResultWithSemanticDb( - compileTo = compileJavaOp.compileTo, + val compileJavaOp = ZincCompileJava( + compileTo = Task.dest, + upstreamCompileOutput = upstreamCompileOutput(), sources = sources, - workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), - compilationResult = compilationResult + compileClasspath = (compileClasspath() ++ semanticDbJavaPluginMvnDepsTask()).map(_.path), + javacOptions = jOpts.compiler, + incrementalCompilation = zincIncrementalCompilation() ) - else compilationResult + + Task.log.debug(s"compiling to: ${compileJavaOp.compileTo}") + Task.log.debug(s"semantic db enabled: $compileSemanticDb") + Task.log.debug(s"effective javac options: ${jOpts.compiler}") + Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") + + val compileJavaResult = jvmWorker() + .internalWorker() + .compileJava( + compileJavaOp, + javaHome = javaHome().map(_.path), + javaRuntimeOptions = jOpts.runtime, + reporter = Task.reporter.apply(hashCode), + reportCachedProblems = zincReportCachedProblems() + ) + + compileJavaResult.map { compilationResult => + if (compileSemanticDb) SemanticDbJavaModule.enhanceCompilationResultWithSemanticDb( + compileTo = compileJavaOp.compileTo, + sources = sources, + workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), + compilationResult = compilationResult + ) + else compilationResult + } } } @@ -127,7 +129,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi Version.chooseNewest(requested, builtin)(using Version.IgnoreQualifierOrdering) } - def semanticDbJavaVersion: T[String] = Task.Input { + def semanticDbJavaVersion: Task.Simple[String] = Task.Input { val builtin = SemanticDbJavaModuleApi.buildTimeJavaSemanticDbVersion val requested = Task.env.getOrElse[String]( "JAVASEMANTICDB_VERSION", @@ -184,7 +186,7 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi /** * Scalac options to activate the compiler plugins. */ - protected def semanticDbEnablePluginScalacOptions: T[Seq[String]] = Task { + protected def semanticDbEnablePluginScalacOptions: Task.Simple[Seq[String]] = Task { val resolvedJars = try { defaultResolver().classpath( @@ -192,7 +194,9 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi ) } catch { case t - if t.getMessage.contains(s"$semanticDbScalaArtifactOrganization:${semanticDbScalaArtifactName("")}") => + if t.getMessage.contains( + s"$semanticDbScalaArtifactOrganization:${semanticDbScalaArtifactName("")}" + ) => Task.log.error( s"""!!! It seems that your SemanticDB version is not compatible with your Scala version !!! | @@ -214,11 +218,11 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi resolvedJars.iterator.map(jar => s"-Xplugin:${jar.path}").toSeq } - protected def semanticDbPluginClasspath: T[Seq[PathRef]] = Task { + protected def semanticDbPluginClasspath: Task.Simple[Seq[PathRef]] = Task { defaultResolver().classpath(semanticDbPluginMvnDeps()) } - protected def resolvedSemanticDbJavaPluginMvnDeps: T[Seq[PathRef]] = Task { + protected def resolvedSemanticDbJavaPluginMvnDeps: Task.Simple[Seq[PathRef]] = Task { defaultResolver().classpath(semanticDbJavaPluginMvnDeps()) } @@ -233,8 +237,10 @@ trait SemanticDbJavaModule extends CoursierModule with SemanticDbJavaModuleApi /** * Returns true if the semanticdb will be needed by the BSP client or any of the other Mill daemons that are using * the same `out/` directory. + * + * @note if this value changes the whole module will be reinstantiated. */ - private[mill] def bspAnyClientNeedsSemanticDb(): Boolean = { + private[mill] lazy val bspAnyClientNeedsSemanticDb: Boolean = { // Allows accessing files outside of normal task scope. BuildCtx.withFilesystemCheckerDisabled { val directory = semanticDbSessionsDirWatch @@ -322,11 +328,9 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { ) derives upickle.ReadWriter /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ - private[mill] def compile(mod: SemanticDbJavaModule): Task[mill.javalib.api.CompilationResult] = - Task.Anon { - val compileSemanticDb = mod.bspAnyClientNeedsSemanticDb() - mod.compileInternal.apply().apply(compileSemanticDb) - } + private[mill] def compile(mod: SemanticDbJavaModule): Task[mill.javalib.api.CompilationResult] = { + mod.compileInternal(mod.bspAnyClientNeedsSemanticDb) + } /** @note extracted code to be invoked from multiple places for binary compatibility reasons. */ private[mill] def semanticDbDataDetailed(mod: SemanticDbJavaModule): Task[SemanticDbData] = { @@ -336,17 +340,16 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { * to save resources. */ val task = - if (mod.bspAnyClientNeedsSemanticDb()) mod.compile.map(Result.Success(_)) - else mod.compileInternal.map(run => run( /* compileSemanticDb */ true)) + if (mod.bspAnyClientNeedsSemanticDb) mod.compile + else mod.compileInternal(compileSemanticDb = true) Task.Anon { - task().map { compilationResult => - val semanticDbData = - compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( - "SemanticDB files were not produced, this is a bug in Mill." - )) - SemanticDbData(compilationResult, semanticDbData) - } + val compilationResult = task() + val semanticDbData = + compilationResult.semanticDbFiles.getOrElse(throw IllegalStateException( + "SemanticDB files were not produced, this is a bug in Mill." + )) + SemanticDbData(compilationResult, semanticDbData) } } @@ -436,7 +439,7 @@ object SemanticDbJavaModule extends ExternalModule with CoursierModule { sources: Seq[os.Path], workerClasspath: Seq[os.Path], compilationResult: CompilationResult - ) = { + ): CompilationResult = { val semanticDbFiles = BuildCtx.withFilesystemCheckerDisabled { copySemanticdbFiles( classesDir = compilationResult.classes.path, diff --git a/libs/scalalib/src/mill/scalalib/ScalaModule.scala b/libs/scalalib/src/mill/scalalib/ScalaModule.scala index da6fbd5827de..be4d293d1182 100644 --- a/libs/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/libs/scalalib/src/mill/scalalib/ScalaModule.scala @@ -1,22 +1,19 @@ package mill package scalalib -import mill.util.JarManifest -import mill.api.{BuildCtx, DummyInputStream, ModuleRef, PathRef, Result, Task} -import mill.util.BuildInfo -import mill.util.Jvm -import mill.javalib.api.{CompilationResult, JvmWorkerUtil, Versions} import mainargs.Flag import mill.api.daemon.internal.bsp.{BspBuildTarget, BspModuleApi, ScalaBuildTarget} import mill.api.daemon.internal.{ScalaModuleApi, ScalaPlatform, internal} -import mill.javalib.dependency.versions.{ValidVersion, Version} +import mill.api.{BuildCtx, DummyInputStream, ModuleRef, PathRef, Result, Task} import mill.javalib.SemanticDbJavaModule import mill.javalib.api.internal.{JavaCompilerOptions, ZincCompileMixed, ZincScaladocJar} +import mill.javalib.api.{CompilationResult, JvmWorkerUtil, Versions} +import mill.javalib.dependency.versions.{ValidVersion, Version} +import mill.util.{BuildInfo, JarManifest, Jvm} // this import requires scala-reflect library to be on the classpath // it was duplicated to scala3-compiler, but is that too powerful to add as a dependency? import scala.reflect.internal.util.ScalaClassLoader - import scala.util.Using /** @@ -276,84 +273,89 @@ trait ScalaModule extends JavaModule with TestModule.ScalaModuleBase /** * Keep the return paths in sync with [[bspCompileClassesPath]]. */ - override private[mill] def compileInternal = Task.Anon { (compileSemanticDb: Boolean) => - val sv = scalaVersion() - if (sv == "2.12.4") Task.log.warn( - """Attention: Zinc is known to not work properly for Scala version 2.12.4. - |You may want to select another version. Upgrading to a more recent Scala version is recommended. - |For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin - ) - - val jOpts = JavaCompilerOptions { - val baseOpts = javacOptions() ++ mandatoryJavacOptions() + override private[mill] def compileInternal(compileSemanticDb: Boolean) = { + val ( + semanticDbJavacOptionsTask, + semanticDbEnablePluginScalacOptionsTask, + semanticDbJavaPluginMvnDepsTask + ) = + if (compileSemanticDb) ( + Task.Anon { SemanticDbJavaModule.javacOptionsTask(semanticDbJavaVersion()) }, + semanticDbEnablePluginScalacOptions, + resolvedSemanticDbJavaPluginMvnDeps + ) + else (Task.Anon(Seq.empty), Task.Anon(Seq.empty), Task.Anon(Seq.empty)) - if (compileSemanticDb) - baseOpts ++ SemanticDbJavaModule.javacOptionsTask(semanticDbJavaVersion()) - else baseOpts - } + Task.Anon { + val sv = scalaVersion() + if (sv == "2.12.4") Task.log.warn( + """Attention: Zinc is known to not work properly for Scala version 2.12.4. + |You may want to select another version. Upgrading to a more recent Scala version is recommended. + |For details, see: https://github.com/sbt/zinc/issues/1010""".stripMargin + ) - val scalacOptions = { - def semanticDbOptions = - if (JvmWorkerUtil.isScala3(sv)) - Seq("-Xsemanticdb", s"-sourceroot:${BuildCtx.workspaceRoot}") - else Seq("-Yrangepos", s"-P:semanticdb:sourceroot:${BuildCtx.workspaceRoot}") - - if (compileSemanticDb) { - // Filter out -Xfatal-warnings to avoid semanticdb from failing the build. - allScalacOptions().filterNot(_ == "-Xfatal-warnings") ++ - semanticDbEnablePluginScalacOptions() ++ semanticDbOptions - } else allScalacOptions() - } + val jOpts = JavaCompilerOptions( + javacOptions() ++ mandatoryJavacOptions() ++ semanticDbJavacOptionsTask() + ) - val compileClasspath = { - val baseClasspath = this.compileClasspath() + val scalacOptions = { + def semanticDbOptions = + if (JvmWorkerUtil.isScala3(sv)) + Seq("-Xsemanticdb", s"-sourceroot:${BuildCtx.workspaceRoot}") + else Seq("-Yrangepos", s"-P:semanticdb:sourceroot:${BuildCtx.workspaceRoot}") + + if (compileSemanticDb) { + // Filter out -Xfatal-warnings to avoid semanticdb from failing the build. + allScalacOptions().filterNot(_ == "-Xfatal-warnings") ++ + semanticDbEnablePluginScalacOptionsTask() ++ semanticDbOptions + } else allScalacOptions() + } - if (compileSemanticDb) baseClasspath ++ resolvedSemanticDbJavaPluginMvnDeps() - else baseClasspath - } + val compileClasspath = this.compileClasspath() ++ semanticDbJavaPluginMvnDepsTask() - val compileTo = Task.dest - - Task.log.debug(s"compiling to: $compileTo") - Task.log.debug(s"semantic db enabled: $compileSemanticDb") - Task.log.debug(s"effective scalac options: $scalacOptions") - Task.log.debug(s"effective javac options: ${jOpts.compiler}") - Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") - - val sources = allSourceFiles().map(_.path) - val compileMixedOp = ZincCompileMixed( - compileTo = compileTo, - upstreamCompileOutput = upstreamCompileOutput(), - sources = sources, - compileClasspath = compileClasspath.map(_.path), - javacOptions = jOpts.compiler, - scalaVersion = sv, - scalaOrganization = scalaOrganization(), - scalacOptions = allScalacOptions(), - compilerClasspath = scalaCompilerClasspath(), - scalacPluginClasspath = scalacPluginClasspath(), - incrementalCompilation = zincIncrementalCompilation(), - auxiliaryClassFileExtensions = zincAuxiliaryClassFileExtensions() - ) + val compileTo = Task.dest - val compileMixedResult = jvmWorker() - .internalWorker() - .compileMixed( - compileMixedOp, - javaHome = javaHome().map(_.path), - javaRuntimeOptions = jOpts.runtime, - reporter = Task.reporter.apply(hashCode), - reportCachedProblems = zincReportCachedProblems() - ) + Task.log.debug(s"compiling to: $compileTo") + Task.log.debug(s"semantic db enabled: $compileSemanticDb") + Task.log.debug(s"effective scalac options: $scalacOptions") + Task.log.debug(s"effective javac options: ${jOpts.compiler}") + Task.log.debug(s"effective java runtime options: ${jOpts.runtime}") - compileMixedResult.map { compilationResult => - if (compileSemanticDb) SemanticDbJavaModule.enhanceCompilationResultWithSemanticDb( - compileTo = compileMixedOp.compileTo, + val sources = allSourceFiles().map(_.path) + val compileMixedOp = ZincCompileMixed( + compileTo = compileTo, + upstreamCompileOutput = upstreamCompileOutput(), sources = sources, - workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), - compilationResult = compilationResult + compileClasspath = compileClasspath.map(_.path), + javacOptions = jOpts.compiler, + scalaVersion = sv, + scalaOrganization = scalaOrganization(), + scalacOptions = scalacOptions, + compilerClasspath = scalaCompilerClasspath(), + scalacPluginClasspath = scalacPluginClasspath(), + incrementalCompilation = zincIncrementalCompilation(), + auxiliaryClassFileExtensions = zincAuxiliaryClassFileExtensions() ) - else compilationResult + + val compileMixedResult = jvmWorker() + .internalWorker() + .compileMixed( + compileMixedOp, + javaHome = javaHome().map(_.path), + javaRuntimeOptions = jOpts.runtime, + reporter = Task.reporter.apply(hashCode), + reportCachedProblems = zincReportCachedProblems() + ) + + compileMixedResult.map { compilationResult => + if (compileSemanticDb) SemanticDbJavaModule.enhanceCompilationResultWithSemanticDb( + compileTo = compileMixedOp.compileTo, + sources = sources, + workerClasspath = SemanticDbJavaModule.workerClasspath().map(_.path), + compilationResult = compilationResult + ) + else compilationResult + } } }