Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions metals/src/main/scala/scala/meta/internal/metals/Configs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1036,4 +1036,25 @@ object Configs {
}
}
}

/**
* Configuration for turbine cache. When enabled, turbine compilation results
* are persisted to disk and restored on startup to avoid recompiling unchanged
* sources.
*
* @param enabled Whether caching is enabled
*/
final case class TurbineCacheConfig(enabled: Boolean)

object TurbineCacheConfig {
val default: TurbineCacheConfig = TurbineCacheConfig(enabled = false)
val enabled: TurbineCacheConfig = TurbineCacheConfig(enabled = true)

def fromConfig(value: Option[Boolean]): TurbineCacheConfig = {
value match {
case Some(enabled) => TurbineCacheConfig(enabled)
case None => default
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ object Directories {
RelativePath(".metals").resolve("rules")
def explainedDiagnostics: RelativePath =
RelativePath(".metals").resolve("explained-diagnostics")
def turbineCache: RelativePath =
RelativePath(".metals").resolve("turbine-cache.jar")

val stacktraceFilename = "stacktrace.scala"
val dependenciesName = "dependencies"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ abstract class MetalsLspService(
fallbackClasspaths = () => compilers.fallbackClasspaths,
sleeper = sleeper,
turbineRecompileDelay = () => userConfig.javaTurbineRecompileDelay,
turbineCacheConfig = () => userConfig.javaTurbineCache,
indexFilters = MbtIndexFilter.allFilters,
protobufLspConfig = () => userConfig.protobufLspConfig,
metalsOutDir = Some(embedded.targetDir),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import scala.meta.internal.metals.Configs.ProtobufLspConfig
import scala.meta.internal.metals.Configs.RangeFormattingProviders
import scala.meta.internal.metals.Configs.ReferenceProviderConfig
import scala.meta.internal.metals.Configs.ScalaImportsPlacementConfig
import scala.meta.internal.metals.Configs.TurbineCacheConfig
import scala.meta.internal.metals.Configs.TurbineRecompileDelayConfig
import scala.meta.internal.metals.Configs.WorkspaceSymbolProviderConfig
import scala.meta.internal.metals.JsonParser.XtensionSerializedAsOption
Expand Down Expand Up @@ -106,6 +107,7 @@ case class UserConfiguration(
javaSymbolLoader: JavaSymbolLoaderConfig = JavaSymbolLoaderConfig.default,
javaTurbineRecompileDelay: TurbineRecompileDelayConfig =
TurbineRecompileDelayConfig.default,
javaTurbineCache: TurbineCacheConfig = TurbineCacheConfig.default,
Comment thread
tgodzik marked this conversation as resolved.
javacServicesOverrides: JavacServicesOverrides =
JavacServicesOverrides.default,
compilerProgress: CompilerProgressConfig = CompilerProgressConfig.default,
Expand Down Expand Up @@ -299,6 +301,12 @@ case class UserConfiguration(
javaTurbineRecompileDelay.duration.toString(),
)
),
Some(
(
"javaTurbineCache",
javaTurbineCache.enabled,
)
),
Some(
(
"javacServicesOverrides",
Expand Down Expand Up @@ -1393,6 +1401,9 @@ object UserConfiguration {
val javaTurbineRecompileDelay = TurbineRecompileDelayConfig.fromConfig(
getStringKey("java-turbine-recompile-delay")
)
val javaTurbineCache = TurbineCacheConfig.fromConfig(
getBooleanKey("java-turbine-cache")
)
val javacServicesOverrides =
getKey(
"javac-services-overrides",
Expand Down Expand Up @@ -1525,6 +1536,7 @@ object UserConfiguration {
protoOutlineProvider,
javaSymbolLoader,
javaTurbineRecompileDelay,
javaTurbineCache,
javacServicesOverrides,
compilerProgress,
referenceProvider,
Expand Down
24 changes: 24 additions & 0 deletions metals/src/main/scala/scala/meta/internal/metals/mbt/GitVCS.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ import scala.meta.io.AbsolutePath

object GitVCS {

/**
* Gets the current git HEAD hash for the given workspace.
* Returns None if git is not available or the workspace is not a git repo.
*/
def getHeadHash(workspace: AbsolutePath): Option[String] = {
try {
var result: Option[String] = None
val logger = ProcessLogger { line =>
if (result.isEmpty && line.trim.nonEmpty) {
result = Some(line.trim)
}
}
val exitCode = Process(
List("git", "rev-parse", "HEAD"),
cwd = workspace.toFile,
).!(logger)
if (exitCode == 0) result else None
} catch {
case NonFatal(e) =>
scribe.debug(s"GitVCS.getHeadHash failed: ${e.getMessage}")
None
}
}

/**
* Runs `git status --porcelain --untracked-files=all` and returns a list of
* absolute paths to the relevant files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.{util => ju}
import javax.tools.JavaFileManager
import javax.tools.JavaFileObject
import javax.tools.StandardJavaFileManager

import scala.collection.concurrent.TrieMap
Expand All @@ -38,6 +39,7 @@ import scala.meta.internal.metals.BaseWorkDoneProgress
import scala.meta.internal.metals.Buffers
import scala.meta.internal.metals.Configs.JavaSymbolLoaderConfig
import scala.meta.internal.metals.Configs.ProtobufLspConfig
import scala.meta.internal.metals.Configs.TurbineCacheConfig
import scala.meta.internal.metals.Configs.TurbineRecompileDelayConfig
import scala.meta.internal.metals.Configs.WorkspaceSymbolProviderConfig
import scala.meta.internal.metals.Directories
Expand Down Expand Up @@ -106,6 +108,8 @@ class MbtWorkspaceSymbolProvider(
sleeper: Sleeper = Sleeper.TestingSleeper,
turbineRecompileDelay: () => TurbineRecompileDelayConfig = () =>
TurbineRecompileDelayConfig.fromConfig(None),
turbineCacheConfig: () => TurbineCacheConfig = () =>
TurbineCacheConfig.default,
indexFilters: List[MbtIndexFilter] = MbtIndexFilter.allFilters,
protobufLspConfig: () => ProtobufLspConfig = () =>
ProtobufLspConfig.default,
Expand Down Expand Up @@ -140,6 +144,51 @@ class MbtWorkspaceSymbolProvider(
def protoJavaOutlines(file: AbsolutePath): Seq[VirtualTextDocument] =
documents.get(file).toSeq.flatMap(protobufWorkspace.allJavaOutlines)

private val turbineCache = new TurbineCache(
workspace,
turbineCacheConfig,
turbineRecompileDelay,
time,
)

/**
* Returns dirty Java files (uncommitted changes) with their package names.
* Used to populate the sourcepath when loading from cache so that
* changed files take precedence over cached compiled classes.
*/
private def getDirtyJavaFiles(): Seq[(String, JavaFileObject)] = {
val result = for {
status <- GitVCS.status(workspace)
if !status.isDeleted && status.file.isJava
} yield {
try {
// Derive IndexedDocument from current source to handle:
// 1. Untracked Java files (not yet in documents map)
// 2. Package relocations (stale metadata in existing document)
val doc = IndexedDocument.fromFile(
status.file,
mtags(),
buffers,
dialects.Scala3,
)
for {
input <- toInput(status.file)
pkg <- doc.semanticdbPackages.headOption
} yield {
val packageName = normalizePackageName(pkg)
val compilationUnit: JavaFileObject =
doc.toSemanticdbCompilationUnit(input)
(packageName, compilationUnit)
}
} catch {
case NonFatal(e) =>
scribe.debug(s"mbt-v2: error indexing dirty file ${status.file}: $e")
None
}
}
result.flatten.toSeq
}

private val turbineCompiler: TurbineCompiler[AbsolutePath] =
new TurbineCompiler[AbsolutePath](
() => documentsKeys,
Expand Down Expand Up @@ -180,6 +229,8 @@ class MbtWorkspaceSymbolProvider(
onIndexingDone = onIndexingDone,
onNewProjectClasspath = classpath =>
protobufWorkspace.onNewProjectClasspath(classpath),
turbineCache = Some(turbineCache),
getDirtyJavaFiles = getDirtyJavaFiles,
)

// NOTE: runs unconditionally even if the user config is not mbt-v2 for usage
Expand Down Expand Up @@ -454,7 +505,7 @@ class MbtWorkspaceSymbolProvider(
// Add empty file to SOURCE_PATH so javac parses it and doesn't find the class
doc.semanticdbPackages.headOption match {
case Some(pkg) =>
val packageName = pkg.stripSuffix("/").replace("/", ".")
val packageName = normalizePackageName(pkg)
val emptyCompilationUnit = VirtualTextDocument(
SourceJavaFileObject.makeRelativeURI(file.toURI),
pc.Language.JAVA,
Expand Down Expand Up @@ -972,7 +1023,7 @@ class MbtWorkspaceSymbolProvider(
doc.semanticdbPackages.headOption match {
case Some(pkg) =>
val input = file.toInputFromBuffers(buffers)
val packageName = pkg.stripSuffix("/").replace("/", ".")
val packageName = normalizePackageName(pkg)
val compilationUnit = doc.toSemanticdbCompilationUnit(input)
turbineCompiler
.onDidChange(packageName, compilationUnit)
Expand Down Expand Up @@ -1072,6 +1123,10 @@ class MbtWorkspaceSymbolProvider(
newValue
}

private def normalizePackageName(packageName: String): String = {
packageName.stripSuffix("/").replace("/", ".")
}

// Reads .metals/index.mbt, which is a serialized Mbt.Index protobuf payload,
// into memory and converts it into TrieMap[AbsolutePath, IndexedDocument].
// For a very large repo (>100k Scala/Java files), this file still only takes
Expand Down
Loading
Loading