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

org.pantsbuild.zinc.compiler.Settings.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2012 Typesafe, Inc. 
 */

package org.pantsbuild.zinc.compiler

import java.io.File
import sbt.internal.util.{ ConsoleLogger, ConsoleOut }
import java.nio.file.{Files, Path}
import java.lang.{ Boolean => JBoolean }
import java.util.function.{ Function => JFunction }
import java.util.{ List => JList, logging => jlogging }

import scala.collection.JavaConverters._
import scala.compat.java8.OptionConverters._
import scala.util.matching.Regex

import sbt.io.Path._
import sbt.io.syntax._
import sbt.util.{Level, Logger}
import xsbti.compile.{
  ClassFileManagerType,
  CompileOrder,
  IncOptionsUtil,
  TransactionalManagerType
}
import xsbti.compile.{IncOptions => ZincIncOptions}

import org.pantsbuild.zinc.analysis.AnalysisOptions
import org.pantsbuild.zinc.options.OptionSet
import org.pantsbuild.zinc.util.Util

/**
 * All parsed command-line options.
 */
case class Settings(
  help: Boolean                     = false,
  version: Boolean                  = false,
  consoleLog: ConsoleOptions        = ConsoleOptions(),
  _sources: Seq[File]               = Seq.empty,
  classpath: Seq[File]              = Seq.empty,
  _classesDirectory: Option[File]   = None,
  outputJar: Option[File]           = None,
  scala: ScalaLocation              = ScalaLocation(),
  scalacOptions: Seq[String]        = Seq.empty,
  javaHome: Option[File]            = None,
  javaOnly: Boolean                 = false,
  javacOptions: Seq[String]         = Seq.empty,
  compileOrder: CompileOrder        = CompileOrder.Mixed,
  _incOptions: IncOptions           = IncOptions(),
  analysis: AnalysisOptions         = AnalysisOptions(),
  creationTime: Long                = 0,
  compiledBridgeJar: Option[File]= None
) {
  import Settings._

  lazy val sources: Seq[File] = _sources map normalise

  lazy val classesDirectory: File =
    normalise(_classesDirectory.getOrElse(defaultClassesDirectory()))

  lazy val incOptions: IncOptions = {
    _incOptions.copy(
      apiDumpDirectory = _incOptions.apiDumpDirectory map normalise,
      backup = {
        if (_incOptions.transactional)
          Some(normalise(_incOptions.backup.getOrElse(defaultBackupLocation(classesDirectory))))
        else
          None
      }
    )
  }

  def withAbsolutePaths(relativeTo: File): Settings = {
    def normaliseSeq(seq: Seq[File]): Seq[File] = Util.normaliseSeq(Some(relativeTo))(seq)
    def normaliseOpt(opt: Option[File]): Option[File] = Util.normaliseOpt(Some(relativeTo))(opt)

    // It's a shame that this is manually listing the args which are files, but this doesn't feel
    // high-value enough to fold into the full options parsing
    // (which we may delete at some point anyway)...
    this.copy(
      _sources = normaliseSeq(_sources),
      classpath = normaliseSeq(classpath),
      _classesDirectory = normaliseOpt(_classesDirectory),
      outputJar = normaliseOpt(outputJar),
      scala = scala.withAbsolutePaths(relativeTo),
      javaHome = normaliseOpt(javaHome),
      _incOptions = _incOptions.withAbsolutePaths(relativeTo),
      analysis = analysis.withAbsolutePaths(relativeTo),
      compiledBridgeJar = normaliseOpt(compiledBridgeJar)
    )
  }
}

/**
 * Console logging options.
 */
