All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.epfl.scala.debugadapter.DebugTools.scala Maven / Gradle / Ivy

package ch.epfl.scala.debugadapter

import scala.reflect.io.File
import ch.epfl.scala.debugadapter.internal.ScalaExtension._
import ch.epfl.scala.debugadapter.internal.evaluator.ExpressionCompiler

final case class DebugTools(expressionCompilers: Map[ClassEntry, ExpressionCompiler], stepFilter: Option[ClassLoader])

object DebugTools {

  /**
   * Resolve the expression compilers and the step filter of the debuggee
   *
   * At most 2 expression compilers are resolved, one for Scala 2 and one for Scala 3
   * For both Scala 2 and Scala 3 we want to use the most recent known version:
   *   - the version of the main module
   *   - or the version of the scala-library/scala3-library in the classpath
   *   - or the latest known version of the scala-debug-adapter
   * That's because we want to be able to read the full classpath.
   * Examples:
   *  - if there are Scala 3.1 and 3.2 compiled jars in the classpath, we don't want to use the Scala 3.1 compiler
   *    because it cannot read the 3.2 jar.
   *  - similarly, only the latest Scala 2.13 version can unpickle the classes of the latest Scala 3 version
   *
   * The scalacOptions are adapted so that:
   *   - both compilers can unpickle Scala 2 AND Scala 3
   *   - both compilers does not fail on warnings
   *
   * TODO if scala3Entries is not empty we should use the Scala 3 step filter
   */
  def apply(debuggee: Debuggee, resolver: DebugToolsResolver, logger: Logger): DebugTools = {
    val scala2Entries = debuggee.managedEntries.filter(_.isScala2)
    val scala3Entries = debuggee.managedEntries.filter(_.isScala3)
    val scala2Version =
      if (debuggee.scalaVersion.isScala2) debuggee.scalaVersion
      else
        debuggee.libraries
          .find(_.artifactId == "scala-library")
          .flatMap(_.scalaVersion)
          .getOrElse(ScalaVersion.`2.13`)
    val scala3Version =
      if (debuggee.scalaVersion.isScala3) debuggee.scalaVersion
      else
        debuggee.libraries
          .find(_.artifactId.startsWith("scala3-library"))
          .map(lib => ScalaVersion(lib.version))
          .getOrElse(ScalaVersion.`3.1+`)

    def resolveCompilerClassLoader(scalaVersion: ScalaVersion): Option[ClassLoader] =
      resolver
        .resolveExpressionCompiler(scalaVersion)
        .warnFailure(logger, s"Cannot fetch expression compiler of Scala ${scalaVersion}")

    val scala3Loader = if (scala3Entries.isEmpty) None else resolveCompilerClassLoader(scala3Version)
    val scala2Loader = if (scala2Entries.isEmpty) None else resolveCompilerClassLoader(scala2Version)

    val defaultScala2Options =
      if (scala3Entries.nonEmpty) Seq("-Xsource:3", "-Ytasty-reader")
      else Seq("-Xsource:3")

    val classPath = debuggee.classPath.mkString(File.pathSeparator)

    def loadExpressionCompiler(entry: ManagedEntry): Option[(ClassEntry, ExpressionCompiler)] = {
      val optionsToAdd = if (entry.isScala2) defaultScala2Options else Seq.empty
      val scalacOptions = entry match {
        case module: Module => prepareOptions(module.scalacOptions, optionsToAdd)
        case lib: Library => optionsToAdd
      }
      for {
        classLoader <- if (entry.isScala2) scala2Loader else if (entry.isScala3) scala3Loader else None
        scalaVersion <- entry.scalaVersion
        compiler <- ExpressionCompiler(scalaVersion, scalacOptions, classPath, classLoader)
          .warnFailure(logger, s"Cannot load expression compiler of Scala ${scalaVersion}")
      } yield entry -> compiler
    }

    val allCompilers = debuggee.managedEntries.flatMap(loadExpressionCompiler).toMap
    val stepFilter =
      if (debuggee.scalaVersion.isScala3) {
        resolver
          .resolveStepFilter(debuggee.scalaVersion)
          .warnFailure(logger, s"Cannot fetch step filter of Scala ${debuggee.scalaVersion}")
      } else None

    new DebugTools(allCompilers, stepFilter)
  }

  private val optionsToRemove = Set("-Xfatal-warnings", "-Werror")

  private def prepareOptions(options: Seq[String], toAdd: Seq[String]) = {
    val withoutRemoved = options.filter(o => !optionsToRemove.contains(o))
    val withAdded = withoutRemoved ++ toAdd.filter(o => !options.contains(o))
    withAdded
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy