jps bazel compiler - simplify and improve "mark dirty" logic in kotlin jps builder in the light of what Baze and new storage provide (part 2)

GitOrigin-RevId: 28a2244b1ec90904e13873e18b5284f8eb3c623d
This commit is contained in:
Vladimir Krivosheev
2025-02-14 09:23:24 +01:00
committed by intellij-monorepo-bot
parent aa10ad2f33
commit c6638ddcf4
10 changed files with 265 additions and 166 deletions

View File

@@ -1 +1 @@
8.1.0rc3
8.1.0

View File

@@ -1 +1 @@
8.1.0rc3
8.1.0

View File

@@ -271,10 +271,7 @@ suspend fun buildUsingJps(
}
}
val buildState = if (isRebuild) null
else tracer.span("load and check state") { parentSpan ->
computeBuildState(parentSpan)
}
val buildState = if (isRebuild) null else tracer.span("load and check state") { parentSpan -> computeBuildState(parentSpan) }
var exitCode = initAndBuild(
compileScope = BazelCompileScope(isIncrementalCompilation = true, isRebuild = isRebuild),

View File

@@ -100,6 +100,12 @@ internal class BazelStampStorage(private val map: Map<Path, SourceDescriptor>) :
}
}
fun markChanged(sourceFile: Path) {
synchronized(map) {
map.get(sourceFile)?.isChanged = true
}
}
override fun getCurrentStampIfUpToDate(file: Path, buildTarget: BuildTarget<*>?, attrs: BasicFileAttributes?): ByteArray? {
throw UnsupportedOperationException("Must not be used")
}

View File

