Implement multiproject IC in Gradle
#KT-13528 fixed
This commit is contained in:
@@ -175,8 +175,8 @@ fun<Target> OutputItemsCollectorImpl.generatedFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class DirtyData(
|
data class DirtyData(
|
||||||
val dirtyLookupSymbols: Iterable<LookupSymbol>,
|
val dirtyLookupSymbols: Collection<LookupSymbol> = emptyList(),
|
||||||
val dirtyClassesFqNames: Iterable<FqName>
|
val dirtyClassesFqNames: Collection<FqName> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <Target> CompilationResult.getDirtyData(
|
fun <Target> CompilationResult.getDirtyData(
|
||||||
@@ -261,7 +261,7 @@ private fun File.isJavaFile() = extension.equals(JavaFileType.INSTANCE.defaultEx
|
|||||||
private fun findSrcDirRoot(file: File, roots: Iterable<File>): File? =
|
private fun findSrcDirRoot(file: File, roots: Iterable<File>): File? =
|
||||||
roots.firstOrNull { FileUtil.isAncestor(it, file, false) }
|
roots.firstOrNull { FileUtil.isAncestor(it, file, false) }
|
||||||
|
|
||||||
private fun <Target> withSubtypes(
|
fun <Target> withSubtypes(
|
||||||
typeFqName: FqName,
|
typeFqName: FqName,
|
||||||
caches: Iterable<IncrementalCacheImpl<Target>>
|
caches: Iterable<IncrementalCacheImpl<Target>>
|
||||||
): Set<FqName> {
|
): Set<FqName> {
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.tasks
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.incremental.DirtyData
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
interface ArtifactDifferenceRegistry {
|
||||||
|
operator fun get(artifact: File): Iterable<ArtifactDifference>?
|
||||||
|
fun add(artifact: File, difference: ArtifactDifference)
|
||||||
|
fun remove(artifact: File)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArtifactDifference(val buildTS: Long, val dirtyData: DirtyData)
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.tasks
|
||||||
|
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
internal data class BuildInfo(val startTS: Long) : Serializable {
|
||||||
|
companion object {
|
||||||
|
fun read(file: File): BuildInfo? =
|
||||||
|
try {
|
||||||
|
ObjectInputStream(FileInputStream(file)).use {
|
||||||
|
it.readObject() as BuildInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(buildInfo: BuildInfo, file: File) {
|
||||||
|
ObjectOutputStream(FileOutputStream(file)).use {
|
||||||
|
it.writeObject(buildInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.tasks
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||||
|
|
||||||
|
internal sealed class ChangesEither {
|
||||||
|
internal class Known(val lookupSymbols: Set<LookupSymbol>) : ChangesEither()
|
||||||
|
internal class Unknown : ChangesEither()
|
||||||
|
}
|
||||||
+149
-55
@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.gradle.tasks
|
|||||||
import org.apache.commons.io.FilenameUtils
|
import org.apache.commons.io.FilenameUtils
|
||||||
import org.codehaus.groovy.runtime.MethodClosure
|
import org.codehaus.groovy.runtime.MethodClosure
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.file.SourceDirectorySet
|
import org.gradle.api.file.SourceDirectorySet
|
||||||
import org.gradle.api.logging.Logger
|
import org.gradle.api.logging.Logger
|
||||||
import org.gradle.api.logging.Logging
|
import org.gradle.api.logging.Logging
|
||||||
@@ -42,6 +43,7 @@ import org.jetbrains.kotlin.config.Services
|
|||||||
import org.jetbrains.kotlin.incremental.*
|
import org.jetbrains.kotlin.incremental.*
|
||||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||||
import org.jetbrains.kotlin.modules.TargetId
|
import org.jetbrains.kotlin.modules.TargetId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
|
||||||
import org.jetbrains.kotlin.utils.LibraryUtils
|
import org.jetbrains.kotlin.utils.LibraryUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -52,6 +54,7 @@ const val ANNOTATIONS_PLUGIN_NAME = "org.jetbrains.kotlin.kapt"
|
|||||||
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
|
const val KOTLIN_BUILD_DIR_NAME = "kotlin"
|
||||||
const val CACHES_DIR_NAME = "caches"
|
const val CACHES_DIR_NAME = "caches"
|
||||||
const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt"
|
const val DIRTY_SOURCES_FILE_NAME = "dirty-sources.txt"
|
||||||
|
const val LAST_BUILD_INFO_FILE_NAME = "last-build.bin"
|
||||||
const val USING_EXPERIMENTAL_INCREMENTAL_MESSAGE = "Using experimental kotlin incremental compilation"
|
const val USING_EXPERIMENTAL_INCREMENTAL_MESSAGE = "Using experimental kotlin incremental compilation"
|
||||||
|
|
||||||
abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCompile() {
|
abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractCompile() {
|
||||||
@@ -154,6 +157,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
private val taskBuildDirectory: File by lazy { File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name) }
|
private val taskBuildDirectory: File by lazy { File(File(project.buildDir, KOTLIN_BUILD_DIR_NAME), name) }
|
||||||
private val cacheDirectory: File by lazy { File(taskBuildDirectory, CACHES_DIR_NAME) }
|
private val cacheDirectory: File by lazy { File(taskBuildDirectory, CACHES_DIR_NAME) }
|
||||||
private val dirtySourcesSinceLastTimeFile: File by lazy { File(taskBuildDirectory, DIRTY_SOURCES_FILE_NAME) }
|
private val dirtySourcesSinceLastTimeFile: File by lazy { File(taskBuildDirectory, DIRTY_SOURCES_FILE_NAME) }
|
||||||
|
private val lastBuildInfoFile: File by lazy { File(taskBuildDirectory, LAST_BUILD_INFO_FILE_NAME) }
|
||||||
private val cacheVersions by lazy {
|
private val cacheVersions by lazy {
|
||||||
listOf(normalCacheVersion(taskBuildDirectory),
|
listOf(normalCacheVersion(taskBuildDirectory),
|
||||||
experimentalCacheVersion(taskBuildDirectory),
|
experimentalCacheVersion(taskBuildDirectory),
|
||||||
@@ -172,6 +176,7 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
|
|
||||||
val kaptOptions = KaptOptions()
|
val kaptOptions = KaptOptions()
|
||||||
val pluginOptions = CompilerPluginOptions()
|
val pluginOptions = CompilerPluginOptions()
|
||||||
|
var artifactDifferenceRegistry: ArtifactDifferenceRegistry? = null
|
||||||
|
|
||||||
override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) {
|
override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) {
|
||||||
logger.kotlinDebug("args.freeArgs = ${args.freeArgs}")
|
logger.kotlinDebug("args.freeArgs = ${args.freeArgs}")
|
||||||
@@ -232,15 +237,11 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun callCompiler(args: K2JVMCompilerArguments, sources: List<File>, isIncrementalRequested: Boolean, modified: List<File>, removed: List<File>) {
|
override fun callCompiler(args: K2JVMCompilerArguments, sources: List<File>, isIncrementalRequested: Boolean, modified: List<File>, removed: List<File>) {
|
||||||
|
val rootProjectDir = project.rootProject.projectDir
|
||||||
|
|
||||||
fun projectRelativePath(f: File) = f.toRelativeString(project.projectDir)
|
fun projectRelativePath(f: File) = f.toRelativeString(rootProjectDir)
|
||||||
|
fun filesToString(files: Iterable<File>) =
|
||||||
if (incremental) {
|
"[" + files.map(::projectRelativePath).sorted().joinToString(separator = ", \n") + "]"
|
||||||
// TODO: consider other ways to pass incremental flag to compiler/builder
|
|
||||||
System.setProperty("kotlin.incremental.compilation", "true")
|
|
||||||
// TODO: experimental should be removed as soon as it becomes standard
|
|
||||||
System.setProperty("kotlin.incremental.compilation.experimental", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
val targetType = "java-production"
|
val targetType = "java-production"
|
||||||
val moduleName = args.moduleName
|
val moduleName = args.moduleName
|
||||||
@@ -252,6 +253,8 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
var currentRemoved = removed.filter { it.isKotlinFile() }
|
var currentRemoved = removed.filter { it.isKotlinFile() }
|
||||||
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
|
val allGeneratedFiles = hashSetOf<GeneratedFile<TargetId>>()
|
||||||
val logAction = { logStr: String -> logger.kotlinInfo(logStr) }
|
val logAction = { logStr: String -> logger.kotlinInfo(logStr) }
|
||||||
|
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile)
|
||||||
|
logger.kotlinDebug { "Last Kotlin Build info for task $path -- $lastBuildInfo" }
|
||||||
|
|
||||||
fun getOrCreateIncrementalCache(target: TargetId): GradleIncrementalCacheImpl {
|
fun getOrCreateIncrementalCache(target: TargetId): GradleIncrementalCacheImpl {
|
||||||
val cacheDir = File(cacheDirectory, "increCache.${target.name}")
|
val cacheDir = File(cacheDirectory, "increCache.${target.name}")
|
||||||
@@ -261,68 +264,117 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
|
|
||||||
fun getIncrementalCache(it: TargetId) = caches.getOrPut(it, { getOrCreateIncrementalCache(it) })
|
fun getIncrementalCache(it: TargetId) = caches.getOrPut(it, { getOrCreateIncrementalCache(it) })
|
||||||
|
|
||||||
fun PsiClass.findLookupSymbols(): Iterable<LookupSymbol> {
|
fun PsiClass.addLookupSymbols(symbols: MutableSet<LookupSymbol>) {
|
||||||
val fqn = qualifiedName.orEmpty()
|
val fqn = qualifiedName.orEmpty()
|
||||||
return listOf(LookupSymbol(name.orEmpty(), if (fqn == name) "" else fqn.removeSuffix("." + name!!))) +
|
|
||||||
methods.map { LookupSymbol(it.name, fqn) } +
|
symbols.add(LookupSymbol(name.orEmpty(), if (fqn == name) "" else fqn.removeSuffix("." + name!!)))
|
||||||
fields.map { LookupSymbol(it.name.orEmpty(), fqn) } +
|
methods.forEach { symbols.add(LookupSymbol(it.name, fqn)) }
|
||||||
innerClasses.flatMap { it.findLookupSymbols() }
|
fields.forEach { symbols.add(LookupSymbol(it.name.orEmpty(), fqn)) }
|
||||||
|
innerClasses.forEach { it.addLookupSymbols(symbols) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dirtyLookupSymbolsFromModifiedJavaFiles(): List<LookupSymbol> {
|
val dirtyJavaLookupSymbols: Lazy<Collection<LookupSymbol>> = lazy {
|
||||||
|
val symbols = HashSet<LookupSymbol>()
|
||||||
val modifiedJavaFiles = modified.filter { it.isJavaFile() }
|
val modifiedJavaFiles = modified.filter { it.isJavaFile() }
|
||||||
return (if (modifiedJavaFiles.any()) {
|
if (modifiedJavaFiles.isEmpty()) return@lazy symbols
|
||||||
val rootDisposable = Disposer.newDisposable()
|
|
||||||
val configuration = CompilerConfiguration()
|
val rootDisposable = Disposer.newDisposable()
|
||||||
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
val configuration = CompilerConfiguration()
|
||||||
val project = environment.project
|
val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||||
val psiFileFactory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
|
val project = environment.project
|
||||||
modifiedJavaFiles.flatMap {
|
val psiFileFactory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
|
||||||
val javaFile = psiFileFactory.createFileFromText(it.nameWithoutExtension, Language.findLanguageByID("JAVA")!!, it.readText())
|
|
||||||
if (javaFile is PsiJavaFile)
|
modifiedJavaFiles.forEach {
|
||||||
javaFile.classes.flatMap { it.findLookupSymbols() }
|
val javaFile = psiFileFactory.createFileFromText(it.nameWithoutExtension, Language.findLanguageByID("JAVA")!!, it.readText())
|
||||||
else listOf()
|
if (javaFile is PsiJavaFile) {
|
||||||
|
javaFile.classes.forEach { it.addLookupSymbols(symbols) }
|
||||||
}
|
}
|
||||||
} else listOf())
|
}
|
||||||
|
symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dirtyKotlinSourcesFromGradle(): MutableSet<File> {
|
fun getClasspathChanges(modifiedClasspath: List<File>): DirtyData? {
|
||||||
// TODO: handle classpath changes similarly - compare with cashed version (likely a big change, may be costly, some heuristics could be considered)
|
if (modifiedClasspath.isEmpty()) {
|
||||||
val modifiedKotlinFiles = modified.filter { it.isKotlinFile() }.toMutableSet()
|
logger.kotlinDebug { "No classpath changes" }
|
||||||
val lookupSymbols = dirtyLookupSymbolsFromModifiedJavaFiles()
|
return DirtyData()
|
||||||
// TODO: add dirty lookups from modified kotlin files to reduce number of steps needed
|
}
|
||||||
|
if (artifactDifferenceRegistry == null) {
|
||||||
if (lookupSymbols.any()) {
|
logger.kotlinDebug { "No artifact history provider" }
|
||||||
val kotlinModifiedFilesSet = modifiedKotlinFiles.toHashSet()
|
return null
|
||||||
val dirtyFilesFromLookups = mapLookupSymbolsToFiles(lookupStorage, lookupSymbols, logAction, ::projectRelativePath, excludes = kotlinModifiedFilesSet)
|
|
||||||
modifiedKotlinFiles.addAll(dirtyFilesFromLookups)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return modifiedKotlinFiles
|
val lastBuildTS = lastBuildInfo?.startTS
|
||||||
|
if (lastBuildTS == null) {
|
||||||
|
logger.kotlinDebug { "Could not determine last build timestamp" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val symbols = HashSet<LookupSymbol>()
|
||||||
|
val fqNames = HashSet<FqName>()
|
||||||
|
for (file in modifiedClasspath) {
|
||||||
|
val diffs = artifactDifferenceRegistry!![file]
|
||||||
|
if (diffs == null) {
|
||||||
|
logger.kotlinDebug { "Could not get changes for file: $file" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val (beforeLastBuild, afterLastBuild) = diffs.partition { it.buildTS < lastBuildTS }
|
||||||
|
if (beforeLastBuild.isEmpty()) {
|
||||||
|
logger.kotlinDebug { "No known build preceding timestamp $lastBuildTS for file $file" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
afterLastBuild.forEach {
|
||||||
|
symbols.addAll(it.dirtyData.dirtyLookupSymbols)
|
||||||
|
fqNames.addAll(it.dirtyData.dirtyClassesFqNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DirtyData(symbols, fqNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isClassPathChanged(): Boolean =
|
|
||||||
modified.any { classpath.contains(it) }
|
|
||||||
|
|
||||||
fun calculateSourcesToCompile(): Pair<Set<File>, Boolean> {
|
fun calculateSourcesToCompile(): Pair<Set<File>, Boolean> {
|
||||||
if (!incremental
|
fun rebuild(reason: String): Pair<Set<File>, Boolean> {
|
||||||
|| !isIncrementalRequested
|
logger.kotlinInfo("Non-incremental compilation will be performed: $reason")
|
||||||
// TODO: more precise will be not to rebuild unconditionally on classpath changes, but retrieve lookup info and try to find out which sources are affected by cp changes
|
|
||||||
|| isClassPathChanged()
|
|
||||||
// so far considering it not incremental TODO: store java files in the cache and extract removed symbols from it here
|
|
||||||
|| removed.any { it.isJavaFile() || it.hasClassFileExtension() }
|
|
||||||
|| modified.any { it.hasClassFileExtension() }
|
|
||||||
) {
|
|
||||||
logger.kotlinInfo(if (!isIncrementalRequested) "clean caches on rebuild" else "classpath changed, rebuilding all kotlin files")
|
|
||||||
targets.forEach { getIncrementalCache(it).clean() }
|
targets.forEach { getIncrementalCache(it).clean() }
|
||||||
lookupStorage.clean()
|
lookupStorage.clean()
|
||||||
dirtySourcesSinceLastTimeFile.delete()
|
dirtySourcesSinceLastTimeFile.delete()
|
||||||
return Pair(sources.toSet(), false)
|
return Pair(sources.toSet(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dirtyFiles = dirtyKotlinSourcesFromGradle()
|
if (!incremental) return rebuild("incremental compilation is not enabled")
|
||||||
|
if (!isIncrementalRequested) return rebuild("inputs' changes are unknown (first or clean build)")
|
||||||
|
|
||||||
|
// TODO: store java files in the cache and extract removed symbols from it here
|
||||||
|
val illegalRemovedFiles = removed.filter { it.isJavaFile() || it.hasClassFileExtension() }
|
||||||
|
if (illegalRemovedFiles.any()) return rebuild("unsupported removed files: ${filesToString(illegalRemovedFiles)}")
|
||||||
|
|
||||||
|
val illegalModifiedFiles = modified.filter(File::hasClassFileExtension)
|
||||||
|
if (illegalModifiedFiles.any()) return rebuild("unsupported modified files: ${filesToString(illegalModifiedFiles)}")
|
||||||
|
|
||||||
|
val modifiedClasspathEntries = modified.filter { it in classpath }
|
||||||
|
val classpathChanges = getClasspathChanges(modifiedClasspathEntries)
|
||||||
|
?: return rebuild("could not get changes from modified classpath entries: ${filesToString(modifiedClasspathEntries)}")
|
||||||
|
|
||||||
|
val dirtyFiles = modified.filter { it.isKotlinFile() }.toMutableSet()
|
||||||
|
val lookupSymbols = HashSet<LookupSymbol>()
|
||||||
|
lookupSymbols.addAll(dirtyJavaLookupSymbols.value)
|
||||||
|
lookupSymbols.addAll(classpathChanges.dirtyLookupSymbols)
|
||||||
|
|
||||||
|
if (lookupSymbols.any()) {
|
||||||
|
val dirtyFilesFromLookups = mapLookupSymbolsToFiles(lookupStorage, lookupSymbols, logAction, ::projectRelativePath)
|
||||||
|
dirtyFiles.addAll(dirtyFilesFromLookups)
|
||||||
|
}
|
||||||
|
|
||||||
|
val allCaches = targets.map(::getIncrementalCache)
|
||||||
|
val dirtyClassesFqNames = classpathChanges.dirtyClassesFqNames.flatMap { withSubtypes(it, allCaches) }
|
||||||
|
if (dirtyClassesFqNames.any()) {
|
||||||
|
val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(allCaches, dirtyClassesFqNames, logAction, ::projectRelativePath)
|
||||||
|
dirtyFiles.addAll(dirtyFilesFromFqNames)
|
||||||
|
}
|
||||||
|
|
||||||
if (dirtySourcesSinceLastTimeFile.exists()) {
|
if (dirtySourcesSinceLastTimeFile.exists()) {
|
||||||
val files = dirtySourcesSinceLastTimeFile.readLines().map(::File).filter { it.exists() }
|
val files = dirtySourcesSinceLastTimeFile.readLines().map(::File).filter(File::exists)
|
||||||
if (files.isNotEmpty()) {
|
if (files.isNotEmpty()) {
|
||||||
logger.kotlinDebug { "Source files added since last compilation: $files" }
|
logger.kotlinDebug { "Source files added since last compilation: $files" }
|
||||||
}
|
}
|
||||||
@@ -334,9 +386,8 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cleanupOnError() {
|
fun cleanupOnError() {
|
||||||
val filesToDelete = allGeneratedFiles.map { it.outputFile }
|
logger.kotlinInfo("deleting $destinationDir on error")
|
||||||
logger.kotlinInfo("deleting output on error: ${filesToDelete.joinToString()}")
|
destinationDir.deleteRecursively()
|
||||||
filesToDelete.forEach { it.delete() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processCompilerExitCode(exitCode: ExitCode) {
|
fun processCompilerExitCode(exitCode: ExitCode) {
|
||||||
@@ -379,6 +430,13 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
kaptAnnotationsFileUpdater = null
|
kaptAnnotationsFileUpdater = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val artifactFile = project.tryGetSingleArtifact()
|
||||||
|
logger.kotlinDebug { "Artifact to register difference for task $path: $artifactFile" }
|
||||||
|
val currentBuildInfo = BuildInfo(startTS = System.currentTimeMillis())
|
||||||
|
BuildInfo.write(currentBuildInfo, lastBuildInfoFile)
|
||||||
|
val buildDirtyLookupSymbols = HashSet<LookupSymbol>()
|
||||||
|
val buildDirtyFqNames = HashSet<FqName>()
|
||||||
|
|
||||||
var exitCode = ExitCode.OK
|
var exitCode = ExitCode.OK
|
||||||
while (sourcesToCompile.any() || currentRemoved.any()) {
|
while (sourcesToCompile.any() || currentRemoved.any()) {
|
||||||
val removedAndModified = (sourcesToCompile + currentRemoved).toList()
|
val removedAndModified = (sourcesToCompile + currentRemoved).toList()
|
||||||
@@ -419,18 +477,38 @@ open class KotlinCompile() : AbstractKotlinCompile<K2JVMCompilerArguments>() {
|
|||||||
|
|
||||||
lookupStorage.update(lookupTracker, sourcesToCompile, currentRemoved)
|
lookupStorage.update(lookupTracker, sourcesToCompile, currentRemoved)
|
||||||
|
|
||||||
if (!isIncrementalDecided) break
|
if (!isIncrementalDecided) {
|
||||||
|
artifactFile?.let { artifactDifferenceRegistry?.remove(it) }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
val (dirtyLookupSymbols, dirtyClassFqNames) = compilationResult.getDirtyData(caches.values, logAction)
|
val (dirtyLookupSymbols, dirtyClassFqNames) = compilationResult.getDirtyData(caches.values, logAction)
|
||||||
sourcesToCompile = mapLookupSymbolsToFiles(lookupStorage, dirtyLookupSymbols, logAction, ::projectRelativePath, excludes = sourcesToCompile) +
|
sourcesToCompile = mapLookupSymbolsToFiles(lookupStorage, dirtyLookupSymbols, logAction, ::projectRelativePath, excludes = sourcesToCompile) +
|
||||||
mapClassesFqNamesToFiles(caches.values, dirtyClassFqNames, logAction, ::projectRelativePath, excludes = sourcesToCompile)
|
mapClassesFqNamesToFiles(caches.values, dirtyClassFqNames, logAction, ::projectRelativePath, excludes = sourcesToCompile)
|
||||||
|
|
||||||
|
buildDirtyLookupSymbols.addAll(dirtyLookupSymbols)
|
||||||
|
buildDirtyFqNames.addAll(dirtyClassFqNames)
|
||||||
|
|
||||||
if (currentRemoved.any()) {
|
if (currentRemoved.any()) {
|
||||||
anyClassesCompiled = true
|
anyClassesCompiled = true
|
||||||
currentRemoved = listOf()
|
currentRemoved = listOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exitCode == ExitCode.OK) {
|
||||||
|
buildDirtyLookupSymbols.addAll(dirtyJavaLookupSymbols.value)
|
||||||
|
}
|
||||||
|
if (artifactFile != null && artifactDifferenceRegistry != null) {
|
||||||
|
val dirtyData = DirtyData(buildDirtyLookupSymbols, buildDirtyFqNames)
|
||||||
|
val artifactDifference = ArtifactDifference(currentBuildInfo.startTS, dirtyData)
|
||||||
|
artifactDifferenceRegistry!!.add(artifactFile, artifactDifference)
|
||||||
|
logger.kotlinDebug {
|
||||||
|
val dirtySymbolsSorted = buildDirtyLookupSymbols.map { it.scope + "#" + it.name }.sorted()
|
||||||
|
"Added artifact difference for $artifactFile (ts: ${currentBuildInfo.startTS}): " +
|
||||||
|
"[\n\t${dirtySymbolsSorted.joinToString(",\n\t")}]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
anyClassesCompiled = anyClassesCompiled || allGeneratedFiles.isNotEmpty()
|
anyClassesCompiled = anyClassesCompiled || allGeneratedFiles.isNotEmpty()
|
||||||
processCompilerExitCode(exitCode)
|
processCompilerExitCode(exitCode)
|
||||||
}
|
}
|
||||||
@@ -719,6 +797,22 @@ class GradleMessageCollector(val logger: Logger, val outputCollector: OutputItem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Project.tryGetSingleArtifact(): File? {
|
||||||
|
val log = logger
|
||||||
|
log.kotlinDebug { "Trying to determine single artifact for project $path" }
|
||||||
|
|
||||||
|
val archives = configurations.findByName("archives")
|
||||||
|
if (archives == null) {
|
||||||
|
log.kotlinDebug { "Could not find 'archives' configuration for project $path" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val artifacts = archives.artifacts.files.files
|
||||||
|
log.kotlinDebug { "All artifacts for project $path: [${artifacts.joinToString()}]" }
|
||||||
|
|
||||||
|
return if (artifacts.size == 1) artifacts.first() else null
|
||||||
|
}
|
||||||
|
|
||||||
internal fun Logger.kotlinInfo(message: String) {
|
internal fun Logger.kotlinInfo(message: String) {
|
||||||
this.info("[KOTLIN] $message")
|
this.info("[KOTLIN] $message")
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-12
@@ -26,6 +26,9 @@ import org.jetbrains.kotlin.com.intellij.openapi.util.io.ZipFileCache
|
|||||||
import org.jetbrains.kotlin.com.intellij.openapi.vfs.impl.ZipHandler
|
import org.jetbrains.kotlin.com.intellij.openapi.vfs.impl.ZipHandler
|
||||||
import org.jetbrains.kotlin.com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
|
import org.jetbrains.kotlin.com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
|
||||||
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.ArtifactDifferenceRegistry
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.incremental.BuildCacheStorage
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
private fun comparableVersionStr(version: String) =
|
private fun comparableVersionStr(version: String) =
|
||||||
"(\\d+)\\.(\\d+).*"
|
"(\\d+)\\.(\\d+).*"
|
||||||
@@ -37,31 +40,44 @@ private fun comparableVersionStr(version: String) =
|
|||||||
?.let { if (it.all { (it?.value?.length ?: 0).let { it > 0 && it < 4 }}) it else null }
|
?.let { if (it.all { (it?.value?.length ?: 0).let { it > 0 && it < 4 }}) it else null }
|
||||||
?.joinToString(".", transform = { it!!.value.padStart(3, '0') })
|
?.joinToString(".", transform = { it!!.value.padStart(3, '0') })
|
||||||
|
|
||||||
class KotlinGradleBuildServices private constructor(): BuildAdapter() {
|
class KotlinGradleBuildServices private constructor(gradle: Gradle): BuildAdapter() {
|
||||||
companion object {
|
companion object {
|
||||||
|
private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName
|
||||||
const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()"
|
const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()"
|
||||||
const val INIT_MESSAGE = "Initialized KotlinGradleBuildServices"
|
val INIT_MESSAGE = "Initialized $CLASS_NAME"
|
||||||
const val DISPOSE_MESSAGE = "Disposed KotlinGradleBuildServices"
|
val DISPOSE_MESSAGE = "Disposed $CLASS_NAME"
|
||||||
private var initialized = false
|
val ALREADY_INITIALIZED_MESSAGE = "$CLASS_NAME is already initialized"
|
||||||
|
@field:Volatile
|
||||||
|
private var instance: KotlinGradleBuildServices? = null
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun init(gradle: Gradle) {
|
fun getInstance(gradle: Gradle): KotlinGradleBuildServices {
|
||||||
if (initialized) return
|
|
||||||
|
|
||||||
val listener = KotlinGradleBuildServices()
|
|
||||||
gradle.addBuildListener(listener)
|
|
||||||
initialized = true
|
|
||||||
val log = Logging.getLogger(KotlinGradleBuildServices::class.java)
|
val log = Logging.getLogger(KotlinGradleBuildServices::class.java)
|
||||||
|
|
||||||
|
if (instance != null) {
|
||||||
|
log.kotlinDebug(ALREADY_INITIALIZED_MESSAGE)
|
||||||
|
return instance!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val services = KotlinGradleBuildServices(gradle)
|
||||||
|
gradle.addBuildListener(services)
|
||||||
|
instance = services
|
||||||
log.kotlinDebug(INIT_MESSAGE)
|
log.kotlinDebug(INIT_MESSAGE)
|
||||||
|
|
||||||
listener.buildStarted()
|
services.buildStarted()
|
||||||
|
return services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val log = Logging.getLogger(this.javaClass)
|
private val log = Logging.getLogger(this.javaClass)
|
||||||
private val cleanup = CompilerServicesCleanup()
|
private val cleanup = CompilerServicesCleanup()
|
||||||
private var startMemory: Long? = null
|
private var startMemory: Long? = null
|
||||||
|
private val workingDir = File(gradle.rootProject.buildDir, "kotlin-build").apply { mkdirs() }
|
||||||
|
private val buildCacheStorage = BuildCacheStorage(workingDir)
|
||||||
|
|
||||||
|
internal val artifactDifferenceRegistry: ArtifactDifferenceRegistry
|
||||||
|
get() = buildCacheStorage.artifactDifferenceRegistry
|
||||||
|
|
||||||
// There is function with the same name in BuildAdapter,
|
// There is function with the same name in BuildAdapter,
|
||||||
// but it is called before any plugin can attach build listener
|
// but it is called before any plugin can attach build listener
|
||||||
@@ -93,8 +109,11 @@ class KotlinGradleBuildServices private constructor(): BuildAdapter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildCacheStorage.flush(memoryCachesOnly = false)
|
||||||
|
buildCacheStorage.close()
|
||||||
|
|
||||||
gradle.removeListener(this)
|
gradle.removeListener(this)
|
||||||
initialized = false
|
instance = null
|
||||||
log.kotlinDebug(DISPOSE_MESSAGE)
|
log.kotlinDebug(DISPOSE_MESSAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-12
@@ -25,10 +25,8 @@ import org.jetbrains.kotlin.gradle.internal.Kapt2GradleSubplugin
|
|||||||
import org.jetbrains.kotlin.gradle.internal.Kapt2KotlinGradleSubplugin
|
import org.jetbrains.kotlin.gradle.internal.Kapt2KotlinGradleSubplugin
|
||||||
import org.jetbrains.kotlin.gradle.internal.initKapt
|
import org.jetbrains.kotlin.gradle.internal.initKapt
|
||||||
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
|
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
|
||||||
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
|
import org.jetbrains.kotlin.gradle.tasks.*
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.incremental.configureMultiProjectIncrementalCompilation
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.SyncOutputTask
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -98,18 +96,14 @@ class Kotlin2JvmSourceSetProcessor(
|
|||||||
sourceSet: SourceSet,
|
sourceSet: SourceSet,
|
||||||
tasksProvider: KotlinTasksProvider,
|
tasksProvider: KotlinTasksProvider,
|
||||||
kotlinSourceSetProvider: KotlinSourceSetProvider,
|
kotlinSourceSetProvider: KotlinSourceSetProvider,
|
||||||
private val kotlinPluginVersion: String
|
private val kotlinPluginVersion: String,
|
||||||
|
private val artifactDifferenceRegistry: ArtifactDifferenceRegistry
|
||||||
) : KotlinSourceSetProcessor<KotlinCompile>(
|
) : KotlinSourceSetProcessor<KotlinCompile>(
|
||||||
project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider,
|
project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider,
|
||||||
dslExtensionName = KOTLIN_DSL_NAME,
|
dslExtensionName = KOTLIN_DSL_NAME,
|
||||||
compileTaskNameSuffix = "kotlin",
|
compileTaskNameSuffix = "kotlin",
|
||||||
taskDescription = "Compiles the $sourceSet.kotlin."
|
taskDescription = "Compiles the $sourceSet.kotlin."
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private companion object {
|
|
||||||
private var cachedKotlinAnnotationProcessingDep: String? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultKotlinDestinationDir: File
|
override val defaultKotlinDestinationDir: File
|
||||||
get() = File(project.buildDir, "kotlin-classes/$sourceSetName")
|
get() = File(project.buildDir, "kotlin-classes/$sourceSetName")
|
||||||
|
|
||||||
@@ -147,6 +141,7 @@ class Kotlin2JvmSourceSetProcessor(
|
|||||||
kotlinAfterJavaTask?.let { it.source(kotlinSourceSet.kotlin) }
|
kotlinAfterJavaTask?.let { it.source(kotlinSourceSet.kotlin) }
|
||||||
configureJavaTask(kotlinTask, javaTask, logger)
|
configureJavaTask(kotlinTask, javaTask, logger)
|
||||||
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName)
|
createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName)
|
||||||
|
configureMultiProjectIncrementalCompilation(project, kotlinTask, javaTask, kotlinAfterJavaTask, artifactDifferenceRegistry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,10 +217,11 @@ abstract class AbstractKotlinPlugin(val tasksProvider: KotlinTasksProvider, val
|
|||||||
open class KotlinPlugin(
|
open class KotlinPlugin(
|
||||||
tasksProvider: KotlinTasksProvider,
|
tasksProvider: KotlinTasksProvider,
|
||||||
kotlinSourceSetProvider: KotlinSourceSetProvider,
|
kotlinSourceSetProvider: KotlinSourceSetProvider,
|
||||||
kotlinPluginVersion: String
|
kotlinPluginVersion: String,
|
||||||
|
private val artifactDifferenceRegistry: ArtifactDifferenceRegistry
|
||||||
) : AbstractKotlinPlugin(tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion) {
|
) : AbstractKotlinPlugin(tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion) {
|
||||||
override fun buildSourceSetProcessor(project: Project, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet, kotlinPluginVersion: String) =
|
override fun buildSourceSetProcessor(project: Project, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet, kotlinPluginVersion: String) =
|
||||||
Kotlin2JvmSourceSetProcessor(project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion)
|
Kotlin2JvmSourceSetProcessor(project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion, artifactDifferenceRegistry)
|
||||||
|
|
||||||
override fun apply(project: Project) {
|
override fun apply(project: Project) {
|
||||||
project.createKaptExtension()
|
project.createKaptExtension()
|
||||||
|
|||||||
+8
-8
@@ -19,28 +19,28 @@ abstract class KotlinBasePluginWrapper(protected val fileResolver: FileResolver)
|
|||||||
override fun apply(project: Project) {
|
override fun apply(project: Project) {
|
||||||
// TODO: consider only set if if daemon or parallel compilation are enabled, though this way it should be safe too
|
// TODO: consider only set if if daemon or parallel compilation are enabled, though this way it should be safe too
|
||||||
System.setProperty(org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true")
|
System.setProperty(org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true")
|
||||||
|
val kotlinGradleBuildServices = KotlinGradleBuildServices.getInstance(project.gradle)
|
||||||
|
|
||||||
val plugin = getPlugin()
|
val plugin = getPlugin(kotlinGradleBuildServices)
|
||||||
plugin.apply(project)
|
plugin.apply(project)
|
||||||
|
|
||||||
KotlinGradleBuildServices.init(project.gradle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun getPlugin(): Plugin<Project>
|
protected abstract fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin<Project>
|
||||||
}
|
}
|
||||||
|
|
||||||
open class KotlinPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
|
open class KotlinPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
|
||||||
override fun getPlugin() =
|
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
|
||||||
KotlinPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
|
KotlinPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion,
|
||||||
|
kotlinGradleBuildServices.artifactDifferenceRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class KotlinAndroidPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
|
open class KotlinAndroidPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
|
||||||
override fun getPlugin() =
|
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
|
||||||
KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
|
KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Kotlin2JsPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
|
open class Kotlin2JsPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
|
||||||
override fun getPlugin() =
|
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
|
||||||
Kotlin2JsPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
|
Kotlin2JsPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.tasks.incremental
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.com.intellij.util.io.DataExternalizer
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.ArtifactDifference
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.ArtifactDifferenceRegistry
|
||||||
|
import org.jetbrains.kotlin.incremental.DirtyData
|
||||||
|
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.BasicStringMap
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.io.DataOutput
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
internal class ArtifactDifferenceRegistryImpl(
|
||||||
|
storageFile: File
|
||||||
|
) : ArtifactDifferenceRegistry, BasicStringMap<Collection<ArtifactDifference>>(
|
||||||
|
storageFile,
|
||||||
|
ArtifactDifferenceCollectionExternalizer
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val MAX_BUILDS_PER_ARTIFACT = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(artifact: File): Iterable<ArtifactDifference>? =
|
||||||
|
storage[artifact.canonicalPath]
|
||||||
|
|
||||||
|
override fun add(artifact: File, difference: ArtifactDifference) {
|
||||||
|
val key = artifact.canonicalPath
|
||||||
|
val oldVal = storage[key] ?: emptyList()
|
||||||
|
val newVal = ArrayList(oldVal)
|
||||||
|
newVal.add(difference)
|
||||||
|
newVal.sortBy { it.buildTS }
|
||||||
|
storage[key] = newVal.takeLast(MAX_BUILDS_PER_ARTIFACT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(artifact: File) {
|
||||||
|
storage.remove(artifact.canonicalPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dumpValue(value: Collection<ArtifactDifference>): String =
|
||||||
|
value.sortedBy { it.buildTS }.joinToString(separator = ",\n\t") { diff ->
|
||||||
|
"{ " +
|
||||||
|
"timestamp: ${diff.buildTS}, " +
|
||||||
|
"lookup symbols: [${diff.dirtyData.dirtyLookupSymbols.dumpLookupSymbols()}], " +
|
||||||
|
"fq names: [${diff.dirtyData.dirtyClassesFqNames.dumpFqNames()}]"
|
||||||
|
"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> Collection<T>.takeLast(n: Int): Collection<T> =
|
||||||
|
drop(Math.max(size - n, 0))
|
||||||
|
|
||||||
|
private fun Collection<LookupSymbol>.dumpLookupSymbols(): String =
|
||||||
|
map { "${it.scope}#${it.name}" }.sorted().joinToString()
|
||||||
|
|
||||||
|
private fun Collection<FqName>.dumpFqNames(): String =
|
||||||
|
map(FqName::asString).sorted().joinToString()
|
||||||
|
|
||||||
|
private object ArtifactDifferenceCollectionExternalizer : DataExternalizer<Collection<ArtifactDifference>> {
|
||||||
|
override fun read(input: DataInput): Collection<ArtifactDifference> =
|
||||||
|
input.readCollectionTo(ArrayList()) {
|
||||||
|
val buildTS = readLong()
|
||||||
|
val dirtyLookupSymbols = readCollectionTo(HashSet()) {
|
||||||
|
val scope = readUTF()
|
||||||
|
val name = readUTF()
|
||||||
|
LookupSymbol(name, scope)
|
||||||
|
}
|
||||||
|
val dirtyFqNames = readCollectionTo(HashSet()) {
|
||||||
|
FqName(readUTF())
|
||||||
|
}
|
||||||
|
ArtifactDifference(buildTS, DirtyData(dirtyLookupSymbols, dirtyFqNames))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun save(output: DataOutput, value: Collection<ArtifactDifference>) {
|
||||||
|
output.writeCollection(value) { diff ->
|
||||||
|
writeLong(diff.buildTS)
|
||||||
|
writeCollection(diff.dirtyData.dirtyLookupSymbols) {
|
||||||
|
writeUTF(it.scope)
|
||||||
|
writeUTF(it.name)
|
||||||
|
}
|
||||||
|
writeCollection(diff.dirtyData.dirtyClassesFqNames) {
|
||||||
|
writeUTF(it.asString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> DataInput.readCollectionTo(col: MutableCollection<T>, readT: DataInput.()->T): Collection<T> {
|
||||||
|
val size = readInt()
|
||||||
|
|
||||||
|
repeat(size) {
|
||||||
|
col.add(readT())
|
||||||
|
}
|
||||||
|
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> DataOutput.writeCollection(col: Collection<T>, writeT: DataOutput.(T)->Unit) {
|
||||||
|
writeInt(col.size)
|
||||||
|
col.forEach { writeT(it) }
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.tasks.incremental
|
||||||
|
|
||||||
|
import org.gradle.api.logging.Logging
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.ArtifactDifferenceRegistry
|
||||||
|
import org.jetbrains.kotlin.incremental.CacheVersion
|
||||||
|
import org.jetbrains.kotlin.incremental.storage.BasicMapsOwner
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Global" cache holder. Should be created once per root project.
|
||||||
|
*/
|
||||||
|
internal class BuildCacheStorage(workingDir: File) : BasicMapsOwner() {
|
||||||
|
companion object {
|
||||||
|
private val OWN_VERSION = 0
|
||||||
|
private val ARTIFACT_DIFFERENCE = "artifact-difference"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val log = Logging.getLogger(this.javaClass)
|
||||||
|
private val cachesDir: File = File(workingDir, "caches").apply { mkdirs() }
|
||||||
|
private val versionFile = File(cachesDir, "version.txt")
|
||||||
|
private val version = CacheVersion(
|
||||||
|
OWN_VERSION,
|
||||||
|
versionFile,
|
||||||
|
whenVersionChanged = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||||
|
whenTurnedOff = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||||
|
whenTurnedOn = CacheVersion.Action.REBUILD_ALL_KOTLIN,
|
||||||
|
// assume it's always enabled for simplicity (if IC is not enabled, just don't write to cache)
|
||||||
|
isEnabled = { true })
|
||||||
|
|
||||||
|
internal val artifactDifferenceRegistry: ArtifactDifferenceRegistry
|
||||||
|
|
||||||
|
private val String.storageFile: File
|
||||||
|
get() = File(cachesDir, this + "." + CACHE_EXTENSION)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (version.checkVersion() != CacheVersion.Action.DO_NOTHING) {
|
||||||
|
log.kotlinDebug { "Cache version is not up-to-date. Removing $cachesDir" }
|
||||||
|
cachesDir.deleteRecursively()
|
||||||
|
cachesDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
artifactDifferenceRegistry = registerMap(ArtifactDifferenceRegistryImpl(ARTIFACT_DIFFERENCE.storageFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clean() {
|
||||||
|
super.clean()
|
||||||
|
versionFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
super.close()
|
||||||
|
version.saveIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun flush(memoryCachesOnly: Boolean) {
|
||||||
|
super.flush(memoryCachesOnly)
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle.tasks.incremental
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.tasks.compile.AbstractCompile
|
||||||
|
import org.gradle.api.tasks.compile.JavaCompile
|
||||||
|
import org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.ArtifactDifferenceRegistry
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.tryGetSingleArtifact
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun configureMultiProjectIncrementalCompilation(
|
||||||
|
project: Project,
|
||||||
|
kotlinTask: KotlinCompile,
|
||||||
|
javaTask: JavaCompile,
|
||||||
|
kotlinAfterJavaTask: KotlinCompile?,
|
||||||
|
artifactDifferenceRegistry: ArtifactDifferenceRegistry
|
||||||
|
) {
|
||||||
|
val log = kotlinTask.logger
|
||||||
|
log.kotlinDebug { "Configuring multi-project incremental compilation for project ${project.path}" }
|
||||||
|
|
||||||
|
fun cannotPerformMultiProjectIC(artifact: File, reason: String) {
|
||||||
|
log.kotlinDebug {
|
||||||
|
"Multi-project kotlin incremental compilation cannot be performed for project ${project.path}: $reason"
|
||||||
|
}
|
||||||
|
artifactDifferenceRegistry.remove(artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUnknownTaskOutputtingToJavaDestination(task: Task): Boolean {
|
||||||
|
return task !is JavaCompile &&
|
||||||
|
task !is KotlinCompile &&
|
||||||
|
task is AbstractCompile &&
|
||||||
|
FileUtil.isAncestor(javaTask.destinationDir, task.destinationDir, /* strict = */ false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val artifact = project.tryGetSingleArtifact() ?: return
|
||||||
|
if (!kotlinTask.incremental) {
|
||||||
|
return cannotPerformMultiProjectIC(artifact, reason = "incremental compilation is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
val illegalTask = project.tasks.find(::isUnknownTaskOutputtingToJavaDestination)
|
||||||
|
if (illegalTask != null) {
|
||||||
|
return cannotPerformMultiProjectIC(artifact,
|
||||||
|
reason = "unknown task outputs to java destination dir ${illegalTask.path} $(${illegalTask.javaClass})")
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinCompile = kotlinAfterJavaTask ?: kotlinTask
|
||||||
|
kotlinCompile.artifactDifferenceRegistry = artifactDifferenceRegistry
|
||||||
|
}
|
||||||
+2
-2
@@ -92,7 +92,7 @@ fun getSomething() = 10
|
|||||||
|
|
||||||
project.build("build", options = options) {
|
project.build("build", options = options) {
|
||||||
assertSuccessful()
|
assertSuccessful()
|
||||||
assertCompiledKotlinSources(listOf("src/main/kotlin/foo/KotlinActivity1.kt", "src/main/kotlin/foo/getSomething.kt"))
|
assertCompiledKotlinSources(listOf("app/src/main/kotlin/foo/KotlinActivity1.kt", "app/src/main/kotlin/foo/getSomething.kt"))
|
||||||
assertCompiledJavaSources(listOf("app/src/main/java/foo/JavaActivity.java"), weakTesting = true)
|
assertCompiledJavaSources(listOf("app/src/main/java/foo/JavaActivity.java"), weakTesting = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ fun getSomething() = 10
|
|||||||
|
|
||||||
project.build(":app:assembleDebug", options = options) {
|
project.build(":app:assembleDebug", options = options) {
|
||||||
assertSuccessful()
|
assertSuccessful()
|
||||||
assertCompiledKotlinSources(project.relativizeToSubproject("app",
|
assertCompiledKotlinSources(project.relativize(
|
||||||
androidModuleKt,
|
androidModuleKt,
|
||||||
baseApplicationKt,
|
baseApplicationKt,
|
||||||
useBuildConfigJavaKt,
|
useBuildConfigJavaKt,
|
||||||
|
|||||||
-5
@@ -95,11 +95,6 @@ abstract class BaseGradleIT {
|
|||||||
fun relativize(vararg files: File): List<String> =
|
fun relativize(vararg files: File): List<String> =
|
||||||
files.map { it.relativeTo(projectDir).path }
|
files.map { it.relativeTo(projectDir).path }
|
||||||
|
|
||||||
fun relativizeToSubproject(subproject: String, vararg files: File): List<String> {
|
|
||||||
val subprojectSir = File(projectDir, subproject)
|
|
||||||
return files.map { it.relativeTo(subprojectSir).path }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun performModifications() {
|
fun performModifications() {
|
||||||
for (file in projectDir.walk()) {
|
for (file in projectDir.walk()) {
|
||||||
if (!file.isFile) continue
|
if (!file.isFile) continue
|
||||||
|
|||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.util.allKotlinFiles
|
||||||
|
import org.jetbrains.kotlin.gradle.util.getFileByName
|
||||||
|
import org.jetbrains.kotlin.gradle.util.getFilesByNames
|
||||||
|
import org.jetbrains.kotlin.gradle.util.modify
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class IncrementalCompilationMultiProjectIT : BaseGradleIT() {
|
||||||
|
companion object {
|
||||||
|
private val GRADLE_VERSION = "2.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun defaultBuildOptions(): BuildOptions =
|
||||||
|
super.defaultBuildOptions().copy(withDaemon = true, incremental = true)
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMoveFunctionFromLib() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
val barUseABKt = project.projectDir.getFileByName("barUseAB.kt")
|
||||||
|
val barInApp = File(project.projectDir, "app/src/main/java/bar").apply { mkdirs() }
|
||||||
|
barUseABKt.copyTo(File(barInApp, barUseABKt.name))
|
||||||
|
barUseABKt.delete()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
val affectedSources = project.projectDir.getFilesByNames("fooCallUseAB.kt", "barUseAB.kt")
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAddNewMethodToLib() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
val aKt = project.projectDir.getFileByName("A.kt")
|
||||||
|
aKt.writeText("""
|
||||||
|
package bar
|
||||||
|
|
||||||
|
open class A {
|
||||||
|
fun a() {}
|
||||||
|
fun newA() {}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
val affectedSources = project.projectDir.getFilesByNames("A.kt", "B.kt", "AA.kt", "BB.kt")
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLibClassBecameFinal() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
val bKt = project.projectDir.getFileByName("B.kt")
|
||||||
|
bKt.modify { it.replace("open class", "class") }
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertFailed()
|
||||||
|
val affectedSources = project.projectDir.getFilesByNames(
|
||||||
|
"B.kt", "barUseAB.kt", "barUseB.kt",
|
||||||
|
"BB.kt", "fooCallUseAB.kt", "fooUseB.kt")
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testModifyJavaInLib() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
val javaClassJava = project.projectDir.getFileByName("JavaClass.java")
|
||||||
|
javaClassJava.modify { it.replace("String getString", "Object getString") }
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
val affectedSources = project.projectDir.getFilesByNames("JavaClassChild.kt", "useJavaClass.kt")
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCleanBuildLib() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
project.build(":lib:clean", ":lib:build") {
|
||||||
|
assertSuccessful()
|
||||||
|
val affectedSources = File(project.projectDir, "lib").allKotlinFiles()
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
val affectedSources = File(project.projectDir, "app").allKotlinFiles()
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCompileErrorInLib() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
val bKt = project.projectDir.getFileByName("B.kt")
|
||||||
|
bKt.delete()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertFailed()
|
||||||
|
}
|
||||||
|
|
||||||
|
project.projectDir.getFileByName("barUseB.kt").delete()
|
||||||
|
project.projectDir.getFileByName("barUseAB.kt").delete()
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertFailed()
|
||||||
|
val affectedSources = project.projectDir.allKotlinFiles()
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks that multi-project ic is disabled when there is a task that outputs to javaDestination dir
|
||||||
|
// that is not JavaCompile or KotlinCompile
|
||||||
|
@Test
|
||||||
|
fun testCompileLibWithGroovy() {
|
||||||
|
val project = Project("incrementalMultiproject", GRADLE_VERSION)
|
||||||
|
project.setupWorkingDir()
|
||||||
|
val lib = File(project.projectDir, "lib")
|
||||||
|
val libBuildGradle = File(lib, "build.gradle")
|
||||||
|
libBuildGradle.modify {
|
||||||
|
"""
|
||||||
|
apply plugin: 'groovy'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
val libGroovySrcBar = File(lib, "src/main/groovy/bar").apply { mkdirs() }
|
||||||
|
val groovyClass = File(libGroovySrcBar, "GroovyClass.groovy")
|
||||||
|
groovyClass.writeText("""
|
||||||
|
package bar
|
||||||
|
|
||||||
|
class GroovyClass {}
|
||||||
|
""")
|
||||||
|
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
}
|
||||||
|
|
||||||
|
project.projectDir.getFileByName("barUseB.kt").delete()
|
||||||
|
project.build("build") {
|
||||||
|
assertSuccessful()
|
||||||
|
val affectedSources = File(project.projectDir, "app").allKotlinFiles()
|
||||||
|
val relativePaths = project.relativize(affectedSources)
|
||||||
|
assertCompiledKotlinSources(relativePaths, weakTesting = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -5,6 +5,9 @@ import java.io.File
|
|||||||
fun File.getFileByName(name: String): File =
|
fun File.getFileByName(name: String): File =
|
||||||
findFileByName(name) ?: throw AssertionError("Could not find file with name '$name' in $this")
|
findFileByName(name) ?: throw AssertionError("Could not find file with name '$name' in $this")
|
||||||
|
|
||||||
|
fun File.getFilesByNames(vararg names: String): List<File> =
|
||||||
|
names.map { getFileByName(it) }
|
||||||
|
|
||||||
fun File.findFileByName(name: String): File? =
|
fun File.findFileByName(name: String): File? =
|
||||||
walk().filter { it.isFile && it.name.equals(name, ignoreCase = true) }.firstOrNull()
|
walk().filter { it.isFile && it.name.equals(name, ignoreCase = true) }.firstOrNull()
|
||||||
|
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(':lib')
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
class AA : A() {
|
||||||
|
fun aa() {}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
class BB : B() {
|
||||||
|
fun bb() {}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
class FooDummy
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
class JavaClassChild : JavaClass() {}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
fun callUseAB() {
|
||||||
|
val b = B()
|
||||||
|
useAB(b)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
private fun fooUseA(a: A) {
|
||||||
|
a.a()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
private fun fooUseAA(aa: AA) {
|
||||||
|
aa.aa()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
private fun fooUseB(b: B) {
|
||||||
|
b.b()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
private fun fooUseBB(bb: BB) {
|
||||||
|
bb.bb()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import bar.*
|
||||||
|
|
||||||
|
fun useJavaClass(jc: JavaClass) {
|
||||||
|
jc.getString()
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '0.1-SNAPSHOT'
|
||||||
|
repositories {
|
||||||
|
maven { url 'file://' + pathToKotlinPlugin }
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
// for test with groovy
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
apply plugin: 'kotlin'
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
open class A {
|
||||||
|
fun a() {}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
open class B : A() {
|
||||||
|
fun b() {}
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class BarDummy
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar;
|
||||||
|
|
||||||
|
public class JavaClass {
|
||||||
|
public String getString() { return "Hello, World!"; }
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
private fun barUseA(a: A) {
|
||||||
|
a.a()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
fun useAB(b: B) {
|
||||||
|
b.b()
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
private fun barUseB(b: B) {
|
||||||
|
b.b()
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
include ':app', ':lib'
|
||||||
Reference in New Issue
Block a user