Make tasks outputs snapshot into zip file.
Kotlin's compilation task outputs usually contains a lot of small files, which is not so performant to copy as-is into new location. In this change coping was replaced by using zip archive with no compression. From my test I see around 2x performance improvement on doing task outputs snapshot. Further optimizations will be done via separate issue as they are non-trivial and requires some changes on the compiler side. ^KT-49782 Fixed
This commit is contained in:
+2
-1
@@ -63,7 +63,8 @@ internal class GradleCompilerRunnerWithWorkers(
|
|||||||
fileSystemOperations,
|
fileSystemOperations,
|
||||||
parameters.buildDir,
|
parameters.buildDir,
|
||||||
parameters.snapshotsDir,
|
parameters.snapshotsDir,
|
||||||
parameters.taskOutputs
|
parameters.taskOutputs,
|
||||||
|
logger
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|||||||
+2
-1
@@ -353,7 +353,8 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> : AbstractKotl
|
|||||||
fileSystemOperations,
|
fileSystemOperations,
|
||||||
layout.buildDirectory,
|
layout.buildDirectory,
|
||||||
layout.buildDirectory.dir("snapshot/kotlin/$name"),
|
layout.buildDirectory.dir("snapshot/kotlin/$name"),
|
||||||
allOutputFiles()
|
allOutputFiles(),
|
||||||
|
logger
|
||||||
).also {
|
).also {
|
||||||
it.createSnapshot()
|
it.createSnapshot()
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-12
@@ -5,18 +5,23 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.gradle.tasks
|
package org.jetbrains.kotlin.gradle.tasks
|
||||||
|
|
||||||
import org.gradle.api.file.Directory
|
import org.gradle.api.file.*
|
||||||
import org.gradle.api.file.DirectoryProperty
|
import org.gradle.api.logging.Logger
|
||||||
import org.gradle.api.file.FileCollection
|
|
||||||
import org.gradle.api.file.FileSystemOperations
|
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
|
import java.nio.file.FileSystems
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.util.zip.*
|
||||||
|
|
||||||
internal class TaskOutputsBackup(
|
internal class TaskOutputsBackup(
|
||||||
val fileSystemOperations: FileSystemOperations,
|
val fileSystemOperations: FileSystemOperations,
|
||||||
val buildDirectory: DirectoryProperty,
|
val buildDirectory: DirectoryProperty,
|
||||||
val snapshotsDir: Provider<Directory>,
|
val snapshotsDir: Provider<Directory>,
|
||||||
val outputs: FileCollection
|
val outputs: FileCollection,
|
||||||
|
val logger: Logger
|
||||||
) {
|
) {
|
||||||
fun createSnapshot() {
|
fun createSnapshot() {
|
||||||
// Kotlin JS compilation task declares one file from 'destinationDirectory' output as task `@OutputFile'
|
// Kotlin JS compilation task declares one file from 'destinationDirectory' output as task `@OutputFile'
|
||||||
@@ -24,10 +29,16 @@ internal class TaskOutputsBackup(
|
|||||||
outputs.files.toSortedSet().forEachIndexed { index, outputPath ->
|
outputs.files.toSortedSet().forEachIndexed { index, outputPath ->
|
||||||
val pathInSnapshot = "$index${File.separator}${outputPath.pathRelativeToBuildDirectory}"
|
val pathInSnapshot = "$index${File.separator}${outputPath.pathRelativeToBuildDirectory}"
|
||||||
if (outputPath.isDirectory) {
|
if (outputPath.isDirectory) {
|
||||||
fileSystemOperations.sync { spec ->
|
snapshotsDir
|
||||||
spec.from(outputPath)
|
.map { it.file(pathInSnapshot) }
|
||||||
spec.into(snapshotsDir.map { it.dir(pathInSnapshot) })
|
.get()
|
||||||
}
|
.asFile
|
||||||
|
.run {
|
||||||
|
compressDirectoryToZip(
|
||||||
|
File(this, DIRECTORY_SNAPSHOT_ARCHIVE_FILE),
|
||||||
|
outputPath
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fileSystemOperations.copy { spec ->
|
fileSystemOperations.copy { spec ->
|
||||||
spec.from(outputPath)
|
spec.from(outputPath)
|
||||||
@@ -46,10 +57,17 @@ internal class TaskOutputsBackup(
|
|||||||
val pathInSnapshot = "$index${File.separator}${outputPath.pathRelativeToBuildDirectory}"
|
val pathInSnapshot = "$index${File.separator}${outputPath.pathRelativeToBuildDirectory}"
|
||||||
val fileInSnapshot = snapshotsDir.get().file(pathInSnapshot).asFile
|
val fileInSnapshot = snapshotsDir.get().file(pathInSnapshot).asFile
|
||||||
if (fileInSnapshot.isDirectory) {
|
if (fileInSnapshot.isDirectory) {
|
||||||
fileSystemOperations.sync { spec ->
|
val snapshotArchive = File(fileInSnapshot, DIRECTORY_SNAPSHOT_ARCHIVE_FILE)
|
||||||
spec.from(snapshotsDir.map { it.dir(pathInSnapshot) })
|
if (!snapshotArchive.exists()) {
|
||||||
spec.into(outputPath)
|
logger.warn(
|
||||||
|
"""
|
||||||
|
|Failed to restore task outputs as snapshot file ${snapshotArchive.absolutePath} does not exist!
|
||||||
|
|On recompilation full rebuild will be performed.
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
uncompressZipIntoDirectory(snapshotArchive, outputPath)
|
||||||
} else {
|
} else {
|
||||||
fileSystemOperations.copy { spec ->
|
fileSystemOperations.copy { spec ->
|
||||||
spec.from(snapshotsDir.map { it.file(pathInSnapshot).asFile.parentFile })
|
spec.from(snapshotsDir.map { it.file(pathInSnapshot).asFile.parentFile })
|
||||||
@@ -63,9 +81,65 @@ internal class TaskOutputsBackup(
|
|||||||
fileSystemOperations.delete { it.delete(snapshotsDir) }
|
fileSystemOperations.delete { it.delete(snapshotsDir) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kotlin's compilation in a "fat" project may contain a lot of small files that is slow to copy
|
||||||
|
* So we speeding it up by archiving them into single zip file without compression. Such approach reduces snapshotting
|
||||||
|
* time up to half ot the time needed to copy similar files.
|
||||||
|
*/
|
||||||
|
private fun compressDirectoryToZip(
|
||||||
|
snapshotFile: File,
|
||||||
|
outputPath: File
|
||||||
|
) {
|
||||||
|
snapshotFile.parentFile.mkdirs()
|
||||||
|
snapshotFile.createNewFile()
|
||||||
|
|
||||||
|
ZipOutputStream(snapshotFile.outputStream().buffered()).use { zip ->
|
||||||
|
zip.setLevel(Deflater.NO_COMPRESSION)
|
||||||
|
outputPath
|
||||||
|
.walkTopDown()
|
||||||
|
.filter { !it.isDirectory }
|
||||||
|
.forEach { file ->
|
||||||
|
val entry = ZipEntry(file.relativeTo(outputPath).invariantSeparatorsPath)
|
||||||
|
zip.putNextEntry(entry)
|
||||||
|
file.inputStream().buffered().use { it.copyTo(zip) }
|
||||||
|
zip.closeEntry()
|
||||||
|
}
|
||||||
|
zip.flush()
|
||||||
|
zip.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uncompressZipIntoDirectory(
|
||||||
|
snapshotFile: File,
|
||||||
|
outputDirectory: File
|
||||||
|
) {
|
||||||
|
val outputPath = outputDirectory.toPath()
|
||||||
|
val snapshotUri = URI.create("jar:${snapshotFile.toURI()}")
|
||||||
|
FileSystems.newFileSystem(snapshotUri, emptyMap<String, Any>()).use { zipFs ->
|
||||||
|
zipFs.rootDirectories.forEach { rootDir ->
|
||||||
|
Files.walk(rootDir).use { paths ->
|
||||||
|
paths.forEach {
|
||||||
|
if (Files.isDirectory(it)) {
|
||||||
|
Files.createDirectories(outputPath.resolve(it.normalizedToBeRelative))
|
||||||
|
} else if (Files.isRegularFile(it)) {
|
||||||
|
Files.copy(it, outputPath.resolve(it.normalizedToBeRelative), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Path.normalizedToBeRelative: String
|
||||||
|
get() = if (toString() == "/") "." else toString().removePrefix("/")
|
||||||
|
|
||||||
private val File.pathRelativeToBuildDirectory: String
|
private val File.pathRelativeToBuildDirectory: String
|
||||||
get() {
|
get() {
|
||||||
val buildDir = buildDirectory.get().asFile
|
val buildDir = buildDirectory.get().asFile
|
||||||
return relativeTo(buildDir).path
|
return relativeTo(buildDir).path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DIRECTORY_SNAPSHOT_ARCHIVE_FILE = "snapshot.zip"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user