sbt.internal.inc.ClassFileManager.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of zinc-core_2.13 Show documentation
Show all versions of zinc-core_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
package internal
package inc
import sbt.io.IO
import xsbti.compile.{ ClassFileManager => XClassFileManager, _ }
import xsbti.{ PathBasedFile, VirtualFile }
import java.io.File
import java.nio.file.{ Files, Path }
import java.util.Optional
import scala.collection.mutable
private abstract class AbstractClassFileManager(auxiliaryFiles: Iterable[AuxiliaryClassFiles])
extends XClassFileManager {
protected def allPaths(classes: Vector[Path]): Vector[Path] = {
classes ++ classes.flatMap(associatedFiles)
}
protected def allFiles(classes: Vector[File]): Vector[File] = {
allPaths(classes.map(_.toPath)).map(_.toFile)
}
private def associatedFiles(classFile: Path): Iterable[Path] =
auxiliaryFiles.flatMap(_.associatedFiles(classFile))
}
object ClassFileManager {
def getDefaultClassFileManager(
classFileManagerType: Optional[ClassFileManagerType],
auxiliaryClassFiles: Array[AuxiliaryClassFiles]
): XClassFileManager = {
if (classFileManagerType.isPresent) {
classFileManagerType.get match {
case _: DeleteImmediatelyManagerType =>
new DeleteClassFileManager(auxiliaryClassFiles)
case m: TransactionalManagerType =>
transactional(m.backupDirectory.toPath, Array.empty, m.logger)
}
} else new DeleteClassFileManager(auxiliaryClassFiles)
}
def getDefaultClassFileManager(
classFileManagerType: Optional[ClassFileManagerType],
output: Output,
outputJarContent: JarUtils.OutputJarContent,
auxiliaryClassFiles: Array[AuxiliaryClassFiles]
): XClassFileManager = {
if (classFileManagerType.isPresent) {
classFileManagerType.get match {
case _: DeleteImmediatelyManagerType =>
deleteImmediately(output, outputJarContent, auxiliaryClassFiles)
case m: TransactionalManagerType =>
transactional(
output,
outputJarContent,
m.backupDirectory.toPath,
auxiliaryClassFiles,
m.logger
)
}
} else deleteImmediately(output, outputJarContent, auxiliaryClassFiles)
}
def getClassFileManager(
options: IncOptions,
output: Output,
outputJarContent: JarUtils.OutputJarContent
): XClassFileManager = {
import sbt.internal.inc.JavaInterfaceUtil.{ EnrichOption, EnrichOptional }
val internal =
getDefaultClassFileManager(
options.classfileManagerType,
output,
outputJarContent,
options.auxiliaryClassFiles
)
val external = Option(options.externalHooks()).flatMap(_.getExternalClassFileManager.toOption)
xsbti.compile.WrappedClassFileManager.of(internal, external.toOptional)
}
private final class DeleteClassFileManager(auxiliaryFiles: Array[AuxiliaryClassFiles])
extends AbstractClassFileManager(auxiliaryFiles) {
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def delete(classes: Array[File]): Unit = deleteImpl(classes.toVector)
override def delete(classes: Array[VirtualFile]): Unit = {
deleteImpl(classes.toVector.map(toPath).map(_.toFile))
}
private def deleteImpl(classes: Vector[File]): Unit = {
IO.deleteFilesEmptyDirs(allFiles(classes))
}
override def generated(classes: Array[VirtualFile]): Unit = ()
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def generated(classes: Array[File]): Unit = {}
override def complete(success: Boolean): Unit = ()
}
private def toPath(vf: VirtualFile): Path =
vf match {
case x: PathBasedFile => x.toPath
case x => sys.error(s"${x.id} is not path-based")
}
/**
* Constructs a minimal [[ClassFileManager]] implementation that immediately deletes
* class files when they are requested. This is the default implementation of the class
* file manager by the Scala incremental compiler if no class file manager is specified.
*/
def deleteImmediately(auxiliaryClassFiles: Array[AuxiliaryClassFiles]): XClassFileManager =
new DeleteClassFileManager(auxiliaryClassFiles)
def deleteImmediatelyFromJar(
outputJar: Path,
outputJarContent: JarUtils.OutputJarContent,
auxiliaryClassFiles: Array[AuxiliaryClassFiles],
): XClassFileManager =
new DeleteClassFileManagerForJar(outputJar, outputJarContent, auxiliaryClassFiles)
def deleteImmediately(
output: Output,
outputJarContent: JarUtils.OutputJarContent,
auxiliaryClassFiles: Array[AuxiliaryClassFiles]
): XClassFileManager = {
val outputJar = JarUtils.getOutputJar(output)
outputJar.fold(deleteImmediately(auxiliaryClassFiles))(
deleteImmediatelyFromJar(_, outputJarContent, auxiliaryClassFiles)
)
}
/**
* Constructs a transactional [[ClassFileManager]] implementation that restores class
* files to the way they were before compilation if there is an error. Otherwise, it
* keeps the successfully generated class files from the new compilation.
*
* This is the default class file manager used by sbt, and makes sense in a lot of scenarios.
*/
def transactional(
tempDir0: Path,
auxiliaryClassFiles: Array[AuxiliaryClassFiles],
logger: sbt.util.Logger
): XClassFileManager =
new TransactionalClassFileManager(tempDir0, auxiliaryClassFiles, logger)
def transactionalForJar(
outputJar: Path,
outputJarContent: JarUtils.OutputJarContent,
auxiliaryClassFiles: Array[AuxiliaryClassFiles]
): XClassFileManager = {
new TransactionalClassFileManagerForJar(
outputJar,
outputJarContent,
auxiliaryClassFiles.toVector
)
}
def transactional(
output: Output,
outputJarContent: JarUtils.OutputJarContent,
tempDir: Path,
auxiliaryClassFiles: Array[AuxiliaryClassFiles],
logger: sbt.util.Logger
): XClassFileManager = {
val outputJar = JarUtils.getOutputJar(output)
outputJar.fold(
transactional(tempDir, auxiliaryClassFiles, logger)
)(transactionalForJar(_, outputJarContent, auxiliaryClassFiles))
}
private final class TransactionalClassFileManager(
tempDir0: Path,
auxiliaryFiles: Array[AuxiliaryClassFiles],
logger: sbt.util.Logger
) extends AbstractClassFileManager(auxiliaryFiles) {
private val tempDir = tempDir0.normalize
IO.delete(tempDir.toFile)
Files.createDirectories(tempDir)
logger.debug(s"Created transactional ClassFileManager with tempDir = $tempDir")
private[this] val generatedFiles = new mutable.HashSet[File]
private[this] val movedFiles = new mutable.HashMap[File, File]
private def showFiles(files: Iterable[File]): String =
files.map(f => s"\t${f.getName}").mkString("\n")
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def delete(classes: Array[File]): Unit = deleteImpl(classes.toVector)
override def delete(classes: Array[VirtualFile]): Unit =
deleteImpl(classes.toVector.map(c => toPath(c).toFile))
private def deleteImpl(classes: Vector[File]): Unit = {
val allFiles = this.allFiles(classes)
logger.debug(s"About to delete class files:\n${showFiles(allFiles)}")
val toBeBackedUp =
allFiles.filter(c => !movedFiles.contains(c) && !generatedFiles(c))
logger.debug(s"We backup class files:\n${showFiles(toBeBackedUp)}")
for { c <- toBeBackedUp } if (c.exists) movedFiles.put(c, move(c))
IO.deleteFilesEmptyDirs(allFiles)
}
override def generated(classes0: Array[VirtualFile]): Unit =
generatedImpl(classes0.toVector.map(c => toPath(c).toFile))
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def generated(classes: Array[File]): Unit = generatedImpl(classes.toVector)
private def generatedImpl(classes: Vector[File]): Unit = {
val allFiles = this.allFiles(classes)
logger.debug(s"Registering generated classes:\n${showFiles(allFiles)}")
generatedFiles ++= allFiles
()
}
override def complete(success: Boolean): Unit = {
if (!success) {
logger.debug("Rolling back changes to class files.")
logger.debug(s"Removing generated classes:\n${showFiles(generatedFiles)}")
IO.deleteFilesEmptyDirs(generatedFiles.toVector)
logger.debug(s"Restoring class files: \n${showFiles(movedFiles.keys)}")
for {
(orig, tmp) <- movedFiles
} {
if (tmp.exists) {
if (!orig.getParentFile.exists) {
IO.createDirectory(orig.getParentFile)
} // if
IO.move(tmp, orig)
} // if
}
}
logger.debug(s"Removing the temporary directory used for backing up class files: $tempDir")
IO.delete(tempDir.toFile)
}
def move(c: File): File = {
val target = Files.createTempFile(tempDir, "sbt", ".class").toFile
IO.move(c, target)
target
}
}
private final class DeleteClassFileManagerForJar(
outputJar: Path,
outputJarContent: JarUtils.OutputJarContent,
auxiliaryFiles: Array[AuxiliaryClassFiles],
) extends AbstractClassFileManager(auxiliaryFiles) {
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def delete(classes: Array[File]): Unit = deleteImpl(classes.toVector)
override def delete(classes0: Array[VirtualFile]): Unit =
deleteImpl(classes0.toVector.map(toPath(_).toFile))
private def deleteImpl(classes: Vector[File]): Unit = {
val relClasses =
allFiles(classes).flatMap(c => JarUtils.ClassInJar.fromFile(c).toClassFilePath)
outputJarContent.removeClasses(relClasses.toSet)
JarUtils.removeFromJar(outputJar, relClasses)
}
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def generated(classes: Array[File]): Unit = ()
override def generated(classes: Array[VirtualFile]): Unit = ()
override def complete(success: Boolean): Unit = ()
}
/**
* Version of [[sbt.internal.inc.ClassFileManager.TransactionalClassFileManager]]
* that works when sources are compiled directly to a jar file.
*
* Before compilation the index is read from the output jar if it exists
* and after failed compilation it is reverted. This implementation relies
* on the fact that nothing is actually removed from jar during incremental
* compilation. Files are only removed from index or new files are appended
* and potential overwrite is also handled by replacing index entry. For this
* reason the old index with offsets to old files will still be valid.
*/
private final class TransactionalClassFileManagerForJar(
outputJar: Path,
outputJarContent: JarUtils.OutputJarContent,
auxiliaryFiles: Vector[AuxiliaryClassFiles]
) extends AbstractClassFileManager(auxiliaryFiles) {
private val backedUpIndex = Some(outputJar)
.filter(Files.exists(_))
.map(JarUtils.stashIndex)
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def delete(classes: Array[File]): Unit = {
val classPaths = classes.toVector.map(_.toPath)
val filesInJar =
allPaths(classPaths)
.flatMap(c => JarUtils.ClassInJar.fromPath(c).toClassFilePath)
JarUtils.removeFromJar(outputJar, filesInJar)
outputJarContent.removeClasses(filesInJar.toSet)
}
override def delete(classes: Array[VirtualFile]): Unit = {
val classPaths = classes.toVector.map(toPath)
val filesInJar =
allPaths(classPaths).flatMap(c => JarUtils.ClassInJar.fromPath(c).toClassFilePath)
JarUtils.removeFromJar(outputJar, filesInJar)
outputJarContent.removeClasses(filesInJar.toSet)
}
@deprecated("Use variant that takes Array[VirtualFile]", "1.4.0")
override def generated(classes: Array[File]): Unit = ()
override def generated(classes: Array[VirtualFile]): Unit = ()
override def complete(success: Boolean): Unit = {
if (!success) {
backedUpIndex.foreach(index => JarUtils.unstashIndex(outputJar, index))
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy