[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",
":publicPackageJson",
":rootPackageJson",
":kotlinNpmInstall",
":compileKotlinJs",
":nodeTest",
) + if (irBackend) listOf(":compileProductionExecutableKotlinJs") else emptyList()
@@ -109,7 +108,6 @@ abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean)
":packageJson",
":publicPackageJson",
":rootPackageJson",
":kotlinNpmInstall",
":compileKotlinJs",
":nodeTest",
) + if (irBackend) listOf(":compileProductionExecutableKotlinJs") else emptyList()
@@ -125,7 +123,7 @@ abstract class AbstractJsConfigurationCacheIT(protected val irBackend: Boolean)
assertSimpleConfigurationCacheScenarioWorks(
"assemble", "kotlinStoreYarnLock",
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) {
buildGradleKts.modify { originalScript ->
buildString {
append("import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager")
append(originalScript)
append(
"""
@@ -1583,7 +1584,7 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
| val nodejs = the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>()
| tasks.named<org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask>("kotlinNpmInstall") {
| doFirst {
| nodejs.npmResolutionManager.state =
| project.rootProject.kotlinNpmResolutionManager.get().state =
| 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(
NpmPackage("webpack"),
NpmPackage("webpack", "4.46.0", "webpack4"),
NpmPackage("webpack-cli"),
NpmPackage("webpack-cli", "3.3.12", "webpackCli3"),
NpmPackage("webpack-bundle-analyzer"),
NpmPackage("webpack-dev-server"),
NpmPackage("webpack-dev-server", "3.11.2", "webpackDevServer3"),
NpmPackage("source-map-loader"),
NpmPackage("source-map-loader", "1.1.0", "sourceMapLoader1"),
NpmPackage("source-map-support"),
NpmPackage("css-loader"),
NpmPackage("style-loader"),
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.gradle.internal
import java.io.Serializable
import kotlin.reflect.KProperty
abstract class ConfigurationPhaseAware<C : Any> {
@@ -456,19 +456,6 @@ internal class PropertiesProvider private constructor(private val project: Proje
val publishJsCompilerAttribute: Boolean
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
*/
@@ -106,7 +106,7 @@ class DefaultKotlinDependencyHandler(
version: String,
): NpmDependency =
NpmDependency(
project = project,
objectFactory = project.objects,
name = name,
version = version,
)
@@ -134,7 +134,7 @@ class DefaultKotlinDependencyHandler(
version: String
): NpmDependency =
NpmDependency(
project = project,
objectFactory = project.objects,
name = name,
version = version,
scope = NpmDependency.Scope.DEV
@@ -161,7 +161,7 @@ class DefaultKotlinDependencyHandler(
version: String,
): NpmDependency =
NpmDependency(
project = project,
objectFactory = project.objects,
name = name,
version = version,
scope = NpmDependency.Scope.OPTIONAL,
@@ -190,7 +190,7 @@ class DefaultKotlinDependencyHandler(
version: String
): NpmDependency =
NpmDependency(
project = project,
objectFactory = project.objects,
name = name,
version = version,
scope = NpmDependency.Scope.PEER
@@ -202,7 +202,7 @@ class DefaultKotlinDependencyHandler(
scope: NpmDependency.Scope,
): NpmDependency =
directoryNpmDependency(
project = project,
objectFactory = project.objects,
name = name,
directory = directory,
scope = scope,
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.api.Project
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 {
override fun createDependency(project: Project, scope: NpmDependency.Scope): Dependency =
data class KotlinGradleNpmPackage(val simpleModuleName: String) : Serializable {
fun createDependency(project: Project): Dependency =
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.KotlinJsCompilerType.LEGACY
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.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.KotlinNodeJs
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
@@ -147,8 +152,13 @@ constructor(
private val propertiesProvider = PropertiesProvider(project)
private val commonLazy by lazy {
NpmResolverPlugin.apply(project)
}
//Browser
private val browserLazyDelegate = lazy {
commonLazy
project.objects.newInstance(KotlinBrowserJs::class.java, this).also {
it.configure()
@@ -177,6 +187,7 @@ constructor(
//node.js
private val nodejsLazyDelegate = lazy {
commonLazy
project.objects.newInstance(KotlinNodeJs::class.java, this).also {
it.configure()
@@ -5,11 +5,11 @@
package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.model.ObjectFactory
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
data class NpmPackageVersion(val name: String, var version: String) : RequiredKotlinJsDependency {
override fun createDependency(project: Project, scope: NpmDependency.Scope): Dependency =
NpmDependency(project, name, version, scope)
override fun createDependency(objectFactory: ObjectFactory, scope: NpmDependency.Scope): Dependency =
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
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 webpackCli = NpmPackageVersion("webpack-cli", "4.10.0")
val webpackBundleAnalyzer = NpmPackageVersion("webpack-bundle-analyzer", "4.5.0")
val webpackDevServer = NpmPackageVersion("webpack-dev-server", "4.9.3")
val webpackDevServer = NpmPackageVersion("webpack-dev-server", "4.11.1")
val sourceMapLoader = NpmPackageVersion("source-map-loader", "4.0.0")
val sourceMapSupport = NpmPackageVersion("source-map-support", "0.5.21")
val cssLoader = NpmPackageVersion("css-loader", "6.7.1")
val styleLoader = NpmPackageVersion("style-loader", "3.3.1")
val sassLoader = NpmPackageVersion("sass-loader", "13.0.2")
val sass = NpmPackageVersion("sass", "1.54.3")
val sassLoader = NpmPackageVersion("sass-loader", "13.1.0")
val sass = NpmPackageVersion("sass", "1.55.0")
val toStringLoader = NpmPackageVersion("to-string-loader", "1.2.0")
val miniCssExtractPlugin = NpmPackageVersion("mini-css-extract-plugin", "2.6.1")
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 karmaPhantomjsLauncher = NpmPackageVersion("karma-phantomjs-launcher", "1.0.4")
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 karmaSourcemapLoader = NpmPackageVersion("karma-sourcemap-loader", "0.3.8")
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")
}
@@ -7,9 +7,10 @@ package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.model.ObjectFactory
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import java.io.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.jetbrains.kotlin.gradle.dsl.KotlinJsDce
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.pm20.util.archivesName
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.KotlinJsBrowserDsl
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.karma.KotlinKarma
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.Mode
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.utils.doNotTrackStateCompat
import org.jetbrains.kotlin.gradle.utils.newFileProperty
@@ -42,15 +40,11 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
KotlinJsIrSubTarget(target, "browser"),
KotlinJsBrowserDsl {
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val webpackTaskConfigurations: 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
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(
compilation: KotlinJsIrCompilation
) {
val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
val commonRunTask = registerSubTargetTask<Task>(disambiguateCamelCased(RUN_TASK_NAME)) {}
compilation.binaries
@@ -134,10 +125,7 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
task.dependsOn(binary.linkSyncTask)
task.outputDirectory.convention(distsDirectory).finalizeValueOnRead()
webpackMajorVersion.choose(
{ task.args.add(0, "serve") },
{ task.bin = "webpack-dev-server/bin/webpack-dev-server.js" }
)()
task.args.add(0, "serve")
task.description = "start ${mode.name.toLowerCaseAsciiOnly()} webpack dev server"
val openValue = if (compilation.platformType == KotlinPlatformType.wasm) {
@@ -156,29 +144,19 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
true
}
webpackMajorVersion.choose(
{
task.devServer = KotlinWebpackConfig.DevServer(
open = openValue,
static = mutableListOf(
"./kotlin",
compilation.output.resourcesDir.canonicalPath,
),
client = KotlinWebpackConfig.DevServer.Client(
KotlinWebpackConfig.DevServer.Client.Overlay(
errors = true,
warnings = false
)
)
task.devServer = KotlinWebpackConfig.DevServer(
open = openValue,
static = mutableListOf(
"./kotlin",
compilation.output.resourcesDir.canonicalPath
),
client = KotlinWebpackConfig.DevServer.Client(
KotlinWebpackConfig.DevServer.Client.Overlay(
errors = true,
warnings = false
)
},
{
task.devServer = KotlinWebpackConfig.DevServer(
open = true,
contentBase = mutableListOf(compilation.output.resourcesDir.canonicalPath)
)
}
)()
)
)
task.doNotTrackStateCompat("Tracked by external webpack tool")
@@ -205,7 +183,6 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
compilation: KotlinJsIrCompilation
) {
val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
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.TaskProvider
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.whenEvaluated
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.KotlinJsBinaryMode
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.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.dependsOn
@@ -52,8 +53,6 @@ abstract class KotlinJsIrSubTarget(
}
internal fun configure() {
NpmResolverPlugin.apply(project)
target.compilations.all {
val npmProject = it.npmProject
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.binaryen.BinaryenExec
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.typescript.TypeScriptValidationTask
import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask
@@ -110,6 +111,7 @@ constructor(
}
private val commonLazy by lazy {
NpmResolverPlugin.apply(project)
compilations.all { compilation ->
compilation.binaries
.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.targets.js.dsl.KotlinJsNodeDsl
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.KotlinWasmNode
import org.jetbrains.kotlin.gradle.tasks.dependsOn
@@ -21,7 +21,8 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) :
KotlinJsIrSubTargetBase(target, "node"),
KotlinJsNodeDsl {
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val nodeJsTaskProviders = project.rootProject.kotlinNodeJsExtension
override val testTaskDescription: String
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) {
test.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
nodeJs.nodeJsSetupTaskProvider
nodeJsTaskProviders.npmInstallTaskProvider,
nodeJsTaskProviders.storeYarnLockTaskProvider,
nodeJsTaskProviders.nodeJsSetupTaskProvider
)
}
@@ -1,10 +1,12 @@
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 java.io.File
data class NodeJsEnv(
val cleanableStore: CleanableStore,
val rootPackageDir: File,
val nodeDir: File,
val nodeBinDir: File,
val nodeExecutable: String,
@@ -12,7 +14,9 @@ data class NodeJsEnv(
val platformName: String,
val architectureName: String,
val ivyDependency: String,
val downloadBaseUrl: String
val downloadBaseUrl: String,
val packageManager: NpmApi,
) {
val isWindows: Boolean
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.targets.js.RequiredKotlinJsDependency
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.npmProject
import org.jetbrains.kotlin.gradle.tasks.registerTask
@@ -48,10 +49,6 @@ constructor(
@NormalizeLineEndings
val inputFileProperty: RegularFileProperty = project.newFileProperty()
@get:Internal
override val nodeModulesRequired: Boolean
get() = true
@get:Internal
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency> by lazy {
mutableSetOf<RequiredKotlinJsDependency>().also {
@@ -92,7 +89,9 @@ constructor(
): TaskProvider<NodeJsExec> {
val target = compilation.target
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
return project.registerTask(
@@ -103,8 +102,8 @@ constructor(
it.executable = nodeJs.requireConfigured().nodeExecutable
it.workingDir = npmProject.dir
it.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
nodeJsTaskProviders.npmInstallTaskProvider,
nodeJsTaskProviders.storeYarnLockTaskProvider,
)
it.dependsOn(compilation.compileKotlinTaskProvider)
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.plugin.PropertiesProvider
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.resolver.KotlinRootNpmResolver
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.KotlinNpmInstallTask
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.RESTORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.STORE_YARN_LOCK_NAME
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File
import java.io.Serializable
open class NodeJsRootExtension(@Transient val rootProject: Project) : ConfigurationPhaseAware<NodeJsEnv>(), Serializable {
open class NodeJsRootExtension(
val project: Project
) : ConfigurationPhaseAware<NodeJsEnv>() {
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 {
logger.kotlinInfo("Storing cached files in $it")
private val gradleHome = project.gradle.gradleUserHomeDir.also {
project.logger.kotlinInfo("Storing cached files in $it")
}
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())
@Transient
private val projectProperties = PropertiesProvider(rootProject)
val taskRequirements: TasksRequirements
get() = resolver.tasksRequirements
private val errorGenerateExternals = run {
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()
)
}
}
lateinit var resolver: KotlinRootNpmResolver
val taskRequirements: TasksRequirements = TasksRequirements()
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 rootPackageDir: File = project.buildDir.resolve("js")
val projectPackagesDir: File
get() = rootPackageDir.resolve("packages")
@@ -123,6 +81,8 @@ open class NodeJsRootExtension(@Transient val rootProject: Project) : Configurat
val nodeModulesGradleCacheDir: File
get() = rootPackageDir.resolve("packages_imported")
val versions = NpmVersions()
override fun finalizeConfiguration(): NodeJsEnv {
val platformHelper = PlatformHelper
val platform = platformHelper.osName
@@ -146,28 +106,35 @@ open class NodeJsRootExtension(@Transient val rootProject: Project) : Configurat
return NodeJsEnv(
cleanableStore = cleanableStore,
rootPackageDir = rootPackageDir,
nodeDir = nodeDir,
nodeBinDir = nodeBinDir,
nodeExecutable = getExecutable("node", nodeCommand, "exe"),
platformName = platform,
architectureName = architecture,
ivyDependency = getIvyDependency(),
downloadBaseUrl = nodeDownloadBaseUrl
downloadBaseUrl = nodeDownloadBaseUrl,
packageManager = packageManager
)
}
internal fun executeSetup() {
if (download) {
val nodeJsSetupTask = nodeJsSetupTaskProvider.get()
nodeJsSetupTask.actions.forEach {
it.execute(nodeJsSetupTask)
}
}
}
val nodeJsSetupTaskProvider: TaskProvider<out NodeJsSetupTask>
get() = project.tasks.withType(NodeJsSetupTask::class.java).named(NodeJsSetupTask.NAME)
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 {
const val EXTENSION_NAME: String = "kotlinNodeJs"
@@ -9,18 +9,25 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
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.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.tasks.KotlinNpmInstallTask
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.tasks.CleanDataTask
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> {
override fun apply(project: Project): Unit = project.run {
override fun apply(project: Project) {
MultiplePluginDeclarationDetector.detect(project)
project.plugins.apply(BasePlugin::class.java)
@@ -29,35 +36,93 @@ open class NodeJsRootPlugin : Plugin<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.description = "Download and install a local node/npm version"
it.configuration = provider {
this.project.configurations.detachedConfiguration(this.project.dependencies.create(it.ivyDependency))
it.configuration = project.provider {
project.configurations.detachedConfiguration(project.dependencies.create(it.ivyDependency))
.markResolvable()
.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.gradleNodeModules.set(gradleNodeModulesProvider)
it.compositeNodeModules.set(compositeNodeModulesProvider)
}
registerTask<KotlinNpmInstallTask>(KotlinNpmInstallTask.NAME) {
val npmInstall = project.registerTask<KotlinNpmInstallTask>(KotlinNpmInstallTask.NAME) {
it.dependsOn(setupTask)
it.dependsOn(setupFileHasherTask)
it.group = TASKS_GROUP_NAME
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)
tasks.register("node" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) {
it.cleanableStoreProvider = provider { settings.requireConfigured().cleanableStore }
nodeJs.resolver = KotlinRootNpmResolver(
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.description = "Clean unused local node version"
}
@@ -69,7 +134,33 @@ open class NodeJsRootPlugin : Plugin<Project> {
fun apply(rootProject: Project): NodeJsRootExtension {
check(rootProject == rootProject.rootProject)
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.artifacts.Configuration
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
@@ -9,8 +10,8 @@ import org.gradle.api.tasks.*
import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.gradle.logging.kotlinInfo
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.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics
import java.io.File
import java.net.URI
@@ -21,12 +22,13 @@ import javax.inject.Inject
abstract class NodeJsSetupTask : DefaultTask() {
@Transient
private val settings = NodeJsRootPlugin.apply(project.rootProject)
private val settings = project.rootProject.kotlinNodeJsExtension
private val env by lazy { settings.requireConfigured() }
private val shouldDownload = settings.download
private val archiveOperations = ArchiveOperationsCompat(project)
@get:Inject
abstract internal val archiveOperations: ArchiveOperations
@get:Inject
internal open val fileHasher: FileHasher
@@ -36,8 +38,7 @@ abstract class NodeJsSetupTask : DefaultTask() {
internal abstract val objects: ObjectFactory
@get:Inject
internal open val fs: FileSystemOperations
get() = error("Should be injected")
abstract internal val fs: FileSystemOperations
val ivyDependency: String
@Input get() = env.ivyDependency
@@ -30,7 +30,7 @@ class TasksRequirements : Serializable {
val requiredNpmDependencies = requirements
.asSequence()
.map { it.createDependency(task.compilation.target.project) }
.map { it.createDependency(task.compilation.target.project.objects) }
.filterIsInstance<NpmDependency>()
.toMutableSet()
@@ -16,8 +16,8 @@ import java.io.File
/**
* Cache for storing already created [GradleNodeModule]s
*/
internal abstract class AbstractNodeModulesCache : AutoCloseable, BuildService<AbstractNodeModulesCache.Parameters> {
internal interface Parameters : BuildServiceParameters {
abstract class AbstractNodeModulesCache : AutoCloseable, BuildService<AbstractNodeModulesCache.Parameters> {
interface Parameters : BuildServiceParameters {
val rootProjectDir: DirectoryProperty
val cacheDir: DirectoryProperty
}
@@ -5,14 +5,24 @@
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.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
internal interface UsesCompositeNodeModulesCache : Task {
@get:Internal
val compositeNodeModules: Property<CompositeNodeModulesCache>
}
/**
* Cache for storing already created [GradleNodeModule]s
*/
internal abstract class CompositeNodeModulesCache : AbstractNodeModulesCache() {
abstract class CompositeNodeModulesCache : AbstractNodeModulesCache() {
override val type: String
get() = "composite"
@@ -24,4 +34,44 @@ internal abstract class CompositeNodeModulesCache : AbstractNodeModulesCache() {
val module = CompositeNodeModuleBuilder(file, parameters.cacheDir.get().asFile)
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
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.FileTree
import org.jetbrains.kotlin.gradle.targets.js.HTML
import org.jetbrains.kotlin.gradle.targets.js.JS
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.ir.KLIB_TYPE
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.gradle.utils.FileSystemOperationsCompat
import java.io.File
/**
* Creates fake NodeJS module directory from given gradle [dependency].
*/
internal class GradleNodeModuleBuilder(
val fs: FileSystemOperationsCompat,
val archiveOperations: ArchiveOperationsCompat,
val fs: FileSystemOperations,
val archiveOperations: ArchiveOperations,
val moduleName: String,
val moduleVersion: String,
val srcFiles: Collection<File>,
@@ -5,28 +5,40 @@
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.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.gradle.utils.FileSystemOperationsCompat
import org.gradle.api.provider.Property
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 javax.inject.Inject
internal interface UsesGradleNodeModulesCache : Task {
@get:Internal
val gradleNodeModules: Property<GradleNodeModulesCache>
}
/**
* 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
// https://github.com/gradle/gradle/commit/d02b9d84c08dba64775fb9581e3280f88d319a21
@Transient
lateinit var fs: FileSystemOperationsCompat
@get:Inject
abstract val fs: FileSystemOperations
override val type: String
get() = "gradle"
// TODO: replace by injected service org.gradle.api.file.ArchiveOperations once min supported Gradle is 6.6
@Transient
lateinit var archiveOperations: ArchiveOperationsCompat
@get:Inject
abstract val archiveOperations: ArchiveOperations
override fun buildImportedPackage(
name: String,
@@ -37,4 +49,44 @@ internal abstract class GradleNodeModulesCache : AbstractNodeModulesCache() {
module.visitArtifacts()
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
import org.gradle.api.Incubating
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.project.ProjectInternal
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.BuildServiceParameters
import org.gradle.api.tasks.Internal
import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.internal.isInIdeaSync
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.Installation
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.targets.js.npm.resolver.KotlinProjectNpmResolver
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
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
/**
* # NPM resolution state manager
*
* ## Resolving process from Gradle
*
* **configuring**. Global initial state. [NpmResolverPlugin] should be applied for each project
* that requires NPM resolution. When applied, [KotlinProjectNpmResolver] will be created for the
* corresponding project and will subscribe to all js compilations. [NpmResolverPlugin] requires
* kotlin mulitplatform or plaform plugin applied first.
*
* **up-to-date-checked**. This state is compilation local: one compilation may be in up-to-date-checked
* state, while another may be steel in configuring state. New compilations may be added in this
* state, but compilations that are already up-to-date-checked cannot be changed.
* Initiated by calling [KotlinPackageJsonTask.producerInputs] getter (will be called by Gradle).
* [KotlinCompilationNpmResolver] will create and **resolve** aggregated compilation configuration,
* 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
internal interface UsesKotlinNpmResolutionManager : Task {
@get:Internal
val npmResolutionManager: Property<KotlinNpmResolutionManager>
}
abstract class KotlinNpmResolutionManager : BuildService<KotlinNpmResolutionManager.Parameters> {
interface Parameters : BuildServiceParameters {
val resolution: Property<KotlinRootNpmResolution>
// pulled up from compilation resolver since it was failing with ClassNotFoundException on deserialization, see KT-49061
val packageJsonHandlers: MapProperty<String, List<PackageJson.() -> Unit>>
val gradleNodeModulesProvider: Property<GradleNodeModulesCache>
val compositeNodeModulesProvider: Property<CompositeNodeModulesCache>
}
val resolver = KotlinRootNpmResolver(nodeJsSettings, forceFullResolve)
val resolution
get() = parameters.resolution
internal abstract class KotlinNpmResolutionManagerStateHolder : BuildService<BuildServiceParameters.None> {
@Volatile
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
}
@Volatile
var state: ResolutionState = ResolutionState.Configuring(resolution.get())
sealed class ResolutionState {
abstract val npmProjects: List<NpmProject>
class Configuring(val resolver: KotlinRootNpmResolver) : ResolutionState() {
override val npmProjects: List<NpmProject>
get() = resolver.compilations.map { it.npmProject }
}
class Configuring(val resolution: KotlinRootNpmResolution) : ResolutionState()
open class Prepared(val preparedInstallation: KotlinRootNpmResolver.Installation) : ResolutionState() {
override val npmProjects: List<NpmProject>
get() = npmProjectsByProjectResolutions(preparedInstallation.projectResolutions)
}
open class Prepared(val preparedInstallation: Installation) : ResolutionState()
class Installed internal constructor(internal val resolved: KotlinRootNpmResolution) : ResolutionState() {
override val npmProjects: List<NpmProject>
get() = npmProjectsByProjectResolutions(resolved.projects)
}
class Installed() : 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 } }
}
}
class Error(val wrappedException: Throwable) : ResolutionState()
}
@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 =
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(
reason: String? = "",
args: List<String> = emptyList(),
services: ServiceRegistry,
logger: Logger
): KotlinRootNpmResolution? {
synchronized(stateHolder) {
logger: Logger,
npmEnvironment: NpmEnvironment,
yarnEnvironment: YarnEnvironment,
): Unit? {
synchronized(this) {
if (state is ResolutionState.Installed) {
return (state as ResolutionState.Installed).resolved
return Unit
}
if (state is ResolutionState.Error) {
@@ -175,14 +80,9 @@ class KotlinNpmResolutionManager(@Transient private val nodeJsSettings: NodeJsRo
}
return try {
val installUpToDate = nodeJsSettings?.npmInstallTaskProvider?.get()?.state?.upToDate ?: false
val forceUpToDate = installUpToDate && !forceFullResolve
val installation = prepareIfNeeded(requireUpToDateReason = reason, logger = logger)
val resolution = installation
.install(forceUpToDate, args, services, logger)
state = ResolutionState.Installed(resolution)
resolution
val installation: Installation = prepareIfNeeded(logger = logger, npmEnvironment, yarnEnvironment)
installation.install(args, services, logger, npmEnvironment, yarnEnvironment)
state = ResolutionState.Installed()
} catch (e: Exception) {
state = ResolutionState.Error(e)
throw e
@@ -190,93 +90,97 @@ class KotlinNpmResolutionManager(@Transient private val nodeJsSettings: NodeJsRo
}
}
internal val packageJsonFiles: Collection<File>
get() = state.npmProjects.map { it.packageJsonFile }
// internal val packageJsonFiles: Collection<File>
// 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(
requireUpToDateReason: String? = null,
requireNotPrepared: Boolean = false,
logger: Logger
): KotlinRootNpmResolver.Installation {
fun alreadyResolved(installation: KotlinRootNpmResolver.Installation): KotlinRootNpmResolver.Installation {
if (requireNotPrepared) error("Project already prepared")
return installation
}
logger: Logger,
npmEnvironment: NpmEnvironment,
yarnEnvironment: YarnEnvironment,
): Installation {
val state0 = this.state
return when (state0) {
is ResolutionState.Prepared -> {
alreadyResolved(state0.preparedInstallation)
state0.preparedInstallation
}
is ResolutionState.Configuring -> {
synchronized(stateHolder) {
synchronized(this) {
val state1 = this.state
when (state1) {
is ResolutionState.Prepared -> alreadyResolved(state1.preparedInstallation)
is ResolutionState.Prepared -> state1.preparedInstallation
is ResolutionState.Configuring -> {
val upToDate = nodeJsSettings?.rootPackageJsonTaskProvider?.get()?.state?.upToDate ?: true
if (requireUpToDateReason != null && !upToDate) {
error("NPM dependencies should be resolved $requireUpToDateReason")
}
state1.resolver.prepareInstallation(logger).also {
state1.resolution.prepareInstallation(
logger,
npmEnvironment,
yarnEnvironment,
this
).also {
this.state = ResolutionState.Prepared(it)
}
}
is ResolutionState.Installed -> error("Project already installed")
is ResolutionState.Error -> throw state1.wrappedException
}
}
}
is ResolutionState.Installed -> error("Project already installed")
is ResolutionState.Error -> throw state0.wrappedException
}
}
internal fun getNpmDependencyResolvedCompilation(npmDependency: NpmDependency): KotlinCompilationNpmResolution? {
val project = npmDependency.project!!
val projectPath = project.path
val services = (project as ProjectInternal).services
val logger = project.logger
companion object {
private val serviceClass = KotlinNpmResolutionManager::class.java
private val serviceName = serviceClass.name
val resolvedProject =
if (forceFullResolve) {
installIfNeeded(reason = null, services = services, logger = logger)?.get(projectPath) ?: return null
} else {
// 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)
val state0 = state
when (state0) {
is ResolutionState.Prepared -> state0.preparedInstallation[projectPath]
is ResolutionState.Configuring -> {
return null
//error("Cannot use NpmDependency before :kotlinNpmInstall task execution")
}
is ResolutionState.Installed -> state0.resolved[projectPath]
is ResolutionState.Error -> {
return null
}
}
private fun registerIfAbsentImpl(
project: Project,
resolution: Provider<KotlinRootNpmResolution>?,
packageJsonHandlers: Provider<Map<String, MutableList<PackageJson.() -> Unit>>>?,
gradleNodeModulesProvider: Provider<GradleNodeModulesCache>,
compositeNodeModulesProvider: Provider<CompositeNodeModulesCache>
): Provider<KotlinNpmResolutionManager> {
project.gradle.sharedServices.registrations.findByName(serviceName)?.let {
@Suppress("UNCHECKED_CAST")
return it.service as Provider<KotlinNpmResolutionManager>
}
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)
where T : RequiresNpmDependencies,
T : Task {
val targetRequired = resolver.taskRequirements.byTask[task.path]?.toSet() ?: setOf()
requireNotNull(resolution, message)
requireNotNull(packageJsonHandlers, message)
task.requiredNpmDependencies.forEach {
check(it in targetRequired) {
"${it.createDependency(task.project)} required by $task was not found resolved at the time of nodejs package manager call. " +
"This may be caused by changing $task configuration after npm dependencies resolution."
return project.gradle.sharedServices.registerIfAbsent(serviceName, serviceClass) {
it.parameters.resolution.set(resolution)
it.parameters.packageJsonHandlers.set(packageJsonHandlers)
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
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry
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.nodejs.NodeJsEnv
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.YarnResolution
import java.io.File
import java.io.Serializable
@@ -18,36 +18,26 @@ import java.io.Serializable
* NodeJS package manager API
*/
interface NpmApi : Serializable {
fun setup(project: Project)
fun preparedFiles(nodeJs: NpmEnvironment): Collection<File>
fun prepareRootProject(
rootProject: Project?,
nodeJs: NpmEnvironment,
rootProjectName: String,
rootProjectVersion: String,
logger: Logger,
subProjects: Collection<KotlinCompilationNpmResolution>,
subProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>,
forceFullResolve: Boolean
)
fun resolveRootProject(
services: ServiceRegistry,
logger: Logger,
nodeJs: NpmEnvironment,
yarn: YarnEnv,
npmProjects: Collection<KotlinCompilationNpmResolution>,
yarn: YarnEnvironment,
npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
cliArgs: List<String>
)
fun resolveDependency(
npmResolution: KotlinCompilationNpmResolution,
dependency: NpmDependency,
transitive: Boolean
): Set<File>
companion object {
fun resolveOperationDescription(packageManagerTitle: String): String =
"Resolving NPM dependencies using $packageManagerTitle"
@@ -57,12 +47,29 @@ interface NpmApi : Serializable {
data class NpmEnvironment(
val rootPackageDir: File,
val nodeExecutable: String,
val isWindows: Boolean
val isWindows: Boolean,
val packageManager: NpmApi
) : Serializable
internal val NodeJsRootExtension.asNpmEnvironment
internal val NodeJsEnv.asNpmEnvironment
get() = NpmEnvironment(
rootPackageDir,
requireConfigured().nodeExecutable,
requireConfigured().isWindows
nodeExecutable,
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
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
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.internal.artifacts.DependencyResolveContext
import org.gradle.api.internal.artifacts.ResolvableDependency
import org.gradle.api.internal.artifacts.dependencies.SelfResolvingDependencyInternal
import org.gradle.api.model.ObjectFactory
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.resolved.KotlinCompilationNpmResolution
import java.io.File
data class NpmDependency(
@Transient
internal val project: Project?,
val objectFactory: ObjectFactory,
val scope: Scope = Scope.NORMAL,
private val name: String,
private val version: String,
val scope: Scope = Scope.NORMAL,
) : SelfResolvingDependency,
SelfResolvingDependencyInternal,
ResolvableDependency,
FileCollectionDependency {
) : FileCollectionDependency,
SelfResolvingDependencyInternal {
enum class Scope {
NORMAL,
@@ -38,73 +30,33 @@ data class NpmDependency(
PEER
}
private var reason: String? = null
override fun getGroup(): String? = null
override fun getName() = name
override fun getVersion() = version
internal var parent: NpmDependency? = null
internal val dependencies = mutableSetOf<NpmDependency>()
internal var resolvedVersion: String? = null
internal var integrity: String? = null
override fun resolve(transitive: Boolean): Set<File> =
resolveProject()
?.let {
it
.npmProject
.nodeJs
.packageManager
.resolveDependency(
it,
this,
transitive
)
}
?: mutableSetOf()
resolve()
override fun resolve(): MutableSet<File> {
val npmPackage = parent?.resolveProject()
?: resolveProject()
?: return mutableSetOf()
return mutableSetOf(npmPackage.npmProject.resolve(key)!!)
}
override fun getTargetComponentId(): ComponentIdentifier? = null
override fun resolve(): MutableSet<File> = mutableSetOf()
override fun resolve(context: DependencyResolveContext) {
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 getFiles(): FileCollection = objectFactory.fileCollection()
override fun getBuildDependencies(): TaskDependency = TaskDependency { mutableSetOf() }
override fun contentEquals(dependency: Dependency) = this == dependency
override fun getTargetComponentId() = DefaultLibraryBinaryIdentifier(project!!.path, key, "npm")
override fun copy(): Dependency = this.copy(name = name)
private var reason: String? = null
override fun because(reason: String?) {
this.reason = reason
}
@@ -113,20 +65,20 @@ data class NpmDependency(
}
internal fun directoryNpmDependency(
project: Project,
objectFactory: ObjectFactory,
scope: NpmDependency.Scope,
name: String,
directory: File,
scope: NpmDependency.Scope,
): NpmDependency {
check(directory.isDirectory) {
"Dependency on local path should point on directory but $directory found"
}
return NpmDependency(
project = project,
objectFactory = objectFactory,
scope = scope,
name = name,
version = fileVersion(directory),
scope = scope,
)
}
@@ -118,7 +118,7 @@ private abstract class NpmDependencyExtensionDelegate(
version: String,
): NpmDependency =
NpmDependency(
project = project,
objectFactory = project.objects,
name = name,
version = version,
scope = scope,
@@ -289,10 +289,10 @@ private fun defaultNpmDependencyDelegate(
directory: File,
): NpmDependency =
directoryNpmDependency(
project = project,
objectFactory = project.objects,
scope = scope,
name = name,
directory = directory,
scope = scope,
)
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.targets.js.dsl.KotlinJsTargetDsl
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.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
import java.io.File
@@ -37,7 +38,7 @@ open class NpmProject(@Transient val compilation: KotlinJsCompilation) : Seriali
}
@Transient
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
val nodeJs = project.rootProject.kotlinNodeJsExtension
val dir: File by lazy {
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.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.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> {
override fun apply(target: Project) {
val project = target
override fun apply(project: Project) {
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 {
@@ -6,33 +6,40 @@
package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
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.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.utils.property
import java.io.File
import javax.inject.Inject
open class PublicPackageJsonTask
@Inject
constructor(
@Transient
private val compilation: KotlinJsCompilation
) : DefaultTask() {
private val npmProject = compilation.npmProject
abstract class PublicPackageJsonTask :
DefaultTask(),
UsesKotlinNpmResolutionManager {
@Transient
private val nodeJs = npmProject.nodeJs
private val resolutionManager = nodeJs.npmResolutionManager
@get:Internal
abstract val compilationDisambiguatedName: Property<String>
private val compilationName = compilation.disambiguatedName
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
val packageJsonCustomFields: Map<String, Any?>
@@ -41,47 +48,40 @@ constructor(
packageJsonHandlers.forEach { it() }
}.customFields
private val compilationResolver
get() = resolutionManager.resolver[projectPath][compilationName]
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
internal val externalDependencies: Collection<NpmDependencyDeclaration>
@get:Input
val externalDependencies: Collection<NpmDependencyDeclaration>
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 {
project.buildDir
.resolve("tmp")
.resolve(publicPackageJsonTaskName)
.resolve(name)
.resolve(PACKAGE_JSON)
}
@get:OutputFile
var packageJsonFile: File by property { defaultPackageJsonFile }
private val isJrIrCompilation = compilation is KotlinJsIrCompilation
private val projectVersion = project.version.toString()
@TaskAction
fun resolve() {
packageJson(npmProject.name, projectVersion, npmProject.main, externalDependencies, packageJsonHandlers).let { packageJson ->
packageJson.main = "${npmProject.name}.js"
packageJson(
npmProjectName.get(),
projectVersion,
npmProjectMain.get(),
externalDependencies,
packageJsonHandlers
).let { packageJson ->
packageJson.main = "${npmProjectName}.js"
if (isJrIrCompilation) {
packageJson.types = "${npmProject.name}.d.ts"
if (jsIrCompilation.get()) {
packageJson.types = "${npmProjectName}.d.ts"
}
packageJson.apply {
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
interface RequiresNpmDependencies {
val compilation: KotlinJsCompilation
val nodeModulesRequired: Boolean
val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
@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
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolution
import java.io.Serializable
/**
* Info about NPM projects inside particular gradle [project].
*/
class KotlinProjectNpmResolution(
val project: String,
val npmProjects: List<KotlinCompilationNpmResolution>,
val taskRequirements: Map<String, Collection<RequiredKotlinJsDependency>>
) {
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 } }
val byCompilation: Map<String, KotlinCompilationNpmResolution>,
) : Serializable {
val npmProjects: Collection<KotlinCompilationNpmResolution>
get() = byCompilation.values
operator fun get(compilationName: String): KotlinCompilationNpmResolution {
// check(compilation.target.project.path == project)
return byCompilation.getValue(compilationName)
}
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
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(
val rootProject: Project?,
internal val projects: Map<String, KotlinProjectNpmResolution>
) {
operator fun get(project: String) = projects[project] ?: KotlinProjectNpmResolution.empty(project)
class KotlinRootNpmResolution(
val projects: Map<String, KotlinProjectNpmResolution>,
val rootProjectName: String,
val rootProjectVersion: String
) : 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.attributes.Category
import org.gradle.api.attributes.Usage
import org.gradle.api.initialization.IncludedBuild
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.configurationcache.extensions.serviceOf
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.categoryByName
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.sourceSetDependencyConfigurationByScope
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.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager
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.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.CompositeProjectComponentArtifactMetadata
import org.jetbrains.kotlin.gradle.utils.`is`
import org.jetbrains.kotlin.gradle.utils.topRealPath
import org.jetbrains.kotlin.gradle.utils.unavailableValueError
import java.io.File
import java.io.Serializable
/**
* See [KotlinNpmResolutionManager] for details about resolution process.
*/
internal class KotlinCompilationNpmResolver(
@Transient
class KotlinCompilationNpmResolver(
val projectResolver: KotlinProjectNpmResolver,
@Transient
val compilation: KotlinJsCompilation
) : Serializable {
@Transient
var rootResolver = projectResolver.resolver
val npmProject = compilation.npmProject
@@ -61,26 +53,32 @@ internal class KotlinCompilationNpmResolver(
project.version.toString()
}
val nodeJs get() = rootResolver.nodeJs
private val nodeJs_ get() = nodeJs ?: unavailableValueError("nodeJs")
val target get() = compilation.target
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)
@Transient
val publicPackageJsonTaskHolder: TaskProvider<PublicPackageJsonTask> =
val publicPackageJsonTaskHolder: TaskProvider<PublicPackageJsonTask> = run {
val npmResolutionManager = project.kotlinNpmResolutionManager
project.registerTask<PublicPackageJsonTask>(
npmProject.publicPackageJsonTaskName,
listOf(compilation)
npmProject.publicPackageJsonTaskName
) {
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 ->
if (compilation.isMain()) {
project.tasks
@@ -91,66 +89,29 @@ internal class KotlinCompilationNpmResolver(
}
}
}
}
override fun toString(): String = "KotlinCompilationNpmResolver(${npmProject.name})"
@delegate:Transient
private val aggregatedConfiguration: Configuration by lazy {
createAggregatedConfiguration()
}
private var _compilationNpmResolution: KotlinCompilationNpmResolution? = null
private val packageJsonProducer_: PackageJsonProducer by lazy {
val visitor = ConfigurationVisitor()
visitor.visit(aggregatedConfiguration)
visitor.toPackageJsonProducer().also { it.compilationResolver = this }
}
val packageJsonProducer: PackageJsonProducer
val compilationNpmResolution: KotlinCompilationNpmResolution
get() {
val packageJsonProducer = packageJsonProducer_
packageJsonProducer.compilationResolver = this
return packageJsonProducer
return _compilationNpmResolution ?: run {
val visitor = ConfigurationVisitor()
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
fun close(): KotlinCompilationNpmResolution? {
check(!closed) { "$this already closed" }
val resolution = getResolutionOrResolveIfForced()
closed = true
return resolution
return _compilationNpmResolution
}
private fun createAggregatedConfiguration(): Configuration {
fun createAggregatedConfiguration(): Configuration {
val all = project.configurations.create(compilation.disambiguateName("npm"))
all.usesPlatformOf(target)
@@ -174,46 +135,16 @@ internal class KotlinCompilationNpmResolver(
}
// 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
}
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 {
private val internalDependencies = mutableSetOf<InternalDependency>()
private val internalCompositeDependencies = mutableSetOf<CompositeDependency>()
private val externalGradleDependencies = mutableSetOf<ExternalGradleDependency>()
private val externalNpmDependencies = mutableSetOf<NpmDependency>()
private val externalNpmDependencies = mutableSetOf<NpmDependencyDeclaration>()
private val fileCollectionDependencies = mutableSetOf<FileCollectionExternalGradleDependency>()
private val visitedDependencies = mutableSetOf<ResolvedDependency>()
@@ -225,7 +156,7 @@ internal class KotlinCompilationNpmResolver(
configuration.allDependencies.forEach { dependency ->
when (dependency) {
is NpmDependency -> externalNpmDependencies.add(dependency)
is NpmDependency -> externalNpmDependencies.add(dependency.toDeclaration())
is FileCollectionDependency -> fileCollectionDependencies.add(
FileCollectionExternalGradleDependency(
dependency.files.files,
@@ -240,7 +171,7 @@ internal class KotlinCompilationNpmResolver(
val main = compilation.target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME) as KotlinJsCompilation
internalDependencies.add(
InternalDependency(
projectResolver.project.path,
projectResolver.projectPath,
main.disambiguatedName,
projectResolver[main].npmProject.name
)
@@ -336,7 +267,7 @@ internal class KotlinCompilationNpmResolver(
}
}
fun toPackageJsonProducer() = PackageJsonProducer(
fun toPackageJsonProducer() = KotlinCompilationNpmResolution(
internalDependencies,
internalCompositeDependencies,
externalGradleDependencies.map {
@@ -346,169 +277,18 @@ internal class KotlinCompilationNpmResolver(
it.artifact.file
)
},
externalNpmDependencies.map { it.toDeclaration() },
externalNpmDependencies,
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.KotlinTarget
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.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.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.utils.unavailableValueError
import java.io.Serializable
import kotlin.reflect.KClass
/**
* See [KotlinNpmResolutionManager] for details about resolution process.
*/
internal class KotlinProjectNpmResolver(
@Transient
val project: Project,
@Transient
class KotlinProjectNpmResolver(
project: Project,
var resolver: KotlinRootNpmResolver
) : Serializable {
override fun toString(): String = "ProjectNpmResolver($project)"
private val projectPath by lazy { project.path }
val projectPath by lazy { project.path }
private val byCompilation = mutableMapOf<String, KotlinCompilationNpmResolver>()
operator fun get(compilation: KotlinJsCompilation): KotlinCompilationNpmResolver {
check(compilation.target.project == project)
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")
}
private var closed = false
private var resolution: KotlinProjectNpmResolution? = null
val compilationResolvers: Collection<KotlinCompilationNpmResolver>
get() = byCompilation.values
init {
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,
)
}
}
}
project.addContainerListeners()
}
private fun addContainerListeners() {
val kotlin = project.kotlinExtensionOrNull
private fun Project.addContainerListeners() {
val kotlin = kotlinExtensionOrNull
?: error("NpmResolverPlugin should be applied after kotlin plugin")
when (kotlin) {
@@ -91,10 +62,11 @@ internal class KotlinProjectNpmResolver(
}
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 ||
target.platformType == KotlinPlatformType.wasm) {
target.platformType == KotlinPlatformType.wasm
) {
target.compilations.all { compilation ->
if (compilation is KotlinJsCompilation) {
// compilation may be KotlinWithJavaTarget for old Kotlin2JsPlugin
@@ -115,19 +87,23 @@ internal class KotlinProjectNpmResolver(
@Synchronized
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 {
check(!closed)
closed = true
return KotlinProjectNpmResolution(
projectPath,
byCompilation.values.mapNotNull { it.close() },
resolver.taskRequirements.byTask
return resolution ?: KotlinProjectNpmResolution(
byCompilation
.map { (key, value) ->
value.close()?.let { key to it }
}
.filterNotNull()
.toMap(),
)
}
}
@@ -6,204 +6,45 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolver
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.mpp.KotlinJsCompilation
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.nodejs.NodeJsRootExtension
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.tasks.KotlinNpmCachesSetup
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
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
import java.io.File
import java.io.Serializable
/**
* 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(
@Transient
val nodeJs: NodeJsRootExtension?,
val forceFullResolve: Boolean
) {
private val nodeJs_
get() = nodeJs ?: unavailableValueError("nodeJs")
val rootProjectName: String,
val rootProjectVersion: String,
val tasksRequirements: TasksRequirements,
val versions: NpmVersions,
val projectPackagesDir: File,
val rootProjectDir: File,
) : Serializable {
private val rootProject: Project?
get() = nodeJs?.rootProject
internal var resolution: KotlinRootNpmResolution? = null
private val rootProject_: Project
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_)
val projectResolvers: MutableMap<String, KotlinProjectNpmResolver> = mutableMapOf()
fun alreadyResolvedMessage(action: String) = "Cannot $action. NodeJS projects already resolved."
fun addProject(target: Project) {
synchronized(projectResolvers) {
check(state == RootResolverState.CONFIGURING) { alreadyResolvedMessage("add new project: $target") }
projectResolvers[target.path] = KotlinProjectNpmResolver(target, this)
check(resolution == null) { alreadyResolvedMessage("add new project: $target") }
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>
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>? {
// todo: proper finding using KotlinTargetComponent.findUsageContext
val targetResolver = this[target.path]
@@ -242,113 +83,15 @@ class KotlinRootNpmResolver internal constructor(
return mainCompilations
}
/**
* Don't use directly, use [KotlinNpmResolutionManager.installIfNeeded] instead.
*/
internal fun prepareInstallation(logger: Logger): Installation {
synchronized(projectResolvers) {
state = RootResolverState.PROJECTS_CLOSED
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
)
}
internal fun close(): KotlinRootNpmResolution {
return resolution ?: KotlinRootNpmResolution(
projectResolvers
.map { (key, value) -> key to value.close() }
.toMap(),
rootProjectName,
rootProjectVersion
)
}
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"
@@ -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
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.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
abstract class KotlinNpmCachesSetup : DefaultTask() {
abstract class KotlinNpmCachesSetup :
DefaultTask(),
UsesGradleNodeModulesCache,
UsesCompositeNodeModulesCache {
@get:Inject
open val fileHasher: FileHasher
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
fun setup() {
gradleNodeModules.get().fileHasher = fileHasher
@@ -9,72 +9,66 @@ import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings
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.UsesKotlinNpmResolutionManager
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
open class KotlinNpmInstallTask : DefaultTask() {
abstract class KotlinNpmInstallTask :
DefaultTask(),
UsesKotlinNpmResolutionManager {
init {
check(project == project.rootProject)
onlyIf {
preparedFiles.all {
it.exists()
}
}
}
@Transient
private val nodeJs: NodeJsRootExtension? = NodeJsRootPlugin.apply(project.rootProject)
private val resolutionManager = (nodeJs ?: unavailableValueError("nodeJs")).npmResolutionManager
// Only in configuration phase
// Not part of configuration caching
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
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:IgnoreEmptyDirectories
@get:NormalizeLineEndings
@get:InputFiles
val preparedFiles: Collection<File> by lazy {
(nodeJs ?: unavailableValueError("nodeJs")).packageManager.preparedFiles(nodeJs.asNpmEnvironment)
nodeJs.packageManager.preparedFiles(npmEnvironment)
}
@get:OutputFile
val yarnLock: File by lazy {
(nodeJs ?: unavailableValueError("nodeJs")).rootPackageDir.resolve("yarn.lock")
nodeJs.rootPackageDir.resolve("yarn.lock")
}
@TaskAction
fun resolve() {
resolutionManager.installIfNeeded(
args = args,
services = services,
logger = logger
) ?: throw (resolutionManager.state as KotlinNpmResolutionManager.ResolutionState.Error).wrappedException
npmResolutionManager.get()
.installIfNeeded(
args = args,
services = services,
logger = logger,
npmEnvironment,
yarnEnv
) ?: throw (npmResolutionManager.get().state as KotlinNpmResolutionManager.ResolutionState.Error).wrappedException
}
companion object {
@@ -11,92 +11,86 @@ import org.gradle.api.tasks.*
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.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.npm.fakePackageJsonValue
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.MayBeUpToDatePackageJsonTasksRegistry
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.*
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.PackageJsonProducerInputs
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
abstract class KotlinPackageJsonTask : DefaultTask() {
abstract class KotlinPackageJsonTask :
DefaultTask(),
UsesKotlinNpmResolutionManager,
UsesGradleNodeModulesCache,
UsesCompositeNodeModulesCache {
// Only in configuration phase
// Not part of configuration caching
init {
onlyIf {
npmResolutionManager.isConfiguringState()
private val nodeJs: NodeJsRootExtension
get() = project.rootProject.kotlinNodeJsExtension
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 }
@Transient
private lateinit var compilation: KotlinJsCompilation
private val projectPath = project.path
@get:Internal
internal abstract val mayBeUpToDateTasksRegistry: Property<MayBeUpToDatePackageJsonTasksRegistry>
abstract val compilationDisambiguatedName: Property<String>
private val compilationDisambiguatedName by lazy {
compilation.disambiguatedName
}
@Input
val projectPath = project.path
private val compilationResolver
get() = npmResolutionManager.resolver[projectPath][compilationDisambiguatedName]
private val producer: KotlinCompilationNpmResolver.PackageJsonProducer
get() = compilationResolver.packageJsonProducer
private val packageJsonHandlers: List<PackageJson.() -> Unit>
get() = npmResolutionManager.get().parameters.packageJsonHandlers.get()
.getValue("$projectPath:${compilationDisambiguatedName.get()}")
@get:Input
val packageJsonCustomFields: Map<String, Any?> by lazy {
PackageJson(fakePackageJsonValue, fakePackageJsonValue)
.apply {
compilation.packageJsonHandlers.forEach { it() }
packageJsonHandlers.forEach { it() }
}.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
internal val toolsNpmDependencies: List<String> by lazy {
nodeJs.taskRequirements
.getCompilationNpmRequirements(projectPath, compilationDisambiguatedName)
.getCompilationNpmRequirements(projectPath, compilationDisambiguatedName.get())
.map { it.toString() }
.sorted()
}
// nested inputs are processed in configuration phase
// so npmResolutionManager must not be used
@get:Nested
internal val producerInputs: KotlinCompilationNpmResolver.PackageJsonProducerInputs by lazy {
producer.inputs
internal val producerInputs: PackageJsonProducerInputs by lazy {
compilationResolver.compilationNpmResolution.inputs
}
@get:OutputFile
val packageJson: File by lazy {
compilationResolver.npmProject.packageJsonFile
}
abstract val packageJson: Property<File>
@TaskAction
fun resolve() {
compilationResolver.resolve()
npmResolutionManager.get().resolution.get()[projectPath][compilationDisambiguatedName.get()]
.resolve(
npmResolutionManager = npmResolutionManager.get(),
logger = logger
)
}
companion object {
@@ -104,29 +98,50 @@ abstract class KotlinPackageJsonTask : DefaultTask() {
val target = compilation.target
val project = target.project
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 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 ->
task.nodeJs = nodeJs
task.compilation = compilation
task.compilationDisambiguatedName.set(compilation.disambiguatedName)
task.description = "Create package.json file for $compilation"
task.group = NodeJsRootPlugin.TASKS_GROUP_NAME
val packageJsonStateService = MayBeUpToDatePackageJsonTasksRegistry.registerIfAbsent(project)
task.mayBeUpToDateTasksRegistry.value(packageJsonStateService).chainedDisallowChanges().also {
task.usesService(packageJsonStateService)
task.npmResolutionManager.apply {
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(npmCachesSetupTask)
}
packageJsonUmbrella.configure { task ->
task.inputs.file(packageJsonTask.map { it.packageJson })
}
nodeJs.rootPackageJsonTaskProvider!!.configure { it.mustRunAfter(packageJsonTask) }
nodeJsTaskProviders.rootPackageJsonTaskProvider.configure { it.mustRunAfter(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.tasks.*
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension
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
open class RootPackageJsonTask : DefaultTask() {
abstract class RootPackageJsonTask :
DefaultTask(),
UsesKotlinNpmResolutionManager {
init {
check(project == project.rootProject)
onlyIf {
resolutionManager.isConfiguringState()
}
}
@Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val resolutionManager = nodeJs.npmResolutionManager
// Only in configuration phase
// Not part of configuration caching
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
val rootPackageJson: File by lazy {
@@ -35,12 +53,15 @@ open class RootPackageJsonTask : DefaultTask() {
@get:NormalizeLineEndings
@get:InputFiles
val packageJsonFiles: Collection<File> by lazy {
resolutionManager.packageJsonFiles
rootResolver.projectResolvers.values
.flatMap { it.compilationResolvers }
.map { it.compilationNpmResolution }
.map { it.npmProjectPackageJsonFile }
}
@TaskAction
fun resolve() {
resolutionManager.prepare(logger)
npmResolutionManager.get().prepare(logger, npmEnvironment, yarnEnv)
}
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.plugin.COMPILER_CLASSPATH_CONFIGURATION_NAME
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.isMain
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.ir.executeTaskBaseName
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.testing.KotlinJsTest
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.Mode
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.registerTask
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 dceConfigurations: MutableList<Action<KotlinJsDce>> = mutableListOf()
private val distribution: Distribution = createDefaultDistribution(project)
private val propertiesProvider = PropertiesProvider(project)
private val webpackMajorVersion
get() = propertiesProvider.webpackMajorVersion
override val testTaskDescription: String
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>
) {
val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
val nodeJs = project.rootProject.kotlinNodeJsExtension
val commonRunTask = registerSubTargetTask<Task>(disambiguateCamelCased(RUN_TASK_NAME)) {}
@@ -156,33 +151,20 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
listOf(compilation)
) { task ->
task.outputDirectory.convention(distsDirectory).finalizeValueOnRead()
webpackMajorVersion.choose(
{ task.args.add(0, "serve") },
{ task.bin = "webpack-dev-server/bin/webpack-dev-server.js" }
)()
task.args.add(0, "serve")
task.description = "start ${type.name.toLowerCaseAsciiOnly()} webpack dev server"
webpackMajorVersion.choose(
{
task.devServer = KotlinWebpackConfig.DevServer(
open = true,
static = mutableListOf(compilation.output.resourcesDir.canonicalPath),
client = KotlinWebpackConfig.DevServer.Client(
KotlinWebpackConfig.DevServer.Client.Overlay(
errors = true,
warnings = false
)
)
task.devServer = KotlinWebpackConfig.DevServer(
open = true,
static = mutableListOf(compilation.output.resourcesDir.canonicalPath),
client = KotlinWebpackConfig.DevServer.Client(
KotlinWebpackConfig.DevServer.Client.Overlay(
errors = true,
warnings = false
)
},
{
task.devServer = KotlinWebpackConfig.DevServer(
open = true,
contentBase = mutableListOf(compilation.output.resourcesDir.canonicalPath)
)
}
)()
)
)
task.doNotTrackStateCompat("Tracked by external webpack tool")
@@ -212,7 +194,7 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
devDceTaskProvider: TaskProvider<KotlinJsDceTask>
) {
val project = compilation.target.project
val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
val nodeJs = project.rootProject.kotlinNodeJsExtension
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.KotlinJsTarget
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.npm.NpmResolverPlugin
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.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.registerTask
@@ -35,7 +34,8 @@ abstract class KotlinJsSubTarget(
) : KotlinJsSubTargetDsl {
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
@@ -53,8 +53,6 @@ abstract class KotlinJsSubTarget(
}
internal fun configure() {
NpmResolverPlugin.apply(project)
configureTests()
target.compilations.all {
@@ -108,10 +106,10 @@ abstract class KotlinJsSubTarget(
testJs.inputFileProperty.fileProvider(compileOutputFile)
testJs.dependsOn(
nodeJs.npmInstallTaskProvider,
nodeJs.storeYarnLockTaskProvider,
nodeJsTaskProviders.npmInstallTaskProvider,
nodeJsTaskProviders.storeYarnLockTaskProvider,
compileTask,
nodeJs.nodeJsSetupTaskProvider
nodeJsTaskProviders.nodeJsSetupTaskProvider
)
testJs.onlyIf {
@@ -14,7 +14,7 @@ import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
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.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma
@@ -33,16 +33,12 @@ constructor(
) : KotlinTest(),
RequiresNpmDependencies {
@Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val npmResolutionManager by project.provider { nodeJs.npmResolutionManager }
private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val nodeExecutable by project.provider { nodeJs.requireConfigured().nodeExecutable }
private val npmProjectDir by project.provider { compilation.npmProject.dir }
private val projectPath = project.path
@Input
var environment = mutableMapOf<String, String>()
@@ -110,9 +106,6 @@ constructor(
val nodeJsArgs: MutableList<String> =
mutableListOf()
override val nodeModulesRequired: Boolean
@Internal get() = testFramework!!.nodeModulesRequired
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
@Internal get() = testFramework!!.requiredNpmDependencies
@@ -164,11 +157,6 @@ constructor(
return testFramework
}
override fun executeTests() {
npmResolutionManager.checkRequiredDependencies(task = this)
super.executeTests()
}
override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec {
val forkOptions = DefaultProcessForkOptions(fileResolver)
forkOptions.workingDir = npmProjectDir
@@ -18,7 +18,4 @@ interface KotlinJsTestFramework : RequiresNpmDependencies {
nodeJsArgs: MutableList<String>,
debug: Boolean
): 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.jsQuoted
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.testing.*
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.testing.internal.reportsDir
import org.jetbrains.kotlin.gradle.utils.appendLine
@@ -56,7 +55,7 @@ class KotlinKarma(
private val npmProject = compilation.npmProject
@Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val nodeRootPackageDir by lazy { nodeJs.rootPackageDir }
private val versions = nodeJs.versions
@@ -81,8 +80,6 @@ class KotlinKarma(
override val settingsState: String
get() = "KotlinKarma($config)"
private val webpackMajorVersion = PropertiesProvider(project).webpackMajorVersion
val webpackConfig = KotlinWebpackConfig(
configDirectory = project.projectDir.resolve("webpack.config.d"),
optimization = KotlinWebpackConfig.Optimization(
@@ -94,13 +91,11 @@ class KotlinKarma(
export = false,
progressReporter = true,
progressReporterPathFilter = nodeRootPackageDir,
webpackMajorVersion = webpackMajorVersion,
rules = project.objects.webpackRulesContainer(),
experiments = mutableSetOf("topLevelAwait")
)
init {
requiredDependencies.add(versions.kotlinJsTestRunner)
requiredDependencies.add(versions.karma)
useKotlinReporter()
@@ -266,23 +261,14 @@ class KotlinKarma(
config.frameworks.add("webpack")
requiredDependencies.add(versions.karmaWebpack)
requiredDependencies.add(
webpackMajorVersion.choose(
versions.webpack,
versions.webpack4
)
versions.webpack
)
requiredDependencies.add(
webpackMajorVersion.choose(
versions.webpackCli,
versions.webpackCli3
)
versions.webpackCli
)
requiredDependencies.add(versions.formatUtil)
requiredDependencies.add(
webpackMajorVersion.choose(
versions.sourceMapLoader,
versions.sourceMapLoader1
)
versions.sourceMapLoader
)
addPreprocessor("webpack")
@@ -298,14 +284,12 @@ class KotlinKarma(
// noinspection JSUnnecessarySemicolon
;(function(config) {
const webpack = require('webpack');
${
if (webpackMajorVersion != WebpackMajorVersion.V4) {
"""
// https://github.com/webpack/webpack/issues/12951
const PatchSourceMapSource = require('kotlin-test-js-runner/webpack-5-debug');
config.plugins.push(new PatchSourceMapSource())
"""
} else ""
${
"""
// https://github.com/webpack/webpack/issues/12951
const PatchSourceMapSource = require('kotlin-test-js-runner/webpack-5-debug');
config.plugins.push(new PatchSourceMapSource())
"""
}
config.plugins.push(new webpack.SourceMapDevToolPlugin({
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.internal.parseNodeJsStackTraceAsJvm
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.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTestFramework
@@ -25,7 +26,7 @@ class KotlinMocha(@Transient override val compilation: KotlinJsCompilation, priv
@Transient
private val project: Project = compilation.target.project
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)
override val settingsState: String
@@ -33,7 +34,6 @@ class KotlinMocha(@Transient override val compilation: KotlinJsCompilation, priv
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
get() = setOf(
versions.kotlinJsTestRunner,
versions.mocha,
versions.sourceMapSupport,
versions.formatUtil
@@ -6,15 +6,15 @@
package org.jetbrains.kotlin.gradle.targets.js.typescript
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.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.internal.execWithProgress
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
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.npmProject
import javax.inject.Inject
@@ -30,14 +30,13 @@ constructor(
@get:Internal
@Transient
protected val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
protected val nodeJs = project.rootProject.kotlinNodeJsExtension
@get:Internal
override val nodeModulesRequired: Boolean get() = false
private val versions = nodeJs.versions
@get:Internal
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
get() = setOf(nodeJs.versions.typescript)
get() = setOf(versions.typescript)
@get:SkipWhenEmpty
@get:NormalizeLineEndings
@@ -69,9 +68,8 @@ constructor(
val message = "Oops, Kotlin/JS compiler generated invalid d.ts files."
when (validationStrategy) {
KotlinIrJsGeneratedTSValidationStrategy.ERROR -> error(message)
KotlinIrJsGeneratedTSValidationStrategy.IGNORE -> {}
if (validationStrategy == KotlinIrJsGeneratedTSValidationStrategy.ERROR) {
error(message)
}
}
@@ -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.Companion.webpackRulesContainer
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.npmProject
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode
@@ -55,9 +56,8 @@ constructor(
private val objects: ObjectFactory
) : DefaultTask(), RequiresNpmDependencies, WebpackRulesDsl, UsesBuildMetricsService {
@Transient
private val nodeJs = NodeJsRootPlugin.apply(project.rootProject)
private val nodeJs = project.rootProject.kotlinNodeJsExtension
private val versions = nodeJs.versions
private val resolutionManager = nodeJs.npmResolutionManager
private val rootPackageDir by lazy { nodeJs.rootPackageDir }
private val npmProject = compilation.npmProject
@@ -202,9 +202,6 @@ constructor(
@Internal
var generateConfigOnly: Boolean = false
@Input
val webpackMajorVersion = PropertiesProvider(project).webpackMajorVersion
fun webpackConfigApplier(body: Action<KotlinWebpackConfig>) {
webpackConfigAppliers.add(body)
}
@@ -234,7 +231,6 @@ constructor(
devtool = devtool,
sourceMaps = sourceMaps,
resolveFromModulesFirst = resolveFromModulesFirst,
webpackMajorVersion = webpackMajorVersion
)
private fun createRunner(): KotlinWebpackRunner {
@@ -262,9 +258,6 @@ constructor(
)
}
override val nodeModulesRequired: Boolean
@Internal get() = true
override val requiredNpmDependencies: Set<RequiredKotlinJsDependency>
@Internal get() = createWebpackConfig(true).getRequiredDependencies(versions)
@@ -272,8 +265,6 @@ constructor(
@TaskAction
fun doExecute() {
resolutionManager.checkRequiredDependencies(task = this)
val runner = createRunner()
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.WebpackRulesDsl
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.relativeOrAbsolute
import java.io.File
@@ -49,6 +48,8 @@ data class KotlinWebpackConfig(
@get:NormalizeLineEndings
@get:InputDirectory
var configDirectory: File? = null,
@Internal
var reportEvaluatedConfigFile: File? = null,
@Input
@Optional
var devServer: DevServer? = null,
@@ -72,9 +73,7 @@ data class KotlinWebpackConfig(
@Optional
var progressReporterPathFilter: File? = null,
@Input
var resolveFromModulesFirst: Boolean = false,
@Input
val webpackMajorVersion: WebpackMajorVersion = WebpackMajorVersion.V5
var resolveFromModulesFirst: Boolean = false
) : WebpackRulesDsl {
@get:Input
@@ -94,36 +93,23 @@ data class KotlinWebpackConfig(
fun getRequiredDependencies(versions: NpmVersions) =
mutableSetOf<RequiredKotlinJsDependency>().also {
it.add(versions.kotlinJsTestRunner)
it.add(
webpackMajorVersion.choose(
versions.webpack,
versions.webpack4
)
versions.webpack
)
it.add(
webpackMajorVersion.choose(
versions.webpackCli,
versions.webpackCli3
)
versions.webpackCli
)
it.add(versions.formatUtil)
if (sourceMaps) {
it.add(
webpackMajorVersion.choose(
versions.sourceMapLoader,
versions.sourceMapLoader1
)
versions.sourceMapLoader
)
}
if (devServer != null) {
it.add(
webpackMajorVersion.choose(
versions.webpackDevServer,
versions.webpackDevServer3
)
versions.webpackDevServer
)
}
@@ -294,16 +280,8 @@ data class KotlinWebpackConfig(
enforce: "pre"
});
config.devtool = ${devtool?.let { "'$it'" } ?: false};
${
webpackMajorVersion.choose(
"config.ignoreWarnings = [/Failed to parse source map/]",
"""
config.stats = config.stats || {}
Object.assign(config.stats, config.stats, {
warningsFilter: [/Failed to parse source map/]
})
"""
)
${
"config.ignoreWarnings = [/Failed to parse source map/]"
}
""".trimIndent()
@@ -5,55 +5,43 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.gradle.api.logging.Logger
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.NpmDependency
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
class Yarn : NpmApi {
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> =
yarnWorkspaces.preparedFiles(nodeJs)
override fun prepareRootProject(
rootProject: Project?,
nodeJs: NpmEnvironment,
rootProjectName: String,
rootProjectVersion: String,
logger: Logger,
subProjects: Collection<KotlinCompilationNpmResolution>,
subProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>,
forceFullResolve: Boolean
) = yarnWorkspaces
.prepareRootProject(
rootProject,
nodeJs,
rootProjectName,
rootProjectVersion,
logger,
subProjects,
resolutions,
forceFullResolve
)
override fun resolveRootProject(
services: ServiceRegistry,
logger: Logger,
nodeJs: NpmEnvironment,
yarn: YarnEnv,
npmProjects: Collection<KotlinCompilationNpmResolution>,
yarn: YarnEnvironment,
npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
cliArgs: List<String>
) {
yarnWorkspaces
@@ -66,15 +54,4 @@ class Yarn : NpmApi {
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
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.gradle.internal.service.ServiceRegistry
import org.jetbrains.kotlin.gradle.internal.execWithProgress
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.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment
import java.io.File
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(
services: ServiceRegistry,
logger: Logger,
nodeJs: NpmEnvironment,
yarn: YarnEnv,
yarn: YarnEnvironment,
dir: File,
description: 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 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 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 java.io.File
import java.io.Serializable
data class YarnEnv(
val downloadUrl: String,
@@ -20,4 +19,5 @@ data class YarnEnv(
val yarnLockMismatchReport: YarnLockMismatchReport,
val reportNewYarnLock: 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.PackageJson
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
class YarnImportedPackagesVersionResolver(
private val logger: Logger,
private val npmProjects: Collection<KotlinCompilationNpmResolution>,
npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
private val nodeJsWorldDir: File
) {
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.targets.js.MultiplePluginDeclarationDetector
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing
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 nodeJs = NodeJsRootPlugin.apply(this)
NodeJsRootPlugin.apply(project)
val nodeJs = this.kotlinNodeJsExtension
val nodeJsTaskProviders = this.kotlinNodeJsExtension
val setupTask = registerTask<YarnSetupTask>(YarnSetupTask.NAME) {
it.dependsOn(nodeJs.nodeJsSetupTaskProvider)
it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider)
it.configuration = provider {
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 ->
task.dependsOn(nodeJs.npmCachesSetupTaskProvider)
task.dependsOn(nodeJsTaskProviders.npmCachesSetupTaskProvider)
task.group = NodeJsRootPlugin.TASKS_GROUP_NAME
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)
@@ -62,7 +76,7 @@ open class YarnPlugin : Plugin<Project> {
it.description = "Clean unused local yarn version"
}
val packageJsonUmbrella = nodeJs
val packageJsonUmbrella = nodeJsTaskProviders
.packageJsonUmbrellaTaskProvider
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.gradle.api.Action
import org.gradle.api.Incubating
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware
@@ -20,7 +19,6 @@ import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File
open class YarnRootExtension(
@Transient
val project: Project
) : ConfigurationPhaseAware<YarnEnv>() {
init {
@@ -60,7 +58,7 @@ open class YarnRootExtension(
.withType(RootPackageJsonTask::class.java)
.named(RootPackageJsonTask.NAME)
var resolutions: MutableList<YarnResolution> = mutableListOf()
var resolutions: MutableList<YarnResolution> by Property(mutableListOf())
fun resolution(path: String, configure: Action<YarnResolution>) {
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 {
val cleanableStore = CleanableStore[installationDir.path]
@@ -116,20 +99,10 @@ open class YarnRootExtension(
yarnLockMismatchReport = yarnLockMismatchReport,
reportNewYarnLock = reportNewYarnLock,
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 {
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.artifacts.Configuration
import org.gradle.api.file.ArchiveOperations
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
@@ -15,24 +16,23 @@ import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.extractWithUpToDate
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.utils.ArchiveOperationsCompat
import org.jetbrains.kotlin.statistics.metrics.NumericalMetrics
import java.io.File
import java.net.URI
import javax.inject.Inject
open class YarnSetupTask : DefaultTask() {
abstract class YarnSetupTask : DefaultTask() {
@Transient
private val settings = project.yarn
private val env by lazy { settings.requireConfigured() }
private val shouldDownload = settings.download
private val archiveOperations = ArchiveOperationsCompat(project)
@get:Inject
abstract internal val archiveOperations: ArchiveOperations
@get:Inject
internal open val fileHasher: FileHasher
get() = error("Should be injected")
abstract internal val fileHasher: FileHasher
@get:Inject
internal open val fs: FileSystemOperations
@@ -5,14 +5,10 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn
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.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment
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 org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution
import java.io.File
class YarnWorkspaces : YarnBasics() {
@@ -25,18 +21,13 @@ class YarnWorkspaces : YarnBasics() {
}
override fun prepareRootProject(
rootProject: Project?,
nodeJs: NpmEnvironment,
rootProjectName: String,
rootProjectVersion: String,
logger: Logger,
subProjects: Collection<KotlinCompilationNpmResolution>,
subProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>,
forceFullResolve: Boolean
) {
if (forceFullResolve) {
rootProject?.let { setup(it) }
}
return prepareRootPackageJson(
nodeJs,
rootProjectName,
@@ -52,7 +43,7 @@ class YarnWorkspaces : YarnBasics() {
rootProjectName: String,
rootProjectVersion: String,
logger: Logger,
npmProjects: Collection<KotlinCompilationNpmResolution>,
npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>
) {
val rootPackageJsonFile = preparedFiles(nodeJs).single()
@@ -71,8 +62,8 @@ class YarnWorkspaces : YarnBasics() {
services: ServiceRegistry,
logger: Logger,
nodeJs: NpmEnvironment,
yarn: YarnEnv,
npmProjects: Collection<KotlinCompilationNpmResolution>,
yarn: YarnEnvironment,
npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
cliArgs: List<String>
) {
val nodeJsWorldDir = nodeJs.rootPackageDir
@@ -86,15 +77,13 @@ class YarnWorkspaces : YarnBasics() {
NpmApi.resolveOperationDescription("yarn"),
cliArgs
)
yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.externalNpmDependencies })
}
private fun saveRootProjectWorkspacesPackageJson(
rootProjectName: String,
rootProjectVersion: String,
logger: Logger,
npmProjects: Collection<KotlinCompilationNpmResolution>,
npmProjects: Collection<PreparedKotlinCompilationNpmResolution>,
resolutions: Map<String, String>,
rootPackageJsonFile: File
) {
@@ -102,7 +91,7 @@ class YarnWorkspaces : YarnBasics() {
val rootPackageJson = PackageJson(rootProjectName, rootProjectVersion)
rootPackageJson.private = true
val npmProjectWorkspaces = npmProjects.map { it.npmProject.dir.relativeTo(nodeJsWorldDir).path }
val npmProjectWorkspaces = npmProjects.map { it.npmProjectDir.relativeTo(nodeJsWorldDir).path }
val importedProjectWorkspaces =
YarnImportedPackagesVersionResolver(logger, npmProjects, nodeJsWorldDir).resolveAndUpdatePackages()
@@ -53,45 +53,6 @@ internal fun checkGradleCompatibility(
internal val AbstractArchiveTask.archivePathCompatible: File
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
// See https://github.com/gradle/gradle/commit/0296f4441ae69ad608cfef6a90fef3fdf314fa2c
internal interface LegacyTestDescriptorInternal : TestDescriptor {