[Gradle, JS] Remove requireNodeModules

[Gradle, JS] Remove 4 major webpack

[Gradle, JS] Remove webpack 4 related dependencies

[Gradle, JS] How want to see npm dependency

[JS IR] Object factory and remove forceResolve

[Gradle, JS] Object factory to package json producer

[Gradle, JS] Only npm dependencies as required

[Gradle, JS] NodeJsRootPlugin applied once

[Gradle, JS] NodeJsRootPlugin applied once #2

[Gradle, JS] Divide task providers from NodeJsRootExtension

[Gradle, JS] Remove redundant

[Gradle, JS] Fix #1

[Gradle, JS] Fix #2

[Gradle, JS] Fix #3

[Gradle, JS] Fix #4

[Gradle, JS] Fix #5

[Gradle, JS] Fix tests

[Gradle, JS] No objectFactory

[Gradle, JS] Remove archive compat and fs compat

[Gradle, JS] Build service kotlin npm resolution manager

[JS] Build servicify

Build servicify

Build servicify

Fix internal

Tasks requirements as build service

~ try to move from build service

[JS] no nested

[JS] Configuration resolver inside input files

Revert "[JS] Configuration resolver inside input files"

This reverts commit 48536e011a8a341e9cb62fbf8e03a09f2ba115ba.

[JS] nested but only configuration

[JS] packageJsonHandlers serializable

[JS] packageJsonProducer as part of task inputs

[JS] packageJsonProducer

~1

~2

~3

~4

~5

~6

~7

~8

~9

~10

Remove comments

[Gradle, JS] Remove comments

[Gradle, JS] Fix internals and test

[Gradle, JS] Input package json files for root package json

[Gradle, JS] Kotlin npm install always tracked externally

[Gradle, JS] Rename resolution items