@@ -3,7 +3,6 @@
package org.jetbrains.bazel.jvm.jps.impl
import com.intellij.openapi.util.io.FileUtilRt
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
@@ -12,41 +11,50 @@ import org.jetbrains.bazel.jvm.linkedSet
import org.jetbrains.jps.ModuleChunk
import org.jetbrains.jps.incremental.CompileContext
import org.jetbrains.jps.incremental.FSOperations.addCompletelyMarkedDirtyTarget
import org.jetbrains.jps.incremental.fs.BuildFSState
import org.jetbrains.jps.incremental.fs.CompilationRound
import org.jetbrains.jps.incremental.fs.FilesDelta
import org.jetbrains.kotlin.jps.build.KotlinDirtySourceFilesHolder.TargetFiles
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.exists
internal class BazelKotlinFsOperationsHelper(
private val context: CompileContext,
private val chunk: ModuleChunk,
private val span: Span,
private val dataManager: BazelBuildDataProvider,
) {
internal var hasMarkedDirty = false
private set
fun markChunk(excludeFiles: Set<File>) {
fun markChunk(context: CompileContext, excludeFiles: Set<File>, dataManager: BazelBuildDataProvider) {
val target = chunk.targets.single()
var completelyMarkedDirty = true
val stampStorage = dataManager.getFileStampStorage(target)
for (rootDescriptor in (context.projectDescriptor.buildRootIndex as BazelBuildRootIndex).descriptors) {
val stampStorage = if (dataManager.isCleanBuild) null else dataManager.stampStorage
val projectDescriptor = context.projectDescriptor
for (rootDescriptor in (projectDescriptor.buildRootIndex as BazelBuildRootIndex).descriptors) {
val file = rootDescriptor.rootFile
val filePath = file.toString()
if (!(FileUtilRt.extensionEquals(filePath, "kt") || FileUtilRt.extensionEquals(filePath, "kts")) ||
excludeFiles.contains(file.toFile())) {
if ((!filePath.endsWith(".kt") && !filePath.endsWith(".kts")) || excludeFiles.contains(file.toFile())) {
completelyMarkedDirty = false
continue
}
hasMarkedDirty = true
// if it is a full project rebuild, all storages are already completely cleared;
// so passing null because there is no need to access the storage to clear non-existing data
val marker = if (dataManager.isCleanBuild) null else stampStorage
context.projectDescriptor.fsState.markDirty(context, CompilationRound.NEXT, file, rootDescriptor, marker, false)
val roundDelta = context.getUserData(BuildFSState.NEXT_ROUND_DELTA_KEY)
roundDelta?.markRecompile(rootDescriptor, file)
val filesDelta = projectDescriptor.fsState.getDelta(target)
filesDelta.lockData()
try {
val marked = filesDelta.markRecompile(rootDescriptor, file)
if (marked) {
stampStorage?.markChanged(file)
}
}
finally {
filesDelta.unlockData()
}
}
if (completelyMarkedDirty) {
@@ -54,16 +62,6 @@ internal class BazelKotlinFsOperationsHelper(
}
}
fun markFilesForCurrentRound(files: Sequence<Path>, targetDirtyFiles: TargetFiles?) {
val buildRootIndex = context.projectDescriptor.buildRootIndex as BazelBuildRootIndex
for (file in files) {
val root = buildRootIndex.fileToDescriptors.get(file) ?: continue
targetDirtyFiles?._markDirty(file, root)
}
markFilesImpl(files = files, currentRound = true, span = span) { it.exists() }
}
/**
* Marks given [files] as dirty for current round.
*/
@@ -72,61 +70,116 @@ internal class BazelKotlinFsOperationsHelper(
targetDirtyFiles: TargetFiles?,
outputSink: OutputSink,
parentSpan: Span,
target: BazelModuleBuildTarget,
dataManager: BazelBuildDataProvider,
) {
val buildRootIndex = context.projectDescriptor.buildRootIndex as BazelBuildRootIndex
val fileToDescriptors = (context.projectDescriptor.buildRootIndex as BazelBuildRootIndex).fileToDescriptors
for (file in files) {
targetDirtyFiles._markDirty(file, buildRootIndex.fileToDescriptors.get(file) ?: continue)
targetDirtyFiles._markDirty(file.toFile(), fileToDescriptors.get(file) ?: continue)
}
markFilesImpl(files.asSequence(), currentRound = true, span = span) { Files.exists(it) }
markFiles(
files = files.filterTo(linkedSet()) { Files.exists(it) },
currentRound = true,
dataManager = dataManager,
target = target,
span = parentSpan,
)
cleanOutputsCorrespondingToChangedFiles(files = files, dataManager = dataManager, outputSink = outputSink, parentSpan = parentSpan)
}
fun markFiles(files: Sequence<Path>) {
markFilesImpl(files = files, currentRound = false, span = span) { it.exists() }
}
fun markInChunkOrDependents(files: Sequence<Path>, excludeFiles: Set<Path>) {
markFilesImpl(files = files, currentRound = false, span = span) {
!excludeFiles.contains(it) && it.exists()
}
}
private inline fun markFilesImpl(
files: Sequence<Path>,
fun markFiles(
files: Collection<Path>,
currentRound: Boolean,
target: BazelModuleBuildTarget,
dataManager: BazelBuildDataProvider,
span: Span,
shouldMark: (Path) -> Boolean
) {
val filesToMark = files.filterTo(linkedSet(), shouldMark)
if (filesToMark.isEmpty()) {
if (files.isEmpty()) {
return
}
val roundDelta: FilesDelta?
val compilationRound = if (currentRound) {
roundDelta = context.getUserData(BuildFSState.CURRENT_ROUND_DELTA_KEY)
CompilationRound.CURRENT
}
else {
roundDelta = context.getUserData(BuildFSState.NEXT_ROUND_DELTA_KEY)
hasMarkedDirty = true
CompilationRound.NEXT
}
val projectDescriptor = context.projectDescriptor
val stampStorage = dataManager.stampStorage
val fileToDescriptors = (projectDescriptor.buildRootIndex as BazelBuildRootIndex).fileToDescriptors
for (fileToMark in filesToMark) {
val rootDescriptor = fileToDescriptors.get(fileToMark) ?: continue
projectDescriptor.fsState.markDirty(
/* context = */ context,
/* round = */ compilationRound,
/* file = */ fileToMark,
/* buildRootDescriptor = */ rootDescriptor,
/* stampStorage = */ projectDescriptor.dataManager.getFileStampStorage(rootDescriptor.target),
/* saveEventStamp = */ false,
)
val filesDelta = projectDescriptor.fsState.getDelta(target)
filesDelta.lockData()
try {
for (fileToMark in files) {
val rootDescriptor = fileToDescriptors.get(fileToMark) ?: continue
roundDelta?.markRecompile(rootDescriptor, fileToMark)
val marked = filesDelta.markRecompile(rootDescriptor, fileToMark)
if (marked) {
stampStorage.markChanged(fileToMark)
}
}
}
finally {
filesDelta.unlockData()
}
if (span.isRecording) {
span.addEvent("mark dirty", Attributes.of(
AttributeKey.stringArrayKey("filesToMark"), files.map { it.toString() },
AttributeKey.stringKey("compilationRound"), compilationRound.name,
))
}
}
}
internal fun markFilesForCurrentRound(
context: CompileContext,
files: Set<File>,
targetDirtyFiles: TargetFiles?,
span: Span,
target: BazelModuleBuildTarget,
dataManager: BazelBuildDataProvider,
) {
if (files.isEmpty()) {
return
}
val buildRootIndex = context.projectDescriptor.buildRootIndex as BazelBuildRootIndex
val fileToDescriptors = buildRootIndex.fileToDescriptors
for (file in files) {
val root = fileToDescriptors.get(file.toPath()) ?: continue
targetDirtyFiles?._markDirty(file, root)
}
val stampStorage = dataManager.stampStorage
val roundDelta = context.getUserData(BuildFSState.CURRENT_ROUND_DELTA_KEY)
val fileDelta = context.projectDescriptor.fsState.getDelta(target)
fileDelta.lockData()
try {
for (ioFile in files) {
val file = ioFile.toPath()
val rootDescriptor = fileToDescriptors.get(file) ?: continue
roundDelta?.markRecompile(rootDescriptor, file)
val marked = fileDelta.markRecompile(rootDescriptor, file)
if (marked) {
stampStorage.markChanged(file)
}
}
}
finally {
fileDelta.unlockData()
}
if (span.isRecording) {
span.addEvent("mark dirty", Attributes.of(
AttributeKey.stringArrayKey("filesToMark"), filesToMark.map { it.toString() },
AttributeKey.stringKey("compilationRound"), compilationRound.name,
AttributeKey.stringArrayKey("filesToMark"), files.map { it.toString() },
AttributeKey.stringKey("compilationRound"), "CURRENT",
))
}
}
}

View File

@@ -14,10 +14,9 @@ import org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer
import org.jetbrains.kotlin.backend.common.output.OutputFile
import java.io.File
import java.nio.file.Path
import java.util.*
internal class BazelTargetBuildOutputConsumer(
private val dataManager: BazelBuildDataProvider?,
@JvmField val dataManager: BazelBuildDataProvider?,
@JvmField val outputSink: OutputSink,
) : OutputConsumer {
private var registeredSourceCount = 0

View File

@@ -1,5 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("HardCodedStringLiteral", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "DialogTitleCapitalization", "UnstableApiUsage", "ReplaceGetOrSet")
@file:Suppress("HardCodedStringLiteral", "DialogTitleCapitalization", "UnstableApiUsage", "ReplaceGetOrSet", "PackageDirectoryMismatch")
package org.jetbrains.bazel.jvm.jps.kotlin
@@ -19,6 +19,7 @@ import org.jetbrains.bazel.jvm.jps.impl.BazelKotlinFsOperationsHelper
import org.jetbrains.bazel.jvm.jps.impl.BazelModuleBuildTarget
import org.jetbrains.bazel.jvm.jps.impl.BazelTargetBuildOutputConsumer
import org.jetbrains.bazel.jvm.jps.impl.BazelTargetBuilder
import org.jetbrains.bazel.jvm.jps.impl.markFilesForCurrentRound
import org.jetbrains.bazel.jvm.kotlin.configureModule
import org.jetbrains.bazel.jvm.kotlin.createJvmPipeline
import org.jetbrains.bazel.jvm.kotlin.executeJvmPipeline
@@ -34,7 +35,6 @@ import org.jetbrains.jps.incremental.BuilderCategory
import org.jetbrains.jps.incremental.CompileContext
import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.jps.incremental.ModuleLevelBuilder
import org.jetbrains.jps.incremental.RebuildRequestedException
import org.jetbrains.jps.incremental.Utils
import org.jetbrains.jps.model.module.JpsModule
import org.jetbrains.kotlin.backend.common.output.OutputFile
@@ -73,9 +73,7 @@ import org.jetbrains.kotlin.jps.build.KotlinChunk
import org.jetbrains.kotlin.jps.build.KotlinCompileContext
import org.jetbrains.kotlin.jps.build.KotlinDirtySourceFilesHolder
import org.jetbrains.kotlin.jps.build.KotlinDirtySourceFilesHolder.TargetFiles
import org.jetbrains.kotlin.jps.build.kotlin
import org.jetbrains.kotlin.jps.build.testingContext
import org.jetbrains.kotlin.jps.incremental.CacheStatus
import org.jetbrains.kotlin.jps.incremental.JpsIncrementalCache
import org.jetbrains.kotlin.jps.incremental.JpsLookupStorageManager
import org.jetbrains.kotlin.jps.targets.KotlinJvmModuleBuildTarget
@@ -87,7 +85,6 @@ import org.jetbrains.kotlin.metadata.deserialization.MetadataVersion
import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.preloading.ClassCondition
import org.jetbrains.kotlin.progress.CompilationCanceledException
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import java.io.File
import java.nio.file.Files
@@ -115,24 +112,19 @@ internal class IncrementalKotlinBuilder(
override fun chunkBuildStarted(context: CompileContext, chunk: ModuleChunk) {
val kotlinContext = ensureKotlinContextInitialized(context, span)
if (isRebuild) {
return
}
if (chunk.targets.none { kotlinContext.hasKotlinMarker.get(it) == true }) {
if (isRebuild || kotlinContext.hasKotlinMarker.get(chunk.targets.single()) != true) {
return
}
val kotlinChunk = kotlinContext.getChunk(chunk) ?: return
if (!kotlinContext.rebuildingAllKotlin) {
val target = kotlinChunk.targets.single()
if (target.initialLocalCacheAttributesDiff.status == CacheStatus.INVALID) {
throw RebuildRequestedException(RuntimeException("cache is invalid, rebuilding (diff=${target.initialLocalCacheAttributesDiff}"))
}
if (kotlinChunk.isEnabled) {
markAdditionalFilesForInitialRound(kotlinChunk, chunk, kotlinContext, jpsTarget)
}
if (kotlinChunk.isEnabled) {
markAdditionalFilesForInitialRound(
kotlinChunk = kotlinChunk,
chunk = chunk,
kotlinContext = kotlinContext,
moduleTarget = jpsTarget,
span = span,
)
}
}
@@ -141,6 +133,7 @@ internal class IncrementalKotlinBuilder(
chunk: ModuleChunk,
kotlinContext: KotlinCompileContext,
moduleTarget: BazelModuleBuildTarget,
span: Span,
) {
val context = kotlinContext.jpsContext
val dirtyFilesHolder = KotlinDirtySourceFilesHolder(
@@ -150,7 +143,7 @@ internal class IncrementalKotlinBuilder(
override fun processDirtyFiles(processor: FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>) {
context.projectDescriptor.fsState.processFilesToRecompile(context, chunk.targets.single(), processor)
}
}
},
)
val removedClasses = hashSet<String>()
@@ -183,10 +176,13 @@ internal class IncrementalKotlinBuilder(
reporter = BazelJpsICReporter(span),
)
val fsOperations = BazelKotlinFsOperationsHelper(context = context, chunk = chunk, span = span, dataManager = dataManager)
fsOperations.markFilesForCurrentRound(
files = affectedByRemovedClasses.dirtyFiles.asSequence().map { it.toPath() } + affectedByRemovedClasses.forceRecompileTogether.asSequence().map { it.toPath() },
markFilesForCurrentRound(
files = affectedByRemovedClasses.dirtyFiles.concat(affectedByRemovedClasses.forceRecompileTogether),
targetDirtyFiles = targetDirtyFiles,
span = span,
context = context,
target = moduleTarget,
dataManager = dataManager,
)
}
@@ -199,30 +195,26 @@ internal class IncrementalKotlinBuilder(
outputConsumer: BazelTargetBuildOutputConsumer,
outputSink: OutputSink
): ModuleLevelBuilder.ExitCode {
val kotlinTarget = context.kotlin.targetsBinding.get(chunk.representativeTarget()) ?: return ModuleLevelBuilder.ExitCode.OK
val fsOperations = BazelKotlinFsOperationsHelper(
context = context,
val kotlinContext = getKotlinCompileContext(context)
val kotlinTarget = kotlinContext.targetsIndex.byJpsTarget.get(jpsTarget) ?: return ModuleLevelBuilder.ExitCode.OK
val fsOperations = BazelKotlinFsOperationsHelper(context = context, chunk = chunk)
val proposedExitCode = doBuild(
chunk = chunk,
dataManager = dataManager,
span = span,
representativeTarget = kotlinTarget,
context = context,
dirtyFilesHolder = dirtyFilesHolder,
messageCollector = MessageCollectorAdapter(context, span, kotlinTarget),
outputConsumer = outputConsumer,
fsOperations = fsOperations,
kotlinContext = kotlinContext,
)
val proposedExitCode = try {
doBuild(
chunk = chunk,
representativeTarget = kotlinTarget,
context = context,
dirtyFilesHolder = dirtyFilesHolder,
messageCollector = MessageCollectorAdapter(context, span, kotlinTarget),
outputConsumer = outputConsumer,
fsOperations = fsOperations,
)
}
catch (e: CompilationCanceledException) {
// https://youtrack.jetbrains.com/issue/KTI-2139
throw CancellationException(e)
}
val actualExitCode = if (proposedExitCode == ModuleLevelBuilder.ExitCode.OK && fsOperations.hasMarkedDirty) ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED else proposedExitCode
val actualExitCode = if (proposedExitCode == ModuleLevelBuilder.ExitCode.OK && fsOperations.hasMarkedDirty) {
ModuleLevelBuilder.ExitCode.ADDITIONAL_PASS_REQUIRED
}
else {
proposedExitCode
}
context.testingContext?.buildLogger?.buildFinished(actualExitCode)
return actualExitCode
}
@@ -235,8 +227,8 @@ internal class IncrementalKotlinBuilder(
messageCollector: MessageCollectorAdapter,
outputConsumer: BazelTargetBuildOutputConsumer,
fsOperations: BazelKotlinFsOperationsHelper,
kotlinContext: KotlinCompileContext,
): ExitCode {
val kotlinContext = context.kotlin
val kotlinChunk = kotlinContext.getChunk(chunk)!!
if (!kotlinChunk.isEnabled) {
return ModuleLevelBuilder.ExitCode.NOTHING_DONE
@@ -247,7 +239,7 @@ internal class IncrementalKotlinBuilder(
val isChunkRebuilding = isRebuild || kotlinContext.rebuildAfterCacheVersionChanged.get(target) == true
val kotlinDirtyFilesHolder = KotlinDirtySourceFilesHolder(chunk, context, dirtyFilesHolder)
val dirtyByTarget = kotlinDirtyFilesHolder.byTarget.get(dirtyFilesHolder.target)
val dirtyByTarget = kotlinDirtyFilesHolder.byTarget.get(jpsTarget)
if (dirtyByTarget == null || (dirtyByTarget.removed.isEmpty() && dirtyByTarget.dirty.isEmpty())) {
if (isChunkRebuilding) {
kotlinContext.hasKotlinMarker.set(target, false)
@@ -280,7 +272,6 @@ internal class IncrementalKotlinBuilder(
val generatedFiles = doCompileModuleChunk(
chunk = kotlinChunk,
outputItemCollector = outputItemCollector,
representativeTarget = representativeTarget,
context = context,
targetDirtyFiles = dirtyByTarget,
fsOperations = fsOperations,
@@ -304,17 +295,22 @@ internal class IncrementalKotlinBuilder(
JavaBuilderUtil.registerSuccessfullyCompiled(context, dirtyByTarget.dirty.keys.toList())
}
markDirtyComplementaryMultifileClasses(
files = generatedFiles,
kotlinModuleBuilderTarget = representativeTarget,
incrementalCaches = incrementalCaches,
fsOperations = fsOperations,
)
val cache = incrementalCaches.get(representativeTarget)
if (cache is IncrementalJvmCache) {
markDirtyComplementaryMultifileClasses(
files = generatedFiles,
cache = cache,
fsOperations = fsOperations,
target = jpsTarget,
dataManager = dataManager,
span = span,
)
}
// we do not save cache version - TargetConfigurationDigestProperty.KOTLIN_VERSION is used to rebuild in case of kotlinc update
if (kotlinContext.hasKotlinMarker.get(target) == null) {
fsOperations.markChunk(excludeFiles = dirtyByTarget.dirty.keys)
fsOperations.markChunk(context = context, excludeFiles = dirtyByTarget.dirty.keys, dataManager = dataManager)
}
kotlinContext.hasKotlinMarker.set(target, true)
@@ -363,11 +359,14 @@ internal class IncrementalKotlinBuilder(
if (!isChunkRebuilding) {
doProcessChangesUsingLookups(
collector = changeCollector,
compiledFiles = dirtyByTarget.dirty.keys.mapTo(linkedSet()) { it.toPath() },
compiledFiles = dirtyByTarget.dirty.keys.mapTo(hashSet()) { it.toPath() },
lookupStorageManager = kotlinContext.lookupStorageManager,
fsOperations = fsOperations,
caches = incrementalCaches.values,
reporter = BazelJpsICReporter(span),
target = jpsTarget,
dataManager = dataManager,
span = span,
)
}
@@ -379,7 +378,6 @@ internal class IncrementalKotlinBuilder(
// todo(1.2.80): introduce KotlinRoundCompileContext, move dirtyFilesHolder, fsOperations, environment to it
private fun doCompileModuleChunk(
chunk: KotlinChunk,
representativeTarget: KotlinModuleBuildTarget<*>,
context: CompileContext,
targetDirtyFiles: TargetFiles?,
fsOperations: BazelKotlinFsOperationsHelper,
@@ -388,14 +386,13 @@ private fun doCompileModuleChunk(
messageCollector: MessageCollectorAdapter,
outputConsumer: BazelTargetBuildOutputConsumer,
outputItemCollector: OutputItemsCollectorImpl,
span: Span,
): List<GeneratedFile>? {
val target = chunk.targets.single() as KotlinJvmModuleBuildTarget
target.nextRound(context)
val cache = incrementalCaches.get(target)
val jpsTarget = target.jpsModuleBuildTarget
val jpsTarget = target.jpsModuleBuildTarget as BazelModuleBuildTarget
if (cache != null && targetDirtyFiles != null) {
val dirtyFiles = targetDirtyFiles.dirty.keys.concat(targetDirtyFiles.removed)
@@ -406,6 +403,8 @@ private fun doCompileModuleChunk(
targetDirtyFiles = targetDirtyFiles,
parentSpan = span,
outputSink = outputConsumer.outputSink,
dataManager = outputConsumer.dataManager!!,
target = jpsTarget,
)
cache.markDirty(dirtyFiles)
@@ -419,7 +418,6 @@ private fun doCompileModuleChunk(
registerFilesToCompile(context, allDirtyFiles)
}
require(chunk.representativeTarget == representativeTarget)
val filesSet = targetDirtyFiles?.dirty?.keys ?: emptySet()
val changedSources = targetDirtyFiles?.dirty?.values ?: emptyList()
val removedFiles = targetDirtyFiles?.removed ?: emptyList()
@@ -546,27 +544,41 @@ private fun updateLookupStorage(
private fun markDirtyComplementaryMultifileClasses(
files: List<GeneratedFile>,
incrementalCaches: Map<KotlinModuleBuildTarget<*>, JpsIncrementalCache>,
cache: IncrementalJvmCache,
fsOperations: BazelKotlinFsOperationsHelper,
kotlinModuleBuilderTarget: KotlinModuleBuildTarget<*>,
target: BazelModuleBuildTarget,
dataManager: BazelBuildDataProvider,
span: Span,
) {
val cache = incrementalCaches.get(kotlinModuleBuilderTarget) as? IncrementalJvmCache ?: return
val generated = files.filterIsInstance<GeneratedJvmClass>()
val multifileClasses = generated.filter { it.outputClass.classHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS }
val expectedAllParts = multifileClasses.flatMap { cache.getAllPartsOfMultifileFacade(it.outputClass.className).orEmpty() }
val multifileClasses = files
.asSequence()
.filterIsInstance<GeneratedJvmClass>()
.filter { it.outputClass.classHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS }
.toList()
if (multifileClasses.isEmpty()) {
return
}
val actualParts = generated
val actualParts = files
.asSequence()
.filterIsInstance<GeneratedJvmClass>()
.filter { it.outputClass.classHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART }
.map { it.outputClass.className.toString() }
.toList()
val expectedAllParts = multifileClasses.flatMap { cache.getAllPartsOfMultifileFacade(it.outputClass.className).orEmpty() }
if (!actualParts.containsAll(expectedAllParts)) {
fsOperations.markFiles(
expectedAllParts.asSequence().flatMap { cache.sourcesByInternalName(it) }.map { it.toPath() }
+ multifileClasses.asSequence().flatMap { it.sourceFiles }.map { it.toPath() }
files = (
expectedAllParts.asSequence().flatMap { cache.sourcesByInternalName(it) }
+ multifileClasses.asSequence().flatMap { it.sourceFiles }
)
.map { it.toPath() }
.filterTo(linkedSet()) { Files.exists(it) },
currentRound = false,
target = target,
dataManager = dataManager,
span = span,
)
}
}
@@ -587,26 +599,38 @@ private fun doProcessChangesUsingLookups(
fsOperations: BazelKotlinFsOperationsHelper,
caches: Iterable<JpsIncrementalCache>,
reporter: ICReporter,
target: BazelModuleBuildTarget,
dataManager: BazelBuildDataProvider,
span: Span,
) {
val allCaches = caches.flatMap { it.thisWithDependentCaches }
val dirtyFiles = getDirtyFiles(
changeCollector = collector,
caches = allCaches,
caches = caches.flatMap { it.thisWithDependentCaches },
lookupStorageManager = lookupStorageManager,
reporter = reporter,
)
// if a list of inheritors of sealed class has changed it should be recompiled with all the inheritors
// If a list of inheritors of sealed class has changed, it should be recompiled with all the inheritors
// Here we have a small optimization. Do not recompile the bunch if ALL these files were recompiled during the previous round.
val excludeFiles = if (compiledFiles.containsAll(dirtyFiles.forceRecompileTogether.map { it.toPath() })) {
val forceRecompileTogether = dirtyFiles.forceRecompileTogether
val excludeFiles = if (forceRecompileTogether.isEmpty() || compiledFiles.containsAll(forceRecompileTogether.map { it.toPath() })) {
compiledFiles
}
else {
compiledFiles.minus(dirtyFiles.forceRecompileTogether.map { it.toPath() })
val result = HashSet(compiledFiles)
for (file in forceRecompileTogether) {
result.remove(file.toPath())
}
result
}
fsOperations.markInChunkOrDependents(
files = (dirtyFiles.dirtyFiles.asSequence().map { it.toPath() } + dirtyFiles.forceRecompileTogether.asSequence().map { it.toPath() }),
excludeFiles = excludeFiles,
fsOperations.markFiles(
files = (dirtyFiles.dirtyFiles.asSequence() + forceRecompileTogether.asSequence())
.map { it.toPath() }
.filterTo(linkedSet()) { !excludeFiles.contains(it) && Files.exists(it) },
currentRound = false,
dataManager = dataManager,
target = target,
span = span,
)
}
@@ -624,6 +648,6 @@ private fun getDirtyFiles(
}
return FilesToRecompile(
dirtyFiles = dirtyFilesFromLookups + mapClassesFqNamesToFiles(caches, dirtyClassFqNames, reporter),
forceRecompileTogether = mapClassesFqNamesToFiles(caches, forceRecompile, reporter)
forceRecompileTogether = mapClassesFqNamesToFiles(caches, forceRecompile, reporter),
)
}

View File

@@ -0,0 +1,33 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("unused", "PackageDirectoryMismatch", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "ReplaceJavaStaticMethodWithKotlinAnalog")
package org.jetbrains.kotlin.jps.targets
import org.jetbrains.bazel.jvm.jps.impl.BazelBuildTargetIndex
import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.kotlin.jps.build.KotlinChunk
import org.jetbrains.kotlin.jps.build.KotlinCompileContext
class KotlinTargetsIndex(
val byJpsTarget: Map<ModuleBuildTarget, KotlinModuleBuildTarget<*>>,
val chunks: List<KotlinChunk>,
val chunksByJpsRepresentativeTarget: Map<ModuleBuildTarget, KotlinChunk>
)
internal class KotlinTargetsIndexBuilder internal constructor(
private val uninitializedContext: KotlinCompileContext
) {
fun build(): KotlinTargetsIndex {
val target = (uninitializedContext.jpsContext.projectDescriptor.buildTargetIndex as BazelBuildTargetIndex).moduleTarget
val moduleBuildTarget = KotlinJvmModuleBuildTarget(uninitializedContext, target)
val kotlinChunk = KotlinChunk(uninitializedContext, java.util.List.of(moduleBuildTarget))
moduleBuildTarget.chunk = kotlinChunk
val chunks = listOf(kotlinChunk)
KotlinChunk.calculateChunkDependencies(chunks, java.util.Map.of<ModuleBuildTarget, KotlinModuleBuildTarget<*>>() as MutableMap<ModuleBuildTarget, KotlinModuleBuildTarget<*>>)
return KotlinTargetsIndex(
byJpsTarget = java.util.Map.of(target, moduleBuildTarget),
chunks = chunks,
chunksByJpsRepresentativeTarget = java.util.Map.of(target, kotlinChunk)
)
}
}

View File

@@ -10,54 +10,41 @@ import org.jetbrains.kotlin.jps.build.kotlinCompileContextKey
import org.jetbrains.kotlin.jps.build.testingContext
import kotlin.system.measureTimeMillis
/**
* Ensure Kotlin Context initialized.
* Kotlin Context should be initialized only when required (before the first kotlin chunk build).
*/
internal fun getKotlinCompileContext(context: CompileContext): KotlinCompileContext = context.getUserData(kotlinCompileContextKey)!!
internal fun ensureKotlinContextInitialized(context: CompileContext, span: Span): KotlinCompileContext {
context.getUserData(kotlinCompileContextKey)?.let {
return it
}
// don't synchronize on context, since it is chunk local only
synchronized(kotlinCompileContextKey) {
context.getUserData(kotlinCompileContextKey)?.let {
return it
}
return initializeKotlinContext(context, span)
synchronized(context) {
return context.getUserData(kotlinCompileContextKey) ?: initializeKotlinContext(context, span)
}
}
internal fun ensureKotlinContextDisposed(context: CompileContext) {
if (context.getUserData(kotlinCompileContextKey) != null) {
synchronized(kotlinCompileContextKey) {
val kotlinCompileContext = context.getUserData(kotlinCompileContextKey)
if (kotlinCompileContext != null) {
kotlinCompileContext.dispose()
context.putUserData(kotlinCompileContextKey, null)
}
}
if (context.getUserData(kotlinCompileContextKey) == null) {
return
}
synchronized(context) {
val kotlinCompileContext = context.getUserData(kotlinCompileContextKey) ?: return
kotlinCompileContext.dispose()
context.putUserData(kotlinCompileContextKey, null)
}
}
private fun initializeKotlinContext(context: CompileContext, span: Span): KotlinCompileContext {
lateinit var kotlinContext: KotlinCompileContext
val time = measureTimeMillis {
kotlinContext = KotlinCompileContext(context)
context.putUserData(kotlinCompileContextKey, kotlinContext)
context.testingContext?.kotlinCompileContext = kotlinContext
if (kotlinContext.shouldCheckCacheVersions && kotlinContext.hasKotlin()) {
kotlinContext.checkCacheVersions()
}
kotlinContext.cleanupCaches()
kotlinContext.reportUnsupportedTargets()
}
span.addEvent("total Kotlin global compile context initialization time: $time ms")
return kotlinContext
}

View File

@@ -1 +1 @@
8.1.0rc3
8.1.0