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

xsbt.CallbackGlobal.scala Maven / Gradle / Ivy

The newest version!
/*
 * Zinc - The incremental compiler for Scala.
 * Copyright Scala Center, Lightbend, and Mark Harrah
 *
 * Licensed under Apache License 2.0
 * SPDX-License-Identifier: Apache-2.0
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package xsbt

import xsbti.{ AnalysisCallback, AnalysisCallback3, Severity }
import xsbti.compile._

import scala.tools.nsc._
import io.AbstractFile
import java.nio.file.{ Files, Path }

import scala.reflect.io.PlainFile
import scala.reflect.ReflectAccess._
import scala.reflect.internal.util.BatchSourceFile

/** Defines the interface of the incremental compiler hiding implementation details. */
sealed abstract class CallbackGlobal(
    settings: Settings,
    reporter: reporters.Reporter,
    output: Output
) extends Global(settings, reporter) {

  def callback: AnalysisCallback
  def findAssociatedFile(name: String): Option[(AbstractFile, Boolean)]

  def fullName(
      symbol: Symbol,
      separator: Char,
      suffix: CharSequence,
      includePackageObjectClassNames: Boolean
  ): String

  lazy val outputDirs: Iterable[Path] = {
    output match {
      case single: SingleOutput => List(single.getOutputDirectoryAsPath)
      // Use Stream instead of List because Analyzer maps intensively over the directories
      case multi: MultipleOutput =>
        multi.getOutputGroups.toIterator.map(_.getOutputDirectoryAsPath).toSeq
    }
  }

  lazy val JarUtils = new JarUtils(outputDirs)

  /**
   * Defines the sbt phase in which the dependency analysis is performed.
   * The reason why this is exposed in the callback global is because it's used
   * in [[xsbt.LocalToNonLocalClass]] to make sure the we don't resolve local
   * classes before we reach this phase.
   */
  private[xsbt] val sbtDependency: SubComponent

  /**
   * A map from local classes to non-local class that contains it.
   *
   * This map is used by both Dependency and Analyzer phase so it has to be
   * exposed here. The Analyzer phase uses the cached lookups performed by
   * the Dependency phase. By the time Analyzer phase is run (close to backend
   * phases), original owner chains are lost so Analyzer phase relies on
   * information saved before.
   *
   * The LocalToNonLocalClass duplicates the tracking that Scala compiler does
   * internally for backed purposes (generation of EnclosingClass attributes) but
   * that internal mapping doesn't have a stable interface we could rely on.
   */
  private[xsbt] val localToNonLocalClass = new LocalToNonLocalClass[this.type](this)
}

