Skip to content

Commit

Permalink
Refactor JaredClass
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszwawrzyk committed Oct 1, 2018
1 parent 9f833af commit b4e54c3
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 113 deletions.
2 changes: 1 addition & 1 deletion internal/compiler-bridge/src/main/scala/xsbt/API.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ final class API(val global: CallbackGlobal) extends Compat with GlobalHelpers wi
val classFileName = s"${names.binaryName}.class"
val outputDir = global.settings.outputDirs.outputDirFor(sourceFile).file
val classFile = if (STJ.enabled) {
new java.io.File(STJ.jaredClass(outputDir, classFileName))
new java.io.File(STJ.JaredClass(outputDir, classFileName))
} else {
new java.io.File(outputDir, classFileName)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/compiler-bridge/src/main/scala/xsbt/Analyzer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class Analyzer(val global: CallbackGlobal) extends LocateClassFile {
STJ.outputJar
.map { jar =>
val classes = STJ.listFiles(jar)
classes.map(STJ.jaredClass(jar, _))
classes.map(STJ.JaredClass(jar, _))
}
.getOrElse(Set.empty)
}
Expand Down Expand Up @@ -77,7 +77,7 @@ final class Analyzer(val global: CallbackGlobal) extends LocateClassFile {
val classFile =
fileForClass(new java.io.File("."), sym, separatorRequired).toString
.drop(2) // stripPrefix ./ or .\
val jaredClass = STJ.jaredClass(classFile)
val jaredClass = STJ.JaredClass(classFile)
if (existingJaredClasses.contains(jaredClass)) {
Some(new File(jaredClass))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ sealed class ZincCompiler(settings: Settings, dreporter: DelegatingReporter, out
def getOutputClass(name: String): Option[AbstractFile] = {
val relPathToClass = name.replace('.', '/') + ".class"
if (STJ.enabled) {
val jaredClass = STJ.jaredClass(relPathToClass)
val jaredClass = STJ.JaredClass(relPathToClass)
if (jaredClassesFromPrevCompilation.contains(jaredClass)) {
Some(new PlainFile(jaredClass))
} else None
Expand Down
24 changes: 5 additions & 19 deletions internal/compiler-bridge/src/main/scala/xsbt/STJ.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,18 @@ final class STJ(outputDirs: Iterable[File]) {
type RelClass = String

/** Creates an identifier for a class located inside a jar.
* For plain class files it is enough to simply use the path.
* A class in jar `JaredClass` is identified as a path to jar
* and path to the class within that jar. Those two values
* are held in one string separated by `!`. Slashes in both
* paths are consistent with `File.separatorChar` as the actual
* string is usually kept in `File` object.
*
* As an example given a jar file "C:\develop\zinc\target\output.jar"
* and relative path to the class "sbt/internal/inc/Compile.class"
* The resulting identifier would be:
* "C:\develop\zinc\target\output.jar!sbt\internal\inc\Compile.class"
*
* @param jar jar file that contains the class
* @param cls relative path to the class within the jar
* @return identifier/path to a class in jar.
* Mimics the behavior of sbt.internal.inc.STJ.JaredClass.
*/
def jaredClass(jar: File, cls: RelClass): JaredClass = {
def JaredClass(jar: File, cls: RelClass): JaredClass = {
// This identifier will be stored as a java.io.File. Its constructor will normalize slashes
// which means that the identifier to be consistent should at all points have consistent
// slashes for safe comparisons, especially in sets or maps.
val relClass = if (File.separatorChar == '/') cls else cls.replace('/', File.separatorChar)
s"$jar!$relClass"
}

def jaredClass(cls: RelClass): JaredClass = {
jaredClass(outputJar.get, cls)
def JaredClass(cls: RelClass): JaredClass = {
JaredClass(outputJar.get, cls)
}

def listFiles(jar: File): Set[RelClass] = {
Expand Down Expand Up @@ -87,7 +73,7 @@ final class STJ(outputDirs: Iterable[File]) {
.filter(_.exists())
.fold(Set.empty[JaredClass]) { prevJar =>
val classes = listFiles(prevJar)
classes.map(jaredClass)
classes.map(JaredClass)
}
}
}
Expand Down
158 changes: 76 additions & 82 deletions internal/zinc-classfile/src/main/scala/sbt/internal/inc/STJ.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,53 @@ import xsbti.compile.{ Output, SingleOutput }
* [[xsbt.STJ]] is a class that has similar purpose and
* duplicates some of the code, as it is difficult to share it.
*/
object STJ extends PathFunctions with ForTestCode {
object STJ {

type RelClass = String

/** `JaredClass` is an identifier for a class located inside a jar.
* For plain class files it is enough to simply use the actual file
* system path. A class in jar is identified as a path to a jar
* and path to the class within that jar. Those two values
* are held in one string separated by `!`. Slashes in both
* paths are consistent with `File.separatorChar` as the actual
* string is usually kept in `File` object.
*
* As an example given a jar file "C:\develop\zinc\target\output.jar"
* and relative path to the class "sbt/internal/inc/Compile.class"
* The resulting identifier would be:
* "C:\develop\zinc\target\output.jar!sbt\internal\inc\Compile.class"
*/
class JaredClass(override val toString: String) extends AnyVal {

def relClass: RelClass = toJarAndRelClass._2

def toJarAndRelClass: (File, RelClass) = {
val Array(jar, cls) = toString.split("!")
// JaredClass stores RelClass part with File.separatorChar, however actual paths in zips always use '/'
val relClass = cls.replace('\\', '/')
(new File(jar), relClass)
}

def toFile: File = new File(toString)

}

object JaredClass {

def apply(jar: File, cls: RelClass): JaredClass = {
val relClass = if (File.separatorChar == '/') cls else cls.replace('/', File.separatorChar)
new JaredClass(s"$jar!$relClass")
}

// Converts URL to JaredClass but reuses the jar file extracted at the call site to avoid recomputing.
def fromURL(url: URL, jar: File): JaredClass = {
val Array(_, cls) = url.getPath.split("!/")
apply(jar, cls)
}

def fromFile(f: File): JaredClass = new JaredClass(f.toString)
}

val scalacOptions = Set("-YdisableFlatCpCaching")
val javacOptions = Set("-XDuseOptimizedZip=false")
Expand All @@ -45,7 +91,7 @@ object STJ extends PathFunctions with ForTestCode {
val reader = new IndexBasedZipFsOps.CachedStampReader
file: File =>
if (isJar(file)) {
val (jar, cls) = toJarAndRelClass(file.toString)
val (jar, cls) = JaredClass.fromFile(file).toJarAndRelClass
reader.readStamp(jar, cls)
} else {
IO.getModifiedTimeOrZero(file)
Expand Down Expand Up @@ -92,10 +138,33 @@ object STJ extends PathFunctions with ForTestCode {
}

val prevJarPrefix: String = "prev-jar"
}

sealed trait ForTestCode { this: PathFunctions =>
def isJar(file: File): Boolean = {
file.toString.split("!") match {
case Array(jar, _) => jar.endsWith(".jar")
case _ => false
}
}

def isEnabled(output: Output): Boolean = {
getOutputJar(output).isDefined
}

def getOutputJar(output: Output): Option[File] = {
output match {
case s: SingleOutput =>
Some(s.getOutputDirectory).filter(_.getName.endsWith(".jar"))
case _ => None
}
}

def javacTempOutput(outputJar: File): File = {
val outJarName = outputJar.getName
val outDirName = outJarName + "-javac-output"
outputJar.toPath.resolveSibling(outDirName).toFile
}

// for test code only
def listFiles(jar: File): Seq[String] = {
if (jar.exists()) {
withZipFile(jar) { zip =>
Expand All @@ -105,16 +174,16 @@ sealed trait ForTestCode { this: PathFunctions =>
}

def readModifiedTimeFromJar(jc: JaredClass): Long = {
val (jar, cls) = toJarAndRelClass(jc)
val (jar, cls) = jc.toJarAndRelClass
if (jar.exists()) {
withZipFile(jar) { zip =>
Option(zip.getEntry(cls)).map(_.getLastModifiedTime.toMillis).getOrElse(0)
}
} else 0
}

def existsInJar(s: JaredClass): Boolean = {
val (jar, cls) = toJarAndRelClass(s)
def existsInJar(jc: JaredClass): Boolean = {
val (jar, cls) = jc.toJarAndRelClass
jar.exists() && {
withZipFile(jar)(zip => zip.getEntry(cls) != null)
}
Expand All @@ -126,78 +195,3 @@ sealed trait ForTestCode { this: PathFunctions =>
finally file.close()
}
}

sealed trait PathFunctions {

type JaredClass = String
type RelClass = String

/** Creates an identifier for a class located inside a jar.
* For plain class files it is enough to simply use the path.
* A class in jar `JaredClass` is identified as a path to jar
* and path to the class within that jar. Those two values
* are held in one string separated by `!`. Slashes in both
* paths are consistent with `File.separatorChar` as the actual
* string is usually kept in `File` object.
*
* As an example given a jar file "C:\develop\zinc\target\output.jar"
* and relative path to the class "sbt/internal/inc/Compile.class"
* The resulting identifier would be:
* "C:\develop\zinc\target\output.jar!sbt\internal\inc\Compile.class"
*
* @param jar jar file that contains the class
* @param cls relative path to the class within the jar
* @return identifier/path to a class in jar.
*/
def jaredClass(jar: File, cls: RelClass): JaredClass = {
val relClass = if (File.separatorChar == '/') cls else cls.replace('/', File.separatorChar)
s"$jar!$relClass"
}

// Converts URL to JaredClass but reuses the jar file extracted at the call site to avoid recalculation.
def fromJarAndUrl(jar: File, url: URL): JaredClass = {
val Array(_, cls) = url.getPath.split("!/")
jaredClass(jar, cls)
}

def getRelClass(jc: JaredClass): RelClass = {
toJarAndRelClass(jc)._2
}

def getJarFile(jc: JaredClass): File = {
toJarAndRelClass(jc)._1
}

def toJarAndRelClass(jc: JaredClass): (File, RelClass) = {
val Array(jar, cls) = jc.split("!")
// JaredClass stores this part with File.separatorChar, however actual paths in zips always use '/'
val relClass = cls.replace('\\', '/')
(new File(jar), relClass)
}

def isJar(file: File): Boolean = {
file.toString.split("!") match {
case Array(jar, _) => jar.endsWith(".jar")
case _ => false
}
}

def isEnabled(output: Output): Boolean = {
getOutputJar(output).isDefined
}

def getOutputJar(output: Output): Option[File] = {
output match {
case s: SingleOutput =>
Some(s.getOutputDirectory).filter(_.getName.endsWith(".jar"))
case _ => None
}
}

def javacOutputTempDir(outputJar: File): File = {
val outJarName = outputJar.getName
val outDirName = outJarName + "-javac-output"
outputJar.toPath.resolveSibling(outDirName).toFile
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ private[sbt] object Analyze {
outputDir <- Some(output).collect { case s: SingleOutput => s.getOutputDirectory }
relativeClass <- IO.relativize(outputDir, realClassFile)
} yield {
new File(STJ.jaredClass(outputJar, relativeClass))
STJ.JaredClass(outputJar, relativeClass).toFile
}
jaredClass.getOrElse(realClassFile)
}
Expand All @@ -212,7 +212,7 @@ private[sbt] object Analyze {
IO.urlAsFile(url).map { file =>
// .contains does not compile with 2.10
if (finalJarOutput.exists(_ == file)) {
new File(STJ.fromJarAndUrl(file, url))
STJ.JaredClass.fromURL(url, file).toFile
} else {
file
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ object ClassFileManager {

private final class DeleteClassFileManagerForJar(outputJar: File) extends XClassFileManager {
override def delete(classes: Array[File]): Unit = {
val relClasses = classes.map(c => STJ.getRelClass(c.toString))
val relClasses = classes.map(c => STJ.JaredClass.fromFile(c).relClass)
STJ.removeFromJar(outputJar, relClasses)
}
override def generated(classes: Array[File]): Unit = ()
Expand All @@ -168,7 +168,7 @@ object ClassFileManager {
private val backedUpIndex = Some(outputJar).filter(_.exists()).map(STJ.stashIndex)

override def delete(jaredClasses: Array[File]): Unit = {
val classes = jaredClasses.map(s => STJ.getRelClass(s.toString))
val classes = jaredClasses.map(c => STJ.JaredClass.fromFile(c).relClass)
STJ.removeFromJar(outputJar, classes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ case class ProjectStructure(
val productFiles = analysis.relations.products(baseDirectory / srcFile)
productFiles.map { file =>
if (STJ.isJar(file)) {
STJ.getRelClass(file.toString)
STJ.JaredClass.fromFile(file).relClass
} else {
relativeClassDir(file).getPath.replace('\\', '/')
}
Expand Down Expand Up @@ -443,6 +443,7 @@ case class ProjectStructure(
outputJar
.map { currentJar =>
IO.copy(Seq(currentJar -> targetJar))
()
}
.getOrElse {
val manifest = new Manifest
Expand All @@ -454,6 +455,7 @@ case class ProjectStructure(
}
}
IO.jar(sources, targetJar, manifest)
()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ZincFileCommands(baseDirectory: File) extends FileCommands(baseDirectory)
val relBasePath = "target/classes"
IO.relativize(new File(relBasePath), new File(path)).map { relClass =>
val jar = Paths.get(baseDirectory.toString, relBasePath, "output.jar").toFile
transformJared(STJ.jaredClass(jar, relClass))
transformJared(STJ.JaredClass(jar, relClass))
}
}
val regularRes = transformPlain(fromString(path))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ final class MixedAnalyzingCompiler(

STJ.getOutputJar(output) match {
case Some(outputJar) =>
val outputDir = STJ.javacOutputTempDir(outputJar)
val outputDir = STJ.javacTempOutput(outputJar)
IO.createDirectory(outputDir)
javac.compile(javaSrcs,
joptions,
Expand Down Expand Up @@ -305,7 +305,7 @@ object MixedAnalyzingCompiler {
// it will be compiled to a temporary directory (with deterministic name)
// and then added to the final jar. This temporary directory has to be
// available for Analyze to work phase to work.
val tempJavacOutput = STJ.getOutputJar(currentSetup.output).map(STJ.javacOutputTempDir).toSeq
val tempJavacOutput = STJ.getOutputJar(currentSetup.output).map(STJ.javacTempOutput).toSeq
val absClasspath = classpath.map(_.getAbsoluteFile)
val cArgs =
new CompilerArguments(compiler.scalaInstance, compiler.classpathOptions)
Expand Down

0 comments on commit b4e54c3

Please sign in to comment.