From 39036e7c2097c5597df5e66a9d4923dd5154a510 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Tue, 12 Nov 2013 14:04:51 +0100 Subject: [PATCH] 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. --- .../src/main/scala/sbt/inc/IncOptions.scala | 65 ++++++++++++++----- .../src/main/scala/sbt/inc/Incremental.scala | 6 +- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/compile/inc/src/main/scala/sbt/inc/IncOptions.scala b/compile/inc/src/main/scala/sbt/inc/IncOptions.scala index 800eae4b3..0441eb6c5 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncOptions.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncOptions.scala @@ -46,42 +46,62 @@ final class IncOptions( */ val apiDumpDirectory: Option[java.io.File], /** 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 { + /** + * 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) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef) } def withRecompileAllFraction(recompileAllFraction: Double): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef) } def withRelationsDebug(relationsDebug: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef) } def withApiDebug(apiDebug: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef) } def withApiDiffContextSize(apiDiffContextSize: Int): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef) } def withApiDumpDirectory(apiDumpDirectory: Option[File]): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef) } def withNewClassfileManager(newClassfileManager: () => ClassfileManager): IncOptions = { 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 -// @@ -147,7 +167,8 @@ final class IncOptions( } object IncOptions extends Serializable { - val Default = new IncOptions( + 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). // 4. further changes invalidate all dependencies transitively to avoid too many steps @@ -157,17 +178,25 @@ object IncOptions extends Serializable { 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) } - @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)] = { if (x$0 == null) None 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 apiDebugKey = "apiDebug" val apiDumpDirectoryKey = "apiDumpDirectory" - val apiDiffContextSize = "apiDiffContextSize" + val apiDiffContextSizeKey = "apiDiffContextSize" + 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 @@ -208,7 +238,7 @@ object IncOptions extends Serializable { 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] = { @@ -217,9 +247,13 @@ object IncOptions extends Serializable { 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 + } new IncOptions(getTransitiveStep, getRecompileAllFraction, getRelationsDebug, getApiDebug, getApiDiffContextSize, - getApiDumpDirectory, ClassfileManager.deleteImmediately) + getApiDumpDirectory, ClassfileManager.deleteImmediately, getRecompileOnMacroDef) } 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(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 } } diff --git a/compile/inc/src/main/scala/sbt/inc/Incremental.scala b/compile/inc/src/main/scala/sbt/inc/Incremental.scala index 321a0b1c9..74ad517ac 100644 --- a/compile/inc/src/main/scala/sbt/inc/Incremental.scala +++ b/compile/inc/src/main/scala/sbt/inc/Incremental.scala @@ -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) }