From df017bfe310558c9701fd8d585b4ffe739659279 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 4 May 2020 15:21:57 +0100 Subject: [PATCH 1/3] Add provenance to data --- .../xsbti/api/AnalyzedClass.java | 45 ++++++++++++++++--- .../src/main/contraband/other.json | 11 +++++ .../src/main/protobuf/schema.proto | 1 + .../binary/converters/ProtobufReaders.scala | 5 ++- .../binary/converters/ProtobufWriters.scala | 4 +- .../inc/text/AnalyzedClassFormats.scala | 12 ++--- .../scala/sbt/inc/AnalysisGenerators.scala | 4 +- 7 files changed, 66 insertions(+), 16 deletions(-) diff --git a/internal/compiler-interface/src/main/contraband-java/xsbti/api/AnalyzedClass.java b/internal/compiler-interface/src/main/contraband-java/xsbti/api/AnalyzedClass.java index 6460246c66..a0f614bd36 100644 --- a/internal/compiler-interface/src/main/contraband-java/xsbti/api/AnalyzedClass.java +++ b/internal/compiler-interface/src/main/contraband-java/xsbti/api/AnalyzedClass.java @@ -18,6 +18,12 @@ public static AnalyzedClass create(long _compilationTimestamp, String _name, xsb public static AnalyzedClass of(long _compilationTimestamp, String _name, xsbti.api.Lazy _api, int _apiHash, NameHash[] _nameHashes, boolean _hasMacro, int _extraHash) { return new AnalyzedClass(_compilationTimestamp, _name, _api, _apiHash, _nameHashes, _hasMacro, _extraHash); } + public static AnalyzedClass create(long _compilationTimestamp, String _name, xsbti.api.Lazy _api, int _apiHash, NameHash[] _nameHashes, boolean _hasMacro, int _extraHash, String _provenance) { + return new AnalyzedClass(_compilationTimestamp, _name, _api, _apiHash, _nameHashes, _hasMacro, _extraHash, _provenance); + } + public static AnalyzedClass of(long _compilationTimestamp, String _name, xsbti.api.Lazy _api, int _apiHash, NameHash[] _nameHashes, boolean _hasMacro, int _extraHash, String _provenance) { + return new AnalyzedClass(_compilationTimestamp, _name, _api, _apiHash, _nameHashes, _hasMacro, _extraHash, _provenance); + } private long compilationTimestamp; private String name; private xsbti.api.Lazy api; @@ -25,6 +31,7 @@ public static AnalyzedClass of(long _compilationTimestamp, String _name, xsbti.a private NameHash[] nameHashes; private boolean hasMacro; private int extraHash; + private String provenance; protected AnalyzedClass(long _compilationTimestamp, String _name, xsbti.api.Lazy _api, int _apiHash, NameHash[] _nameHashes, boolean _hasMacro) { super(); compilationTimestamp = _compilationTimestamp; @@ -34,6 +41,7 @@ protected AnalyzedClass(long _compilationTimestamp, String _name, xsbti.api.Lazy nameHashes = _nameHashes; hasMacro = _hasMacro; extraHash = apiHash; + provenance = ""; } protected AnalyzedClass(long _compilationTimestamp, String _name, xsbti.api.Lazy _api, int _apiHash, NameHash[] _nameHashes, boolean _hasMacro, int _extraHash) { super(); @@ -44,6 +52,18 @@ protected AnalyzedClass(long _compilationTimestamp, String _name, xsbti.api.Lazy nameHashes = _nameHashes; hasMacro = _hasMacro; extraHash = _extraHash; + provenance = ""; + } + protected AnalyzedClass(long _compilationTimestamp, String _name, xsbti.api.Lazy _api, int _apiHash, NameHash[] _nameHashes, boolean _hasMacro, int _extraHash, String _provenance) { + super(); + compilationTimestamp = _compilationTimestamp; + name = _name; + api = _api; + apiHash = _apiHash; + nameHashes = _nameHashes; + hasMacro = _hasMacro; + extraHash = _extraHash; + provenance = _provenance; } public long compilationTimestamp() { @@ -67,26 +87,37 @@ public boolean hasMacro() { public int extraHash() { return this.extraHash; } + /** + * An identifier of the 'provenance' of a class, such as the jar that contained it. + * Combined with a way to tell if the provenance has changed, + * it can be used to short-circuit the 'lookupAnalyzedClass' operation. + */ + public String provenance() { + return this.provenance; + } public AnalyzedClass withCompilationTimestamp(long compilationTimestamp) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public AnalyzedClass withName(String name) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public AnalyzedClass withApi(xsbti.api.Lazy api) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public AnalyzedClass withApiHash(int apiHash) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public AnalyzedClass withNameHashes(NameHash[] nameHashes) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public AnalyzedClass withHasMacro(boolean hasMacro) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public AnalyzedClass withExtraHash(int extraHash) { - return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash); + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); + } + public AnalyzedClass withProvenance(String provenance) { + return new AnalyzedClass(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance); } public boolean equals(Object obj) { return this == obj; // We have lazy members, so use object identity to avoid circularity. diff --git a/internal/compiler-interface/src/main/contraband/other.json b/internal/compiler-interface/src/main/contraband/other.json index 6d948bfd54..1c91cbdc8b 100644 --- a/internal/compiler-interface/src/main/contraband/other.json +++ b/internal/compiler-interface/src/main/contraband/other.json @@ -17,6 +17,17 @@ "type": "int", "since": "1.2.0", "default": "apiHash" + }, + { + "name": "provenance", + "type": "String", + "since": "1.4.0", + "default": "\"\"", + "doc": [ + "An identifier of the 'provenance' of a class, such as the jar that contained it.", + "Combined with a way to tell if the provenance has changed,", + "it can be used to short-circuit the 'lookupAnalyzedClass' operation." + ] } ] }, diff --git a/internal/zinc-persist/src/main/protobuf/schema.proto b/internal/zinc-persist/src/main/protobuf/schema.proto index 2b522723ba..7956ad8a19 100644 --- a/internal/zinc-persist/src/main/protobuf/schema.proto +++ b/internal/zinc-persist/src/main/protobuf/schema.proto @@ -393,6 +393,7 @@ message AnalyzedClass { repeated NameHash nameHashes = 5; bool hasMacro = 6; sint32 extraHash = 7; + string provenance = 8; } message APIs { diff --git a/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufReaders.scala b/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufReaders.scala index b57d568523..4735170135 100644 --- a/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufReaders.scala +++ b/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufReaders.scala @@ -564,7 +564,7 @@ final class ProtobufReaders(mapper: ReadMapper, currentVersion: schema.Version) import SafeLazyProxy.{ strict => mkLazy } import ReadersFeedback.ExpectedCompanionsInAnalyzedClass - val compilationTimestamp = analyzedClass.compilationTimestamp + val compilationTs = analyzedClass.compilationTimestamp val name = analyzedClass.name val api = if (!shouldStoreApis) EmptyLazyCompanions @@ -575,7 +575,8 @@ final class ProtobufReaders(mapper: ReadMapper, currentVersion: schema.Version) val extraHash = if (currentVersion == schema.Version.V1) 0 else analyzedClass.extraHash val nameHashes = analyzedClass.nameHashes.toZincArray(fromNameHash) val hasMacro = analyzedClass.hasMacro - AnalyzedClass.of(compilationTimestamp, name, api, apiHash, nameHashes, hasMacro, extraHash) + val provenance = analyzedClass.provenance.intern + AnalyzedClass.of(compilationTs, name, api, apiHash, nameHashes, hasMacro, extraHash, provenance) } private final val stringId = identity[String] _ diff --git a/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufWriters.scala b/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufWriters.scala index c35c7c5f91..28b27938d6 100644 --- a/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufWriters.scala +++ b/internal/zinc-persist/src/main/scala/sbt/internal/inc/binary/converters/ProtobufWriters.scala @@ -601,6 +601,7 @@ final class ProtobufWriters(mapper: WriteMapper) { val hasMacro = analyzedClass.hasMacro val name = analyzedClass.name() val nameHashes = analyzedClass.nameHashes().toSchemaList(toNameHash) + val provenance = analyzedClass.provenance schema.AnalyzedClass( compilationTimestamp = compilationTimestamp, name = name, @@ -608,7 +609,8 @@ final class ProtobufWriters(mapper: WriteMapper) { apiHash = apiHash, nameHashes = nameHashes, hasMacro = hasMacro, - extraHash = extraHash + extraHash = extraHash, + provenance = provenance ) } diff --git a/internal/zinc-persist/src/main/scala/sbt/internal/inc/text/AnalyzedClassFormats.scala b/internal/zinc-persist/src/main/scala/sbt/internal/inc/text/AnalyzedClassFormats.scala index b14345880b..e794e4e302 100644 --- a/internal/zinc-persist/src/main/scala/sbt/internal/inc/text/AnalyzedClassFormats.scala +++ b/internal/zinc-persist/src/main/scala/sbt/internal/inc/text/AnalyzedClassFormats.scala @@ -23,16 +23,17 @@ object AnalyzedClassFormats { implicit ev0: Format[Compilation], ev1: Format[NameHash] ): Format[AnalyzedClass] = - wrap[AnalyzedClass, (Long, String, Int, Array[NameHash], Boolean)]( - a => (a.compilationTimestamp(), a.name, a.apiHash, a.nameHashes, a.hasMacro), - (x: (Long, String, Int, Array[NameHash], Boolean)) => + wrap[AnalyzedClass, (Long, String, Int, Array[NameHash], Boolean, String)]( + a => (a.compilationTimestamp(), a.name, a.apiHash, a.nameHashes, a.hasMacro, a.provenance), + (x: (Long, String, Int, Array[NameHash], Boolean, String)) => x match { case ( compilationTimestamp: Long, name: String, apiHash: Int, nameHashes: Array[NameHash], - hasMacro: Boolean + hasMacro: Boolean, + provenance: String ) => val _ = ev0.toString AnalyzedClass.of( @@ -42,7 +43,8 @@ object AnalyzedClassFormats { apiHash, nameHashes, hasMacro, - apiHash + apiHash, + provenance ) } ) diff --git a/internal/zinc-persist/src/test/scala/sbt/inc/AnalysisGenerators.scala b/internal/zinc-persist/src/test/scala/sbt/inc/AnalysisGenerators.scala index 3912866271..65638b0962 100644 --- a/internal/zinc-persist/src/test/scala/sbt/inc/AnalysisGenerators.scala +++ b/internal/zinc-persist/src/test/scala/sbt/inc/AnalysisGenerators.scala @@ -113,6 +113,7 @@ object AnalysisGenerators { apiHash <- arbitrary[Int] hasMacro <- arbitrary[Boolean] nameHash <- genNameHash(name) + provenance <- arbitrary[String] } yield { AnalyzedClass.of( startTime, @@ -121,7 +122,8 @@ object AnalysisGenerators { apiHash, Array(nameHash), hasMacro, - apiHash // The default is to use the public API hash + apiHash, // The default is to use the public API hash + provenance ) } From 016085437a53c89ea70346b23ce62ea0244f49d2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 5 May 2020 12:36:20 +0100 Subject: [PATCH 2/3] Wire provenance to a new "quickAPI" --- .../xsbti/compile/DefaultExternalHooks.java | 8 +++ .../java/xsbti/compile/ExternalHooks.java | 6 +++ .../main/scala/sbt/internal/inc/Hooks.scala | 54 +++++++++++++++++++ .../scala/sbt/internal/inc/Incremental.scala | 15 ++++-- .../sbt/internal/inc/IncrementalCommon.scala | 11 +++- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala diff --git a/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java b/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java index 3c622bdee0..ef644c719f 100644 --- a/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java +++ b/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java @@ -11,6 +11,9 @@ package xsbti.compile; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; public class DefaultExternalHooks implements ExternalHooks { @@ -45,4 +48,9 @@ public ExternalHooks withExternalClassFileManager(ClassFileManager externalClass public ExternalHooks withExternalLookup(ExternalHooks.Lookup externalLookup) { return new DefaultExternalHooks(Optional.of(externalLookup), classFileManager); } + + @Override + public Map extraHooks() { + return Collections.unmodifiableMap(new HashMap<>()); + } } diff --git a/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java b/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java index e43397d132..676b0a99a6 100644 --- a/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java +++ b/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java @@ -11,6 +11,7 @@ package xsbti.compile; +import java.util.Map; import java.util.Optional; import java.util.Set; import xsbti.VirtualFileRef; @@ -93,4 +94,9 @@ interface Lookup { * @return An instance of {@link ExternalHooks} with the specified lookup. */ ExternalHooks withExternalLookup(Lookup externalLookup); + + /** + * Until interface stabilizes, park experimental hooks here. + */ + Map extraHooks(); } diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala new file mode 100644 index 0000000000..c2fddb4df4 --- /dev/null +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala @@ -0,0 +1,54 @@ +/* + * Zinc - The incremental compiler for Scala. + * Copyright Lightbend, Inc. and Mark Harrah + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package sbt +package internal +package inc + +import java.nio.file.Path +import java.util.{ Map => jMap } +import java.util.Optional + +import sbt.util.InterfaceUtil +import xsbti.api.AnalyzedClass +import xsbti.compile.ExternalHooks + +object Hooks { + private val QUICK_API = "QUICKAPI" + private val GET_PROVENANCE = "GETPROVENANCE" + + /** + * None => Found class somewhere outside of project. No analysis possible. + * Some(analyzed) if analyzed.provenance.isEmpty => Couldn't find it. + * Some(analyzed) => good + */ + private[sbt] def quickAPI(hooks: ExternalHooks): String => Option[AnalyzedClass] = { + val f = getOrElse(hooks, QUICK_API, (_: String) => Optional.empty[AnalyzedClass]) + c => InterfaceUtil.toOption(f(c)) + } + + def addQuickAPI(m: jMap[String, Object], f: String => Optional[AnalyzedClass]): Unit = { + m.put(QUICK_API, f) + () + } + + private[sbt] def getProvenance(hooks: ExternalHooks): Path => String = { + getOrElse(hooks, GET_PROVENANCE, (_: Path) => "") + } + + def addGetProvenance(m: jMap[String, Object], f: Path => String): Unit = { + m.put(GET_PROVENANCE, f) + () + } + + private def getOrElse[A <: AnyRef](hooks: ExternalHooks, key: String, alt: A): A = + hooks.extraHooks().getOrDefault(key, alt).asInstanceOf[A] +} diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala index bc885f3971..430d114b9d 100644 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala @@ -25,6 +25,7 @@ import xsbti.api._ import xsbti.compile.{ CompileAnalysis, DependencyChanges, + ExternalHooks, IncOptions, Output, ClassFileManager => XClassFileManager @@ -80,12 +81,13 @@ object Incremental { ): (Boolean, Analysis) = { log.debug(s"[zinc] IncrementalCompile -----------") val previous = previous0 match { case a: Analysis => a } + val externalHooks = options.externalHooks() val currentStamper = Stamps.initial(stamper) val internalBinaryToSourceClassName = (binaryClassName: String) => previous.relations.productClassName.reverse(binaryClassName).headOption val internalSourceToClassNamesMap: VirtualFile => Set[String] = (f: VirtualFile) => previous.relations.classNames(f) - val externalAPI = getExternalAPI(lookup) + val externalAPI = getExternalAPI(externalHooks, lookup) try { incrementalCompile( sources, @@ -165,8 +167,9 @@ object Incremental { val previous = previous0 match { case a: Analysis => a } val runProfiler = profiler.profileRun val incremental: IncrementalCommon = new IncrementalNameHashing(log, options, runProfiler) + val hooks = options.externalHooks val initialChanges = - incremental.detectInitialChanges(sources, previous, current, lookup, converter, output) + incremental.detectInitialChanges(sources, previous, current, lookup, converter, hooks, output) log.debug(s"> initialChanges = $initialChanges") val binaryChanges = new DependencyChanges { val modifiedLibraries = initialChanges.libraryDeps.toArray @@ -314,6 +317,11 @@ private final class AnalysisCallback( private[this] val compileStartTime: Long = System.currentTimeMillis() private[this] val compilation: Compilation = Compilation(compileStartTime, output) + private val hooks = options.externalHooks + + private val provenance = + jo2o(output.getSingleOutput).fold("")(Hooks.getProvenance(hooks)(_)).intern + override def toString = (List("Class APIs", "Object APIs", "Library deps", "Products", "Source deps") zip List(classApis, objectApis, libraryDeps, nonLocalClasses, intSrcDeps)) @@ -581,7 +589,8 @@ private final class AnalysisCallback( apiHash, nameHashes, hasMacro, - extraHash + extraHash, + provenance ) } diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala index e11a69a7c1..fcaa7a4e87 100644 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala @@ -19,7 +19,9 @@ import xsbt.api.APIUtil import xsbti.api.AnalyzedClass import xsbti.compile.{ Changes, + CompileAnalysis, DependencyChanges, + ExternalHooks, IncOptions, Output, ClassFileManager => XClassFileManager @@ -323,12 +325,14 @@ private[inc] abstract class IncrementalCommon( stamps: ReadStamps, lookup: Lookup, converter: FileConverter, + hooks: ExternalHooks, output: Output )(implicit equivS: Equiv[XStamp]): InitialChanges = { import IncrementalCommon.isLibraryModified import lookup.lookupAnalyzedClass val previous = previousAnalysis.stamps val previousRelations = previousAnalysis.relations + val quickAPI: String => Option[AnalyzedClass] = Hooks.quickAPI(hooks) val sourceChanges: Changes[VirtualFileRef] = lookup.changedSources(previousAnalysis).getOrElse { val previousSources: Set[VirtualFileRef] = previous.allSources.toSet @@ -375,7 +379,12 @@ private[inc] abstract class IncrementalCommon( val externalApiChanges: APIChanges = { val incrementalExternalChanges = { val previousAPIs = previousAnalysis.apis - val externalFinder = lookupAnalyzedClass(_: String).getOrElse(APIs.emptyAnalyzedClass) + val externalFinder = (binaryClassName: String) => + (quickAPI(binaryClassName) match { + case Some(api) if api.provenance.isEmpty => + lookupAnalyzedClass(binaryClassName) // found without a provenance, so looking it up + case x => x // fast-track success: either found w/ provenance or not found at all + }).getOrElse(APIs.emptyAnalyzedClass) detectAPIChanges(previousAPIs.allExternals, previousAPIs.externalAPI, externalFinder) } From fc3807984b04ae7d0352ff8365e58ad791633946 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 14 May 2020 17:43:04 +0100 Subject: [PATCH 3/3] Subsume extraHooks into ExternalHooks * add GetProvenance * implement "quickAPI" in LookupImpl#lookupAnalyzedClass --- .../xsbti/compile/DefaultExternalHooks.java | 26 ++++++--- .../java/xsbti/compile/ExternalHooks.java | 18 +++++-- .../main/scala/sbt/internal/inc/Hooks.scala | 54 ------------------- .../scala/sbt/internal/inc/Incremental.scala | 11 ++-- .../sbt/internal/inc/IncrementalCommon.scala | 11 +--- .../main/scala/sbt/internal/inc/Lookup.scala | 4 ++ .../scala/sbt/internal/inc/LookupImpl.scala | 9 +++- 7 files changed, 46 insertions(+), 87 deletions(-) delete mode 100644 internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala diff --git a/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java b/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java index ef644c719f..f4bc253482 100644 --- a/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java +++ b/internal/compiler-interface/src/main/java/xsbti/compile/DefaultExternalHooks.java @@ -11,18 +11,25 @@ package xsbti.compile; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; public class DefaultExternalHooks implements ExternalHooks { private Optional lookup = Optional.empty(); private Optional classFileManager = Optional.empty(); + private GetProvenance getProvenance = NoProvenance.INSTANCE; - public DefaultExternalHooks(Optional lookup, Optional classFileManager) { + public DefaultExternalHooks( + Optional lookup, + Optional classFileManager, + GetProvenance getProvenance + ) { this.lookup = lookup; this.classFileManager = classFileManager; + this.getProvenance = getProvenance; + } + + public DefaultExternalHooks(Optional lookup, Optional classFileManager) { + this(lookup, classFileManager, NoProvenance.INSTANCE); } @Override @@ -35,22 +42,25 @@ public Optional getExternalClassFileManager() { return classFileManager; } + @Override public GetProvenance getProvenance() { return getProvenance; } + @Override public ExternalHooks withExternalClassFileManager(ClassFileManager externalClassFileManager) { Optional external = Optional.of(externalClassFileManager); Optional mixedManager = classFileManager.isPresent() ? Optional.of(WrappedClassFileManager.of(classFileManager.get(), external)) : external; - return new DefaultExternalHooks(lookup, mixedManager); + return new DefaultExternalHooks(lookup, mixedManager, getProvenance); } @Override public ExternalHooks withExternalLookup(ExternalHooks.Lookup externalLookup) { - return new DefaultExternalHooks(Optional.of(externalLookup), classFileManager); + Optional externalLookup1 = Optional.of(externalLookup); + return new DefaultExternalHooks(externalLookup1, classFileManager, getProvenance); } @Override - public Map extraHooks() { - return Collections.unmodifiableMap(new HashMap<>()); + public ExternalHooks withGetProvenance(GetProvenance getProvenance) { + return new DefaultExternalHooks(lookup, classFileManager, getProvenance); } } diff --git a/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java b/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java index 676b0a99a6..d628ceba72 100644 --- a/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java +++ b/internal/compiler-interface/src/main/java/xsbti/compile/ExternalHooks.java @@ -11,7 +11,7 @@ package xsbti.compile; -import java.util.Map; +import java.nio.file.Path; import java.util.Optional; import java.util.Set; import xsbti.VirtualFileRef; @@ -61,6 +61,15 @@ interface Lookup { Optional hashClasspath(VirtualFile[] classpath); } + interface GetProvenance { + String get(Path path); + } + + enum NoProvenance implements GetProvenance { + INSTANCE; + @Override public String get(Path path) { return ""; } + } + /** * Returns the implementation of a lookup mechanism to be used instead of * the internal lookup provided by the default implementation. @@ -76,6 +85,8 @@ interface Lookup { */ Optional getExternalClassFileManager(); + default GetProvenance getProvenance() { return NoProvenance.INSTANCE; } + /** * Returns an instance of hooks that executes the external passed class file manager. * @@ -95,8 +106,5 @@ interface Lookup { */ ExternalHooks withExternalLookup(Lookup externalLookup); - /** - * Until interface stabilizes, park experimental hooks here. - */ - Map extraHooks(); + default ExternalHooks withGetProvenance(GetProvenance getProvenance) { return this; } } diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala deleted file mode 100644 index c2fddb4df4..0000000000 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/Hooks.scala +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Zinc - The incremental compiler for Scala. - * Copyright Lightbend, Inc. and Mark Harrah - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package sbt -package internal -package inc - -import java.nio.file.Path -import java.util.{ Map => jMap } -import java.util.Optional - -import sbt.util.InterfaceUtil -import xsbti.api.AnalyzedClass -import xsbti.compile.ExternalHooks - -object Hooks { - private val QUICK_API = "QUICKAPI" - private val GET_PROVENANCE = "GETPROVENANCE" - - /** - * None => Found class somewhere outside of project. No analysis possible. - * Some(analyzed) if analyzed.provenance.isEmpty => Couldn't find it. - * Some(analyzed) => good - */ - private[sbt] def quickAPI(hooks: ExternalHooks): String => Option[AnalyzedClass] = { - val f = getOrElse(hooks, QUICK_API, (_: String) => Optional.empty[AnalyzedClass]) - c => InterfaceUtil.toOption(f(c)) - } - - def addQuickAPI(m: jMap[String, Object], f: String => Optional[AnalyzedClass]): Unit = { - m.put(QUICK_API, f) - () - } - - private[sbt] def getProvenance(hooks: ExternalHooks): Path => String = { - getOrElse(hooks, GET_PROVENANCE, (_: Path) => "") - } - - def addGetProvenance(m: jMap[String, Object], f: Path => String): Unit = { - m.put(GET_PROVENANCE, f) - () - } - - private def getOrElse[A <: AnyRef](hooks: ExternalHooks, key: String, alt: A): A = - hooks.extraHooks().getOrDefault(key, alt).asInstanceOf[A] -} diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala index 430d114b9d..556c4331ba 100644 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/Incremental.scala @@ -25,7 +25,6 @@ import xsbti.api._ import xsbti.compile.{ CompileAnalysis, DependencyChanges, - ExternalHooks, IncOptions, Output, ClassFileManager => XClassFileManager @@ -81,13 +80,12 @@ object Incremental { ): (Boolean, Analysis) = { log.debug(s"[zinc] IncrementalCompile -----------") val previous = previous0 match { case a: Analysis => a } - val externalHooks = options.externalHooks() val currentStamper = Stamps.initial(stamper) val internalBinaryToSourceClassName = (binaryClassName: String) => previous.relations.productClassName.reverse(binaryClassName).headOption val internalSourceToClassNamesMap: VirtualFile => Set[String] = (f: VirtualFile) => previous.relations.classNames(f) - val externalAPI = getExternalAPI(externalHooks, lookup) + val externalAPI = getExternalAPI(lookup) try { incrementalCompile( sources, @@ -167,9 +165,8 @@ object Incremental { val previous = previous0 match { case a: Analysis => a } val runProfiler = profiler.profileRun val incremental: IncrementalCommon = new IncrementalNameHashing(log, options, runProfiler) - val hooks = options.externalHooks val initialChanges = - incremental.detectInitialChanges(sources, previous, current, lookup, converter, hooks, output) + incremental.detectInitialChanges(sources, previous, current, lookup, converter, output) log.debug(s"> initialChanges = $initialChanges") val binaryChanges = new DependencyChanges { val modifiedLibraries = initialChanges.libraryDeps.toArray @@ -318,9 +315,7 @@ private final class AnalysisCallback( private[this] val compilation: Compilation = Compilation(compileStartTime, output) private val hooks = options.externalHooks - - private val provenance = - jo2o(output.getSingleOutput).fold("")(Hooks.getProvenance(hooks)(_)).intern + private val provenance = jo2o(output.getSingleOutput).fold("")(hooks.getProvenance.get(_)).intern override def toString = (List("Class APIs", "Object APIs", "Library deps", "Products", "Source deps") zip diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala index fcaa7a4e87..e11a69a7c1 100644 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/IncrementalCommon.scala @@ -19,9 +19,7 @@ import xsbt.api.APIUtil import xsbti.api.AnalyzedClass import xsbti.compile.{ Changes, - CompileAnalysis, DependencyChanges, - ExternalHooks, IncOptions, Output, ClassFileManager => XClassFileManager @@ -325,14 +323,12 @@ private[inc] abstract class IncrementalCommon( stamps: ReadStamps, lookup: Lookup, converter: FileConverter, - hooks: ExternalHooks, output: Output )(implicit equivS: Equiv[XStamp]): InitialChanges = { import IncrementalCommon.isLibraryModified import lookup.lookupAnalyzedClass val previous = previousAnalysis.stamps val previousRelations = previousAnalysis.relations - val quickAPI: String => Option[AnalyzedClass] = Hooks.quickAPI(hooks) val sourceChanges: Changes[VirtualFileRef] = lookup.changedSources(previousAnalysis).getOrElse { val previousSources: Set[VirtualFileRef] = previous.allSources.toSet @@ -379,12 +375,7 @@ private[inc] abstract class IncrementalCommon( val externalApiChanges: APIChanges = { val incrementalExternalChanges = { val previousAPIs = previousAnalysis.apis - val externalFinder = (binaryClassName: String) => - (quickAPI(binaryClassName) match { - case Some(api) if api.provenance.isEmpty => - lookupAnalyzedClass(binaryClassName) // found without a provenance, so looking it up - case x => x // fast-track success: either found w/ provenance or not found at all - }).getOrElse(APIs.emptyAnalyzedClass) + val externalFinder = lookupAnalyzedClass(_: String).getOrElse(APIs.emptyAnalyzedClass) detectAPIChanges(previousAPIs.allExternals, previousAPIs.externalAPI, externalFinder) } diff --git a/internal/zinc-core/src/main/scala/sbt/internal/inc/Lookup.scala b/internal/zinc-core/src/main/scala/sbt/internal/inc/Lookup.scala index 5488161552..97b49438a8 100644 --- a/internal/zinc-core/src/main/scala/sbt/internal/inc/Lookup.scala +++ b/internal/zinc-core/src/main/scala/sbt/internal/inc/Lookup.scala @@ -49,6 +49,7 @@ trait Lookup extends ExternalLookup { def lookupAnalysis(binaryClassName: String): Option[CompileAnalysis] def lookupAnalyzedClass(binaryClassName: String): Option[AnalyzedClass] = { + // This is the default, slow, route, via Analysis; overridden in LookupImpl for the fast-track. for { analysis0 <- lookupAnalysis(binaryClassName) analysis = analysis0 match { case a: Analysis => a } @@ -71,6 +72,9 @@ trait ExternalLookup extends ExternalHooks.Lookup { * Find the external `AnalyzedClass` (from another analysis) given a class name. * * @return The `AnalyzedClass` associated with the given class name, if one is found. + * Optional.empty() => Found class somewhere outside of project. No analysis possible. + * Optional.of(analyzed) if analyzed.provenance.isEmpty => Couldn't find it. + * Optional.of(analyzed) => good */ def lookupAnalyzedClass(binaryClassName: String): Option[AnalyzedClass] diff --git a/zinc/src/main/scala/sbt/internal/inc/LookupImpl.scala b/zinc/src/main/scala/sbt/internal/inc/LookupImpl.scala index 651fd1305c..9a100bdccb 100644 --- a/zinc/src/main/scala/sbt/internal/inc/LookupImpl.scala +++ b/zinc/src/main/scala/sbt/internal/inc/LookupImpl.scala @@ -58,8 +58,13 @@ class LookupImpl(compileConfiguration: CompileConfiguration, previousSetup: Opti override def lookupAnalyzedClass(binaryClassName: String): Option[AnalyzedClass] = { externalLookup match { // not flatMap so that external lookup can fast-track returning None - case Some(externalLookup) => externalLookup.lookupAnalyzedClass(binaryClassName) - case _ => super.lookupAnalyzedClass(binaryClassName) + case Some(externalLookup) => + externalLookup.lookupAnalyzedClass(binaryClassName) match { + case Some(api) if api.provenance.isEmpty => // found but w/o provenance, so go slow route + super.lookupAnalyzedClass(binaryClassName) + case x => x // fast-track success: either found w/ provenance or not found at all + } + case _ => super.lookupAnalyzedClass(binaryClassName) } }