diff --git a/cache/tracking/src/main/scala/sbt/Tracked.scala b/cache/tracking/src/main/scala/sbt/Tracked.scala index 66b15546f..e17c4052c 100644 --- a/cache/tracking/src/main/scala/sbt/Tracked.scala +++ b/cache/tracking/src/main/scala/sbt/Tracked.scala @@ -232,23 +232,23 @@ object FileFunction { type UpdateFunction = (ChangeReport[File], ChangeReport[File]) => Set[File] /** - Generic change-detection helper used to help build / artifact generation / - etc. steps detect whether or not they need to run. Returns a function whose - input is a Set of input files, and subsequently executes the action function - (which does the actual work: compiles, generates resources, etc.), returning - a Set of output files that it generated. - - The input file and resulting output file state is cached in - cacheBaseDirectory. On each invocation, the state of the input and output - files from the previous run is compared against the cache, as is the set of - input files. If a change in file state / input files set is detected, the - action function is re-executed. - - @param cacheBaseDirectory The folder in which to store - @param inStyle The strategy by which to detect state change in the input files from the previous run - @param outStyle The strategy by which to detect state change in the output files from the previous run - @param action The work function, which receives a list of input files and returns a list of output files - */ + * Generic change-detection helper used to help build / artifact generation / + * etc. steps detect whether or not they need to run. Returns a function whose + * input is a Set of input files, and subsequently executes the action function + * (which does the actual work: compiles, generates resources, etc.), returning + * a Set of output files that it generated. + * + * The input file and resulting output file state is cached in + * cacheBaseDirectory. On each invocation, the state of the input and output + * files from the previous run is compared against the cache, as is the set of + * input files. If a change in file state / input files set is detected, the + * action function is re-executed. + * + * @param cacheBaseDirectory The folder in which to store + * @param inStyle The strategy by which to detect state change in the input files from the previous run + * @param outStyle The strategy by which to detect state change in the output files from the previous run + * @param action The work function, which receives a list of input files and returns a list of output files + */ def cached(cacheBaseDirectory: File, inStyle: FilesInfo.Style = FilesInfo.lastModified, outStyle: FilesInfo.Style = FilesInfo.exists)(action: Set[File] => Set[File]): Set[File] => Set[File] = cached(cacheBaseDirectory)(inStyle, outStyle)((in, out) => action(in.checked)) diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index a5da65860..40a11e286 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -206,6 +206,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external def usedName(sourceFile: File, name: String) = add(usedNames, sourceFile, name) def nameHashing: Boolean = options.nameHashing + def includeSynthToNameHashing: Boolean = options.includeSynthToNameHashing def get: Analysis = addUsedNames(addCompilation(addProductsAndDeps(Analysis.empty(nameHashing = nameHashing)))) diff --git a/compile/inc/src/main/scala/sbt/inc/IncOptions.scala b/compile/inc/src/main/scala/sbt/inc/IncOptions.scala index b38fed952..17b65d65f 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncOptions.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncOptions.scala @@ -80,7 +80,11 @@ final class IncOptions( * Once Scala compiler sources are refactored to work well with name hashing algorithm this option will be * deleted immediately. */ - val antStyle: Boolean) extends Product with Serializable { + val antStyle: Boolean, + /** + * Include synthetic methods into the dependency tracking by name hashing. + */ + val includeSynthToNameHashing: Boolean) extends Product with Serializable { /** * Secondary constructor introduced to make IncOptions to be binary compatible with version that didn't have @@ -90,59 +94,72 @@ final class IncOptions( apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File], newClassfileManager: () => ClassfileManager) = { this(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, apiDumpDirectory, newClassfileManager, IncOptions.recompileOnMacroDefDefault, IncOptions.nameHashingDefault, - IncOptions.antStyleDefault) + IncOptions.antStyleDefault, IncOptions.includeSynthToNameHashingDefault) + } + + def this(transitiveStep: Int, recompileAllFraction: Double, relationsDebug: Boolean, apiDebug: Boolean, + apiDiffContextSize: Int, apiDumpDirectory: Option[java.io.File], newClassfileManager: () => ClassfileManager, + recompileOnMacroDef: Boolean, nameHashing: Boolean, antStyle: Boolean) = { + this(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, + apiDiffContextSize, apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, + antStyle, IncOptions.includeSynthToNameHashingDefault) } assert(!(antStyle && nameHashing), "Name hashing and Ant-style cannot be enabled at the same time.") def withTransitiveStep(transitiveStep: Int): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withRecompileAllFraction(recompileAllFraction: Double): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withRelationsDebug(relationsDebug: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withApiDebug(apiDebug: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withApiDiffContextSize(apiDiffContextSize: Int): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withApiDumpDirectory(apiDumpDirectory: Option[File]): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withNewClassfileManager(newClassfileManager: () => ClassfileManager): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withRecompileOnMacroDef(recompileOnMacroDef: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withNameHashing(nameHashing: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) + } + + def withIncludeSynthToNameHashing(includeSynthToNameHashing: Boolean): IncOptions = { + new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } def withAntStyle(antStyle: Boolean): IncOptions = { new IncOptions(transitiveStep, recompileAllFraction, relationsDebug, apiDebug, apiDiffContextSize, - apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle) + apiDumpDirectory, newClassfileManager, recompileOnMacroDef, nameHashing, antStyle, includeSynthToNameHashing) } //- EXPANDED CASE CLASS METHOD BEGIN -// @@ -219,6 +236,8 @@ object IncOptions extends Serializable { private val recompileOnMacroDefDefault: Boolean = true private[sbt] val nameHashingDefault: Boolean = true private val antStyleDefault: Boolean = false + // This should default to false + private[sbt] val includeSynthToNameHashingDefault = java.lang.Boolean.getBoolean("sbt.inc.include_synth") 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). diff --git a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala index 56f67f3e8..281131736 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala @@ -41,6 +41,8 @@ import scala.tools.nsc._ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat { import global._ + @inline def debug(msg: => String) = if (settings.verbose.value) inform(msg) + def extract(unit: CompilationUnit): Set[String] = { val tree = unit.body val extractedByTreeWalk = extractByTreeWalk(tree) @@ -122,7 +124,7 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) ext } (symbol != NoSymbol) && - !symbol.isSynthetic && + (callback.includeSynthToNameHashing || !symbol.isSynthetic) && !emptyName(symbol.name) } } diff --git a/compile/interface/src/test/scala/xsbt/ExtractAPISpecification.scala b/compile/interface/src/test/scala/xsbt/ExtractAPISpecification.scala index 839955689..3d58699e9 100644 --- a/compile/interface/src/test/scala/xsbt/ExtractAPISpecification.scala +++ b/compile/interface/src/test/scala/xsbt/ExtractAPISpecification.scala @@ -15,7 +15,7 @@ class ExtractAPISpecification extends Specification { def stableExistentialNames: Boolean = { def compileAndGetFooMethodApi(src: String): Def = { - val compilerForTesting = new ScalaCompilerForUnitTesting + val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = false) val sourceApi = compilerForTesting.extractApiFromSrc(src) val FooApi = sourceApi.definitions().find(_.name() == "Foo").get.asInstanceOf[ClassLike] val fooMethodApi = FooApi.structure().declared().find(_.name == "foo").get @@ -66,7 +66,7 @@ class ExtractAPISpecification extends Specification { | class Foo extends Namers |} |""".stripMargin - val compilerForTesting = new ScalaCompilerForUnitTesting + val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = false) val apis = compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = false)(List(src1, src2), List(src2)) val _ :: src2Api1 :: src2Api2 :: Nil = apis.toList val namerApi1 = selectNamer(src2Api1) @@ -92,7 +92,7 @@ class ExtractAPISpecification extends Specification { val srcC6 = "class C6 extends AnyRef with X { self: X with Y => }" val srcC7 = "class C7 { _ => }" val srcC8 = "class C8 { self => }" - val compilerForTesting = new ScalaCompilerForUnitTesting + val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = false) val apis = compilerForTesting.extractApisFromSrcs(reuseCompilerInstance = true)( List(srcX, srcY, srcC1, srcC2, srcC3, srcC4, srcC5, srcC6, srcC7, srcC8) ).map(x => collectFirstClass(x.definitions)) diff --git a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala index 95a27a30d..31faf2a24 100644 --- a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala +++ b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala @@ -20,7 +20,7 @@ import ScalaCompilerForUnitTesting.ExtractedSourceDependencies * Provides common functionality needed for unit tests that require compiling * source code using Scala compiler. */ -class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { +class ScalaCompilerForUnitTesting(nameHashing: Boolean, includeSynthToNameHashing: Boolean = false) { /** * Compiles given source code using Scala compiler and returns API representation @@ -127,7 +127,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { private def compileSrcs(groupedSrcs: List[List[String]], reuseCompilerInstance: Boolean): (Seq[File], TestCallback) = { withTemporaryDirectory { temp => - val analysisCallback = new TestCallback(nameHashing) + val analysisCallback = new TestCallback(nameHashing, includeSynthToNameHashing) val classesDir = new File(temp, "classes") classesDir.mkdir() diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index a51628f15..9a342fdf1 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -59,4 +59,8 @@ public interface AnalysisCallback * Do not depend on it, please. */ boolean nameHashing(); + /** + * Include synthetic methods into the dependency tracking by name hashing. + */ + boolean includeSynthToNameHashing(); } \ No newline at end of file diff --git a/interface/src/test/scala/xsbti/TestCallback.scala b/interface/src/test/scala/xsbti/TestCallback.scala index f0658597b..04c9b5756 100644 --- a/interface/src/test/scala/xsbti/TestCallback.scala +++ b/interface/src/test/scala/xsbti/TestCallback.scala @@ -5,7 +5,7 @@ import scala.collection.mutable.ArrayBuffer import xsbti.api.SourceAPI import xsbti.DependencyContext._ -class TestCallback(override val nameHashing: Boolean = false) extends AnalysisCallback +class TestCallback(override val nameHashing: Boolean, override val includeSynthToNameHashing: Boolean) extends AnalysisCallback { val sourceDependencies = new ArrayBuffer[(File, File, DependencyContext)] val binaryDependencies = new ArrayBuffer[(File, String, File, DependencyContext)] diff --git a/notes/0.13.12/include_synth.markdown b/notes/0.13.12/include_synth.markdown new file mode 100644 index 000000000..0348f2b03 --- /dev/null +++ b/notes/0.13.12/include_synth.markdown @@ -0,0 +1,10 @@ + [@eed3si9n]: https://github.com/eed3si9n + [@jsuereth]: https://github.com/jsuereth + [@dwijnand]: http://github.com/dwijnand + [@Duhemm]: http://github.com/Duhemm + [@gkossakowski]: https://github.com/gkossakowski + [2573]: https://github.com/sbt/sbt/issues/2537 + +### Bug fixes + +- Provides a workaround flag `incOptions := incOptions.value.withIncludeSynthToNameHashing(true)` for name hashing not including synthetic methods. This will not be enabled by default in sbt 0.13. It can also enabled by passing `sbt.inc.include_synth=true` to JVM. [#2537][2573] by [@eed3si9h][@eed3si9h] diff --git a/sbt/src/sbt-test/source-dependencies/naha-synthetic/A.scala b/sbt/src/sbt-test/source-dependencies/naha-synthetic/A.scala new file mode 100644 index 000000000..acafe2da1 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/naha-synthetic/A.scala @@ -0,0 +1 @@ +case class A(a: Int) diff --git a/sbt/src/sbt-test/source-dependencies/naha-synthetic/B.scala b/sbt/src/sbt-test/source-dependencies/naha-synthetic/B.scala new file mode 100644 index 000000000..2c4519ab3 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/naha-synthetic/B.scala @@ -0,0 +1 @@ +class B { def test(a: A) = a.copy() } diff --git a/sbt/src/sbt-test/source-dependencies/naha-synthetic/build.sbt b/sbt/src/sbt-test/source-dependencies/naha-synthetic/build.sbt new file mode 100644 index 000000000..cd5970b1c --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/naha-synthetic/build.sbt @@ -0,0 +1,5 @@ +lazy val root = (project in file(".")). + settings( + scalaVersion := "2.11.7", + incOptions := incOptions.value.withIncludeSynthToNameHashing(true) + ) diff --git a/sbt/src/sbt-test/source-dependencies/naha-synthetic/changes/A.scala b/sbt/src/sbt-test/source-dependencies/naha-synthetic/changes/A.scala new file mode 100644 index 000000000..57eb90cee --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/naha-synthetic/changes/A.scala @@ -0,0 +1 @@ +case class A(a: Int) { private def copy = ??? } diff --git a/sbt/src/sbt-test/source-dependencies/naha-synthetic/test b/sbt/src/sbt-test/source-dependencies/naha-synthetic/test new file mode 100644 index 000000000..4896f0dcc --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/naha-synthetic/test @@ -0,0 +1,5 @@ +> compile + +$ copy-file changes/A.scala A.scala + +-> compile