Skip to content

Commit

Permalink
Merge branch '1.0' into 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Duhemm committed Jul 10, 2017
2 parents 9c5c75f + 26f11d2 commit 92197af
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ final class CompilerInterface {
def newCompiler(options: Array[String],
output: Output,
initialLog: Logger,
initialDelegate: Reporter,
resident: Boolean): CachedCompiler =
new CachedCompiler0(options, output, new WeakLog(initialLog, initialDelegate), resident)
initialDelegate: Reporter): CachedCompiler =
new CachedCompiler0(options, output, new WeakLog(initialLog, initialDelegate))

def run(sources: Array[File],
changes: DependencyChanges,
Expand Down Expand Up @@ -54,10 +53,7 @@ private final class WeakLog(private[this] var log: Logger, private[this] var del
}
}

private final class CachedCompiler0(args: Array[String],
output: Output,
initialLog: WeakLog,
resident: Boolean)
private final class CachedCompiler0(args: Array[String], output: Output, initialLog: WeakLog)
extends CachedCompiler
with CachedCompilerCompat {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class ScalaCompilerForUnitTesting {
override def toString = s"SingleOutput($getOutputDirectory)"
}
val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter)
val cachedCompiler = new CachedCompiler0(args, output, weakLog, false)
val cachedCompiler = new CachedCompiler0(args, output, weakLog)
val settings = cachedCompiler.settings
settings.classpath.value = classpath
settings.usejavacp.value = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public interface CachedCompilerProvider {
ScalaInstance scalaInstance();

/** Return a new cached compiler from the usual compiler input. */
CachedCompiler newCachedCompiler(String[] arguments, Output output, Logger log, Reporter reporter, boolean resident);
}
CachedCompiler newCachedCompiler(String[] arguments, Output output, Logger log, Reporter reporter);
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private[xsbt] object ZincBenchmark {
val args = compilationInfo.scalacOptions
val classpath = compilationInfo.classpath
val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter)
val cachedCompiler = new CachedCompiler0(args, output, weakLog, false)
val cachedCompiler = new CachedCompiler0(args, output, weakLog)
val settings = cachedCompiler.settings
settings.classpath.value = classpath
val delegatingReporter = DelegatingReporter(settings, ConsoleReporter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,26 +114,22 @@ final class AnalyzingCompiler(
arguments: Array[String],
output: Output,
log: xLogger,
reporter: Reporter,
resident: Boolean
reporter: Reporter
): CachedCompiler =
newCachedCompiler(arguments: Seq[String], output, log, reporter, resident)
newCachedCompiler(arguments: Seq[String], output, log, reporter)

def newCachedCompiler(
arguments: Seq[String],
output: Output,
log: xLogger,
reporter: Reporter,
resident: Boolean
reporter: Reporter
): CachedCompiler = {
val javaResident: java.lang.Boolean = resident
val compiler = call("xsbt.CompilerInterface", "newCompiler", log)(
classOf[Array[String]],
classOf[Output],
classOf[xLogger],
classOf[Reporter],
classOf[Boolean]
)(arguments.toArray[String], output, log, reporter, javaResident)
classOf[Reporter]
)(arguments.toArray[String], output, log, reporter)
compiler.asInstanceOf[CachedCompiler]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ final class CompilerCache(val maxInstances: Int) extends GlobalsCache {
case null =>
log.debug(f0(s"Compiler cache miss. $key "))
val newCompiler: CachedCompiler =
c.newCachedCompiler(args, output, log, reporter, !forceNew)
c.newCachedCompiler(args, output, log, reporter)
cache.put(key, newCompiler)
newCompiler
case cachedCompiler =>
Expand Down Expand Up @@ -75,5 +75,5 @@ final class FreshCompilerCache extends GlobalsCache {
c: CachedCompilerProvider,
log: xLogger,
reporter: Reporter
): CachedCompiler = c.newCachedCompiler(args, output, log, reporter, false)
): CachedCompiler = c.newCachedCompiler(args, output, log, reporter)
}
268 changes: 220 additions & 48 deletions internal/zinc-core/src/main/scala/sbt/internal/inc/APIDiff.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,9 @@ import xsbti.api.Companions
/**
* A class which computes diffs (unified diffs) between two textual representations of an API.
*
* Internally, it uses java-diff-utils library but it calls it through reflection so there's
* no hard dependency on java-diff-utils.
*
* The reflective lookup of java-diff-utils library is performed in the constructor. Exceptions
* thrown by reflection are passed as-is to the caller of the constructor.
*
* @throws ClassNotFoundException if difflib.DiffUtils class cannot be located
* @throws LinkageError
* @throws ExceptionInInitializerError
*/
private[inc] class APIDiff {

import APIDiff._

private val diffUtilsClass = Class.forName(diffUtilsClassName)
// method signature: diff(List<?>, List<?>)
private val diffMethod: Method =
diffUtilsClass.getMethod(diffMethodName, classOf[JList[_]], classOf[JList[_]])

private val generateUnifiedDiffMethod: Method = {
val patchClass = Class.forName(patchClassName)
// method signature: generateUnifiedDiff(String, String, List<String>, Patch, int)
diffUtilsClass.getMethod(generateUnifiedDiffMethodName,
classOf[String],
classOf[String],
classOf[JList[String]],
patchClass,
classOf[Int])
}

/**
* Generates an unified diff between textual representations of `api1` and `api2`.
*/
Expand All @@ -57,29 +30,228 @@ private[inc] class APIDiff {
contextSize: Int): String = {
val api1Str = DefaultShowAPI(api1.classApi) + "\n" + DefaultShowAPI(api1.objectApi)
val api2Str = DefaultShowAPI(api2.classApi) + "\n" + DefaultShowAPI(api2.objectApi)
generateApiDiff(fileName, api1Str, api2Str, contextSize)
DiffUtil.mkColoredCodeDiff(api1Str, api2Str, printDiffDel = true)
}

private def generateApiDiff(fileName: String, f1: String, f2: String, contextSize: Int): String = {
assert((diffMethod != null) && (generateUnifiedDiffMethod != null),
"APIDiff isn't properly initialized.")
import scala.collection.JavaConverters._
def asJavaList[T](it: Iterator[T]): java.util.List[T] = it.toSeq.asJava
val f1Lines = asJavaList(f1.lines)
val f2Lines = asJavaList(f2.lines)
//val diff = DiffUtils.diff(f1Lines, f2Lines)
val diff /*: Patch*/ = diffMethod.invoke(null, f1Lines, f2Lines)
val unifiedPatch: JList[String] = generateUnifiedDiffMethod
.invoke(null, fileName, fileName, f1Lines, diff, (contextSize: java.lang.Integer))
.asInstanceOf[JList[String]]
unifiedPatch.asScala.mkString("\n")
}
/**
* This class was directly copied from Dotty
* [[https://github.com/lampepfl/dotty/blob/0.1.2-RC1/compiler/src/dotty/tools/dotc/util/DiffUtil.scala]]
*
* Copyright (c) 2014, EPFL
*
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
private object DiffUtil {

}
import scala.annotation.tailrec
import scala.collection.mutable

private final val ANSI_DEFAULT = "\u001B[0m"
private final val ANSI_RED = "\u001B[31m"
private final val ANSI_GREEN = "\u001B[32m"

private final val DELETION_COLOR = ANSI_RED
private final val ADDITION_COLOR = ANSI_GREEN

@tailrec private def splitTokens(str: String, acc: List[String] = Nil): List[String] = {
if (str == "") {
acc.reverse
} else {
val head = str.charAt(0)
val (token, rest) = if (Character.isAlphabetic(head) || Character.isDigit(head)) {
str.span(c => Character.isAlphabetic(c) || Character.isDigit(c))
} else if (Character.isMirrored(head) || Character.isWhitespace(head)) {
str.splitAt(1)
} else {
str.span { c =>
!Character.isAlphabetic(c) && !Character.isDigit(c) &&
!Character.isMirrored(c) && !Character.isWhitespace(c)
}
}
splitTokens(rest, token :: acc)
}
}

/** @return a tuple of the (found, expected, changedPercentage) diffs as strings */
def mkColoredTypeDiff(found: String, expected: String): (String, String, Double) = {
var totalChange = 0
val foundTokens = splitTokens(found, Nil).toArray
val expectedTokens = splitTokens(expected, Nil).toArray

val diffExp = hirschberg(foundTokens, expectedTokens)
val diffAct = hirschberg(expectedTokens, foundTokens)

val exp = diffExp.collect {
case Unmodified(str) => str
case Inserted(str) =>
totalChange += str.length
ADDITION_COLOR + str + ANSI_DEFAULT
}.mkString

val fnd = diffAct.collect {
case Unmodified(str) => str
case Inserted(str) =>
totalChange += str.length
DELETION_COLOR + str + ANSI_DEFAULT
}.mkString

(fnd, exp, totalChange.toDouble / (expected.length + found.length))
}

def mkColoredLineDiff(expected: String, actual: String): String = {
val tokens = splitTokens(expected, Nil).toArray
val lastTokens = splitTokens(actual, Nil).toArray

val diff = hirschberg(lastTokens, tokens)

" |SOF\n" + diff.collect {
case Unmodified(str) =>
" |" + str
case Inserted(str) =>
ADDITION_COLOR + "e |" + str + ANSI_DEFAULT
case Modified(old, str) =>
DELETION_COLOR + "a |" + old + "\ne |" + ADDITION_COLOR + str + ANSI_DEFAULT
case Deleted(str) =>
DELETION_COLOR + "\na |" + str + ANSI_DEFAULT
}.mkString + "\n |EOF"
}

def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = {
val tokens = splitTokens(code, Nil).toArray
val lastTokens = splitTokens(lastCode, Nil).toArray

val diff = hirschberg(lastTokens, tokens)

diff.collect {
case Unmodified(str) => str
case Inserted(str) => ADDITION_COLOR + str + ANSI_DEFAULT
case Modified(old, str) if printDiffDel =>
DELETION_COLOR + old + ADDITION_COLOR + str + ANSI_DEFAULT
case Modified(_, str) => ADDITION_COLOR + str + ANSI_DEFAULT
case Deleted(str) if printDiffDel => DELETION_COLOR + str + ANSI_DEFAULT
}.mkString
}

private sealed trait Patch
private final case class Unmodified(str: String) extends Patch
private final case class Modified(original: String, str: String) extends Patch
private final case class Deleted(str: String) extends Patch
private final case class Inserted(str: String) extends Patch

private def hirschberg(a: Array[String], b: Array[String]): Array[Patch] = {
def build(x: Array[String], y: Array[String], builder: mutable.ArrayBuilder[Patch]): Unit = {
if (x.isEmpty) {
builder += Inserted(y.mkString)
} else if (y.isEmpty) {
builder += Deleted(x.mkString)
} else if (x.length == 1 || y.length == 1) {
needlemanWunsch(x, y, builder)
} else {
val xlen = x.length
val xmid = xlen / 2
val ylen = y.length

val (x1, x2) = x.splitAt(xmid)
val leftScore = nwScore(x1, y)
val rightScore = nwScore(x2.reverse, y.reverse)
val scoreSum = (leftScore zip rightScore.reverse).map {
case (left, right) => left + right
}
val max = scoreSum.max
val ymid = scoreSum.indexOf(max)

val (y1, y2) = y.splitAt(ymid)
build(x1, y1, builder)
build(x2, y2, builder)
}
}
val builder = Array.newBuilder[Patch]
build(a, b, builder)
builder.result()
}

private def nwScore(x: Array[String], y: Array[String]): Array[Int] = {
def ins(s: String) = -2
def del(s: String) = -2
def sub(s1: String, s2: String) = if (s1 == s2) 2 else -1

val score = Array.fill(x.length + 1, y.length + 1)(0)
for (j <- 1 to y.length)
score(0)(j) = score(0)(j - 1) + ins(y(j - 1))
for (i <- 1 to x.length) {
score(i)(0) = score(i - 1)(0) + del(x(i - 1))
for (j <- 1 to y.length) {
val scoreSub = score(i - 1)(j - 1) + sub(x(i - 1), y(j - 1))
val scoreDel = score(i - 1)(j) + del(x(i - 1))
val scoreIns = score(i)(j - 1) + ins(y(j - 1))
score(i)(j) = scoreSub max scoreDel max scoreIns
}
}
Array.tabulate(y.length + 1)(j => score(x.length)(j))
}

private def needlemanWunsch(x: Array[String],
y: Array[String],
builder: mutable.ArrayBuilder[Patch]): Unit = {
def similarity(a: String, b: String) = if (a == b) 2 else -1
val d = 1
val score = Array.tabulate(x.length + 1, y.length + 1) { (i, j) =>
if (i == 0) d * j
else if (j == 0) d * i
else 0
}
for (i <- 1 to x.length) {
for (j <- 1 to y.length) {
val mtch = score(i - 1)(j - 1) + similarity(x(i - 1), y(j - 1))
val delete = score(i - 1)(j) + d
val insert = score(i)(j - 1) + d
score(i)(j) = mtch max insert max delete
}
}

var alignment = List.empty[Patch]
var i = x.length
var j = y.length
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && score(i)(j) == score(i - 1)(j - 1) + similarity(x(i - 1), y(j - 1))) {
val newHead =
if (x(i - 1) == y(j - 1)) Unmodified(x(i - 1))
else Modified(x(i - 1), y(j - 1))
alignment = newHead :: alignment
i = i - 1
j = j - 1
} else if (i > 0 && score(i)(j) == score(i - 1)(j) + d) {
alignment = Deleted(x(i - 1)) :: alignment
i = i - 1
} else {
alignment = Inserted(y(j - 1)) :: alignment
j = j - 1
}
}
builder ++= alignment
}

}

private[inc] object APIDiff {
private val diffUtilsClassName = "difflib.DiffUtils"
private val patchClassName = "difflib.Patch"
private val diffMethodName = "diff"
private val generateUnifiedDiffMethodName = "generateUnifiedDiff"
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,6 @@ private[inc] abstract class IncrementalCommon(val log: sbt.util.Logger, options:
wrappedLog.debug(s"Detected a change in a public API ($src):\n$apiUnifiedPatch")
}
} catch {
case e: ClassNotFoundException =>
log.error(
"You have api debugging enabled but DiffUtils library cannot be found on sbt's classpath")
case e: LinkageError =>
log.error("Encountered linkage error while trying to load DiffUtils library.")
log.trace(e)
case e: Exception =>
log.error("An exception has been thrown while trying to dump an api diff.")
log.trace(e)
Expand Down
Loading

0 comments on commit 92197af

Please sign in to comment.