Make recompilation on macro definition optional.

Introduce a new incremental compiler option that controls
incremental compiler's treatment of macro definitions and their clients.
The current strategy is that whenever a source file containing a macro
definition is touched it will cause recompilation of all direct
dependencies of that file.

That strategy has proven to be too conservative for some projects like
Scala compiler of specs2 leading to too many source files being recompiled.
We make this behavior optional by introducing a new option
`recompileOnMacroDef` in `IncOptions` class. The default value is set to
`true` which preserves the previous behavior.
This commit is contained in:
Grzegorz Kossakowski 2013-11-12 14:04:51 +01:00
parent a6f04cf53b
commit 39036e7c20
2 changed files with 53 additions and 18 deletions

View File

@ -46,42 +46,62 @@ final class IncOptions(
*/ */
val 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. */ /** Creates a new ClassfileManager that will handle class file deletion and addition during a single incremental compilation run. */
val 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 { ) extends Product with Serializable {
/**
* 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 = { def withTransitiveStep(transitiveStep: Int): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
def withRecompileAllFraction(recompileAllFraction: Double): IncOptions = { def withRecompileAllFraction(recompileAllFraction: Double): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
def withRelationsDebug(relationsDebug: Boolean): IncOptions = { def withRelationsDebug(relationsDebug: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
def withApiDebug(apiDebug: Boolean): IncOptions = { def withApiDebug(apiDebug: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
def withApiDiffContextSize(apiDiffContextSize: Int): IncOptions = { def withApiDiffContextSize(apiDiffContextSize: Int): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
def withApiDumpDirectory(apiDumpDirectory: Option[File]): IncOptions = { def withApiDumpDirectory(apiDumpDirectory: Option[File]): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
def withNewClassfileManager(newClassfileManager: () => ClassfileManager): IncOptions = { def withNewClassfileManager(newClassfileManager: () => ClassfileManager): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
}
def withRecompileOnMacroDef(recompileOnMacroDef: Boolean): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager, recompileOnMacroDef)
} }
//- EXPANDED CASE CLASS METHOD BEGIN -// //- EXPANDED CASE CLASS METHOD BEGIN -//
@ -147,7 +167,8 @@ final class IncOptions(
} }
object IncOptions extends Serializable { object IncOptions extends Serializable {
val Default = new IncOptions( private val recompileOnMacroDefDefault: Boolean = true
val Default = IncOptions(
// 1. recompile changed sources // 1. recompile changed sources
// 2(3). recompile direct dependencies and transitive public inheritance dependencies of sources with API changes in 1(2). // 2(3). recompile direct dependencies and transitive public inheritance dependencies of sources with API changes in 1(2).
// 4. further changes invalidate all dependencies transitively to avoid too many steps // 4. further changes invalidate all dependencies transitively to avoid too many steps
@ -157,17 +178,25 @@ object IncOptions extends Serializable {
apiDebug = false, apiDebug = false,
apiDiffContextSize = 5, apiDiffContextSize = 5,
apiDumpDirectory = None, apiDumpDirectory = None,
newClassfileManager = ClassfileManager.deleteImmediately newClassfileManager = ClassfileManager.deleteImmediately,
recompileOnMacroDef = recompileOnMacroDefDefault
) )
//- EXPANDED CASE CLASS METHOD BEGIN -// //- EXPANDED CASE CLASS METHOD BEGIN -//
final override def toString(): String = "IncOptions" 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, def apply(transitiveStep: Int, recompileAllFraction: Double, relationsDebug: Boolean, apiDebug: Boolean,
apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File], apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File],
newClassfileManager: () => ClassfileManager): IncOptions = { newClassfileManager: () => ClassfileManager): IncOptions = {
new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize,
apiDumpDirectory, newClassfileManager) apiDumpDirectory, newClassfileManager)
} }
@deprecated("Methods generated for case class will be removed in the future.", "0.13.2") 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)] = { def unapply(x$0: IncOptions): Option[(Int, Double, Boolean, Boolean, Int, Option[java.io.File], () => AnyRef)] = {
if (x$0 == null) None if (x$0 == null) None
else Some.apply[(Int, Double, Boolean, Boolean, Int, Option[java.io.File], () => AnyRef)]( else Some.apply[(Int, Double, Boolean, Boolean, Int, Option[java.io.File], () => AnyRef)](
@ -187,7 +216,8 @@ object IncOptions extends Serializable {
val relationsDebugKey = "relationsDebug" val relationsDebugKey = "relationsDebug"
val apiDebugKey = "apiDebug" val apiDebugKey = "apiDebug"
val apiDumpDirectoryKey = "apiDumpDirectory" val apiDumpDirectoryKey = "apiDumpDirectory"
val apiDiffContextSize = "apiDiffContextSize" val apiDiffContextSizeKey = "apiDiffContextSize"
val recompileOnMacroDefKey = "recompileOnMacroDefKey"
def fromStringMap(m: java.util.Map[String, String]): IncOptions = { 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 // all the code below doesn't look like idiomatic Scala for a good reason: we are working with Java API
@ -208,7 +238,7 @@ object IncOptions extends Serializable {
if (m.containsKey(k)) m.get(k).toBoolean else Default.apiDebug if (m.containsKey(k)) m.get(k).toBoolean else Default.apiDebug
} }
def getApiDiffContextSize: Int = { def getApiDiffContextSize: Int = {
val k = apiDiffContextSize val k = apiDiffContextSizeKey
if (m.containsKey(k)) m.get(k).toInt else Default.apiDiffContextSize if (m.containsKey(k)) m.get(k).toInt else Default.apiDiffContextSize
} }
def getApiDumpDirectory: Option[java.io.File] = { def getApiDumpDirectory: Option[java.io.File] = {
@ -217,9 +247,13 @@ object IncOptions extends Serializable {
Some(new java.io.File(m.get(k))) Some(new java.io.File(m.get(k)))
else None else None
} }
def getRecompileOnMacroDef: Boolean = {
val k = recompileOnMacroDefKey
if (m.containsKey(k)) m.get(k).toBoolean else Default.recompileOnMacroDef
}
new IncOptions(getTransitiveStep, getRecompileAllFraction, getRelationsDebug, getApiDebug, getApiDiffContextSize, new IncOptions(getTransitiveStep, getRecompileAllFraction, getRelationsDebug, getApiDebug, getApiDiffContextSize,
getApiDumpDirectory, ClassfileManager.deleteImmediately) getApiDumpDirectory, ClassfileManager.deleteImmediately, getRecompileOnMacroDef)
} }
def toStringMap(o: IncOptions): java.util.Map[String, String] = { def toStringMap(o: IncOptions): java.util.Map[String, String] = {
@ -229,7 +263,8 @@ object IncOptions extends Serializable {
m.put(relationsDebugKey, o.relationsDebug.toString) m.put(relationsDebugKey, o.relationsDebug.toString)
m.put(apiDebugKey, o.apiDebug.toString) m.put(apiDebugKey, o.apiDebug.toString)
o.apiDumpDirectory.foreach(f => m.put(apiDumpDirectoryKey, f.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 m
} }
} }

View File

@ -142,7 +142,7 @@ object Incremental
{ {
val oldApis = lastSources.toSeq map oldAPI val oldApis = lastSources.toSeq map oldAPI
val newApis = lastSources.toSeq map newAPI 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) { if (apiDebug(options) && apiChanges.nonEmpty) {
logApiChanges(apiChanges, oldAPI, newAPI, log, options) logApiChanges(apiChanges, oldAPI, newAPI, log, options)
@ -150,13 +150,13 @@ object Incremental
new APIChanges(apiChanges) 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. // Clients of a modified source file (ie, one that doesn't satisfy `shortcutSameSource`) containing macros must be recompiled.
val hasMacro = a.hasMacro || b.hasMacro val hasMacro = a.hasMacro || b.hasMacro
if (shortcutSameSource(a, b)) { if (shortcutSameSource(a, b)) {
None None
} else { } else {
if (hasMacro) { if (hasMacro && options.recompileOnMacroDef) {
Some(APIChangeDueToMacroDefinition(src)) Some(APIChangeDueToMacroDefinition(src))
} else sameAPI(src, a, b, log) } else sameAPI(src, a, b, log)
} }