case class ConsoleOptions(
  logLevel: Level.Value      = Level.Info,
  color: Boolean             = true,
  fileFilters: Seq[Regex]    = Seq.empty,
  msgFilters: Seq[Regex]     = Seq.empty
) {
  def javaLogLevel: jlogging.Level = logLevel match {
    case Level.Info =>
      jlogging.Level.INFO
    case Level.Warn =>
      jlogging.Level.WARNING
    case Level.Error =>
      jlogging.Level.SEVERE
    case Level.Debug =>
      jlogging.Level.FINE
    case x =>
      sys.error(s"Unsupported log level: $x")
  }

  /**
   * Because filtering Path objects requires first converting to a String, we compose
   * the regexes into one predicate.
   */
  def filePredicates: Seq[JFunction[Path, JBoolean]] =
    Seq(
      new JFunction[Path, JBoolean] {
        def apply(path: Path) = {
          val pathStr = path.toString
          fileFilters.exists(_.findFirstIn(pathStr).isDefined)
        }
      }
    )

  def msgPredicates: Seq[JFunction[String, JBoolean]] =
    msgFilters.map { regex =>
      new JFunction[String, JBoolean] {
        def apply(msg: String) = regex.findFirstIn(msg).isDefined
      }
    }
}

/**
 * Alternative ways to locate the scala jars.
 */
case class ScalaLocation(
  home: Option[File]     = None,
  path: Seq[File]        = Seq.empty,
  compiler: Option[File] = None,
  library: Option[File]  = None,
  extra: Seq[File]       = Seq.empty
) {
  def withAbsolutePaths(relativeTo: File): ScalaLocation = {
    def normaliseSeq(seq: Seq[File]): Seq[File] = Util.normaliseSeq(Some(relativeTo))(seq)
    def normaliseOpt(opt: Option[File]): Option[File] = Util.normaliseOpt(Some(relativeTo))(opt)

    // It's a shame that this is manually listing the args which are files, but this doesn't feel
    // high-value enough to fold into the full options parsing
    // (which we may delete at some point anyway)...
    this.copy(
      home = normaliseOpt(home),
      path = normaliseSeq(path),
      compiler = normaliseOpt(compiler),
      library = normaliseOpt(library),
      extra = normaliseSeq(extra)
    )
  }
}

object ScalaLocation {
  /**
   * Java API for creating ScalaLocation.
   */
  def create(
    home: File,
    path: JList[File],
    compiler: File,
    library: File,
    extra: JList[File]): ScalaLocation =
  ScalaLocation(
    Option(home),
    path.asScala,
    Option(compiler),
    Option(library),
    extra.asScala
  )

  /**
   * Java API for creating ScalaLocation with scala home.
   */
  def fromHome(home: File) = ScalaLocation(home = Option(home))

  /**
   * Java API for creating ScalaLocation with scala path.
   */
  def fromPath(path: JList[File]) = ScalaLocation(path = path.asScala)
}

/**
 * Wrapper around incremental compiler options.
 */
case class IncOptions(
  transitiveStep: Int            = ZincIncOptions.defaultTransitiveStep,
  recompileAllFraction: Double   = ZincIncOptions.defaultRecompileAllFraction,
  relationsDebug: Boolean        = ZincIncOptions.defaultRelationsDebug,
  apiDebug: Boolean              = ZincIncOptions.defaultApiDebug,
  apiDiffContextSize: Int        = ZincIncOptions.defaultApiDiffContextSize,
  apiDumpDirectory: Option[File] = ZincIncOptions.defaultApiDumpDirectory.asScala,
  transactional: Boolean         = false,
  useZincFileManager: Boolean    = true,
  backup: Option[File]           = None
) {
  def options(log: Logger): ZincIncOptions =
    ZincIncOptions.create()
      .withTransitiveStep(transitiveStep)
      .withRecompileAllFraction(recompileAllFraction)
      .withRelationsDebug(relationsDebug)
      .withApiDebug(apiDebug)
      .withApiDiffContextSize(apiDiffContextSize)
      .withApiDumpDirectory(apiDumpDirectory.asJava)
      .withClassfileManagerType(classfileManager(log).asJava)
      .withUseCustomizedFileManager(useZincFileManager)

  def classfileManager(log: Logger): Option[ClassFileManagerType] =
    if (transactional && backup.isDefined)
      Some(TransactionalManagerType.create(backup.get, log))
    else
      None

  def withAbsolutePaths(relativeTo: File): IncOptions = {
    // It's a shame that this is manually listing the args which are files, but this doesn't feel
    // high-value enough to fold into the full options parsing
    // (which we may delete at some point anyway)...
    this.copy(
      apiDumpDirectory = Util.normaliseOpt(Some(relativeTo))(apiDumpDirectory),
      backup = Util.normaliseOpt(Some(relativeTo))(backup)
    )
  }
}

