Remove global artifact history cache in Gradle

Each Kotlin task now writes build history to separate file.
A map of output directories to history files is used to get changes for
modified files.

    #KT-22623 fixed
This commit is contained in:
Alexey Tsvetkov
2018-05-16 16:18:45 +03:00
parent 598e89c03d
commit 6a45310830
31 changed files with 304 additions and 843 deletions
@@ -106,16 +106,6 @@ class Android25ProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools
override fun addJavaSourceDirectoryToVariantModel(variantData: BaseVariant, javaSourceDirectory: File) =
variantData.addJavaSourceFoldersToModel(javaSourceDirectory)
override fun configureMultiProjectIc(project: Project,
variantData: BaseVariant,
javaTask: AbstractCompile,
kotlinTask: KotlinCompile,
kotlinAfterJavaTask: KotlinCompile?) {
//todo: No easy solution because of the absence of the output information in library modules
// Though it is affordable not to implement this for the first previews, because the impact is tolerable
// to some degree -- the dependent projects will rebuild non-incrementally when a library project changes
}
override fun getResDirectories(variantData: BaseVariant): List<File> {
return variantData.mergeResources?.computeResourceSetList0() ?: emptyList()
}
@@ -6,8 +6,6 @@ import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.gradle.tasks.GradleMessageCollector
import org.jetbrains.kotlin.gradle.tasks.findToolsJar
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.incremental.ICReporter
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
import java.io.File
import java.net.URL
@@ -27,18 +25,14 @@ internal open class GradleCompilerEnvironment(
}
internal class GradleIncrementalCompilerEnvironment(
compilerClasspath: List<File>,
val changedFiles: ChangedFiles,
val reporter: ICReporter,
val workingDir: File,
messageCollector: GradleMessageCollector,
outputItemsCollector: OutputItemsCollector,
compilerArgs: CommonCompilerArguments,
val kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null,
val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null,
val artifactFile: File? = null,
val buildHistoryFile: File? = null,
val friendBuildHistoryFile: File? = null,
val usePreciseJavaTracking: Boolean = false,
val localStateDirs: List<File> = emptyList()
compilerClasspath: List<File>,
val changedFiles: ChangedFiles,
val workingDir: File,
messageCollector: GradleMessageCollector,
outputItemsCollector: OutputItemsCollector,
compilerArgs: CommonCompilerArguments,
val buildHistoryFile: File,
val kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null,
val usePreciseJavaTracking: Boolean = false,
val localStateDirs: List<File> = emptyList()
) : GradleCompilerEnvironment(compilerClasspath, messageCollector, outputItemsCollector, compilerArgs)
@@ -7,14 +7,8 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.daemon.client.reportFromDaemon
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.incremental.toDirtyData
import org.jetbrains.kotlin.daemon.incremental.toSimpleDirtyData
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.kotlinWarn
import org.jetbrains.kotlin.incremental.DirtyData
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifference
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.io.File
import java.io.Serializable
import java.rmi.Remote
import java.rmi.server.UnicastRemoteObject
@@ -67,32 +61,4 @@ internal class GradleIncrementalCompilerServicesFacadeImpl(
override fun revert() {
environment.kaptAnnotationsFileUpdater!!.revert()
}
override fun getChanges(artifact: File, sinceTS: Long): Iterable<SimpleDirtyData>? {
val artifactChanges = environment.artifactDifferenceRegistryProvider?.withRegistry(environment.reporter) { registry ->
registry[artifact]
} ?: return null
val (beforeLastBuild, afterLastBuild) = artifactChanges.partition { it.buildTS < sinceTS }
if (beforeLastBuild.isEmpty()) return null
return afterLastBuild.map { it.dirtyData.toSimpleDirtyData() }
}
override fun registerChanges(timestamp: Long, dirtyData: SimpleDirtyData) {
val artifactFile = environment.artifactFile ?: return
environment.artifactDifferenceRegistryProvider?.withRegistry(environment.reporter) { registry ->
registry.add(artifactFile, ArtifactDifference(timestamp, dirtyData.toDirtyData()))
}
}
override fun unknownChanges(timestamp: Long) {
val artifactFile = environment.artifactFile ?: return
environment.artifactDifferenceRegistryProvider?.withRegistry(environment.reporter) { registry ->
registry.remove(artifactFile)
registry.add(artifactFile, ArtifactDifference(timestamp, DirtyData()))
}
}
}
@@ -28,14 +28,19 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import com.intellij.openapi.util.io.FileUtil
import org.gradle.api.invocation.Gradle
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.client.CompileServiceSession
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.common.IncrementalModuleEntry
import org.jetbrains.kotlin.daemon.common.IncrementalModuleInfo
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.incremental.*
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.lang.ref.WeakReference
import java.net.URLClassLoader
import java.rmi.RemoteException
@@ -267,10 +272,10 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
requestedCompilationResults = arrayOf(CompilationResultCategory.IC_COMPILE_ITERATION.code),
compilerMode = CompilerMode.INCREMENTAL_COMPILER,
targetPlatform = targetPlatform,
resultDifferenceFile = environment.buildHistoryFile,
friendDifferenceFile = environment.friendBuildHistoryFile,
usePreciseJavaTracking = environment.usePreciseJavaTracking,
localStateDirs = environment.localStateDirs
localStateDirs = environment.localStateDirs,
buildHistoryFile = environment.buildHistoryFile,
modulesInfo = buildModulesInfo(project.gradle)
)
log.info("Options for KOTLIN DAEMON: $compilationOptions")
@@ -340,6 +345,39 @@ internal class GradleCompilerRunner(private val project: Project) : KotlinCompil
}
companion object {
@Volatile
private var cachedGradle = WeakReference<Gradle>(null)
@Volatile
private var cachedModulesInfo: IncrementalModuleInfo? = null
@Synchronized
private fun buildModulesInfo(gradle: Gradle): IncrementalModuleInfo {
if (cachedGradle.get() === gradle && cachedModulesInfo != null) return cachedModulesInfo!!
val dirToModule = HashMap<File, IncrementalModuleEntry>()
val nameToModules = HashMap<String, HashSet<IncrementalModuleEntry>>()
for (project in gradle.rootProject.allprojects) {
for (task in project.tasks.withType(KotlinCompile::class.java)) {
val module = IncrementalModuleEntry(project.path, task.moduleName, project.buildDir, task.buildHistoryFile)
dirToModule[task.destinationDir] = module
nameToModules.getOrPut(module.name) { HashSet() }.add(module)
}
}
return IncrementalModuleInfo(gradle.rootProject.projectDir, dirToModule, nameToModules)
.also {
cachedGradle = WeakReference(gradle)
cachedModulesInfo = it
}
}
@Synchronized
internal fun clearBuildModulesInfo() {
cachedGradle = WeakReference<Gradle>(null)
cachedModulesInfo = null
}
// created once per gradle instance
// when gradle daemon dies, kotlin daemon should die too
// however kotlin daemon (if it idles enough) can die before gradle daemon dies
@@ -27,17 +27,11 @@ import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX
import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.isWindows
import org.jetbrains.kotlin.incremental.BuildCacheStorage
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
import org.jetbrains.kotlin.incremental.relativeToRoot
import org.jetbrains.kotlin.incremental.stackTraceStr
import org.jetbrains.kotlin.utils.addToStdlib.sumByLong
import java.io.File
import java.lang.management.ManagementFactory
internal class KotlinGradleBuildServices private constructor(gradle: Gradle): BuildAdapter() {
internal class KotlinGradleBuildServices private constructor(gradle: Gradle) : BuildAdapter() {
companion object {
private val CLASS_NAME = KotlinGradleBuildServices::class.java.simpleName
const val FORCE_SYSTEM_GC_MESSAGE = "Forcing System.gc()"
@@ -72,13 +66,8 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle): Bu
private val log = Logging.getLogger(this.javaClass)
private val cleanup = CompilerServicesCleanup()
private var startMemory: Long? = null
internal val workingDir: File by lazy { File(gradle.rootProject.buildDir, "kotlin-build").apply { mkdirs() } }
private val buildCacheStorage: BuildCacheStorage by lazy { BuildCacheStorage(workingDir) }
private val shouldReportMemoryUsage = System.getProperty(SHOULD_REPORT_MEMORY_USAGE_PROPERTY) != null
internal val artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider
get() = buildCacheStorage
// There is function with the same name in BuildAdapter,
// but it is called before any plugin can attach build listener
fun buildStarted() {
@@ -99,6 +88,7 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle): Bu
else {
log.kotlinDebug("Skipping kotlin cleanup since compiler wasn't called")
}
GradleCompilerRunner.clearBuildModulesInfo()
val rootProject = gradle.rootProject
val sessionsDir = GradleCompilerRunner.sessionsDir(rootProject)
@@ -125,45 +115,11 @@ internal class KotlinGradleBuildServices private constructor(gradle: Gradle): Bu
log.lifecycle("[KOTLIN][PERF] Used memory after build: $endMem kb (difference since build start: ${"%+d".format(endMem - startMem)} kb)")
}
closeArtifactDifferenceRegistry()
gradle.removeListener(this)
instance = null
log.kotlinDebug(DISPOSE_MESSAGE)
}
private fun closeArtifactDifferenceRegistry() {
var caughtError = false
try {
if (workingDir.exists()) {
// The working directory may have been removed by the clean task.
// https://youtrack.jetbrains.com/issue/KT-16298
buildCacheStorage.flush(memoryCachesOnly = false)
}
}
catch (e: Throwable) {
log.kotlinDebug { "Error trying to flush artifact difference registry: ${e.stackTraceStr}" }
caughtError = true
}
finally {
try {
buildCacheStorage.close()
}
catch (e: Throwable) {
log.kotlinDebug { "Error trying to close artifact difference registry: ${e.stackTraceStr}" }
caughtError = true
}
}
if (caughtError && workingDir.exists()) {
try {
workingDir.deleteRecursively()
}
catch (e: Throwable) {
log.kotlinDebug { "Error trying to delete kotlin-build $workingDir: ${e.stackTraceStr}" }
}
}
}
private fun getUsedMemoryKb(): Long? {
if (!shouldReportMemoryUsage) return null
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.gradle.internal.*
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.configureMultiProjectIncrementalCompilation
import org.jetbrains.kotlin.scripting.gradle.ScriptingGradleSubplugin
import java.io.File
import java.net.URL
@@ -129,8 +128,7 @@ internal class Kotlin2JvmSourceSetProcessor(
sourceSet: SourceSet,
tasksProvider: KotlinTasksProvider,
kotlinSourceSetProvider: KotlinSourceSetProvider,
private val kotlinPluginVersion: String,
private val kotlinGradleBuildServices: KotlinGradleBuildServices
private val kotlinPluginVersion: String
) : KotlinSourceSetProcessor<KotlinCompile>(
project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider,
dslExtensionName = KOTLIN_DSL_NAME,
@@ -189,11 +187,6 @@ internal class Kotlin2JvmSourceSetProcessor(
syncOutputTask = createSyncOutputTask(project, kotlinTask, javaTask, kotlinAfterJavaTask, sourceSetName)
}
val artifactFile = project.tryGetSingleArtifact()
configureMultiProjectIncrementalCompilation(project, kotlinTask, javaTask, kotlinAfterJavaTask,
kotlinGradleBuildServices.artifactDifferenceRegistryProvider,
artifactFile)
if (project.pluginManager.hasPlugin("java-library") && sourceSetName == SourceSet.MAIN_SOURCE_SET_NAME) {
val (classesProviderTask, classesDirectory) = when {
isSeparateClassesDirSupported -> (kotlinAfterJavaTask ?: kotlinTask).let { it to it.destinationDir }
@@ -203,28 +196,10 @@ internal class Kotlin2JvmSourceSetProcessor(
registerKotlinOutputForJavaLibrary(classesDirectory, classesProviderTask)
}
// To make Gradle read this location from the task's @LocalState property and clean it on cache hit:
kotlinTask.buildServicesWorkingDir = Callable { kotlinGradleBuildServices.workingDir }
}
}
}
private 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
}
private fun registerKotlinOutputForJavaLibrary(outputDir: File, taskDependency: Task): Boolean {
val configuration = project.configurations.getByName("apiElements")
@@ -420,11 +395,10 @@ internal abstract class AbstractKotlinPlugin(
internal open class KotlinPlugin(
tasksProvider: KotlinTasksProvider,
kotlinSourceSetProvider: KotlinSourceSetProvider,
kotlinPluginVersion: String,
private val kotlinGradleBuildServices: KotlinGradleBuildServices
kotlinPluginVersion: String
) : AbstractKotlinPlugin(tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion) {
override fun buildSourceSetProcessor(project: Project, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet, kotlinPluginVersion: String) =
Kotlin2JvmSourceSetProcessor(project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion, kotlinGradleBuildServices)
Kotlin2JvmSourceSetProcessor(project, javaBasePlugin, sourceSet, tasksProvider, kotlinSourceSetProvider, kotlinPluginVersion)
override fun apply(project: Project) {
project.createKaptExtension()
@@ -454,8 +428,7 @@ internal open class Kotlin2JsPlugin(
internal open class KotlinAndroidPlugin(
val tasksProvider: KotlinTasksProvider,
private val kotlinSourceSetProvider: KotlinSourceSetProvider,
private val kotlinPluginVersion: String,
private val kotlinGradleBuildServices: KotlinGradleBuildServices
private val kotlinPluginVersion: String
) : Plugin<Project> {
override fun apply(project: Project) {
@@ -470,8 +443,7 @@ internal open class KotlinAndroidPlugin(
val kotlinTools = KotlinConfigurationTools(
kotlinSourceSetProvider,
tasksProvider,
kotlinPluginVersion,
kotlinGradleBuildServices)
kotlinPluginVersion)
val legacyVersionThreshold = "2.5.0"
@@ -492,17 +464,12 @@ internal open class KotlinAndroidPlugin(
class KotlinConfigurationTools internal constructor(val kotlinSourceSetProvider: KotlinSourceSetProvider,
val kotlinTasksProvider: KotlinTasksProvider,
val kotlinPluginVersion: String,
val kotlinGradleBuildServices: KotlinGradleBuildServices)
val kotlinPluginVersion: String)
/** Part of Android configuration, that works only with the old public API.
* @see [LegacyAndroidAndroidProjectHandler] that is implemented with the old internal API and [AndroidGradle25VariantProcessor] that works
* with the new public API */
abstract class AbstractAndroidProjectHandler<V>(private val kotlinConfigurationTools: KotlinConfigurationTools) {
protected val artifactDifferenceRegistryProvider get() =
kotlinConfigurationTools.kotlinGradleBuildServices.artifactDifferenceRegistryProvider
protected val logger = Logging.getLogger(this.javaClass)
abstract fun forEachVariant(project: Project, action: (V) -> Unit): Unit
@@ -525,12 +492,6 @@ abstract class AbstractAndroidProjectHandler<V>(private val kotlinConfigurationT
kotlinTask: KotlinCompile,
kotlinAfterJavaTask: KotlinCompile?): Unit
protected abstract fun configureMultiProjectIc(project: Project,
variantData: V,
javaTask: AbstractCompile,
kotlinTask: KotlinCompile,
kotlinAfterJavaTask: KotlinCompile?)
protected abstract fun wrapVariantDataForKapt(variantData: V): KaptVariantData<V>
fun handleProject(project: Project) {
@@ -648,8 +609,6 @@ abstract class AbstractAndroidProjectHandler<V>(private val kotlinConfigurationT
wireKotlinTasks(project, androidPlugin, androidExt, variantData, javaTask,
kotlinTask, kotlinAfterJavaTask)
configureMultiProjectIc(project, variantData, javaTask, kotlinTask, kotlinAfterJavaTask)
val appliedPlugins = subpluginEnvironment.addSubpluginOptions(
project, kotlinTask, javaTask, wrapVariantDataForKapt(variantData), this, null)
@@ -56,7 +56,7 @@ abstract class KotlinBasePluginWrapper(protected val fileResolver: FileResolver)
open class KotlinPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
KotlinPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion, kotlinGradleBuildServices)
KotlinPlugin(KotlinTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
override val projectExtensionClass: KClass<out KotlinJvmProjectExtension>
get() = KotlinJvmProjectExtension::class
@@ -69,7 +69,7 @@ open class KotlinCommonPluginWrapper @Inject constructor(fileResolver: FileResol
open class KotlinAndroidPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(kotlinGradleBuildServices: KotlinGradleBuildServices) =
KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion, kotlinGradleBuildServices)
KotlinAndroidPlugin(AndroidTasksProvider(), KotlinSourceSetProviderImpl(fileResolver), kotlinPluginVersion)
}
open class Kotlin2JsPluginWrapper @Inject constructor(fileResolver: FileResolver): KotlinBasePluginWrapper(fileResolver) {
@@ -16,8 +16,6 @@ import org.jetbrains.kotlin.gradle.internal.registerGeneratedJavaSource
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.utils.checkedReflection
import org.jetbrains.kotlin.incremental.configureMultiProjectIncrementalCompilation
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProviderAndroidWrapper
import java.io.File
internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools)
@@ -99,18 +97,6 @@ internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: Kotl
javaSourceDirectory: File) =
variantData.addJavaSourceFoldersToModel(javaSourceDirectory)
override fun configureMultiProjectIc(project: Project, variantData: BaseVariantData<out BaseVariantOutputData>, javaTask: AbstractCompile, kotlinTask: KotlinCompile, kotlinAfterJavaTask: KotlinCompile?) {
if ((kotlinAfterJavaTask ?: kotlinTask).incremental) {
val artifactFile = project.tryGetSingleArtifact(variantData)
val artifactDifferenceRegistryProvider = ArtifactDifferenceRegistryProviderAndroidWrapper(
artifactDifferenceRegistryProvider,
{ AndroidGradleWrapper.getJarToAarMapping(variantData) }
)
configureMultiProjectIncrementalCompilation(project, kotlinTask, javaTask, kotlinAfterJavaTask,
artifactDifferenceRegistryProvider, artifactFile)
}
}
override fun getTestedVariantData(variantData: BaseVariantData<*>): BaseVariantData<*>? =
((variantData as? TestVariantData)?.testedVariantData as? BaseVariantData<*>)
@@ -118,19 +104,6 @@ internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: Kotl
return variantData.mergeResourcesTask?.rawInputFolders?.toList() ?: emptyList()
}
private fun Project.tryGetSingleArtifact(variantData: BaseVariantData<*>): File? {
val log = logger
log.kotlinDebug { "Trying to determine single artifact for project $path" }
val outputs = variantData.outputs
if (outputs.size != 1) {
log.kotlinDebug { "Output count != 1 for variant: ${outputs.map { it.outputFile.relativeTo(rootDir).path }.joinToString()}" }
return null
}
return variantData.outputs.first().outputFile
}
private val BaseVariantData<*>.sourceProviders: List<SourceProvider>
get() = variantConfiguration.sortedSourceProviders
@@ -41,12 +41,9 @@ import org.jetbrains.kotlin.gradle.internal.prepareCompilerArguments
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistry
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
import org.jetbrains.kotlin.utils.LibraryUtils
import java.io.File
import java.util.*
import java.util.concurrent.Callable
import kotlin.properties.Delegates
const val ANNOTATIONS_PLUGIN_NAME = "org.jetbrains.kotlin.kapt"
@@ -99,6 +96,9 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
logger.kotlinDebug { "Set $this.incremental=$value" }
}
@get:Internal
internal val buildHistoryFile: File get() = File(taskBuildDirectory, "build-history.bin")
@get:Internal
internal val pluginOptions = CompilerPluginOptions()
@@ -267,9 +267,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null
@get:Internal
val buildHistoryFile: File get() = File(taskBuildDirectory, "build-history.bin")
@get:Internal
val kaptOptions = KaptOptions()
@@ -281,12 +278,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
@get:Optional
var javaPackagePrefix: String? = null
@get:Internal
internal var artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider? = null
@get:Internal
internal var artifactFile: File? = null
@get:Input
var usePreciseJavaTracking: Boolean = true
set(value) {
@@ -294,9 +285,6 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
logger.kotlinDebug { "Set $this.usePreciseJavaTracking=$value" }
}
@get:LocalState @get:Optional
internal var buildServicesWorkingDir: Callable<File>? = null
init {
incremental = true
}
@@ -350,23 +338,19 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
val messageCollector = GradleMessageCollector(logger)
val outputItemCollector = OutputItemsCollectorImpl()
val compilerRunner = GradleCompilerRunner(project)
val reporter = GradleICReporter(project.rootProject.projectDir)
val environment = when {
!incremental ->
GradleCompilerEnvironment(computedCompilerClasspath, messageCollector, outputItemCollector, args)
else -> {
logger.info(USING_INCREMENTAL_COMPILATION_MESSAGE)
val friendTask = friendTaskName?.let { project.tasks.findByName(it) as? KotlinCompile }
GradleIncrementalCompilerEnvironment(
computedCompilerClasspath,
if (hasFilesInTaskBuildDirectory()) changedFiles else ChangedFiles.Unknown(),
reporter, taskBuildDirectory,
messageCollector, outputItemCollector, args, kaptAnnotationsFileUpdater,
artifactDifferenceRegistryProvider,
artifactFile = artifactFile,
taskBuildDirectory,
messageCollector, outputItemCollector, args,
buildHistoryFile = buildHistoryFile,
friendBuildHistoryFile = friendTask?.buildHistoryFile,
kaptAnnotationsFileUpdater = kaptAnnotationsFileUpdater,
usePreciseJavaTracking = usePreciseJavaTracking,
localStateDirs = outputDirectories
)
@@ -386,18 +370,11 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), Kotl
environment)
processCompilerExitCode(exitCode)
artifactDifferenceRegistryProvider?.withRegistry(reporter) {
it.flush(true)
}
}
catch (e: Throwable) {
cleanupOnError()
artifactDifferenceRegistryProvider?.clean()
throw e
}
finally {
artifactDifferenceRegistryProvider?.withRegistry(reporter, ArtifactDifferenceRegistry::close)
}
anyClassesCompiled = true
}
@@ -525,8 +502,10 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile<K2JSCompilerArguments>(),
GradleIncrementalCompilerEnvironment(
computedCompilerClasspath,
if (hasFilesInTaskBuildDirectory()) changedFiles else ChangedFiles.Unknown(),
reporter, taskBuildDirectory,
messageCollector, outputItemCollector, args)
taskBuildDirectory,
messageCollector, outputItemCollector, args,
buildHistoryFile = buildHistoryFile
)
}
else -> {
GradleCompilerEnvironment(computedCompilerClasspath, messageCollector, outputItemCollector, args)
@@ -23,19 +23,19 @@ import org.jetbrains.kotlin.gradle.plugin.mapKotlinTaskProperties
internal open class KotlinTasksProvider {
fun createKotlinJVMTask(project: Project, name: String, sourceSetName: String): KotlinCompile =
project.tasks.create(name, KotlinCompile::class.java).apply {
configure(project, sourceSetName)
}
project.tasks.create(name, KotlinCompile::class.java).apply {
configure(project, sourceSetName)
}
fun createKotlinJSTask(project: Project, name: String, sourceSetName: String): Kotlin2JsCompile =
project.tasks.create(name, Kotlin2JsCompile::class.java).apply {
configure(project, sourceSetName)
}
project.tasks.create(name, Kotlin2JsCompile::class.java).apply {
configure(project, sourceSetName)
}
fun createKotlinCommonTask(project: Project, name: String, sourceSetName: String): KotlinCompileCommon =
project.tasks.create(name, KotlinCompileCommon::class.java).apply {
configure(project, sourceSetName)
}
project.tasks.create(name, KotlinCompileCommon::class.java).apply {
configure(project, sourceSetName)
}
private fun AbstractKotlinCompile<*>.configure(project: Project, sourceSetName: String) {
this.sourceSetName = sourceSetName
@@ -44,20 +44,20 @@ internal open class KotlinTasksProvider {
}
protected open val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Default()
RegexTaskToFriendTaskMapper.Default()
}
internal class KotlinCommonTasksProvider : KotlinTasksProvider() {
override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Common()
RegexTaskToFriendTaskMapper.Common()
}
internal class Kotlin2JsTasksProvider : KotlinTasksProvider() {
override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.JavaScript()
RegexTaskToFriendTaskMapper.JavaScript()
}
internal class AndroidTasksProvider : KotlinTasksProvider() {
override val taskToFriendTaskMapper: TaskToFriendTaskMapper =
RegexTaskToFriendTaskMapper.Android()
RegexTaskToFriendTaskMapper.Android()
}
@@ -1,108 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental
import org.gradle.api.logging.Logging
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.incremental.CacheVersion
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistry
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryImpl
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
import org.jetbrains.kotlin.incremental.stackTraceStr
import org.jetbrains.kotlin.incremental.storage.BasicMapsOwner
import java.io.File
/**
* "Global" cache holder. Should be created once per root project.
*/
internal class BuildCacheStorage(private val workingDir: File) : BasicMapsOwner(workingDir), ArtifactDifferenceRegistryProvider {
companion object {
private val OWN_VERSION = 0
private val ARTIFACT_DIFFERENCE = "artifact-difference"
}
private val log = Logging.getLogger(this.javaClass)
private val versionFile = File(workingDir, "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 })
@Volatile
private var artifactDifferenceRegistry: ArtifactDifferenceRegistryImpl? = null
@Synchronized
override fun <T> withRegistry(report: (String)->Unit, fn: (ArtifactDifferenceRegistry)->T): T? {
try {
if (artifactDifferenceRegistry == null) {
artifactDifferenceRegistry = registerMap(ArtifactDifferenceRegistryImpl(ARTIFACT_DIFFERENCE.storageFile))
}
return fn(artifactDifferenceRegistry!!)
}
catch (e1: Throwable) {
report("Error accessing artifact file difference registry: ${e1.stackTraceStr}}")
report("Cleaning artifact difference storage and trying again")
clean()
try {
artifactDifferenceRegistry = registerMap(ArtifactDifferenceRegistryImpl(ARTIFACT_DIFFERENCE.storageFile))
return fn(artifactDifferenceRegistry!!)
}
catch (e2: Throwable) {
report("Second error accessing artifact file difference registry: ${e2.stackTraceStr}}")
}
}
return null
}
init {
if (version.checkVersion() != CacheVersion.Action.DO_NOTHING) {
log.kotlinDebug { "Cache version is not up-to-date" }
clean()
}
}
@Synchronized
override fun clean() {
try {
close()
}
catch (e: Throwable) {
log.kotlinDebug { "Exception while closing caches: ${e.stackTraceStr}" }
}
workingDir.deleteRecursively()
workingDir.mkdirs()
versionFile.delete()
}
override fun flush(memoryCachesOnly: Boolean) {
super.flush(memoryCachesOnly)
version.saveIfNeeded()
}
override fun close() {
super.close()
artifactDifferenceRegistry = null
}
}
@@ -1,72 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.logging.Logger
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.api.tasks.compile.JavaCompile
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.gradle.plugin.kotlinDebug
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.incremental.multiproject.ArtifactDifferenceRegistryProvider
import java.io.File
internal fun configureMultiProjectIncrementalCompilation(
project: Project,
kotlinTask: KotlinCompile,
javaTask: AbstractCompile,
kotlinAfterJavaTask: KotlinCompile?,
artifactDifferenceRegistryProvider: ArtifactDifferenceRegistryProvider,
artifactFile: File?
) {
val log: Logger = kotlinTask.logger
log.kotlinDebug { "Configuring multi-project incremental compilation for project ${project.path}" }
fun cannotPerformMultiProjectIC(reason: String) {
log.kotlinDebug {
"Multi-project kotlin incremental compilation won't be performed for projects that depend on ${project.path}: $reason"
}
if (artifactFile != null) {
artifactDifferenceRegistryProvider.withRegistry({log.kotlinDebug {it}}) {
it.remove(artifactFile)
}
}
}
fun isUnknownTaskOutputtingToJavaDestination(task: Task): Boolean {
return task !is JavaCompile &&
task !is KotlinCompile &&
task is AbstractCompile &&
FileUtil.isAncestor(javaTask.destinationDir, task.destinationDir, /* strict = */ false)
}
if (!kotlinTask.incremental) {
return cannotPerformMultiProjectIC(reason = "incremental compilation is not enabled")
}
// todo: split registry for reading and writing changes
val illegalTask = project.tasks.find(::isUnknownTaskOutputtingToJavaDestination)
if (illegalTask != null) {
return cannotPerformMultiProjectIC(reason = "unknown task outputs to java destination dir ${illegalTask.path} $(${illegalTask.javaClass})")
}
val kotlinCompile = kotlinAfterJavaTask ?: kotlinTask
kotlinCompile.artifactDifferenceRegistryProvider = artifactDifferenceRegistryProvider
kotlinCompile.artifactFile = artifactFile
}
@@ -1,47 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental.multiproject
import org.jetbrains.kotlin.incremental.DirtyData
import org.jetbrains.kotlin.incremental.ICReporter
import java.io.File
interface ArtifactDifferenceRegistry {
operator fun get(artifact: File): Iterable<ArtifactDifference>?
fun add(artifact: File, difference: ArtifactDifference)
fun remove(artifact: File)
fun flush(memoryCachesOnly: Boolean)
fun close()
}
class ArtifactDifference(val buildTS: Long, val dirtyData: DirtyData)
interface ArtifactDifferenceRegistryProvider {
fun <T> withRegistry(
report: (String) -> Unit,
fn: (ArtifactDifferenceRegistry) -> T
): T?
fun <T> withRegistry(
reporter: ICReporter,
fn: (ArtifactDifferenceRegistry) -> T
): T? {
return withRegistry({reporter.report {it}}, fn)
}
fun clean()
}
@@ -1,47 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental.multiproject
import java.io.File
internal class ArtifactDifferenceRegistryProviderAndroidWrapper(
private val provider: ArtifactDifferenceRegistryProvider,
private val jarToAarMapping: () -> Map<File, File>
) : ArtifactDifferenceRegistryProvider {
override fun <T> withRegistry(report: (String)->Unit, fn: (ArtifactDifferenceRegistry)->T): T? {
return provider.withRegistry(report) { originalRegistry ->
val wrapped = ArtifactDifferenceRegistryAndroidWrapper(originalRegistry, jarToAarMapping())
fn(wrapped)
}
}
override fun clean() {
provider.clean()
}
}
// When lib is compiled, changes are associated with .aar files.
// However when app is compiled, there is just .jar in classpath.
private class ArtifactDifferenceRegistryAndroidWrapper(
private val registry: ArtifactDifferenceRegistry,
private val jarToAarMapping: Map<File, File>
) : ArtifactDifferenceRegistry by registry {
override fun get(artifact: File): Iterable<ArtifactDifference>? {
val mappedFile = jarToAarMapping[artifact] ?: return null
return registry[mappedFile]
}
}
@@ -1,116 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental.multiproject
import com.intellij.util.io.DataExternalizer
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) }
}