
sbt.internal.inc.text.TextAnalysisFormat.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zinc-persist_2.13 Show documentation
Show all versions of zinc-persist_2.13 Show documentation
Incremental compiler of Scala
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 sbt.internal.inc.text
import java.io._
import java.nio.file.{ Path, Paths }
import sbt.internal.inc._
import sbt.util.InterfaceUtil
import sbt.util.InterfaceUtil.{ jl2l, jo2o, position, problem }
import xsbti.{
Action,
DiagnosticCode,
DiagnosticRelatedInformation,
T2,
TextEdit,
UseScope,
VirtualFileRef,
WorkspaceEdit
}
import xsbti.api._
import xsbti.compile._
import xsbti.compile.analysis.{ ReadWriteMappers, SourceInfo, Stamp }
import scala.collection.Seq
// A text-based serialization format for Analysis objects.
// This code has been tuned for high performance, and therefore has non-idiomatic areas.
// Please refrain from making changes that significantly degrade read/write performance on large analysis files.
object TextAnalysisFormat extends TextAnalysisFormat(ReadWriteMappers.getEmptyMappers)
class TextAnalysisFormat(val mappers: ReadWriteMappers)
extends FormatCommons
with RelationsTextFormat {
private final val readMapper = mappers.getReadMapper
private final val writeMapper = mappers.getWriteMapper
// Some types are not required for external inspection/manipulation of the analysis file,
// and are complex to serialize as text. So we serialize them as base64-encoded sbinary-serialized blobs.
// TODO: This is a big performance hit. Figure out a more efficient way to serialize API objects?
import sbinary.DefaultProtocol._
import sbinary.Format
import xsbti.{ Position, Problem, Severity }
private implicit val compilationF: Format[Compilation] = CompilationFormat
private implicit val nameHashesFormat: Format[NameHash] = {
def read(name: String, scopeName: String, hash: Int) =
NameHash.of(name, UseScope.valueOf(scopeName), hash)
asProduct3(read)(a => (a.name(), a.scope().name(), a.hash()))
}
private implicit val companionsFomrat: Format[Companions] = CompanionsFormat
private implicit def positionFormat: Format[Position] =
asProduct13(position)(p =>
(
jo2o(p.line),
p.lineContent,
jo2o(p.offset),
jo2o(p.pointer),
jo2o(p.pointerSpace),
jo2o(p.sourcePath),
jo2o(p.sourceFile),
jo2o(p.startOffset),
jo2o(p.endOffset),
jo2o(p.startLine),
jo2o(p.startColumn),
jo2o(p.endLine),
jo2o(p.endColumn)
)
)
private implicit val severityFormat: Format[Severity] =
wrap[Severity, Byte](_.ordinal.toByte, b => Severity.values.apply(b.toInt))
private implicit val integerFormat: Format[Integer] =
wrap[Integer, Int](_.toInt, Integer.valueOf)
private implicit val analyzedClassFormat: Format[AnalyzedClass] =
AnalyzedClassFormats.analyzedClassFormat
private implicit def infoFormat: Format[SourceInfo] =
wrap[SourceInfo, (Seq[Problem], Seq[Problem], Seq[String])](
si => (si.getReportedProblems, si.getUnreportedProblems, si.getMainClasses),
{
case (a, b, c) => SourceInfos.makeInfo(a.toVector, b.toVector, c.toVector)
}
)
private implicit val pathFormat: Format[Path] =
wrap[Path, String](_.toString, s => Paths.get(s))
private implicit def fileHashFormat: Format[FileHash] =
asProduct2((file: Path, hash: Int) => FileHash.of(file, hash))(h => (h.file, h.hash))
private implicit def seqFormat[T](implicit optionFormat: Format[T]): Format[Seq[T]] =
viaSeq[Seq[T], T](x => x)
private implicit def listFormat[A](implicit optionFormat: Format[A]): Format[List[A]] =
wrap[List[A], Seq[A]](_.toSeq, _.toList)
private def t2[A1, A2](a1: A1, a2: A2): T2[A1, A2] = InterfaceUtil.t2(a1 -> a2)
private implicit def diagnosticCodeFormat: Format[DiagnosticCode] =
asProduct2(DiagnosticsUtil.diagnosticCode)(d => (d.code(), jo2o(d.explanation())))
private implicit def diagnosticRelatedInformationFormat: Format[DiagnosticRelatedInformation] =
asProduct2(DiagnosticsUtil.diagnosticRelatedInformation)(d => (d.position(), d.message()))
private implicit def problemFormat: Format[Problem] = {
implicit val ev: Format[List[DiagnosticRelatedInformation]] =
listFormat(diagnosticRelatedInformationFormat)
implicit val ev2: Format[List[Action]] =
listFormat(actionFormat)
asProduct8(problem)(p =>
(
p.category,
p.position,
p.message,
p.severity,
jo2o(p.rendered),
jo2o(p.diagnosticCode),
jl2l(p.diagnosticRelatedInformation),
jl2l(p.actions),
)
)
}
private implicit def actionFormat: Format[Action] =
asProduct3(InterfaceUtil.action)((a: Action) =>
(
a.title,
jo2o(a.description),
a.edit,
)
)
private implicit def workspaceEditFormat: Format[WorkspaceEdit] = {
implicit val ev: Format[List[TextEdit]] = listFormat(textEditFormat)
wrap[WorkspaceEdit, List[TextEdit]](
(e: WorkspaceEdit) => jl2l(e.changes),
InterfaceUtil.workspaceEdit
)
}
private implicit def textEditFormat: Format[TextEdit] =
asProduct2(InterfaceUtil.textEdit)((e: TextEdit) =>
(
e.position,
e.newText,
)
)
// Companions portion of the API info is written in a separate entry later.
def write(out: Writer, analysis: CompileAnalysis, setup: MiniSetup): Unit = {
val analysis0 = analysis match { case analysis: Analysis => analysis }
VersionF.write(out)
// We start with writing compile setup
FormatTimer.time("write setup") { MiniSetupF.write(out, setup) }
// Next we write relations because that's the part of greatest interest to external readers,
// who can abort reading early once they're read them.
FormatTimer.time("write relations") { RelationsF.write(out, analysis0.relations) }
FormatTimer.time("write stamps") { StampsF.write(out, analysis0.stamps) }
FormatTimer.time("write apis") { APIsF.write(out, analysis0.apis) }
FormatTimer.time("write sourceinfos") { SourceInfosF.write(out, analysis0.infos) }
FormatTimer.time("write compilations") { CompilationsF.write(out, analysis0.compilations) }
out.flush()
}
// Writes the "api" portion of xsbti.api.AnalyzedClass.
def writeCompanionMap(out: Writer, apis: APIs): Unit = {
VersionF.write(out)
CompanionsF.write(out, apis)
out.flush()
}
// Companions portion of the API info is read from a separate file lazily.
def read(in: BufferedReader, companionsStore: CompanionsStore): (CompileAnalysis, MiniSetup) = {
VersionF.read(in)
val setup = FormatTimer.time("read setup") { MiniSetupF.read(in) }
val relations = FormatTimer.time("read relations") { RelationsF.read(in) }
val stamps = FormatTimer.time("read stamps") { StampsF.read(in) }
val apis = FormatTimer.time("read apis") {
APIsF.read(in, if (setup.storeApis) Some(companionsStore) else None)
}
val infos = FormatTimer.time("read sourceinfos") { SourceInfosF.read(in) }
val compilations = FormatTimer.time("read compilations") { CompilationsF.read(in) }
(Analysis.Empty.copy(stamps, apis, relations, infos, compilations), setup)
}
def readCompanionMap(in: BufferedReader): (Map[String, Companions], Map[String, Companions]) = {
VersionF.read(in)
CompanionsF.read(in)
}
private[this] object VersionF {
val currentVersion = "7"
def write(out: Writer): Unit = {
out.write("format version: %s\n".format(currentVersion))
}
private val versionPattern = """format version: (\w+)""".r
def read(in: BufferedReader): Unit = {
in.readLine() match {
case versionPattern(version) => validateVersion(version)
case s: String => throw new ReadException("\"format version: \"", s)
case null => throw new EOFException
}
}
def validateVersion(version: String): Unit = {
// TODO: Support backwards compatibility?
if (version != currentVersion) {
throw new ReadException(
"File uses format version %s, but we are compatible with version %s only."
.format(version, currentVersion)
)
}
}
}
override def productsMapper = Mapper(
(str: String) => readMapper.mapProductFile(Mapper.forFileV.read(str)),
(file: VirtualFileRef) => Mapper.forFileV.write(writeMapper.mapProductFile(file))
)
override def sourcesMapper = Mapper(
(str: String) => readMapper.mapSourceFile(Mapper.forFileV.read(str)),
(file: VirtualFileRef) => Mapper.forFileV.write(writeMapper.mapSourceFile(file))
)
override def binariesMapper = Mapper(
(str: String) => readMapper.mapBinaryFile(Mapper.forFileV.read(str)),
(file: VirtualFileRef) => Mapper.forFileV.write(writeMapper.mapBinaryFile(file))
)
private[this] object StampsF {
object Headers {
val products = "product stamps"
val sources = "source stamps"
val binaries = "binary stamps"
}
final val productsStampsMapper = ContextAwareMapper[VirtualFileRef, Stamp](
(f: VirtualFileRef, str: String) =>
readMapper.mapProductStamp(f, Mapper.forStampV.read(f, str)),
(f: VirtualFileRef, stamp: Stamp) =>
Mapper.forStampV.write(f, writeMapper.mapProductStamp(f, stamp))
)
final val sourcesStampsMapper = ContextAwareMapper[VirtualFileRef, Stamp](
(f: VirtualFileRef, str: String) =>
readMapper.mapSourceStamp(f, Mapper.forStampV.read(f, str)),
(f: VirtualFileRef, stamp: Stamp) =>
Mapper.forStampV.write(f, writeMapper.mapSourceStamp(f, stamp))
)
final val binariesStampsMapper = ContextAwareMapper[VirtualFileRef, Stamp](
(f: VirtualFileRef, str: String) =>
readMapper.mapBinaryStamp(f, Mapper.forStampV.read(f, str)),
(f: VirtualFileRef, stamp: Stamp) =>
Mapper.forStampV.write(f, writeMapper.mapBinaryStamp(f, stamp))
)
def write(out: Writer, stamps: Stamps): Unit = {
def doWriteMap(
header: String,
m: Map[File, Stamp],
keyMapper: Mapper[File],
valueMapper: ContextAwareMapper[File, Stamp]
) = {
val pairsToWrite = m.map(kv => (kv._1, valueMapper.write(kv._1, m(kv._1))))
writePairs(out)(header, pairsToWrite.toSeq, keyMapper.write, identity[String])
}
def doWriteMapV(
header: String,
m: Map[VirtualFileRef, Stamp],
keyMapper: Mapper[VirtualFileRef],
valueMapper: ContextAwareMapper[VirtualFileRef, Stamp]
) = {
val pairsToWrite = m.map(kv => (kv._1, valueMapper.write(kv._1, m(kv._1))))
writePairs(out)(header, pairsToWrite.toSeq, keyMapper.write, identity[String])
}
doWriteMapV(Headers.products, stamps.products, productsMapper, productsStampsMapper)
doWriteMapV(Headers.sources, stamps.sources, sourcesMapper, sourcesStampsMapper)
doWriteMapV(Headers.binaries, stamps.libraries, binariesMapper, binariesStampsMapper)
}
def read(in: BufferedReader): Stamps = {
import scala.collection.immutable.TreeMap
def doReadMap(
expectedHeader: String,
keyMapper: Mapper[File],
valueMapper: ContextAwareMapper[File, Stamp]
): TreeMap[File, Stamp] = {
TreeMap(readMappedPairs(in)(expectedHeader, keyMapper.read, valueMapper.read).toSeq: _*)
}
def doReadMapV(
expectedHeader: String,
keyMapper: Mapper[VirtualFileRef],
valueMapper: ContextAwareMapper[VirtualFileRef, Stamp]
): TreeMap[VirtualFileRef, Stamp] = {
import VirtualFileUtil._
TreeMap(readMappedPairs(in)(expectedHeader, keyMapper.read, valueMapper.read).toSeq: _*)
}
val products = doReadMapV(Headers.products, productsMapper, productsStampsMapper)
val sources = doReadMapV(Headers.sources, sourcesMapper, sourcesStampsMapper)
val libraries = doReadMapV(Headers.binaries, binariesMapper, binariesStampsMapper)
Stamps(products, sources, libraries)
}
}
private[this] object APIsF {
object Headers {
val internal = "internal apis"
val external = "external apis"
}
val stringToAnalyzedClass = ObjectStringifier.stringToObj[AnalyzedClass] _
val analyzedClassToString = ObjectStringifier.objToString[AnalyzedClass] _
def write(out: Writer, apis: APIs): Unit = {
writeMap(out)(
Headers.internal,
apis.internal,
identity[String],
analyzedClassToString,
inlineVals = false
)
writeMap(out)(
Headers.external,
apis.external,
identity[String],
analyzedClassToString,
inlineVals = false
)
FormatTimer.close("bytes -> base64")
FormatTimer.close("byte copy")
FormatTimer.close("sbinary write")
}
@inline final def lzy[T](t: => T) = SafeLazyProxy(t)
def read(in: BufferedReader, companionsStore: Option[CompanionsStore]): APIs = {
val internal = readMap(in)(Headers.internal, identity[String], stringToAnalyzedClass)
val external = readMap(in)(Headers.external, identity[String], stringToAnalyzedClass)
FormatTimer.close("base64 -> bytes")
FormatTimer.close("sbinary read")
companionsStore match {
case Some(companionsStore) =>
val companions: Lazy[(Map[String, Companions], Map[String, Companions])] =
lzy(companionsStore.getUncaught())
APIs(
internal map { case (k, v) => k -> v.withApi(lzy(companions.get._1(k))) },
external map { case (k, v) => k -> v.withApi(lzy(companions.get._2(k))) }
)
case _ => APIs(internal, external)
}
}
}
private[this] object CompanionsF {
object Headers {
val internal = "internal companions"
val external = "external companions"
}
val stringToCompanions = ObjectStringifier.stringToObj[Companions] _
val companionsToString = ObjectStringifier.objToString[Companions] _
def write(out: Writer, apis: APIs): Unit = {
val internal = apis.internal map { case (k, v) => k -> v.api }
val external = apis.external map { case (k, v) => k -> v.api }
write(out, internal, external)
}
def write(
out: Writer,
internal: Map[String, Companions],
external: Map[String, Companions]
): Unit = {
writeMap(out)(
Headers.internal,
internal,
identity[String],
companionsToString,
inlineVals = false
)
writeMap(out)(
Headers.external,
external,
identity[String],
companionsToString,
inlineVals = false
)
}
def read(in: BufferedReader): (Map[String, Companions], Map[String, Companions]) = {
val internal = readMap(in)(Headers.internal, identity[String], stringToCompanions)
val external = readMap(in)(Headers.external, identity[String], stringToCompanions)
(internal, external)
}
}
private[this] object SourceInfosF {
import VirtualFileUtil._
object Headers {
val infos = "source infos"
}
val stringToSourceInfo = ObjectStringifier.stringToObj[SourceInfo] _
val sourceInfoToString = ObjectStringifier.objToString[SourceInfo] _
def write(out: Writer, infos: SourceInfos): Unit =
writeMap(out)(
Headers.infos,
infos.allInfos,
sourcesMapper.write,
sourceInfoToString,
inlineVals = false
)
def read(in: BufferedReader): SourceInfos =
SourceInfos.of(readMap(in)(Headers.infos, sourcesMapper.read, stringToSourceInfo))
}
// Path is no serializable, so this is currently stubbed.
private[this] object CompilationsF {
object Headers {
val compilations = "compilations"
}
val stringToCompilation = ObjectStringifier.stringToObj[Compilation] _
val compilationToString = ObjectStringifier.objToString[Compilation] _
def write(out: Writer, compilations: Compilations): Unit =
writeSeq(out)(Headers.compilations, Nil, compilationToString)
def read(in: BufferedReader): Compilations = Compilations.of(Nil)
}
private[this] object MiniSetupF {
object Headers {
val outputMode = "output mode"
val outputDir = "output directories"
val classpathHash = "classpath hash"
val compileOptions = "compile options"
val javacOptions = "javac options"
val compilerVersion = "compiler version"
val compileOrder = "compile order"
val skipApiStoring = "skip Api storing"
val extra = "extra"
}
private[this] val singleOutputMode = "single"
private[this] val multipleOutputMode = "multiple"
val stringToFileHash = ObjectStringifier.stringToObj[FileHash] _
val fileHashToString = ObjectStringifier.objToString[FileHash] _
final val sourceDirMapper = Mapper(
(str: String) => readMapper.mapSourceDir(Mapper.forPath.read(str)),
(file: Path) => Mapper.forPath.write(writeMapper.mapSourceDir(file))
)
final val outputDirMapper = Mapper(
(str: String) => readMapper.mapOutputDir(Mapper.forPath.read(str)),
(file: Path) => Mapper.forPath.write(writeMapper.mapOutputDir(file))
)
final val soptionsMapper = Mapper(
(serialized: String) => readMapper.mapScalacOption(serialized),
(option: String) => writeMapper.mapScalacOption(option)
)
final val joptionsMapper = Mapper(
(serialized: String) => readMapper.mapJavacOption(serialized),
(option: String) => writeMapper.mapJavacOption(option)
)
def write(out: Writer, setup0: MiniSetup): Unit = {
val setup = writeMapper.mapMiniSetup(setup0)
val mode = singleOutputMode
// just to be compatible with multipleOutputMode
val outputAsMap = Map(Analysis.dummyOutputPath -> Analysis.dummyOutputPath)
val mappedClasspathHash = setup.options.classpathHash
.map(fh => fh.withFile(writeMapper.mapClasspathEntry(fh.file)))
writeSeq(out)(Headers.outputMode, mode :: Nil, identity[String])
writeMap(out)(Headers.outputDir, outputAsMap, sourceDirMapper.write, outputDirMapper.write)
writeSeq(out)(
Headers.classpathHash,
mappedClasspathHash.toIndexedSeq,
fileHashToString
)
writeSeq(out)(
Headers.compileOptions,
setup.options.scalacOptions.toIndexedSeq,
soptionsMapper.write
)
writeSeq(out)(
Headers.javacOptions,
setup.options.javacOptions.toIndexedSeq,
joptionsMapper.write
)
writeSeq(out)(Headers.compilerVersion, setup.compilerVersion :: Nil, identity[String])
writeSeq(out)(Headers.compileOrder, setup.order.name :: Nil, identity[String])
writeSeq(out)(Headers.skipApiStoring, setup.storeApis() :: Nil, (b: Boolean) => b.toString)
writePairs[String, String](out)(
Headers.extra,
setup.extra.toList map { x =>
(x.get1, x.get2)
},
identity[String],
identity[String]
)
}
def read(in: BufferedReader): MiniSetup = {
def s2b(s: String): Boolean = s.toBoolean
val outputDirMode = readSeq(in)(Headers.outputMode, identity[String]).headOption
val outputAsMap = readMap(in)(Headers.outputDir, sourceDirMapper.read, outputDirMapper.read)
val classpathHash0 = readSeq(in)(Headers.classpathHash, stringToFileHash)
val classpathHash = classpathHash0.map(h => h.withFile(readMapper.mapClasspathEntry(h.file)))
val compileOptions = readSeq(in)(Headers.compileOptions, soptionsMapper.read)
val javacOptions = readSeq(in)(Headers.javacOptions, joptionsMapper.read)
val compilerVersion = readSeq(in)(Headers.compilerVersion, identity[String]).head
val compileOrder = readSeq(in)(Headers.compileOrder, identity[String]).head
val skipApiStoring = readSeq(in)(Headers.skipApiStoring, s2b).head
val extra = readPairs(in)(Headers.extra, identity[String], identity[String]) map {
case (a, b) => t2[String, String](a, b)
}
val output = outputDirMode match {
case Some(s) =>
s match {
case `singleOutputMode` => CompileOutput(outputAsMap.values.head)
case `multipleOutputMode` =>
val groups = outputAsMap.iterator.map {
case (src: Path, out: Path) => CompileOutput.outputGroup(src, out)
}
CompileOutput(groups.toArray)
case str: String => throw new ReadException("Unrecognized output mode: " + str)
}
case None => throw new ReadException("No output mode specified")
}
val original = MiniSetup.of(
output, // note: this is a dummy value
MiniOptions.of(classpathHash.toArray, compileOptions.toArray, javacOptions.toArray),
compilerVersion,
xsbti.compile.CompileOrder.valueOf(compileOrder),
skipApiStoring,
extra.toArray
)
readMapper.mapMiniSetup(original)
}
}
private[this] object ObjectStringifier {
def objToString[T](o: T)(implicit fmt: sbinary.Format[T]) = {
val baos = new ByteArrayOutputStream()
val out = new sbinary.JavaOutput(baos)
FormatTimer.aggregate("sbinary write") {
try {
fmt.writes(out, o)
} finally {
baos.close()
}
}
val bytes = FormatTimer.aggregate("byte copy") { baos.toByteArray }
FormatTimer.aggregate("bytes -> base64") { Base64.factory().encode(bytes) }
}
def stringToObj[T](s: String)(implicit fmt: sbinary.Format[T]) = {
val bytes = FormatTimer.aggregate("base64 -> bytes") { Base64.factory().decode(s) }
val in = new sbinary.JavaInput(new ByteArrayInputStream(bytes))
FormatTimer.aggregate("sbinary read") { fmt.reads(in) }
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy