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) }