[Gradle, JS] Reporting in case when yarn.lock was changed during build

----
[Gradle, JS] Change naming

[Gradle, JS] storeYarnLock not finalizing kotlinNpmInstall

[Gradle, JS] Add double clean to test for check correctness without input yarn.lock

[Gradle, JS] Consider reportNewYarnLock in yarn.lock service

[Gradle, JS] actualizeYarnLock for explicit actualizing yarn.lock

[Gradle, JS] Not update yarn.lock automatically

[Gradle, JS] Add test on yarn.lock reporting

[Gradle, JS] Error report by default

[Gradle, JS] Report with failing after build, not failing task

[Gradle, JS] Report about yarn.lock mismatch reporting

Merge-request: KT-MR-6614
Merged-by: Ilya Goncharov <Ilya.Goncharov@jetbrains.com>

^KT-52950 fixed
This commit is contained in:
Ilya Goncharov
2022-07-21 09:16:11 +00:00
committed by Space
parent 151448274b
commit 3e0b50c924
20 changed files with 474 additions and 18 deletions
@@ -17,6 +17,9 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectModules
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.npm.fromSrcPackageJson
import org.jetbrains.kotlin.gradle.targets.js.yarn.YARN_LOCK_MISMATCH_MESSAGE
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.STORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.UPGRADE_YARN_LOCK
import org.jetbrains.kotlin.gradle.tasks.USING_JS_INCREMENTAL_COMPILATION_MESSAGE
import org.jetbrains.kotlin.gradle.tasks.USING_JS_IR_BACKEND_MESSAGE
import org.jetbrains.kotlin.gradle.testbase.*
@@ -25,7 +28,6 @@ import org.jetbrains.kotlin.gradle.util.normalizePath
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.DisabledIf
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.util.zip.ZipFile
@@ -33,7 +35,6 @@ import kotlin.io.path.*
import kotlin.streams.toList
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@JsGradlePluginTests
@@ -867,6 +868,10 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
}
}
build(UPGRADE_YARN_LOCK) {
assertTasksExecuted(":$UPGRADE_YARN_LOCK")
}
build("jsJar") {
val archive = Files.list(projectPath.resolve("build").resolve("libs")).use { files ->
files
@@ -1178,6 +1183,202 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
}
}
@DisplayName("Failing with yarn.lock update")
@GradleTest
fun testFailingWithYarnLockUpdate(gradleVersion: GradleVersion) {
project("kotlin-js-yarn-lock-project", gradleVersion) {
build("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
}
projectPath.resolve("kotlin-js-store").deleteRecursively()
buildGradleKts.modify {
it + "\n" +
"""
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().reportNewYarnLock = true
}
""".trimIndent()
}
buildAndFail("compileKotlinJs") {
assertTasksFailed(":$STORE_YARN_LOCK_NAME")
}
buildGradleKts.modify {
it + "\n" +
"""
dependencies {
implementation(npm("decamelize", "6.0.0"))
}
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.FAIL
}
""".trimIndent()
}
buildAndFail("compileKotlinJs") {
assertTasksFailed(":$STORE_YARN_LOCK_NAME")
}
// yarn.lock was not updated
buildAndFail("compileKotlinJs") {
assertTasksFailed(":$STORE_YARN_LOCK_NAME")
}
build(UPGRADE_YARN_LOCK) {
assertTasksExecuted(":$UPGRADE_YARN_LOCK")
}
buildGradleKts.modify {
val replaced = it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "")
replaced + "\n" +
"""
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.WARNING
}
""".trimIndent()
}
build("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
assertOutputContains(YARN_LOCK_MISMATCH_MESSAGE)
}
build(UPGRADE_YARN_LOCK) {
assertTasksExecuted(":$UPGRADE_YARN_LOCK")
}
buildGradleKts.modify {
it + "\n" +
"""
dependencies {
implementation(npm("decamelize", "6.0.0"))
}
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.NONE
}
""".trimIndent()
}
build("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
assertOutputDoesNotContain(YARN_LOCK_MISMATCH_MESSAGE)
}
build(UPGRADE_YARN_LOCK) {
assertTasksExecuted(":$UPGRADE_YARN_LOCK")
}
buildGradleKts.modify {
val replaced = it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "")
replaced + "\n" +
"""
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.FAIL_AFTER_BUILD
}
""".trimIndent()
}
buildAndFail("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
assertOutputContains(YARN_LOCK_MISMATCH_MESSAGE)
}
buildAndFail("compileKotlinJs") {
assertTasksUpToDate(":$STORE_YARN_LOCK_NAME")
assertOutputContains(YARN_LOCK_MISMATCH_MESSAGE)
}
projectPath.resolve("kotlin-js-store").deleteRecursively()
buildGradleKts.modify {
it + "\n" +
"""
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.NONE
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().reportNewYarnLock =
true
}
""".trimIndent()
}
build("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
assertOutputDoesNotContain(YARN_LOCK_MISMATCH_MESSAGE)
}
buildGradleKts.modify {
it + "\n" +
"""
dependencies {
implementation(npm("decamelize", "6.0.0"))
}
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.FAIL
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockAutoReplace =
true
}
""".trimIndent()
}
buildAndFail("compileKotlinJs") {
assertTasksFailed(":$STORE_YARN_LOCK_NAME")
}
//yarn.lock was updated
build("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
}
buildGradleKts.modify {
val replaced = it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "")
replaced + "\n" +
"""
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().yarnLockMismatchReport =
org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.FAIL_AFTER_BUILD
}
""".trimIndent()
}
buildAndFail("compileKotlinJs") {
assertTasksExecuted(":$STORE_YARN_LOCK_NAME")
assertOutputContains(YARN_LOCK_MISMATCH_MESSAGE)
}
//yarn.lock was updated
build("compileKotlinJs") {
assertTasksUpToDate(":$STORE_YARN_LOCK_NAME")
}
// check if everything ok without build/js/yarn.lock
build("clean") {
assertTasksExecuted(":clean")
}
build("clean") {
assertTasksUpToDate(":clean")
}
}
}
@DisplayName("webpack configuration is valid")
@GradleTest
fun testWebpackConfig(gradleVersion: GradleVersion) {
@@ -0,0 +1,22 @@
plugins {
id("org.jetbrains.kotlin.js")
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-js")
testImplementation("org.jetbrains.kotlin:kotlin-test-js")
}
repositories {
mavenLocal()
mavenCentral()
}
kotlin {
js {
useCommonJs()
binaries.executable()
nodejs {
}
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2019 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 com.example
fun best(): Int {
return 42
}
fun simpleBest(): Int {
return 73
}
@@ -52,7 +52,11 @@ internal class DukatCompilationResolverPlugin(
it.group = DUKAT_TASK_GROUP
it.description = "Integrated generation Kotlin/JS external declarations for .d.ts files in $compilation"
it.externalsOutputFormat = externalsOutputFormat
it.dependsOn(nodeJs_.npmInstallTaskProvider, npmProject.packageJsonTask)
it.dependsOn(
nodeJs_.npmInstallTaskProvider,
nodeJs_.storeYarnLockTaskProvider,
npmProject.packageJsonTask
)
}
}
@@ -82,7 +86,11 @@ internal class DukatCompilationResolverPlugin(
) {
it.group = DUKAT_TASK_GROUP
it.description = "Generate Kotlin/JS external declarations for .d.ts files of all NPM dependencies in ${compilation}"
it.dependsOn(nodeJs_.npmInstallTaskProvider, npmProject.packageJsonTask)
it.dependsOn(
nodeJs_.npmInstallTaskProvider,
nodeJs_.storeYarnLockTaskProvider,
npmProject.packageJsonTask
)
}
}
@@ -47,7 +47,11 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
get() = "Run all ${target.name} tests inside browser using karma and webpack"
override fun configureTestDependencies(test: KotlinJsTest) {
test.dependsOn(nodeJs.npmInstallTaskProvider, nodeJs.nodeJsSetupTaskProvider)
test.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
nodeJs.nodeJsSetupTaskProvider
)
}
override fun configureDefaultTestFramework(test: KotlinJsTest) {
@@ -256,6 +260,7 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
) {
dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
target.project.tasks.named(compilation.processResourcesTaskName)
)
@@ -47,7 +47,11 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) :
}
override fun configureTestDependencies(test: KotlinJsTest) {
test.dependsOn(nodeJs.npmInstallTaskProvider, nodeJs.nodeJsSetupTaskProvider)
test.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
nodeJs.nodeJsSetupTaskProvider
)
}
override fun configureDefaultTestFramework(test: KotlinJsTest) {
@@ -100,7 +100,10 @@ constructor(
it.nodeJs = nodeJs
it.executable = nodeJs.requireConfigured().nodeExecutable
it.workingDir = npmProject.dir
it.dependsOn(nodeJs.npmInstallTaskProvider)
it.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
)
it.dependsOn(compilation.compileKotlinTaskProvider)
if (compilation.platformType == KotlinPlatformType.wasm) {
it.nodeArgs.addWasmExperimentalArguments()
@@ -19,6 +19,9 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmCachesSetup
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.RESTORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.STORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File
import java.io.Serializable
@@ -73,6 +76,14 @@ open class NodeJsRootExtension(@Transient val rootProject: Project) : Configurat
val npmCachesSetupTaskProvider: TaskProvider<out KotlinNpmCachesSetup>?
get() = rootProject?.tasks?.withType(KotlinNpmCachesSetup::class.java)?.named(KotlinNpmCachesSetup.NAME)
@Suppress("UNNECESSARY_SAFE_CALL", "SAFE_CALL_WILL_CHANGE_NULLABILITY") // TODO: investigate this warning; fixing it breaks integration tests.
val storeYarnLockTaskProvider: TaskProvider<out YarnLockCopyTask>?
get() = rootProject?.tasks?.withType(YarnLockCopyTask::class.java)?.named(STORE_YARN_LOCK_NAME)
@Suppress("UNNECESSARY_SAFE_CALL", "SAFE_CALL_WILL_CHANGE_NULLABILITY") // TODO: investigate this warning; fixing it breaks integration tests.
val restoreYarnLockTaskProvider: TaskProvider<out YarnLockCopyTask>?
get() = rootProject?.tasks?.withType(YarnLockCopyTask::class.java)?.named(RESTORE_YARN_LOCK_NAME)
val rootPackageDir: File by lazy {
rootProject.buildDir.resolve("js")
}
@@ -80,7 +80,10 @@ internal class KotlinCompilationNpmResolver(
npmProject.publicPackageJsonTaskName,
listOf(compilation)
) {
it.dependsOn(nodeJs_.npmInstallTaskProvider)
it.dependsOn(
nodeJs_.npmInstallTaskProvider,
nodeJs_.storeYarnLockTaskProvider,
)
it.dependsOn(packageJsonTaskHolder)
}.also { packageJsonTask ->
if (compilation.isMain()) {
@@ -68,7 +68,10 @@ internal class KotlinProjectNpmResolver(
nodeJs.taskRequirements.addTaskRequirements(task)
}
task.dependsOn(packageJsonTaskHolder)
task.dependsOn(nodeJs.npmInstallTaskProvider)
task.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
)
}
}
}
@@ -133,6 +133,7 @@ abstract class KotlinPackageJsonTask : DefaultTask() {
nodeJs.rootPackageJsonTaskProvider!!.configure { it.mustRunAfter(packageJsonTask) }
compilation.compileKotlinTaskProvider.dependsOn(npmInstallTask!!)
compilation.compileKotlinTaskProvider.dependsOn(nodeJs.storeYarnLockTaskProvider!!)
compilation.compileKotlinTaskProvider.dependsOn(packageJsonTask)
return packageJsonTask
@@ -278,6 +278,7 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
) {
dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
target.project.tasks.named(compilation.processResourcesTaskName)
)
@@ -106,7 +106,12 @@ abstract class KotlinJsSubTarget(
val compileOutputFile = compileTask.flatMap { it.outputFileProperty }
testJs.inputFileProperty.fileProvider(compileOutputFile)
testJs.dependsOn(nodeJs.npmInstallTaskProvider, compileTask, nodeJs.nodeJsSetupTaskProvider)
testJs.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
compileTask,
nodeJs.nodeJsSetupTaskProvider
)
testJs.onlyIf {
compileOutputFile.get().exists()
@@ -17,4 +17,7 @@ data class YarnEnv(
val ivyDependency: String,
val standalone: Boolean,
val ignoreScripts: Boolean,
val yarnLockMismatchReport: YarnLockMismatchReport,
val reportNewYarnLock: Boolean,
val yarnLockAutoReplace: Boolean,
) : Serializable
@@ -6,15 +6,18 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.utils.contentEquals
import java.io.File
import javax.inject.Inject
abstract class YarnLockCopyTask : DefaultTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val inputFile: RegularFileProperty
@@ -35,11 +38,66 @@ abstract class YarnLockCopyTask : DefaultTask() {
abstract val fs: FileSystemOperations
@TaskAction
fun copy() {
open fun copy() {
fs.copy { copy ->
copy.from(inputFile)
copy.rename { fileName.get() }
copy.into(outputDirectory)
}
}
companion object {
val STORE_YARN_LOCK_NAME = "kotlinStoreYarnLock"
val RESTORE_YARN_LOCK_NAME = "kotlinRestoreYarnLock"
val UPGRADE_YARN_LOCK = "kotlinUpgradeYarnLock"
}
}
abstract class YarnLockStoreTask : YarnLockCopyTask() {
@get:Internal
abstract val yarnLockMismatchReportService: Property<YarnLockMismatchReportService>
@Input
lateinit var yarnLockMismatchReport: Provider<YarnLockMismatchReport>
@Input
lateinit var reportNewYarnLock: Provider<Boolean>
@Input
lateinit var yarnLockAutoReplace: Provider<Boolean>
override fun copy() {
val outputFile = outputDirectory.get().asFile.resolve(fileName.get())
val shouldReportMismatch = if (!outputFile.exists()) {
reportNewYarnLock.get()
} else {
yarnLockMismatchReport.get() != YarnLockMismatchReport.NONE && !contentEquals(inputFile.get().asFile, outputFile)
}
if (!outputFile.exists() || yarnLockAutoReplace.get()) {
super.copy()
}
if (shouldReportMismatch) {
when (yarnLockMismatchReport.get()) {
YarnLockMismatchReport.NONE -> {}
YarnLockMismatchReport.WARNING -> {
logger.warn(YARN_LOCK_MISMATCH_MESSAGE)
}
YarnLockMismatchReport.FAIL -> throw GradleException(YARN_LOCK_MISMATCH_MESSAGE)
YarnLockMismatchReport.FAIL_AFTER_BUILD -> {
yarnLockMismatchReportService.get().failOnClose()
}
else -> error("Unknown yarn.lock mismatch report kind")
}
}
}
}
enum class YarnLockMismatchReport {
NONE,
WARNING,
FAIL,
FAIL_AFTER_BUILD,
}
@@ -0,0 +1,53 @@
/*
* Copyright 2010-2022 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.yarn
import org.gradle.api.GradleException
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.tooling.events.FinishEvent
import org.gradle.tooling.events.OperationCompletionListener
import org.jetbrains.kotlin.gradle.utils.contentEquals
abstract class YarnLockMismatchReportService : BuildService<YarnLockMismatchReportService.Params>, AutoCloseable,
OperationCompletionListener {
@Volatile
private var shouldFailOnClose: Boolean = false
// Some parameters for the web server
internal interface Params : BuildServiceParameters {
val inputFile: RegularFileProperty
val outputFile: RegularFileProperty
val shouldFailOnClose: Property<Boolean>
val reportNewYarnLock: Property<Boolean>
}
fun failOnClose() {
shouldFailOnClose = true
}
override fun onFinish(event: FinishEvent?) {
// noop
}
override fun close() {
if (!parameters.inputFile.get().asFile.exists()) {
return
}
if (parameters.reportNewYarnLock.get() && !parameters.outputFile.get().asFile.exists()) {
throw GradleException(YARN_LOCK_MISMATCH_MESSAGE)
}
if (shouldFailOnClose || parameters.shouldFailOnClose.get() && !contentEquals(parameters.inputFile.get().asFile, parameters.outputFile.get().asFile)) {
throw GradleException(YARN_LOCK_MISMATCH_MESSAGE)
}
}
}
val YARN_LOCK_MISMATCH_MESSAGE = "yarn.lock was changed. Run `kotlinActualizeYarnLock` task to actualize yarn.lock file"
@@ -9,12 +9,16 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.BuildEventsListenerRegistryHolder
import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.RESTORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.STORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.UPGRADE_YARN_LOCK
import org.jetbrains.kotlin.gradle.tasks.CleanDataTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
@@ -69,14 +73,42 @@ open class YarnPlugin : Plugin<Project> {
it.dependsOn(packageJsonUmbrella)
}
val storeYarnLock = tasks.register("kotlinStoreYarnLock", YarnLockCopyTask::class.java) {
it.dependsOn(kotlinNpmInstall)
it.inputFile.set(nodeJs.rootPackageDir.resolve("yarn.lock"))
it.outputDirectory.set(yarnRootExtension.lockFileDirectory)
it.fileName.set(yarnRootExtension.lockFileName)
val service = project.gradle.sharedServices
.registerIfAbsent("kotlin-store-yarn-lock-mismatch-reporter", YarnLockMismatchReportService::class.java) {
it.parameters.inputFile.set(nodeJs.rootPackageDir.resolve("yarn.lock"))
it.parameters.outputFile.set(yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName))
it.parameters.shouldFailOnClose.set(
provider { yarnRootExtension.requireConfigured().yarnLockMismatchReport == YarnLockMismatchReport.FAIL_AFTER_BUILD }
)
it.parameters.reportNewYarnLock.set(
provider { yarnRootExtension.requireConfigured().reportNewYarnLock }
)
}
BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry
.onTaskCompletion(service)
val storeYarnLock = tasks.register(STORE_YARN_LOCK_NAME, YarnLockStoreTask::class.java) { task ->
task.dependsOn(kotlinNpmInstall)
task.inputFile.set(nodeJs.rootPackageDir.resolve("yarn.lock"))
task.outputDirectory.set(yarnRootExtension.lockFileDirectory)
task.fileName.set(yarnRootExtension.lockFileName)
task.yarnLockMismatchReportService.set(service)
task.yarnLockMismatchReport = provider { yarnRootExtension.requireConfigured().yarnLockMismatchReport }
task.reportNewYarnLock = provider { yarnRootExtension.requireConfigured().reportNewYarnLock }
task.yarnLockAutoReplace = provider { yarnRootExtension.requireConfigured().yarnLockAutoReplace }
}
val restoreYarnLock = tasks.register("kotlinRestoreYarnLock", YarnLockCopyTask::class.java) {
tasks.register(UPGRADE_YARN_LOCK, YarnLockCopyTask::class.java) { task ->
task.dependsOn(kotlinNpmInstall)
task.inputFile.set(nodeJs.rootPackageDir.resolve("yarn.lock"))
task.outputDirectory.set(yarnRootExtension.lockFileDirectory)
task.fileName.set(yarnRootExtension.lockFileName)
}
val restoreYarnLock = tasks.register(RESTORE_YARN_LOCK_NAME, YarnLockCopyTask::class.java) {
val lockFile = yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName)
it.inputFile.set(yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName))
it.outputDirectory.set(nodeJs.rootPackageDir)
@@ -88,7 +120,6 @@ open class YarnPlugin : Plugin<Project> {
kotlinNpmInstall.configure {
it.dependsOn(restoreYarnLock)
it.finalizedBy(storeYarnLock)
}
}
@@ -44,6 +44,12 @@ open class YarnRootExtension(
var ignoreScripts by Property(true)
var yarnLockMismatchReport: YarnLockMismatchReport by Property(YarnLockMismatchReport.FAIL)
var reportNewYarnLock: Boolean by Property(false)
var yarnLockAutoReplace: Boolean by Property(false)
val yarnSetupTaskProvider: TaskProvider<YarnSetupTask>
get() = project.tasks
.withType(YarnSetupTask::class.java)
@@ -107,6 +113,9 @@ open class YarnRootExtension(
standalone = !download,
ivyDependency = "com.yarnpkg:yarn:$version@tar.gz",
ignoreScripts = ignoreScripts,
yarnLockMismatchReport = yarnLockMismatchReport,
reportNewYarnLock = reportNewYarnLock,
yarnLockAutoReplace = yarnLockAutoReplace,
)
}
@@ -95,3 +95,23 @@ internal inline fun <T> withTemporaryDirectory(prefix: String, action: (director
directory.deleteRecursively()
}
}
fun contentEquals(file1: File, file2: File): Boolean {
file1.useLines { seq1 ->
file2.useLines { seq2 ->
val iterator1 = seq1.iterator()
val iterator2 = seq2.iterator()
while(iterator1.hasNext() == iterator2.hasNext()) {
if (!iterator1.hasNext()) return true
if (iterator1.next() != iterator2.next()) {
return false
}
}
return true
}
}
}