[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
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"
}
@@ -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<NodeJsEnv>() {
) : AbstractSettings<NodeJsEnv>() {
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,
@@ -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
@@ -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<NodeJsEnv, NodeJsRootExtension>() {
@Transient
@get:Internal
internal lateinit var configuration: Provider<Configuration>
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)
@@ -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? {
@@ -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<YarnResolution>
)
) : AbstractEnv
@@ -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<Project> {
@@ -45,6 +43,9 @@ open class YarnPlugin : Plugin<Project> {
val setupTask = registerTask<YarnSetupTask>(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<Project> {
// https://youtrack.jetbrains.com/issue/KT-48241
private fun configureRequiresNpmDependencies(
project: Project,
rootPackageJson: TaskProvider<RootPackageJsonTask>
rootPackageJson: TaskProvider<RootPackageJsonTask>,
) {
val fn: (Project) -> Unit = {
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.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<YarnEnv>() {
) : AbstractSettings<YarnEnv>() {
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",
@@ -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<YarnEnv, YarnRootExtension>() {
@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<Configuration>
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))