Skip to content

Commit

Permalink
Move jar content logic out of bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszwawrzyk committed Oct 2, 2018
1 parent ff27cac commit 15f631e
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 114 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ lazy val compilerInterface212 = (project in internalPath / "compiler-interface")
exclude[ReversedMissingMethodProblem]("xsbti.compile.ScalaInstance.loaderLibraryOnly"),
exclude[DirectMissingMethodProblem]("xsbti.api.AnalyzedClass.of"),
exclude[DirectMissingMethodProblem]("xsbti.api.AnalyzedClass.create"),
exclude[ReversedMissingMethodProblem]("xsbti.AnalysisCallback.previousJar")
exclude[ReversedMissingMethodProblem]("xsbti.AnalysisCallback.classesInJar")
)
},
)
Expand Down
5 changes: 3 additions & 2 deletions internal/compiler-bridge/src/main/scala/xsbt/Analyzer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package xsbt
import java.io.File

import scala.tools.nsc.Phase
import scala.collection.JavaConverters._

object Analyzer {
def name = "xsbt-analyzer"
Expand All @@ -27,8 +28,8 @@ final class Analyzer(val global: CallbackGlobal) extends LocateClassFile {
private lazy val existingClassesInJar: Set[JarUtils.ClassInJar] = {
JarUtils.outputJar match {
case Some(jar) =>
val classes = JarUtils.listFiles(jar)
classes.map(JarUtils.ClassInJar(jar, _))
val classes = global.callback.classesInJar().asScala
classes.map(JarUtils.ClassInJar(jar, _)).toSet
case None => Set.empty
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,6 @@ sealed class ZincCompiler(settings: Settings, dreporter: DelegatingReporter, out
this.computePhaseDescriptors
}

private final lazy val classesInJarFromPrevCompilation = {
val prevJarOptional = callback.previousJar()
val prevJar = if (prevJarOptional.isPresent) Some(prevJarOptional.get) else None
perRunCaches.recordCache(new JarUtils.PrevJarCache(prevJar))
}

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. */
Expand All @@ -150,7 +144,7 @@ sealed class ZincCompiler(settings: Settings, dreporter: DelegatingReporter, out
val relPathToClass = name.replace('.', '/') + ".class"
if (JarUtils.isCompilingToJar) {
val classInJar = JarUtils.ClassInJar(relPathToClass)
if (classesInJarFromPrevCompilation.contains(classInJar)) {
if (callback.classesInJar().contains(relPathToClass)) {
Some(new PlainFile(classInJar))
} else None
} else {
Expand Down
45 changes: 0 additions & 45 deletions internal/compiler-bridge/src/main/scala/xsbt/JarUtils.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package xsbt

import java.io.File
import java.util.zip.ZipFile

/**
* This is a utility class that provides a set of functions that
Expand All @@ -28,24 +27,6 @@ final class JarUtils(outputDirs: Iterable[File]) {
ClassInJar(outputJar.get, cls)
}

/**
* Lists regular files (not directories) inside the given jar.
*
* @param jar the file to list jars from
* @return list of paths to files in jar
*/
def listFiles(jar: File): Set[RelClass] = {
import scala.collection.JavaConverters._
// ZipFile is slightly slower than IndexBasedZipFsOps but it is quite difficult to use reuse
// IndexBasedZipFsOps in compiler bridge.
val zip = new ZipFile(jar)
try {
zip.entries().asScala.filterNot(_.isDirectory).map(_.getName).toSet
} finally {
zip.close()
}
}

/**
* The jar file that is used as output for classes. If the output is
* not set to a single .jar file, value of this field is [[None]].
Expand All @@ -63,30 +44,4 @@ final class JarUtils(outputDirs: Iterable[File]) {
*/
val isCompilingToJar: Boolean = outputJar.isDefined

/**
* Class that holds cached list of paths located within previous jar for quick lookup.
* @see sbt.internal.inc.JarUtils#withPreviousJar for details on what previous jar is
*/
class PrevJarCache(prevJar: Option[File]) extends scala.collection.generic.Clearable {
private var cache: Set[ClassInJar] = _

def contains(classInJar: ClassInJar): Boolean = {
if (cache == null) {
cache = loadEntriesFromPrevJar()
}
cache.contains(classInJar)
}

def clear(): Unit = cache = null

private def loadEntriesFromPrevJar(): Set[ClassInJar] = {
prevJar
.filter(_.exists())
.fold(Set.empty[ClassInJar]) { prevJar =>
val classes = listFiles(prevJar)
classes.map(ClassInJar)
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
package xsbti;

import xsbti.api.DependencyContext;

import java.io.File;
import java.util.EnumSet;
import java.util.Optional;

public interface AnalysisCallback {
/**
Expand Down Expand Up @@ -175,11 +175,9 @@ void problem(String what,
boolean enabled();

/**
* Returns path to a jar generated in previous compilation if it exists
* and compilation to jar is enabled.
*
* @see sbt.internal.inc.JarUtils#withPreviousJar for more information.
* Returns paths to classes that are currently in jar.
* Format is "xsbti/AnalysisCallback.class".
*/
Optional<File> previousJar();
java.util.Set<String> classesInJar();

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ class TestCallback extends AnalysisCallback {

override def apiPhaseCompleted(): Unit = {}

override def previousJar(): Optional[File] = Optional.empty[File]

override def classesInJar(): util.Set[String] = java.util.Collections.emptySet()
}

object TestCallback {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ abstract class IndexBasedZipOps extends CreateZip {
writeCentralDir(zipFile.toPath, centralDir)
}

def listEntries(zipFile: File): Seq[String] = {
val centralDir = readCentralDir(zipFile)
val headers = getHeaders(centralDir)
headers.map(getFileName)
}

/**
* Represents the central directory (index) of a zip file. It must contain the start offset
* (where it is located in the zip file) and list of headers
Expand Down
109 changes: 78 additions & 31 deletions internal/zinc-classfile/src/main/scala/sbt/internal/inc/JarUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,33 +187,31 @@ object JarUtils {
* @param callback analysis callback used to set previus jar
* @param compile function that given extra classpath for compiler runs the compilation
*/
def withPreviousJar[A](output: Output, callback: AnalysisCallback)(
compile: /*extra classpath: */ Seq[File] => A): A = {
(for {
outputJar <- getOutputJar(output)
prevJar <- toOption(callback.previousJar())
} yield {
IO.move(outputJar, prevJar)

val result = try {
compile(Seq(prevJar))
} catch {
case e: Exception =>
IO.move(prevJar, outputJar)
throw e
}

if (outputJar.exists()) {
JarUtils.mergeJars(into = prevJar, from = outputJar)
}
IO.move(prevJar, outputJar)
result
}).getOrElse {
compile(Nil)
def withPreviousJar[A](output: Output)(compile: /*extra classpath: */ Seq[File] => A): A = {
getOutputJar(output).filter(_.exists()) match {
case Some(outputJar) =>
val prevJar = createPrevJarPath()
IO.move(outputJar, prevJar)

val result = try {
compile(Seq(prevJar))
} catch {
case e: Exception =>
IO.move(prevJar, outputJar)
throw e
}

if (outputJar.exists()) {
JarUtils.mergeJars(into = prevJar, from = outputJar)
}
IO.move(prevJar, outputJar)
result
case None =>
compile(Nil)
}
}

def createPrevJarPath(): File = {
private def createPrevJarPath(): File = {
val tempDir =
sys.props.get("zinc.compile-to-jar.tmp-dir").map(new File(_)).getOrElse(IO.temporaryDirectory)
val prevJarName = s"$prevJarPrefix-${UUID.randomUUID()}.jar"
Expand Down Expand Up @@ -262,17 +260,66 @@ object JarUtils {
outputJar.toPath.resolveSibling(outDirName).toFile
}

/* Methods below are only used for test code. They are not optimized for performance. */
/** Lists class file entries in jar e.g. sbt/internal/inc/JarUtils.class */
def listClassFiles(jar: File): Seq[String] = {
// IndexBasedZipFsOps.listEntries(jar).filter(_.endsWith(".class"))
withZipFile(jar) { zip =>
zip
.entries()
.asScala
.filterNot(_.isDirectory)
.map(_.getName)
.filter(_.endsWith(".class"))
.toList
}
}

/** Lists file entries in jars (no directories) */
def listFiles(jar: File): Seq[String] = {
if (jar.exists()) {
withZipFile(jar) { zip =>
zip.entries().asScala.filterNot(_.isDirectory).map(_.getName).toList
object OutputJarContent {
private var content: Option[Content] = None
private var shouldReadJar: Boolean = false

def reset(output: Output): Unit = {
content = JarUtils.getOutputJar(output).map(new Content(_))
content.foreach { content =>
content.update()
shouldReadJar = false
}
}

def dependencyPhaseCompleted(): Unit = {
shouldReadJar = true
}

def scalacRunCompleted(): Unit = {
shouldReadJar = false
}

def addClasses(classes: Set[RelClass]): Unit = {
content.foreach(_.include(classes))
}

def get(): Set[RelClass] = {
content.fold(Set.empty[RelClass]) { content =>
if (shouldReadJar) content.update()
shouldReadJar = false
content.get()
}
} else Seq.empty
}

private class Content(outputJar: File) {
private var content: Set[RelClass] = Set.empty
def get(): Set[RelClass] = content
def include(classes: Set[RelClass]): Unit = content ++= classes
def update(): Unit = {
if (outputJar.exists()) {
content = JarUtils.listClassFiles(outputJar).toSet
}
}
}

}

/* Methods below are only used for test code. They are not optimized for performance. */
/** Reads timestamp of given jared class */
def readModifiedTime(jc: ClassInJar): Long = {
val (jar, cls) = jc.toJarAndRelClass
Expand Down
31 changes: 15 additions & 16 deletions internal/zinc-core/src/main/scala/sbt/internal/inc/Compile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@ import sbt.internal.inc.Analysis.{ LocalProduct, NonLocalProduct }
import xsbt.api.{ APIUtil, NameHashing, HashAPI }
import xsbti.api._
import xsbti.compile.{
ClassFileManager => XClassFileManager,
CompileAnalysis,
Output,
DependencyChanges,
IncOptions,
Output
CompileAnalysis,
ClassFileManager => XClassFileManager
}
import xsbti.{ Position, Problem, Severity, UseScope }
import xsbti.{ Position, UseScope, Problem, Severity }
import sbt.util.Logger
import sbt.util.InterfaceUtil.jo2o
import java.io.File
import java.util
import java.util.Optional
import sbt.internal.inc.JavaInterfaceUtil.EnrichOption

import scala.collection.JavaConverters._
import xsbti.api.DependencyContext
import xsbti.compile.analysis.ReadStamps

Expand Down Expand Up @@ -66,6 +64,7 @@ object IncrementalCompile {
val internalSourceToClassNamesMap: File => Set[String] = (f: File) =>
previous.relations.classNames(f)
val externalAPI = getExternalAPI(lookup)
JarUtils.OutputJarContent.reset(output)
try {
Incremental.compile(
sources,
Expand Down Expand Up @@ -174,12 +173,6 @@ private final class AnalysisCallback(
private[this] val binaryClassName = new HashMap[File, String]
// source files containing a macro def.
private[this] val macroClasses = Set[String]()
private[this] val prevJar = {
JarUtils
.getOutputJar(output)
.filter(_.exists())
.map(_ => JarUtils.createPrevJarPath())
}

private def add[A, B](map: Map[A, Set[B]], a: A, b: B): Unit = {
map.getOrElseUpdate(a, new HashSet[B]) += b
Expand Down Expand Up @@ -312,8 +305,10 @@ private final class AnalysisCallback(

override def enabled(): Boolean = options.enabled

def get: Analysis =
def get: Analysis = {
JarUtils.OutputJarContent.scalacRunCompleted()
addUsedNames(addCompilation(addProductsAndDeps(Analysis.empty)))
}

def getOrNil[A, B](m: collection.Map[A, Seq[B]], a: A): Seq[B] = m.get(a).toList.flatten
def addCompilation(base: Analysis): Analysis =
Expand Down Expand Up @@ -410,10 +405,14 @@ private final class AnalysisCallback(
}
}

override def dependencyPhaseCompleted(): Unit = {}
override def dependencyPhaseCompleted(): Unit = {
JarUtils.OutputJarContent.dependencyPhaseCompleted()
}

override def apiPhaseCompleted(): Unit = {}

override def previousJar(): Optional[File] = prevJar.toOptional
override def classesInJar(): java.util.Set[String] = {
JarUtils.OutputJarContent.get().asJava
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ case class ProjectStructure(

def checkNoGeneratedClassFiles(): Unit = {
val allPlainClassFiles = generatedClassFiles.get.map(_.toString)
val allClassesInJar = outputJar.toSeq.flatMap(JarUtils.listFiles).filter(_.endsWith(".class"))
val allClassesInJar = outputJar.toSeq.filter(_.exists()).flatMap(JarUtils.listClassFiles)
if (allPlainClassFiles.nonEmpty || allClassesInJar.nonEmpty) {
val allClassFiles = allPlainClassFiles ++ allClassesInJar
sys.error(s"Classes existed:\n\t${allClassFiles.mkString("\n\t")}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class IncScriptedRunner {
IO.withTemporaryDirectory { tempDir =>
// Create a global temporary directory to store the bridge et al
val handlers = new IncScriptedHandlers(tempDir, compileToJar)
ScriptedRunnerImpl.run(resourceBaseDirectory, bufferLog, tests, handlers, 4)
ScriptedRunnerImpl.run(resourceBaseDirectory, bufferLog, tests, handlers, 1)
}
}
}
Expand Down
Loading

0 comments on commit 15f631e

Please sign in to comment.