[Gradle, JS] Implement own IncrementalSyncTask

This commit is contained in:
Ilya Goncharov
2023-03-14 11:16:20 +00:00
committed by Space Team
parent 7d2ba28911
commit 63e27a9713
11 changed files with 145 additions and 113 deletions
@@ -1484,6 +1484,16 @@ public abstract interface class org/jetbrains/kotlin/gradle/tasks/CompileUsingKo
public abstract fun getUseDaemonFallbackStrategy ()Lorg/gradle/api/provider/Property;
}
public abstract interface class org/jetbrains/kotlin/gradle/tasks/IncrementalSyncTask : org/gradle/api/Task {
public abstract fun getDestinationDir ()Ljava/io/File;
public abstract fun getDestinationDirectory ()Lorg/gradle/api/provider/Property;
public abstract fun getFrom ()Lorg/gradle/api/file/ConfigurableFileCollection;
}
public final class org/jetbrains/kotlin/gradle/tasks/IncrementalSyncTask$DefaultImpls {
public static fun getDestinationDir (Lorg/jetbrains/kotlin/gradle/tasks/IncrementalSyncTask;)Ljava/io/File;
}
public abstract interface class org/jetbrains/kotlin/gradle/tasks/Kapt : org/jetbrains/kotlin/gradle/tasks/BaseKapt {
public abstract fun getAddJdkClassesToClasspath ()Lorg/gradle/api/provider/Property;
public abstract fun getKaptJars ()Lorg/gradle/api/file/ConfigurableFileCollection;
@@ -0,0 +1,24 @@
package org.jetbrains.kotlin.gradle.tasks
import org.gradle.api.Task
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings
import java.io.File
interface IncrementalSyncTask : Task {
@get:InputFiles
@get:NormalizeLineEndings
@get:IgnoreEmptyDirectories
@get:SkipWhenEmpty
val from: ConfigurableFileCollection
@get:OutputDirectory
val destinationDirectory: Property<File>
@get:Internal
@Deprecated("Use destinationDirProperty with Provider API", ReplaceWith("destinationDirProperty.get()"))
val destinationDir: File
get() = destinationDirectory.get()
}
@@ -209,7 +209,6 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
assertTasksExecuted(":app:developmentExecutableCompileSync")
assertFileInProjectNotExists("build/js/packages/kotlin-js-browser-app/kotlin/foo/foo.txt")
assertFileInProjectNotExists("build/js/packages/kotlin-js-browser-app/sync-hashes/foo/foo.txt.hash")
}
}
}
@@ -0,0 +1,95 @@
package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.DefaultTask
import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.*
import org.gradle.work.ChangeType
import org.gradle.work.DisableCachingByDefault
import org.gradle.work.InputChanges
import org.jetbrains.kotlin.gradle.targets.js.internal.RewriteSourceMapFilterReader
import org.jetbrains.kotlin.gradle.tasks.IncrementalSyncTask
import java.io.File
import javax.inject.Inject
@DisableCachingByDefault
abstract class DefaultIncrementalSyncTask : DefaultTask(), IncrementalSyncTask {
@get:Inject
abstract val fs: FileSystemOperations
@get:Inject
abstract val objectFactory: ObjectFactory
@TaskAction
fun doCopy(inputChanges: InputChanges) {
val destinationDir = destinationDirectory.get()
val commonAction: CopySpec.() -> Unit = {
into(destinationDir)
// Rewrite relative paths in sourcemaps in the target directory
eachFile {
if (it.name.endsWith(".js.map")) {
it.filter(
mapOf(
RewriteSourceMapFilterReader::srcSourceRoot.name to it.file.parentFile,
RewriteSourceMapFilterReader::targetSourceRoot.name to destinationDir
),
RewriteSourceMapFilterReader::class.java
)
}
}
}
val work = if (!inputChanges.isIncremental) {
fs.copy {
it.from(from)
it.commonAction()
}.didWork
} else {
val changedFiles = inputChanges.getFileChanges(from)
val modified = changedFiles
.filter {
it.changeType == ChangeType.ADDED || it.changeType == ChangeType.MODIFIED
}
.map { it.file }
.toSet()
val forCopy = from.asFileTree
.matching { patternFilterable ->
patternFilterable.exclude {
it.file.isFile && it.file !in modified
}
}
val nonRemovingFiles = mutableSetOf<File>()
from.asFileTree
.visit {
nonRemovingFiles.add(it.relativePath.getFile(destinationDir))
}
val removingFiles = objectFactory.fileTree()
.from(destinationDir)
.also { fileTree ->
fileTree.exclude {
it.file.isFile && it.file in nonRemovingFiles
}
}
val deleteWork = fs.delete {
it.delete(removingFiles)
}
val copyWork = fs.copy {
it.from(forCopy)
it.commonAction()
}
deleteWork.didWork || copyWork.didWork
}
didWork = work
}
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.Project
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
@@ -14,9 +13,9 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.dsl.Distribution
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsBinaryContainer.Companion.generateBinaryName
import org.jetbrains.kotlin.gradle.targets.js.subtargets.DefaultDistribution
import org.jetbrains.kotlin.gradle.targets.js.subtargets.createDefaultDistribution
import org.jetbrains.kotlin.gradle.targets.js.typescript.TypeScriptValidationTask
import org.jetbrains.kotlin.gradle.tasks.IncrementalSyncTask
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -56,9 +55,9 @@ sealed class JsIrBinary(
val validateGeneratedTsTaskName: String = validateTypeScriptTaskName()
val linkSyncTask: TaskProvider<Copy>
val linkSyncTask: TaskProvider<IncrementalSyncTask>
get() = target.project.tasks
.withType<Copy>()
.withType<IncrementalSyncTask>()
.named(linkSyncTaskName)
private fun linkSyncTaskName(): String =
@@ -164,7 +164,7 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
task.commonConfigure(
compilation = compilation,
mode = mode,
inputFilesDirectory = binary.linkSyncTask.map { it.destinationDir },
inputFilesDirectory = binary.linkSyncTask.flatMap { it.destinationDirectory },
entryModuleName = binary.linkTask.flatMap { it.compilerOptions.moduleName },
configurationActions = runTaskConfigurations,
nodeJs = nodeJs,
@@ -229,7 +229,7 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
task.commonConfigure(
compilation = compilation,
mode = mode,
inputFilesDirectory = binary.linkSyncTask.map { it.destinationDir },
inputFilesDirectory = binary.linkSyncTask.flatMap { it.destinationDirectory },
entryModuleName = binary.linkTask.flatMap { it.compilerOptions.moduleName },
configurationActions = webpackTaskConfigurations,
nodeJs = nodeJs,
@@ -37,8 +37,8 @@ abstract class KotlinD8Ir @Inject constructor(target: KotlinJsIrTarget) :
inputFileProperty.fileProvider(
binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
linkTask.outputFileProperty.map { file ->
linkSyncTask.destinationDirectory.get().resolve(file.name)
}
}
}
@@ -125,8 +125,8 @@ abstract class KotlinJsIrSubTarget(
testJs.inputFileProperty.fileProvider(
binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
linkTask.outputFileProperty.map { file ->
linkSyncTask.destinationDirectory.get().resolve(file.name)
}
}
}
@@ -131,25 +131,22 @@ constructor(
}
}
private fun registerCompileSync(binary: JsIrBinary): TaskProvider<SyncExecutableTask> {
private fun registerCompileSync(binary: JsIrBinary): TaskProvider<DefaultIncrementalSyncTask> {
val compilation = binary.compilation
val npmProject = compilation.npmProject
return project.registerTask<SyncExecutableTask>(
return project.registerTask<DefaultIncrementalSyncTask>(
binary.linkSyncTaskName
) { task ->
task.from(
task.from.from(
binary.linkTask.flatMap { linkTask ->
linkTask.destinationDirectory.map { it.asFile }
}
)
task.from(project.tasks.named(compilation.processResourcesTaskName))
task.from.from(project.tasks.named(compilation.processResourcesTaskName))
val hashDir = npmProject.dir.resolve("sync-hashes")
task.hashDir.set(hashDir)
task.into(npmProject.dist)
task.destinationDirectory.set(npmProject.dist)
}
}
@@ -40,8 +40,8 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) :
inputFileProperty.fileProvider(
binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
linkTask.outputFileProperty.map { file ->
linkSyncTask.destinationDirectory.get().resolve(file.name)
}
}
}
@@ -1,92 +0,0 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.gradle.targets.js.ir
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.OutputDirectory
import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.gradle.targets.js.internal.RewriteSourceMapFilterReader
import org.jetbrains.kotlin.gradle.targets.js.toHex
import java.io.File
import javax.inject.Inject
abstract class SyncExecutableTask : Copy() {
@get:Inject
abstract val fileHasher: FileHasher
@get:Inject
abstract val objects: ObjectFactory
@get:OutputDirectory
abstract val hashDir: Property<File>
override fun copy() {
val actualFiles = mutableSetOf<File>()
val hashDirFile = hashDir.get()
eachFile {
actualFiles.add(it.relativeSourcePath.getFile(destinationDir))
val hashFile = hashDirFile.resolve(it.relativeSourcePath.pathString + ".$HASH_EXTENSION")
val currentHash = fileHasher.hash(it.file)
val currentHashHex = currentHash.toByteArray().toHex()
if (hashFile.exists()) {
val previousHash = hashFile.readText()
if (previousHash == currentHashHex) {
it.exclude()
return@eachFile
}
} else {
hashFile.parentFile.mkdirs()
}
hashFile.writeText(currentHashHex)
}
// Rewrite relative paths in sourcemaps in the target directory
eachFile {
if (it.name.endsWith(".js.map")) {
it.filter(
mapOf(
RewriteSourceMapFilterReader::srcSourceRoot.name to it.file.parentFile,
RewriteSourceMapFilterReader::targetSourceRoot.name to destinationDir
),
RewriteSourceMapFilterReader::class.java
)
}
}
super.copy()
objects.fileTree()
.from(hashDirFile)
.files
.forEach {
if (destinationDir.resolve(it.relativeTo(hashDirFile).path.removeSuffix(".$HASH_EXTENSION")) !in actualFiles) {
it.delete()
}
}
objects.fileTree()
.from(destinationDir)
.files
.forEach {
if (it !in actualFiles) {
it.delete()
}
}
}
companion object {
const val HASH_EXTENSION = "hash"
}
}