diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index dd71e3dc32f..ab2dfec5f97 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -1782,4 +1782,75 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } } + + @DisplayName("test FAIL_ON_PROJECT_REPOS using custom repository") + @GradleTest + fun testFailOnProjectReposUsingCustomRepo(gradleVersion: GradleVersion) { + project("js-project-repos", gradleVersion) { + settingsGradleKts.modify { + it + """ + + dependencyResolutionManagement { + repositories { + ivy { + name = "Node.JS dist" + url = URI("https://nodejs.org/dist") + patternLayout { + artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]") + } + metadataSources { + artifact() + } + content { + includeModule("org.nodejs", "node") + } + } + ivy { + name = "Yarn dist" + url = URI("https://github.com/yarnpkg/yarn/releases/download") + patternLayout { + artifact("v[revision]/[artifact](-v[revision]).[ext]") + } + metadataSources { + artifact() + } + content { + includeModule("com.yarnpkg", "yarn") + } + } + } + } + """.trimIndent() + } + + build("kotlinNodeJsSetup", "kotlinYarnSetup") { + assertTasksExecuted(":kotlinNodeJsSetup") + assertTasksExecuted(":kotlinYarnSetup") + } + } + } + + @DisplayName("test FAIL_ON_PROJECT_REPOS no download") + @GradleTest + fun testFailOnProjectReposNoDownload(gradleVersion: GradleVersion) { + project("js-project-repos", gradleVersion) { + buildGradleKts.modify { + it + """ + + rootProject.plugins.withType { + rootProject.the().download = false + } + + rootProject.plugins.withType { + rootProject.the().download = false + } + """.trimIndent() + } + + build("kotlinNodeJsSetup", "kotlinYarnSetup") { + assertTasksSkipped(":kotlinNodeJsSetup") + assertTasksSkipped(":kotlinYarnSetup") + } + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts new file mode 100644 index 00000000000..6788d08735b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + kotlin("js") +} + +kotlin { + js(IR) { + binaries.executable() + nodejs { + } + } +} + +rootProject.plugins.withType { + rootProject.the().downloadBaseUrl = null +} + +rootProject.plugins.withType { + rootProject.the().downloadBaseUrl = null +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/settings.gradle.kts new file mode 100644 index 00000000000..7a3a8e60ea2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/settings.gradle.kts @@ -0,0 +1,7 @@ +import java.net.URI + +dependencyResolutionManagement { + repositories { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/src/main/kotlin/main.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/src/main/kotlin/main.kt new file mode 100644 index 00000000000..30c13f47715 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/src/main/kotlin/main.kt @@ -0,0 +1,15 @@ +/* + * 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 example + +@JsExport +class Child { + val name: String = "Child" +} + +fun main() { + println("Hello there!") +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractEnv.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractEnv.kt new file mode 100644 index 00000000000..816293a5666 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractEnv.kt @@ -0,0 +1,14 @@ +package org.jetbrains.kotlin.gradle.targets.js + +import java.io.File + +interface AbstractEnv { + + val download: Boolean + + val downloadBaseUrl: String? + + val ivyDependency: String + + val dir: File +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSettings.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSettings.kt new file mode 100644 index 00000000000..542b51095a0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSettings.kt @@ -0,0 +1,18 @@ +package org.jetbrains.kotlin.gradle.targets.js + +import org.gradle.work.DisableCachingByDefault +import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware +import java.io.File + +abstract class AbstractSettings : ConfigurationPhaseAware() { + + abstract var download: Boolean + + abstract var downloadBaseUrl: String? + + abstract var installationDir: File + + abstract var version: String + + abstract var command: String +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt new file mode 100644 index 00000000000..253da6568cc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt @@ -0,0 +1,165 @@ +package org.jetbrains.kotlin.gradle.targets.js + +import org.gradle.api.DefaultTask +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.ArchiveOperations +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.gradle.internal.hash.FileHasher +import org.gradle.work.DisableCachingByDefault +import org.jetbrains.kotlin.gradle.logging.kotlinInfo +import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService +import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics +import java.io.File +import java.net.URI +import javax.inject.Inject + +@DisableCachingByDefault +abstract class AbstractSetupTask> : DefaultTask() { + @get:Internal + protected abstract val settings: Settings + + @get:Internal + protected abstract val artifactPattern: String + + @get:Internal + protected abstract val artifactModule: String + + @get:Internal + protected abstract val artifactName: String + + @get:Internal + protected val env: Env by lazy { settings.requireConfigured() } + + private val shouldDownload by lazy { + env.download + } + + @get:Inject + internal abstract val archiveOperations: ArchiveOperations + + @get:Inject + internal abstract val fileHasher: FileHasher + + @get:Inject + internal abstract val objects: ObjectFactory + + @get:Inject + internal abstract val fs: FileSystemOperations + + val ivyDependency: String + @Input get() = env.ivyDependency + + val downloadBaseUrl: String? + @Input + @Optional + get() = env.downloadBaseUrl + + val destination: File + @OutputDirectory get() = env.dir + + val destinationHashFile: File + @OutputFile get() = destination.parentFile.resolve("${destination.name}.hash") + + @Transient + @get:Internal + internal var configuration: Provider? = null + + @get:Classpath + @get:Optional + val dist: File? by lazy { + if (!shouldDownload) return@lazy null + + withUrlRepo { + val startDownloadTime = System.currentTimeMillis() + configuration!!.get().files.single().also { + val downloadDuration = System.currentTimeMillis() - startDownloadTime + if (downloadDuration > 0) { + KotlinBuildStatsService.getInstance() + ?.report(NumericalMetrics.ARTIFACTS_DOWNLOAD_SPEED, it.length() * 1000 / downloadDuration) + } + } + } + } + + init { + onlyIf { + shouldDownload + } + } + + @Suppress("unused") + @TaskAction + fun exec() { + if (!shouldDownload) return + + logger.kotlinInfo("Using distribution from '$dist'") + + extractWithUpToDate( + destination, + destinationHashFile, + dist!!, + fileHasher, + ::extract + ) + } + + private fun withUrlRepo(action: () -> T): T { + val repo = downloadBaseUrl?.let { + project.repositories.ivy { repo -> + repo.name = "Distributions at ${it}" + repo.url = URI(it) + + repo.patternLayout { + it.artifact(artifactPattern) + } + repo.metadataSources { it.artifact() } + repo.content { it.includeModule(artifactModule, artifactName) } + } + } + + return action().also { + repo?.let { project.repositories.remove(it) } + } + } + + private fun extractWithUpToDate( + destination: File, + destinationHashFile: File, + dist: File, + fileHasher: FileHasher, + extract: (File, File) -> Unit, + ) { + var distHash: String? = null + val upToDate = destinationHashFile.let { file -> + if (file.exists()) { + file.useLines { seq -> + val list = seq.first().split(" ") + list.size == 2 && + list[0] == fileHasher.calculateDirHash(destination) && + list[1] == fileHasher.hash(dist).toByteArray().toHex().also { distHash = it } + } + } else false + } + + if (upToDate) { + return + } + + if (destination.isDirectory) { + destination.deleteRecursively() + } + + extract(dist, destination.parentFile) + + destinationHashFile.writeText( + fileHasher.calculateDirHash(destination)!! + + " " + + (distHash ?: fileHasher.hash(dist).toByteArray().toHex()) + ) + } + + abstract fun extract(archive: File, destination: File) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsEnv.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsEnv.kt index dde42b7ec3f..89d9b591401 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsEnv.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsEnv.kt @@ -1,23 +1,24 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs +import org.jetbrains.kotlin.gradle.targets.js.AbstractEnv import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import java.io.File data class NodeJsEnv( + override val download: Boolean, val cleanableStore: CleanableStore, val rootPackageDir: File, - val nodeDir: File, + override val dir: File, val nodeBinDir: File, val nodeExecutable: String, - val platformName: String, val architectureName: String, - val ivyDependency: String, - val downloadBaseUrl: String, + override val ivyDependency: String, + override val downloadBaseUrl: String?, val packageManager: NpmApi, -) { +) : AbstractEnv { val isWindows: Boolean get() = platformName == "win" } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt index 285b3150e72..0e44d203e5d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt @@ -8,9 +8,9 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.tasks.TaskProvider -import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider +import org.jetbrains.kotlin.gradle.targets.js.AbstractSettings import org.jetbrains.kotlin.gradle.targets.js.NpmVersions import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver @@ -26,7 +26,7 @@ import java.io.File open class NodeJsRootExtension( val project: Project, -) : ConfigurationPhaseAware() { +) : AbstractSettings() { init { check(project.rootProject == project) @@ -54,18 +54,42 @@ open class NodeJsRootExtension( project.logger.kotlinInfo("Storing cached files in $it") } - var installationDir by Property(gradleHome.resolve("nodejs")) + override var installationDir by Property(gradleHome.resolve("nodejs")) - var download by Property(true) + override var download by Property(true) - var nodeDownloadBaseUrl by Property("https://nodejs.org/dist") + // deprecate after bootstrap +// @Deprecated("Use downloadBaseUrl instead", ReplaceWith("downloadBaseUrl")) + var nodeDownloadBaseUrl + get() = downloadBaseUrl + set(value) { + downloadBaseUrl = value + } + + override var downloadBaseUrl: String? by Property("https://nodejs.org/dist") + + // deprecate after bootstrap +// @Deprecated("Use version instead", ReplaceWith("version")) + var nodeVersion + get() = version + set(value) { + version = value + } // Release schedule: https://github.com/nodejs/Release // Actual LTS and Current versions: https://nodejs.org/en/download/ // Older versions and more information, e.g. V8 version inside: https://nodejs.org/en/download/releases/ - var nodeVersion by Property("18.12.1") + override var version by Property("18.12.1") - var nodeCommand by Property("node") + override var command by Property("node") + + // deprecate after bootstrap +// @Deprecated("Use command instead", ReplaceWith("command")) + var nodeCommand + get() = command + set(value) { + command = value + } var packageManager: NpmApi by Property(Yarn()) @@ -107,9 +131,10 @@ open class NodeJsRootExtension( } return NodeJsEnv( + download = download, cleanableStore = cleanableStore, rootPackageDir = rootPackageDir, - nodeDir = nodeDir, + dir = nodeDir, nodeBinDir = nodeBinDir, nodeExecutable = getExecutable("node", nodeCommand, "exe"), platformName = name, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt index a734fea81db..c7778e73357 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt @@ -10,7 +10,6 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.plugins.BasePlugin import org.gradle.api.provider.Provider -import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.plugin.internal.configurationTimePropertiesAccessor import org.jetbrains.kotlin.gradle.plugin.internal.usedAtConfigurationTime import org.jetbrains.kotlin.gradle.plugin.variantImplementationFactory diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt index 857ec128327..344b61a7f12 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt @@ -1,127 +1,52 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs -import org.gradle.api.DefaultTask -import org.gradle.api.artifacts.Configuration -import org.gradle.api.file.ArchiveOperations -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -import org.gradle.internal.hash.FileHasher +import org.gradle.api.tasks.Internal import org.gradle.work.DisableCachingByDefault -import org.jetbrains.kotlin.gradle.logging.kotlinInfo -import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService +import org.jetbrains.kotlin.gradle.targets.js.AbstractSetupTask import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension -import org.jetbrains.kotlin.gradle.targets.js.extractWithUpToDate -import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics import java.io.File -import java.net.URI import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths -import javax.inject.Inject @DisableCachingByDefault -abstract class NodeJsSetupTask : DefaultTask() { - @Transient - private val settings = project.rootProject.kotlinNodeJsExtension - private val env by lazy { settings.requireConfigured() } - - private val shouldDownload = settings.download - - @get:Inject - abstract internal val archiveOperations: ArchiveOperations - - @get:Inject - internal open val fileHasher: FileHasher - get() = error("Should be injected") - - @get:Inject - internal abstract val objects: ObjectFactory - - @get:Inject - abstract internal val fs: FileSystemOperations - - val ivyDependency: String - @Input get() = env.ivyDependency - - val downloadBaseUrl: String - @Input get() = env.downloadBaseUrl - - val destination: File - @OutputDirectory get() = env.nodeDir - - val destinationHashFile: File - @OutputFile get() = destination.parentFile.resolve("${destination.name}.hash") - +abstract class NodeJsSetupTask : AbstractSetupTask() { @Transient @get:Internal - internal lateinit var configuration: Provider + override val settings = project.rootProject.kotlinNodeJsExtension - @get:Classpath - @get:Optional - val nodeJsDist: File? by lazy { - if (shouldDownload) { - val repo = project.repositories.ivy { repo -> - repo.name = "Node Distributions at ${downloadBaseUrl}" - repo.url = URI(downloadBaseUrl) + @get:Internal + override val artifactPattern: String + get() = "v[revision]/[artifact](-v[revision]-[classifier]).[ext]" - repo.patternLayout { - it.artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]") - } - repo.metadataSources { it.artifact() } - repo.content { it.includeModule("org.nodejs", "node") } - } - val startDownloadTime = System.currentTimeMillis() - val dist = configuration.get().files.single() - val downloadDuration = System.currentTimeMillis() - startDownloadTime - if (downloadDuration > 0) { - KotlinBuildStatsService.getInstance() - ?.report(NumericalMetrics.ARTIFACTS_DOWNLOAD_SPEED, dist.length() * 1000 / downloadDuration) - } - project.repositories.remove(repo) - dist - } else null - } + @get:Internal + override val artifactModule: String + get() = "org.nodejs" - init { - onlyIf { - shouldDownload - } - } + @get:Internal + override val artifactName: String + get() = "node" - @Suppress("unused") - @TaskAction - fun exec() { - if (!shouldDownload) return - logger.kotlinInfo("Using node distribution from '$nodeJsDist'") + override fun extract(archive: File, destination: File) { + var fixBrokenSymLinks = false - extractWithUpToDate( - destination, - destinationHashFile, - nodeJsDist!!, - fileHasher - ) { dist, destination -> - var fixBrokenSymLinks = false - - fs.copy { - it.from( - when { - dist.name.endsWith("zip") -> archiveOperations.zipTree(dist) - else -> { - fixBrokenSymLinks = true - archiveOperations.tarTree(dist) - } + fs.copy { + it.from( + when { + archive.name.endsWith("zip") -> archiveOperations.zipTree(archive) + else -> { + fixBrokenSymLinks = true + archiveOperations.tarTree(archive) } - ) - it.into(destination) - } + } + ) + it.into(destination) + } - fixBrokenSymlinks(destination, env.isWindows, fixBrokenSymLinks) + fixBrokenSymlinks(destination, env.isWindows, fixBrokenSymLinks) - if (!env.isWindows) { - File(env.nodeExecutable).setExecutable(true) - } + if (!env.isWindows) { + File(env.nodeExecutable).setExecutable(true) } } @@ -137,7 +62,7 @@ abstract class NodeJsSetupTask : DefaultTask() { name: String, nodeBinDirPath: Path, nodeDirProvider: File, - isWindows: Boolean + isWindows: Boolean, ) { val script = nodeBinDirPath.resolve(name) val scriptFile = computeNpmScriptFile(nodeDirProvider, name, isWindows) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt index 2fc9ea9be36..648b365fef7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt @@ -37,42 +37,6 @@ fun ByteArray.toHex(): String { return String(result) } -fun extractWithUpToDate( - destination: File, - destinationHashFile: File, - dist: File, - fileHasher: FileHasher, - extract: (File, File) -> Unit -) { - var distHash: String? = null - val upToDate = destinationHashFile.let { file -> - if (file.exists()) { - file.useLines { seq -> - val list = seq.first().split(" ") - list.size == 2 && - list[0] == fileHasher.calculateDirHash(destination) && - list[1] == fileHasher.hash(dist).toByteArray().toHex().also { distHash = it } - } - } else false - } - - if (upToDate) { - return - } - - if (destination.isDirectory) { - destination.deleteRecursively() - } - - extract(dist, destination.parentFile) - - destinationHashFile.writeText( - fileHasher.calculateDirHash(destination)!! + - " " + - (distHash ?: fileHasher.hash(dist).toByteArray().toHex()) - ) -} - fun FileHasher.calculateDirHash( dir: File ): String? { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt index de51e43f45e..9206660dd76 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt @@ -5,19 +5,21 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn +import org.jetbrains.kotlin.gradle.targets.js.AbstractEnv import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import java.io.File data class YarnEnv( - val downloadUrl: String, + override val download: Boolean, + override val downloadBaseUrl: String?, val cleanableStore: CleanableStore, - val home: File, + override val dir: File, val executable: String, - val ivyDependency: String, + override val ivyDependency: String, val standalone: Boolean, val ignoreScripts: Boolean, val yarnLockMismatchReport: YarnLockMismatchReport, val reportNewYarnLock: Boolean, val yarnLockAutoReplace: Boolean, val yarnResolutions: List -) +) : AbstractEnv diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt index 6df2288924d..fdef6820980 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt @@ -7,14 +7,11 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider -import org.jetbrains.kotlin.gradle.utils.markResolvable import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager -import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager 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 @@ -24,6 +21,7 @@ import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.ST 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 +import org.jetbrains.kotlin.gradle.utils.markResolvable import org.jetbrains.kotlin.gradle.utils.onlyIfCompat open class YarnPlugin : Plugin { @@ -45,6 +43,9 @@ open class YarnPlugin : Plugin { val setupTask = registerTask(YarnSetupTask.NAME) { it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider) + it.group = NodeJsRootPlugin.TASKS_GROUP_NAME + it.description = "Download and install a local yarn version" + it.configuration = provider { this.project.configurations.detachedConfiguration(this.project.dependencies.create(it.ivyDependency)) .markResolvable() @@ -131,7 +132,7 @@ open class YarnPlugin : Plugin { // https://youtrack.jetbrains.com/issue/KT-48241 private fun configureRequiresNpmDependencies( project: Project, - rootPackageJson: TaskProvider + rootPackageJson: TaskProvider, ) { val fn: (Project) -> Unit = { it.tasks.implementing(RequiresNpmDependencies::class) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt index ff872e0c4e7..0899872c730 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt @@ -8,8 +8,8 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.tasks.TaskProvider -import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware import org.jetbrains.kotlin.gradle.logging.kotlinInfo +import org.jetbrains.kotlin.gradle.targets.js.AbstractSettings import org.jetbrains.kotlin.gradle.targets.js.nodejs.Platform import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore @@ -17,7 +17,7 @@ import java.io.File open class YarnRootExtension( val project: Project -) : ConfigurationPhaseAware() { +) : AbstractSettings() { init { check(project == project.rootProject) } @@ -26,14 +26,14 @@ open class YarnRootExtension( project.logger.kotlinInfo("Storing cached files in $it") } - var installationDir by Property(gradleHome.resolve("yarn")) + override var installationDir by Property(gradleHome.resolve("yarn")) - var downloadBaseUrl by Property("https://github.com/yarnpkg/yarn/releases/download") - var version by Property("1.22.17") + override var downloadBaseUrl: String? by Property("https://github.com/yarnpkg/yarn/releases/download") + override var version by Property("1.22.17") - var command by Property("yarn") + override var command by Property("yarn") - var download by Property(true) + override var download by Property(true) var lockFileName by Property("yarn.lock") var lockFileDirectory: File by Property(project.rootDir.resolve("kotlin-js-store")) @@ -88,9 +88,10 @@ open class YarnRootExtension( finalCommand } return YarnEnv( - downloadUrl = downloadBaseUrl, + download = download, + downloadBaseUrl = downloadBaseUrl, cleanableStore = cleanableStore, - home = home, + dir = home, executable = getExecutable("yarn", command, "cmd"), standalone = !download, ivyDependency = "com.yarnpkg:yarn:$version@tar.gz", diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnSetupTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnSetupTask.kt index 451fdeceb8a..73c406daf4c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnSetupTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnSetupTask.kt @@ -5,109 +5,30 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn -import org.gradle.api.DefaultTask -import org.gradle.api.artifacts.Configuration -import org.gradle.api.file.ArchiveOperations -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -import org.gradle.internal.hash.FileHasher +import org.gradle.api.tasks.Internal import org.gradle.work.DisableCachingByDefault -import org.jetbrains.kotlin.gradle.logging.kotlinInfo -import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService -import org.jetbrains.kotlin.gradle.targets.js.extractWithUpToDate -import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin -import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics +import org.jetbrains.kotlin.gradle.targets.js.AbstractSetupTask import java.io.File -import java.net.URI -import javax.inject.Inject @DisableCachingByDefault -abstract class YarnSetupTask : DefaultTask() { +abstract class YarnSetupTask : AbstractSetupTask() { @Transient - private val settings = project.yarn - private val env by lazy { settings.requireConfigured() } + @Internal + override val settings = project.yarn - private val shouldDownload = settings.download - - @get:Inject - internal abstract val archiveOperations: ArchiveOperations - - @get:Inject - internal abstract val fileHasher: FileHasher - - @get:Inject - internal abstract val fs: FileSystemOperations - - @Suppress("MemberVisibilityCanBePrivate") - val downloadUrl - @Input get() = env.downloadUrl - - @Suppress("MemberVisibilityCanBePrivate") - val destination: File - @OutputDirectory get() = env.home - - val destinationHashFile: File - @OutputFile get() = destination.parentFile.resolve("${destination.name}.hash") - - init { - group = NodeJsRootPlugin.TASKS_GROUP_NAME - description = "Download and install a local yarn version" - } - - val ivyDependency: String - @Input get() = env.ivyDependency - - @Transient @get:Internal - internal lateinit var configuration: Provider + override val artifactPattern: String + get() = "v[revision]/[artifact](-v[revision]).[ext]" - @get:Classpath - @get:Optional - val yarnDist: File? by lazy { - if (shouldDownload) { - val repo = project.repositories.ivy { repo -> - repo.name = "Yarn Distributions at ${downloadUrl}" - repo.url = URI(downloadUrl) - repo.patternLayout { - it.artifact("v[revision]/[artifact](-v[revision]).[ext]") - } - repo.metadataSources { it.artifact() } - repo.content { it.includeModule("com.yarnpkg", "yarn") } - } - val startDownloadTime = System.currentTimeMillis() - val dist = configuration.get().files.single() - val downloadDuration = System.currentTimeMillis() - startDownloadTime - if (downloadDuration > 0) { - KotlinBuildStatsService.getInstance() - ?.report(NumericalMetrics.ARTIFACTS_DOWNLOAD_SPEED, dist.length() * 1000 / downloadDuration) - } - project.repositories.remove(repo) - dist - } else null - } + @get:Internal + override val artifactModule: String + get() = "com.yarnpkg" - init { - onlyIf { - shouldDownload - } - } + @get:Internal + override val artifactName: String + get() = "yarn" - @TaskAction - fun setup() { - if (!shouldDownload) return - logger.kotlinInfo("Using yarn distribution from '$yarnDist'") - - extractWithUpToDate( - destination, - destinationHashFile, - yarnDist!!, - fileHasher, - ::extract - ) - } - - private fun extract(archive: File, destination: File) { + override fun extract(archive: File, destination: File) { val dirInTar = archive.name.removeSuffix(".tar.gz") fs.copy { it.from(archiveOperations.tarTree(archive))