/** Defines the implementation of Zinc with all its corresponding phases. */
sealed class ZincCompiler(settings: Settings, dreporter: DelegatingReporter, output: Output)
    extends CallbackGlobal(settings, dreporter, output)
    with ZincGlobalCompat {

  override def getSourceFile(f: AbstractFile): BatchSourceFile = {
    val file = (f, callback) match {
      case (plainFile: PlainFile, callback3: AnalysisCallback3) =>
        AbstractZincFile(callback3.toVirtualFile(plainFile.file.toPath))
      case _ => f
    }
    super.getSourceFile(file)
  }

  final class ZincRun(compileProgress: CompileProgress) extends Run {
    override def informUnitStarting(phase: Phase, unit: CompilationUnit): Unit = {
      compileProgress.startUnit(phase.name, unit.source.path)
    }

    override def progress(current: Int, total: Int): Unit = {
      if (!compileProgress.advance(current, total, phase.name, phase.next.name)) cancel()
      else ()
    }
  }

  object dummy // temporary fix for #4426

  /** Phase that analyzes the generated class files and maps them to sources. */
  object sbtAnalyzer extends {
        val global: ZincCompiler.this.type = ZincCompiler.this
        val phaseName = Analyzer.name
        val runsAfter = List("jvm")
        override val runsBefore = List("terminal")
        val runsRightAfter = None
      } with SubComponent {
    val analyzer = new Analyzer(global)
    def newPhase(prev: Phase) = analyzer.newPhase(prev)
    def name = phaseName

    def description = "analyze the generated class files and map them to sources"
  }

  /** Phase that extracts dependency information */
  object sbtDependency extends {
        val global: ZincCompiler.this.type = ZincCompiler.this
        val phaseName = Dependency.name
        val runsAfter = List(API.name)
        override val runsBefore = List("refchecks")
        // Keep API and dependency close to each other -- we may want to merge them in the future.
        override val runsRightAfter = Some(API.name)
      } with SubComponent {
    val dependency = new Dependency(global)
    def newPhase(prev: Phase) = dependency.newPhase(prev)
    def name = phaseName

    def description = "extract dependency information"
  }

  /**
   * Phase that walks the trees and constructs a representation of the public API.
   *
   * @note It extracts the API information after picklers to see the same symbol information
   *       irrespective of whether we typecheck from source or unpickle previously compiled classes.
   */
  object apiExtractor extends {
        val global: ZincCompiler.this.type = ZincCompiler.this
        val phaseName = API.name
        val runsAfter = List("typer")
        override val runsBefore = List("erasure")
        // TODO: Consider migrating to "uncurry" for `runsBefore`.
        // TODO: Consider removing the system property to modify which phase is used for API extraction.
        val runsRightAfter = Option(System.getProperty("sbt.api.phase")) orElse Some("pickler")
      } with SubComponent {
    val api = new API(global)
    def newPhase(prev: Phase) = api.newPhase(prev)
    def name = phaseName

    def description = "construct a representation of the public API"
  }

  override lazy val phaseDescriptors = {
    phasesSet += sbtAnalyzer
    phasesDescMap(sbtAnalyzer) = sbtAnalyzer.description
    if (callback.enabled()) {
      phasesSet += sbtDependency
      phasesDescMap(sbtDependency) = sbtDependency.description
      phasesSet += apiExtractor
      phasesDescMap(apiExtractor) = apiExtractor.description
    }
    this.computePhaseDescriptors
  }

  private final val fqnsToAssociatedFiles = perRunCaches.newMap[String, (AbstractFile, Boolean)]()

  /**
   * Returns the associated file of a fully qualified name and whether it's on the classpath.
   * Note that the abstract file returned must exist.
   */
  def findAssociatedFile(fqn: String): Option[(AbstractFile, Boolean)] = {
    def findOnPreviousCompilationProducts(name: String): Option[AbstractFile] = {
      // This class file path is relative to the output jar/directory and computed from class name
      val classFilePath = name.replace('.', '/') + ".class"

      JarUtils.outputJar match {
        case Some(outputJar) =>
          if (!callback.classesInOutputJar().contains(classFilePath)) None
          else {
            /*
             * Important implementation detail: `classInJar` has the format of `$JAR!$CLASS_REF`
             * which is, of course, a path to a file that does not exist. This file path is
             * interpreted especially by Zinc to decompose the format under straight-to-jar
             * compilation. For this strategy to work, `PlainFile` must **not** check that
             * this file does exist or not because, if it does, it will return `null` in
             * `processExternalDependency` and the dependency will not be correctly registered.
             * If scalac breaks this contract (the check for existence is done when creating
             * a normal reflect file but not a plain file), Zinc will not work correctly.
             */
            Some(new PlainFile(JarUtils.classNameInJar(outputJar, classFilePath)))
          }

        case None => // The compiler outputs class files in a classes directory (the default)
          // This lookup could be improved if a hint where to look is given.
          if (classFilePath.contains("<")) None
          else
            outputDirs
              .map(_.resolve(classFilePath))
              .find(Files.exists(_))
              .map(ZincCompat.plainNioFile(_))
      }
    }

    def findOnClassPath(name: String): Option[AbstractFile] =
      classPath.findClass(name).flatMap(_.binary.asInstanceOf[Option[AbstractFile]])

    fqnsToAssociatedFiles.get(fqn).orElse {
      val newResult = findOnPreviousCompilationProducts(fqn)
        .map(f => (f, true))
        .orElse(findOnClassPath(fqn).map(f => (f, false)))

      newResult.foreach(res => fqnsToAssociatedFiles.put(fqn, res))
      newResult
    }
  }

  /**
   * Replicate the behaviour of `fullName` with a few changes to the code to produce
   * correct file-system compatible full names for non-local classes. It mimics the
   * paths of the class files produced by genbcode.
   *
   * Changes compared to the normal version in the compiler:
   *
   * 1. It will use the encoded name instead of the normal n2. It will not skip the name of the package object class (required for the class file path).
   *
   * Note that using `javaBinaryName` is not useful for these symbols because we
   * need the encoded names. Zinc keeps track of encoded names in both the binary
   * names and the Zinc names.
   *
   * @param symbol The symbol for which we extract the full name.
   * @param separator The separator that we will apply between every name.
   * @param suffix The suffix to add at the end (in case it's a module).
   * @param includePackageObjectClassNames Include package object class names or not.
   * @return The full name.
   */
  override def fullName(
      symbol: Symbol,
      separator: Char,
      suffix: CharSequence,
      includePackageObjectClassNames: Boolean
  ): String = {
    var b: java.lang.StringBuffer = null
    def loop(size: Int, sym: Symbol): Unit = {
      val symName = flattenedName(this)(sym)
      // Use of encoded to produce correct paths for names that have symbols
      val encodedName = symName.encode
      val nSize = encodedName.length - (if (symName.endsWith(nme.LOCAL_SUFFIX_STRING)) 1 else 0)
      val owner = flattenedOwner(this)(sym)
      if (sym.isRoot || sym.isRootPackage || sym == NoSymbol || owner.isEffectiveRoot) {
        val capacity = size + nSize
        b = new java.lang.StringBuffer(capacity)
        b.append(chrs, encodedName.start, nSize)
      } else {
        val next = if (owner.isPackageObjectClass) owner else owner.skipPackageObject
        loop(size + nSize + 1, next)
        if (owner.isPackageObjectClass) b.append(nme.MODULE_SUFFIX_STRING) else b.append(separator)
        b.append(chrs, encodedName.start, nSize)
      }
      ()
    }
    loop(suffix.length(), symbol)
    b.append(suffix)
    b.toString
  }

  private[this] var callback0: AnalysisCallback = null

  /** Returns the active analysis callback, set by [[set]] and cleared by [[clear]]. */
  def callback: AnalysisCallback = callback0

  final def set(callback: AnalysisCallback, dreporter: DelegatingReporter): Unit = {
    this.callback0 = callback
    reporter = dreporter
  }

  final def clear(): Unit = {
    callback0 = null
    superDropRun()
    reporter = null
    this match {
      case c: java.io.Closeable => c.close()
      case _                    =>
    }
  }

  // Scala 2.10.x and later
  private[xsbt] def logUnreportedWarnings(seq: Seq[(String, List[(Position, String)])]): Unit = {
    for ((what, warnings) <- seq; (pos, msg) <- warnings)
      yield callback.problem(what, DelegatingReporter.convert(pos), msg, Severity.Warn, false)
    ()
  }
}

import scala.reflect.internal.Positions
final class ZincCompilerRangePos(settings: Settings, dreporter: DelegatingReporter, output: Output)
    extends ZincCompiler(settings, dreporter, output)
    with Positions




© 2015 - 2025 Weber Informatics LLC | Privacy Policy