Merge pull request #976 from gkossakowski/macro-handling

Make recompilation on macro definition optional
This commit is contained in:
Grzegorz Kossakowski 2013-11-12 12:40:51 -08:00
commit bf0ef95356
2 changed files with 180 additions and 24 deletions

View File

@ -5,10 +5,15 @@ package sbt.inc
/**
* Represents all configuration options for the incremental compiler itself and
* not the underlying Java/Scala compiler.
*
* NOTE: This class used to be a case class but due to problems with retaining
* binary compatibility while new fields are added it has been expanded to a
* regular class. All compiler-generated methods for a case class has been
* defined explicitly.
*/
final case class IncOptions(
final class IncOptions(
/** After which step include whole transitive closure of invalidated source files. */
transitiveStep: Int,
val transitiveStep: Int,
/**
* What's the fraction of invalidated source files when we switch to recompiling
* all files and giving up incremental compilation altogether. That's useful in
@ -16,9 +21,9 @@ final case class IncOptions(
* in multiple steps is high. Multi-step incremental recompilation is slower
* than recompiling everything in one step.
*/
recompileAllFraction: Double,
val recompileAllFraction: Double,
/** Print very detailed information about relations, such as dependencies between source files. */
relationsDebug: Boolean,
val relationsDebug: Boolean,
/**
* Enable tools for debugging API changes. At the moment this option is unused but in the
* future it will enable for example:
@ -26,25 +31,143 @@ final case class IncOptions(
* - diffing textual API representation which helps understanding what kind of changes
* to APIs are visible to the incremental compiler
*/
apiDebug: Boolean,
val apiDebug: Boolean,
/**
* Controls context size (in lines) displayed when diffs are produced for textual API
* representation.
*
* This option is used only when `apiDebug == true`.
*/
apiDiffContextSize: Int,
val apiDiffContextSize: Int,
/**
* The directory where we dump textual representation of APIs. This method might be called
* only if apiDebug returns true. This is unused option at the moment as the needed functionality
* is not implemented yet.
*/
apiDumpDirectory: Option[java.io.File],
val apiDumpDirectory: Option[java.io.File],
/** Creates a new ClassfileManager that will handle class file deletion and addition during a single incremental compilation run. */
newClassfileManager: () => ClassfileManager
)
val newClassfileManager: () => ClassfileManager,
/**
* Determines whether incremental compiler should recompile all dependencies of a file
* that contains a macro definition.
*/
val recompileOnMacroDef: Boolean
) extends Product with Serializable {
object IncOptions {
/**
* Secondary constructor introduced to make IncOptions to be binary compatible with version that didn't have
* `recompileOnMacroDef` filed defined.
*/
def this(transitiveStep: Int, recompileAllFraction: Double, relationsDebug: Boolean, apiDebug: Boolean,
apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File], newClassfileManager: () => ClassfileManager) = {
this(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, IncOptions.recompileOnMacroDefDefault)
}
def withTransitiveStep(transitiveStep: Int): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withRecompileAllFraction(recompileAllFraction: Double): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withRelationsDebug(relationsDebug: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withApiDebug(apiDebug: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withApiDiffContextSize(apiDiffContextSize: Int): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withApiDumpDirectory(apiDumpDirectory: Option[File]): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withNewClassfileManager(newClassfileManager: () => ClassfileManager): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withRecompileOnMacroDef(recompileOnMacroDef: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
//- EXPANDED CASE CLASS METHOD BEGIN -//
@deprecated("Use `with$nameOfTheField` copying methods instead.", "0.13.2")
def copy(transitiveStep: Int = this.transitiveStep, recompileAllFraction: Double = this.recompileAllFraction,
relationsDebug: Boolean = this.relationsDebug, apiDebug: Boolean = this.apiDebug,
apiDiffContextSize: Int = this.apiDiffContextSize,
apiDumpDirectory: Option[java.io.File] = this.apiDumpDirectory,
newClassfileManager: () => ClassfileManager = this.newClassfileManager): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager)
}
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2")
override def productPrefix: String = "IncOptions"
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2")
def productArity: Int = 7
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2")
def productElement(x$1: Int): Any = x$1 match {
case 0 => IncOptions.this.transitiveStep
case 1 => IncOptions.this.recompileAllFraction
case 2 => IncOptions.this.relationsDebug
case 3 => IncOptions.this.apiDebug
case 4 => IncOptions.this.apiDiffContextSize
case 5 => IncOptions.this.apiDumpDirectory
case 6 => IncOptions.this.newClassfileManager
case _ => throw new IndexOutOfBoundsException(x$1.toString())
}
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2")
override def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](IncOptions.this)
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2")
def canEqual(x$1: Any): Boolean = x$1.isInstanceOf[IncOptions]
override def hashCode(): Int = {
import scala.runtime.Statics
var acc: Int = -889275714
acc = Statics.mix(acc, transitiveStep)
acc = Statics.mix(acc, Statics.doubleHash(recompileAllFraction))
acc = Statics.mix(acc, if (relationsDebug) 1231 else 1237)
acc = Statics.mix(acc, if (apiDebug) 1231 else 1237)
acc = Statics.mix(acc, apiDiffContextSize)
acc = Statics.mix(acc, Statics.anyHash(apiDumpDirectory))
acc = Statics.mix(acc, Statics.anyHash(newClassfileManager))
Statics.finalizeHash(acc, 7)
}
override def toString(): String = scala.runtime.ScalaRunTime._toString(IncOptions.this)
override def equals(x$1: Any): Boolean = {
this.eq(x$1.asInstanceOf[Object]) || (x$1.isInstanceOf[IncOptions] && ({
val IncOptions$1: IncOptions = x$1.asInstanceOf[IncOptions]
transitiveStep == IncOptions$1.transitiveStep && recompileAllFraction == IncOptions$1.recompileAllFraction &&
relationsDebug == IncOptions$1.relationsDebug && apiDebug == IncOptions$1.apiDebug &&
apiDiffContextSize == IncOptions$1.apiDiffContextSize && apiDumpDirectory == IncOptions$1.apiDumpDirectory &&
newClassfileManager == IncOptions$1.newClassfileManager
}))
}
//- EXPANDED CASE CLASS METHOD END -//
}
object IncOptions extends Serializable {
private val recompileOnMacroDefDefault: Boolean = true
val Default = IncOptions(
// 1. recompile changed sources
// 2(3). recompile direct dependencies and transitive public inheritance dependencies of sources with API changes in 1(2).
@ -55,18 +178,46 @@ object IncOptions {
apiDebug = false,
apiDiffContextSize = 5,
apiDumpDirectory = None,
newClassfileManager = ClassfileManager.deleteImmediately
newClassfileManager = ClassfileManager.deleteImmediately,
recompileOnMacroDef = recompileOnMacroDefDefault
)
//- EXPANDED CASE CLASS METHOD BEGIN -//
final override def toString(): String = "IncOptions"
@deprecated("Use overloaded variant of `apply` with complete list of arguments instead.", "0.13.2")
def apply(transitiveStep: Int, recompileAllFraction: Double, relationsDebug: Boolean, apiDebug: Boolean,
apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File],
newClassfileManager: () => ClassfileManager): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager)
}
def apply(transitiveStep: Int, recompileAllFraction: Double, relationsDebug: Boolean, apiDebug: Boolean,
apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File],
newClassfileManager: () => ClassfileManager, recompileOnMacroDef: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2")
def unapply(x$0: IncOptions): Option[(Int, Double, Boolean, Boolean, Int, Option[java.io.File], () => AnyRef)] = {
if (x$0 == null) None
else Some.apply[(Int, Double, Boolean, Boolean, Int, Option[java.io.File], () => AnyRef)](
Tuple7.apply[Int, Double, Boolean, Boolean, Int, Option[java.io.File], () => AnyRef](
x$0.transitiveStep, x$0.recompileAllFraction, x$0.relationsDebug, x$0.apiDebug, x$0.apiDiffContextSize,
x$0.apiDumpDirectory, x$0.newClassfileManager))
}
private def readResolve(): Object = IncOptions
//- EXPANDED CASE CLASS METHOD END -//
def defaultTransactional(tempDir: File): IncOptions = setTransactional(Default, tempDir)
def setTransactional(opts: IncOptions, tempDir: File): IncOptions =
opts.copy(newClassfileManager = ClassfileManager.transactional(tempDir))
val transitiveStepKey = "transitiveStep"
val recompileAllFractionKey = "recompileAllFraction"
val relationsDebugKey = "relationsDebug"
val apiDebugKey = "apiDebug"
val apiDumpDirectoryKey = "apiDumpDirectory"
val apiDiffContextSize = "apiDiffContextSize"
private val transitiveStepKey = "transitiveStep"
private val recompileAllFractionKey = "recompileAllFraction"
private val relationsDebugKey = "relationsDebug"
private val apiDebugKey = "apiDebug"
private val apiDumpDirectoryKey = "apiDumpDirectory"
private val apiDiffContextSizeKey = "apiDiffContextSize"
private val recompileOnMacroDefKey = "recompileOnMacroDefKey"
def fromStringMap(m: java.util.Map[String, String]): IncOptions = {
// all the code below doesn't look like idiomatic Scala for a good reason: we are working with Java API
@ -87,7 +238,7 @@ object IncOptions {
if (m.containsKey(k)) m.get(k).toBoolean else Default.apiDebug
}
def getApiDiffContextSize: Int = {
val k = apiDiffContextSize
val k = apiDiffContextSizeKey
if (m.containsKey(k)) m.get(k).toInt else Default.apiDiffContextSize
}
def getApiDumpDirectory: Option[java.io.File] = {
@ -96,9 +247,13 @@ object IncOptions {
Some(new java.io.File(m.get(k)))
else None
}
def getRecompileOnMacroDef: Boolean = {
val k = recompileOnMacroDefKey
if (m.containsKey(k)) m.get(k).toBoolean else Default.recompileOnMacroDef
}
IncOptions(getTransitiveStep, getRecompileAllFraction, getRelationsDebug, getApiDebug, getApiDiffContextSize,
getApiDumpDirectory, ClassfileManager.deleteImmediately)
new IncOptions(getTransitiveStep, getRecompileAllFraction, getRelationsDebug, getApiDebug, getApiDiffContextSize,
getApiDumpDirectory, ClassfileManager.deleteImmediately, getRecompileOnMacroDef)
}
def toStringMap(o: IncOptions): java.util.Map[String, String] = {
@ -108,7 +263,8 @@ object IncOptions {
m.put(relationsDebugKey, o.relationsDebug.toString)
m.put(apiDebugKey, o.apiDebug.toString)
o.apiDumpDirectory.foreach(f => m.put(apiDumpDirectoryKey, f.toString))
m.put(apiDiffContextSize, o.apiDiffContextSize.toString)
m.put(apiDiffContextSizeKey, o.apiDiffContextSize.toString)
m.put(recompileOnMacroDefKey, o.recompileOnMacroDef.toString)
m
}
}

View File

@ -142,7 +142,7 @@ object Incremental
{
val oldApis = lastSources.toSeq map oldAPI
val newApis = lastSources.toSeq map newAPI
val apiChanges = (lastSources, oldApis, newApis).zipped.flatMap { (src, oldApi, newApi) => sameSource(src, oldApi, newApi, log) }
val apiChanges = (lastSources, oldApis, newApis).zipped.flatMap { (src, oldApi, newApi) => sameSource(src, oldApi, newApi, log, options) }
if (apiDebug(options) && apiChanges.nonEmpty) {
logApiChanges(apiChanges, oldAPI, newAPI, log, options)
@ -150,13 +150,13 @@ object Incremental
new APIChanges(apiChanges)
}
def sameSource[T](src: T, a: Source, b: Source, log: Logger): Option[APIChange[T]] = {
def sameSource[T](src: T, a: Source, b: Source, log: Logger, options: IncOptions): Option[APIChange[T]] = {
// Clients of a modified source file (ie, one that doesn't satisfy `shortcutSameSource`) containing macros must be recompiled.
val hasMacro = a.hasMacro || b.hasMacro
if (shortcutSameSource(a, b)) {
None
} else {
if (hasMacro) {
if (hasMacro && options.recompileOnMacroDef) {
Some(APIChangeDueToMacroDefinition(src))
} else sameAPI(src, a, b, log)
}