object Settings extends OptionSet[Settings] {
  val DestinationOpt = "-d"
  val JarDestinationOpt = "-jar"

  override def empty = Settings()

  override def applyResidual(t: Settings, residualArgs: Seq[String]) =
    t.copy(_sources = residualArgs map (new File(_)))

  override val options = Seq(
    header("Output options:"),
    boolean(  ("-help", "-h"),                 "Print this usage message",                   (s: Settings) => s.copy(help = true)),
    boolean(   "-version",                     "Print version",                              (s: Settings) => s.copy(version = true)),

    header("Logging Options:"),
    boolean(   "-debug",                       "Set log level for stdout to debug",
      (s: Settings) => s.copy(consoleLog = s.consoleLog.copy(logLevel = Level.Debug))),
    string(    "-log-level", "level",          "Set log level for stdout (debug|info|warn|error)",
      (s: Settings, l: String) => s.copy(consoleLog = s.consoleLog.copy(logLevel = Level.withName(l)))),
    boolean(   "-no-color",                    "No color in logging to stdout",
      (s: Settings) => s.copy(consoleLog = s.consoleLog.copy(color = false))),
    string(    "-msg-filter", "regex",         "Filter warning messages matching the given regex",
      (s: Settings, re: String) => s.copy(consoleLog = s.consoleLog.copy(msgFilters = s.consoleLog.msgFilters :+ re.r))),
    string(    "-file-filter", "regex",        "Filter warning messages from filenames matching the given regex",
      (s: Settings, re: String) => s.copy(consoleLog = s.consoleLog.copy(fileFilters = s.consoleLog.fileFilters :+ re.r))),

    header("Compile options:"),
    path(     ("-classpath", "-cp"), "path",   "Specify the classpath",                      (s: Settings, cp: Seq[File]) => s.copy(classpath = cp)),
    file(     DestinationOpt, "directory",     "Destination for compiled classes",           (s: Settings, f: File) => s.copy(_classesDirectory = Some(f))),
    file(     JarDestinationOpt, "directory",     "Jar destination for compiled classes",           (s: Settings, f: File) => s.copy(outputJar = Some(f))),
    long("-jar-creation-time", "n",        "Creation timestamp for compiled jars, default is current time", (s: Settings, l: Long) => s.copy(creationTime = l)),

    header("Scala options:"),
    file(      "-scala-home", "directory",     "Scala home directory (for locating jars)",   (s: Settings, f: File) => s.copy(scala = s.scala.copy(home = Some(f)))),
    path(      "-scala-path", "path",          "Specify all Scala jars directly",            (s: Settings, sp: Seq[File]) => s.copy(scala = s.scala.copy(path = sp))),
    file(      "-scala-compiler", "file",      "Specify Scala compiler jar directly" ,       (s: Settings, f: File) => s.copy(scala = s.scala.copy(compiler = Some(f)))),
    file(      "-scala-library", "file",       "Specify Scala library jar directly" ,        (s: Settings, f: File) => s.copy(scala = s.scala.copy(library = Some(f)))),
    path(      "-scala-extra", "path",         "Specify extra Scala jars directly",          (s: Settings, e: Seq[File]) => s.copy(scala = s.scala.copy(extra = e))),
    prefix(    "-S", "",        "Pass option to scalac",                      (s: Settings, o: String) => s.copy(scalacOptions = s.scalacOptions :+ o)),

    header("Java options:"),
    file(      "-java-home", "directory",      "Select javac home directory (and fork)",     (s: Settings, f: File) => s.copy(javaHome = Some(f))),
    string(    "-compile-order", "order",      "Compile order for Scala and Java sources",   (s: Settings, o: String) => s.copy(compileOrder = compileOrder(o))),
    boolean(   "-java-only",                   "Don't add scala library to classpath",       (s: Settings) => s.copy(javaOnly = true)),
    prefix(    "-C", "",         "Pass option to javac",                       (s: Settings, o: String) => s.copy(javacOptions = s.javacOptions :+ o)),

    header("sbt options:"),
    file("-compiled-bridge-jar", "file", "Path to pre-compiled compiler interface", (s: Settings, f: File) => s.copy(compiledBridgeJar = Some(f))),

    header("Incremental compiler options:"),
    int(       "-transitive-step", "n",        "Steps before transitive closure",            (s: Settings, i: Int) => s.copy(_incOptions = s._incOptions.copy(transitiveStep = i))),
    fraction(  "-recompile-all-fraction", "x", "Limit before recompiling all sources",       (s: Settings, d: Double) => s.copy(_incOptions = s._incOptions.copy(recompileAllFraction = d))),
    boolean(   "-debug-relations",             "Enable debug logging of analysis relations", (s: Settings) => s.copy(_incOptions = s._incOptions.copy(relationsDebug = true))),
    boolean(   "-debug-api",                   "Enable analysis API debugging",              (s: Settings) => s.copy(_incOptions = s._incOptions.copy(apiDebug = true))),
    file(      "-api-dump", "directory",       "Destination for analysis API dump",          (s: Settings, f: File) => s.copy(_incOptions = s._incOptions.copy(apiDumpDirectory = Some(f)))),
    int(       "-api-diff-context-size", "n",  "Diff context size (in lines) for API debug", (s: Settings, i: Int) => s.copy(_incOptions = s._incOptions.copy(apiDiffContextSize = i))),
    boolean(   "-transactional",               "Restore previous class files on failure",    (s: Settings) => s.copy(_incOptions = s._incOptions.copy(transactional = true))),
    boolean(   "-no-zinc-file-manager",        "Disable zinc provided file manager",           (s: Settings) => s.copy(_incOptions = s._incOptions.copy(useZincFileManager = false))),
    file(      "-backup", "directory",         "Backup location (if transactional)",         (s: Settings, f: File) => s.copy(_incOptions = s._incOptions.copy(backup = Some(f)))),

    header("Analysis options:"),
    file(      "-analysis-cache", "file",      "Cache file for compile analysis",            (s: Settings, f: File) => s.copy(analysis =
      s.analysis.copy(_cache = Some(f)))),
    fileMap(   "-analysis-map",                "Upstream analysis mapping (file:file,...)",
      (s: Settings, m: Map[File, File]) => s.copy(analysis = s.analysis.copy(cacheMap = m))),
    fileMap(   "-rebase-map",                  "Source and destination paths to rebase in persisted analysis (file:file,...)",
      (s: Settings, m: Map[File, File]) => s.copy(analysis = s.analysis.copy(rebaseMap = m))),
    boolean(   "-no-clear-invalid-analysis",   "If set, zinc will fail rather than purging illegal analysis.",
      (s: Settings) => s.copy(analysis = s.analysis.copy(clearInvalid = false)))
  )

  /**
   * Create a CompileOrder value based on string input.
   */
  def compileOrder(order: String): CompileOrder = {
    order.toLowerCase match {
      case "mixed"                                       => CompileOrder.Mixed
      case "java"  | "java-then-scala" | "javathenscala" => CompileOrder.JavaThenScala
      case "scala" | "scala-then-java" | "scalathenjava" => CompileOrder.ScalaThenJava
    }
  }

  /**
   * Normalise all relative paths to absolute paths.
   */
  def normalise(f: File): File = f.getAbsoluteFile

  /**
   * By default the cache location is relative to the classes directory (for example, target/classes/../cache/classes).
   */
  def defaultCacheLocation(classesDir: File) = {
    classesDir.getParentFile / "cache" / classesDir.getName
  }

  /**
   * By default the backup location is relative to the classes directory (for example, target/classes/../backup/classes).
   */
  def defaultBackupLocation(classesDir: File): File = {
    classesDir.getParentFile / "backup" / classesDir.getName
  }

  /**
   * If a settings.classesDirectory option isnt specified, create a temporary directory for output
   * classes to be written to.
   */
  def defaultClassesDirectory(): File = {
    Files.createTempDirectory("temp-zinc-classes").toFile
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy