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
@@ -60,13 +60,13 @@ class IncrementalCompilationOptions(
reportSeverity: Int,
/** @See [CompilationResultCategory]] */
requestedCompilationResults: Array<Int>,
val resultDifferenceFile: File? = null,
val friendDifferenceFile: File? = null,
val usePreciseJavaTracking: Boolean,
/**
* Directories that should be cleared when IC decides to rebuild
*/
val localStateDirs: List<File>
val localStateDirs: List<File>,
val buildHistoryFile: File,
val modulesInfo: IncrementalModuleInfo
) : CompilationOptions(compilerMode, targetPlatform, reportCategories, reportSeverity, requestedCompilationResults) {
companion object {
const val serialVersionUID: Long = 0
@@ -81,8 +81,7 @@ class IncrementalCompilationOptions(
"workingDir=$workingDir, " +
"customCacheVersionFileName='$customCacheVersionFileName', " +
"customCacheVersion=$customCacheVersion, " +
"resultDifferenceFile=$resultDifferenceFile, " +
"friendDifferenceFile=$friendDifferenceFile, " +
"buildHistoryFile=$buildHistoryFile, " +
"usePreciseJavaTracking=$usePreciseJavaTracking" +
"localStateDirs=$localStateDirs" +
")"
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.daemon.common
import java.io.File
import java.rmi.RemoteException
interface IncrementalCompilerServicesFacade : CompilerServicesFacadeBase {
@@ -29,14 +28,4 @@ interface IncrementalCompilerServicesFacade : CompilerServicesFacadeBase {
@Throws(RemoteException::class)
fun revert()
// ChangesRegistry
@Throws(RemoteException::class)
fun registerChanges(timestamp: Long, dirtyData: SimpleDirtyData)
@Throws(RemoteException::class)
fun unknownChanges(timestamp: Long)
@Throws(RemoteException::class)
fun getChanges(artifact: File, sinceTS: Long): Iterable<SimpleDirtyData>?
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.daemon.common
import java.io.File
import java.io.Serializable
data class IncrementalModuleEntry(
private val projectPath: String,
val name: String,
val buildDir: File,
val buildHistoryFile: File
) : Serializable {
companion object {
private const val serialVersionUID = 0L
}
}
class IncrementalModuleInfo(
val projectRoot: File,
val dirToModule: Map<File, IncrementalModuleEntry>,
val nameToModules: Map<String, Set<IncrementalModuleEntry>>
) : Serializable {
companion object {
private const val serialVersionUID = 0L
}
}
@@ -41,8 +41,6 @@ import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.daemon.common.*
import org.jetbrains.kotlin.daemon.incremental.RemoteAnnotationsFileUpdater
import org.jetbrains.kotlin.daemon.incremental.RemoteArtifactChangesProvider
import org.jetbrains.kotlin.daemon.incremental.RemoteChangesRegistry
import org.jetbrains.kotlin.daemon.report.CompileServicesFacadeMessageCollector
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporter
import org.jetbrains.kotlin.daemon.report.DaemonMessageReporterPrintStreamAdapter
@@ -50,6 +48,8 @@ import org.jetbrains.kotlin.daemon.report.RemoteICReporter
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.parsing.classesFqNames
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
import org.jetbrains.kotlin.incremental.multiproject.ModulesApiHistoryImpl
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.modules.Module
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
@@ -517,9 +517,6 @@ class CompileServiceImpl(
ChangedFiles.Unknown()
}
val artifactChanges = RemoteArtifactChangesProvider(servicesFacade)
val changesRegistry = RemoteChangesRegistry(servicesFacade)
val workingDir = incrementalCompilationOptions.workingDir
val versions = commonCacheVersions(workingDir) +
customCacheVersion(incrementalCompilationOptions.customCacheVersion,
@@ -527,13 +524,19 @@ class CompileServiceImpl(
workingDir,
enabled = true)
val compiler = IncrementalJvmCompilerRunner(workingDir, javaSourceRoots, versions,
reporter, annotationFileUpdater,
artifactChanges, changesRegistry,
buildHistoryFile = incrementalCompilationOptions.resultDifferenceFile,
friendBuildHistoryFile = incrementalCompilationOptions.friendDifferenceFile,
usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking,
localStateDirs = incrementalCompilationOptions.localStateDirs
val modulesApiHistory = incrementalCompilationOptions.run {
ModulesApiHistoryImpl(modulesInfo)
}
val compiler = IncrementalJvmCompilerRunner(
workingDir,
javaSourceRoots,
versions,
reporter, annotationFileUpdater,
buildHistoryFile = incrementalCompilationOptions.buildHistoryFile,
localStateDirs = incrementalCompilationOptions.localStateDirs,
usePreciseJavaTracking = incrementalCompilationOptions.usePreciseJavaTracking,
modulesApiHistory = modulesApiHistory
)
return compiler.compile(allKotlinFiles, k2jvmArgs, compilerMessageCollector, changedFiles)
}
@@ -1,27 +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.daemon.incremental
import org.jetbrains.kotlin.daemon.common.IncrementalCompilerServicesFacade
import org.jetbrains.kotlin.incremental.DirtyData
import org.jetbrains.kotlin.incremental.multiproject.ArtifactChangesProvider
import java.io.File
class RemoteArtifactChangesProvider(private val servicesFacade: IncrementalCompilerServicesFacade) : ArtifactChangesProvider {
override fun getChanges(artifact: File, sinceTS: Long): Iterable<DirtyData>? =
servicesFacade.getChanges(artifact, sinceTS)?.map { it.toDirtyData() }
}
@@ -1,31 +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.daemon.incremental
import org.jetbrains.kotlin.daemon.common.IncrementalCompilerServicesFacade
import org.jetbrains.kotlin.incremental.DirtyData
import org.jetbrains.kotlin.incremental.multiproject.ChangesRegistry
internal class RemoteChangesRegistry(private val servicesFacade: IncrementalCompilerServicesFacade) : ChangesRegistry {
override fun unknownChanges(timestamp: Long) {
servicesFacade.unknownChanges(timestamp)
}
override fun registerChanges(timestamp: Long, dirtyData: DirtyData) {
servicesFacade.registerChanges(timestamp, dirtyData.toSimpleDirtyData())
}
}
@@ -15,6 +15,7 @@ dependencies {
compile(project(":compiler:frontend.java"))
compile(project(":compiler:cli"))
compile(project(":kotlin-build-common"))
compile(project(":compiler:daemon-common"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeJars("annotations") }
@@ -29,6 +29,11 @@ data class BuildDifference(val ts: Long, val isIncremental: Boolean, val dirtyDa
data class BuildDiffsStorage(val buildDiffs: List<BuildDifference>) {
companion object {
fun readFromFile(file: File, reporter: ICReporter?): BuildDiffsStorage? {
val diffs = readDiffsFromFile(file, reporter)
return diffs?.let { BuildDiffsStorage(it) }
}
fun readDiffsFromFile(file: File, reporter: ICReporter?): MutableList<BuildDifference>? {
fun reportFail(reason: String) {
reporter?.report { "Could not read diff from file $file: $reason" }
}
@@ -48,7 +53,7 @@ data class BuildDiffsStorage(val buildDiffs: List<BuildDifference>) {
repeat(size) {
result.add(input.readBuildDifference())
}
return BuildDiffsStorage(result)
return result
}
}
catch (e: IOException) {
@@ -23,5 +23,5 @@ internal sealed class ChangesEither {
val lookupSymbols: Collection<LookupSymbol> = emptyList(),
val fqNames: Collection<FqName> = emptyList()
) : ChangesEither()
internal class Unknown : ChangesEither()
internal class Unknown(val reason: String? = null) : ChangesEither()
}
@@ -27,8 +27,6 @@ import org.jetbrains.kotlin.compilerRunner.toGeneratedFile
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.multiproject.ArtifactChangesProvider
import org.jetbrains.kotlin.incremental.multiproject.ChangesRegistry
import org.jetbrains.kotlin.incremental.parsing.classesFqNames
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
@@ -43,8 +41,6 @@ abstract class IncrementalCompilerRunner<
cacheDirName: String,
protected val cacheVersions: List<CacheVersion>,
protected val reporter: ICReporter,
protected val artifactChangesProvider: ArtifactChangesProvider?,
protected val changesRegistry: ChangesRegistry?,
private val localStateDirs: Collection<File> = emptyList()
) {
@@ -307,16 +303,11 @@ abstract class IncrementalCompilerRunner<
open fun runWithNoDirtyKotlinSources(caches: CacheManager): Boolean = false
protected open fun processChangesAfterBuild(compilationMode: CompilationMode, currentBuildInfo: BuildInfo, dirtyData: DirtyData) {
if (changesRegistry == null) return
if (compilationMode is CompilationMode.Incremental) {
changesRegistry.registerChanges(currentBuildInfo.startTS, dirtyData)
}
else {
assert(compilationMode is CompilationMode.Rebuild) { "Unexpected compilation mode: ${compilationMode::class.java}" }
changesRegistry.unknownChanges(currentBuildInfo.startTS)
}
protected open fun processChangesAfterBuild(
compilationMode: CompilationMode,
currentBuildInfo: BuildInfo,
dirtyData: DirtyData
) {
}
companion object {
@@ -67,9 +67,7 @@ class IncrementalJsCompilerRunner(
workingDir,
"caches-js",
cacheVersions,
reporter,
artifactChangesProvider = null,
changesRegistry = null
reporter
) {
override fun isICEnabled(): Boolean =
IncrementalCompilation.isEnabled() && IncrementalCompilation.isEnabledForJs()
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.incremental
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
@@ -39,8 +38,9 @@ import org.jetbrains.kotlin.config.IncrementalCompilation
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.multiproject.ArtifactChangesProvider
import org.jetbrains.kotlin.incremental.multiproject.ChangesRegistry
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
import org.jetbrains.kotlin.incremental.multiproject.ModulesApiHistory
import org.jetbrains.kotlin.incremental.util.Either
import org.jetbrains.kotlin.load.java.JavaClassesTracker
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
@@ -64,6 +64,7 @@ fun makeIncrementally(
val rootsWalk = sourceRoots.asSequence().flatMap { it.walk() }
val files = rootsWalk.filter(File::isFile)
val sourceFiles = files.filter { it.extension.toLowerCase() in allExtensions }.toList()
val buildHistoryFile = File(cachesDir, "build-history.bin")
withIC {
val compiler = IncrementalJvmCompilerRunner(
@@ -72,7 +73,9 @@ fun makeIncrementally(
versions, reporter,
// Use precise setting in case of non-Gradle build
usePreciseJavaTracking = true,
localStateDirs = emptyList()
localStateDirs = emptyList(),
buildHistoryFile = buildHistoryFile,
modulesApiHistory = EmptyModulesApiHistory
)
compiler.compile(sourceFiles, args, messageCollector, providedChangedFiles = null)
}
@@ -101,19 +104,15 @@ class IncrementalJvmCompilerRunner(
cacheVersions: List<CacheVersion>,
reporter: ICReporter,
private var kaptAnnotationsFileUpdater: AnnotationFileUpdater? = null,
artifactChangesProvider: ArtifactChangesProvider? = null,
changesRegistry: ChangesRegistry? = null,
private val buildHistoryFile: File? = null,
private val friendBuildHistoryFile: File? = null,
private val usePreciseJavaTracking: Boolean,
localStateDirs: Collection<File>
private val buildHistoryFile: File,
localStateDirs: Collection<File>,
private val modulesApiHistory: ModulesApiHistory
) : IncrementalCompilerRunner<K2JVMCompilerArguments, IncrementalJvmCachesManager>(
workingDir,
"caches-jvm",
cacheVersions,
reporter,
artifactChangesProvider,
changesRegistry,
localStateDirs = localStateDirs
) {
override fun isICEnabled(): Boolean =
@@ -166,41 +165,18 @@ class IncrementalJvmCompilerRunner(
val lastBuildInfo = BuildInfo.read(lastBuildInfoFile) ?: return CompilationMode.Rebuild { "No information on previous build" }
reporter.report { "Last Kotlin Build info -- $lastBuildInfo" }
val changesFromFriend by lazy {
val myLastTS = lastBuildInfo.startTS
val storage = friendBuildHistoryFile?.let { BuildDiffsStorage.readFromFile(it, reporter) } ?: return@lazy ChangesEither.Unknown()
val classpathChanges = getClasspathChanges(args.classpathAsList, changedFiles, lastBuildInfo)
val (prevDiffs, newDiffs) = storage.buildDiffs.partition { it.ts < myLastTS }
if (prevDiffs.isEmpty()) return@lazy ChangesEither.Unknown()
val dirtyLookupSymbols = HashSet<LookupSymbol>()
val dirtyClassesFqNames = HashSet<FqName>()
for ((_, isIncremental, dirtyData) in newDiffs) {
if (!isIncremental) return@lazy ChangesEither.Unknown()
dirtyLookupSymbols.addAll(dirtyData.dirtyLookupSymbols)
dirtyClassesFqNames.addAll(dirtyData.dirtyClassesFqNames)
@Suppress("UNUSED_VARIABLE") // for sealed when
val unused = when (classpathChanges) {
is ChangesEither.Unknown -> return CompilationMode.Rebuild {
// todo: we can recompile all files incrementally (not cleaning caches), so rebuild won't propagate
"Could not get classpath's changes${classpathChanges.reason?.let { ": $it" }}"
}
is ChangesEither.Known -> {
markDirtyBy(classpathChanges.lookupSymbols)
markDirtyBy(classpathChanges.fqNames)
}
markDirtyBy(dirtyLookupSymbols)
markDirtyBy(dirtyClassesFqNames)
ChangesEither.Known(dirtyLookupSymbols, dirtyClassesFqNames)
}
val friendDirs = args.friendPaths?.map { File(it) } ?: emptyList()
for (file in changedFiles.removed.asSequence() + changedFiles.modified.asSequence()) {
if (!file.isClassFile()) continue
val isFriendClassFile = friendDirs.any { FileUtil.isAncestor(it, file, false) }
if (isFriendClassFile && changesFromFriend is ChangesEither.Known) continue
return CompilationMode.Rebuild { "Cannot get changes from modified or removed class file: ${reporter.pathsAsString(file)}" }
}
val classpathSet = args.classpathAsList.toHashSet()
val modifiedClasspathEntries = changedFiles.modified.filter { it in classpathSet }
val classpathChanges = getClasspathChanges(modifiedClasspathEntries, lastBuildInfo)
if (classpathChanges !is ChangesEither.Known) {
return CompilationMode.Rebuild { "could not get changes from modified classpath entries: ${reporter.pathsAsString(modifiedClasspathEntries)}" }
}
if (!usePreciseJavaTracking) {
@@ -221,8 +197,6 @@ class IncrementalJvmCompilerRunner(
val removedClassesChanges = getRemovedClassesChanges(caches, changedFiles)
markDirtyBy(androidLayoutChanges)
markDirtyBy(classpathChanges.lookupSymbols)
markDirtyBy(classpathChanges.fqNames)
markDirtyBy(removedClassesChanges.dirtyLookupSymbols)
markDirtyBy(removedClassesChanges.dirtyClassesFqNames)
@@ -285,33 +259,49 @@ class IncrementalJvmCompilerRunner(
}
private fun getClasspathChanges(
modifiedClasspath: List<File>,
lastBuildInfo: BuildInfo?
classpath: List<File>,
changedFiles: ChangedFiles.Known,
lastBuildInfo: BuildInfo
): ChangesEither {
if (modifiedClasspath.isEmpty()) {
reporter.report {"No classpath changes"}
return ChangesEither.Known()
val classpathSet = HashSet<File>()
for (file in classpath) {
when {
file.isFile -> classpathSet.add(file)
file.isDirectory -> file.walk().filterTo(classpathSet) { it.isFile }
}
}
val lastBuildTS = lastBuildInfo?.startTS
if (lastBuildTS == null) {
reporter.report {"Could not determine last build timestamp"}
return ChangesEither.Unknown()
}
val modifiedClasspath = changedFiles.modified.filterTo(HashSet()) { it in classpathSet }
val removedClasspath = changedFiles.removed.filterTo(HashSet()) { it in classpathSet }
// todo: removed classes could be processed normally
if (removedClasspath.isNotEmpty()) return ChangesEither.Unknown("Some files are removed from classpath $removedClasspath")
if (modifiedClasspath.isEmpty()) return ChangesEither.Known()
val lastBuildTS = lastBuildInfo.startTS
val symbols = HashSet<LookupSymbol>()
val fqNames = HashSet<FqName>()
for (file in modifiedClasspath) {
val diffs = artifactChangesProvider?.getChanges(file, lastBuildTS)
if (diffs == null) {
reporter.report {"Could not get changes for file: $file"}
return ChangesEither.Unknown()
val historyFilesEither = modulesApiHistory.historyFilesForChangedFiles(modifiedClasspath)
val historyFiles = when (historyFilesEither) {
is Either.Success<Set<File>> -> historyFilesEither.value
is Either.Error -> return ChangesEither.Unknown(historyFilesEither.reason)
}
for (historyFile in historyFiles) {
val allBuilds = BuildDiffsStorage.readDiffsFromFile(historyFile, reporter = reporter)
?: return ChangesEither.Unknown("Could not read diffs from $historyFile")
val (knownBuilds, newBuilds) = allBuilds.partition { it.ts <= lastBuildTS }
if (knownBuilds.isEmpty()) {
return ChangesEither.Unknown("No previously known builds for $historyFile")
}
diffs.forEach {
symbols.addAll(it.dirtyLookupSymbols)
fqNames.addAll(it.dirtyClassesFqNames)
for (buildDiff in newBuilds) {
val dirtyData = buildDiff.dirtyData
symbols.addAll(dirtyData.dirtyLookupSymbols)
fqNames.addAll(dirtyData.dirtyClassesFqNames)
}
}
@@ -400,16 +390,15 @@ class IncrementalJvmCompilerRunner(
override fun additionalDirtyLookupSymbols(): Iterable<LookupSymbol> =
javaFilesProcessor?.allChangedSymbols ?: emptyList()
override fun processChangesAfterBuild(compilationMode: CompilationMode, currentBuildInfo: BuildInfo, dirtyData: DirtyData) {
super.processChangesAfterBuild(compilationMode, currentBuildInfo, dirtyData)
if (buildHistoryFile == null) return
override fun processChangesAfterBuild(
compilationMode: CompilationMode,
currentBuildInfo: BuildInfo,
dirtyData: DirtyData
) {
val prevDiffs = BuildDiffsStorage.readFromFile(buildHistoryFile, reporter)?.buildDiffs ?: emptyList()
val newDiff = if (compilationMode is CompilationMode.Incremental) {
BuildDifference(currentBuildInfo.startTS, true, dirtyData)
}
else {
} else {
val emptyDirtyData = DirtyData()
BuildDifference(currentBuildInfo.startTS, false, emptyDirtyData)
}
@@ -1,24 +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 java.io.File
interface ArtifactChangesProvider {
fun getChanges(artifact: File, sinceTS: Long): Iterable<DirtyData>?
}
@@ -1,24 +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
interface ChangesRegistry {
fun registerChanges(timestamp: Long, dirtyData: DirtyData)
fun unknownChanges(timestamp: Long)
}
@@ -0,0 +1,86 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.incremental.multiproject
import org.jetbrains.kotlin.daemon.common.IncrementalModuleInfo
import org.jetbrains.kotlin.incremental.util.Either
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import java.util.zip.ZipFile
interface ModulesApiHistory {
fun historyFilesForChangedFiles(changedFiles: Set<File>): Either<Set<File>>
}
object EmptyModulesApiHistory : ModulesApiHistory {
override fun historyFilesForChangedFiles(changedFiles: Set<File>): Either<Set<File>> =
Either.Error("Multi-module IC is not configured")
}
class ModulesApiHistoryImpl(
private val modulesInfo: IncrementalModuleInfo
) : ModulesApiHistory {
private val projectRootPath = Paths.get(modulesInfo.projectRoot.absolutePath)
private val dirToHistoryFileCache = HashMap<File, File?>()
override fun historyFilesForChangedFiles(changedFiles: Set<File>): Either<Set<File>> {
val result = HashSet<File>()
val jarFiles = ArrayList<File>()
val classFiles = ArrayList<File>()
for (file in changedFiles) {
val extension = file.extension
when {
extension.equals("class", ignoreCase = true) -> {
classFiles.add(file)
}
extension.equals("jar", ignoreCase = true) -> {
jarFiles.add(file)
}
}
}
for (jar in jarFiles) {
val historyEither = getBuildHistoryForJar(jar)
when (historyEither) {
is Either.Success<File> -> result.add(historyEither.value)
is Either.Error -> return historyEither
}
}
val classFileDirs = classFiles.groupBy { it.parentFile }
for ((dir, files) in classFileDirs) {
val buildHistory = getBuildHistoryForDir(dir)
?: return Either.Error("Could not get build history for class files: ${files.joinToString()}")
result.add(buildHistory)
}
return Either.Success(result)
}
private fun getBuildHistoryForDir(file: File): File? =
dirToHistoryFileCache.getOrPut(file) {
val module = modulesInfo.dirToModule[file]
val parent = file.parentFile
when {
module != null ->
module.buildHistoryFile
parent != null && projectRootPath.isParentOf(parent) ->
getBuildHistoryForDir(parent)
else ->
null
}
}
private fun getBuildHistoryForJar(jar: File): Either<File> =
Either.Error("Cannot get changes for jar $jar")
private fun Path.isParentOf(path: Path) = path.startsWith(this)
private fun Path.isParentOf(file: File) = this.isParentOf(Paths.get(file.absolutePath))
}
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.incremental.util
sealed class Either<out T> {
class Success<T>(val value: T) : Either<T>()
class Error(val reason: String) : Either<Nothing>()
}
@@ -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) }
}