Skip to content

Commit

Permalink
Added more comments
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszwawrzyk committed Oct 1, 2018
1 parent b4e54c3 commit 0f54b6d
Show file tree
Hide file tree
Showing 14 changed files with 319 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ final class Analyzer(val global: CallbackGlobal) extends LocateClassFile {

private def locateClassInJar(sym: Symbol, separatorRequired: Boolean): Option[File] = {
val classFile =
fileForClass(new java.io.File("."), sym, separatorRequired).toString
fileForClass(new File("."), sym, separatorRequired).toString
.drop(2) // stripPrefix ./ or .\
val jaredClass = STJ.JaredClass(classFile)
if (existingJaredClasses.contains(jaredClass)) {
Expand Down
42 changes: 32 additions & 10 deletions internal/compiler-bridge/src/main/scala/xsbt/STJ.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,39 @@ package xsbt
import java.io.File
import java.util.zip.ZipFile

/** STJ stands for Straight to Jar compilation.
/**
* STJ stands for Straight to Jar compilation.
*
* This is a utility class that provides a set of functions that
* are used to implement this feature.
* This is a utility class that provides a set of functions that
* are used to implement this feature.
*
* [[sbt.internal.inc.STJ]] is an object that has similar purpose and
* duplicates some of the code, as it is difficult to share it.
* [[sbt.internal.inc.STJ]] is an object that has similar purpose and
* duplicates some of the code, as it is difficult to share it.
*/
final class STJ(outputDirs: Iterable[File]) {
type JaredClass = String
type RelClass = String

/** Creates an identifier for a class located inside a jar.
* Mimics the behavior of sbt.internal.inc.STJ.JaredClass.
/**
* Creates an identifier for a class located inside a jar.
* Mimics the behavior of [[sbt.internal.inc.STJ.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"
}

/** Creates an identifier for a class located inside the current output jar. */
def JaredClass(cls: RelClass): JaredClass = {
JaredClass(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
Expand All @@ -42,15 +48,31 @@ final class STJ(outputDirs: Iterable[File]) {
}
}

/**
* 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]].
*/
val outputJar: Option[File] = {
outputDirs match {
case Seq(file) if file.getName.endsWith(".jar") => Some(file)
case _ => None
}
}

/**
* Informs if the Straight to Jar compilation feature is enabled,
* i.e. if the output is set to a jar file.
*/
val enabled: Boolean = outputJar.isDefined

/**
* Class that holds cached list of paths located within previous jar for quick lookup.
* See [[sbt.internal.inc.STJ#withPreviousJar]] for details on what previous jar is.
* The previous jar is located using the classpath (if it exists it is a first entry
* and has a special prefix.
*
* @param rawClasspath the classpath in a single string (entries separated with [[File.pathSeparator]])
*/
class PrevJarCache(rawClasspath: String) extends scala.collection.generic.Clearable {
private var cache: Set[JaredClass] = _

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,15 @@
* @author Xueming Shen
*/

// Modified implementation of com.sun.nio.zipfs.ZipFileSystem that allows to:
// read index (central directory), modify it and write at specified offset
/**
* Modified implementation of [[com.sun.nio.zipfs.ZipFileSystem]] that allows to:
* read index (central directory), modify it and write at specified offset.
*
* The changes focus on making public whatever is required and remove what is not.
* It is possible to use unmodified ZipFileSystem to implement operations required
* for Straight to Jar but it does not work in place (has to recreate zips) and does
* not allow to disable compression that makes it not efficient enough.
*/
public class ZipCentralDir {

private final byte[] cen; // CEN & ENDHDR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import sbt.internal.inc.zip.ZipCentralDir

import scala.collection.JavaConverters._

/**
* The concrete implementation of [[sbt.internal.inc.IndexBasedZipOps]]
* based on [[sbt.internal.inc.zip.ZipCentralDir]].
*/
object IndexBasedZipFsOps extends IndexBasedZipOps {
override type CentralDir = ZipCentralDir
override type Header = ZipCentralDir.Entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,43 @@ import java.util.zip.{ Deflater, ZipOutputStream, ZipEntry }

import sbt.io.{ IO, Using }

/**
* Provides efficient implementation of operations on zip files * that are
* used for implementation of the Straight to Jar feature.
*
* The implementation is based on index (aka central directory) that is
* located at the end of the zip file and contains among others the name/path
* and offset where the actual data of stored file is located. Reading zips
* should always be done based on that index, which means that it is often enough
* to manipulate this index without rewriting the other part of the file.
* This class heavily relies on this fact.
*
* This class abstracts over the actual operations on index i.e. reading, manipulating
* and storing it making it easy to replace.
*/
abstract class IndexBasedZipOps extends CreateZip {

/**
* Reads timestamps of zip entries. On the first access to a given zip
* it reads the timestamps once and keeps them cached for future lookups.
*
* It only supports reading stamps from a single zip. The zip passed as
* an argument is only used to initialize the cache and is later ignored.
* This is enough as stamps are only read from the output jar.
*/
class CachedStampReader {
private var cachedNameToTimestamp: Map[String, Long] = _

def readStamp(jar: File, cls: String): Long = {
def readStamp(zip: File, entry: String): Long = {
if (cachedNameToTimestamp == null) {
cachedNameToTimestamp = initializeCache(jar)
cachedNameToTimestamp = initializeCache(zip)
}
cachedNameToTimestamp.getOrElse(cls, 0)
cachedNameToTimestamp.getOrElse(entry, 0)
}

private def initializeCache(jar: File): Map[String, Long] = {
if (jar.exists()) {
val centralDir = readCentralDir(jar.toPath)
private def initializeCache(zipFile: File): Map[String, Long] = {
if (zipFile.exists()) {
val centralDir = readCentralDir(zipFile.toPath)
val headers = getHeaders(centralDir)
headers.map(header => getFileName(header) -> getLastModifiedTime(header))(
collection.breakOut)
Expand All @@ -32,33 +54,80 @@ abstract class IndexBasedZipOps extends CreateZip {
}
}

def removeEntries(jarFile: File, classes: Iterable[String]): Unit = {
removeEntries(jarFile.toPath, classes.toSet)
/**
* Removes specified entries from given zip file by replacing current index
* with a version without those entries.
* @param zipFile the zip file to remove entries from
* @param entries paths to files inside the jar e.g. sbt/internal/inc/IndexBasedZipOps.class
*/
def removeEntries(zipFile: File, entries: Iterable[String]): Unit = {
removeEntries(zipFile.toPath, entries.toSet)
}

/**
* Merges two zip files. It works by appending contents of `from`
* to `into`. Indices are combined, in case of duplicates, the
* final entries that are used are from `from`.
* The final merged zip is available under `into` path, and `from`
* is deleted.
*
* @param into the target zip file to merge to
* @param from the source zip file that is added/merged to `into`
*/
def mergeArchives(into: File, from: File): Unit = {
mergeArchives(into.toPath, from.toPath)
}

def includeInJar(jar: File, files: Seq[(File, String)]): Unit = {
if (jar.exists()) {
val tempZip = jar.toPath.resolveSibling(s"${UUID.randomUUID()}.jar").toFile
/**
* Adds `files` (plain files) to the specified zip file. Implemented by creating
* a new zip with the plain files. If `zipFile` already exists, the archives will
* be merged.
* Plain files are not removed after this operation.
*
* @param zipFile A zip file to add files to
* @param files a sequence of tuples with actual file to include and the path in
* the zip where it should be put.
*/
def includeInArchive(zipFile: File, files: Seq[(File, String)]): Unit = {
if (zipFile.exists()) {
val tempZip = zipFile.toPath.resolveSibling(s"${UUID.randomUUID()}.jar").toFile
createZip(tempZip, files)
mergeArchives(jar, tempZip)
mergeArchives(zipFile, tempZip)
} else {
createZip(jar, files)
createZip(zipFile, files)
}
}

def readCentralDir(file: File): CentralDir = {
readCentralDir(file.toPath)
/**
* Reads the current index from given zip file
*
* @param zipFile path to the zip file
* @return current index
*/
def readCentralDir(zipFile: File): CentralDir = {
readCentralDir(zipFile.toPath)
}

def writeCentralDir(file: File, centralDir: CentralDir): Unit = {
writeCentralDir(file.toPath, centralDir)
/**
* Replaces index inside the zip file.
*
* @param zipFile the zip file that should have the index updated
* @param centralDir the index to be stored in the file
*/
def writeCentralDir(zipFile: File, centralDir: CentralDir): Unit = {
writeCentralDir(zipFile.toPath, centralDir)
}

/**
* 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
*/
type CentralDir

/**
* Represents a header of a zip entry located inside the central directory. It has to contain
* the timestamp, name/path and offset to the actual data in zip file.
*/
type Header

private def writeCentralDir(path: Path, newCentralDir: CentralDir): Unit = {
Expand Down
Loading

0 comments on commit 0f54b6d

Please sign in to comment.