[Gradle,JS]make downloadBaseUrl nullable to work with FAIL_ON_PROJECT_REPOS

^KT-56300 fixed
^KT-55620 fixed
This commit is contained in:
Ilya Goncharov
2023-10-24 13:10:12 +00:00
committed by Space Team
parent f4d18da6a0
commit c9e328528e
16 changed files with 413 additions and 265 deletions
@@ -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<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().download = false
}
rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin> {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().download = false
}
""".trimIndent()
}
build("kotlinNodeJsSetup", "kotlinYarnSetup") {
assertTasksSkipped(":kotlinNodeJsSetup")
assertTasksSkipped(":kotlinYarnSetup")
}
}
}
} }
@@ -0,0 +1,19 @@
plugins {
kotlin("js")
}
kotlin {
js(IR) {
binaries.executable()
nodejs {
}
}
}
rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().downloadBaseUrl = null
}
rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin> {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().downloadBaseUrl = null
}
@@ -0,0 +1,7 @@
import java.net.URI
dependencyResolutionManagement {
repositories {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
}
}
@@ -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!")
}
@@ -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
}
@@ -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<Env : AbstractEnv> : ConfigurationPhaseAware<Env>() {
abstract var download: Boolean
abstract var downloadBaseUrl: String?
abstract var installationDir: File
abstract var version: String
abstract var command: String
}
@@ -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<Env : AbstractEnv, Settings : AbstractSettings<Env>> : 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<Configuration>? = 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 <T> 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)
}
@@ -1,23 +1,24 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs 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.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
data class NodeJsEnv( data class NodeJsEnv(
override val download: Boolean,
val cleanableStore: CleanableStore, val cleanableStore: CleanableStore,
val rootPackageDir: File, val rootPackageDir: File,
val nodeDir: File, override val dir: File,
val nodeBinDir: File, val nodeBinDir: File,
val nodeExecutable: String, val nodeExecutable: String,
val platformName: String, val platformName: String,
val architectureName: String, val architectureName: String,
val ivyDependency: String, override val ivyDependency: String,
val downloadBaseUrl: String, override val downloadBaseUrl: String?,
val packageManager: NpmApi, val packageManager: NpmApi,
) { ) : AbstractEnv {
val isWindows: Boolean val isWindows: Boolean
get() = platformName == "win" get() = platformName == "win"
} }
@@ -8,9 +8,9 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.tasks.TaskProvider 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.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider 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.NpmVersions
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
@@ -26,7 +26,7 @@ import java.io.File
open class NodeJsRootExtension( open class NodeJsRootExtension(
val project: Project, val project: Project,
) : ConfigurationPhaseAware<NodeJsEnv>() { ) : AbstractSettings<NodeJsEnv>() {
init { init {
check(project.rootProject == project) check(project.rootProject == project)
@@ -54,18 +54,42 @@ open class NodeJsRootExtension(
project.logger.kotlinInfo("Storing cached files in $it") 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 // Release schedule: https://github.com/nodejs/Release
// Actual LTS and Current versions: https://nodejs.org/en/download/ // 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/ // 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()) var packageManager: NpmApi by Property(Yarn())
@@ -107,9 +131,10 @@ open class NodeJsRootExtension(
} }
return NodeJsEnv( return NodeJsEnv(
download = download,
cleanableStore = cleanableStore, cleanableStore = cleanableStore,
rootPackageDir = rootPackageDir, rootPackageDir = rootPackageDir,
nodeDir = nodeDir, dir = nodeDir,
nodeBinDir = nodeBinDir, nodeBinDir = nodeBinDir,
nodeExecutable = getExecutable("node", nodeCommand, "exe"), nodeExecutable = getExecutable("node", nodeCommand, "exe"),
platformName = name, platformName = name,
@@ -10,7 +10,6 @@ import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.BasePlugin
import org.gradle.api.provider.Provider 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.configurationTimePropertiesAccessor
import org.jetbrains.kotlin.gradle.plugin.internal.usedAtConfigurationTime import org.jetbrains.kotlin.gradle.plugin.internal.usedAtConfigurationTime
import org.jetbrains.kotlin.gradle.plugin.variantImplementationFactory import org.jetbrains.kotlin.gradle.plugin.variantImplementationFactory
@@ -1,127 +1,52 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.DefaultTask import org.gradle.api.tasks.Internal
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.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.targets.js.AbstractSetupTask
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension 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.io.File
import java.net.URI
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import javax.inject.Inject
@DisableCachingByDefault @DisableCachingByDefault
abstract class NodeJsSetupTask : DefaultTask() { abstract class NodeJsSetupTask : AbstractSetupTask<NodeJsEnv, NodeJsRootExtension>() {
@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")
@Transient @Transient
@get:Internal @get:Internal
internal lateinit var configuration: Provider<Configuration> override val settings = project.rootProject.kotlinNodeJsExtension
@get:Classpath @get:Internal
@get:Optional override val artifactPattern: String
val nodeJsDist: File? by lazy { get() = "v[revision]/[artifact](-v[revision]-[classifier]).[ext]"
if (shouldDownload) {
val repo = project.repositories.ivy { repo ->
repo.name = "Node Distributions at ${downloadBaseUrl}"
repo.url = URI(downloadBaseUrl)
repo.patternLayout { @get:Internal
it.artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]") override val artifactModule: String
} get() = "org.nodejs"
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
}
init { @get:Internal
onlyIf { override val artifactName: String
shouldDownload get() = "node"
}
}
@Suppress("unused") override fun extract(archive: File, destination: File) {
@TaskAction var fixBrokenSymLinks = false
fun exec() {
if (!shouldDownload) return
logger.kotlinInfo("Using node distribution from '$nodeJsDist'")
extractWithUpToDate( fs.copy {
destination, it.from(
destinationHashFile, when {
nodeJsDist!!, archive.name.endsWith("zip") -> archiveOperations.zipTree(archive)
fileHasher else -> {
) { dist, destination -> fixBrokenSymLinks = true
var fixBrokenSymLinks = false archiveOperations.tarTree(archive)
fs.copy {
it.from(
when {
dist.name.endsWith("zip") -> archiveOperations.zipTree(dist)
else -> {
fixBrokenSymLinks = true
archiveOperations.tarTree(dist)
}
} }
) }
it.into(destination) )
} it.into(destination)
}
fixBrokenSymlinks(destination, env.isWindows, fixBrokenSymLinks) fixBrokenSymlinks(destination, env.isWindows, fixBrokenSymLinks)
if (!env.isWindows) { if (!env.isWindows) {
File(env.nodeExecutable).setExecutable(true) File(env.nodeExecutable).setExecutable(true)
}
} }
} }
@@ -137,7 +62,7 @@ abstract class NodeJsSetupTask : DefaultTask() {
name: String, name: String,
nodeBinDirPath: Path, nodeBinDirPath: Path,
nodeDirProvider: File, nodeDirProvider: File,
isWindows: Boolean isWindows: Boolean,
) { ) {
val script = nodeBinDirPath.resolve(name) val script = nodeBinDirPath.resolve(name)
val scriptFile = computeNpmScriptFile(nodeDirProvider, name, isWindows) val scriptFile = computeNpmScriptFile(nodeDirProvider, name, isWindows)
@@ -37,42 +37,6 @@ fun ByteArray.toHex(): String {
return String(result) 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( fun FileHasher.calculateDirHash(
dir: File dir: File
): String? { ): String? {
@@ -5,19 +5,21 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn 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 org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
data class YarnEnv( data class YarnEnv(
val downloadUrl: String, override val download: Boolean,
override val downloadBaseUrl: String?,
val cleanableStore: CleanableStore, val cleanableStore: CleanableStore,
val home: File, override val dir: File,
val executable: String, val executable: String,
val ivyDependency: String, override val ivyDependency: String,
val standalone: Boolean, val standalone: Boolean,
val ignoreScripts: Boolean, val ignoreScripts: Boolean,
val yarnLockMismatchReport: YarnLockMismatchReport, val yarnLockMismatchReport: YarnLockMismatchReport,
val reportNewYarnLock: Boolean, val reportNewYarnLock: Boolean,
val yarnLockAutoReplace: Boolean, val yarnLockAutoReplace: Boolean,
val yarnResolutions: List<YarnResolution> val yarnResolutions: List<YarnResolution>
) ) : AbstractEnv
@@ -7,14 +7,11 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider 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.MultiplePluginDeclarationDetector
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin 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.kotlinNodeJsExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager 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.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing 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.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.targets.js.yarn.YarnLockCopyTask.Companion.UPGRADE_YARN_LOCK
import org.jetbrains.kotlin.gradle.tasks.CleanDataTask import org.jetbrains.kotlin.gradle.tasks.CleanDataTask
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.markResolvable
import org.jetbrains.kotlin.gradle.utils.onlyIfCompat import org.jetbrains.kotlin.gradle.utils.onlyIfCompat
open class YarnPlugin : Plugin<Project> { open class YarnPlugin : Plugin<Project> {
@@ -45,6 +43,9 @@ open class YarnPlugin : Plugin<Project> {
val setupTask = registerTask<YarnSetupTask>(YarnSetupTask.NAME) { val setupTask = registerTask<YarnSetupTask>(YarnSetupTask.NAME) {
it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider) it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider)
it.group = NodeJsRootPlugin.TASKS_GROUP_NAME
it.description = "Download and install a local yarn version"
it.configuration = provider { it.configuration = provider {
this.project.configurations.detachedConfiguration(this.project.dependencies.create(it.ivyDependency)) this.project.configurations.detachedConfiguration(this.project.dependencies.create(it.ivyDependency))
.markResolvable() .markResolvable()
@@ -131,7 +132,7 @@ open class YarnPlugin : Plugin<Project> {
// https://youtrack.jetbrains.com/issue/KT-48241 // https://youtrack.jetbrains.com/issue/KT-48241
private fun configureRequiresNpmDependencies( private fun configureRequiresNpmDependencies(
project: Project, project: Project,
rootPackageJson: TaskProvider<RootPackageJsonTask> rootPackageJson: TaskProvider<RootPackageJsonTask>,
) { ) {
val fn: (Project) -> Unit = { val fn: (Project) -> Unit = {
it.tasks.implementing(RequiresNpmDependencies::class) it.tasks.implementing(RequiresNpmDependencies::class)
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Action import org.gradle.api.Action
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider 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.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.nodejs.Platform
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
@@ -17,7 +17,7 @@ import java.io.File
open class YarnRootExtension( open class YarnRootExtension(
val project: Project val project: Project
) : ConfigurationPhaseAware<YarnEnv>() { ) : AbstractSettings<YarnEnv>() {
init { init {
check(project == project.rootProject) check(project == project.rootProject)
} }
@@ -26,14 +26,14 @@ open class YarnRootExtension(
project.logger.kotlinInfo("Storing cached files in $it") 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") override var downloadBaseUrl: String? by Property("https://github.com/yarnpkg/yarn/releases/download")
var version by Property("1.22.17") 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 lockFileName by Property("yarn.lock")
var lockFileDirectory: File by Property(project.rootDir.resolve("kotlin-js-store")) var lockFileDirectory: File by Property(project.rootDir.resolve("kotlin-js-store"))
@@ -88,9 +88,10 @@ open class YarnRootExtension(
finalCommand finalCommand
} }
return YarnEnv( return YarnEnv(
downloadUrl = downloadBaseUrl, download = download,
downloadBaseUrl = downloadBaseUrl,
cleanableStore = cleanableStore, cleanableStore = cleanableStore,
home = home, dir = home,
executable = getExecutable("yarn", command, "cmd"), executable = getExecutable("yarn", command, "cmd"),
standalone = !download, standalone = !download,
ivyDependency = "com.yarnpkg:yarn:$version@tar.gz", ivyDependency = "com.yarnpkg:yarn:$version@tar.gz",
@@ -5,109 +5,30 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.DefaultTask import org.gradle.api.tasks.Internal
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.work.DisableCachingByDefault import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.targets.js.AbstractSetupTask
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 java.io.File import java.io.File
import java.net.URI
import javax.inject.Inject
@DisableCachingByDefault @DisableCachingByDefault
abstract class YarnSetupTask : DefaultTask() { abstract class YarnSetupTask : AbstractSetupTask<YarnEnv, YarnRootExtension>() {
@Transient @Transient
private val settings = project.yarn @Internal
private val env by lazy { settings.requireConfigured() } 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 @get:Internal
internal lateinit var configuration: Provider<Configuration> override val artifactPattern: String
get() = "v[revision]/[artifact](-v[revision]).[ext]"
@get:Classpath @get:Internal
@get:Optional override val artifactModule: String
val yarnDist: File? by lazy { get() = "com.yarnpkg"
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
}
init { @get:Internal
onlyIf { override val artifactName: String
shouldDownload get() = "yarn"
}
}
@TaskAction override fun extract(archive: File, destination: File) {
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) {
val dirInTar = archive.name.removeSuffix(".tar.gz") val dirInTar = archive.name.removeSuffix(".tar.gz")
fs.copy { fs.copy {
it.from(archiveOperations.tarTree(archive)) it.from(archiveOperations.tarTree(archive))