[Gradle, JS] Build service with new scheme
This commit is contained in:
Ilya Goncharov
2022-10-04 14:41:24 +02:00
committed by Space Team
parent 7fa7118325
commit b2f416dbbe
69 changed files with 1291 additions and 1997 deletions
@@ -86,7 +86,6 @@ abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean)
":packageJson", ":packageJson",
":publicPackageJson", ":publicPackageJson",
":rootPackageJson", ":rootPackageJson",
":kotlinNpmInstall",
":compileKotlinJs", ":compileKotlinJs",
":nodeTest", ":nodeTest",
) + if (irBackend) listOf(":compileProductionExecutableKotlinJs") else emptyList() ) + if (irBackend) listOf(":compileProductionExecutableKotlinJs") else emptyList()
@@ -109,7 +108,6 @@ abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean)
":packageJson", ":packageJson",
":publicPackageJson", ":publicPackageJson",
":rootPackageJson", ":rootPackageJson",
":kotlinNpmInstall",
":compileKotlinJs", ":compileKotlinJs",
":nodeTest", ":nodeTest",
) + if (irBackend) listOf(":compileProductionExecutableKotlinJs") else emptyList() ) + if (irBackend) listOf(":compileProductionExecutableKotlinJs") else emptyList()
@@ -125,7 +123,7 @@ abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean)
assertSimpleConfigurationCacheScenarioWorks( assertSimpleConfigurationCacheScenarioWorks(
"assemble", "kotlinStoreYarnLock", "assemble", "kotlinStoreYarnLock",
buildOptions = defaultBuildOptions, buildOptions = defaultBuildOptions,
executedTaskNames = listOf(":kotlinNpmInstall") executedTaskNames = listOf(":rootPackageJson")
) )
} }
} }
@@ -1575,6 +1575,7 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
project("kotlin-js-browser-project", gradleVersion) { project("kotlin-js-browser-project", gradleVersion) {
buildGradleKts.modify { originalScript -> buildGradleKts.modify { originalScript ->
buildString { buildString {
append("import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager")
append(originalScript) append(originalScript)
append( append(
""" """
@@ -1583,7 +1584,7 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
| val nodejs = the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>() | val nodejs = the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>()
| tasks.named<org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask>("kotlinNpmInstall") { | tasks.named<org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask>("kotlinNpmInstall") {
| doFirst { | doFirst {
| nodejs.npmResolutionManager.state = | project.rootProject.kotlinNpmResolutionManager.get().state =
| org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager.ResolutionState.Error(GradleException("someSpecialException")) | org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager.ResolutionState.Error(GradleException("someSpecialException"))
| } | }
| } | }
@@ -7,14 +7,9 @@ package org.jetbrains.kotlin.generators.gradle.targets.js
val npmPackages = listOf( val npmPackages = listOf(
NpmPackage("webpack"), NpmPackage("webpack"),
NpmPackage("webpack", "4.46.0", "webpack4"),
NpmPackage("webpack-cli"), NpmPackage("webpack-cli"),
NpmPackage("webpack-cli", "3.3.12", "webpackCli3"),
NpmPackage("webpack-bundle-analyzer"),
NpmPackage("webpack-dev-server"), NpmPackage("webpack-dev-server"),
NpmPackage("webpack-dev-server", "3.11.2", "webpackDevServer3"),
NpmPackage("source-map-loader"), NpmPackage("source-map-loader"),
NpmPackage("source-map-loader", "1.1.0", "sourceMapLoader1"),
NpmPackage("source-map-support"), NpmPackage("source-map-support"),
NpmPackage("css-loader"), NpmPackage("css-loader"),
NpmPackage("style-loader"), NpmPackage("style-loader"),
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle.internal package org.jetbrains.kotlin.gradle.internal
import java.io.Serializable
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
abstract class ConfigurationPhaseAware<C : Any> { abstract class ConfigurationPhaseAware<C : Any> {
@@ -456,19 +456,6 @@ internal class PropertiesProvider private constructor(private val project: Proje
val publishJsCompilerAttribute: Boolean val publishJsCompilerAttribute: Boolean
get() = this.booleanProperty("$jsCompilerProperty.publish.attribute") ?: true get() = this.booleanProperty("$jsCompilerProperty.publish.attribute") ?: true
/**
* Use Webpack 4 for compatibility
*/
val webpackMajorVersion: WebpackMajorVersion
get() = this.property(WebpackMajorVersion.webpackMajorVersion)?.let { WebpackMajorVersion.byArgument(it) }
?.also { version ->
if (!WebpackMajorVersion.webpackVersionWarning && version != WebpackMajorVersion.DEFAULT) {
WebpackMajorVersion.webpackVersionWarning = true
project.logger.warn(WebpackMajorVersion.warningMessage)
}
}
?: WebpackMajorVersion.DEFAULT
/** /**
* Use Kotlin/JS backend compiler type * Use Kotlin/JS backend compiler type
*/ */
@@ -106,7 +106,7 @@ class DefaultKotlinDependencyHandler(
version: String, version: String,
): NpmDependency = ): NpmDependency =
NpmDependency( NpmDependency(
project = project, objectFactory = project.objects,
name = name, name = name,
version = version, version = version,
) )
@@ -134,7 +134,7 @@ class DefaultKotlinDependencyHandler(
version: String version: String
): NpmDependency = ): NpmDependency =
NpmDependency( NpmDependency(
project = project, objectFactory = project.objects,
name = name, name = name,
version = version, version = version,
scope = NpmDependency.Scope.DEV scope = NpmDependency.Scope.DEV
@@ -161,7 +161,7 @@ class DefaultKotlinDependencyHandler(
version: String, version: String,
): NpmDependency = ): NpmDependency =
NpmDependency( NpmDependency(
project = project, objectFactory = project.objects,
name = name, name = name,
version = version, version = version,
scope = NpmDependency.Scope.OPTIONAL, scope = NpmDependency.Scope.OPTIONAL,
@@ -190,7 +190,7 @@ class DefaultKotlinDependencyHandler(
version: String version: String
): NpmDependency = ): NpmDependency =
NpmDependency( NpmDependency(
project = project, objectFactory = project.objects,
name = name, name = name,
version = version, version = version,
scope = NpmDependency.Scope.PEER scope = NpmDependency.Scope.PEER
@@ -202,7 +202,7 @@ class DefaultKotlinDependencyHandler(
scope: NpmDependency.Scope, scope: NpmDependency.Scope,
): NpmDependency = ): NpmDependency =
directoryNpmDependency( directoryNpmDependency(
project = project, objectFactory = project.objects,
name = name, name = name,
directory = directory, directory = directory,
scope = scope, scope = scope,
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import java.io.Serializable
data class KotlinGradleNpmPackage(val simpleModuleName: String) : RequiredKotlinJsDependency { data class KotlinGradleNpmPackage(val simpleModuleName: String) : Serializable {
override fun createDependency(project: Project, scope: NpmDependency.Scope): Dependency = fun createDependency(project: Project): Dependency =
project.dependencies.create("org.jetbrains.kotlin:kotlin-$simpleModuleName") project.dependencies.create("org.jetbrains.kotlin:kotlin-$simpleModuleName")
} }
@@ -16,9 +16,14 @@ import org.jetbrains.kotlin.gradle.plugin.AbstractKotlinTargetConfigurator.Compa
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.LEGACY import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType.LEGACY
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.targets.js.dsl.* import org.jetbrains.kotlin.gradle.plugin.mpp.PRIMARY_SINGLE_COMPONENT_NAME
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBrowserDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsNodeDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetContainerDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsBinaryContainer import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsBinaryContainer
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolverPlugin
import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinBrowserJs import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinBrowserJs
import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinNodeJs import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinNodeJs
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
@@ -147,8 +152,13 @@ constructor(
private val propertiesProvider = PropertiesProvider(project) private val propertiesProvider = PropertiesProvider(project)
private val commonLazy by lazy {
NpmResolverPlugin.apply(project)
}
//Browser //Browser
private val browserLazyDelegate = lazy { private val browserLazyDelegate = lazy {
commonLazy
project.objects.newInstance(KotlinBrowserJs::class.java, this).also { project.objects.newInstance(KotlinBrowserJs::class.java, this).also {
it.configure() it.configure()
@@ -177,6 +187,7 @@ constructor(
//node.js //node.js
private val nodejsLazyDelegate = lazy { private val nodejsLazyDelegate = lazy {
commonLazy
project.objects.newInstance(KotlinNodeJs::class.java, this).also { project.objects.newInstance(KotlinNodeJs::class.java, this).also {
it.configure() it.configure()
@@ -5,11 +5,11 @@
package org.jetbrains.kotlin.gradle.targets.js package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.model.ObjectFactory
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
data class NpmPackageVersion(val name: String, var version: String) : RequiredKotlinJsDependency { data class NpmPackageVersion(val name: String, var version: String) : RequiredKotlinJsDependency {
override fun createDependency(project: Project, scope: NpmDependency.Scope): Dependency = override fun createDependency(objectFactory: ObjectFactory, scope: NpmDependency.Scope): Dependency =
NpmDependency(project, name, version, scope) NpmDependency(objectFactory, scope, name, version)
} }
@@ -12,24 +12,19 @@ import java.io.Serializable
*/ */
// DO NOT MODIFY DIRECTLY! Use org.jetbrains.kotlin.generators.gradle.targets.js.MainKt // DO NOT MODIFY DIRECTLY! Use org.jetbrains.kotlin.generators.gradle.targets.js.MainKt
class NpmVersions : Serializable { class NpmVersions : Serializable {
val webpack4 = NpmPackageVersion("webpack", "4.46.0")
val webpackCli3 = NpmPackageVersion("webpack-cli", "3.3.12")
val webpackDevServer3 = NpmPackageVersion("webpack-dev-server", "3.11.2")
val sourceMapLoader1 = NpmPackageVersion("source-map-loader", "1.1.0")
val webpack = NpmPackageVersion("webpack", "5.74.0") val webpack = NpmPackageVersion("webpack", "5.74.0")
val webpackCli = NpmPackageVersion("webpack-cli", "4.10.0") val webpackCli = NpmPackageVersion("webpack-cli", "4.10.0")
val webpackBundleAnalyzer = NpmPackageVersion("webpack-bundle-analyzer", "4.5.0") val webpackDevServer = NpmPackageVersion("webpack-dev-server", "4.11.1")
val webpackDevServer = NpmPackageVersion("webpack-dev-server", "4.9.3")
val sourceMapLoader = NpmPackageVersion("source-map-loader", "4.0.0") val sourceMapLoader = NpmPackageVersion("source-map-loader", "4.0.0")
val sourceMapSupport = NpmPackageVersion("source-map-support", "0.5.21") val sourceMapSupport = NpmPackageVersion("source-map-support", "0.5.21")
val cssLoader = NpmPackageVersion("css-loader", "6.7.1") val cssLoader = NpmPackageVersion("css-loader", "6.7.1")
val styleLoader = NpmPackageVersion("style-loader", "3.3.1") val styleLoader = NpmPackageVersion("style-loader", "3.3.1")
val sassLoader = NpmPackageVersion("sass-loader", "13.0.2") val sassLoader = NpmPackageVersion("sass-loader", "13.1.0")
val sass = NpmPackageVersion("sass", "1.54.3") val sass = NpmPackageVersion("sass", "1.55.0")
val toStringLoader = NpmPackageVersion("to-string-loader", "1.2.0") val toStringLoader = NpmPackageVersion("to-string-loader", "1.2.0")
val miniCssExtractPlugin = NpmPackageVersion("mini-css-extract-plugin", "2.6.1") val miniCssExtractPlugin = NpmPackageVersion("mini-css-extract-plugin", "2.6.1")
val mocha = NpmPackageVersion("mocha", "10.0.0") val mocha = NpmPackageVersion("mocha", "10.0.0")
val karma = NpmPackageVersion("karma", "6.4.0") val karma = NpmPackageVersion("karma", "6.4.1")
val karmaChromeLauncher = NpmPackageVersion("karma-chrome-launcher", "3.1.1") val karmaChromeLauncher = NpmPackageVersion("karma-chrome-launcher", "3.1.1")
val karmaPhantomjsLauncher = NpmPackageVersion("karma-phantomjs-launcher", "1.0.4") val karmaPhantomjsLauncher = NpmPackageVersion("karma-phantomjs-launcher", "1.0.4")
val karmaFirefoxLauncher = NpmPackageVersion("karma-firefox-launcher", "2.1.2") val karmaFirefoxLauncher = NpmPackageVersion("karma-firefox-launcher", "2.1.2")
@@ -41,7 +36,7 @@ class NpmVersions : Serializable {
val karmaCoverage = NpmPackageVersion("karma-coverage", "2.2.0") val karmaCoverage = NpmPackageVersion("karma-coverage", "2.2.0")
val karmaSourcemapLoader = NpmPackageVersion("karma-sourcemap-loader", "0.3.8") val karmaSourcemapLoader = NpmPackageVersion("karma-sourcemap-loader", "0.3.8")
val formatUtil = NpmPackageVersion("format-util", "1.0.5") val formatUtil = NpmPackageVersion("format-util", "1.0.5")
val typescript = NpmPackageVersion("typescript", "4.7.4") val typescript = NpmPackageVersion("typescript", "4.8.4")
val kotlinJsTestRunner = KotlinGradleNpmPackage("test-js-runner") val kotlinJsTestRunner = KotlinGradleNpmPackage("test-js-runner")
} }
@@ -7,9 +7,10 @@ package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.model.ObjectFactory
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import java.io.Serializable import java.io.Serializable
interface RequiredKotlinJsDependency : Serializable { interface RequiredKotlinJsDependency : Serializable {
fun createDependency(project: Project, scope: NpmDependency.Scope = NpmDependency.Scope.DEV): Dependency fun createDependency(objectFactory: ObjectFactory, scope: NpmDependency.Scope = NpmDependency.Scope.DEV): Dependency
} }
@@ -13,7 +13,6 @@ import org.gradle.api.tasks.Copy
import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinJsDce import org.jetbrains.kotlin.gradle.dsl.KotlinJsDce
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.distsDirectory import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.distsDirectory
@@ -23,14 +22,13 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDceDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBrowserDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBrowserDsl
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
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.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackDevtool import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackDevtool
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion.Companion.choose
import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat
import org.jetbrains.kotlin.gradle.utils.newFileProperty import org.jetbrains.kotlin.gradle.utils.newFileProperty
@@ -42,15 +40,11 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
KotlinJsIrSubTarget(target, "browser"), KotlinJsIrSubTarget(target, "browser"),
KotlinJsBrowserDsl { KotlinJsBrowserDsl {
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val webpackTaskConfigurations: MutableList<Action<KotlinWebpack>> = mutableListOf() private val webpackTaskConfigurations: MutableList<Action<KotlinWebpack>> = mutableListOf()
private val runTaskConfigurations: MutableList<Action<KotlinWebpack>> = mutableListOf() private val runTaskConfigurations: MutableList<Action<KotlinWebpack>> = mutableListOf()
private val propertiesProvider = PropertiesProvider(project)
private val webpackMajorVersion
get() = propertiesProvider.webpackMajorVersion
override val testTaskDescription: String override val testTaskDescription: String
get() = "Run all ${target.name} tests inside browser using karma and webpack" get() = "Run all ${target.name} tests inside browser using karma and webpack"
@@ -110,9 +104,6 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
override fun configureRun( override fun configureRun(
compilation: KotlinJsIrCompilation compilation: KotlinJsIrCompilation
) { ) {
val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
val commonRunTask = registerSubTargetTask<Task>(disambiguateCamelCased(RUN_TASK_NAME)) {} val commonRunTask = registerSubTargetTask<Task>(disambiguateCamelCased(RUN_TASK_NAME)) {}
compilation.binaries compilation.binaries
@@ -134,10 +125,7 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
task.dependsOn(binary.linkSyncTask) task.dependsOn(binary.linkSyncTask)
task.outputDirectory.convention(distsDirectory).finalizeValueOnRead() task.outputDirectory.convention(distsDirectory).finalizeValueOnRead()
webpackMajorVersion.choose( task.args.add(0, "serve")
{ task.args.add(0, "serve") },
{ task.bin = "webpack-dev-server/bin/webpack-dev-server.js" }
)()
task.description = "start ${mode.name.toLowerCaseAsciiOnly()} webpack dev server" task.description = "start ${mode.name.toLowerCaseAsciiOnly()} webpack dev server"
val openValue = if (compilation.platformType == KotlinPlatformType.wasm) { val openValue = if (compilation.platformType == KotlinPlatformType.wasm) {
@@ -156,29 +144,19 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
true true
} }
webpackMajorVersion.choose( task.devServer = KotlinWebpackConfig.DevServer(
{ open = openValue,
task.devServer = KotlinWebpackConfig.DevServer( static = mutableListOf(
open = openValue, "./kotlin",
static = mutableListOf( compilation.output.resourcesDir.canonicalPath
"./kotlin", ),
compilation.output.resourcesDir.canonicalPath, client = KotlinWebpackConfig.DevServer.Client(
), KotlinWebpackConfig.DevServer.Client.Overlay(
client = KotlinWebpackConfig.DevServer.Client( errors = true,
KotlinWebpackConfig.DevServer.Client.Overlay( warnings = false
errors = true,
warnings = false
)
)
) )
}, )
{ )
task.devServer = KotlinWebpackConfig.DevServer(
open = true,
contentBase = mutableListOf(compilation.output.resourcesDir.canonicalPath)
)
}
)()
task.doNotTrackStateCompat("Tracked by external webpack tool") task.doNotTrackStateCompat("Tracked by external webpack tool")
@@ -205,7 +183,6 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
compilation: KotlinJsIrCompilation compilation: KotlinJsIrCompilation
) { ) {
val project = compilation.target.project val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
val processResourcesTask = target.project.tasks.named(compilation.processResourcesTaskName) val processResourcesTask = target.project.tasks.named(compilation.processResourcesTaskName)
@@ -12,7 +12,9 @@ import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.AbstractKotlinTargetConfigurator
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlatformTestRun import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlatformTestRun
@@ -20,7 +22,6 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.Distribution
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolverPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.dependsOn
@@ -52,8 +53,6 @@ abstract class KotlinJsIrSubTarget(
} }
internal fun configure() { internal fun configure() {
NpmResolverPlugin.apply(project)
target.compilations.all { target.compilations.all {
val npmProject = it.npmProject val npmProject = it.npmProject
it.kotlinOptions { it.kotlinOptions {
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.targets.js.KotlinJsReportAggregatingTestRun
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenExec import org.jetbrains.kotlin.gradle.targets.js.binaryen.BinaryenExec
import org.jetbrains.kotlin.gradle.targets.js.dsl.* import org.jetbrains.kotlin.gradle.targets.js.dsl.*
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolverPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.typescript.TypeScriptValidationTask import org.jetbrains.kotlin.gradle.targets.js.typescript.TypeScriptValidationTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
@@ -110,6 +111,7 @@ constructor(
} }
private val commonLazy by lazy { private val commonLazy by lazy {
NpmResolverPlugin.apply(project)
compilations.all { compilation -> compilations.all { compilation ->
compilation.binaries compilation.binaries
.withType(JsIrBinary::class.java) .withType(JsIrBinary::class.java)
@@ -9,7 +9,7 @@ import org.gradle.api.Action
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsNodeDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsNodeDsl
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExec import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExec
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.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinWasmNode import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinWasmNode
import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.dependsOn
@@ -21,7 +21,8 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) :
KotlinJsIrSubTargetBase(target, "node"), KotlinJsIrSubTargetBase(target, "node"),
KotlinJsNodeDsl { KotlinJsNodeDsl {
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val nodeJsTaskProviders = project.rootProject.kotlinNodeJsExtension
override val testTaskDescription: String override val testTaskDescription: String
get() = "Run all ${target.name} tests inside nodejs using the builtin test framework" get() = "Run all ${target.name} tests inside nodejs using the builtin test framework"
@@ -51,9 +52,9 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) :
override fun configureTestDependencies(test: KotlinJsTest) { override fun configureTestDependencies(test: KotlinJsTest) {
test.dependsOn( test.dependsOn(
nodeJs.npmInstallTaskProvider, nodeJsTaskProviders.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider, nodeJsTaskProviders.storeYarnLockTaskProvider,
nodeJs.nodeJsSetupTaskProvider nodeJsTaskProviders.nodeJsSetupTaskProvider
) )
} }
@@ -1,10 +1,12 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs package org.jetbrains.kotlin.gradle.targets.js.nodejs
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(
val cleanableStore: CleanableStore, val cleanableStore: CleanableStore,
val rootPackageDir: File,
val nodeDir: File, val nodeDir: File,
val nodeBinDir: File, val nodeBinDir: File,
val nodeExecutable: String, val nodeExecutable: String,
@@ -12,7 +14,9 @@ data class NodeJsEnv(
val platformName: String, val platformName: String,
val architectureName: String, val architectureName: String,
val ivyDependency: String, val ivyDependency: String,
val downloadBaseUrl: String val downloadBaseUrl: String,
val packageManager: NpmApi,
) { ) {
val isWindows: Boolean val isWindows: Boolean
get() = platformName == "win" get() = platformName == "win"
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.addWasmExperimentalArguments import org.jetbrains.kotlin.gradle.targets.js.addWasmExperimentalArguments
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension
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.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
@@ -48,10 +49,6 @@ constructor(
@NormalizeLineEndings @NormalizeLineEndings
val inputFileProperty: RegularFileProperty = project.newFileProperty() val inputFileProperty: RegularFileProperty = project.newFileProperty()
@get:Internal
override val nodeModulesRequired: Boolean
get() = true
@get:Internal @get:Internal
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> by lazy { override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> by lazy {
mutableSetOf<RequiredKotlinJsDependency>().also { mutableSetOf<RequiredKotlinJsDependency>().also {
@@ -92,7 +89,9 @@ constructor(
): TaskProvider<NodeJsExec> { ): TaskProvider<NodeJsExec> {
val target = compilation.target val target = compilation.target
val project = target.project val project = target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject) NodeJsRootPlugin.apply(project.rootProject)
val nodeJs = project.rootProject.kotlinNodeJsExtension
val nodeJsTaskProviders = project.rootProject.kotlinNodeJsExtension
val npmProject = compilation.npmProject val npmProject = compilation.npmProject
return project.registerTask( return project.registerTask(
@@ -103,8 +102,8 @@ constructor(
it.executable = nodeJs.requireConfigured().nodeExecutable it.executable = nodeJs.requireConfigured().nodeExecutable
it.workingDir = npmProject.dir it.workingDir = npmProject.dir
it.dependsOn( it.dependsOn(
nodeJs.npmInstallTaskProvider, nodeJsTaskProviders.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider, nodeJsTaskProviders.storeYarnLockTaskProvider,
) )
it.dependsOn(compilation.compileKotlinTaskProvider) it.dependsOn(compilation.compileKotlinTaskProvider)
if (compilation.platformType == KotlinPlatformType.wasm) { if (compilation.platformType == KotlinPlatformType.wasm) {
@@ -12,29 +12,45 @@ 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.NpmVersions import org.jetbrains.kotlin.gradle.targets.js.NpmVersions
import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager
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.PACKAGE_JSON_UMBRELLA_TASK_NAME import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmCachesSetup 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.KotlinNpmInstallTask
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.targets.js.yarn.Yarn 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
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 org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
import java.io.Serializable
open class NodeJsRootExtension(@Transient val rootProject: Project) : ConfigurationPhaseAware<NodeJsEnv>(), Serializable { open class NodeJsRootExtension(
val project: Project
) : ConfigurationPhaseAware<NodeJsEnv>() {
init { init {
check(rootProject.rootProject == rootProject) check(project.rootProject == project)
val projectProperties = PropertiesProvider(project)
if (projectProperties.errorJsGenerateExternals != null) {
project.logger.warn(
"""
|
|==========
|Please note, Dukat integration in Gradle plugin does not work now, it was removed.
|We rethink how we can integrate properly.
|==========
|
""".trimMargin()
)
}
} }
private val logger = rootProject.logger val rootProjectDir
get() = project.projectDir
private val gradleHome = rootProject.gradle.gradleUserHomeDir.also { private val gradleHome = project.gradle.gradleUserHomeDir.also {
logger.kotlinInfo("Storing cached files in $it") project.logger.kotlinInfo("Storing cached files in $it")
} }
var installationDir by Property(gradleHome.resolve("nodejs")) var installationDir by Property(gradleHome.resolve("nodejs"))
@@ -52,70 +68,12 @@ open class NodeJsRootExtension(@Transient val rootProject: Project) : Configurat
var packageManager: NpmApi by Property(Yarn()) var packageManager: NpmApi by Property(Yarn())
@Transient val taskRequirements: TasksRequirements
private val projectProperties = PropertiesProvider(rootProject) get() = resolver.tasksRequirements
private val errorGenerateExternals = run { lateinit var resolver: KotlinRootNpmResolver
if (projectProperties.errorJsGenerateExternals != null) {
logger.warn(
"""
|
|==========
|Please note, Dukat integration in Gradle plugin does not work now, it was removed.
|We rethink how we can integrate properly.
|==========
|
""".trimMargin()
)
}
}
val taskRequirements: TasksRequirements = TasksRequirements() val rootPackageDir: File = project.buildDir.resolve("js")
val nodeJsSetupTaskProvider: TaskProvider<out NodeJsSetupTask>
get() = rootProject.tasks.withType(NodeJsSetupTask::class.java).named(NodeJsSetupTask.NAME)
@Suppress(
"UNNECESSARY_SAFE_CALL",
"SAFE_CALL_WILL_CHANGE_NULLABILITY"
) // TODO: investigate this warning; fixing it breaks integration tests.
val npmInstallTaskProvider: TaskProvider<out KotlinNpmInstallTask>?
get() = rootProject?.tasks?.withType(KotlinNpmInstallTask::class.java)?.named(KotlinNpmInstallTask.NAME)
val packageJsonUmbrellaTaskProvider: TaskProvider<Task>
get() = rootProject.tasks.named(PACKAGE_JSON_UMBRELLA_TASK_NAME)
@Suppress(
"UNNECESSARY_SAFE_CALL",
"SAFE_CALL_WILL_CHANGE_NULLABILITY"
) // TODO: investigate this warning; fixing it breaks integration tests.
val rootPackageJsonTaskProvider: TaskProvider<RootPackageJsonTask>?
get() = rootProject?.tasks?.withType(RootPackageJsonTask::class.java)?.named(RootPackageJsonTask.NAME)
@Suppress(
"UNNECESSARY_SAFE_CALL",
"SAFE_CALL_WILL_CHANGE_NULLABILITY"
) // TODO: investigate this warning; fixing it breaks integration tests.
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")
}
val projectPackagesDir: File val projectPackagesDir: File
get() = rootPackageDir.resolve("packages") get() = rootPackageDir.resolve("packages")
@@ -123,6 +81,8 @@ open class NodeJsRootExtension(@Transient val rootProject: Project) : Configurat
val nodeModulesGradleCacheDir: File val nodeModulesGradleCacheDir: File
get() = rootPackageDir.resolve("packages_imported") get() = rootPackageDir.resolve("packages_imported")
val versions = NpmVersions()
override fun finalizeConfiguration(): NodeJsEnv { override fun finalizeConfiguration(): NodeJsEnv {
val platformHelper = PlatformHelper val platformHelper = PlatformHelper
val platform = platformHelper.osName val platform = platformHelper.osName
@@ -146,28 +106,35 @@ open class NodeJsRootExtension(@Transient val rootProject: Project) : Configurat
return NodeJsEnv( return NodeJsEnv(
cleanableStore = cleanableStore, cleanableStore = cleanableStore,
rootPackageDir = rootPackageDir,
nodeDir = nodeDir, nodeDir = nodeDir,
nodeBinDir = nodeBinDir, nodeBinDir = nodeBinDir,
nodeExecutable = getExecutable("node", nodeCommand, "exe"), nodeExecutable = getExecutable("node", nodeCommand, "exe"),
platformName = platform, platformName = platform,
architectureName = architecture, architectureName = architecture,
ivyDependency = getIvyDependency(), ivyDependency = getIvyDependency(),
downloadBaseUrl = nodeDownloadBaseUrl downloadBaseUrl = nodeDownloadBaseUrl,
packageManager = packageManager
) )
} }
internal fun executeSetup() { val nodeJsSetupTaskProvider: TaskProvider<out NodeJsSetupTask>
if (download) { get() = project.tasks.withType(NodeJsSetupTask::class.java).named(NodeJsSetupTask.NAME)
val nodeJsSetupTask = nodeJsSetupTaskProvider.get()
nodeJsSetupTask.actions.forEach {
it.execute(nodeJsSetupTask)
}
}
}
val versions = NpmVersions() val npmInstallTaskProvider: TaskProvider<out KotlinNpmInstallTask>
get() = project.tasks.withType(KotlinNpmInstallTask::class.java).named(KotlinNpmInstallTask.NAME)
val npmResolutionManager = KotlinNpmResolutionManager(this) val rootPackageJsonTaskProvider: TaskProvider<RootPackageJsonTask>
get() = project.tasks.withType(RootPackageJsonTask::class.java).named(RootPackageJsonTask.NAME)
val packageJsonUmbrellaTaskProvider: TaskProvider<Task>
get() = project.tasks.named(PACKAGE_JSON_UMBRELLA_TASK_NAME)
val npmCachesSetupTaskProvider: TaskProvider<out KotlinNpmCachesSetup>
get() = project.tasks.withType(KotlinNpmCachesSetup::class.java).named(KotlinNpmCachesSetup.NAME)
val storeYarnLockTaskProvider: TaskProvider<out YarnLockCopyTask>
get() = project.tasks.withType(YarnLockCopyTask::class.java).named(YarnLockCopyTask.STORE_YARN_LOCK_NAME)
companion object { companion object {
const val EXTENSION_NAME: String = "kotlinNodeJs" const val EXTENSION_NAME: String = "kotlinNodeJs"
@@ -9,18 +9,25 @@ import org.gradle.api.Plugin
import org.gradle.api.Project 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.jetbrains.kotlin.gradle.utils.markResolvable import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.Companion.EXTENSION_NAME import org.jetbrains.kotlin.gradle.targets.js.npm.CompositeNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmCachesSetup 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.yarn.YarnPlugin import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin
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.castIsolatedKotlinPluginClassLoaderAware
import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat
import org.jetbrains.kotlin.gradle.utils.markResolvable
import org.jetbrains.kotlin.gradle.utils.providerWithLazyConvention
open class NodeJsRootPlugin : Plugin<Project> { open class NodeJsRootPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run { override fun apply(project: Project) {
MultiplePluginDeclarationDetector.detect(project) MultiplePluginDeclarationDetector.detect(project)
project.plugins.apply(BasePlugin::class.java) project.plugins.apply(BasePlugin::class.java)
@@ -29,35 +36,93 @@ open class NodeJsRootPlugin : Plugin<Project> {
"NodeJsRootPlugin can be applied only to root project" "NodeJsRootPlugin can be applied only to root project"
} }
val settings = this.extensions.create(EXTENSION_NAME, NodeJsRootExtension::class.java, this) val nodeJs = project.extensions.create(
NodeJsRootExtension.EXTENSION_NAME,
NodeJsRootExtension::class.java,
project
)
val setupTask = registerTask<NodeJsSetupTask>(NodeJsSetupTask.NAME) { val setupTask = project.registerTask<NodeJsSetupTask>(NodeJsSetupTask.NAME) {
it.group = TASKS_GROUP_NAME it.group = TASKS_GROUP_NAME
it.description = "Download and install a local node/npm version" it.description = "Download and install a local node/npm version"
it.configuration = provider { it.configuration = project.provider {
this.project.configurations.detachedConfiguration(this.project.dependencies.create(it.ivyDependency)) project.configurations.detachedConfiguration(project.dependencies.create(it.ivyDependency))
.markResolvable() .markResolvable()
.also { conf -> conf.isTransitive = false } .also { conf -> conf.isTransitive = false }
} }
} }
val setupFileHasherTask = registerTask<KotlinNpmCachesSetup>(KotlinNpmCachesSetup.NAME) { val gradleNodeModulesProvider: Provider<GradleNodeModulesCache> = GradleNodeModulesCache.registerIfAbsent(
project,
project.projectDir,
nodeJs.nodeModulesGradleCacheDir
)
val compositeNodeModulesProvider: Provider<CompositeNodeModulesCache> = CompositeNodeModulesCache.registerIfAbsent(
project,
project.projectDir,
nodeJs.nodeModulesGradleCacheDir
)
val setupFileHasherTask = project.registerTask<KotlinNpmCachesSetup>(KotlinNpmCachesSetup.NAME) {
it.description = "Setup file hasher for caches" it.description = "Setup file hasher for caches"
it.gradleNodeModules.set(gradleNodeModulesProvider)
it.compositeNodeModules.set(compositeNodeModulesProvider)
} }
registerTask<KotlinNpmInstallTask>(KotlinNpmInstallTask.NAME) { val npmInstall = project.registerTask<KotlinNpmInstallTask>(KotlinNpmInstallTask.NAME) {
it.dependsOn(setupTask) it.dependsOn(setupTask)
it.dependsOn(setupFileHasherTask) it.dependsOn(setupFileHasherTask)
it.group = TASKS_GROUP_NAME it.group = TASKS_GROUP_NAME
it.description = "Find, download and link NPM dependencies and projects" it.description = "Find, download and link NPM dependencies and projects"
it.onlyIf { task ->
task as KotlinNpmInstallTask
task.preparedFiles.all { file ->
file.exists()
}
}
it.doNotTrackStateCompat("NPM package manager track by its own")
} }
registerTask<Task>(PACKAGE_JSON_UMBRELLA_TASK_NAME) project.registerTask<Task>(PACKAGE_JSON_UMBRELLA_TASK_NAME)
YarnPlugin.apply(project) YarnPlugin.apply(project)
tasks.register("node" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) { nodeJs.resolver = KotlinRootNpmResolver(
it.cleanableStoreProvider = provider { settings.requireConfigured().cleanableStore } project.name,
project.version.toString(),
TasksRequirements(),
nodeJs.versions,
nodeJs.projectPackagesDir,
nodeJs.rootProjectDir,
)
val objectFactory = project.objects
val npmResolutionManager = KotlinNpmResolutionManager.registerIfAbsent(
project,
objectFactory.providerWithLazyConvention {
nodeJs.resolver.close()
},
objectFactory.providerWithLazyConvention {
nodeJs.resolver.compilations.associate { compilation ->
"${compilation.project.path}:${compilation.disambiguatedName}" to compilation.packageJsonHandlers
}
},
gradleNodeModulesProvider,
compositeNodeModulesProvider
)
npmInstall.configure {
it.npmResolutionManager.set(npmResolutionManager)
it.npmResolutionManager.disallowChanges()
}
project.tasks.register("node" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) {
it.cleanableStoreProvider = project.provider { nodeJs.requireConfigured().cleanableStore }
it.group = TASKS_GROUP_NAME it.group = TASKS_GROUP_NAME
it.description = "Clean unused local node version" it.description = "Clean unused local node version"
} }
@@ -69,7 +134,33 @@ open class NodeJsRootPlugin : Plugin<Project> {
fun apply(rootProject: Project): NodeJsRootExtension { fun apply(rootProject: Project): NodeJsRootExtension {
check(rootProject == rootProject.rootProject) check(rootProject == rootProject.rootProject)
rootProject.plugins.apply(NodeJsRootPlugin::class.java) rootProject.plugins.apply(NodeJsRootPlugin::class.java)
return rootProject.extensions.getByName(EXTENSION_NAME) as NodeJsRootExtension return rootProject.extensions.getByName(NodeJsRootExtension.EXTENSION_NAME) as NodeJsRootExtension
} }
val Project.kotlinNodeJsExtension: NodeJsRootExtension
get() = extensions.getByName(NodeJsRootExtension.EXTENSION_NAME).castIsolatedKotlinPluginClassLoaderAware()
private val Project.gradleNodeModules
get() = GradleNodeModulesCache.registerIfAbsent(
this,
null,
null
)
private val Project.compositeNodeModules
get() = CompositeNodeModulesCache.registerIfAbsent(
this,
null,
null
)
val Project.kotlinNpmResolutionManager
get() = KotlinNpmResolutionManager.registerIfAbsent(
this,
null,
null,
gradleNodeModules,
compositeNodeModules
)
} }
} }
@@ -2,6 +2,7 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileSystemOperations
import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
@@ -9,8 +10,8 @@ import org.gradle.api.tasks.*
import org.gradle.internal.hash.FileHasher import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService 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.extractWithUpToDate import org.jetbrains.kotlin.gradle.targets.js.extractWithUpToDate
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics
import java.io.File import java.io.File
import java.net.URI import java.net.URI
@@ -21,12 +22,13 @@ import javax.inject.Inject
abstract class NodeJsSetupTask : DefaultTask() { abstract class NodeJsSetupTask : DefaultTask() {
@Transient @Transient
private val settings = NodeJsRootPlugin.apply(project.rootProject) private val settings = project.rootProject.kotlinNodeJsExtension
private val env by lazy { settings.requireConfigured() } private val env by lazy { settings.requireConfigured() }
private val shouldDownload = settings.download private val shouldDownload = settings.download
private val archiveOperations = ArchiveOperationsCompat(project) @get:Inject
abstract internal val archiveOperations: ArchiveOperations
@get:Inject @get:Inject
internal open val fileHasher: FileHasher internal open val fileHasher: FileHasher
@@ -36,8 +38,7 @@ abstract class NodeJsSetupTask : DefaultTask() {
internal abstract val objects: ObjectFactory internal abstract val objects: ObjectFactory
@get:Inject @get:Inject
internal open val fs: FileSystemOperations abstract internal val fs: FileSystemOperations
get() = error("Should be injected")
val ivyDependency: String val ivyDependency: String
@Input get() = env.ivyDependency @Input get() = env.ivyDependency
@@ -30,7 +30,7 @@ class TasksRequirements : Serializable {
val requiredNpmDependencies = requirements val requiredNpmDependencies = requirements
.asSequence() .asSequence()
.map { it.createDependency(task.compilation.target.project) } .map { it.createDependency(task.compilation.target.project.objects) }
.filterIsInstance<NpmDependency>() .filterIsInstance<NpmDependency>()
.toMutableSet() .toMutableSet()
@@ -16,8 +16,8 @@ import java.io.File
/** /**
* Cache for storing already created [GradleNodeModule]s * Cache for storing already created [GradleNodeModule]s
*/ */
internal abstract class AbstractNodeModulesCache : AutoCloseable, BuildService<AbstractNodeModulesCache.Parameters> { abstract class AbstractNodeModulesCache : AutoCloseable, BuildService<AbstractNodeModulesCache.Parameters> {
internal interface Parameters : BuildServiceParameters { interface Parameters : BuildServiceParameters {
val rootProjectDir: DirectoryProperty val rootProjectDir: DirectoryProperty
val cacheDir: DirectoryProperty val cacheDir: DirectoryProperty
} }
@@ -5,14 +5,24 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
import java.io.File import java.io.File
internal interface UsesCompositeNodeModulesCache : Task {
@get:Internal
val compositeNodeModules: Property<CompositeNodeModulesCache>
}
/** /**
* Cache for storing already created [GradleNodeModule]s * Cache for storing already created [GradleNodeModule]s
*/ */
internal abstract class CompositeNodeModulesCache : AbstractNodeModulesCache() { abstract class CompositeNodeModulesCache : AbstractNodeModulesCache() {
override val type: String override val type: String
get() = "composite" get() = "composite"
@@ -24,4 +34,44 @@ internal abstract class CompositeNodeModulesCache : AbstractNodeModulesCache() {
val module = CompositeNodeModuleBuilder(file, parameters.cacheDir.get().asFile) val module = CompositeNodeModuleBuilder(file, parameters.cacheDir.get().asFile)
return module.rebuild() return module.rebuild()
} }
companion object {
private val serviceClass = CompositeNodeModulesCache::class.java
private val serviceName = serviceClass.name
private fun registerIfAbsentImpl(
project: Project,
rootProjectDir: File?,
cacheDir: File?
): Provider<CompositeNodeModulesCache> {
project.gradle.sharedServices.registrations.findByName(serviceName)?.let {
@Suppress("UNCHECKED_CAST")
return it.service as Provider<CompositeNodeModulesCache>
}
val message = {
"Build service Composite Node Modules should be already registered"
}
requireNotNull(rootProjectDir, message)
requireNotNull(cacheDir, message)
return project.gradle.sharedServices.registerIfAbsent(serviceName, serviceClass) {
it.parameters.rootProjectDir.set(rootProjectDir)
it.parameters.cacheDir.set(cacheDir)
}
}
fun registerIfAbsent(
project: Project,
rootProjectDir: File?,
cacheDir: File?
) = registerIfAbsentImpl(project, rootProjectDir, cacheDir).also { serviceProvider ->
SingleActionPerProject.run(project, UsesCompositeNodeModulesCache::class.java.name) {
project.tasks.withType<UsesCompositeNodeModulesCache>().configureEach { task ->
task.usesService(serviceProvider)
}
}
}
}
} }
@@ -5,22 +5,22 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.FileTree import org.gradle.api.file.FileTree
import org.jetbrains.kotlin.gradle.targets.js.HTML import org.jetbrains.kotlin.gradle.targets.js.HTML
import org.jetbrains.kotlin.gradle.targets.js.JS import org.jetbrains.kotlin.gradle.targets.js.JS
import org.jetbrains.kotlin.gradle.targets.js.JS_MAP import org.jetbrains.kotlin.gradle.targets.js.JS_MAP
import org.jetbrains.kotlin.gradle.targets.js.META_JS import org.jetbrains.kotlin.gradle.targets.js.META_JS
import org.jetbrains.kotlin.gradle.targets.js.ir.KLIB_TYPE import org.jetbrains.kotlin.gradle.targets.js.ir.KLIB_TYPE
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.gradle.utils.FileSystemOperationsCompat
import java.io.File import java.io.File
/** /**
* Creates fake NodeJS module directory from given gradle [dependency]. * Creates fake NodeJS module directory from given gradle [dependency].
*/ */
internal class GradleNodeModuleBuilder( internal class GradleNodeModuleBuilder(
val fs: FileSystemOperationsCompat, val fs: FileSystemOperations,
val archiveOperations: ArchiveOperationsCompat, val archiveOperations: ArchiveOperations,
val moduleName: String, val moduleName: String,
val moduleVersion: String, val moduleVersion: String,
val srcFiles: Collection<File>, val srcFiles: Collection<File>,
@@ -5,28 +5,40 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileSystemOperations
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat import org.gradle.api.provider.Property
import org.jetbrains.kotlin.gradle.utils.FileSystemOperationsCompat import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.gradle.report.BuildMetricsService
import org.jetbrains.kotlin.gradle.report.UsesBuildMetricsService
import org.jetbrains.kotlin.gradle.report.reportingSettings
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
internal interface UsesGradleNodeModulesCache : Task {
@get:Internal
val gradleNodeModules: Property<GradleNodeModulesCache>
}
/** /**
* Cache for storing already created [GradleNodeModule]s * Cache for storing already created [GradleNodeModule]s
*/ */
internal abstract class GradleNodeModulesCache : AbstractNodeModulesCache() { abstract class GradleNodeModulesCache : AbstractNodeModulesCache() {
// TODO: replace by injected service org.gradle.api.file.FileSystemOperations once min support Gradle is 6.2 @get:Inject
// https://github.com/gradle/gradle/commit/d02b9d84c08dba64775fb9581e3280f88d319a21 abstract val fs: FileSystemOperations
@Transient
lateinit var fs: FileSystemOperationsCompat
override val type: String override val type: String
get() = "gradle" get() = "gradle"
// TODO: replace by injected service org.gradle.api.file.ArchiveOperations once min supported Gradle is 6.6 @get:Inject
@Transient abstract val archiveOperations: ArchiveOperations
lateinit var archiveOperations: ArchiveOperationsCompat
override fun buildImportedPackage( override fun buildImportedPackage(
name: String, name: String,
@@ -37,4 +49,44 @@ internal abstract class GradleNodeModulesCache : AbstractNodeModulesCache() {
module.visitArtifacts() module.visitArtifacts()
return module.rebuild() return module.rebuild()
} }
companion object {
private val serviceClass = GradleNodeModulesCache::class.java
private val serviceName = serviceClass.name
private fun registerIfAbsentImpl(
project: Project,
rootProjectDir: File?,
cacheDir: File?
): Provider<GradleNodeModulesCache> {
project.gradle.sharedServices.registrations.findByName(serviceName)?.let {
@Suppress("UNCHECKED_CAST")
return it.service as Provider<GradleNodeModulesCache>
}
val message = {
"Build service Gradle Node Modules should be already registered"
}
requireNotNull(rootProjectDir, message)
requireNotNull(cacheDir, message)
return project.gradle.sharedServices.registerIfAbsent(serviceName, serviceClass) {
it.parameters.rootProjectDir.set(rootProjectDir)
it.parameters.cacheDir.set(cacheDir)
}
}
fun registerIfAbsent(
project: Project,
rootProjectDir: File?,
cacheDir: File?
) = registerIfAbsentImpl(project, rootProjectDir, cacheDir).also { serviceProvider ->
SingleActionPerProject.run(project, UsesGradleNodeModulesCache::class.java.name) {
project.tasks.withType<UsesGradleNodeModulesCache>().configureEach { task ->
task.usesService(serviceProvider)
}
}
}
}
} }
@@ -5,169 +5,74 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Incubating import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters import org.gradle.api.services.BuildServiceParameters
import org.gradle.api.tasks.Internal
import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.internal.isInIdeaSync import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.Installation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinProjectNpmResolver import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import org.jetbrains.kotlin.gradle.utils.unavailableValueError
import java.io.File
/** internal interface UsesKotlinNpmResolutionManager : Task {
* # NPM resolution state manager @get:Internal
* val npmResolutionManager: Property<KotlinNpmResolutionManager>
* ## Resolving process from Gradle }
*
* **configuring**. Global initial state. [NpmResolverPlugin] should be applied for each project abstract class KotlinNpmResolutionManager : BuildService<KotlinNpmResolutionManager.Parameters> {
* that requires NPM resolution. When applied, [KotlinProjectNpmResolver] will be created for the
* corresponding project and will subscribe to all js compilations. [NpmResolverPlugin] requires interface Parameters : BuildServiceParameters {
* kotlin mulitplatform or plaform plugin applied first. val resolution: Property<KotlinRootNpmResolution>
*
* **up-to-date-checked**. This state is compilation local: one compilation may be in up-to-date-checked // pulled up from compilation resolver since it was failing with ClassNotFoundException on deserialization, see KT-49061
* state, while another may be steel in configuring state. New compilations may be added in this val packageJsonHandlers: MapProperty<String, List<PackageJson.() -> Unit>>
* state, but compilations that are already up-to-date-checked cannot be changed.
* Initiated by calling [KotlinPackageJsonTask.producerInputs] getter (will be called by Gradle). val gradleNodeModulesProvider: Property<GradleNodeModulesCache>
* [KotlinCompilationNpmResolver] will create and **resolve** aggregated compilation configuration, val compositeNodeModulesProvider: Property<CompositeNodeModulesCache>
* which contains all related compilation configuration and NPM tools configuration.
* NPM tools configuration contains all dependencies that is required for all enabled
* tasks related to this compilation. It is important to resolve this configuration inside particular
* project and not globally. Aggregated configuration will be analyzed for gradle internal dependencies
* (project dependencies), gradle external dependencies and npm dependencies. This collections will
* be treated as `packageJson` task inputs.
*
* **package-json-created**. This state also compilation local. Initiated by executing `packageJson`
* task for particular compilation. If `packageJson` task is up-to-date, this state is reached by
* first calling [KotlinCompilationNpmResolver.getResolutionOrResolveIfForced] which may be called
* by compilation that depends on this compilation. Note that package.json will be executed only for
* required compilations, while other may be missed.
*
* **Prepared**.
* Global final state. Initiated by executing global `rootPackageJson` task.
*
* **Installed**.
* All created package.json files will be gathered and package manager will be executed.
* Package manager will create lock file, that will be parsed for transitive npm dependencies
* that will be added to the root [NpmDependency] objects. `kotlinNpmInstall` task may be up-to-date.
* In this case, installed state will be reached by first call of [installIfNeeded] without executing
* package manager.
*
* Resolution will be used from [NpmDependency] by calling [getNpmDependencyResolvedCompilation].
* Also resolution will be checked before calling [NpmProject.require] and executing any task
* that requires npm dependencies or node_modules.
*
* User can call [requireInstalled] to get resolution info.
*
* ## Resolving process during Idea import
*
* In this case [forceFullResolve] will be set, and all compilations will be resolved
* even without `packageJson` task execution.
*
* During building gradle project model, all [NpmDependency] will be asked for there files,
* and [getNpmDependencyResolvedCompilation] will be called. In the [forceFullResolve] mode
* project will be fully resolved: all `package.json` files will be created, and package manager
* will be called. We are manually tracking package.json files contents to avoid calling package manager
* if nothing was changes.
*
* Note that in this case resolution process will be performed outside of task execution.
*/
class KotlinNpmResolutionManager(@Transient private val nodeJsSettings: NodeJsRootExtension?) {
private val forceFullResolve: Boolean by lazy {
(nodeJsSettings ?: unavailableValueError("nodeJsSettings")).rootProject.isInIdeaSync
} }
val resolver = KotlinRootNpmResolver(nodeJsSettings, forceFullResolve) val resolution
get() = parameters.resolution
internal abstract class KotlinNpmResolutionManagerStateHolder : BuildService<BuildServiceParameters.None> { @Volatile
@Volatile var state: ResolutionState = ResolutionState.Configuring(resolution.get())
internal var state: ResolutionState? = null
}
private val stateHolderProvider = (nodeJsSettings ?: unavailableValueError("nodeJsSettings"))
.rootProject.gradle.sharedServices.registerIfAbsent(
"npm-resolution-manager-state-holder", KotlinNpmResolutionManagerStateHolder::class.java
) {
}.also {
resolver.declareBuildServiceForRootProjectJsTasks(it)
resolver.declareBuildServiceForAllProjectsJsTasks(it)
}
private val stateHolder get() = stateHolderProvider.get()
var state: ResolutionState
get() = stateHolder.state ?: ResolutionState.Configuring(resolver)
set(value) {
stateHolder.state = value
}
sealed class ResolutionState { sealed class ResolutionState {
abstract val npmProjects: List<NpmProject>
class Configuring(val resolver: KotlinRootNpmResolver) : ResolutionState() { class Configuring(val resolution: KotlinRootNpmResolution) : ResolutionState()
override val npmProjects: List<NpmProject>
get() = resolver.compilations.map { it.npmProject }
}
open class Prepared(val preparedInstallation: KotlinRootNpmResolver.Installation) : ResolutionState() { open class Prepared(val preparedInstallation: Installation) : ResolutionState()
override val npmProjects: List<NpmProject>
get() = npmProjectsByProjectResolutions(preparedInstallation.projectResolutions)
}
class Installed internal constructor(internal val resolved: KotlinRootNpmResolution) : ResolutionState() { class Installed() : ResolutionState()
override val npmProjects: List<NpmProject>
get() = npmProjectsByProjectResolutions(resolved.projects)
}
class Error(val wrappedException: Throwable) : ResolutionState() { class Error(val wrappedException: Throwable) : ResolutionState()
override val npmProjects: List<NpmProject>
get() = emptyList()
}
companion object {
fun npmProjectsByProjectResolutions(
resolutions: Map<String, KotlinProjectNpmResolution>
): List<NpmProject> {
return resolutions
.values
.flatMap { it.npmProjects.map { it.npmProject } }
}
}
} }
@Incubating
internal fun requireInstalled(
services: ServiceRegistry,
logger: Logger,
reason: String = ""
) = installIfNeeded(reason = reason, services = services, logger = logger)
internal fun requireConfiguringState(): KotlinRootNpmResolver =
(this.state as? ResolutionState.Configuring ?: error("NPM Dependencies already resolved and installed")).resolver
internal fun isConfiguringState(): Boolean = internal fun isConfiguringState(): Boolean =
this.state is ResolutionState.Configuring this.state is ResolutionState.Configuring
internal fun prepare(logger: Logger) = prepareIfNeeded(requireNotPrepared = true, logger = logger) internal fun prepare(
logger: Logger,
npmEnvironment: NpmEnvironment,
yarnEnvironment: YarnEnvironment,
) = prepareIfNeeded(logger = logger, npmEnvironment, yarnEnvironment)
internal fun installIfNeeded( internal fun installIfNeeded(
reason: String? = "",
args: List<String> = emptyList(), args: List<String> = emptyList(),
services: ServiceRegistry, services: ServiceRegistry,
logger: Logger logger: Logger,
): KotlinRootNpmResolution? { npmEnvironment: NpmEnvironment,
synchronized(stateHolder) { yarnEnvironment: YarnEnvironment,
): Unit? {
synchronized(this) {
if (state is ResolutionState.Installed) { if (state is ResolutionState.Installed) {
return (state as ResolutionState.Installed).resolved return Unit
} }
if (state is ResolutionState.Error) { if (state is ResolutionState.Error) {
@@ -175,14 +80,9 @@ class KotlinNpmResolutionManager(@Transient private val nodeJsSettings: NodeJsRo
} }
return try { return try {
val installUpToDate = nodeJsSettings?.npmInstallTaskProvider?.get()?.state?.upToDate ?: false val installation: Installation = prepareIfNeeded(logger = logger, npmEnvironment, yarnEnvironment)
val forceUpToDate = installUpToDate && !forceFullResolve installation.install(args, services, logger, npmEnvironment, yarnEnvironment)
state = ResolutionState.Installed()
val installation = prepareIfNeeded(requireUpToDateReason = reason, logger = logger)
val resolution = installation
.install(forceUpToDate, args, services, logger)
state = ResolutionState.Installed(resolution)
resolution
} catch (e: Exception) { } catch (e: Exception) {
state = ResolutionState.Error(e) state = ResolutionState.Error(e)
throw e throw e
@@ -190,93 +90,97 @@ class KotlinNpmResolutionManager(@Transient private val nodeJsSettings: NodeJsRo
} }
} }
internal val packageJsonFiles: Collection<File> // internal val packageJsonFiles: Collection<File>
get() = state.npmProjects.map { it.packageJsonFile } // get() = state.npmProjects.map { it.packageJsonFile }
/**
* @param requireUpToDateReason Check that project already resolved,
* or it is up-to-date but just not closed. Show given message if it is not.
* @param requireNotPrepared Check that project is not prepared
*/
private fun prepareIfNeeded( private fun prepareIfNeeded(
requireUpToDateReason: String? = null, logger: Logger,
requireNotPrepared: Boolean = false, npmEnvironment: NpmEnvironment,
logger: Logger yarnEnvironment: YarnEnvironment,
): KotlinRootNpmResolver.Installation { ): Installation {
fun alreadyResolved(installation: KotlinRootNpmResolver.Installation): KotlinRootNpmResolver.Installation {
if (requireNotPrepared) error("Project already prepared")
return installation
}
val state0 = this.state val state0 = this.state
return when (state0) { return when (state0) {
is ResolutionState.Prepared -> { is ResolutionState.Prepared -> {
alreadyResolved(state0.preparedInstallation) state0.preparedInstallation
} }
is ResolutionState.Configuring -> { is ResolutionState.Configuring -> {
synchronized(stateHolder) { synchronized(this) {
val state1 = this.state val state1 = this.state
when (state1) { when (state1) {
is ResolutionState.Prepared -> alreadyResolved(state1.preparedInstallation) is ResolutionState.Prepared -> state1.preparedInstallation
is ResolutionState.Configuring -> { is ResolutionState.Configuring -> {
val upToDate = nodeJsSettings?.rootPackageJsonTaskProvider?.get()?.state?.upToDate ?: true state1.resolution.prepareInstallation(
if (requireUpToDateReason != null && !upToDate) { logger,
error("NPM dependencies should be resolved $requireUpToDateReason") npmEnvironment,
} yarnEnvironment,
this
state1.resolver.prepareInstallation(logger).also { ).also {
this.state = ResolutionState.Prepared(it) this.state = ResolutionState.Prepared(it)
} }
} }
is ResolutionState.Installed -> error("Project already installed") is ResolutionState.Installed -> error("Project already installed")
is ResolutionState.Error -> throw state1.wrappedException is ResolutionState.Error -> throw state1.wrappedException
} }
} }
} }
is ResolutionState.Installed -> error("Project already installed") is ResolutionState.Installed -> error("Project already installed")
is ResolutionState.Error -> throw state0.wrappedException is ResolutionState.Error -> throw state0.wrappedException
} }
} }
internal fun getNpmDependencyResolvedCompilation(npmDependency: NpmDependency): KotlinCompilationNpmResolution? { companion object {
val project = npmDependency.project!! private val serviceClass = KotlinNpmResolutionManager::class.java
val projectPath = project.path private val serviceName = serviceClass.name
val services = (project as ProjectInternal).services
val logger = project.logger
val resolvedProject = private fun registerIfAbsentImpl(
if (forceFullResolve) { project: Project,
installIfNeeded(reason = null, services = services, logger = logger)?.get(projectPath) ?: return null resolution: Provider<KotlinRootNpmResolution>?,
} else { packageJsonHandlers: Provider<Map<String, MutableList<PackageJson.() -> Unit>>>?,
// may return null only during npm resolution gradleNodeModulesProvider: Provider<GradleNodeModulesCache>,
// (it can be called since NpmDependency added to configuration that compositeNodeModulesProvider: Provider<CompositeNodeModulesCache>
// requires resolve to build package.json, in this case we should just skip this call) ): Provider<KotlinNpmResolutionManager> {
val state0 = state project.gradle.sharedServices.registrations.findByName(serviceName)?.let {
when (state0) { @Suppress("UNCHECKED_CAST")
is ResolutionState.Prepared -> state0.preparedInstallation[projectPath] return it.service as Provider<KotlinNpmResolutionManager>
is ResolutionState.Configuring -> {
return null
//error("Cannot use NpmDependency before :kotlinNpmInstall task execution")
}
is ResolutionState.Installed -> state0.resolved[projectPath]
is ResolutionState.Error -> {
return null
}
}
} }
return resolvedProject.npmProjectsByNpmDependency[npmDependency] ?: error("NPM project resolved without $this") val message = {
} "Build service KotlinNpmResolutionManager should be already registered"
}
internal fun <T> checkRequiredDependencies(task: T) requireNotNull(resolution, message)
where T : RequiresNpmDependencies, requireNotNull(packageJsonHandlers, message)
T : Task {
val targetRequired = resolver.taskRequirements.byTask[task.path]?.toSet() ?: setOf()
task.requiredNpmDependencies.forEach { return project.gradle.sharedServices.registerIfAbsent(serviceName, serviceClass) {
check(it in targetRequired) { it.parameters.resolution.set(resolution)
"${it.createDependency(task.project)} required by $task was not found resolved at the time of nodejs package manager call. " + it.parameters.packageJsonHandlers.set(packageJsonHandlers)
"This may be caused by changing $task configuration after npm dependencies resolution." it.parameters.gradleNodeModulesProvider.set(gradleNodeModulesProvider)
it.parameters.compositeNodeModulesProvider.set(compositeNodeModulesProvider)
}
}
fun registerIfAbsent(
project: Project,
resolution: Provider<KotlinRootNpmResolution>?,
packageJsonHandlers: Provider<Map<String, MutableList<PackageJson.() -> Unit>>>?,
gradleNodeModulesProvider: Provider<GradleNodeModulesCache>,
compositeNodeModulesProvider: Provider<CompositeNodeModulesCache>
) = registerIfAbsentImpl(
project,
resolution,
packageJsonHandlers,
gradleNodeModulesProvider,
compositeNodeModulesProvider
).also { serviceProvider ->
SingleActionPerProject.run(project, UsesKotlinNpmResolutionManager::class.java.name) {
project.tasks.withType<UsesKotlinNpmResolutionManager>().configureEach { task ->
task.usesService(serviceProvider)
task.usesService(gradleNodeModulesProvider)
task.usesService(compositeNodeModulesProvider)
}
} }
} }
} }
@@ -5,12 +5,12 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnv
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnEnv import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnEnv
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnResolution
import java.io.File import java.io.File
import java.io.Serializable import java.io.Serializable
@@ -18,36 +18,26 @@ import java.io.Serializable
* NodeJS package manager API * NodeJS package manager API
*/ */
interface NpmApi : Serializable { interface NpmApi : Serializable {
fun setup(project: Project)
fun preparedFiles(nodeJs: NpmEnvironment): Collection<File> fun preparedFiles(nodeJs: NpmEnvironment): Collection<File>
fun prepareRootProject( fun prepareRootProject(
rootProject: Project?,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
rootProjectName: String, rootProjectName: String,
rootProjectVersion: String, rootProjectVersion: String,
logger: Logger, logger: Logger,
subProjects: Collection<KotlinCompilationNpmResolution>, subProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>, resolutions: Map<String, String>,
forceFullResolve: Boolean
) )
fun resolveRootProject( fun resolveRootProject(
services: ServiceRegistry, services: ServiceRegistry,
logger: Logger, logger: Logger,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
yarn: YarnEnv, yarn: YarnEnvironment,
npmProjects: Collection<KotlinCompilationNpmResolution>, npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
cliArgs: List<String> cliArgs: List<String>
) )
fun resolveDependency(
npmResolution: KotlinCompilationNpmResolution,
dependency: NpmDependency,
transitive: Boolean
): Set<File>
companion object { companion object {
fun resolveOperationDescription(packageManagerTitle: String): String = fun resolveOperationDescription(packageManagerTitle: String): String =
"Resolving NPM dependencies using $packageManagerTitle" "Resolving NPM dependencies using $packageManagerTitle"
@@ -57,12 +47,29 @@ interface NpmApi : Serializable {
data class NpmEnvironment( data class NpmEnvironment(
val rootPackageDir: File, val rootPackageDir: File,
val nodeExecutable: String, val nodeExecutable: String,
val isWindows: Boolean val isWindows: Boolean,
val packageManager: NpmApi
) : Serializable ) : Serializable
internal val NodeJsRootExtension.asNpmEnvironment internal val NodeJsEnv.asNpmEnvironment
get() = NpmEnvironment( get() = NpmEnvironment(
rootPackageDir, rootPackageDir,
requireConfigured().nodeExecutable, nodeExecutable,
requireConfigured().isWindows isWindows,
packageManager
)
data class YarnEnvironment(
val executable: String,
val standalone: Boolean,
val ignoreScripts: Boolean,
val yarnResolutions: List<YarnResolution>
) : Serializable
internal val YarnEnv.asYarnEnvironment
get() = YarnEnvironment(
executable,
standalone,
ignoreScripts,
yarnResolutions
) )
@@ -5,31 +5,23 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.FileCollectionDependency import org.gradle.api.artifacts.FileCollectionDependency
import org.gradle.api.artifacts.SelfResolvingDependency import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.internal.artifacts.DependencyResolveContext
import org.gradle.api.internal.artifacts.ResolvableDependency
import org.gradle.api.internal.artifacts.dependencies.SelfResolvingDependencyInternal import org.gradle.api.internal.artifacts.dependencies.SelfResolvingDependencyInternal
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.TaskDependency import org.gradle.api.tasks.TaskDependency
import org.gradle.internal.component.local.model.DefaultLibraryBinaryIdentifier
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import java.io.File import java.io.File
data class NpmDependency( data class NpmDependency(
@Transient val objectFactory: ObjectFactory,
internal val project: Project?, val scope: Scope = Scope.NORMAL,
private val name: String, private val name: String,
private val version: String, private val version: String,
val scope: Scope = Scope.NORMAL, ) : FileCollectionDependency,
) : SelfResolvingDependency, SelfResolvingDependencyInternal {
SelfResolvingDependencyInternal,
ResolvableDependency,
FileCollectionDependency {
enum class Scope { enum class Scope {
NORMAL, NORMAL,
@@ -38,73 +30,33 @@ data class NpmDependency(
PEER PEER
} }
private var reason: String? = null
override fun getGroup(): String? = null override fun getGroup(): String? = null
override fun getName() = name
override fun getVersion() = version
internal var parent: NpmDependency? = null internal var parent: NpmDependency? = null
internal val dependencies = mutableSetOf<NpmDependency>() internal val dependencies = mutableSetOf<NpmDependency>()
internal var resolvedVersion: String? = null internal var resolvedVersion: String? = null
internal var integrity: String? = null internal var integrity: String? = null
override fun resolve(transitive: Boolean): Set<File> = override fun resolve(transitive: Boolean): Set<File> =
resolveProject() resolve()
?.let {
it
.npmProject
.nodeJs
.packageManager
.resolveDependency(
it,
this,
transitive
)
}
?: mutableSetOf()
override fun resolve(): MutableSet<File> { override fun getTargetComponentId(): ComponentIdentifier? = null
val npmPackage = parent?.resolveProject() override fun resolve(): MutableSet<File> = mutableSetOf()
?: resolveProject()
?: return mutableSetOf()
return mutableSetOf(npmPackage.npmProject.resolve(key)!!)
}
override fun resolve(context: DependencyResolveContext) { override fun getFiles(): FileCollection = objectFactory.fileCollection()
val npmPackage = resolveProject()
if (npmPackage != null) {
npmPackage.project.files(npmPackage.npmProject.resolve(key))
dependencies.forEach {
context.add(it)
}
}
}
// may return null only during npm resolution
// (it can be called since NpmDependency added to configuration that
// requires resolve to build package.json, in this case we should just skip this call)
private fun resolveProject(): KotlinCompilationNpmResolution? {
val nodeJs = NodeJsRootPlugin.apply(project!!.rootProject)
return nodeJs.npmResolutionManager.getNpmDependencyResolvedCompilation(this)
}
val key: String = name
override fun toString() = "$key: $version"
override fun getFiles(): FileCollection = project!!.files(resolve(true))
override fun getName() = name
override fun getVersion() = version
override fun getBuildDependencies(): TaskDependency = TaskDependency { mutableSetOf() } override fun getBuildDependencies(): TaskDependency = TaskDependency { mutableSetOf() }
override fun contentEquals(dependency: Dependency) = this == dependency override fun contentEquals(dependency: Dependency) = this == dependency
override fun getTargetComponentId() = DefaultLibraryBinaryIdentifier(project!!.path, key, "npm")
override fun copy(): Dependency = this.copy(name = name) override fun copy(): Dependency = this.copy(name = name)
private var reason: String? = null
override fun because(reason: String?) { override fun because(reason: String?) {
this.reason = reason this.reason = reason
} }
@@ -113,20 +65,20 @@ data class NpmDependency(
} }
internal fun directoryNpmDependency( internal fun directoryNpmDependency(
project: Project, objectFactory: ObjectFactory,
scope: NpmDependency.Scope,
name: String, name: String,
directory: File, directory: File,
scope: NpmDependency.Scope,
): NpmDependency { ): NpmDependency {
check(directory.isDirectory) { check(directory.isDirectory) {
"Dependency on local path should point on directory but $directory found" "Dependency on local path should point on directory but $directory found"
} }
return NpmDependency( return NpmDependency(
project = project, objectFactory = objectFactory,
scope = scope,
name = name, name = name,
version = fileVersion(directory), version = fileVersion(directory),
scope = scope,
) )
} }
@@ -118,7 +118,7 @@ private abstract class NpmDependencyExtensionDelegate(
version: String, version: String,
): NpmDependency = ): NpmDependency =
NpmDependency( NpmDependency(
project = project, objectFactory = project.objects,
name = name, name = name,
version = version, version = version,
scope = scope, scope = scope,
@@ -289,10 +289,10 @@ private fun defaultNpmDependencyDelegate(
directory: File, directory: File,
): NpmDependency = ): NpmDependency =
directoryNpmDependency( directoryNpmDependency(
project = project, objectFactory = project.objects,
scope = scope,
name = name, name = name,
directory = directory, directory = directory,
scope = scope,
) )
override fun processNonStringFirstArgument(arg: Any?, vararg args: Any?): NpmDependency { override fun processNonStringFirstArgument(arg: Any?, vararg args: Any?): NpmDependency {
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
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.npm.tasks.KotlinPackageJsonTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import java.io.File import java.io.File
@@ -37,7 +38,7 @@ open class NpmProject(@Transient val compilation: KotlinJsCompilation) : Seriali
} }
@Transient @Transient
val nodeJs = NodeJsRootPlugin.apply(project.rootProject) val nodeJs = project.rootProject.kotlinNodeJsExtension
val dir: File by lazy { val dir: File by lazy {
nodeJs.projectPackagesDir.resolve(name) nodeJs.projectPackagesDir.resolve(name)
@@ -7,13 +7,34 @@ package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
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.npm.resolver.implementing
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
class NpmResolverPlugin : Plugin<Project> { class NpmResolverPlugin : Plugin<Project> {
override fun apply(target: Project) { override fun apply(project: Project) {
val project = target
val nodeJs = NodeJsRootPlugin.apply(project.rootProject) val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
nodeJs.npmResolutionManager.requireConfiguringState().addProject(project) project.rootProject.kotlinNodeJsExtension.resolver.addProject(project)
val kotlinNodeJsTaskProvidersExtension = project.rootProject.kotlinNodeJsExtension
project.whenEvaluated {
project.tasks.implementing(RequiresNpmDependencies::class)
.configureEach { task ->
if (task.enabled) {
task as RequiresNpmDependencies
// KotlinJsTest delegates npm dependencies to testFramework,
// which can be defined after this configure action
if (task !is KotlinJsTest) {
nodeJs.taskRequirements.addTaskRequirements(task)
}
task.dependsOn(
kotlinNodeJsTaskProvidersExtension.npmInstallTaskProvider,
kotlinNodeJsTaskProvidersExtension.storeYarnLockTaskProvider,
)
}
}
}
} }
companion object { companion object {
@@ -6,33 +6,40 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON
import org.jetbrains.kotlin.gradle.utils.property import org.jetbrains.kotlin.gradle.utils.property
import java.io.File import java.io.File
import javax.inject.Inject
open class PublicPackageJsonTask abstract class PublicPackageJsonTask :
@Inject DefaultTask(),
constructor( UsesKotlinNpmResolutionManager {
@Transient
private val compilation: KotlinJsCompilation
) : DefaultTask() {
private val npmProject = compilation.npmProject
@Transient @get:Internal
private val nodeJs = npmProject.nodeJs abstract val compilationDisambiguatedName: Property<String>
private val resolutionManager = nodeJs.npmResolutionManager
private val compilationName = compilation.disambiguatedName
private val projectPath = project.path private val projectPath = project.path
private val packageJsonHandlers = compilation.packageJsonHandlers @get:Input
val projectVersion = project.version.toString()
@get:Input
abstract val jsIrCompilation: Property<Boolean>
@get:Input
abstract val npmProjectName: Property<String>
@get:Internal
abstract val npmProjectMain: Property<String>
private val packageJsonHandlers: List<PackageJson.() -> Unit>
get() = npmResolutionManager.get().parameters.packageJsonHandlers.get()
.getValue("$projectPath:${compilationDisambiguatedName.get()}")
@get:Input @get:Input
val packageJsonCustomFields: Map<String, Any?> val packageJsonCustomFields: Map<String, Any?>
@@ -41,47 +48,40 @@ constructor(
packageJsonHandlers.forEach { it() } packageJsonHandlers.forEach { it() }
}.customFields }.customFields
private val compilationResolver
get() = resolutionManager.resolver[projectPath][compilationName]
private val compilationResolution private val compilationResolution
get() = compilationResolver.getResolutionOrResolveIfForced() ?: error("Compilation resolution isn't available") get() = npmResolutionManager.get().resolution.get()[projectPath][compilationDisambiguatedName.get()]
.getResolutionOrResolve(
npmResolutionManager.get(),
logger
)
@get:Nested @get:Input
internal val externalDependencies: Collection<NpmDependencyDeclaration> val externalDependencies: Collection<NpmDependencyDeclaration>
get() = compilationResolution.externalNpmDependencies get() = compilationResolution.externalNpmDependencies
.map {
NpmDependencyDeclaration(
scope = it.scope,
name = it.name,
version = it.version,
)
}
private val publicPackageJsonTaskName by lazy {
npmProject.publicPackageJsonTaskName
}
private val defaultPackageJsonFile by lazy { private val defaultPackageJsonFile by lazy {
project.buildDir project.buildDir
.resolve("tmp") .resolve("tmp")
.resolve(publicPackageJsonTaskName) .resolve(name)
.resolve(PACKAGE_JSON) .resolve(PACKAGE_JSON)
} }
@get:OutputFile @get:OutputFile
var packageJsonFile: File by property { defaultPackageJsonFile } var packageJsonFile: File by property { defaultPackageJsonFile }
private val isJrIrCompilation = compilation is KotlinJsIrCompilation
private val projectVersion = project.version.toString()
@TaskAction @TaskAction
fun resolve() { fun resolve() {
packageJson(npmProject.name, projectVersion, npmProject.main, externalDependencies, packageJsonHandlers).let { packageJson -> packageJson(
packageJson.main = "${npmProject.name}.js" npmProjectName.get(),
projectVersion,
npmProjectMain.get(),
externalDependencies,
packageJsonHandlers
).let { packageJson ->
packageJson.main = "${npmProjectName}.js"
if (isJrIrCompilation) { if (jsIrCompilation.get()) {
packageJson.types = "${npmProject.name}.d.ts" packageJson.types = "${npmProjectName}.d.ts"
} }
packageJson.apply { packageJson.apply {
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
interface RequiresNpmDependencies { interface RequiresNpmDependencies {
val compilation: KotlinJsCompilation val compilation: KotlinJsCompilation
val nodeModulesRequired: Boolean
val requiredNpmDependencies: Set<RequiredKotlinJsDependency> val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
@Internal @Internal
@@ -1,37 +0,0 @@
/*
* Copyright 2010-2020 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.npm.resolved
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.*
import java.io.Serializable
/**
* Resolved [NpmProject]
*/
class KotlinCompilationNpmResolution(
@Transient
private val _project: Project?,
val npmProject: NpmProject,
val internalCompositeDependencies: Collection<GradleNodeModule>,
val externalGradleDependencies: Collection<GradleNodeModule>,
private val _externalNpmDependencies: Collection<NpmDependencyDeclaration>,
val packageJson: PackageJson
) : Serializable {
val project
get() = _project!!
val externalNpmDependencies
get() = _externalNpmDependencies
.map {
NpmDependency(
project = _project,
name = it.name,
version = it.version,
scope = it.scope,
)
}
}
@@ -5,35 +5,20 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolved package org.jetbrains.kotlin.gradle.targets.js.npm.resolved
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import java.io.Serializable
/**
* Info about NPM projects inside particular gradle [project].
*/
class KotlinProjectNpmResolution( class KotlinProjectNpmResolution(
val project: String, val byCompilation: Map<String, KotlinCompilationNpmResolution>,
val npmProjects: List<KotlinCompilationNpmResolution>, ) : Serializable {
val taskRequirements: Map<String, Collection<RequiredKotlinJsDependency>> val npmProjects: Collection<KotlinCompilationNpmResolution>
) { get() = byCompilation.values
val npmProjectsByNpmDependency: Map<NpmDependency, KotlinCompilationNpmResolution> by lazy {
mutableMapOf<NpmDependency, KotlinCompilationNpmResolution>().also { result ->
npmProjects.forEach { npmPackage ->
npmPackage.externalNpmDependencies.forEach { npmDependency ->
result[npmDependency] = npmPackage
}
}
}
}
val byCompilation by lazy { npmProjects.associateBy { it.npmProject.compilationName } }
operator fun get(compilationName: String): KotlinCompilationNpmResolution { operator fun get(compilationName: String): KotlinCompilationNpmResolution {
// check(compilation.target.project.path == project)
return byCompilation.getValue(compilationName) return byCompilation.getValue(compilationName)
} }
companion object { companion object {
fun empty(project: String) = KotlinProjectNpmResolution(project, listOf(), mapOf()) fun empty() = KotlinProjectNpmResolution(emptyMap())
} }
} }
@@ -5,11 +5,69 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolved package org.jetbrains.kotlin.gradle.targets.js.npm.resolved
import org.gradle.api.Project import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment
import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment
import org.jetbrains.kotlin.gradle.targets.js.yarn.toVersionString
import java.io.Serializable
internal class KotlinRootNpmResolution( class KotlinRootNpmResolution(
val rootProject: Project?, val projects: Map<String, KotlinProjectNpmResolution>,
internal val projects: Map<String, KotlinProjectNpmResolution> val rootProjectName: String,
) { val rootProjectVersion: String
operator fun get(project: String) = projects[project] ?: KotlinProjectNpmResolution.empty(project) ) : Serializable {
operator fun get(project: String) = projects[project] ?: KotlinProjectNpmResolution.empty()
/**
* Don't use directly, use [KotlinNpmResolutionManager.installIfNeeded] instead.
*/
internal fun prepareInstallation(
logger: Logger,
npmEnvironment: NpmEnvironment,
yarnEnvironment: YarnEnvironment,
npmResolutionManager: KotlinNpmResolutionManager
): Installation {
synchronized(projects) {
npmResolutionManager.parameters.gradleNodeModulesProvider.get().close()
npmResolutionManager.parameters.compositeNodeModulesProvider.get().close()
val projectResolutions: List<PreparedKotlinCompilationNpmResolution> = projects.values.flatMap { it.npmProjects }.map { it.close(npmResolutionManager, logger) }
npmEnvironment.packageManager.prepareRootProject(
npmEnvironment,
rootProjectName,
rootProjectVersion,
logger,
projectResolutions,
yarnEnvironment.yarnResolutions
.associate { it.path to it.toVersionString() },
)
return Installation(
projectResolutions
)
}
}
}
class Installation(val compilationResolutions: Collection<PreparedKotlinCompilationNpmResolution>) {
internal fun install(
args: List<String>,
services: ServiceRegistry,
logger: Logger,
npmEnvironment: NpmEnvironment,
yarnEnvironment: YarnEnvironment,
) {
synchronized(compilationResolutions) {
npmEnvironment.packageManager.resolveRootProject(
services,
logger,
npmEnvironment,
yarnEnvironment,
compilationResolutions,
args
)
}
}
} }
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2020 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.npm.resolved
import org.jetbrains.kotlin.gradle.targets.js.npm.*
import java.io.File
import java.io.Serializable
/**
* Resolved [NpmProject]
*/
class PreparedKotlinCompilationNpmResolution(
val npmProjectDir: File,
val internalCompositeDependencies: Collection<GradleNodeModule>,
val externalGradleDependencies: Collection<GradleNodeModule>,
val externalNpmDependencies: Collection<NpmDependencyDeclaration>,
) : Serializable
@@ -0,0 +1,42 @@
/*
* 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.npm.resolver
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.initialization.IncludedBuild
import java.io.File
import java.io.Serializable
data class ExternalGradleDependency(
val dependency: ResolvedDependency,
val artifact: ResolvedArtifact
) : Serializable
data class FileCollectionExternalGradleDependency(
val files: Collection<File>,
val dependencyVersion: String?
) : Serializable
data class FileExternalGradleDependency(
val dependencyName: String,
val dependencyVersion: String,
val file: File
) : Serializable
data class CompositeDependency(
val dependencyName: String,
val dependencyVersion: String,
val includedBuildDir: File,
@Transient
val includedBuild: IncludedBuild?
) : Serializable
data class InternalDependency(
val projectPath: String,
val compilationName: String,
val projectName: String
) : Serializable
@@ -0,0 +1,201 @@
/*
* 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.npm.resolver
import org.gradle.api.logging.Logger
import org.jetbrains.kotlin.gradle.targets.js.nodejs.TasksRequirements
import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution
import java.io.Serializable
import java.io.File
class KotlinCompilationNpmResolution(
var internalDependencies: Collection<InternalDependency>,
var internalCompositeDependencies: Collection<CompositeDependency>,
var externalGradleDependencies: Collection<FileExternalGradleDependency>,
var externalNpmDependencies: Collection<NpmDependencyDeclaration>,
var fileCollectionDependencies: Collection<FileCollectionExternalGradleDependency>,
val projectPath: String,
val projectPackagesDir: File,
val rootDir: File,
val compilationDisambiguatedName: String,
val npmProjectName: String,
val npmProjectVersion: String,
val npmProjectMain: String,
val npmProjectPackageJsonFile: File,
val npmProjectDir: File,
val tasksRequirements: TasksRequirements
) : Serializable {
val inputs: PackageJsonProducerInputs
get() = PackageJsonProducerInputs(
internalDependencies.map { it.projectName },
internalCompositeDependencies.flatMap { it.getPackages() },
externalGradleDependencies.map { it.file },
externalNpmDependencies.map { it.uniqueRepresentation() },
fileCollectionDependencies.flatMap { it.files }
)
private var closed = false
private var resolution: PreparedKotlinCompilationNpmResolution? = null
@Synchronized
fun resolve(
skipWriting: Boolean = false,
npmResolutionManager: KotlinNpmResolutionManager,
logger: Logger
): PreparedKotlinCompilationNpmResolution {
check(resolution == null) { "$this already resolved" }
return createPackageJson(
skipWriting,
npmResolutionManager,
logger
).also {
resolution = it
}
}
@Synchronized
fun getResolutionOrResolve(
npmResolutionManager: KotlinNpmResolutionManager,
logger: Logger,
): PreparedKotlinCompilationNpmResolution {
return resolution ?: resolve(
skipWriting = true,
npmResolutionManager,
logger
)
}
@Synchronized
fun close(
npmResolutionManager: KotlinNpmResolutionManager,
logger: Logger,
): PreparedKotlinCompilationNpmResolution {
check(!closed) { "$this already closed" }
closed = true
return getResolutionOrResolve(npmResolutionManager, logger)
}
fun createPackageJson(
skipWriting: Boolean,
npmResolutionManager: KotlinNpmResolutionManager,
logger: Logger
): PreparedKotlinCompilationNpmResolution {
val rootResolver = npmResolutionManager.parameters.resolution.get()
internalDependencies.map {
val compilationNpmResolution: KotlinCompilationNpmResolution = rootResolver[it.projectPath][it.compilationName]
compilationNpmResolution.getResolutionOrResolve(
npmResolutionManager,
logger
)
}
val importedExternalGradleDependencies = externalGradleDependencies.mapNotNull {
npmResolutionManager.parameters.gradleNodeModulesProvider.get().get(it.dependencyName, it.dependencyVersion, it.file)
} + fileCollectionDependencies.flatMap { dependency ->
dependency.files
// Gradle can hash with FileHasher only files and only existed files
.filter { it.isFile }
.map { file ->
npmResolutionManager.parameters.gradleNodeModulesProvider.get().get(
file.name,
dependency.dependencyVersion ?: "0.0.1",
file
)
}
}.filterNotNull()
val transitiveNpmDependencies = importedExternalGradleDependencies.flatMap {
it.dependencies
}.filter { it.scope != NpmDependency.Scope.DEV }
val compositeDependencies = internalCompositeDependencies.flatMap { dependency ->
dependency.getPackages()
.map { file ->
npmResolutionManager.parameters.compositeNodeModulesProvider.get().get(
dependency.dependencyName,
dependency.dependencyVersion,
file
)
}
}.filterNotNull()
val toolsNpmDependencies = tasksRequirements
.getCompilationNpmRequirements(projectPath, compilationDisambiguatedName)
val otherNpmDependencies = toolsNpmDependencies + transitiveNpmDependencies
val allNpmDependencies = disambiguateDependencies(externalNpmDependencies, otherNpmDependencies, logger)
val packageJsonHandlers =
npmResolutionManager.parameters.packageJsonHandlers.get()["$projectPath:${compilationDisambiguatedName}"]
?: emptyList()
val packageJson = packageJson(
npmProjectName,
npmProjectVersion,
npmProjectMain,
allNpmDependencies,
packageJsonHandlers
)
compositeDependencies.forEach {
packageJson.dependencies[it.name] = it.version
}
packageJsonHandlers.forEach {
it(packageJson)
}
if (!skipWriting) {
packageJson.saveTo(npmProjectPackageJsonFile)
}
return PreparedKotlinCompilationNpmResolution(
npmProjectDir,
compositeDependencies,
importedExternalGradleDependencies,
allNpmDependencies,
)
}
private fun disambiguateDependencies(
direct: Collection<NpmDependencyDeclaration>,
others: Collection<NpmDependencyDeclaration>,
logger: Logger,
): Collection<NpmDependencyDeclaration> {
val unique = others.groupBy(NpmDependencyDeclaration::name)
.filterKeys { k -> direct.none { it.name == k } }
.mapNotNull { (name, dependencies) ->
dependencies.maxByOrNull { dep ->
SemVer.from(dep.version, true)
}?.also { selected ->
if (dependencies.size > 1) {
logger.warn(
"""
Transitive npm dependency version clash for compilation "${compilationDisambiguatedName}"
Candidates:
${dependencies.joinToString("\n") { "\t\t" + it.name + "@" + it.version }}
Selected:
${selected.name}@${selected.version}
""".trimIndent()
)
}
}
}
return direct + unique
}
internal fun CompositeDependency.getPackages(): List<File> {
val packages = includedBuildDir.resolve(projectPackagesDir.relativeTo(rootDir))
return packages
.list()
?.map { packages.resolve(it) }
?.map { it.resolve(PACKAGE_JSON) }
?: emptyList()
}
}
@@ -12,12 +12,9 @@ import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Category import org.gradle.api.attributes.Category
import org.gradle.api.attributes.Usage import org.gradle.api.attributes.Usage
import org.gradle.api.initialization.IncludedBuild
import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier
import org.gradle.api.tasks.* import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip import org.gradle.api.tasks.bundling.Zip
import org.gradle.configurationcache.extensions.serviceOf
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.categoryByName import org.jetbrains.kotlin.gradle.plugin.categoryByName
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
@@ -28,29 +25,24 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.compilationDependencyConfigurationByScope import org.jetbrains.kotlin.gradle.plugin.sources.compilationDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.* import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.CompositeProjectComponentArtifactMetadata import org.jetbrains.kotlin.gradle.utils.CompositeProjectComponentArtifactMetadata
import org.jetbrains.kotlin.gradle.utils.`is` import org.jetbrains.kotlin.gradle.utils.`is`
import org.jetbrains.kotlin.gradle.utils.topRealPath import org.jetbrains.kotlin.gradle.utils.topRealPath
import org.jetbrains.kotlin.gradle.utils.unavailableValueError
import java.io.File
import java.io.Serializable import java.io.Serializable
/** /**
* See [KotlinNpmResolutionManager] for details about resolution process. * See [KotlinNpmResolutionManager] for details about resolution process.
*/ */
internal class KotlinCompilationNpmResolver( class KotlinCompilationNpmResolver(
@Transient
val projectResolver: KotlinProjectNpmResolver, val projectResolver: KotlinProjectNpmResolver,
@Transient
val compilation: KotlinJsCompilation val compilation: KotlinJsCompilation
) : Serializable { ) : Serializable {
@Transient
var rootResolver = projectResolver.resolver var rootResolver = projectResolver.resolver
val npmProject = compilation.npmProject val npmProject = compilation.npmProject
@@ -61,26 +53,32 @@ internal class KotlinCompilationNpmResolver(
project.version.toString() project.version.toString()
} }
val nodeJs get() = rootResolver.nodeJs
private val nodeJs_ get() = nodeJs ?: unavailableValueError("nodeJs")
val target get() = compilation.target val target get() = compilation.target
val project get() = target.project val project get() = target.project
val projectPath = project.path val projectPath: String = project.path
@Transient val packageJsonTaskHolder: TaskProvider<KotlinPackageJsonTask> =
val packageJsonTaskHolder: TaskProvider<KotlinPackageJsonTask>? =
KotlinPackageJsonTask.create(compilation) KotlinPackageJsonTask.create(compilation)
@Transient val publicPackageJsonTaskHolder: TaskProvider<PublicPackageJsonTask> = run {
val publicPackageJsonTaskHolder: TaskProvider<PublicPackageJsonTask> = val npmResolutionManager = project.kotlinNpmResolutionManager
project.registerTask<PublicPackageJsonTask>( project.registerTask<PublicPackageJsonTask>(
npmProject.publicPackageJsonTaskName, npmProject.publicPackageJsonTaskName
listOf(compilation)
) { ) {
it.dependsOn(packageJsonTaskHolder) it.dependsOn(packageJsonTaskHolder)
it.compilationDisambiguatedName.set(compilation.disambiguatedName)
it.npmResolutionManager.apply {
set(npmResolutionManager)
disallowChanges()
}
it.jsIrCompilation.set(compilation is KotlinJsIrCompilation)
it.npmProjectName.set(npmProject.name)
it.npmProjectMain.set(npmProject.main)
}.also { packageJsonTask -> }.also { packageJsonTask ->
if (compilation.isMain()) { if (compilation.isMain()) {
project.tasks project.tasks
@@ -91,66 +89,29 @@ internal class KotlinCompilationNpmResolver(
} }
} }
} }
}
override fun toString(): String = "KotlinCompilationNpmResolver(${npmProject.name})" override fun toString(): String = "KotlinCompilationNpmResolver(${npmProject.name})"
@delegate:Transient private var _compilationNpmResolution: KotlinCompilationNpmResolution? = null
private val aggregatedConfiguration: Configuration by lazy {
createAggregatedConfiguration()
}
private val packageJsonProducer_: PackageJsonProducer by lazy { val compilationNpmResolution: KotlinCompilationNpmResolution
val visitor = ConfigurationVisitor()
visitor.visit(aggregatedConfiguration)
visitor.toPackageJsonProducer().also { it.compilationResolver = this }
}
val packageJsonProducer: PackageJsonProducer
get() { get() {
val packageJsonProducer = packageJsonProducer_ return _compilationNpmResolution ?: run {
packageJsonProducer.compilationResolver = this val visitor = ConfigurationVisitor()
return packageJsonProducer visitor.visit(createAggregatedConfiguration())
visitor.toPackageJsonProducer()
}.also {
_compilationNpmResolution = it
}
} }
private var closed = false
private var resolution: KotlinCompilationNpmResolution? = null
@Synchronized
fun resolve(skipWriting: Boolean = false): KotlinCompilationNpmResolution {
check(!closed) { "$this already closed" }
check(resolution == null) { "$this already resolved" }
return packageJsonProducer.createPackageJson(skipWriting).also {
resolution = it
}
}
@Synchronized
fun getResolutionOrResolveIfForced(): KotlinCompilationNpmResolution? {
if (resolution != null) return resolution
if (rootResolver.mayBeUpToDateTasksRegistry.get().shouldResolveNpmDependenciesFor(npmProject.packageJsonTaskPath)) {
// when we need to resolve the compilation but the task is UP-TO-DATE, so we don't have ready resolution yet
return resolve(skipWriting = true)
}
if (rootResolver.forceFullResolve) {
// need to force all NPM tasks to be configured in IDEA import
rootResolver.gradleNodeModules.fileHasher = project.serviceOf()
rootResolver.compositeNodeModules.fileHasher = project.serviceOf()
project.tasks.implementing(RequiresNpmDependencies::class).all {}
return resolve()
}
return null // we don't need to resolve NPM dependencies for the compilation
}
@Synchronized @Synchronized
fun close(): KotlinCompilationNpmResolution? { fun close(): KotlinCompilationNpmResolution? {
check(!closed) { "$this already closed" } return _compilationNpmResolution
val resolution = getResolutionOrResolveIfForced()
closed = true
return resolution
} }
private fun createAggregatedConfiguration(): Configuration { fun createAggregatedConfiguration(): Configuration {
val all = project.configurations.create(compilation.disambiguateName("npm")) val all = project.configurations.create(compilation.disambiguateName("npm"))
all.usesPlatformOf(target) all.usesPlatformOf(target)
@@ -174,46 +135,16 @@ internal class KotlinCompilationNpmResolver(
} }
// We don't have `kotlin-js-test-runner` in NPM yet // We don't have `kotlin-js-test-runner` in NPM yet
all.dependencies.add(nodeJs_.versions.kotlinJsTestRunner.createDependency(project)) all.dependencies.add(rootResolver.versions.kotlinJsTestRunner.createDependency(project))
return all return all
} }
data class ExternalGradleDependency(
val dependency: ResolvedDependency,
val artifact: ResolvedArtifact
) : Serializable
data class FileCollectionExternalGradleDependency(
val files: Collection<File>,
val dependencyVersion: String?
) : Serializable
data class FileExternalGradleDependency(
val dependencyName: String,
val dependencyVersion: String,
val file: File
) : Serializable
data class CompositeDependency(
val dependencyName: String,
val dependencyVersion: String,
val includedBuildDir: File,
@Transient
val includedBuild: IncludedBuild?
) : Serializable
data class InternalDependency(
val projectPath: String,
val compilationName: String,
val projectName: String
) : Serializable
inner class ConfigurationVisitor { inner class ConfigurationVisitor {
private val internalDependencies = mutableSetOf<InternalDependency>() private val internalDependencies = mutableSetOf<InternalDependency>()
private val internalCompositeDependencies = mutableSetOf<CompositeDependency>() private val internalCompositeDependencies = mutableSetOf<CompositeDependency>()
private val externalGradleDependencies = mutableSetOf<ExternalGradleDependency>() private val externalGradleDependencies = mutableSetOf<ExternalGradleDependency>()
private val externalNpmDependencies = mutableSetOf<NpmDependency>() private val externalNpmDependencies = mutableSetOf<NpmDependencyDeclaration>()
private val fileCollectionDependencies = mutableSetOf<FileCollectionExternalGradleDependency>() private val fileCollectionDependencies = mutableSetOf<FileCollectionExternalGradleDependency>()
private val visitedDependencies = mutableSetOf<ResolvedDependency>() private val visitedDependencies = mutableSetOf<ResolvedDependency>()
@@ -225,7 +156,7 @@ internal class KotlinCompilationNpmResolver(
configuration.allDependencies.forEach { dependency -> configuration.allDependencies.forEach { dependency ->
when (dependency) { when (dependency) {
is NpmDependency -> externalNpmDependencies.add(dependency) is NpmDependency -> externalNpmDependencies.add(dependency.toDeclaration())
is FileCollectionDependency -> fileCollectionDependencies.add( is FileCollectionDependency -> fileCollectionDependencies.add(
FileCollectionExternalGradleDependency( FileCollectionExternalGradleDependency(
dependency.files.files, dependency.files.files,
@@ -240,7 +171,7 @@ internal class KotlinCompilationNpmResolver(
val main = compilation.target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME) as KotlinJsCompilation val main = compilation.target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME) as KotlinJsCompilation
internalDependencies.add( internalDependencies.add(
InternalDependency( InternalDependency(
projectResolver.project.path, projectResolver.projectPath,
main.disambiguatedName, main.disambiguatedName,
projectResolver[main].npmProject.name projectResolver[main].npmProject.name
) )
@@ -336,7 +267,7 @@ internal class KotlinCompilationNpmResolver(
} }
} }
fun toPackageJsonProducer() = PackageJsonProducer( fun toPackageJsonProducer() = KotlinCompilationNpmResolution(
internalDependencies, internalDependencies,
internalCompositeDependencies, internalCompositeDependencies,
externalGradleDependencies.map { externalGradleDependencies.map {
@@ -346,169 +277,18 @@ internal class KotlinCompilationNpmResolver(
it.artifact.file it.artifact.file
) )
}, },
externalNpmDependencies.map { it.toDeclaration() }, externalNpmDependencies,
fileCollectionDependencies, fileCollectionDependencies,
projectPath projectPath,
rootResolver.projectPackagesDir,
rootResolver.rootProjectDir,
compilationDisambiguatedName,
npmProject.name,
npmVersion,
npmProject.main,
npmProject.packageJsonFile,
npmProject.dir,
rootResolver.tasksRequirements
) )
} }
@Suppress("unused")
class PackageJsonProducerInputs(
@get:Input
val internalDependencies: Collection<String>,
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:InputFiles
val internalCompositeDependencies: Collection<File>,
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:InputFiles
val externalGradleDependencies: Collection<File>,
@get:Input
val externalDependencies: Collection<String>,
@get:Input
val fileCollectionDependencies: Collection<File>
)
@Suppress("MemberVisibilityCanBePrivate")
class PackageJsonProducer(
var internalDependencies: Collection<InternalDependency>,
var internalCompositeDependencies: Collection<CompositeDependency>,
var externalGradleDependencies: Collection<FileExternalGradleDependency>,
var externalNpmDependencies: Collection<NpmDependencyDeclaration>,
var fileCollectionDependencies: Collection<FileCollectionExternalGradleDependency>,
val projectPath: String
) : Serializable {
private val projectPackagesDir by lazy { compilationResolver.nodeJs_.projectPackagesDir }
private val rootDir by lazy { compilationResolver.nodeJs_.rootProject.rootDir }
@Transient
internal lateinit var compilationResolver: KotlinCompilationNpmResolver
val inputs: PackageJsonProducerInputs
get() = PackageJsonProducerInputs(
internalDependencies.map { it.projectName },
internalCompositeDependencies.flatMap { it.getPackages() },
externalGradleDependencies.map { it.file },
externalNpmDependencies.map { it.uniqueRepresentation() },
fileCollectionDependencies.flatMap { it.files }
)
fun createPackageJson(skipWriting: Boolean): KotlinCompilationNpmResolution {
internalDependencies.map {
compilationResolver.rootResolver[it.projectPath][it.compilationName].getResolutionOrResolveIfForced()
?: error("Unresolved dependent npm package: ${compilationResolver} -> $it")
}
val importedExternalGradleDependencies = externalGradleDependencies.mapNotNull {
compilationResolver.rootResolver.gradleNodeModules.get(it.dependencyName, it.dependencyVersion, it.file)
} + fileCollectionDependencies.flatMap { dependency ->
dependency.files
// Gradle can hash with FileHasher only files and only existed files
.filter { it.isFile }
.map { file ->
compilationResolver.rootResolver.gradleNodeModules.get(
file.name,
dependency.dependencyVersion ?: "0.0.1",
file
)
}
}.filterNotNull()
val transitiveNpmDependencies = importedExternalGradleDependencies.flatMap {
it.dependencies
}.filter { it.scope != NpmDependency.Scope.DEV }
val compositeDependencies = internalCompositeDependencies.flatMap { dependency ->
dependency.getPackages()
.map { file ->
compilationResolver.rootResolver.compositeNodeModules.get(
dependency.dependencyName,
dependency.dependencyVersion,
file
)
}
}.filterNotNull()
val toolsNpmDependencies = compilationResolver.rootResolver.taskRequirements
.getCompilationNpmRequirements(projectPath, compilationResolver.compilationDisambiguatedName)
val otherNpmDependencies = toolsNpmDependencies + transitiveNpmDependencies
val allNpmDependencies = disambiguateDependencies(externalNpmDependencies, otherNpmDependencies)
val packageJsonHandlers = if (compilationResolver.compilation != null) {
compilationResolver.compilation.packageJsonHandlers
} else {
compilationResolver.rootResolver.getPackageJsonHandlers(projectPath, compilationResolver.compilationDisambiguatedName)
}
val packageJson = packageJson(
compilationResolver.npmProject.name,
compilationResolver.npmVersion,
compilationResolver.npmProject.main,
allNpmDependencies,
packageJsonHandlers
)
compositeDependencies.forEach {
packageJson.dependencies[it.name] = it.version
}
packageJsonHandlers.forEach {
it(packageJson)
}
if (!skipWriting) {
packageJson.saveTo(compilationResolver.npmProject.packageJsonFile)
}
return KotlinCompilationNpmResolution(
@Suppress("SENSELESS_COMPARISON")
if (compilationResolver.compilation != null) compilationResolver.project else null,
compilationResolver.npmProject,
compositeDependencies,
importedExternalGradleDependencies,
allNpmDependencies,
packageJson
)
}
private fun disambiguateDependencies(
direct: Collection<NpmDependencyDeclaration>,
others: Collection<NpmDependencyDeclaration>,
): Collection<NpmDependencyDeclaration> {
val unique = others.groupBy(NpmDependencyDeclaration::name)
.filterKeys { k -> direct.none { it.name == k } }
.mapNotNull { (name, dependencies) ->
dependencies.maxByOrNull { dep ->
SemVer.from(dep.version, true)
}?.also { selected ->
if (dependencies.size > 1) {
compilationResolver.project.logger.warn(
"""
Transitive npm dependency version clash for compilation "${compilationResolver.compilation.name}"
Candidates:
${dependencies.joinToString("\n") { "\t\t" + it.name + "@" + it.version }}
Selected:
${selected.name}@${selected.version}
""".trimIndent()
)
}
}
}
return direct + unique
}
private fun CompositeDependency.getPackages(): List<File> {
val packages = includedBuildDir.resolve(projectPackagesDir.relativeTo(rootDir))
return packages
.list()
?.map { packages.resolve(it) }
?.map { it.resolve(PACKAGE_JSON) }
?: emptyList()
}
}
} }
@@ -14,32 +14,24 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.utils.unavailableValueError
import java.io.Serializable import java.io.Serializable
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
* See [KotlinNpmResolutionManager] for details about resolution process. * See [KotlinNpmResolutionManager] for details about resolution process.
*/ */
internal class KotlinProjectNpmResolver( class KotlinProjectNpmResolver(
@Transient project: Project,
val project: Project,
@Transient
var resolver: KotlinRootNpmResolver var resolver: KotlinRootNpmResolver
) : Serializable { ) : Serializable {
override fun toString(): String = "ProjectNpmResolver($project)" val projectPath by lazy { project.path }
private val projectPath by lazy { project.path }
private val byCompilation = mutableMapOf<String, KotlinCompilationNpmResolver>() private val byCompilation = mutableMapOf<String, KotlinCompilationNpmResolver>()
operator fun get(compilation: KotlinJsCompilation): KotlinCompilationNpmResolver { operator fun get(compilation: KotlinJsCompilation): KotlinCompilationNpmResolver {
check(compilation.target.project == project)
return byCompilation[compilation.disambiguatedName] ?: error("$compilation was not registered in $this") return byCompilation[compilation.disambiguatedName] ?: error("$compilation was not registered in $this")
} }
@@ -47,38 +39,17 @@ internal class KotlinProjectNpmResolver(
return byCompilation[compilationName] ?: error("$compilationName was not registered in $this") return byCompilation[compilationName] ?: error("$compilationName was not registered in $this")
} }
private var closed = false private var resolution: KotlinProjectNpmResolution? = null
val compilationResolvers: Collection<KotlinCompilationNpmResolver> val compilationResolvers: Collection<KotlinCompilationNpmResolver>
get() = byCompilation.values get() = byCompilation.values
init { init {
addContainerListeners() project.addContainerListeners()
project.whenEvaluated {
val nodeJs = resolver.nodeJs ?: unavailableValueError("resolver.nodeJs")
project.tasks.implementing(RequiresNpmDependencies::class)
.configureEach { task ->
if (task.enabled) {
task as RequiresNpmDependencies
// KotlinJsTest delegates npm dependencies to testFramework,
// which can be defined after this configure action
val packageJsonTaskHolder = get(task.compilation).packageJsonTaskHolder
if (task !is KotlinJsTest) {
nodeJs.taskRequirements.addTaskRequirements(task)
}
task.dependsOn(packageJsonTaskHolder)
task.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
)
}
}
}
} }
private fun addContainerListeners() { private fun Project.addContainerListeners() {
val kotlin = project.kotlinExtensionOrNull val kotlin = kotlinExtensionOrNull
?: error("NpmResolverPlugin should be applied after kotlin plugin") ?: error("NpmResolverPlugin should be applied after kotlin plugin")
when (kotlin) { when (kotlin) {
@@ -91,10 +62,11 @@ internal class KotlinProjectNpmResolver(
} }
private fun addTargetListeners(target: KotlinTarget) { private fun addTargetListeners(target: KotlinTarget) {
check(!closed) { resolver.alreadyResolvedMessage("add target $target") } check(resolution == null) { resolver.alreadyResolvedMessage("add target $target") }
if (target.platformType == KotlinPlatformType.js || if (target.platformType == KotlinPlatformType.js ||
target.platformType == KotlinPlatformType.wasm) { target.platformType == KotlinPlatformType.wasm
) {
target.compilations.all { compilation -> target.compilations.all { compilation ->
if (compilation is KotlinJsCompilation) { if (compilation is KotlinJsCompilation) {
// compilation may be KotlinWithJavaTarget for old Kotlin2JsPlugin // compilation may be KotlinWithJavaTarget for old Kotlin2JsPlugin
@@ -115,19 +87,23 @@ internal class KotlinProjectNpmResolver(
@Synchronized @Synchronized
private fun addCompilation(compilation: KotlinJsCompilation) { private fun addCompilation(compilation: KotlinJsCompilation) {
check(!closed) { resolver.alreadyResolvedMessage("add compilation $compilation") } check(resolution == null) { resolver.alreadyResolvedMessage("add compilation $compilation") }
byCompilation[compilation.disambiguatedName] = KotlinCompilationNpmResolver(this, compilation) byCompilation[compilation.disambiguatedName] =
KotlinCompilationNpmResolver(
this,
compilation
)
} }
fun close(): KotlinProjectNpmResolution { fun close(): KotlinProjectNpmResolution {
check(!closed) return resolution ?: KotlinProjectNpmResolution(
closed = true byCompilation
.map { (key, value) ->
return KotlinProjectNpmResolution( value.close()?.let { key to it }
projectPath, }
byCompilation.values.mapNotNull { it.close() }, .filterNotNull()
resolver.taskRequirements.byTask .toMap(),
) )
} }
} }
@@ -6,204 +6,45 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolver package org.jetbrains.kotlin.gradle.targets.js.npm.resolver
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.targets.js.NpmVersions
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.TasksRequirements import org.jetbrains.kotlin.gradle.targets.js.nodejs.TasksRequirements
import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmCachesSetup import java.io.File
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask import java.io.Serializable
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnEnv
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnResolution
import org.jetbrains.kotlin.gradle.targets.js.yarn.toVersionString
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.gradle.utils.FileSystemOperationsCompat
import org.jetbrains.kotlin.gradle.utils.unavailableValueError
/**
* See [KotlinNpmResolutionManager] for details about resolution process.
*
* This class contains many transient properties to deduplicate data when configuration cache is used.
* Regularly tasks share the same instance of this class, but with configuration cache each task that holds a reference to the instance will
* create an own copy. We use build services as a single storage for the heavy state of this class.
*/
class KotlinRootNpmResolver internal constructor( class KotlinRootNpmResolver internal constructor(
@Transient val rootProjectName: String,
val nodeJs: NodeJsRootExtension?, val rootProjectVersion: String,
val forceFullResolve: Boolean val tasksRequirements: TasksRequirements,
) { val versions: NpmVersions,
private val nodeJs_ val projectPackagesDir: File,
get() = nodeJs ?: unavailableValueError("nodeJs") val rootProjectDir: File,
) : Serializable {
private val rootProject: Project? internal var resolution: KotlinRootNpmResolution? = null
get() = nodeJs?.rootProject
private val rootProject_: Project val projectResolvers: MutableMap<String, KotlinProjectNpmResolver> = mutableMapOf()
get() = rootProject ?: unavailableValueError("rootProject")
private val rootProjectName by lazy {
rootProject_.name
}
private val rootProjectVersion by lazy {
rootProject_.version.toString()
}
@Transient
@Volatile
private var state_: RootResolverState? = RootResolverState.CONFIGURING
var state
get() = state_ ?: resolverStateHolder.get().state
set(value) {
if (state_ != null) {
state_ = value
} else {
resolverStateHolder.get().state = value
}
}
private val archiveOperations by lazy { ArchiveOperationsCompat(rootProject_) }
private val fs by lazy { FileSystemOperationsCompat(rootProject_) }
internal val gradleNodeModulesProvider: Provider<GradleNodeModulesCache> =
rootProject_
.gradle.sharedServices.registerIfAbsent("gradle-node-modules", GradleNodeModulesCache::class.java) {
it.parameters.cacheDir.set(nodeJs_.nodeModulesGradleCacheDir)
it.parameters.rootProjectDir.set(rootProject_.projectDir)
}.also {
declareBuildServiceForRootProjectJsTasks(it)
declareBuildServiceForAllProjectsJsTasks(it)
}
internal val gradleNodeModules: GradleNodeModulesCache
get() = gradleNodeModulesProvider.get().also {
it.archiveOperations = archiveOperations
it.fs = fs
}
internal val compositeNodeModulesProvider: Provider<CompositeNodeModulesCache> =
rootProject_
.gradle.sharedServices.registerIfAbsent("composite-node-modules", CompositeNodeModulesCache::class.java) {
it.parameters.cacheDir.set(nodeJs_.nodeModulesGradleCacheDir)
it.parameters.rootProjectDir.set(rootProject_.projectDir)
}.also {
declareBuildServiceForRootProjectJsTasks(it)
declareBuildServiceForAllProjectsJsTasks(it)
}
internal val compositeNodeModules: CompositeNodeModulesCache
get() = compositeNodeModulesProvider.get()
@Transient
private val projectResolvers_: MutableMap<String, KotlinProjectNpmResolver>? = mutableMapOf()
@Transient
private val yarnEnvironment_: Provider<YarnEnv>? = rootProject_.provider {
YarnPlugin.apply(rootProject_).requireConfigured()
}
@Transient
private val npmEnvironment_: Provider<NpmEnvironment>? = rootProject_.provider {
nodeJs_.asNpmEnvironment
}
@Transient
private val yarnResolutions_: Provider<List<YarnResolution>>? = rootProject_.provider {
YarnPlugin.apply(rootProject_).resolutions
}
@Transient
private val taskRequirements_: TasksRequirements? = nodeJs_.taskRequirements
private val resolverStateHolder by lazy {
rootProject_.gradle.sharedServices.registerIfAbsent(
KotlinRootNpmResolverStateHolder::class.qualifiedName,
KotlinRootNpmResolverStateHolder::class.java
) { service ->
service.parameters.projectResolvers.set(projectResolvers_)
service.parameters.packageManager.set(nodeJs_.packageManager)
service.parameters.yarnEnvironment.set(yarnEnvironment_?.get())
service.parameters.npmEnvironment.set(npmEnvironment_?.get())
service.parameters.yarnResolutions.set(yarnResolutions_?.get())
service.parameters.taskRequirements.set(taskRequirements_)
service.parameters.packageJsonHandlers.set(compilations.associate { compilation ->
"${compilation.project.path}:${compilation.disambiguatedName}" to compilation.packageJsonHandlers
}.filter { it.value.isNotEmpty() })
}.also {
declareBuildServiceForRootProjectJsTasks(it)
declareBuildServiceForAllProjectsJsTasks(it)
}
}
private val configurationCacheProjectResolvers: MutableMap<String, KotlinProjectNpmResolver>
get() {
val stateHolder = resolverStateHolder.get()
val projResolvers = stateHolder.parameters.projectResolvers.get()
if (stateHolder.initialized) return projResolvers
projResolvers.forEach { (_, value) ->
value.resolver = this
value.compilationResolvers.forEach { compResolver ->
compResolver.rootResolver = this
}
}
stateHolder.initialized = true
return projResolvers
}
private val projectResolvers
get() = projectResolvers_ ?: configurationCacheProjectResolvers
private val packageManager
get() = nodeJs?.packageManager ?: resolverStateHolder.get().parameters.packageManager.get()
private val yarnEnvironment
get() = yarnEnvironment_?.get() ?: resolverStateHolder.get().parameters.yarnEnvironment.get()
private val npmEnvironment
get() = npmEnvironment_?.get() ?: resolverStateHolder.get().parameters.npmEnvironment.get()
private val yarnResolutions
get() = yarnResolutions_?.get() ?: resolverStateHolder.get().parameters.yarnResolutions.get()
internal val taskRequirements
get() = taskRequirements_ ?: resolverStateHolder.get().parameters.taskRequirements.get()
internal val mayBeUpToDateTasksRegistry =
MayBeUpToDatePackageJsonTasksRegistry.registerIfAbsent(rootProject_)
fun alreadyResolvedMessage(action: String) = "Cannot $action. NodeJS projects already resolved." fun alreadyResolvedMessage(action: String) = "Cannot $action. NodeJS projects already resolved."
fun addProject(target: Project) { fun addProject(target: Project) {
synchronized(projectResolvers) { synchronized(projectResolvers) {
check(state == RootResolverState.CONFIGURING) { alreadyResolvedMessage("add new project: $target") } check(resolution == null) { alreadyResolvedMessage("add new project: $target") }
projectResolvers[target.path] = KotlinProjectNpmResolver(target, this) val kotlinProjectNpmResolver = KotlinProjectNpmResolver(target, this)
projectResolvers[target.path] = kotlinProjectNpmResolver
} }
} }
internal operator fun get(projectPath: String) = projectResolvers[projectPath] ?: error("$projectPath is not configured for JS usage") internal operator fun get(projectPath: String) =
projectResolvers[projectPath] ?: error("$projectPath is not configured for JS usage")
val compilations: Collection<KotlinJsCompilation> val compilations: Collection<KotlinJsCompilation>
get() = projectResolvers.values.flatMap { it.compilationResolvers.map { it.compilation } } get() = projectResolvers.values.flatMap { it.compilationResolvers.map { it.compilation } }
internal fun getPackageJsonHandlers(projectPath: String, compilationDisambiguatedName: String): List<PackageJson.() -> Unit> =
resolverStateHolder.get().parameters.packageJsonHandlers.get()["$projectPath:$compilationDisambiguatedName"] ?: emptyList()
internal fun findDependentResolver(src: Project, target: Project): List<KotlinCompilationNpmResolver>? { internal fun findDependentResolver(src: Project, target: Project): List<KotlinCompilationNpmResolver>? {
// todo: proper finding using KotlinTargetComponent.findUsageContext // todo: proper finding using KotlinTargetComponent.findUsageContext
val targetResolver = this[target.path] val targetResolver = this[target.path]
@@ -242,113 +83,15 @@ class KotlinRootNpmResolver internal constructor(
return mainCompilations return mainCompilations
} }
/** internal fun close(): KotlinRootNpmResolution {
* Don't use directly, use [KotlinNpmResolutionManager.installIfNeeded] instead. return resolution ?: KotlinRootNpmResolution(
*/ projectResolvers
internal fun prepareInstallation(logger: Logger): Installation { .map { (key, value) -> key to value.close() }
synchronized(projectResolvers) { .toMap(),
state = RootResolverState.PROJECTS_CLOSED rootProjectName,
rootProjectVersion
val projectResolutions = projectResolvers.values )
.map { it.close() }
.associateBy { it.project }
val allNpmPackages = projectResolutions.values.flatMap { it.npmProjects }
gradleNodeModules.close()
compositeNodeModules.close()
packageManager.prepareRootProject(
rootProject,
npmEnvironment,
rootProjectName,
rootProjectVersion,
logger,
allNpmPackages,
yarnResolutions
.associate { it.path to it.toVersionString() },
forceFullResolve
)
return Installation(
projectResolutions
)
}
} }
open inner class Installation(val projectResolutions: Map<String, KotlinProjectNpmResolution>) {
operator fun get(project: String) =
projectResolutions[project] ?: KotlinProjectNpmResolution.empty(project)
internal fun install(
forceUpToDate: Boolean,
args: List<String>,
services: ServiceRegistry,
logger: Logger
): KotlinRootNpmResolution {
synchronized(projectResolvers) {
if (state == RootResolverState.INSTALLED) {
return KotlinRootNpmResolution(rootProject, projectResolutions)
}
check(state == RootResolverState.PROJECTS_CLOSED) {
"Projects must be closed"
}
state = RootResolverState.INSTALLED
val allNpmPackages = projectResolutions
.values
.flatMap { it.npmProjects }
packageManager.resolveRootProject(
services,
logger,
npmEnvironment,
yarnEnvironment,
allNpmPackages,
args
)
return KotlinRootNpmResolution(rootProject, projectResolutions)
}
}
}
private fun removeOutdatedPackages(nodeJs: NodeJsRootExtension, allNpmPackages: List<KotlinCompilationNpmResolution>) {
val packages = allNpmPackages.mapTo(mutableSetOf()) { it.npmProject.name }
nodeJs.projectPackagesDir.listFiles()?.forEach {
if (it.name !in packages) {
it.deleteRecursively()
}
}
}
internal fun declareBuildServiceForRootProjectJsTasks(serviceProvider: Provider<out BuildService<*>>) {
rootProject_.tasks.withType<RootPackageJsonTask>().configureEach {
it.usesService(serviceProvider)
}
rootProject_.tasks.withType<KotlinNpmInstallTask>().configureEach {
it.usesService(serviceProvider)
}
rootProject_.tasks.withType<KotlinNpmCachesSetup>().configureEach {
it.usesService(serviceProvider)
}
}
internal fun declareBuildServiceForAllProjectsJsTasks(serviceProvider: Provider<out BuildService<*>>) {
rootProject_.allprojects { project ->
project.tasks.withType<Kotlin2JsCompile>().configureEach {
it.usesService(serviceProvider)
}
project.tasks.withType<KotlinPackageJsonTask>().configureEach {
it.usesService(serviceProvider)
}
}
}
}
enum class RootResolverState {
CONFIGURING,
PROJECTS_CLOSED,
INSTALLED
} }
const val PACKAGE_JSON_UMBRELLA_TASK_NAME = "packageJsonUmbrella" const val PACKAGE_JSON_UMBRELLA_TASK_NAME = "packageJsonUmbrella"
@@ -1,38 +0,0 @@
/*
* Copyright 2010-2021 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.npm.resolver
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.jetbrains.kotlin.gradle.targets.js.nodejs.TasksRequirements
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnEnv
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnResolution
// We use this service as a single storage for duplicated data in configuration cache
abstract class KotlinRootNpmResolverStateHolder : BuildService<KotlinRootNpmResolverStateHolder.Parameters> {
internal interface Parameters : BuildServiceParameters {
val projectResolvers: MapProperty<String, KotlinProjectNpmResolver>
val packageManager: Property<NpmApi>
val yarnEnvironment: Property<YarnEnv>
val npmEnvironment: Property<NpmEnvironment>
val yarnResolutions: ListProperty<YarnResolution>
val taskRequirements: Property<TasksRequirements>
// pulled up from compilation resolver since it was failing with ClassNotFoundException on deserialization, see KT-49061
val packageJsonHandlers: MapProperty<String, List<PackageJson.() -> Unit>>
}
var initialized = false
@Volatile
var state = RootResolverState.CONFIGURING
}
@@ -1,31 +0,0 @@
/*
* Copyright 2010-2021 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.npm.resolver
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import java.util.concurrent.ConcurrentHashMap
internal abstract class MayBeUpToDatePackageJsonTasksRegistry : BuildService<BuildServiceParameters.None> {
private val mayBeUpToDateTasks = ConcurrentHashMap.newKeySet<String>()
fun markForNpmDependenciesResolve(task: KotlinPackageJsonTask) {
mayBeUpToDateTasks.add(task.path)
}
fun shouldResolveNpmDependenciesFor(taskPath: String) = taskPath in mayBeUpToDateTasks
companion object {
fun registerIfAbsent(project: Project): Provider<MayBeUpToDatePackageJsonTasksRegistry> =
project.rootProject.gradle.sharedServices.registerIfAbsent(
MayBeUpToDatePackageJsonTasksRegistry::class.qualifiedName,
MayBeUpToDatePackageJsonTasksRegistry::class.java
) {}
}
}
@@ -0,0 +1,33 @@
/*
* 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.npm.resolver
import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings
import java.io.File
class PackageJsonProducerInputs(
@get:Input
val internalDependencies: Collection<String>,
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:InputFiles
val internalCompositeDependencies: Collection<File>,
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:InputFiles
val externalGradleDependencies: Collection<File>,
@get:Input
val externalDependencies: Collection<String>,
@get:Input
val fileCollectionDependencies: Collection<File>
)
@@ -6,21 +6,24 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.tasks package org.jetbrains.kotlin.gradle.targets.js.npm.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.internal.hash.FileHasher import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.npm.CompositeNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.UsesCompositeNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.UsesGradleNodeModulesCache
import javax.inject.Inject import javax.inject.Inject
abstract class KotlinNpmCachesSetup : DefaultTask() { abstract class KotlinNpmCachesSetup :
DefaultTask(),
UsesGradleNodeModulesCache,
UsesCompositeNodeModulesCache {
@get:Inject @get:Inject
open val fileHasher: FileHasher open val fileHasher: FileHasher
get() = throw UnsupportedOperationException() get() = throw UnsupportedOperationException()
@Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val gradleNodeModules = nodeJs.npmResolutionManager.resolver.gradleNodeModulesProvider
private val compositeNodeModules = nodeJs.npmResolutionManager.resolver.compositeNodeModulesProvider
@TaskAction @TaskAction
fun setup() { fun setup() {
gradleNodeModules.get().fileHasher = fileHasher gradleNodeModules.get().fileHasher = fileHasher
@@ -9,72 +9,66 @@ import org.gradle.api.DefaultTask
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
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.npm.KotlinNpmResolutionManager import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.UsesKotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.asNpmEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.asNpmEnvironment
import org.jetbrains.kotlin.gradle.utils.unavailableValueError import org.jetbrains.kotlin.gradle.targets.js.npm.asYarnEnvironment
import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn
import java.io.File import java.io.File
open class KotlinNpmInstallTask : DefaultTask() { abstract class KotlinNpmInstallTask :
DefaultTask(),
UsesKotlinNpmResolutionManager {
init { init {
check(project == project.rootProject) check(project == project.rootProject)
onlyIf {
preparedFiles.all {
it.exists()
}
}
} }
@Transient // Only in configuration phase
private val nodeJs: NodeJsRootExtension? = NodeJsRootPlugin.apply(project.rootProject) // Not part of configuration caching
private val resolutionManager = (nodeJs ?: unavailableValueError("nodeJs")).npmResolutionManager
private val nodeJs: NodeJsRootExtension
get() = project.rootProject.kotlinNodeJsExtension
private val yarn
get() = project.rootProject.yarn
// -----
private val npmEnvironment by lazy {
nodeJs.requireConfigured().asNpmEnvironment
}
private val yarnEnv by lazy {
yarn.requireConfigured().asYarnEnvironment
}
@Input @Input
val args: MutableList<String> = mutableListOf() val args: MutableList<String> = mutableListOf()
@get:Internal
val nodeModulesDir: File by lazy {
(nodeJs ?: unavailableValueError("nodeJs"))
.rootPackageDir
.resolve("node_modules")
}
init {
outputs.upToDateWhen {
nodeModulesDir.isDirectory
}
}
@Suppress("unused")
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:InputFiles
val packageJsonFiles: Collection<File> by lazy {
resolutionManager.packageJsonFiles
}
@get:PathSensitive(PathSensitivity.ABSOLUTE) @get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:IgnoreEmptyDirectories @get:IgnoreEmptyDirectories
@get:NormalizeLineEndings @get:NormalizeLineEndings
@get:InputFiles @get:InputFiles
val preparedFiles: Collection<File> by lazy { val preparedFiles: Collection<File> by lazy {
(nodeJs ?: unavailableValueError("nodeJs")).packageManager.preparedFiles(nodeJs.asNpmEnvironment) nodeJs.packageManager.preparedFiles(npmEnvironment)
} }
@get:OutputFile @get:OutputFile
val yarnLock: File by lazy { val yarnLock: File by lazy {
(nodeJs ?: unavailableValueError("nodeJs")).rootPackageDir.resolve("yarn.lock") nodeJs.rootPackageDir.resolve("yarn.lock")
} }
@TaskAction @TaskAction
fun resolve() { fun resolve() {
resolutionManager.installIfNeeded( npmResolutionManager.get()
args = args, .installIfNeeded(
services = services, args = args,
logger = logger services = services,
) ?: throw (resolutionManager.state as KotlinNpmResolutionManager.ResolutionState.Error).wrappedException logger = logger,
npmEnvironment,
yarnEnv
) ?: throw (npmResolutionManager.get().state as KotlinNpmResolutionManager.ResolutionState.Error).wrappedException
} }
companion object { companion object {
@@ -11,92 +11,86 @@ import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
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.npm.PackageJson import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension
import org.jetbrains.kotlin.gradle.targets.js.npm.fakePackageJsonValue import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.MayBeUpToDatePackageJsonTasksRegistry
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PackageJsonProducerInputs
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.chainedDisallowChanges
import org.jetbrains.kotlin.gradle.utils.getValue
import java.io.File import java.io.File
abstract class KotlinPackageJsonTask : DefaultTask() { abstract class KotlinPackageJsonTask :
DefaultTask(),
UsesKotlinNpmResolutionManager,
UsesGradleNodeModulesCache,
UsesCompositeNodeModulesCache {
// Only in configuration phase
// Not part of configuration caching
init { private val nodeJs: NodeJsRootExtension
onlyIf { get() = project.rootProject.kotlinNodeJsExtension
npmResolutionManager.isConfiguringState()
private val rootResolver: KotlinRootNpmResolver
get() = nodeJs.resolver
private val compilationResolver: KotlinCompilationNpmResolver
get() = rootResolver[projectPath][compilationDisambiguatedName.get()]
private fun findDependentTasks(): Collection<Any> =
compilationResolver.compilationNpmResolution.internalDependencies.map { dependency ->
nodeJs.resolver[dependency.projectPath][dependency.compilationName].npmProject.packageJsonTaskPath
} + compilationResolver.compilationNpmResolution.internalCompositeDependencies.map { dependency ->
dependency.includedBuild?.task(":$PACKAGE_JSON_UMBRELLA_TASK_NAME") ?: error("includedBuild instance is not available")
dependency.includedBuild.task(":${RootPackageJsonTask.NAME}")
} }
outputs.upToDateWhen {
// this way we will ensure that we need to resolve dependencies for the compilation in case of UP-TO-DATE task
// used in KotlinCompilationNpmResolver#getResolutionOrResolveIfForced
mayBeUpToDateTasksRegistry.get().markForNpmDependenciesResolve(it as KotlinPackageJsonTask)
true
}
}
@Transient // -----
private lateinit var nodeJs: NodeJsRootExtension
private val npmResolutionManager by project.provider { nodeJs.npmResolutionManager } private val projectPath = project.path
@Transient
private lateinit var compilation: KotlinJsCompilation
@get:Internal @get:Internal
internal abstract val mayBeUpToDateTasksRegistry: Property<MayBeUpToDatePackageJsonTasksRegistry> abstract val compilationDisambiguatedName: Property<String>
private val compilationDisambiguatedName by lazy { private val packageJsonHandlers: List<PackageJson.() -> Unit>
compilation.disambiguatedName get() = npmResolutionManager.get().parameters.packageJsonHandlers.get()
} .getValue("$projectPath:${compilationDisambiguatedName.get()}")
@Input
val projectPath = project.path
private val compilationResolver
get() = npmResolutionManager.resolver[projectPath][compilationDisambiguatedName]
private val producer: KotlinCompilationNpmResolver.PackageJsonProducer
get() = compilationResolver.packageJsonProducer
@get:Input @get:Input
val packageJsonCustomFields: Map<String, Any?> by lazy { val packageJsonCustomFields: Map<String, Any?> by lazy {
PackageJson(fakePackageJsonValue, fakePackageJsonValue) PackageJson(fakePackageJsonValue, fakePackageJsonValue)
.apply { .apply {
compilation.packageJsonHandlers.forEach { it() } packageJsonHandlers.forEach { it() }
}.customFields }.customFields
} }
private fun findDependentTasks(): Collection<Any> =
producer.internalDependencies.map { dependency ->
npmResolutionManager.resolver[dependency.projectPath][dependency.compilationName].npmProject.packageJsonTaskPath
} + producer.internalCompositeDependencies.map { dependency ->
dependency.includedBuild?.task(":$PACKAGE_JSON_UMBRELLA_TASK_NAME") ?: error("includedBuild instance is not available")
dependency.includedBuild.task(":${RootPackageJsonTask.NAME}")
}
@get:Input @get:Input
internal val toolsNpmDependencies: List<String> by lazy { internal val toolsNpmDependencies: List<String> by lazy {
nodeJs.taskRequirements nodeJs.taskRequirements
.getCompilationNpmRequirements(projectPath, compilationDisambiguatedName) .getCompilationNpmRequirements(projectPath, compilationDisambiguatedName.get())
.map { it.toString() } .map { it.toString() }
.sorted() .sorted()
} }
// nested inputs are processed in configuration phase
// so npmResolutionManager must not be used
@get:Nested @get:Nested
internal val producerInputs: KotlinCompilationNpmResolver.PackageJsonProducerInputs by lazy { internal val producerInputs: PackageJsonProducerInputs by lazy {
producer.inputs compilationResolver.compilationNpmResolution.inputs
} }
@get:OutputFile @get:OutputFile
val packageJson: File by lazy { abstract val packageJson: Property<File>
compilationResolver.npmProject.packageJsonFile
}
@TaskAction @TaskAction
fun resolve() { fun resolve() {
compilationResolver.resolve() npmResolutionManager.get().resolution.get()[projectPath][compilationDisambiguatedName.get()]
.resolve(
npmResolutionManager = npmResolutionManager.get(),
logger = logger
)
} }
companion object { companion object {
@@ -104,29 +98,50 @@ abstract class KotlinPackageJsonTask : DefaultTask() {
val target = compilation.target val target = compilation.target
val project = target.project val project = target.project
val npmProject = compilation.npmProject val npmProject = compilation.npmProject
val nodeJs = NodeJsRootPlugin.apply(project.rootProject) val nodeJsTaskProviders = project.rootProject.kotlinNodeJsExtension
val npmCachesSetupTask = nodeJs.npmCachesSetupTaskProvider val npmCachesSetupTask = nodeJsTaskProviders.npmCachesSetupTaskProvider
val packageJsonTaskName = npmProject.packageJsonTaskName val packageJsonTaskName = npmProject.packageJsonTaskName
val packageJsonUmbrella = nodeJs.packageJsonUmbrellaTaskProvider val packageJsonUmbrella = nodeJsTaskProviders.packageJsonUmbrellaTaskProvider
val npmResolutionManager = project.kotlinNpmResolutionManager
val gradleNodeModules = GradleNodeModulesCache.registerIfAbsent(project, null, null)
val compositeNodeModules = CompositeNodeModulesCache.registerIfAbsent(project, null, null)
val packageJsonTask = project.registerTask<KotlinPackageJsonTask>(packageJsonTaskName) { task -> val packageJsonTask = project.registerTask<KotlinPackageJsonTask>(packageJsonTaskName) { task ->
task.nodeJs = nodeJs task.compilationDisambiguatedName.set(compilation.disambiguatedName)
task.compilation = compilation
task.description = "Create package.json file for $compilation" task.description = "Create package.json file for $compilation"
task.group = NodeJsRootPlugin.TASKS_GROUP_NAME task.group = NodeJsRootPlugin.TASKS_GROUP_NAME
val packageJsonStateService = MayBeUpToDatePackageJsonTasksRegistry.registerIfAbsent(project)
task.mayBeUpToDateTasksRegistry.value(packageJsonStateService).chainedDisallowChanges().also { task.npmResolutionManager.apply {
task.usesService(packageJsonStateService) set(npmResolutionManager)
disallowChanges()
}
task.gradleNodeModules.apply {
set(gradleNodeModules)
disallowChanges()
}
task.compositeNodeModules.apply {
set(compositeNodeModules)
disallowChanges()
}
task.packageJson.set(compilation.npmProject.packageJsonFile)
task.onlyIf {
it as KotlinPackageJsonTask
it.npmResolutionManager.get().isConfiguringState()
} }
task.dependsOn(target.project.provider { task.findDependentTasks() }) task.dependsOn(target.project.provider { task.findDependentTasks() })
task.dependsOn(npmCachesSetupTask) task.dependsOn(npmCachesSetupTask)
} }
packageJsonUmbrella.configure { task -> packageJsonUmbrella.configure { task ->
task.inputs.file(packageJsonTask.map { it.packageJson }) task.inputs.file(packageJsonTask.map { it.packageJson })
} }
nodeJs.rootPackageJsonTaskProvider!!.configure { it.mustRunAfter(packageJsonTask) } nodeJsTaskProviders.rootPackageJsonTaskProvider.configure { it.mustRunAfter(packageJsonTask) }
return packageJsonTask return packageJsonTask
} }
@@ -8,22 +8,40 @@ package org.jetbrains.kotlin.gradle.targets.js.npm.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
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.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn
import java.io.File import java.io.File
open class RootPackageJsonTask : DefaultTask() { abstract class RootPackageJsonTask :
DefaultTask(),
UsesKotlinNpmResolutionManager {
init { init {
check(project == project.rootProject) check(project == project.rootProject)
onlyIf {
resolutionManager.isConfiguringState()
}
} }
@Transient // Only in configuration phase
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) // Not part of configuration caching
private val resolutionManager = nodeJs.npmResolutionManager
private val nodeJs
get() = project.rootProject.kotlinNodeJsExtension
private val yarn
get() = project.rootProject.yarn
private val rootResolver: KotlinRootNpmResolver
get() = nodeJs.resolver
// -----
private val npmEnvironment by lazy {
nodeJs.requireConfigured().asNpmEnvironment
}
private val yarnEnv by lazy {
yarn.requireConfigured().asYarnEnvironment
}
@get:OutputFile @get:OutputFile
val rootPackageJson: File by lazy { val rootPackageJson: File by lazy {
@@ -35,12 +53,15 @@ open class RootPackageJsonTask : DefaultTask() {
@get:NormalizeLineEndings @get:NormalizeLineEndings
@get:InputFiles @get:InputFiles
val packageJsonFiles: Collection<File> by lazy { val packageJsonFiles: Collection<File> by lazy {
resolutionManager.packageJsonFiles rootResolver.projectResolvers.values
.flatMap { it.compilationResolvers }
.map { it.compilationNpmResolution }
.map { it.npmProjectPackageJsonFile }
} }
@TaskAction @TaskAction
fun resolve() { fun resolve() {
resolutionManager.prepare(logger) npmResolutionManager.get().prepare(logger, npmEnvironment, yarnEnv)
} }
companion object { companion object {
@@ -14,7 +14,6 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinJsDce import org.jetbrains.kotlin.gradle.dsl.KotlinJsDce
import org.jetbrains.kotlin.gradle.plugin.COMPILER_CLASSPATH_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.plugin.COMPILER_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.plugin.mpp.isTest import org.jetbrains.kotlin.gradle.plugin.mpp.isTest
@@ -25,7 +24,7 @@ import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.* import org.jetbrains.kotlin.gradle.targets.js.dsl.*
import org.jetbrains.kotlin.gradle.targets.js.ir.executeTaskBaseName import org.jetbrains.kotlin.gradle.targets.js.ir.executeTaskBaseName
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
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.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma
@@ -33,7 +32,6 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackDevtool import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackDevtool
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion.Companion.choose
import org.jetbrains.kotlin.gradle.tasks.dependsOn import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat
@@ -51,9 +49,6 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
private val runTaskConfigurations: MutableList<Action<KotlinWebpack>> = mutableListOf() private val runTaskConfigurations: MutableList<Action<KotlinWebpack>> = mutableListOf()
private val dceConfigurations: MutableList<Action<KotlinJsDce>> = mutableListOf() private val dceConfigurations: MutableList<Action<KotlinJsDce>> = mutableListOf()
private val distribution: Distribution = createDefaultDistribution(project) private val distribution: Distribution = createDefaultDistribution(project)
private val propertiesProvider = PropertiesProvider(project)
private val webpackMajorVersion
get() = propertiesProvider.webpackMajorVersion
override val testTaskDescription: String override val testTaskDescription: String
get() = "Run all ${target.name} tests inside browser using karma and webpack" get() = "Run all ${target.name} tests inside browser using karma and webpack"
@@ -138,7 +133,7 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
devDceTaskProvider: TaskProvider<KotlinJsDceTask> devDceTaskProvider: TaskProvider<KotlinJsDceTask>
) { ) {
val project = compilation.target.project val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject) val nodeJs = project.rootProject.kotlinNodeJsExtension
val commonRunTask = registerSubTargetTask<Task>(disambiguateCamelCased(RUN_TASK_NAME)) {} val commonRunTask = registerSubTargetTask<Task>(disambiguateCamelCased(RUN_TASK_NAME)) {}
@@ -156,33 +151,20 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
listOf(compilation) listOf(compilation)
) { task -> ) { task ->
task.outputDirectory.convention(distsDirectory).finalizeValueOnRead() task.outputDirectory.convention(distsDirectory).finalizeValueOnRead()
webpackMajorVersion.choose( task.args.add(0, "serve")
{ task.args.add(0, "serve") },
{ task.bin = "webpack-dev-server/bin/webpack-dev-server.js" }
)()
task.description = "start ${type.name.toLowerCaseAsciiOnly()} webpack dev server" task.description = "start ${type.name.toLowerCaseAsciiOnly()} webpack dev server"
webpackMajorVersion.choose( task.devServer = KotlinWebpackConfig.DevServer(
{ open = true,
task.devServer = KotlinWebpackConfig.DevServer( static = mutableListOf(compilation.output.resourcesDir.canonicalPath),
open = true, client = KotlinWebpackConfig.DevServer.Client(
static = mutableListOf(compilation.output.resourcesDir.canonicalPath), KotlinWebpackConfig.DevServer.Client.Overlay(
client = KotlinWebpackConfig.DevServer.Client( errors = true,
KotlinWebpackConfig.DevServer.Client.Overlay( warnings = false
errors = true,
warnings = false
)
)
) )
}, )
{ )
task.devServer = KotlinWebpackConfig.DevServer(
open = true,
contentBase = mutableListOf(compilation.output.resourcesDir.canonicalPath)
)
}
)()
task.doNotTrackStateCompat("Tracked by external webpack tool") task.doNotTrackStateCompat("Tracked by external webpack tool")
@@ -212,7 +194,7 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
devDceTaskProvider: TaskProvider<KotlinJsDceTask> devDceTaskProvider: TaskProvider<KotlinJsDceTask>
) { ) {
val project = compilation.target.project val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject) val nodeJs = project.rootProject.kotlinNodeJsExtension
val processResourcesTask = target.project.tasks.named(compilation.processResourcesTaskName) val processResourcesTask = target.project.tasks.named(compilation.processResourcesTaskName)
@@ -20,8 +20,7 @@ import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlatformTestRun import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlatformTestRun
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl
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.npm.NpmResolverPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.tasks.registerTask
@@ -35,7 +34,8 @@ abstract class KotlinJsSubTarget(
) : KotlinJsSubTargetDsl { ) : KotlinJsSubTargetDsl {
val project get() = target.project val project get() = target.project
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val nodeJsTaskProviders = project.rootProject.kotlinNodeJsExtension
abstract val testTaskDescription: String abstract val testTaskDescription: String
@@ -53,8 +53,6 @@ abstract class KotlinJsSubTarget(
} }
internal fun configure() { internal fun configure() {
NpmResolverPlugin.apply(project)
configureTests() configureTests()
target.compilations.all { target.compilations.all {
@@ -108,10 +106,10 @@ abstract class KotlinJsSubTarget(
testJs.inputFileProperty.fileProvider(compileOutputFile) testJs.inputFileProperty.fileProvider(compileOutputFile)
testJs.dependsOn( testJs.dependsOn(
nodeJs.npmInstallTaskProvider, nodeJsTaskProviders.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider, nodeJsTaskProviders.storeYarnLockTaskProvider,
compileTask, compileTask,
nodeJs.nodeJsSetupTaskProvider nodeJsTaskProviders.nodeJsSetupTaskProvider
) )
testJs.onlyIf { testJs.onlyIf {
@@ -14,7 +14,7 @@ import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
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.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma
@@ -33,16 +33,12 @@ constructor(
) : KotlinTest(), ) : KotlinTest(),
RequiresNpmDependencies { RequiresNpmDependencies {
@Transient @Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val npmResolutionManager by project.provider { nodeJs.npmResolutionManager }
private val nodeExecutable by project.provider { nodeJs.requireConfigured().nodeExecutable } private val nodeExecutable by project.provider { nodeJs.requireConfigured().nodeExecutable }
private val npmProjectDir by project.provider { compilation.npmProject.dir } private val npmProjectDir by project.provider { compilation.npmProject.dir }
private val projectPath = project.path
@Input @Input
var environment = mutableMapOf<String, String>() var environment = mutableMapOf<String, String>()
@@ -110,9 +106,6 @@ constructor(
val nodeJsArgs: MutableList<String> = val nodeJsArgs: MutableList<String> =
mutableListOf() mutableListOf()
override val nodeModulesRequired: Boolean
@Internal get() = testFramework!!.nodeModulesRequired
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
@Internal get() = testFramework!!.requiredNpmDependencies @Internal get() = testFramework!!.requiredNpmDependencies
@@ -164,11 +157,6 @@ constructor(
return testFramework return testFramework
} }
override fun executeTests() {
npmResolutionManager.checkRequiredDependencies(task = this)
super.executeTests()
}
override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec { override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec {
val forkOptions = DefaultProcessForkOptions(fileResolver) val forkOptions = DefaultProcessForkOptions(fileResolver)
forkOptions.workingDir = npmProjectDir forkOptions.workingDir = npmProjectDir
@@ -18,7 +18,4 @@ interface KotlinJsTestFramework : RequiresNpmDependencies {
nodeJsArgs: MutableList<String>, nodeJsArgs: MutableList<String>,
debug: Boolean debug: Boolean
): TCServiceMessagesTestExecutionSpec ): TCServiceMessagesTestExecutionSpec
override val nodeModulesRequired: Boolean
get() = true
} }
@@ -32,11 +32,10 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl.Companion.webp
import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm
import org.jetbrains.kotlin.gradle.targets.js.jsQuoted import org.jetbrains.kotlin.gradle.targets.js.jsQuoted
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.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.* import org.jetbrains.kotlin.gradle.targets.js.testing.*
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion.Companion.choose
import org.jetbrains.kotlin.gradle.tasks.KotlinTest import org.jetbrains.kotlin.gradle.tasks.KotlinTest
import org.jetbrains.kotlin.gradle.testing.internal.reportsDir import org.jetbrains.kotlin.gradle.testing.internal.reportsDir
import org.jetbrains.kotlin.gradle.utils.appendLine import org.jetbrains.kotlin.gradle.utils.appendLine
@@ -56,7 +55,7 @@ class KotlinKarma(
private val npmProject = compilation.npmProject private val npmProject = compilation.npmProject
@Transient @Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val nodeRootPackageDir by lazy { nodeJs.rootPackageDir } private val nodeRootPackageDir by lazy { nodeJs.rootPackageDir }
private val versions = nodeJs.versions private val versions = nodeJs.versions
@@ -81,8 +80,6 @@ class KotlinKarma(
override val settingsState: String override val settingsState: String
get() = "KotlinKarma($config)" get() = "KotlinKarma($config)"
private val webpackMajorVersion = PropertiesProvider(project).webpackMajorVersion
val webpackConfig = KotlinWebpackConfig( val webpackConfig = KotlinWebpackConfig(
configDirectory = project.projectDir.resolve("webpack.config.d"), configDirectory = project.projectDir.resolve("webpack.config.d"),
optimization = KotlinWebpackConfig.Optimization( optimization = KotlinWebpackConfig.Optimization(
@@ -94,13 +91,11 @@ class KotlinKarma(
export = false, export = false,
progressReporter = true, progressReporter = true,
progressReporterPathFilter = nodeRootPackageDir, progressReporterPathFilter = nodeRootPackageDir,
webpackMajorVersion = webpackMajorVersion,
rules = project.objects.webpackRulesContainer(), rules = project.objects.webpackRulesContainer(),
experiments = mutableSetOf("topLevelAwait") experiments = mutableSetOf("topLevelAwait")
) )
init { init {
requiredDependencies.add(versions.kotlinJsTestRunner)
requiredDependencies.add(versions.karma) requiredDependencies.add(versions.karma)
useKotlinReporter() useKotlinReporter()
@@ -266,23 +261,14 @@ class KotlinKarma(
config.frameworks.add("webpack") config.frameworks.add("webpack")
requiredDependencies.add(versions.karmaWebpack) requiredDependencies.add(versions.karmaWebpack)
requiredDependencies.add( requiredDependencies.add(
webpackMajorVersion.choose( versions.webpack
versions.webpack,
versions.webpack4
)
) )
requiredDependencies.add( requiredDependencies.add(
webpackMajorVersion.choose( versions.webpackCli
versions.webpackCli,
versions.webpackCli3
)
) )
requiredDependencies.add(versions.formatUtil) requiredDependencies.add(versions.formatUtil)
requiredDependencies.add( requiredDependencies.add(
webpackMajorVersion.choose( versions.sourceMapLoader
versions.sourceMapLoader,
versions.sourceMapLoader1
)
) )
addPreprocessor("webpack") addPreprocessor("webpack")
@@ -298,14 +284,12 @@ class KotlinKarma(
// noinspection JSUnnecessarySemicolon // noinspection JSUnnecessarySemicolon
;(function(config) { ;(function(config) {
const webpack = require('webpack'); const webpack = require('webpack');
${ ${
if (webpackMajorVersion != WebpackMajorVersion.V4) { """
""" // https://github.com/webpack/webpack/issues/12951
// https://github.com/webpack/webpack/issues/12951 const PatchSourceMapSource = require('kotlin-test-js-runner/webpack-5-debug');
const PatchSourceMapSource = require('kotlin-test-js-runner/webpack-5-debug'); config.plugins.push(new PatchSourceMapSource())
config.plugins.push(new PatchSourceMapSource()) """
"""
} else ""
} }
config.plugins.push(new webpack.SourceMapDevToolPlugin({ config.plugins.push(new webpack.SourceMapDevToolPlugin({
moduleFilenameTemplate: "[absolute-resource-path]" moduleFilenameTemplate: "[absolute-resource-path]"
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm
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.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTestFramework import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTestFramework
@@ -25,7 +26,7 @@ class KotlinMocha(@Transient override val compilation: KotlinJsCompilation, priv
@Transient @Transient
private val project: Project = compilation.target.project private val project: Project = compilation.target.project
private val npmProject = compilation.npmProject private val npmProject = compilation.npmProject
private val versions = NodeJsRootPlugin.apply(project.rootProject).versions private val versions = project.rootProject.kotlinNodeJsExtension.versions
private val isTeamCity = project.providers.gradleProperty(TCServiceMessagesTestExecutor.TC_PROJECT_PROPERTY) private val isTeamCity = project.providers.gradleProperty(TCServiceMessagesTestExecutor.TC_PROJECT_PROPERTY)
override val settingsState: String override val settingsState: String
@@ -33,7 +34,6 @@ class KotlinMocha(@Transient override val compilation: KotlinJsCompilation, priv
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
get() = setOf( get() = setOf(
versions.kotlinJsTestRunner,
versions.mocha, versions.mocha,
versions.sourceMapSupport, versions.sourceMapSupport,
versions.formatUtil versions.formatUtil
@@ -6,15 +6,15 @@
package org.jetbrains.kotlin.gradle.targets.js.typescript package org.jetbrains.kotlin.gradle.targets.js.typescript
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
import org.gradle.api.provider.Property
import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.internal.execWithProgress import org.jetbrains.kotlin.gradle.internal.execWithProgress
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinIrJsGeneratedTSValidationStrategy import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinIrJsGeneratedTSValidationStrategy
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.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import javax.inject.Inject import javax.inject.Inject
@@ -30,14 +30,13 @@ constructor(
@get:Internal @get:Internal
@Transient @Transient
protected val nodeJs = NodeJsRootPlugin.apply(project.rootProject) protected val nodeJs = project.rootProject.kotlinNodeJsExtension
@get:Internal private val versions = nodeJs.versions
override val nodeModulesRequired: Boolean get() = false
@get:Internal @get:Internal
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
get() = setOf(nodeJs.versions.typescript) get() = setOf(versions.typescript)
@get:SkipWhenEmpty @get:SkipWhenEmpty
@get:NormalizeLineEndings @get:NormalizeLineEndings
@@ -69,9 +68,8 @@ constructor(
val message = "Oops, Kotlin/JS compiler generated invalid d.ts files." val message = "Oops, Kotlin/JS compiler generated invalid d.ts files."
when (validationStrategy) { if (validationStrategy == KotlinIrJsGeneratedTSValidationStrategy.ERROR) {
KotlinIrJsGeneratedTSValidationStrategy.ERROR -> error(message) error(message)
KotlinIrJsGeneratedTSValidationStrategy.IGNORE -> {}
} }
} }
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWebpackRulesContainer
import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl.Companion.webpackRulesContainer import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl.Companion.webpackRulesContainer
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.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode
@@ -55,9 +56,8 @@ constructor(
private val objects: ObjectFactory private val objects: ObjectFactory
) : DefaultTask(), RequiresNpmDependencies, WebpackRulesDsl, UsesBuildMetricsService { ) : DefaultTask(), RequiresNpmDependencies, WebpackRulesDsl, UsesBuildMetricsService {
@Transient @Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject) private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val versions = nodeJs.versions private val versions = nodeJs.versions
private val resolutionManager = nodeJs.npmResolutionManager
private val rootPackageDir by lazy { nodeJs.rootPackageDir } private val rootPackageDir by lazy { nodeJs.rootPackageDir }
private val npmProject = compilation.npmProject private val npmProject = compilation.npmProject
@@ -202,9 +202,6 @@ constructor(
@Internal @Internal
var generateConfigOnly: Boolean = false var generateConfigOnly: Boolean = false
@Input
val webpackMajorVersion = PropertiesProvider(project).webpackMajorVersion
fun webpackConfigApplier(body: Action<KotlinWebpackConfig>) { fun webpackConfigApplier(body: Action<KotlinWebpackConfig>) {
webpackConfigAppliers.add(body) webpackConfigAppliers.add(body)
} }
@@ -234,7 +231,6 @@ constructor(
devtool = devtool, devtool = devtool,
sourceMaps = sourceMaps, sourceMaps = sourceMaps,
resolveFromModulesFirst = resolveFromModulesFirst, resolveFromModulesFirst = resolveFromModulesFirst,
webpackMajorVersion = webpackMajorVersion
) )
private fun createRunner(): KotlinWebpackRunner { private fun createRunner(): KotlinWebpackRunner {
@@ -262,9 +258,6 @@ constructor(
) )
} }
override val nodeModulesRequired: Boolean
@Internal get() = true
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
@Internal get() = createWebpackConfig(true).getRequiredDependencies(versions) @Internal get() = createWebpackConfig(true).getRequiredDependencies(versions)
@@ -272,8 +265,6 @@ constructor(
@TaskAction @TaskAction
fun doExecute() { fun doExecute() {
resolutionManager.checkRequiredDependencies(task = this)
val runner = createRunner() val runner = createRunner()
if (generateConfigOnly) { if (generateConfigOnly) {
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.gradle.targets.js.appendConfigsFromDir
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWebpackRulesContainer import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinWebpackRulesContainer
import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.WebpackRulesDsl
import org.jetbrains.kotlin.gradle.targets.js.jsQuoted import org.jetbrains.kotlin.gradle.targets.js.jsQuoted
import org.jetbrains.kotlin.gradle.targets.js.webpack.WebpackMajorVersion.Companion.choose
import org.jetbrains.kotlin.gradle.utils.appendLine import org.jetbrains.kotlin.gradle.utils.appendLine
import org.jetbrains.kotlin.gradle.utils.relativeOrAbsolute import org.jetbrains.kotlin.gradle.utils.relativeOrAbsolute
import java.io.File import java.io.File
@@ -49,6 +48,8 @@ data class KotlinWebpackConfig(
@get:NormalizeLineEndings @get:NormalizeLineEndings
@get:InputDirectory @get:InputDirectory
var configDirectory: File? = null, var configDirectory: File? = null,
@Internal
var reportEvaluatedConfigFile: File? = null,
@Input @Input
@Optional @Optional
var devServer: DevServer? = null, var devServer: DevServer? = null,
@@ -72,9 +73,7 @@ data class KotlinWebpackConfig(
@Optional @Optional
var progressReporterPathFilter: File? = null, var progressReporterPathFilter: File? = null,
@Input @Input
var resolveFromModulesFirst: Boolean = false, var resolveFromModulesFirst: Boolean = false
@Input
val webpackMajorVersion: WebpackMajorVersion = WebpackMajorVersion.V5
) : WebpackRulesDsl { ) : WebpackRulesDsl {
@get:Input @get:Input
@@ -94,36 +93,23 @@ data class KotlinWebpackConfig(
fun getRequiredDependencies(versions: NpmVersions) = fun getRequiredDependencies(versions: NpmVersions) =
mutableSetOf<RequiredKotlinJsDependency>().also { mutableSetOf<RequiredKotlinJsDependency>().also {
it.add(versions.kotlinJsTestRunner)
it.add( it.add(
webpackMajorVersion.choose( versions.webpack
versions.webpack,
versions.webpack4
)
) )
it.add( it.add(
webpackMajorVersion.choose( versions.webpackCli
versions.webpackCli,
versions.webpackCli3
)
) )
it.add(versions.formatUtil) it.add(versions.formatUtil)
if (sourceMaps) { if (sourceMaps) {
it.add( it.add(
webpackMajorVersion.choose( versions.sourceMapLoader
versions.sourceMapLoader,
versions.sourceMapLoader1
)
) )
} }
if (devServer != null) { if (devServer != null) {
it.add( it.add(
webpackMajorVersion.choose( versions.webpackDevServer
versions.webpackDevServer,
versions.webpackDevServer3
)
) )
} }
@@ -294,16 +280,8 @@ data class KotlinWebpackConfig(
enforce: "pre" enforce: "pre"
}); });
config.devtool = ${devtool?.let { "'$it'" } ?: false}; config.devtool = ${devtool?.let { "'$it'" } ?: false};
${ ${
webpackMajorVersion.choose( "config.ignoreWarnings = [/Failed to parse source map/]"
"config.ignoreWarnings = [/Failed to parse source map/]",
"""
config.stats = config.stats || {}
Object.assign(config.stats, config.stats, {
warningsFilter: [/Failed to parse source map/]
})
"""
)
} }
""".trimIndent() """.trimIndent()
@@ -5,55 +5,43 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
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.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution
import java.io.File import java.io.File
class Yarn : NpmApi { class Yarn : NpmApi {
private val yarnWorkspaces = YarnWorkspaces() private val yarnWorkspaces = YarnWorkspaces()
private fun getDelegate(project: Project): NpmApi =
yarnWorkspaces
override fun setup(project: Project) =
getDelegate(project.rootProject).setup(project)
override fun preparedFiles(nodeJs: NpmEnvironment): Collection<File> = override fun preparedFiles(nodeJs: NpmEnvironment): Collection<File> =
yarnWorkspaces.preparedFiles(nodeJs) yarnWorkspaces.preparedFiles(nodeJs)
override fun prepareRootProject( override fun prepareRootProject(
rootProject: Project?,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
rootProjectName: String, rootProjectName: String,
rootProjectVersion: String, rootProjectVersion: String,
logger: Logger, logger: Logger,
subProjects: Collection<KotlinCompilationNpmResolution>, subProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>, resolutions: Map<String, String>,
forceFullResolve: Boolean
) = yarnWorkspaces ) = yarnWorkspaces
.prepareRootProject( .prepareRootProject(
rootProject,
nodeJs, nodeJs,
rootProjectName, rootProjectName,
rootProjectVersion, rootProjectVersion,
logger, logger,
subProjects, subProjects,
resolutions, resolutions,
forceFullResolve
) )
override fun resolveRootProject( override fun resolveRootProject(
services: ServiceRegistry, services: ServiceRegistry,
logger: Logger, logger: Logger,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
yarn: YarnEnv, yarn: YarnEnvironment,
npmProjects: Collection<KotlinCompilationNpmResolution>, npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
cliArgs: List<String> cliArgs: List<String>
) { ) {
yarnWorkspaces yarnWorkspaces
@@ -66,15 +54,4 @@ class Yarn : NpmApi {
cliArgs cliArgs
) )
} }
override fun resolveDependency(
npmResolution: KotlinCompilationNpmResolution,
dependency: NpmDependency,
transitive: Boolean
) = getDelegate(npmResolution.project)
.resolveDependency(
npmResolution,
dependency,
transitive
)
} }
@@ -5,31 +5,21 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.internal.execWithProgress import org.jetbrains.kotlin.gradle.internal.execWithProgress
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.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency.Scope.PEER
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment
import java.io.File import java.io.File
abstract class YarnBasics : NpmApi { abstract class YarnBasics : NpmApi {
private val nonTransitiveResolvedDependencies = mutableMapOf<NpmDependency, Set<File>>()
private val transitiveResolvedDependencies = mutableMapOf<NpmDependency, Set<File>>()
override fun setup(project: Project) {
YarnPlugin.apply(project).executeSetup()
}
fun yarnExec( fun yarnExec(
services: ServiceRegistry, services: ServiceRegistry,
logger: Logger, logger: Logger,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
yarn: YarnEnv, yarn: YarnEnvironment,
dir: File, dir: File,
description: String, description: String,
args: List<String> args: List<String>
@@ -69,107 +59,4 @@ abstract class YarnBasics : NpmApi {
} }
} }
override fun resolveDependency(
npmResolution: KotlinCompilationNpmResolution,
dependency: NpmDependency,
transitive: Boolean
): Set<File> {
val files = (if (transitive) {
transitiveResolvedDependencies
} else {
nonTransitiveResolvedDependencies
})[dependency]
if (files != null) {
return files
}
val npmProject = npmResolution.npmProject
val all = mutableSetOf<File>()
npmProject.resolve(dependency.key)?.let {
if (it.isFile) all.add(it)
if (it.path.endsWith(".js")) {
val baseName = it.path.removeSuffix(".js")
val metaJs = File(baseName + ".meta.js")
if (metaJs.isFile) all.add(metaJs)
val kjsmDir = File(baseName)
if (kjsmDir.isDirectory) {
kjsmDir.walkTopDown()
.filter { it.extension == "kjsm" }
.forEach { all.add(it) }
}
}
}
nonTransitiveResolvedDependencies[dependency] = all
if (transitive) {
dependency.dependencies.forEach {
resolveDependency(
npmResolution,
it,
transitive
).also { files ->
all.addAll(files)
}
}
transitiveResolvedDependencies[dependency] = all
}
return all
}
protected fun yarnLockReadTransitiveDependencies(
nodeWorkDir: File,
srcDependenciesList: Collection<NpmDependency>
) {
val yarnLock = nodeWorkDir
.resolve("yarn.lock")
.takeIf { it.isFile }
?: return
val entryRegistry = YarnEntryRegistry(yarnLock)
val visited = mutableMapOf<NpmDependency, NpmDependency>()
fun resolveRecursively(src: NpmDependency) {
if (src.scope == PEER) {
return
}
val copy = visited[src]
if (copy != null) {
src.resolvedVersion = copy.resolvedVersion
src.integrity = copy.integrity
src.dependencies.addAll(copy.dependencies)
return
}
visited[src] = src
val deps = entryRegistry.find(src.key, src.version)
src.resolvedVersion = deps.version
src.integrity = deps.integrity
deps.dependencies.mapTo(src.dependencies) { dep ->
val scopedName = dep.scopedName
val child = NpmDependency(
project = src.project,
name = scopedName.toString(),
version = dep.version ?: "*"
)
child.parent = src
resolveRecursively(child)
child
}
}
srcDependenciesList.forEach { src ->
resolveRecursively(src)
}
}
} }
@@ -11,56 +11,6 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.includedRange
import org.jetbrains.kotlin.gradle.targets.js.npm.intersect import org.jetbrains.kotlin.gradle.targets.js.npm.intersect
import java.io.File import java.io.File
internal class YarnEntryRegistry(private val lockFile: File) {
private val entryMap = YarnLock.parse(lockFile)
.entries
.associateBy { it.dependencyKey }
fun find(packageKey: String, version: String): YarnLock.Entry {
val key = dependencyKey(packageKey, version)
val entry = entryMap[key]
if (entry != null) {
return entry
}
entryMap.entries
.firstOrNull { (_, entry) ->
if (entry.version == null) {
false
} else {
(includedRange(version) intersect includedRange(entry.version)) != null
}
}
?.let { return it.value }
throw GradleException(
"Cannot find $key in yarn.lock"
)
}
private val YarnLock.Entry.dependencyKey: String
get() = key.correctDependencyKey()
private fun dependencyKey(packageKey: String, version: String) =
YarnLock.dependencyKey(packageKey, version).correctDependencyKey()
private fun String.correctDependencyKey(): String =
when {
GITHUB_MARKER in this -> replace(GITHUB_MARKER, SEPARATOR)
FILE_MARKER in this -> {
val location = substringAfter(FILE_MARKER)
val path = lockFile
.parentFile
.resolve(location)
.canonicalPath
replaceAfter(FILE_MARKER, path)
}
else -> this
}
}
private const val SEPARATOR = "@" private const val SEPARATOR = "@"
private const val FILE_MARKER = "${SEPARATOR}$FILE_VERSION_PREFIX" private const val FILE_MARKER = "${SEPARATOR}$FILE_VERSION_PREFIX"
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
import java.io.Serializable
data class YarnEnv( data class YarnEnv(
val downloadUrl: String, val downloadUrl: String,
@@ -20,4 +19,5 @@ data class YarnEnv(
val yarnLockMismatchReport: YarnLockMismatchReport, val yarnLockMismatchReport: YarnLockMismatchReport,
val reportNewYarnLock: Boolean, val reportNewYarnLock: Boolean,
val yarnLockAutoReplace: Boolean, val yarnLockAutoReplace: Boolean,
) : Serializable val yarnResolutions: List<YarnResolution>
)
@@ -11,12 +11,12 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModule
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.npm.fileVersion import org.jetbrains.kotlin.gradle.targets.js.npm.fileVersion
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution
import java.io.File import java.io.File
class YarnImportedPackagesVersionResolver( class YarnImportedPackagesVersionResolver(
private val logger: Logger, private val logger: Logger,
private val npmProjects: Collection<KotlinCompilationNpmResolution>, npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
private val nodeJsWorldDir: File private val nodeJsWorldDir: File
) { ) {
private val resolvedVersion = mutableMapOf<String, ResolvedNpmDependency>() private val resolvedVersion = mutableMapOf<String, ResolvedNpmDependency>()
@@ -1,154 +0,0 @@
/*
* Copyright 2010-2020 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 com.google.gson.stream.JsonReader
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import java.io.File
import java.io.StringReader
data class YarnLock(val entries: List<Entry>) {
data class Entry(
val key: String,
val version: String?,
val resolved: String?,
val integrity: String?,
val dependencies: List<Dependency>
)
data class Dependency(val key: String, val version: String?) {
val scopedName: PackageJson.ScopedName
get() = PackageJson.scopedName(key)
}
companion object {
fun dependencyKey(packageKey: String, version: String) = "$packageKey@$version"
private class Node(val parent: Node?, val value: String? = null) {
var indent: String? = null
val children = mutableMapOf<String, Node>()
}
fun parse(yarnLock: File): YarnLock {
val root = parseTree(yarnLock)
return YarnLock(root.children.map {
val values = it.value.children
Entry(
it.key,
values["version"]?.value,
values["resolved"]?.value,
values["integrity"]?.value,
values["dependencies"]?.children?.map { dep ->
Dependency(dep.key, dep.value.value)
} ?: listOf()
)
})
}
private fun parseTree(yarnLock: File): Node {
val root = Node(null)
var parent = root
var onNewLevel = true
yarnLock.useLines {
it.forEach { line ->
if (!line.startsWith("#")) {
var i = 0
while (i < line.length && line[i].isWhitespace()) i++
val indentPos = i
val indent = line.substring(0, indentPos)
val key = line.endsWith(":")
if (line.isNotEmpty()) {
val values = parseLine(line, indentPos)
if (onNewLevel) {
parent.indent = indent
onNewLevel = false
} else {
while (indent != parent.indent!!) {
parent = parent.parent!!
}
}
val child = Node(parent, if (values.size > 1) values[1] else null)
if (key) {
values.forEach {
parent.children[it] = child
}
parent = child
onNewLevel = true
} else {
parent.children[values[0]] = child
}
}
}
}
}
return root
}
private fun parseLine(line: String, indentPos: Int): List<String> {
val quotedLineResult = replaceQuoted(line.removeSuffix(":"))
return quotedLineResult
.parse(indentPos)
}
private fun replaceQuoted(line: String): QuotedLineResult {
var result: String = line
val quotedValues = QUOTED_VALUE.findAll(line)
.flatMap { it.groupValues.drop(1).asSequence() }
.toList()
quotedValues.forEachIndexed { index, item ->
result = result.replace(
"""
"$item"
""".trimIndent(),
"""
"$$index"
""".trimIndent()
)
}
return QuotedLineResult(
result,
quotedValues
)
}
}
}
private data class QuotedLineResult(
val value: String,
val quotedValues: List<String>
)
private fun QuotedLineResult.parse(indentPos: Int): List<String> {
return value
.substring(indentPos)
.split(" ")
.map { value ->
val withoutComma = value.removeSuffix(",")
if (withoutComma.startsWith("\"")) {
val (index) = QUOTED_PLACEHOLDER.find(withoutComma)!!.destructured
val restoredValue = """
"${quotedValues[index.toInt()]}"
""".trimIndent()
try {
JsonReader(StringReader(restoredValue)).nextString()
} catch (e: Throwable) {
restoredValue.removePrefix("\"").removeSuffix("\"")
}
} else withoutComma
}
}
private val QUOTED_VALUE = """"(.+?)"""".toRegex()
private val QUOTED_PLACEHOLDER = """\$(\d+)""".toRegex()
@@ -11,6 +11,8 @@ import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.utils.markResolvable 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.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
@@ -30,10 +32,12 @@ open class YarnPlugin : Plugin<Project> {
} }
val yarnRootExtension = this.extensions.create(YarnRootExtension.YARN, YarnRootExtension::class.java, this) val yarnRootExtension = this.extensions.create(YarnRootExtension.YARN, YarnRootExtension::class.java, this)
val nodeJs = NodeJsRootPlugin.apply(this) NodeJsRootPlugin.apply(project)
val nodeJs = this.kotlinNodeJsExtension
val nodeJsTaskProviders = this.kotlinNodeJsExtension
val setupTask = registerTask<YarnSetupTask>(YarnSetupTask.NAME) { val setupTask = registerTask<YarnSetupTask>(YarnSetupTask.NAME) {
it.dependsOn(nodeJs.nodeJsSetupTaskProvider) it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider)
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))
@@ -43,9 +47,19 @@ open class YarnPlugin : Plugin<Project> {
} }
val rootPackageJson = tasks.register(RootPackageJsonTask.NAME, RootPackageJsonTask::class.java) { task -> val rootPackageJson = tasks.register(RootPackageJsonTask.NAME, RootPackageJsonTask::class.java) { task ->
task.dependsOn(nodeJs.npmCachesSetupTaskProvider) task.dependsOn(nodeJsTaskProviders.npmCachesSetupTaskProvider)
task.group = NodeJsRootPlugin.TASKS_GROUP_NAME task.group = NodeJsRootPlugin.TASKS_GROUP_NAME
task.description = "Create root package.json" task.description = "Create root package.json"
task.npmResolutionManager.apply {
set(project.kotlinNpmResolutionManager)
disallowChanges()
}
task.onlyIf {
it as RootPackageJsonTask
it.npmResolutionManager.get().isConfiguringState()
}
} }
configureRequiresNpmDependencies(project, rootPackageJson) configureRequiresNpmDependencies(project, rootPackageJson)
@@ -62,7 +76,7 @@ open class YarnPlugin : Plugin<Project> {
it.description = "Clean unused local yarn version" it.description = "Clean unused local yarn version"
} }
val packageJsonUmbrella = nodeJs val packageJsonUmbrella = nodeJsTaskProviders
.packageJsonUmbrellaTaskProvider .packageJsonUmbrellaTaskProvider
yarnRootExtension.rootPackageJsonTaskProvider.configure { yarnRootExtension.rootPackageJsonTaskProvider.configure {
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.jetbrains.kotlin.gradle.targets.js.nodejs.PlatformHelper import org.jetbrains.kotlin.gradle.targets.js.nodejs.PlatformHelper
import org.gradle.api.Action import org.gradle.api.Action
import org.gradle.api.Incubating
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.internal.ConfigurationPhaseAware
@@ -20,7 +19,6 @@ import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
open class YarnRootExtension( open class YarnRootExtension(
@Transient
val project: Project val project: Project
) : ConfigurationPhaseAware<YarnEnv>() { ) : ConfigurationPhaseAware<YarnEnv>() {
init { init {
@@ -60,7 +58,7 @@ open class YarnRootExtension(
.withType(RootPackageJsonTask::class.java) .withType(RootPackageJsonTask::class.java)
.named(RootPackageJsonTask.NAME) .named(RootPackageJsonTask.NAME)
var resolutions: MutableList<YarnResolution> = mutableListOf() var resolutions: MutableList<YarnResolution> by Property(mutableListOf())
fun resolution(path: String, configure: Action<YarnResolution>) { fun resolution(path: String, configure: Action<YarnResolution>) {
resolutions.add( resolutions.add(
@@ -75,21 +73,6 @@ open class YarnRootExtension(
}) })
} }
@Incubating
fun disableGranularWorkspaces() {
val packageJsonUmbrella = NodeJsRootPlugin.apply(project)
.packageJsonUmbrellaTaskProvider
rootPackageJsonTaskProvider.configure {
it.dependsOn(packageJsonUmbrella)
}
project.allprojects
.forEach {
it.tasks.implementing(RequiresNpmDependencies::class).all {}
}
}
override fun finalizeConfiguration(): YarnEnv { override fun finalizeConfiguration(): YarnEnv {
val cleanableStore = CleanableStore[installationDir.path] val cleanableStore = CleanableStore[installationDir.path]
@@ -116,20 +99,10 @@ open class YarnRootExtension(
yarnLockMismatchReport = yarnLockMismatchReport, yarnLockMismatchReport = yarnLockMismatchReport,
reportNewYarnLock = reportNewYarnLock, reportNewYarnLock = reportNewYarnLock,
yarnLockAutoReplace = yarnLockAutoReplace, yarnLockAutoReplace = yarnLockAutoReplace,
yarnResolutions = resolutions
) )
} }
internal fun executeSetup() {
NodeJsRootPlugin.apply(project).executeSetup()
if (!download) return
val yarnSetupTask = yarnSetupTaskProvider.get()
yarnSetupTask.actions.forEach {
it.execute(yarnSetupTask)
}
}
companion object { companion object {
const val YARN: String = "kotlinYarn" const val YARN: String = "kotlinYarn"
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.FileSystemOperations
import org.gradle.api.provider.Provider import org.gradle.api.provider.Provider
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
@@ -15,24 +16,23 @@ import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.extractWithUpToDate import org.jetbrains.kotlin.gradle.targets.js.extractWithUpToDate
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import javax.inject.Inject import javax.inject.Inject
open class YarnSetupTask : DefaultTask() { abstract class YarnSetupTask : DefaultTask() {
@Transient @Transient
private val settings = project.yarn private val settings = project.yarn
private val env by lazy { settings.requireConfigured() } private val env by lazy { settings.requireConfigured() }
private val shouldDownload = settings.download private val shouldDownload = settings.download
private val archiveOperations = ArchiveOperationsCompat(project) @get:Inject
abstract internal val archiveOperations: ArchiveOperations
@get:Inject @get:Inject
internal open val fileHasher: FileHasher abstract internal val fileHasher: FileHasher
get() = error("Should be injected")
@get:Inject @get:Inject
internal open val fs: FileSystemOperations internal open val fs: FileSystemOperations
@@ -5,14 +5,10 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.gradle.api.logging.Logger import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import java.io.File import java.io.File
class YarnWorkspaces : YarnBasics() { class YarnWorkspaces : YarnBasics() {
@@ -25,18 +21,13 @@ class YarnWorkspaces : YarnBasics() {
} }
override fun prepareRootProject( override fun prepareRootProject(
rootProject: Project?,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
rootProjectName: String, rootProjectName: String,
rootProjectVersion: String, rootProjectVersion: String,
logger: Logger, logger: Logger,
subProjects: Collection<KotlinCompilationNpmResolution>, subProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>, resolutions: Map<String, String>,
forceFullResolve: Boolean
) { ) {
if (forceFullResolve) {
rootProject?.let { setup(it) }
}
return prepareRootPackageJson( return prepareRootPackageJson(
nodeJs, nodeJs,
rootProjectName, rootProjectName,
@@ -52,7 +43,7 @@ class YarnWorkspaces : YarnBasics() {
rootProjectName: String, rootProjectName: String,
rootProjectVersion: String, rootProjectVersion: String,
logger: Logger, logger: Logger,
npmProjects: Collection<KotlinCompilationNpmResolution>, npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String> resolutions: Map<String, String>
) { ) {
val rootPackageJsonFile = preparedFiles(nodeJs).single() val rootPackageJsonFile = preparedFiles(nodeJs).single()
@@ -71,8 +62,8 @@ class YarnWorkspaces : YarnBasics() {
services: ServiceRegistry, services: ServiceRegistry,
logger: Logger, logger: Logger,
nodeJs: NpmEnvironment, nodeJs: NpmEnvironment,
yarn: YarnEnv, yarn: YarnEnvironment,
npmProjects: Collection<KotlinCompilationNpmResolution>, npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
cliArgs: List<String> cliArgs: List<String>
) { ) {
val nodeJsWorldDir = nodeJs.rootPackageDir val nodeJsWorldDir = nodeJs.rootPackageDir
@@ -86,15 +77,13 @@ class YarnWorkspaces : YarnBasics() {
NpmApi.resolveOperationDescription("yarn"), NpmApi.resolveOperationDescription("yarn"),
cliArgs cliArgs
) )
yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.externalNpmDependencies })
} }
private fun saveRootProjectWorkspacesPackageJson( private fun saveRootProjectWorkspacesPackageJson(
rootProjectName: String, rootProjectName: String,
rootProjectVersion: String, rootProjectVersion: String,
logger: Logger, logger: Logger,
npmProjects: Collection<KotlinCompilationNpmResolution>, npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>, resolutions: Map<String, String>,
rootPackageJsonFile: File rootPackageJsonFile: File
) { ) {
@@ -102,7 +91,7 @@ class YarnWorkspaces : YarnBasics() {
val rootPackageJson = PackageJson(rootProjectName, rootProjectVersion) val rootPackageJson = PackageJson(rootProjectName, rootProjectVersion)
rootPackageJson.private = true rootPackageJson.private = true
val npmProjectWorkspaces = npmProjects.map { it.npmProject.dir.relativeTo(nodeJsWorldDir).path } val npmProjectWorkspaces = npmProjects.map { it.npmProjectDir.relativeTo(nodeJsWorldDir).path }
val importedProjectWorkspaces = val importedProjectWorkspaces =
YarnImportedPackagesVersionResolver(logger, npmProjects, nodeJsWorldDir).resolveAndUpdatePackages() YarnImportedPackagesVersionResolver(logger, npmProjects, nodeJsWorldDir).resolveAndUpdatePackages()
@@ -53,45 +53,6 @@ internal fun checkGradleCompatibility(
internal val AbstractArchiveTask.archivePathCompatible: File internal val AbstractArchiveTask.archivePathCompatible: File
get() = archiveFile.get().asFile get() = archiveFile.get().asFile
internal class ArchiveOperationsCompat(@Transient private val project: Project) : Serializable {
private val archiveOperations: Any? = try {
(project as ProjectInternal).services.get(ArchiveOperations::class.java)
} catch (e: NoClassDefFoundError) {
// Gradle version < 6.6
null
}
fun zipTree(obj: Any): FileTree {
return when (archiveOperations) {
is ArchiveOperations -> archiveOperations.zipTree(obj)
else -> project.zipTree(obj)
}
}
fun tarTree(obj: Any): FileTree {
return when (archiveOperations) {
is ArchiveOperations -> archiveOperations.tarTree(obj)
else -> project.tarTree(obj)
}
}
}
internal class FileSystemOperationsCompat(@Transient private val project: Project) : Serializable {
private val fileSystemOperations: Any? = try {
(project as ProjectInternal).services.get(FileSystemOperations::class.java)
} catch (e: NoClassDefFoundError) {
// Gradle version < 6.0
null
}
fun copy(action: (CopySpec) -> Unit): WorkResult? {
return when (fileSystemOperations) {
is FileSystemOperations -> fileSystemOperations.copy(action)
else -> project.copy(action)
}
}
}
// Gradle dropped out getOwnerBuildOperationId. Workaround to build correct plugin for Gradle < 6.8 // Gradle dropped out getOwnerBuildOperationId. Workaround to build correct plugin for Gradle < 6.8
// See https://github.com/gradle/gradle/commit/0296f4441ae69ad608cfef6a90fef3fdf314fa2c // See https://github.com/gradle/gradle/commit/0296f4441ae69ad608cfef6a90fef3fdf314fa2c
internal interface LegacyTestDescriptorInternal : TestDescriptor { internal interface LegacyTestDescriptorInternal : TestDescriptor {