KT-45777: Allow 2 levels of granularity when tracking changes
1. CLASS_LEVEL: allows tracking whether a .class file has changed
without tracking what specific parts of the .class file (e.g.,
fields or methods) have changed.
2. CLASS_MEMBER_LEVEL: allows tracking not only whether a .class file
has changed but also what specific parts of the .class file (e.g.,
fields or methods) have changed.
The idea is that for better performance we will use CLASS_LEVEL for
classpath entries that are usually unchanged, and CLASS_MEMBER_LEVEL
for classpath entries that are frequently changed. We'll work out the
specifics in a following commit after some measurements.
Support running kotlinc on Windows in ClasspathSnapshotTestCommon
Also add tests for different Kotlin class kinds.
Add unit tests for CLASS_LEVEL snapshotting and diffing
Test: Updated ClasspathSnapshotterTest + ClasspathChangesComputerTest
Add ClasspathChangesComputerTest.testMixedClassSnapshotGranularities
This commit is contained in:
committed by
nataliya.valtman
parent
05457c291d
commit
d2193f3873
Generated
+2
@@ -1,6 +1,8 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="hungnv">
|
||||
<words>
|
||||
<w>externalizers</w>
|
||||
<w>granularities</w>
|
||||
<w>multifile</w>
|
||||
<w>shrinker</w>
|
||||
<w>snapshotter</w>
|
||||
|
||||
@@ -35,33 +35,33 @@ fun File.isClassFile(): Boolean =
|
||||
*/
|
||||
fun File.cleanDirectoryContents() {
|
||||
when {
|
||||
isDirectory -> listFiles()!!.forEach { it.forceDeleteRecursively() }
|
||||
isDirectory -> listFiles()!!.forEach { it.deleteRecursivelyOrThrow() }
|
||||
isFile -> error("File.cleanDirectoryContents does not accept a regular file: $path")
|
||||
else -> forceMkdirs()
|
||||
else -> mkdirsOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes this file or directory recursively (if it exists). */
|
||||
fun File.forceDeleteRecursively() {
|
||||
/** Deletes this file or directory recursively (if it exists), throwing an exception if the deletion failed. */
|
||||
fun File.deleteRecursivelyOrThrow() {
|
||||
if (!deleteRecursively()) {
|
||||
throw IOException("Could not delete '$path'")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates this directory (if it does not yet exist).
|
||||
*
|
||||
* If this is a regular file, this method will throw an exception.
|
||||
* Creates this directory (if it does not yet exist), throwing an exception if the directiory creation failed or if a regular file already
|
||||
* exists at this path.
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun File.forceMkdirs() {
|
||||
fun File.mkdirsOrThrow() {
|
||||
when {
|
||||
this.isDirectory -> { /* Do nothing */ }
|
||||
this.isFile -> error("File.forceMkdirs does not accept a regular file: $path")
|
||||
isDirectory -> Unit
|
||||
isFile -> error("A regular file already exists at this path: $path")
|
||||
else -> {
|
||||
// Note that if the directory already exists, mkdirs() will return `false`, but here we ensure that the directory does not exist
|
||||
// before calling mkdirs(), so it's safe to check the returned result of mkdirs() below.
|
||||
if (!mkdirs()) {
|
||||
// Note that `mkdirs()` returns `false` if the directory already exists (even though we have checked that the directory did not
|
||||
// exist earlier, it might just have been created by some other thread). Therefore, we need to check if the directory exists
|
||||
// again when `mkdirs()` returns `false`.
|
||||
if (!mkdirs() && !isDirectory) {
|
||||
throw IOException("Could not create directory '$path'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ object ByteArrayExternalizer : DataExternalizer<ByteArray> {
|
||||
}
|
||||
}
|
||||
|
||||
open class GenericCollectionExternalizer<T, C : Collection<T>>(
|
||||
abstract class GenericCollectionExternalizer<T, C : Collection<T>>(
|
||||
private val elementExternalizer: DataExternalizer<T>,
|
||||
private val newCollection: (size: Int) -> MutableCollection<T>
|
||||
) : DataExternalizer<C> {
|
||||
@@ -364,6 +364,9 @@ open class GenericCollectionExternalizer<T, C : Collection<T>>(
|
||||
repeat(size) {
|
||||
collection.add(elementExternalizer.read(input))
|
||||
}
|
||||
// We want `collection` to be both a mutable collection (so we can add elements to it as done above) and a type that can be safely
|
||||
// converted to type `C` (to be used as the returned value of this method). However, there is no type-safe way to express that, so
|
||||
// we have to use this unsafe cast.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return collection as C
|
||||
}
|
||||
@@ -375,12 +378,13 @@ class ListExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
||||
class SetExternalizer<T>(elementExternalizer: DataExternalizer<T>) :
|
||||
GenericCollectionExternalizer<T, Set<T>>(elementExternalizer, { size -> LinkedHashSet(size) })
|
||||
|
||||
class LinkedHashMapExternalizer<K, V>(
|
||||
open class MapExternalizer<K, V, M : Map<K, V>>(
|
||||
private val keyExternalizer: DataExternalizer<K>,
|
||||
private val valueExternalizer: DataExternalizer<V>
|
||||
) : DataExternalizer<LinkedHashMap<K, V>> {
|
||||
private val valueExternalizer: DataExternalizer<V>,
|
||||
private val newMap: (size: Int) -> MutableMap<K, V> = { size -> LinkedHashMap(size) }
|
||||
) : DataExternalizer<M> {
|
||||
|
||||
override fun save(output: DataOutput, map: LinkedHashMap<K, V>) {
|
||||
override fun save(output: DataOutput, map: M) {
|
||||
output.writeInt(map.size)
|
||||
for ((key, value) in map) {
|
||||
keyExternalizer.save(output, key)
|
||||
@@ -388,14 +392,20 @@ class LinkedHashMapExternalizer<K, V>(
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): LinkedHashMap<K, V> {
|
||||
override fun read(input: DataInput): M {
|
||||
val size = input.readInt()
|
||||
val map = LinkedHashMap<K, V>(size)
|
||||
val map = newMap(size)
|
||||
repeat(size) {
|
||||
val key = keyExternalizer.read(input)
|
||||
val value = valueExternalizer.read(input)
|
||||
map[key] = value
|
||||
}
|
||||
return map
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return map as M
|
||||
}
|
||||
}
|
||||
|
||||
class LinkedHashMapExternalizer<K, V>(
|
||||
keyExternalizer: DataExternalizer<K>,
|
||||
valueExternalizer: DataExternalizer<V>
|
||||
) : MapExternalizer<K, V, LinkedHashMap<K, V>>(keyExternalizer, valueExternalizer, { size -> LinkedHashMap(size) })
|
||||
|
||||
+1
-1
@@ -216,7 +216,7 @@ abstract class IncrementalCompilerRunner<
|
||||
}
|
||||
it.isFile -> {
|
||||
reporter.reportVerbose { " Deleting file '${it.path}'" }
|
||||
it.forceDeleteRecursively()
|
||||
it.deleteRecursivelyOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-4
@@ -52,6 +52,8 @@ import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnable
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.NotAvailableForJSCompiler
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.*
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathChangesComputer.computeChangedAndImpactedSet
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrinkClasspath
|
||||
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
|
||||
@@ -219,8 +221,8 @@ class IncrementalJvmCompilerRunner(
|
||||
|
||||
// Used by `calculateSourcesToCompileImpl` and `performWorkAfterSuccessfulCompilation` methods below.
|
||||
// Thread safety: There is no concurrent access to these variables.
|
||||
private var currentClasspathSnapshot: List<ClassSnapshotWithHash>? = null
|
||||
private var shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>? = null
|
||||
private var currentClasspathSnapshot: List<AccessibleClassSnapshot>? = null
|
||||
private var shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>? = null
|
||||
|
||||
private fun calculateSourcesToCompileImpl(
|
||||
caches: IncrementalJvmCachesManager,
|
||||
@@ -245,9 +247,15 @@ class IncrementalJvmCompilerRunner(
|
||||
}
|
||||
check(shrunkCurrentClasspathAgainstPreviousLookups == null)
|
||||
shrunkCurrentClasspathAgainstPreviousLookups = reporter.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT) {
|
||||
ClasspathSnapshotShrinker.shrink(currentClasspathSnapshot!!, caches.lookupCache, reporter)
|
||||
shrinkClasspath(
|
||||
currentClasspathSnapshot!!, caches.lookupCache,
|
||||
ClasspathSnapshotShrinker.MetricsReporter(
|
||||
reporter,
|
||||
BuildTime.GET_LOOKUP_SYMBOLS, BuildTime.FIND_REFERENCED_CLASSES, BuildTime.FIND_TRANSITIVELY_REFERENCED_CLASSES
|
||||
)
|
||||
)
|
||||
}
|
||||
ClasspathChangesComputer.computeChangedAndImpactedSet(
|
||||
computeChangedAndImpactedSet(
|
||||
shrunkCurrentClasspathAgainstPreviousLookups!!,
|
||||
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
||||
reporter
|
||||
|
||||
+6
@@ -49,6 +49,12 @@ class ChangeSet(
|
||||
|
||||
fun addChangedClassMember(className: ClassId, memberName: String) = addChangedClassMembers(className, listOf(memberName))
|
||||
|
||||
fun addChangedTopLevelMembers(packageMemberSet: PackageMemberSet) {
|
||||
packageMemberSet.packageToMembersMap.forEach { (packageName, memberNames) ->
|
||||
changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(memberNames)
|
||||
}
|
||||
}
|
||||
|
||||
fun addChangedTopLevelMembers(packageName: FqName, topLevelMembers: Collection<String>) {
|
||||
if (topLevelMembers.isNotEmpty()) {
|
||||
changedTopLevelMembers.computeIfAbsent(packageName) { mutableSetOf() }.addAll(topLevelMembers)
|
||||
|
||||
+83
-56
@@ -30,12 +30,12 @@ object ClasspathChangesComputer {
|
||||
* NOTE: The original classpath may contain duplicate classes, but the shrunk classpath must not contain duplicate classes.
|
||||
*/
|
||||
fun computeChangedAndImpactedSet(
|
||||
shrunkCurrentClasspathSnapshot: List<ClassSnapshotWithHash>,
|
||||
shrunkCurrentClasspathSnapshot: List<AccessibleClassSnapshot>,
|
||||
shrunkPreviousClasspathSnapshotFile: File,
|
||||
metrics: BuildMetricsReporter
|
||||
): ChangeSet {
|
||||
val shrunkPreviousClasspathSnapshot = metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT) {
|
||||
ListExternalizer(ClassSnapshotWithHashExternalizer).loadFromFile(shrunkPreviousClasspathSnapshotFile)
|
||||
ListExternalizer(AccessibleClassSnapshotExternalizer).loadFromFile(shrunkPreviousClasspathSnapshotFile)
|
||||
}
|
||||
return metrics.measure(BuildTime.COMPUTE_CHANGED_AND_IMPACTED_SET) {
|
||||
computeChangedAndImpactedSet(shrunkCurrentClasspathSnapshot, shrunkPreviousClasspathSnapshot, metrics)
|
||||
@@ -48,22 +48,26 @@ object ClasspathChangesComputer {
|
||||
* NOTE: Each list of classes must not contain duplicates.
|
||||
*/
|
||||
fun computeChangedAndImpactedSet(
|
||||
currentClassSnapshots: List<ClassSnapshotWithHash>,
|
||||
previousClassSnapshots: List<ClassSnapshotWithHash>,
|
||||
currentClassSnapshots: List<AccessibleClassSnapshot>,
|
||||
previousClassSnapshots: List<AccessibleClassSnapshot>,
|
||||
metrics: BuildMetricsReporter
|
||||
): ChangeSet {
|
||||
val currentClasses: Map<ClassId, ClassSnapshotWithHash> = currentClassSnapshots.associateBy { it.classSnapshot.getClassId() }
|
||||
val previousClasses: Map<ClassId, ClassSnapshotWithHash> = previousClassSnapshots.associateBy { it.classSnapshot.getClassId() }
|
||||
val currentClasses: Map<ClassId, AccessibleClassSnapshot> = currentClassSnapshots.associateBy { it.classId }
|
||||
val previousClasses: Map<ClassId, AccessibleClassSnapshot> = previousClassSnapshots.associateBy { it.classId }
|
||||
|
||||
val changedCurrentClasses: List<ClassSnapshot> = currentClasses.filter { (classId, currentClass) ->
|
||||
val changedCurrentClasses: List<AccessibleClassSnapshot> = currentClasses.mapNotNull { (classId, currentClass) ->
|
||||
val previousClass = previousClasses[classId]
|
||||
previousClass == null || currentClass.hash != previousClass.hash
|
||||
}.map { it.value.classSnapshot }
|
||||
if (previousClass == null || currentClass.classAbiHash != previousClass.classAbiHash) {
|
||||
currentClass
|
||||
} else null
|
||||
}
|
||||
|
||||
val changedPreviousClasses: List<ClassSnapshot> = previousClasses.filter { (classId, previousClass) ->
|
||||
val changedPreviousClasses: List<AccessibleClassSnapshot> = previousClasses.mapNotNull { (classId, previousClass) ->
|
||||
val currentClass = currentClasses[classId]
|
||||
currentClass == null || currentClass.hash != previousClass.hash
|
||||
}.map { it.value.classSnapshot }
|
||||
if (currentClass == null || currentClass.classAbiHash != previousClass.classAbiHash) {
|
||||
previousClass
|
||||
} else null
|
||||
}
|
||||
|
||||
val classChanges = metrics.measure(BuildTime.COMPUTE_CLASS_CHANGES) {
|
||||
computeClassChanges(changedCurrentClasses, changedPreviousClasses, metrics)
|
||||
@@ -74,7 +78,7 @@ object ClasspathChangesComputer {
|
||||
}
|
||||
|
||||
return metrics.measure(BuildTime.COMPUTE_IMPACTED_SET) {
|
||||
computeImpactedSet(classChanges, previousClasses.map { it.value.classSnapshot })
|
||||
computeImpactedSet(classChanges, previousClassSnapshots)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,20 +88,13 @@ object ClasspathChangesComputer {
|
||||
*
|
||||
* NOTE: Each list of classes must not contain duplicates.
|
||||
*/
|
||||
fun computeClassChanges(
|
||||
currentClassSnapshots: List<ClassSnapshot>,
|
||||
previousClassSnapshots: List<ClassSnapshot>,
|
||||
private fun computeClassChanges(
|
||||
currentClassSnapshots: List<AccessibleClassSnapshot>,
|
||||
previousClassSnapshots: List<AccessibleClassSnapshot>,
|
||||
metrics: BuildMetricsReporter
|
||||
): ChangeSet {
|
||||
val isKotlinClass: (ClassSnapshot) -> Boolean = {
|
||||
when (it) {
|
||||
is KotlinClassSnapshot -> true
|
||||
is JavaClassSnapshot -> false
|
||||
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${it.javaClass.name}")
|
||||
}
|
||||
}
|
||||
val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition(isKotlinClass)
|
||||
val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition(isKotlinClass)
|
||||
val (currentKotlinClassSnapshots, currentJavaClassSnapshots) = currentClassSnapshots.partition { it is KotlinClassSnapshot }
|
||||
val (previousKotlinClassSnapshots, previousJavaClassSnapshots) = previousClassSnapshots.partition { it is KotlinClassSnapshot }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val kotlinClassChanges = metrics.measure(BuildTime.COMPUTE_KOTLIN_CLASS_CHANGES) {
|
||||
@@ -121,6 +118,36 @@ object ClasspathChangesComputer {
|
||||
private fun computeKotlinClassChanges(
|
||||
currentClassSnapshots: List<KotlinClassSnapshot>,
|
||||
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||
): ChangeSet {
|
||||
val (coarseGrainedCurrentClassSnapshots, fineGrainedCurrentClassSnapshots) =
|
||||
currentClassSnapshots.partition { it.classMemberLevelSnapshot == null }
|
||||
val (coarseGrainedPreviousClassSnapshots, fineGrainedPreviousClassSnapshots) =
|
||||
previousClassSnapshots.partition { it.classMemberLevelSnapshot == null }
|
||||
|
||||
return computeCoarseGrainedKotlinClassChanges(coarseGrainedCurrentClassSnapshots, coarseGrainedPreviousClassSnapshots) +
|
||||
computeFineGrainedKotlinClassChanges(fineGrainedCurrentClassSnapshots, fineGrainedPreviousClassSnapshots)
|
||||
}
|
||||
|
||||
private fun computeCoarseGrainedKotlinClassChanges(
|
||||
currentClassSnapshots: List<KotlinClassSnapshot>,
|
||||
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||
): ChangeSet {
|
||||
// Note: We have removed unchanged classes earlier in computeChangedAndImpactedSet method, so here we only have changed classes.
|
||||
return ChangeSet.Collector().run {
|
||||
(currentClassSnapshots + previousClassSnapshots).forEach {
|
||||
when (it) {
|
||||
is RegularKotlinClassSnapshot -> addChangedClass(it.classId)
|
||||
is PackageFacadeKotlinClassSnapshot -> addChangedTopLevelMembers(it.packageMembers)
|
||||
is MultifileClassKotlinClassSnapshot -> addChangedTopLevelMembers(it.constants)
|
||||
}
|
||||
}
|
||||
getChanges()
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeFineGrainedKotlinClassChanges(
|
||||
currentClassSnapshots: List<KotlinClassSnapshot>,
|
||||
previousClassSnapshots: List<KotlinClassSnapshot>
|
||||
): ChangeSet {
|
||||
val workingDir =
|
||||
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
|
||||
@@ -135,11 +162,11 @@ object ClasspathChangesComputer {
|
||||
val unusedChangesCollector = ChangesCollector()
|
||||
previousClassSnapshots.forEach {
|
||||
incrementalJvmCache.saveClassToCache(
|
||||
kotlinClassInfo = it.classInfo,
|
||||
kotlinClassInfo = it.classMemberLevelSnapshot!!,
|
||||
sourceFiles = null,
|
||||
changesCollector = unusedChangesCollector
|
||||
)
|
||||
incrementalJvmCache.markDirty(it.classInfo.className)
|
||||
incrementalJvmCache.markDirty(it.classMemberLevelSnapshot!!.className)
|
||||
}
|
||||
|
||||
// Step 2:
|
||||
@@ -152,7 +179,7 @@ object ClasspathChangesComputer {
|
||||
val changesCollector = ChangesCollector()
|
||||
currentClassSnapshots.forEach {
|
||||
incrementalJvmCache.saveClassToCache(
|
||||
kotlinClassInfo = it.classInfo,
|
||||
kotlinClassInfo = it.classMemberLevelSnapshot!!,
|
||||
sourceFiles = null,
|
||||
changesCollector = changesCollector
|
||||
)
|
||||
@@ -172,7 +199,10 @@ object ClasspathChangesComputer {
|
||||
return dirtyData.normalize(currentClassSnapshots, previousClassSnapshots)
|
||||
}
|
||||
|
||||
private fun DirtyData.normalize(currentClassSnapshots: List<ClassSnapshot>, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
||||
private fun DirtyData.normalize(
|
||||
currentClassSnapshots: List<AccessibleClassSnapshot>,
|
||||
previousClassSnapshots: List<AccessibleClassSnapshot>
|
||||
): ChangeSet {
|
||||
val changedLookupSymbols =
|
||||
dirtyLookupSymbols.filterLookupSymbols(currentClassSnapshots).toSet() +
|
||||
dirtyLookupSymbols.filterLookupSymbols(previousClassSnapshots)
|
||||
@@ -216,7 +246,7 @@ internal object ImpactAnalysis {
|
||||
*
|
||||
* The returned set is also a [ChangeSet], which includes the given changes plus the impacted ones.
|
||||
*/
|
||||
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<ClassSnapshot>): ChangeSet {
|
||||
fun computeImpactedSet(changes: ChangeSet, previousClassSnapshots: List<AccessibleClassSnapshot>): ChangeSet {
|
||||
val classIdToSubclasses = getClassIdToSubclassesMap(previousClassSnapshots)
|
||||
val impactedClassesResolver = { classId: ClassId -> classIdToSubclasses[classId] ?: emptySet() }
|
||||
|
||||
@@ -234,14 +264,14 @@ internal object ImpactAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getClassIdToSubclassesMap(classSnapshots: List<ClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
||||
val classIds: Set<ClassId> = classSnapshots.map { it.getClassId() }.toSet() // Use Set for presence check
|
||||
private fun getClassIdToSubclassesMap(classSnapshots: List<AccessibleClassSnapshot>): Map<ClassId, Set<ClassId>> {
|
||||
val classIds: Set<ClassId> = classSnapshots.map { it.classId }.toSet() // Use Set for presence check
|
||||
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
||||
|
||||
val classIdToSubclasses = mutableMapOf<ClassId, MutableSet<ClassId>>()
|
||||
classSnapshots.forEach { classSnapshot ->
|
||||
val classId = classSnapshot.getClassId()
|
||||
val classId = classSnapshot.classId
|
||||
classSnapshot.getSupertypes(classNameToClassIdResolver).forEach { supertype ->
|
||||
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
||||
if (supertype in classIds) {
|
||||
@@ -257,39 +287,36 @@ internal object ImpactAnalysis {
|
||||
* impacted classes.
|
||||
*/
|
||||
fun findImpactedClassesInclusive(classIds: Set<ClassId>, impactedClassesResolver: (ClassId) -> Set<ClassId>): Set<ClassId> {
|
||||
val visitedClasses = mutableSetOf<ClassId>()
|
||||
val toVisitClasses = classIds.toMutableSet()
|
||||
while (toVisitClasses.isNotEmpty()) {
|
||||
val nextToVisit = mutableSetOf<ClassId>()
|
||||
toVisitClasses.forEach {
|
||||
nextToVisit.addAll(impactedClassesResolver.invoke(it))
|
||||
}
|
||||
visitedClasses.addAll(toVisitClasses)
|
||||
toVisitClasses.clear()
|
||||
toVisitClasses.addAll(nextToVisit - visitedClasses)
|
||||
}
|
||||
return visitedClasses
|
||||
}
|
||||
}
|
||||
// Standard Breadth-First Search
|
||||
val visitedAndToVisitClasses = classIds.toMutableSet()
|
||||
val classesToVisit = ArrayDeque(classIds)
|
||||
|
||||
internal fun ClassSnapshot.getClassId(): ClassId {
|
||||
return when (this) {
|
||||
is KotlinClassSnapshot -> classInfo.classId
|
||||
is JavaClassSnapshot -> classId
|
||||
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||
while (classesToVisit.isNotEmpty()) {
|
||||
val classToVisit = classesToVisit.removeFirst()
|
||||
val nextClassesToVisit = impactedClassesResolver.invoke(classToVisit) - visitedAndToVisitClasses
|
||||
visitedAndToVisitClasses.addAll(nextClassesToVisit)
|
||||
classesToVisit.addAll(nextClassesToVisit)
|
||||
}
|
||||
return visitedAndToVisitClasses
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [ClassId]s of the supertypes of this class.
|
||||
* Returns the [ClassId]s of the supertypes of this class (could be empty in some cases).
|
||||
*
|
||||
* @param classIdResolver Resolves the [ClassId] from the [JvmClassName] of a supertype. It may return null if the supertype is outside the
|
||||
* considered set of classes (e.g., "java/lang/Object"). Those supertypes do not need to be included in the returned result.
|
||||
*/
|
||||
internal fun ClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
||||
internal fun AccessibleClassSnapshot.getSupertypes(classIdResolver: (JvmClassName) -> ClassId?): List<ClassId> {
|
||||
return when (this) {
|
||||
is KotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||
is RegularKotlinClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||
is PackageFacadeKotlinClassSnapshot, is MultifileClassKotlinClassSnapshot -> {
|
||||
// These classes may have supertypes (e.g., kotlin/collections/ArraysKt (MULTIFILE_CLASS) extends
|
||||
// kotlin/collections/ArraysKt___ArraysKt (MULTIFILE_CLASS_PART)), but we don't have to use that info during impact analysis
|
||||
// because those inheritors and supertypes should have the same package names, and in package facades only the package names and
|
||||
// member names matter.
|
||||
emptyList()
|
||||
}
|
||||
is JavaClassSnapshot -> supertypes.mapNotNull { classIdResolver.invoke(it) }
|
||||
is InaccessibleClassSnapshot -> error("Unexpected type (it should have been handled earlier): ${javaClass.name}")
|
||||
}
|
||||
}
|
||||
|
||||
+95
-65
@@ -6,8 +6,8 @@
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.incremental.KotlinClassInfo
|
||||
import org.jetbrains.kotlin.incremental.md5
|
||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
|
||||
@@ -26,7 +26,7 @@ class ClasspathEntrySnapshot(
|
||||
* Maps (Unix-style) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory
|
||||
* or jar).
|
||||
*/
|
||||
val classSnapshots: LinkedHashMap<String, ClassSnapshotWithHash>
|
||||
val classSnapshots: LinkedHashMap<String, ClassSnapshot>
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -37,70 +37,83 @@ class ClasspathEntrySnapshot(
|
||||
* `KotlinCompile` task and the task needs to support compile avoidance. For example, this class should contain public method signatures,
|
||||
* and should not contain private method signatures, or method implementations.
|
||||
*/
|
||||
sealed class ClassSnapshot {
|
||||
sealed class ClassSnapshot
|
||||
|
||||
/** Computes the hash of this [ClassSnapshot] and returns a [ClassSnapshotWithHash]. */
|
||||
val withHash: ClassSnapshotWithHash by lazy {
|
||||
ClassSnapshotWithHash(this, ClassSnapshotExternalizer.toByteArray(this).md5())
|
||||
}
|
||||
}
|
||||
/** [ClassSnapshot] of an accessible class. See [InaccessibleClassSnapshot] for info on the accessibility of a class. */
|
||||
sealed class AccessibleClassSnapshot : ClassSnapshot() {
|
||||
abstract val classId: ClassId
|
||||
|
||||
/** Contains a [ClassSnapshot] and its hash. */
|
||||
class ClassSnapshotWithHash(val classSnapshot: ClassSnapshot, val hash: Long) {
|
||||
|
||||
override fun toString() = classSnapshot.toString()
|
||||
}
|
||||
|
||||
/** [ClassSnapshot] of a Kotlin class. */
|
||||
class KotlinClassSnapshot(
|
||||
val classInfo: KotlinClassInfo,
|
||||
val supertypes: List<JvmClassName>,
|
||||
|
||||
/**
|
||||
* Package-level members if this class is a package facade (classInfo.classKind != KotlinClassHeader.Kind.CLASS).
|
||||
*
|
||||
* Note that MULTIFILE_CLASS classes do not have proto data, so we can't extract their package-level members even though they are
|
||||
* package facades.
|
||||
*
|
||||
* Therefore, the [packageMembers] property below is not null iff classInfo.classKind !in setOf(CLASS, MULTIFILE_CLASS)
|
||||
*/
|
||||
val packageMembers: List<PackageMember>?
|
||||
) : ClassSnapshot() {
|
||||
|
||||
override fun toString() = classInfo.classId.toString()
|
||||
}
|
||||
|
||||
/** [ClassSnapshot] of a Java class. */
|
||||
class JavaClassSnapshot(
|
||||
|
||||
/** [ClassId] of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
|
||||
val classId: ClassId,
|
||||
|
||||
/** The superclass and interfaces of the class. It is part of the class's ABI ([classAbiExcludingMembers]). */
|
||||
val supertypes: List<JvmClassName>,
|
||||
|
||||
/** [AbiSnapshot] of the class excluding its fields and methods. */
|
||||
val classAbiExcludingMembers: AbiSnapshot,
|
||||
|
||||
/** [AbiSnapshot]s of the class's fields. */
|
||||
val fieldsAbi: List<AbiSnapshot>,
|
||||
|
||||
/** [AbiSnapshot]s of the class's methods. */
|
||||
val methodsAbi: List<AbiSnapshot>
|
||||
|
||||
) : ClassSnapshot() {
|
||||
|
||||
val className by lazy {
|
||||
JvmClassName.byClassId(classId).also {
|
||||
check(it == JvmClassName.byInternalName(classAbiExcludingMembers.name))
|
||||
}
|
||||
}
|
||||
/** The hash of the class's ABI. */
|
||||
abstract val classAbiHash: Long
|
||||
|
||||
override fun toString() = classId.toString()
|
||||
}
|
||||
|
||||
/** The ABI snapshot of a Java element (e.g., class, field, or method). */
|
||||
open class AbiSnapshot(
|
||||
/** [ClassSnapshot] of a Kotlin class. */
|
||||
sealed class KotlinClassSnapshot : AccessibleClassSnapshot() {
|
||||
|
||||
/** Snapshot of this class when [ClassSnapshotGranularity] == [CLASS_MEMBER_LEVEL], null otherwise. */
|
||||
abstract val classMemberLevelSnapshot: KotlinClassInfo?
|
||||
}
|
||||
|
||||
/** [KotlinClassSnapshot] where class kind == [CLASS]. */
|
||||
class RegularKotlinClassSnapshot(
|
||||
override val classId: ClassId,
|
||||
override val classAbiHash: Long,
|
||||
override val classMemberLevelSnapshot: KotlinClassInfo?,
|
||||
val supertypes: List<JvmClassName>
|
||||
) : KotlinClassSnapshot()
|
||||
|
||||
/** [KotlinClassSnapshot] where class kind == [FILE_FACADE] or [MULTIFILE_CLASS_PART]. */
|
||||
class PackageFacadeKotlinClassSnapshot(
|
||||
override val classId: ClassId,
|
||||
override val classAbiHash: Long,
|
||||
override val classMemberLevelSnapshot: KotlinClassInfo?,
|
||||
val packageMembers: PackageMemberSet
|
||||
) : KotlinClassSnapshot()
|
||||
|
||||
/**
|
||||
* [KotlinClassSnapshot] where class kind == [MULTIFILE_CLASS].
|
||||
*
|
||||
* NOTE: We have to handle [MULTIFILE_CLASS] differently from [FILE_FACADE] and [MULTIFILE_CLASS_PART] because [MULTIFILE_CLASS] classes
|
||||
* don't contain proto data. Except for constants (see below), it is actually okay to ignore [MULTIFILE_CLASS] because any change in a
|
||||
* [MULTIFILE_CLASS] will have an associated change in one of its [MULTIFILE_CLASS_PART]s, so the change will be detected when we analyze
|
||||
* the [MULTIFILE_CLASS_PART]s.
|
||||
*
|
||||
* However, if there is a constant is defined in a [MULTIFILE_CLASS], that constant will have a declared value in the [MULTIFILE_CLASS] but
|
||||
* not in its [MULTIFILE_CLASS_PART]s. Therefore, we'll need to track constants for [MULTIFILE_CLASS]. (We don't have to do this for inline
|
||||
* functions or other package members as those are defined in [MULTIFILE_CLASS_PART]s.)
|
||||
*/
|
||||
class MultifileClassKotlinClassSnapshot(
|
||||
override val classId: ClassId,
|
||||
override val classAbiHash: Long,
|
||||
override val classMemberLevelSnapshot: KotlinClassInfo?,
|
||||
val constants: PackageMemberSet
|
||||
) : KotlinClassSnapshot()
|
||||
|
||||
/** [ClassSnapshot] of a Java class. */
|
||||
class JavaClassSnapshot(
|
||||
override val classId: ClassId,
|
||||
override val classAbiHash: Long,
|
||||
/** Snapshot of this class when [ClassSnapshotGranularity] == [CLASS_MEMBER_LEVEL], null otherwise. */
|
||||
val classMemberLevelSnapshot: JavaClassMemberLevelSnapshot?,
|
||||
val supertypes: List<JvmClassName>
|
||||
) : AccessibleClassSnapshot()
|
||||
|
||||
/** Snapshot of a Java class when [ClassSnapshotGranularity] == [CLASS_MEMBER_LEVEL]. */
|
||||
class JavaClassMemberLevelSnapshot(
|
||||
/** [JavaElementSnapshot] of the class excluding its fields and methods. */
|
||||
val classAbiExcludingMembers: JavaElementSnapshot,
|
||||
|
||||
/** [JavaElementSnapshot]s of the class's fields. */
|
||||
val fieldsAbi: List<JavaElementSnapshot>,
|
||||
|
||||
/** [JavaElementSnapshot]s of the class's methods. */
|
||||
val methodsAbi: List<JavaElementSnapshot>
|
||||
)
|
||||
|
||||
/** Snapshot of a Java class or a Java class member (field or method). */
|
||||
open class JavaElementSnapshot(
|
||||
|
||||
/** The name of the Java element. It is part of the Java element's ABI. */
|
||||
val name: String,
|
||||
@@ -109,15 +122,16 @@ open class AbiSnapshot(
|
||||
val abiHash: Long
|
||||
)
|
||||
|
||||
/** TEST-ONLY: An [AbiSnapshot] that is used for testing only and must not be used in production code. */
|
||||
class AbiSnapshotForTests(
|
||||
/** TEST-ONLY: A [JavaElementSnapshot] that is used for testing only and must not be used in production code. */
|
||||
class JavaElementSnapshotForTests(
|
||||
name: String,
|
||||
abiHash: Long,
|
||||
|
||||
/** The Java element's ABI, captured in a [String]. */
|
||||
@Suppress("unused") val abiValue: String
|
||||
@Suppress("unused") // Used by Gson reflection
|
||||
val abiValue: String
|
||||
|
||||
) : AbiSnapshot(name, abiHash)
|
||||
) : JavaElementSnapshot(name, abiHash)
|
||||
|
||||
/**
|
||||
* [ClassSnapshot] of an inaccessible class.
|
||||
@@ -126,3 +140,19 @@ class AbiSnapshotForTests(
|
||||
* will not require recompilation of other source files.
|
||||
*/
|
||||
object InaccessibleClassSnapshot : ClassSnapshot()
|
||||
|
||||
/** The granularity of a [ClassSnapshot]. */
|
||||
enum class ClassSnapshotGranularity {
|
||||
|
||||
/**
|
||||
* Snapshotting level that allows tracking whether a .class file has changed without tracking what specific parts of the .class file
|
||||
* (e.g., fields or methods) have changed.
|
||||
*/
|
||||
CLASS_LEVEL,
|
||||
|
||||
/**
|
||||
* Snapshotting level that allows tracking not only whether a .class file has changed but also what specific parts of the .class file
|
||||
* (e.g., fields or methods) have changed.
|
||||
*/
|
||||
CLASS_MEMBER_LEVEL
|
||||
}
|
||||
|
||||
+133
-74
@@ -48,76 +48,119 @@ object CachedClasspathSnapshotSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
open class DataExternalizerForSealedClass<T>(
|
||||
val baseClass: Class<T>,
|
||||
val inheritorClasses: List<Class<out T>>,
|
||||
val inheritorExternalizers: List<DataExternalizer<*>>
|
||||
) : DataExternalizer<T> {
|
||||
|
||||
override fun save(output: DataOutput, objectToExternalize: T) {
|
||||
val inheritorClassIndex =
|
||||
inheritorClasses.indexOfFirst { it.isAssignableFrom(objectToExternalize!!::class.java) }.also { check(it != -1) }
|
||||
output.writeInt(inheritorClassIndex)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(inheritorExternalizers[inheritorClassIndex] as DataExternalizer<T>).save(output, objectToExternalize)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): T {
|
||||
val inheritorClassIndex = input.readInt()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return inheritorExternalizers[inheritorClassIndex].read(input) as T
|
||||
}
|
||||
}
|
||||
|
||||
object ClasspathEntrySnapshotExternalizer : DataExternalizer<ClasspathEntrySnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: ClasspathEntrySnapshot) {
|
||||
LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).save(output, snapshot.classSnapshots)
|
||||
LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotExternalizer).save(output, snapshot.classSnapshots)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ClasspathEntrySnapshot {
|
||||
return ClasspathEntrySnapshot(
|
||||
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotWithHashExternalizer).read(input)
|
||||
classSnapshots = LinkedHashMapExternalizer(StringExternalizer, ClassSnapshotExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object ClassSnapshotExternalizer : DataExternalizer<ClassSnapshot> {
|
||||
object ClassSnapshotExternalizer : DataExternalizerForSealedClass<ClassSnapshot>(
|
||||
baseClass = ClassSnapshot::class.java,
|
||||
inheritorClasses = listOf(AccessibleClassSnapshot::class.java, InaccessibleClassSnapshot::class.java),
|
||||
inheritorExternalizers = listOf(AccessibleClassSnapshotExternalizer, InaccessibleClassSnapshotExternalizer)
|
||||
)
|
||||
|
||||
override fun save(output: DataOutput, snapshot: ClassSnapshot) {
|
||||
when (snapshot) {
|
||||
is KotlinClassSnapshot -> {
|
||||
output.writeString(KotlinClassSnapshot::class.java.name)
|
||||
KotlinClassSnapshotExternalizer.save(output, snapshot)
|
||||
}
|
||||
is JavaClassSnapshot -> {
|
||||
output.writeString(JavaClassSnapshot::class.java.name)
|
||||
JavaClassSnapshotExternalizer.save(output, snapshot)
|
||||
}
|
||||
is InaccessibleClassSnapshot -> {
|
||||
output.writeString(InaccessibleClassSnapshot::class.java.name)
|
||||
InaccessibleClassSnapshotExternalizer.save(output, snapshot)
|
||||
}
|
||||
}
|
||||
}
|
||||
object AccessibleClassSnapshotExternalizer : DataExternalizerForSealedClass<AccessibleClassSnapshot>(
|
||||
baseClass = AccessibleClassSnapshot::class.java,
|
||||
inheritorClasses = listOf(KotlinClassSnapshot::class.java, JavaClassSnapshot::class.java),
|
||||
inheritorExternalizers = listOf(KotlinClassSnapshotExternalizer, JavaClassSnapshotExternalizer)
|
||||
)
|
||||
|
||||
override fun read(input: DataInput): ClassSnapshot {
|
||||
return when (val className = input.readString()) {
|
||||
KotlinClassSnapshot::class.java.name -> KotlinClassSnapshotExternalizer.read(input)
|
||||
JavaClassSnapshot::class.java.name -> JavaClassSnapshotExternalizer.read(input)
|
||||
InaccessibleClassSnapshot::class.java.name -> InaccessibleClassSnapshotExternalizer.read(input)
|
||||
else -> error("Unrecognized class name: $className")
|
||||
}
|
||||
}
|
||||
}
|
||||
object KotlinClassSnapshotExternalizer : DataExternalizerForSealedClass<KotlinClassSnapshot>(
|
||||
baseClass = KotlinClassSnapshot::class.java,
|
||||
inheritorClasses = listOf(
|
||||
RegularKotlinClassSnapshot::class.java,
|
||||
PackageFacadeKotlinClassSnapshot::class.java,
|
||||
MultifileClassKotlinClassSnapshot::class.java
|
||||
),
|
||||
inheritorExternalizers = listOf(
|
||||
RegularKotlinClassSnapshotExternalizer,
|
||||
PackageFacadeKotlinClassSnapshotExternalizer,
|
||||
MultifileClassKotlinClassSnapshotExternalizer
|
||||
)
|
||||
)
|
||||
|
||||
object ClassSnapshotWithHashExternalizer : DataExternalizer<ClassSnapshotWithHash> {
|
||||
object RegularKotlinClassSnapshotExternalizer : DataExternalizer<RegularKotlinClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: ClassSnapshotWithHash) {
|
||||
ClassSnapshotExternalizer.save(output, snapshot.classSnapshot)
|
||||
LongExternalizer.save(output, snapshot.hash)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ClassSnapshotWithHash {
|
||||
return ClassSnapshotWithHash(
|
||||
classSnapshot = ClassSnapshotExternalizer.read(input),
|
||||
hash = LongExternalizer.read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object KotlinClassSnapshotExternalizer : DataExternalizer<KotlinClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: KotlinClassSnapshot) {
|
||||
KotlinClassInfoExternalizer.save(output, snapshot.classInfo)
|
||||
override fun save(output: DataOutput, snapshot: RegularKotlinClassSnapshot) {
|
||||
ClassIdExternalizer.save(output, snapshot.classId)
|
||||
LongExternalizer.save(output, snapshot.classAbiHash)
|
||||
NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot)
|
||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
||||
NullableValueExternalizer(ListExternalizer(PackageMemberExternalizer)).save(output, snapshot.packageMembers)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): KotlinClassSnapshot {
|
||||
return KotlinClassSnapshot(
|
||||
classInfo = KotlinClassInfoExternalizer.read(input),
|
||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
||||
packageMembers = NullableValueExternalizer(ListExternalizer(PackageMemberExternalizer)).read(input)
|
||||
override fun read(input: DataInput): RegularKotlinClassSnapshot {
|
||||
return RegularKotlinClassSnapshot(
|
||||
classId = ClassIdExternalizer.read(input),
|
||||
classAbiHash = LongExternalizer.read(input),
|
||||
classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input),
|
||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object PackageFacadeKotlinClassSnapshotExternalizer : DataExternalizer<PackageFacadeKotlinClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: PackageFacadeKotlinClassSnapshot) {
|
||||
ClassIdExternalizer.save(output, snapshot.classId)
|
||||
LongExternalizer.save(output, snapshot.classAbiHash)
|
||||
NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot)
|
||||
PackageMemberSetExternalizer.save(output, snapshot.packageMembers)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): PackageFacadeKotlinClassSnapshot {
|
||||
return PackageFacadeKotlinClassSnapshot(
|
||||
classId = ClassIdExternalizer.read(input),
|
||||
classAbiHash = LongExternalizer.read(input),
|
||||
classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input),
|
||||
packageMembers = PackageMemberSetExternalizer.read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object MultifileClassKotlinClassSnapshotExternalizer : DataExternalizer<MultifileClassKotlinClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: MultifileClassKotlinClassSnapshot) {
|
||||
ClassIdExternalizer.save(output, snapshot.classId)
|
||||
LongExternalizer.save(output, snapshot.classAbiHash)
|
||||
NullableValueExternalizer(KotlinClassInfoExternalizer).save(output, snapshot.classMemberLevelSnapshot)
|
||||
PackageMemberSetExternalizer.save(output, snapshot.constants)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): MultifileClassKotlinClassSnapshot {
|
||||
return MultifileClassKotlinClassSnapshot(
|
||||
classId = ClassIdExternalizer.read(input),
|
||||
classAbiHash = LongExternalizer.read(input),
|
||||
classMemberLevelSnapshot = NullableValueExternalizer(KotlinClassInfoExternalizer).read(input),
|
||||
constants = PackageMemberSetExternalizer.read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -126,7 +169,7 @@ object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo> {
|
||||
|
||||
override fun save(output: DataOutput, info: KotlinClassInfo) {
|
||||
ClassIdExternalizer.save(output, info.classId)
|
||||
output.writeInt(info.classKind.id)
|
||||
IntExternalizer.save(output, info.classKind.id)
|
||||
ListExternalizer(StringExternalizer).save(output, info.classHeaderData.toList())
|
||||
ListExternalizer(StringExternalizer).save(output, info.classHeaderStrings.toList())
|
||||
NullableValueExternalizer(StringExternalizer).save(output, info.multifileClassName)
|
||||
@@ -137,7 +180,7 @@ object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo> {
|
||||
override fun read(input: DataInput): KotlinClassInfo {
|
||||
return KotlinClassInfo(
|
||||
classId = ClassIdExternalizer.read(input),
|
||||
classKind = KotlinClassHeader.Kind.getById(input.readInt()),
|
||||
classKind = KotlinClassHeader.Kind.getById(IntExternalizer.read(input)),
|
||||
classHeaderData = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||
classHeaderStrings = ListExternalizer(StringExternalizer).read(input).toTypedArray(),
|
||||
multifileClassName = NullableValueExternalizer(StringExternalizer).read(input),
|
||||
@@ -147,17 +190,15 @@ object KotlinClassInfoExternalizer : DataExternalizer<KotlinClassInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
object PackageMemberExternalizer : DataExternalizer<PackageMember> {
|
||||
object PackageMemberSetExternalizer : DataExternalizer<PackageMemberSet> {
|
||||
|
||||
override fun save(output: DataOutput, packageMember: PackageMember) {
|
||||
FqNameExternalizer.save(output, packageMember.packageFqName)
|
||||
StringExternalizer.save(output, packageMember.memberName)
|
||||
override fun save(output: DataOutput, set: PackageMemberSet) {
|
||||
MapExternalizer(FqNameExternalizer, SetExternalizer(StringExternalizer)).save(output, set.packageToMembersMap)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): PackageMember {
|
||||
return PackageMember(
|
||||
packageFqName = FqNameExternalizer.read(input),
|
||||
memberName = StringExternalizer.read(input)
|
||||
override fun read(input: DataInput): PackageMemberSet {
|
||||
return PackageMemberSet(
|
||||
packageToMembersMap = MapExternalizer(FqNameExternalizer, SetExternalizer(StringExternalizer)).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -166,32 +207,50 @@ object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, snapshot: JavaClassSnapshot) {
|
||||
ClassIdExternalizer.save(output, snapshot.classId)
|
||||
LongExternalizer.save(output, snapshot.classAbiHash)
|
||||
NullableValueExternalizer(JavaClassMemberLevelSnapshotExternalizer).save(output, snapshot.classMemberLevelSnapshot)
|
||||
ListExternalizer(JvmClassNameExternalizer).save(output, snapshot.supertypes)
|
||||
AbiSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers)
|
||||
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.fieldsAbi)
|
||||
ListExternalizer(AbiSnapshotExternalizer).save(output, snapshot.methodsAbi)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): JavaClassSnapshot {
|
||||
return JavaClassSnapshot(
|
||||
classId = ClassIdExternalizer.read(input),
|
||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input),
|
||||
classAbiExcludingMembers = AbiSnapshotExternalizer.read(input),
|
||||
fieldsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input),
|
||||
methodsAbi = ListExternalizer(AbiSnapshotExternalizer).read(input)
|
||||
classAbiHash = LongExternalizer.read(input),
|
||||
classMemberLevelSnapshot = NullableValueExternalizer(JavaClassMemberLevelSnapshotExternalizer).read(input),
|
||||
supertypes = ListExternalizer(JvmClassNameExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object AbiSnapshotExternalizer : DataExternalizer<AbiSnapshot> {
|
||||
object JavaClassMemberLevelSnapshotExternalizer : DataExternalizer<JavaClassMemberLevelSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, value: AbiSnapshot) {
|
||||
output.writeString(value.name)
|
||||
override fun save(output: DataOutput, snapshot: JavaClassMemberLevelSnapshot) {
|
||||
JavaElementSnapshotExternalizer.save(output, snapshot.classAbiExcludingMembers)
|
||||
ListExternalizer(JavaElementSnapshotExternalizer).save(output, snapshot.fieldsAbi)
|
||||
ListExternalizer(JavaElementSnapshotExternalizer).save(output, snapshot.methodsAbi)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): JavaClassMemberLevelSnapshot {
|
||||
return JavaClassMemberLevelSnapshot(
|
||||
classAbiExcludingMembers = JavaElementSnapshotExternalizer.read(input),
|
||||
fieldsAbi = ListExternalizer(JavaElementSnapshotExternalizer).read(input),
|
||||
methodsAbi = ListExternalizer(JavaElementSnapshotExternalizer).read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object JavaElementSnapshotExternalizer : DataExternalizer<JavaElementSnapshot> {
|
||||
|
||||
override fun save(output: DataOutput, value: JavaElementSnapshot) {
|
||||
StringExternalizer.save(output, value.name)
|
||||
LongExternalizer.save(output, value.abiHash)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): AbiSnapshot {
|
||||
return AbiSnapshot(name = input.readString(), abiHash = LongExternalizer.read(input))
|
||||
override fun read(input: DataInput): JavaElementSnapshot {
|
||||
return JavaElementSnapshot(
|
||||
name = StringExternalizer.read(input),
|
||||
abiHash = LongExternalizer.read(input)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+69
-57
@@ -5,7 +5,10 @@
|
||||
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.build.report.metrics.*
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildPerformanceMetric
|
||||
import org.jetbrains.kotlin.build.report.metrics.BuildTime
|
||||
import org.jetbrains.kotlin.build.report.metrics.measure
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.NoChanges
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.IncrementalRun.ToBeComputedByIncrementalCompiler
|
||||
@@ -13,12 +16,12 @@ import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnable
|
||||
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotEnabled.NotAvailableForNonIncrementalRun
|
||||
import org.jetbrains.kotlin.incremental.LookupStorage
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrink
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrinkClasses
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotShrinker.shrinkClasspath
|
||||
import org.jetbrains.kotlin.incremental.storage.ListExternalizer
|
||||
import org.jetbrains.kotlin.incremental.storage.LookupSymbolKey
|
||||
import org.jetbrains.kotlin.incremental.storage.loadFromFile
|
||||
import org.jetbrains.kotlin.incremental.storage.saveToFile
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
|
||||
@@ -27,29 +30,29 @@ object ClasspathSnapshotShrinker {
|
||||
/**
|
||||
* Shrinks the given classes by retaining only classes that are referenced by the lookup symbols stored in the given [LookupStorage].
|
||||
*/
|
||||
fun shrink(
|
||||
allClasses: List<ClassSnapshotWithHash>,
|
||||
fun shrinkClasspath(
|
||||
allClasses: List<AccessibleClassSnapshot>,
|
||||
lookupStorage: LookupStorage,
|
||||
metrics: BuildMetricsReporter = DoNothingBuildMetricsReporter
|
||||
): List<ClassSnapshotWithHash> {
|
||||
val lookupSymbols = metrics.measure(BuildTime.GET_LOOKUP_SYMBOLS) {
|
||||
metrics: MetricsReporter = MetricsReporter()
|
||||
): List<AccessibleClassSnapshot> {
|
||||
val lookupSymbols = metrics.getLookupSymbols {
|
||||
lookupStorage.lookupSymbols
|
||||
.map { LookupSymbol(it.name, it.scope) }
|
||||
.filterLookupSymbols(allClasses.map { it.classSnapshot })
|
||||
.map { LookupSymbol(name = it.name, scope = it.scope) }
|
||||
.filterLookupSymbols(allClasses)
|
||||
}
|
||||
return shrink(allClasses, lookupSymbols, metrics)
|
||||
return shrinkClasses(allClasses, lookupSymbols, metrics)
|
||||
}
|
||||
|
||||
/** Shrinks the given classes by retaining only classes that are referenced by the given lookup symbols. */
|
||||
fun shrink(
|
||||
allClasses: List<ClassSnapshotWithHash>,
|
||||
fun shrinkClasses(
|
||||
allClasses: List<AccessibleClassSnapshot>,
|
||||
lookupSymbols: List<ProgramSymbol>,
|
||||
metrics: BuildMetricsReporter = DoNothingBuildMetricsReporter
|
||||
): List<ClassSnapshotWithHash> {
|
||||
val referencedClasses = metrics.measure(BuildTime.FIND_REFERENCED_CLASSES) {
|
||||
metrics: MetricsReporter = MetricsReporter()
|
||||
): List<AccessibleClassSnapshot> {
|
||||
val referencedClasses = metrics.findReferencedClasses {
|
||||
findReferencedClasses(allClasses, lookupSymbols)
|
||||
}
|
||||
return metrics.measure(BuildTime.FIND_TRANSITIVELY_REFERENCED_CLASSES) {
|
||||
return metrics.findTransitivelyReferencedClasses {
|
||||
findTransitivelyReferencedClasses(allClasses, referencedClasses)
|
||||
}
|
||||
}
|
||||
@@ -57,12 +60,12 @@ object ClasspathSnapshotShrinker {
|
||||
/**
|
||||
* Finds classes that are referenced by the given lookup symbols.
|
||||
*
|
||||
* Note: It's okay to over-approximate referenced classes.
|
||||
* Note: It's okay to over-approximate the result.
|
||||
*/
|
||||
private fun findReferencedClasses(
|
||||
allClasses: List<ClassSnapshotWithHash>,
|
||||
allClasses: List<AccessibleClassSnapshot>,
|
||||
lookupSymbols: List<ProgramSymbol>
|
||||
): List<ClassSnapshotWithHash> {
|
||||
): List<AccessibleClassSnapshot> {
|
||||
val lookedUpClassIds: Set<ClassId> = lookupSymbols.mapNotNullTo(mutableSetOf()) {
|
||||
when (it) {
|
||||
is ClassSymbol -> it.classId
|
||||
@@ -70,18 +73,14 @@ object ClasspathSnapshotShrinker {
|
||||
is PackageMember -> null
|
||||
}
|
||||
}
|
||||
val lookedUpPackageMembers: Set<PackageMember> = lookupSymbols.filterIsInstanceTo(mutableSetOf())
|
||||
val lookedUpPackageMembers: PackageMemberSet =
|
||||
lookupSymbols.filterIsInstanceTo<PackageMember, MutableSet<PackageMember>>(mutableSetOf()).compact()
|
||||
|
||||
return allClasses.filter {
|
||||
val isPackageFacade =
|
||||
it.classSnapshot is KotlinClassSnapshot && it.classSnapshot.classInfo.classKind != KotlinClassHeader.Kind.CLASS
|
||||
if (isPackageFacade) {
|
||||
// If packageMembers == null (e.g., if classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS -- see
|
||||
// `KotlinClassSnapshot.packageMembers`'s kdoc), it means that we don't have the information, so we will always include the
|
||||
// class (it's okay to over-approximate the result).
|
||||
(it.classSnapshot as KotlinClassSnapshot).packageMembers?.any { member -> member in lookedUpPackageMembers } ?: true
|
||||
} else {
|
||||
it.classSnapshot.getClassId() in lookedUpClassIds
|
||||
when (it) {
|
||||
is RegularKotlinClassSnapshot, is JavaClassSnapshot -> it.classId in lookedUpClassIds
|
||||
is PackageFacadeKotlinClassSnapshot -> it.packageMembers.containsElementsIn(lookedUpPackageMembers)
|
||||
is MultifileClassKotlinClassSnapshot -> it.constants.containsElementsIn(lookedUpPackageMembers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +92,10 @@ object ClasspathSnapshotShrinker {
|
||||
* The returned list includes the given referenced classes plus the transitively referenced ones.
|
||||
*/
|
||||
private fun findTransitivelyReferencedClasses(
|
||||
allClasses: List<ClassSnapshotWithHash>,
|
||||
referencedClasses: List<ClassSnapshotWithHash>
|
||||
): List<ClassSnapshotWithHash> {
|
||||
val classIdToClassSnapshot = allClasses.associateBy { it.classSnapshot.getClassId() }
|
||||
allClasses: List<AccessibleClassSnapshot>,
|
||||
referencedClasses: List<AccessibleClassSnapshot>
|
||||
): List<AccessibleClassSnapshot> {
|
||||
val classIdToClassSnapshot = allClasses.associateBy { it.classId }
|
||||
val classIds: Set<ClassId> = classIdToClassSnapshot.keys // Use Set for presence check
|
||||
val classNameToClassId = classIds.associateBy { JvmClassName.byClassId(it) }
|
||||
val classNameToClassIdResolver = { className: JvmClassName -> classNameToClassId[className] }
|
||||
@@ -105,15 +104,30 @@ object ClasspathSnapshotShrinker {
|
||||
// No need to collect supertypes outside the given set of classes (e.g., "java/lang/Object")
|
||||
@Suppress("SimpleRedundantLet")
|
||||
classIdToClassSnapshot[classId]?.let {
|
||||
it.classSnapshot.getSupertypes(classNameToClassIdResolver).filter { supertype -> supertype in classIds }.toSet()
|
||||
it.getSupertypes(classNameToClassIdResolver).filterTo(mutableSetOf()) { supertype -> supertype in classIds }
|
||||
} ?: emptySet()
|
||||
}
|
||||
|
||||
val referencedClassIds = referencedClasses.map { it.classSnapshot.getClassId() }.toSet()
|
||||
val referencedClassIds = referencedClasses.mapTo(mutableSetOf()) { it.classId }
|
||||
val transitivelyReferencedClassIds: Set<ClassId> =
|
||||
ImpactAnalysis.findImpactedClassesInclusive(referencedClassIds, supertypesResolver) // Use Set for presence check
|
||||
|
||||
return allClasses.filter { it.classSnapshot.getClassId() in transitivelyReferencedClassIds }
|
||||
return allClasses.filter { it.classId in transitivelyReferencedClassIds }
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to allow the caller of [ClasspathSnapshotShrinker] to provide a list of [BuildTime]s as different callers may want to
|
||||
* record different [BuildTime]s (because the [BuildTime.parent]s are different).
|
||||
*/
|
||||
class MetricsReporter(
|
||||
private val metrics: BuildMetricsReporter? = null,
|
||||
private val getLookupSymbols: BuildTime? = null,
|
||||
private val findReferencedClasses: BuildTime? = null,
|
||||
private val findTransitivelyReferencedClasses: BuildTime? = null
|
||||
) {
|
||||
fun <T> getLookupSymbols(fn: () -> T) = metrics?.measure(getLookupSymbols!!, fn) ?: fn()
|
||||
fun <T> findReferencedClasses(fn: () -> T) = metrics?.measure(findReferencedClasses!!, fn) ?: fn()
|
||||
fun <T> findTransitivelyReferencedClasses(fn: () -> T) = metrics?.measure(findTransitivelyReferencedClasses!!, fn) ?: fn()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,8 +150,8 @@ object ClasspathSnapshotShrinker {
|
||||
* snapshotting), even though it seems more efficient to do so. For correctness, we need to look at the entire classpath first, remove
|
||||
* duplicate classes, and then remove inaccessible classes.
|
||||
*/
|
||||
internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<ClassSnapshotWithHash> {
|
||||
return getNonDuplicateClassSnapshots().filter { it.classSnapshot !is InaccessibleClassSnapshot }
|
||||
internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<AccessibleClassSnapshot> {
|
||||
return getNonDuplicateClassSnapshots().filterIsInstance<AccessibleClassSnapshot>()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,8 +159,8 @@ internal fun ClasspathSnapshot.removeDuplicateAndInaccessibleClasses(): List<Cla
|
||||
*
|
||||
* If there are duplicate classes on the classpath, retain only the first one to match the compiler's behavior.
|
||||
*/
|
||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshotWithHash> {
|
||||
val classSnapshots = LinkedHashMap<String, ClassSnapshotWithHash>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||
private fun ClasspathSnapshot.getNonDuplicateClassSnapshots(): List<ClassSnapshot> {
|
||||
val classSnapshots = LinkedHashMap<String, ClassSnapshot>(classpathEntrySnapshots.sumOf { it.classSnapshots.size })
|
||||
for (classpathEntrySnapshot in classpathEntrySnapshots) {
|
||||
for ((unixStyleRelativePath, classSnapshot) in classpathEntrySnapshot.classSnapshots) {
|
||||
classSnapshots.putIfAbsent(unixStyleRelativePath, classSnapshot)
|
||||
@@ -160,12 +174,12 @@ private sealed class ShrinkMode {
|
||||
object NoChanges : ShrinkMode()
|
||||
|
||||
class IncrementalNoNewLookups(
|
||||
val shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>,
|
||||
val shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>,
|
||||
) : ShrinkMode()
|
||||
|
||||
class Incremental(
|
||||
val currentClasspathSnapshot: List<ClassSnapshotWithHash>,
|
||||
val shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>,
|
||||
val currentClasspathSnapshot: List<AccessibleClassSnapshot>,
|
||||
val shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>,
|
||||
val addedLookupSymbols: Set<LookupSymbolKey>
|
||||
) : ShrinkMode()
|
||||
|
||||
@@ -175,8 +189,8 @@ private sealed class ShrinkMode {
|
||||
internal fun shrinkAndSaveClasspathSnapshot(
|
||||
classpathChanges: ClasspathChanges.ClasspathSnapshotEnabled,
|
||||
lookupStorage: LookupStorage,
|
||||
currentClasspathSnapshot: List<ClassSnapshotWithHash>?, // Not null iff classpathChanges is ToBeComputedByIncrementalCompiler
|
||||
shrunkCurrentClasspathAgainstPreviousLookups: List<ClassSnapshotWithHash>?, // Same as above
|
||||
currentClasspathSnapshot: List<AccessibleClassSnapshot>?, // Not null iff classpathChanges is ToBeComputedByIncrementalCompiler
|
||||
shrunkCurrentClasspathAgainstPreviousLookups: List<AccessibleClassSnapshot>?, // Same as above
|
||||
metrics: BuildMetricsReporter
|
||||
) {
|
||||
// In the following, we'll try to shrink the classpath snapshot incrementally when possible.
|
||||
@@ -191,7 +205,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
||||
} else {
|
||||
val shrunkPreviousClasspathAgainstPreviousLookups =
|
||||
metrics.measure(BuildTime.LOAD_SHRUNK_PREVIOUS_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||
ListExternalizer(ClassSnapshotWithHashExternalizer)
|
||||
ListExternalizer(AccessibleClassSnapshotExternalizer)
|
||||
.loadFromFile(classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile)
|
||||
}
|
||||
ShrinkMode.Incremental(
|
||||
@@ -219,7 +233,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
||||
}
|
||||
|
||||
// Shrink current classpath against current lookups
|
||||
val shrunkCurrentClasspath: List<ClassSnapshotWithHash>? = when (shrinkMode) {
|
||||
val shrunkCurrentClasspath: List<AccessibleClassSnapshot>? = when (shrinkMode) {
|
||||
is ShrinkMode.NoChanges -> null
|
||||
is ShrinkMode.IncrementalNoNewLookups -> {
|
||||
// There are no new lookups, so
|
||||
@@ -227,13 +241,12 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
||||
shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups
|
||||
}
|
||||
is ShrinkMode.Incremental -> metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||
val shrunkClasses = shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups.map { it.classSnapshot.getClassId() }.toSet()
|
||||
val notYetShrunkClasses = shrinkMode.currentClasspathSnapshot.filter { it.classSnapshot.getClassId() !in shrunkClasses }
|
||||
val shrunkClasses = shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups.mapTo(mutableSetOf()) { it.classId }
|
||||
val notYetShrunkClasses = shrinkMode.currentClasspathSnapshot.filter { it.classId !in shrunkClasses }
|
||||
val addedLookupSymbols = shrinkMode.addedLookupSymbols
|
||||
.map { LookupSymbol(it.name, it.scope) }
|
||||
.filterLookupSymbols(shrinkMode.currentClasspathSnapshot.map { it.classSnapshot })
|
||||
// Don't provide a BuildMetricsReporter for the following call as the sub-BuildTimes in it have a different parent
|
||||
val shrunkRemainingClassesAgainstNewLookups = shrink(notYetShrunkClasses, addedLookupSymbols)
|
||||
.map { LookupSymbol(name = it.name, scope = it.scope) }
|
||||
.filterLookupSymbols(notYetShrunkClasses)
|
||||
val shrunkRemainingClassesAgainstNewLookups = shrinkClasses(notYetShrunkClasses, addedLookupSymbols)
|
||||
|
||||
shrinkMode.shrunkCurrentClasspathAgainstPreviousLookups + shrunkRemainingClassesAgainstNewLookups
|
||||
}
|
||||
@@ -244,8 +257,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
||||
.removeDuplicateAndInaccessibleClasses()
|
||||
}
|
||||
metrics.measure(BuildTime.SHRINK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||
// Don't provide a BuildMetricsReporter for the following call as the sub-BuildTimes in it have a different parent
|
||||
shrink(classpathSnapshot, lookupStorage)
|
||||
shrinkClasspath(classpathSnapshot, lookupStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +269,7 @@ internal fun shrinkAndSaveClasspathSnapshot(
|
||||
}
|
||||
} else {
|
||||
metrics.measure(BuildTime.SAVE_SHRUNK_CURRENT_CLASSPATH_SNAPSHOT_AFTER_COMPILATION) {
|
||||
ListExternalizer(ClassSnapshotWithHashExternalizer).saveToFile(
|
||||
ListExternalizer(AccessibleClassSnapshotExternalizer).saveToFile(
|
||||
classpathChanges.classpathSnapshotFiles.shrunkPreviousClasspathSnapshotFile,
|
||||
shrunkCurrentClasspath!!
|
||||
)
|
||||
|
||||
+29
-11
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.incremental.*
|
||||
import org.jetbrains.kotlin.incremental.ChangesCollector.Companion.getNonPrivateMemberNames
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.*
|
||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader.Kind.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import java.io.File
|
||||
@@ -29,7 +31,7 @@ object ClasspathEntrySnapshotter {
|
||||
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
|
||||
}
|
||||
|
||||
val snapshots = ClassSnapshotter.snapshot(classes).map { it.withHash }
|
||||
val snapshots = ClassSnapshotter.snapshot(classes)
|
||||
|
||||
val relativePathsToSnapshotsMap = classes.map { it.classFile.unixStyleRelativePath }.zip(snapshots).toMap(LinkedHashMap())
|
||||
return ClasspathEntrySnapshot(relativePathsToSnapshotsMap)
|
||||
@@ -40,7 +42,11 @@ object ClasspathEntrySnapshotter {
|
||||
object ClassSnapshotter {
|
||||
|
||||
/** Creates [ClassSnapshot]s of the given classes. */
|
||||
fun snapshot(classes: List<ClassFileWithContents>, includeDebugInfoInJavaSnapshot: Boolean? = null): List<ClassSnapshot> {
|
||||
fun snapshot(
|
||||
classes: List<ClassFileWithContents>,
|
||||
granularity: ClassSnapshotGranularity = CLASS_MEMBER_LEVEL,
|
||||
includeDebugInfoInJavaSnapshot: Boolean = false
|
||||
): List<ClassSnapshot> {
|
||||
// Find inaccessible classes first
|
||||
val classesInfo: List<BasicClassInfo> = classes.map { it.classInfo }
|
||||
val inaccessibleClassesInfo: Set<BasicClassInfo> = getInaccessibleClasses(classesInfo).toSet()
|
||||
@@ -48,23 +54,35 @@ object ClassSnapshotter {
|
||||
return classes.map {
|
||||
when {
|
||||
it.classInfo in inaccessibleClassesInfo -> InaccessibleClassSnapshot
|
||||
it.classInfo.isKotlinClass -> snapshotKotlinClass(it)
|
||||
else -> JavaClassSnapshotter.snapshot(it, includeDebugInfoInJavaSnapshot)
|
||||
it.classInfo.isKotlinClass -> snapshotKotlinClass(it, granularity)
|
||||
else -> JavaClassSnapshotter.snapshot(it, granularity, includeDebugInfoInJavaSnapshot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates [KotlinClassSnapshot] of the given Kotlin class (the caller must ensure that the given class is a Kotlin class). */
|
||||
private fun snapshotKotlinClass(classFile: ClassFileWithContents): KotlinClassSnapshot {
|
||||
private fun snapshotKotlinClass(classFile: ClassFileWithContents, granularity: ClassSnapshotGranularity): KotlinClassSnapshot {
|
||||
val kotlinClassInfo =
|
||||
KotlinClassInfo.createFrom(classFile.classInfo.classId, classFile.classInfo.kotlinClassHeader!!, classFile.contents)
|
||||
val packageMembers = when (kotlinClassInfo.classKind) {
|
||||
CLASS, MULTIFILE_CLASS -> null // See `KotlinClassSnapshot.packageMembers`'s kdoc
|
||||
else -> (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames().map {
|
||||
PackageMember(kotlinClassInfo.classId.packageFqName, it)
|
||||
}
|
||||
val classId = kotlinClassInfo.classId
|
||||
val classAbiHash = KotlinClassInfoExternalizer.toByteArray(kotlinClassInfo).md5()
|
||||
val classMemberLevelSnapshot = kotlinClassInfo.takeIf { granularity == CLASS_MEMBER_LEVEL }
|
||||
|
||||
return when (kotlinClassInfo.classKind) {
|
||||
CLASS -> RegularKotlinClassSnapshot(classId, classAbiHash, classMemberLevelSnapshot, classFile.classInfo.supertypes)
|
||||
FILE_FACADE, MULTIFILE_CLASS_PART -> PackageFacadeKotlinClassSnapshot(
|
||||
classId, classAbiHash, classMemberLevelSnapshot,
|
||||
packageMembers = PackageMemberSet(
|
||||
mapOf(classId.packageFqName to (kotlinClassInfo.protoData as PackagePartProtoData).getNonPrivateMemberNames())
|
||||
)
|
||||
)
|
||||
MULTIFILE_CLASS -> MultifileClassKotlinClassSnapshot(
|
||||
classId, classAbiHash, classMemberLevelSnapshot,
|
||||
constants = PackageMemberSet(mapOf(classId.packageFqName to kotlinClassInfo.constantsMap.keys))
|
||||
)
|
||||
SYNTHETIC_CLASS -> error("Unexpected class $classId with class kind ${SYNTHETIC_CLASS.name} (synthetic classes should have been removed earlier)")
|
||||
UNKNOWN -> error("Can't handle class $classId with class kind ${UNKNOWN.name}")
|
||||
}
|
||||
return KotlinClassSnapshot(kotlinClassInfo, classFile.classInfo.supertypes, packageMembers)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+26
-8
@@ -54,24 +54,42 @@ object JavaClassChangesComputer {
|
||||
previousClassSnapshot: JavaClassSnapshot,
|
||||
changes: ChangeSet.Collector
|
||||
) {
|
||||
if (currentClassSnapshot.classAbiHash == previousClassSnapshot.classAbiHash) return
|
||||
|
||||
val classId = currentClassSnapshot.classId.also { check(it == previousClassSnapshot.classId) }
|
||||
if (currentClassSnapshot.classAbiExcludingMembers.abiHash != previousClassSnapshot.classAbiExcludingMembers.abiHash) {
|
||||
changes.addChangedClass(classId)
|
||||
if (currentClassSnapshot.classMemberLevelSnapshot != null && previousClassSnapshot.classMemberLevelSnapshot != null) {
|
||||
if (currentClassSnapshot.classMemberLevelSnapshot.classAbiExcludingMembers.abiHash
|
||||
!= previousClassSnapshot.classMemberLevelSnapshot.classAbiExcludingMembers.abiHash
|
||||
) {
|
||||
changes.addChangedClass(classId)
|
||||
} else {
|
||||
collectClassMemberChanges(
|
||||
classId,
|
||||
currentClassSnapshot.classMemberLevelSnapshot.fieldsAbi,
|
||||
previousClassSnapshot.classMemberLevelSnapshot.fieldsAbi,
|
||||
changes
|
||||
)
|
||||
collectClassMemberChanges(
|
||||
classId,
|
||||
currentClassSnapshot.classMemberLevelSnapshot.methodsAbi,
|
||||
previousClassSnapshot.classMemberLevelSnapshot.methodsAbi,
|
||||
changes
|
||||
)
|
||||
}
|
||||
} else {
|
||||
collectClassMemberChanges(classId, currentClassSnapshot.fieldsAbi, previousClassSnapshot.fieldsAbi, changes)
|
||||
collectClassMemberChanges(classId, currentClassSnapshot.methodsAbi, previousClassSnapshot.methodsAbi, changes)
|
||||
changes.addChangedClass(classId)
|
||||
}
|
||||
}
|
||||
|
||||
/** Collects changes between two lists of fields/methods within a class. */
|
||||
private fun collectClassMemberChanges(
|
||||
classId: ClassId,
|
||||
currentMemberSnapshots: List<AbiSnapshot>,
|
||||
previousMemberSnapshots: List<AbiSnapshot>,
|
||||
currentMemberSnapshots: List<JavaElementSnapshot>,
|
||||
previousMemberSnapshots: List<JavaElementSnapshot>,
|
||||
changes: ChangeSet.Collector
|
||||
) {
|
||||
val currentMemberHashes: Map<Long, AbiSnapshot> = currentMemberSnapshots.associateBy { it.abiHash }
|
||||
val previousMemberHashes: Map<Long, AbiSnapshot> = previousMemberSnapshots.associateBy { it.abiHash }
|
||||
val currentMemberHashes: Map<Long, JavaElementSnapshot> = currentMemberSnapshots.associateBy { it.abiHash }
|
||||
val previousMemberHashes: Map<Long, JavaElementSnapshot> = previousMemberSnapshots.associateBy { it.abiHash }
|
||||
|
||||
val addedMembers = currentMemberHashes.keys - previousMemberHashes.keys
|
||||
val removedMembers = previousMemberHashes.keys - currentMemberHashes.keys
|
||||
|
||||
+20
-7
@@ -6,7 +6,9 @@
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
|
||||
import org.jetbrains.kotlin.incremental.md5
|
||||
import org.jetbrains.kotlin.incremental.storage.toByteArray
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
@@ -14,7 +16,11 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
/** Computes a [JavaClassSnapshot] of a Java class. */
|
||||
object JavaClassSnapshotter {
|
||||
|
||||
fun snapshot(classFile: ClassFileWithContents, includeDebugInfoInSnapshot: Boolean? = null): JavaClassSnapshot {
|
||||
fun snapshot(
|
||||
classFile: ClassFileWithContents,
|
||||
granularity: ClassSnapshotGranularity,
|
||||
includeDebugInfoInSnapshot: Boolean
|
||||
): JavaClassSnapshot {
|
||||
// We will extract ABI information from the given class and store it into the `abiClass` variable.
|
||||
// It is acceptable to collect more info than required, but it is incorrect to collect less info than required.
|
||||
// There are 2 approaches:
|
||||
@@ -52,9 +58,12 @@ object JavaClassSnapshotter {
|
||||
abiClass.methods.clear()
|
||||
val classAbiExcludingMembers = abiClass.let { snapshotJavaElement(it, it.name, includeDebugInfoInSnapshot) }
|
||||
|
||||
val detailedSnapshot = JavaClassMemberLevelSnapshot(classAbiExcludingMembers, fieldsAbi, methodsAbi)
|
||||
return JavaClassSnapshot(
|
||||
classFile.classInfo.classId, classFile.classInfo.supertypes,
|
||||
classAbiExcludingMembers, fieldsAbi, methodsAbi
|
||||
classId = classFile.classInfo.classId,
|
||||
classAbiHash = JavaClassMemberLevelSnapshotExternalizer.toByteArray(detailedSnapshot).md5(),
|
||||
classMemberLevelSnapshot = detailedSnapshot.takeIf { granularity == CLASS_MEMBER_LEVEL },
|
||||
supertypes = classFile.classInfo.supertypes
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,15 +82,19 @@ object JavaClassSnapshotter {
|
||||
.create()
|
||||
}
|
||||
|
||||
private fun snapshotJavaElement(javaElement: Any, javaElementName: String, includeDebugInfoInSnapshot: Boolean? = null): AbiSnapshot {
|
||||
return if (includeDebugInfoInSnapshot == true) {
|
||||
private fun snapshotJavaElement(
|
||||
javaElement: Any,
|
||||
javaElementName: String,
|
||||
includeDebugInfoInSnapshot: Boolean
|
||||
): JavaElementSnapshot {
|
||||
return if (includeDebugInfoInSnapshot) {
|
||||
val abiValue = gsonForDebug.toJson(javaElement)
|
||||
val abiHash = abiValue.toByteArray().md5()
|
||||
AbiSnapshotForTests(javaElementName, abiHash, abiValue)
|
||||
JavaElementSnapshotForTests(javaElementName, abiHash, abiValue)
|
||||
} else {
|
||||
val abiValue = gson.toJson(javaElement)
|
||||
val abiHash = abiValue.toByteArray().md5()
|
||||
AbiSnapshot(javaElementName, abiHash)
|
||||
JavaElementSnapshot(javaElementName, abiHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
-17
@@ -6,7 +6,6 @@
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
@@ -21,6 +20,39 @@ data class ClassMember(val classId: ClassId, val memberName: String) : ProgramSy
|
||||
|
||||
data class PackageMember(val packageFqName: FqName, val memberName: String) : ProgramSymbol()
|
||||
|
||||
/** Compact representation for a set of [PackageMember]s. */
|
||||
class PackageMemberSet(val packageToMembersMap: Map<FqName, Set<String>>) {
|
||||
|
||||
operator fun contains(other: PackageMember): Boolean {
|
||||
return packageToMembersMap[other.packageFqName]?.let { other.memberName in it } ?: false
|
||||
}
|
||||
|
||||
fun containsElementsIn(other: PackageMemberSet): Boolean {
|
||||
return this.packageToMembersMap.keys.intersect(other.packageToMembersMap.keys).any { commonPackage ->
|
||||
val otherMembers = other.packageToMembersMap[commonPackage]!!
|
||||
this.packageToMembersMap[commonPackage]!!.any { it in otherMembers }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Set<PackageMember>.compact(): PackageMemberSet {
|
||||
val map = mutableMapOf<FqName, MutableSet<String>>()
|
||||
forEach {
|
||||
map.getOrPut(it.packageFqName) { mutableSetOf() }.add(it.memberName)
|
||||
}
|
||||
return PackageMemberSet(map)
|
||||
}
|
||||
|
||||
fun List<PackageMemberSet>.combine(): PackageMemberSet {
|
||||
val combinedMap = mutableMapOf<FqName, MutableSet<String>>()
|
||||
forEach {
|
||||
it.packageToMembersMap.forEach { (packageFqName, memberNames) ->
|
||||
combinedMap.getOrPut(packageFqName) { mutableSetOf() }.addAll(memberNames)
|
||||
}
|
||||
}
|
||||
return PackageMemberSet(combinedMap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds [LookupSymbol]s that potentially refer to classes on the given classpath, and returns the [ProgramSymbol]s corresponding to those
|
||||
* [LookupSymbol]s.
|
||||
@@ -30,24 +62,23 @@ data class PackageMember(val packageFqName: FqName, val memberName: String) : Pr
|
||||
*
|
||||
* The given classpath must not contain duplicate classes.
|
||||
*
|
||||
* It's okay if the returned result is an over-approximation.
|
||||
* It's okay to over-approximate the result.
|
||||
*/
|
||||
internal fun Collection<LookupSymbol>.filterLookupSymbols(classpath: List<ClassSnapshot>): List<ProgramSymbol> {
|
||||
val (packageFacades, regularClasses) = classpath.partition {
|
||||
it is KotlinClassSnapshot && it.classInfo.classKind != KotlinClassHeader.Kind.CLASS
|
||||
internal fun Collection<LookupSymbol>.filterLookupSymbols(classpath: List<AccessibleClassSnapshot>): List<ProgramSymbol> {
|
||||
val regularClassesOnClasspath: List<ClassId> = classpath.mapNotNull {
|
||||
when (it) {
|
||||
is RegularKotlinClassSnapshot -> it.classId
|
||||
is JavaClassSnapshot -> it.classId
|
||||
is PackageFacadeKotlinClassSnapshot, is MultifileClassKotlinClassSnapshot -> null
|
||||
}
|
||||
}
|
||||
val regularClassesOnClasspath: List<ClassId> = regularClasses.map { it.getClassId() }
|
||||
val packageMembersOnClasspath: Set<PackageMember> =
|
||||
packageFacades.flatMap {
|
||||
if ((it as KotlinClassSnapshot).classInfo.classKind == KotlinClassHeader.Kind.MULTIFILE_CLASS) {
|
||||
// If classKind == MULTIFILE_CLASS, we don't have the information about its package members (see
|
||||
// `KotlinClassSnapshot.packageMembers`'s kdoc). However, package members in a MULTIFILE_CLASS should be found in
|
||||
// MULTIFILE_CLASS_PART classes, so it's okay to ignore MULTIFILE_CLASS here.
|
||||
emptyList()
|
||||
} else {
|
||||
it.packageMembers!!
|
||||
}
|
||||
}.toSet() // Use Set for presence check
|
||||
val packageMembersOnClasspath: PackageMemberSet = classpath.mapNotNull {
|
||||
when (it) {
|
||||
is RegularKotlinClassSnapshot, is JavaClassSnapshot -> null
|
||||
is PackageFacadeKotlinClassSnapshot -> it.packageMembers
|
||||
is MultifileClassKotlinClassSnapshot -> it.constants
|
||||
}
|
||||
}.combine()
|
||||
|
||||
// It's rare but possible for 2 ClassIds to have the same FqName (e.g., ClassId `com/example/Foo` and ClassId `com/example.Foo` both
|
||||
// have FqName `com.example.Foo`. ClassId `com/example/Foo` indicates class `Foo` in package `com/example', whereas ClassId
|
||||
|
||||
+135
-22
@@ -8,20 +8,22 @@ package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
import org.jetbrains.kotlin.build.report.metrics.DoNothingBuildMetricsReporter
|
||||
import org.jetbrains.kotlin.incremental.ChangesEither
|
||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_LEVEL
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClassSnapshotGranularity.CLASS_MEMBER_LEVEL
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.asFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.sam.SAM_LOOKUP_NAME
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import kotlin.test.fail
|
||||
|
||||
abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
private val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest")
|
||||
|
||||
companion object {
|
||||
val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathChangesComputerTest")
|
||||
}
|
||||
abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
@Test
|
||||
abstract fun testAbiVersusNonAbiChanges()
|
||||
@@ -29,6 +31,12 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
|
||||
@Test
|
||||
abstract fun testModifiedAddedRemovedElements()
|
||||
|
||||
@Test
|
||||
abstract fun testModifiedAddedRemovedElements_ClassLevelSnapshot()
|
||||
|
||||
@Test
|
||||
abstract fun testMixedClassSnapshotGranularities()
|
||||
|
||||
@Test
|
||||
abstract fun testImpactAnalysis()
|
||||
}
|
||||
@@ -80,6 +88,49 @@ class KotlinOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testModifiedAddedRemovedElements_ClassLevelSnapshot() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/kotlin"), tmpDir, CLASS_LEVEL)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "ModifiedClassUnchangedMembers", scope = "com.example"),
|
||||
LookupSymbol(name = "ModifiedClassChangedMembers", scope = "com.example"),
|
||||
LookupSymbol(name = "AddedClass", scope = "com.example"),
|
||||
LookupSymbol(name = "RemovedClass", scope = "com.example"),
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ModifiedClassUnchangedMembers",
|
||||
"com.example.ModifiedClassChangedMembers",
|
||||
"com.example.AddedClass",
|
||||
"com.example.RemovedClass",
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testMixedClassSnapshotGranularities() {
|
||||
val currentClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("kotlin", "current-classpath", tmpDir)
|
||||
val previousClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("kotlin", "previous-classpath", tmpDir)
|
||||
|
||||
val changes = computeClasspathChanges(currentClasspathSnapshot, previousClasspathSnapshot)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"),
|
||||
LookupSymbol(name = "CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class", scope = "com.example"),
|
||||
LookupSymbol(name = "FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"),
|
||||
LookupSymbol(name = "modifiedProperty", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"),
|
||||
LookupSymbol(name = "modifiedFunction", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class",
|
||||
"com.example.CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class",
|
||||
"com.example.FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class",
|
||||
"com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testImpactAnalysis() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinOnly/src"), tmpDir)
|
||||
@@ -230,6 +281,49 @@ class JavaOnlyClasspathChangesComputerTest : ClasspathChangesComputerTest() {
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testModifiedAddedRemovedElements_ClassLevelSnapshot() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testModifiedAddedRemovedElements/src/java"), tmpDir, CLASS_LEVEL)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "ModifiedClassUnchangedMembers", scope = "com.example"),
|
||||
LookupSymbol(name = "ModifiedClassChangedMembers", scope = "com.example"),
|
||||
LookupSymbol(name = "AddedClass", scope = "com.example"),
|
||||
LookupSymbol(name = "RemovedClass", scope = "com.example")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.ModifiedClassUnchangedMembers",
|
||||
"com.example.ModifiedClassChangedMembers",
|
||||
"com.example.AddedClass",
|
||||
"com.example.RemovedClass"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testMixedClassSnapshotGranularities() {
|
||||
val currentClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("java", "current-classpath", tmpDir)
|
||||
val previousClasspathSnapshot = testMixedClassSnapshotGranularities_snapshotClasspath("java", "previous-classpath", tmpDir)
|
||||
|
||||
val changes = computeClasspathChanges(currentClasspathSnapshot, previousClasspathSnapshot)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"),
|
||||
LookupSymbol(name = "CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class", scope = "com.example"),
|
||||
LookupSymbol(name = "FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class", scope = "com.example"),
|
||||
LookupSymbol(name = "modifiedField", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"),
|
||||
LookupSymbol(name = "modifiedMethod", scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"),
|
||||
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class")
|
||||
),
|
||||
fqNames = setOf(
|
||||
"com.example.CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class",
|
||||
"com.example.CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class",
|
||||
"com.example.FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class",
|
||||
"com.example.FineGrainedFirstBuild_FineGrainedSecondBuild_Class"
|
||||
)
|
||||
).assertEquals(changes)
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun testImpactAnalysis() {
|
||||
val changes = computeClasspathChanges(File(testDataDir, "testImpactAnalysis_JavaOnly/src"), tmpDir)
|
||||
@@ -262,7 +356,7 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon()
|
||||
@Test
|
||||
fun testImpactAnalysis() {
|
||||
val changes =
|
||||
computeClasspathChanges(File(ClasspathChangesComputerTest.testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir)
|
||||
computeClasspathChanges(File(testDataDir, "testImpactAnalysis_KotlinAndJava/src"), tmpDir)
|
||||
Changes(
|
||||
lookupSymbols = setOf(
|
||||
LookupSymbol(name = "changedProperty", scope = "com.example.ChangedKotlinSuperClass"),
|
||||
@@ -296,25 +390,44 @@ class KotlinAndJavaClasspathChangesComputerTest : ClasspathSnapshotTestCommon()
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeClasspathChanges(classpathSourceDir: File, tmpDir: TemporaryFolder): Changes {
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir)
|
||||
return computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
private fun testMixedClassSnapshotGranularities_snapshotClasspath(
|
||||
language: String, classpathSourceDirName: String, tmpDir: TemporaryFolder
|
||||
): ClasspathSnapshot {
|
||||
val classes = compileAll(File("$testDataDir/testMixedClassSnapshotGranularities/src/$language/$classpathSourceDirName/0"), tmpDir)
|
||||
|
||||
fun getGranularity(classFile: ClassFile): ClassSnapshotGranularity {
|
||||
val granularity = when (val className = classFile.asFile().nameWithoutExtension) {
|
||||
"CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class" -> CLASS_LEVEL to CLASS_LEVEL
|
||||
"CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class" -> CLASS_LEVEL to CLASS_MEMBER_LEVEL
|
||||
"FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class" -> CLASS_MEMBER_LEVEL to CLASS_LEVEL
|
||||
"FineGrainedFirstBuild_FineGrainedSecondBuild_Class" -> CLASS_MEMBER_LEVEL to CLASS_MEMBER_LEVEL
|
||||
else -> error("Unrecognized class: $className")
|
||||
}
|
||||
return when (classpathSourceDirName) {
|
||||
"previous-classpath" -> granularity.first
|
||||
"current-classpath" -> granularity.second
|
||||
else -> error("Unrecognized classpathSourceDirName: $classpathSourceDirName")
|
||||
}
|
||||
}
|
||||
|
||||
return classes.map { it.snapshot(getGranularity(it)) }.toClasspathSnapshot()
|
||||
}
|
||||
|
||||
private fun snapshotClasspath(classpathSourceDir: File, tmpDir: TemporaryFolder): ClasspathSnapshot {
|
||||
val classpath = mutableListOf<File>()
|
||||
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
|
||||
val classFiles = compileAll(classpathEntrySourceDir, classpath, tmpDir)
|
||||
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
||||
private fun List<ClassSnapshot>.toClasspathSnapshot(): ClasspathSnapshot {
|
||||
val classpathEntrySnapshot = ClasspathEntrySnapshot(associateByTo(LinkedHashMap()) {
|
||||
JvmClassName.byClassId((it as AccessibleClassSnapshot).classId).internalName + ".class"
|
||||
})
|
||||
return ClasspathSnapshot(listOf(classpathEntrySnapshot))
|
||||
}
|
||||
|
||||
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
||||
val classSnapshots = classFiles.snapshot().map { it.withHash }
|
||||
ClasspathEntrySnapshot(
|
||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
||||
)
|
||||
}
|
||||
return ClasspathSnapshot(classpathEntrySnapshots)
|
||||
private fun computeClasspathChanges(
|
||||
classpathSourceDir: File,
|
||||
tmpDir: TemporaryFolder,
|
||||
granularity: ClassSnapshotGranularity? = null
|
||||
): Changes {
|
||||
val currentSnapshot = snapshotClasspath(File(classpathSourceDir, "current-classpath"), tmpDir, granularity)
|
||||
val previousSnapshot = snapshotClasspath(File(classpathSourceDir, "previous-classpath"), tmpDir, granularity)
|
||||
return computeClasspathChanges(currentSnapshot, previousSnapshot)
|
||||
}
|
||||
|
||||
private fun computeClasspathChanges(
|
||||
|
||||
+3
-3
@@ -36,8 +36,8 @@ class KotlinClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializer
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class")
|
||||
baseDir = File(testDataDir, "kotlin/testSimpleClass/src"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "kotlin/testSimpleClass/classes"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTe
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java",
|
||||
baseDir = File(testDataDir, "java/testSimpleClass/src"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
}
|
||||
|
||||
+62
-9
@@ -6,14 +6,17 @@
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import org.jetbrains.kotlin.cli.common.isWindows
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.asFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.CompileUtil.compileAll
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import java.lang.ProcessBuilder.Redirect
|
||||
|
||||
abstract class ClasspathSnapshotTestCommon {
|
||||
|
||||
@@ -68,12 +71,12 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
} else {
|
||||
val srcDir = tmpDir.newFolder()
|
||||
asFile().copyTo(File(srcDir, unixStyleRelativePath))
|
||||
compileAll(srcDir, classpath = emptyList(), tmpDir)
|
||||
compileAll(srcDir, tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
/** Compiles the source files in the given directory and returns all generated .class files. */
|
||||
fun compileAll(srcDir: File, classpath: List<File>, tmpDir: TemporaryFolder): List<ClassFile> {
|
||||
fun compileAll(srcDir: File, tmpDir: TemporaryFolder, classpath: List<File> = emptyList()): List<ClassFile> {
|
||||
val kotlinClasses = compileKotlin(srcDir, classpath, tmpDir)
|
||||
|
||||
val javaClasspath = classpath + listOfNotNull(kotlinClasses.firstOrNull()?.classRoot)
|
||||
@@ -126,11 +129,20 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
}
|
||||
|
||||
val classesDir = tmpDir.newFolder()
|
||||
org.jetbrains.kotlin.test.MockLibraryUtil.compileKotlin(
|
||||
// Note: Calling the following is simpler:
|
||||
// org.jetbrains.kotlin.test.MockLibraryUtil.compileKotlin(
|
||||
// srcDir.path, classesDir, extraClasspath = classpath.map { it.path }.toTypedArray())
|
||||
// However, it currently fails with UnsupportedClassVersionError, so we have to launch a new kotlinc process instead.
|
||||
val kotlincBinary = if (isWindows) "dist/kotlinc/bin/kotlinc.bat" else "dist/kotlinc/bin/kotlinc"
|
||||
check(File(kotlincBinary).exists()) { "'${File(kotlincBinary).absolutePath}' not found. Run ./gradlew dist first." }
|
||||
val commandAndArgs = listOf(
|
||||
kotlincBinary,
|
||||
srcDir.path,
|
||||
classesDir,
|
||||
extraClasspath = classpath.map { it.path }.toTypedArray()
|
||||
"-d", classesDir.path,
|
||||
"-classpath", (listOf(srcDir) + classpath).joinToString(File.pathSeparator) { it.path }
|
||||
)
|
||||
runCommandInNewProcess(commandAndArgs)
|
||||
|
||||
return getClassFilesInDir(classesDir)
|
||||
}
|
||||
|
||||
@@ -164,11 +176,52 @@ abstract class ClasspathSnapshotTestCommon {
|
||||
|
||||
fun ClassFile.readBytes() = asFile().readBytes()
|
||||
|
||||
fun ClassFile.snapshot(): ClassSnapshot = listOf(this).snapshot().single()
|
||||
fun ClassFile.snapshot(granularity: ClassSnapshotGranularity? = null): ClassSnapshot = listOf(this).snapshot(granularity).single()
|
||||
|
||||
fun List<ClassFile>.snapshot(): List<ClassSnapshot> {
|
||||
val classFilesWithContents = this.map { ClassFileWithContents(it, it.readBytes()) }
|
||||
return ClassSnapshotter.snapshot(classFilesWithContents)
|
||||
fun List<ClassFile>.snapshot(granularity: ClassSnapshotGranularity? = null): List<ClassSnapshot> {
|
||||
val classes = map { ClassFileWithContents(it, it.readBytes()) }
|
||||
return if (granularity == null) {
|
||||
ClassSnapshotter.snapshot(classes)
|
||||
} else {
|
||||
ClassSnapshotter.snapshot(classes, granularity = granularity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun snapshotClasspath(
|
||||
classpathSourceDir: File,
|
||||
tmpDir: TemporaryFolder,
|
||||
granularity: ClassSnapshotGranularity? = null
|
||||
): ClasspathSnapshot {
|
||||
val classpath = mutableListOf<File>()
|
||||
val classpathEntrySnapshots = classpathSourceDir.listFiles()!!.sortedBy { it.name }.map { classpathEntrySourceDir ->
|
||||
val classFiles = compileAll(classpathEntrySourceDir, tmpDir, classpath)
|
||||
classpath.addAll(listOfNotNull(classFiles.firstOrNull()?.classRoot))
|
||||
|
||||
val relativePaths = classFiles.map { it.unixStyleRelativePath }
|
||||
val classSnapshots = classFiles.snapshot(granularity)
|
||||
ClasspathEntrySnapshot(
|
||||
classSnapshots = relativePaths.zip(classSnapshots).toMap(LinkedHashMap())
|
||||
)
|
||||
}
|
||||
return ClasspathSnapshot(classpathEntrySnapshots)
|
||||
}
|
||||
|
||||
private fun runCommandInNewProcess(commandAndArgs: List<String>) {
|
||||
val processBuilder = ProcessBuilder(commandAndArgs)
|
||||
processBuilder.redirectInput(Redirect.INHERIT)
|
||||
processBuilder.redirectOutput(Redirect.INHERIT)
|
||||
processBuilder.redirectErrorStream(true)
|
||||
val process = processBuilder.start()
|
||||
|
||||
val exitCode = try {
|
||||
process.waitFor()
|
||||
} finally {
|
||||
process.destroyForcibly()
|
||||
}
|
||||
check(exitCode == 0) {
|
||||
"Process returned exit code: $exitCode\n" +
|
||||
"commandAndArgs = ${commandAndArgs.joinToString(" ")}"
|
||||
}
|
||||
}
|
||||
|
||||
+129
-64
@@ -6,94 +6,159 @@
|
||||
package org.jetbrains.kotlin.incremental.classpathDiff
|
||||
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.readBytes
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.ClassFileUtil.snapshot
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.JavaSourceFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.SourceFile.KotlinSourceFile
|
||||
import org.jetbrains.kotlin.incremental.classpathDiff.ClasspathSnapshotTestCommon.TestSourceFile
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import java.io.File
|
||||
|
||||
abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||
private val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest")
|
||||
|
||||
companion object {
|
||||
val testDataDir =
|
||||
File("compiler/incremental-compilation-impl/testData/org/jetbrains/kotlin/incremental/classpathDiff/ClasspathSnapshotterTest")
|
||||
}
|
||||
class KotlinOnlyClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
protected abstract val sourceFile: TestSourceFile
|
||||
protected abstract val sourceFileWithAbiChange: TestSourceFile
|
||||
protected abstract val sourceFileWithNonAbiChange: TestSourceFile
|
||||
|
||||
private val expectedSnapshotFile: File
|
||||
get() = sourceFile.asFile().let {
|
||||
val srcDir = File(it.path.substringBeforeLast("src") + "src")
|
||||
val relativePath = it.relativeTo(srcDir)
|
||||
testDataDir.resolve("expected-snapshot").resolve(relativePath.parent).resolve(relativePath.nameWithoutExtension + ".json")
|
||||
}
|
||||
@Suppress("SameParameterValue")
|
||||
private fun getSourceFile(testName: String, relativePath: String) = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File("$testDataDir/kotlin/$testName/src"), relativePath = relativePath,
|
||||
preCompiledClassFile = ClassFile(File("$testDataDir/kotlin/$testName/classes"), relativePath.replace(".kt", ".class"))
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter's result against expected snapshot`() {
|
||||
val classSnapshot = sourceFile.compileSingle().let {
|
||||
ClassSnapshotter.snapshot(listOf(ClassFileWithContents(it, it.readBytes())), includeDebugInfoInJavaSnapshot = true)
|
||||
}.single()
|
||||
fun testSimpleClass() {
|
||||
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.kt")
|
||||
val actualSnapshot = sourceFile.compileAndSnapshot().toGson()
|
||||
val expectedSnapshot = sourceFile.getExpectedSnapshotFile().readText()
|
||||
|
||||
assertEquals(expectedSnapshotFile.readText(), classSnapshot.toGson())
|
||||
assertEquals(expectedSnapshot, actualSnapshot)
|
||||
|
||||
// Check that the snapshot contains ABI info
|
||||
actualSnapshot.assertContains("publicProperty", "publicFunction")
|
||||
|
||||
// Private properties and functions' names/signatures are currently part of the snapshot. We will fix this later.
|
||||
actualSnapshot.assertContains("privateProperty", "privateFunction")
|
||||
|
||||
// Check that the snapshot does not contain non-ABI info
|
||||
actualSnapshot.assertDoesNotContain(
|
||||
"publicProperty's value",
|
||||
"privateProperty's value",
|
||||
"publicFunction's body",
|
||||
"privateFunction's body"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter extracts ABI info from a class`() {
|
||||
// After an ABI change, the snapshot must change
|
||||
assertNotEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithAbiChange.compileAndSnapshot().toGson())
|
||||
fun testSimpleClass_ClassLevelSnapshot() {
|
||||
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.kt")
|
||||
val classFile = sourceFile.compileSingle()
|
||||
val actualSnapshot = classFile.snapshot(ClassSnapshotGranularity.CLASS_LEVEL).toGson()
|
||||
val expectedSnapshot = sourceFile.getExpectedSnapshotFile(ClassSnapshotGranularity.CLASS_LEVEL).readText()
|
||||
|
||||
assertEquals(expectedSnapshot, actualSnapshot)
|
||||
|
||||
// Check that the snapshot does not contain class member details
|
||||
actualSnapshot.assertDoesNotContain("publicProperty", "privateProperty", "publicFunction", "privateFunction")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test ClassSnapshotter does not extract non-ABI info from a class`() {
|
||||
// After a non-ABI change, the snapshot must not change
|
||||
assertEquals(sourceFile.compileAndSnapshot().toGson(), sourceFileWithNonAbiChange.compileAndSnapshot().toGson())
|
||||
fun testPackageFacadeClasses() {
|
||||
val classpathSnapshot = snapshotClasspath(File("$testDataDir/kotlin/testPackageFacadeClasses/src"), tmpDir)
|
||||
val classSnapshots = classpathSnapshot.classpathEntrySnapshots.single().classSnapshots
|
||||
val fileFacadeSnapshot = classSnapshots["com/example/FileFacadeKt.class"]!!.toGson()
|
||||
val multifileClassSnapshot = classSnapshots["com/example/MultifileClass.class"]!!.toGson()
|
||||
val multifileClassPart1Snapshot = classSnapshots["com/example/MultifileClass__MultifileClass1Kt.class"]!!.toGson()
|
||||
val multifileClassPart2Snapshot = classSnapshots["com/example/MultifileClass__MultifileClass2Kt.class"]!!.toGson()
|
||||
|
||||
// Check that the snapshots contain ABI info
|
||||
fileFacadeSnapshot.assertContains("propertyInFileFacade", "functionInFileFacade")
|
||||
multifileClassPart1Snapshot.assertContains("propertyInMultifileClass1", "functionInMultifileClass1")
|
||||
multifileClassPart2Snapshot.assertContains("propertyInMultifileClass2", "functionInMultifileClass2")
|
||||
|
||||
// Check that the snapshots do not contain non-ABI info
|
||||
fileFacadeSnapshot.assertDoesNotContain("propertyInFileFacade's value", "functionInFileFacade's body")
|
||||
multifileClassPart1Snapshot.assertDoesNotContain("propertyInMultifileClass1's value", "functionInMultifileClass1's body")
|
||||
multifileClassPart2Snapshot.assertDoesNotContain("propertyInMultifileClass2's value", "functionInMultifileClass2's body")
|
||||
|
||||
// Classes with MULTIFILE_CLASS kind have no proto data
|
||||
multifileClassSnapshot.assertDoesNotContain(
|
||||
"propertyInMultifileClass1",
|
||||
"functionInMultifileClass1",
|
||||
"propertyInMultifileClass2",
|
||||
"functionInMultifileClass2"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinOnlyClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
class JavaOnlyClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src/kotlin"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes/kotlin"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
@Suppress("SameParameterValue")
|
||||
private fun getSourceFile(testName: String, relativePath: String) = TestSourceFile(
|
||||
JavaSourceFile(baseDir = File("$testDataDir/java/$testName/src"), relativePath = relativePath), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithAbiChange = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/kotlin/abi-change"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotllin/abi-change"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
private fun TestSourceFile.compileAndSnapshotWithDebugInfo(): ClassSnapshot {
|
||||
val classFile = compileSingle()
|
||||
return ClassSnapshotter.snapshot(
|
||||
listOf(ClassFileWithContents(classFile, classFile.readBytes())),
|
||||
includeDebugInfoInJavaSnapshot = true
|
||||
).single()
|
||||
}
|
||||
|
||||
override val sourceFileWithNonAbiChange = TestSourceFile(
|
||||
KotlinSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/kotlin/non-abi-change"), relativePath = "com/example/SimpleClass.kt",
|
||||
preCompiledClassFile = ClassFile(File(testDataDir, "classes-changed/kotlin/non-abi-change"), "com/example/SimpleClass.class")
|
||||
), tmpDir
|
||||
)
|
||||
@Test
|
||||
fun testSimpleClass() {
|
||||
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.java")
|
||||
val actualSnapshot = sourceFile.compileAndSnapshotWithDebugInfo().toGson()
|
||||
val expectedSnapshot = sourceFile.getExpectedSnapshotFile().readText()
|
||||
|
||||
assertEquals(expectedSnapshot, actualSnapshot)
|
||||
|
||||
// Check that the snapshot contains ABI info
|
||||
actualSnapshot.assertContains("publicField", "publicMethod")
|
||||
|
||||
// Check that the snapshot does not contain non-ABI info
|
||||
actualSnapshot.assertDoesNotContain(
|
||||
"privateField",
|
||||
"privateMethod",
|
||||
"publicField's value",
|
||||
"privateField's value",
|
||||
"publicMethod's body",
|
||||
"privateMethod's body"
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleClass_ClassLevelSnapshot() {
|
||||
val sourceFile = getSourceFile("testSimpleClass", "com/example/SimpleClass.java")
|
||||
val classFile = sourceFile.compileSingle()
|
||||
val actualSnapshot = classFile.snapshot(ClassSnapshotGranularity.CLASS_LEVEL).toGson()
|
||||
val expectedSnapshot = sourceFile.getExpectedSnapshotFile(ClassSnapshotGranularity.CLASS_LEVEL).readText()
|
||||
|
||||
assertEquals(expectedSnapshot, actualSnapshot)
|
||||
|
||||
// Check that the snapshot does not contain class member details
|
||||
actualSnapshot.assertDoesNotContain("publicField", "privateField", "publicMethod", "privateMethod")
|
||||
}
|
||||
}
|
||||
|
||||
class JavaOnlyClasspathSnapshotterTest : ClasspathSnapshotterTest() {
|
||||
|
||||
override val sourceFile = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src/java"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithAbiChange = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/java/abi-change"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
|
||||
override val sourceFileWithNonAbiChange = TestSourceFile(
|
||||
JavaSourceFile(
|
||||
baseDir = File(testDataDir, "src-changed/java/non-abi-change"), relativePath = "com/example/SimpleClass.java",
|
||||
), tmpDir
|
||||
)
|
||||
private fun TestSourceFile.getExpectedSnapshotFile(granularity: ClassSnapshotGranularity? = null): File {
|
||||
val relativePath = sourceFile.unixStyleRelativePath.substringBeforeLast(".") + ".json"
|
||||
val expectedSnapshotDirName = if (granularity == null) "expected-snapshot" else "expected-snapshot-${granularity.name}"
|
||||
return sourceFile.baseDir.resolve("../$expectedSnapshotDirName/$relativePath")
|
||||
}
|
||||
|
||||
private fun String.assertContains(vararg elements: String) {
|
||||
elements.forEach {
|
||||
assertTrue(contains(it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.assertDoesNotContain(vararg elements: String) {
|
||||
elements.forEach {
|
||||
assertFalse(contains(it))
|
||||
}
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
@@ -1,5 +1,6 @@
|
||||
package com.example
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
class SomeClass {
|
||||
|
||||
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
package com.example
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
class SomeClass {
|
||||
|
||||
// Constants are not allowed in a class, only allowed at the top level or in an object.
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
|
||||
public long modifiedField = 0;
|
||||
|
||||
public long modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
|
||||
public long modifiedField = 0;
|
||||
|
||||
public long modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
|
||||
public long modifiedField = 0;
|
||||
|
||||
public long modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
|
||||
public long modifiedField = 0;
|
||||
|
||||
public long modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
|
||||
public int modifiedField = 0;
|
||||
|
||||
public int modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
|
||||
public int modifiedField = 0;
|
||||
|
||||
public int modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
|
||||
public int modifiedField = 0;
|
||||
|
||||
public int modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.example;
|
||||
|
||||
public class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
|
||||
public int modifiedField = 0;
|
||||
|
||||
public int modifiedMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Long = 0
|
||||
fun modifiedFunction(): Long = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Long = 0
|
||||
fun modifiedFunction(): Long = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Long = 0
|
||||
fun modifiedFunction(): Long = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Long = 0
|
||||
fun modifiedFunction(): Long = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class CoarseGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Int = 0
|
||||
fun modifiedFunction(): Int = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class CoarseGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Int = 0
|
||||
fun modifiedFunction(): Int = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class FineGrainedFirstBuild_CoarseGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Int = 0
|
||||
fun modifiedFunction(): Int = 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.example
|
||||
|
||||
class FineGrainedFirstBuild_FineGrainedSecondBuild_Class {
|
||||
val modifiedProperty: Int = 0
|
||||
fun modifiedFunction(): Int = 0
|
||||
}
|
||||
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.example
|
||||
|
||||
@java.lang.Deprecated // Changed annotation
|
||||
@kotlin.Deprecated("") // Changed annotation
|
||||
class ModifiedClassUnchangedMembers {
|
||||
val unchangedProperty = 0
|
||||
fun unchangedFunction() {}
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-52
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
"fqName": "com.example"
|
||||
}
|
||||
},
|
||||
"relativeClassName": {
|
||||
"fqName": {
|
||||
"fqName": "SimpleClass"
|
||||
}
|
||||
},
|
||||
"local": false
|
||||
},
|
||||
"supertypes": [
|
||||
{
|
||||
"internalName": "java/lang/Object"
|
||||
}
|
||||
],
|
||||
"classAbiExcludingMembers": {
|
||||
"abiValue": "{\n \"version\": 52,\n \"access\": 32,\n \"name\": \"com/example/SimpleClass\",\n \"superName\": \"java/lang/Object\",\n \"interfaces\": [],\n \"sourceFile\": \"SimpleClass.java\",\n \"innerClasses\": [],\n \"fields\": [],\n \"methods\": [],\n \"api\": 589824\n}",
|
||||
"name": "com/example/SimpleClass",
|
||||
"abiHash": 5093067435262353961
|
||||
},
|
||||
"fieldsAbi": [
|
||||
{
|
||||
"abiValue": "{\n \"access\": 1,\n \"name\": \"publicField\",\n \"desc\": \"I\",\n \"api\": 589824\n}",
|
||||
"name": "publicField",
|
||||
"abiHash": -4066101678319939363
|
||||
}
|
||||
],
|
||||
"methodsAbi": [
|
||||
{
|
||||
"abiValue": "{\n \"access\": 0,\n \"name\": \"\\u003cinit\\u003e\",\n \"desc\": \"()V\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}",
|
||||
"name": "\u003cinit\u003e",
|
||||
"abiHash": -8222317811013429324
|
||||
},
|
||||
{
|
||||
"abiValue": "{\n \"access\": 1,\n \"name\": \"publicMethod\",\n \"desc\": \"()I\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}",
|
||||
"name": "publicMethod",
|
||||
"abiHash": -4555436938653125191
|
||||
}
|
||||
],
|
||||
"className$delegate": {
|
||||
"initializer": {},
|
||||
"_value": {}
|
||||
},
|
||||
"withHash$delegate": {
|
||||
"initializer": {},
|
||||
"_value": {}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
"fqName": "com.example"
|
||||
}
|
||||
},
|
||||
"relativeClassName": {
|
||||
"fqName": {
|
||||
"fqName": "SimpleClass"
|
||||
}
|
||||
},
|
||||
"local": false
|
||||
},
|
||||
"classAbiHash": 2419143040117344894,
|
||||
"supertypes": [
|
||||
{
|
||||
"internalName": "java/lang/Object"
|
||||
}
|
||||
]
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
"fqName": "com.example"
|
||||
}
|
||||
},
|
||||
"relativeClassName": {
|
||||
"fqName": {
|
||||
"fqName": "SimpleClass"
|
||||
}
|
||||
},
|
||||
"local": false
|
||||
},
|
||||
"classAbiHash": 7253633932031152470,
|
||||
"classMemberLevelSnapshot": {
|
||||
"classAbiExcludingMembers": {
|
||||
"abiValue": "{\n \"version\": 52,\n \"access\": 33,\n \"name\": \"com/example/SimpleClass\",\n \"superName\": \"java/lang/Object\",\n \"interfaces\": [],\n \"sourceFile\": \"SimpleClass.java\",\n \"innerClasses\": [],\n \"fields\": [],\n \"methods\": [],\n \"api\": 589824\n}",
|
||||
"name": "com/example/SimpleClass",
|
||||
"abiHash": -1986494095366785349
|
||||
},
|
||||
"fieldsAbi": [
|
||||
{
|
||||
"abiValue": "{\n \"access\": 1,\n \"name\": \"publicField\",\n \"desc\": \"Ljava/lang/String;\",\n \"api\": 589824\n}",
|
||||
"name": "publicField",
|
||||
"abiHash": 1021768301411937004
|
||||
}
|
||||
],
|
||||
"methodsAbi": [
|
||||
{
|
||||
"abiValue": "{\n \"access\": 1,\n \"name\": \"\\u003cinit\\u003e\",\n \"desc\": \"()V\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}",
|
||||
"name": "\u003cinit\u003e",
|
||||
"abiHash": 5725188035643409224
|
||||
},
|
||||
{
|
||||
"abiValue": "{\n \"access\": 1,\n \"name\": \"publicMethod\",\n \"desc\": \"()Ljava/lang/String;\",\n \"exceptions\": [],\n \"visibleAnnotableParameterCount\": 0,\n \"invisibleAnnotableParameterCount\": 0,\n \"instructions\": {\n \"size\": 0\n },\n \"tryCatchBlocks\": [],\n \"maxStack\": 0,\n \"maxLocals\": 0,\n \"localVariables\": [],\n \"visited\": false,\n \"api\": 589824\n}",
|
||||
"name": "publicMethod",
|
||||
"abiHash": 8210873051092995586
|
||||
}
|
||||
]
|
||||
},
|
||||
"supertypes": [
|
||||
{
|
||||
"internalName": "java/lang/Object"
|
||||
}
|
||||
]
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.example;
|
||||
|
||||
public class SimpleClass {
|
||||
|
||||
public String publicField = "publicField's value";
|
||||
|
||||
private String privateField = "privateField's value";
|
||||
|
||||
public String publicMethod() {
|
||||
return "publicMethod's body";
|
||||
}
|
||||
|
||||
private String privateMethod() {
|
||||
return "privateMethod's body";
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+4
@@ -0,0 +1,4 @@
|
||||
package com.example
|
||||
|
||||
val propertyInFileFacade = "propertyInFileFacade's value"
|
||||
fun functionInFileFacade() = "functionInFileFacade's body"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmName("MultifileClass")
|
||||
@file:JvmMultifileClass
|
||||
|
||||
package com.example
|
||||
|
||||
val propertyInMultifileClass1 = "propertyInMultifileClass1's value"
|
||||
fun functionInMultifileClass1() = "functionInMultifileClass1's body"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
@file:JvmName("MultifileClass")
|
||||
@file:JvmMultifileClass
|
||||
|
||||
package com.example
|
||||
|
||||
val propertyInMultifileClass2 = "propertyInMultifileClass2's value"
|
||||
fun functionInMultifileClass2() = "functionInMultifileClass2's body"
|
||||
BIN
Binary file not shown.
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
"fqName": "com.example"
|
||||
}
|
||||
},
|
||||
"relativeClassName": {
|
||||
"fqName": {
|
||||
"fqName": "SimpleClass"
|
||||
}
|
||||
},
|
||||
"local": false
|
||||
},
|
||||
"classAbiHash": -2605231127310346403,
|
||||
"supertypes": [
|
||||
{
|
||||
"internalName": "java/lang/Object"
|
||||
}
|
||||
]
|
||||
}
|
||||
+18
-8
@@ -1,5 +1,19 @@
|
||||
{
|
||||
"classInfo": {
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
"fqName": "com.example"
|
||||
}
|
||||
},
|
||||
"relativeClassName": {
|
||||
"fqName": {
|
||||
"fqName": "SimpleClass"
|
||||
}
|
||||
},
|
||||
"local": false
|
||||
},
|
||||
"classAbiHash": -2605231127310346403,
|
||||
"classMemberLevelSnapshot": {
|
||||
"classId": {
|
||||
"packageFqName": {
|
||||
"fqName": {
|
||||
@@ -15,7 +29,7 @@
|
||||
},
|
||||
"classKind": "CLASS",
|
||||
"classHeaderData": [
|
||||
"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0005\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\b\u001a\u00020\u0004H\u0002J\u0006\u0010\t\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004XD¢\u0006\u0002\n\u0000R\u0014\u0010\u0005\u001a\u00020\u0004XD¢\u0006\b\n\u0000\u001a\u0004\b\u0006\u0010\u0007"
|
||||
"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0005\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\b\u001a\u00020\u0004H\u0002J\u0006\u0010\t\u001a\u00020\u0004R\u000e\u0010\u0003\u001a\u00020\u0004XD¢\u0006\u0002\n\u0000R\u0014\u0010\u0005\u001a\u00020\u0004XD¢\u0006\b\n\u0000\u001a\u0004\b\u0006\u0010\u0007"
|
||||
],
|
||||
"classHeaderStrings": [
|
||||
"Lcom/example/SimpleClass;",
|
||||
@@ -25,7 +39,7 @@
|
||||
"",
|
||||
"publicProperty",
|
||||
"getPublicProperty",
|
||||
"()I",
|
||||
"()Ljava/lang/String;",
|
||||
"privateFunction",
|
||||
"publicFunction"
|
||||
],
|
||||
@@ -48,9 +62,5 @@
|
||||
{
|
||||
"internalName": "java/lang/Object"
|
||||
}
|
||||
],
|
||||
"withHash$delegate": {
|
||||
"initializer": {},
|
||||
"_value": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.example
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
val publicProperty: String = "publicProperty's value"
|
||||
|
||||
private val privateProperty: String = "privateProperty's value"
|
||||
|
||||
fun publicFunction(): String = "publicFunction's body"
|
||||
|
||||
private fun privateFunction(): String = "privateFunction's body"
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
public long publicField = 0;
|
||||
|
||||
private long privateField = 0;
|
||||
|
||||
public long publicMethod() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private long privateMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
public int publicField = 1000;
|
||||
|
||||
private int privateField = 1000;
|
||||
|
||||
public int publicMethod() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
private int privateMethod() {
|
||||
return 1000;
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
val publicProperty: Long = 0
|
||||
|
||||
private val privateProperty: Long = 0
|
||||
|
||||
fun publicFunction(): Long = 0
|
||||
|
||||
private fun privateFunction(): Long = 0
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
val publicProperty: Int = 1000
|
||||
|
||||
private val privateProperty: Int = 1000
|
||||
|
||||
fun publicFunction(): Int = 1000
|
||||
|
||||
private fun privateFunction(): Int = 1000
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
public int publicField = 0;
|
||||
|
||||
private int privateField = 0;
|
||||
|
||||
public int publicMethod() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int privateMethod() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package com.example
|
||||
|
||||
class SimpleClass {
|
||||
|
||||
val publicProperty: Int = 0
|
||||
|
||||
private val privateProperty: Int = 0
|
||||
|
||||
fun publicFunction(): Int = 0
|
||||
|
||||
private fun privateFunction(): Int = 0
|
||||
}
|
||||
+2
-2
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles
|
||||
import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger
|
||||
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
|
||||
import org.jetbrains.kotlin.incremental.cleanDirectoryContents
|
||||
import org.jetbrains.kotlin.incremental.forceDeleteRecursively
|
||||
import org.jetbrains.kotlin.incremental.deleteRecursivelyOrThrow
|
||||
import java.io.File
|
||||
|
||||
fun throwGradleExceptionIfError(
|
||||
@@ -63,7 +63,7 @@ internal fun cleanOutputsAndLocalState(
|
||||
}
|
||||
file.isFile -> {
|
||||
log.debug("Deleting output file: $file")
|
||||
file.forceDeleteRecursively()
|
||||
file.deleteRecursivelyOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user