Gradle, JS: rework extension registering and resolver phasing

This allows to resolve projects lazily.

#KT-32393 Fixed
#KT-31565
#KT-32015
#KT-31917
This commit is contained in:
Sergey Rostov
2019-07-02 13:07:28 +03:00
parent f4dcc18baf
commit dff48b251d
40 changed files with 697 additions and 532 deletions
@@ -143,6 +143,7 @@ internal open class ProcessedFilesCache(
deleteTarget(it) deleteTarget(it)
} }
stateFile.parentFile.mkdirs()
stateFile.writer().use { stateFile.writer().use {
gson.toJson(new, it) gson.toJson(new, it)
} }
@@ -10,11 +10,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) : open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) { KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) {
@@ -24,19 +21,8 @@ open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
super.configureTarget(target) super.configureTarget(target)
target.compilations.forEach { target.compilations.forEach { compilation ->
val npmResolveTask = target.project.nodeJs.root.npmInstallTask KotlinPackageJsonTask.create(compilation)
val npmProject = it.npmProject
val packageJsonTaskName = npmProject.packageJsonTaskName
npmResolveTask.dependsOn(packageJsonTaskName)
target.project.createOrRegisterTask<KotlinPackageJsonTask>(packageJsonTaskName) { task ->
task.compilation = it
task.description = "Create package.json file for $it"
}
it.compileKotlinTask.dependsOn(npmResolveTask)
} }
} }
@@ -6,12 +6,65 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.tasks.AbstractExecTask import org.gradle.api.tasks.AbstractExecTask
import org.gradle.api.tasks.Input
import org.jetbrains.kotlin.gradle.plugin.TaskHolder
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
open class NodeJsExec : AbstractExecTask<NodeJsExec>(NodeJsExec::class.java) { open class NodeJsExec : AbstractExecTask<NodeJsExec>(NodeJsExec::class.java), RequiresNpmDependencies {
init { lateinit var nodeJs: NodeJsRootExtension
val nodeJs = NodeJsPlugin.apply(project).root override lateinit var compilation: KotlinJsCompilation
dependsOn(nodeJs.npmInstallTask)
executable = nodeJs.environment.nodeExecutable @Input
var sourceMapStackTraces = true
override val nodeModulesRequired: Boolean
get() = true
override val requiredNpmDependencies: Collection<RequiredKotlinJsDependency>
get() = mutableListOf<RequiredKotlinJsDependency>().also {
if (sourceMapStackTraces) {
it.add(nodeJs.versions.kotlinNodeJsTestRunner)
}
}
override fun exec() {
if (sourceMapStackTraces) {
args(
"--require",
compilation.npmProject.require("kotlin-test-nodejs-runner/kotlin-nodejs-source-map-support")
)
}
super.exec()
}
companion object {
fun create(
compilation: KotlinJsCompilation,
name: String,
providedNodeJs: NodeJsRootExtension? = null,
configuration: NodeJsExec.() -> Unit = {}
): TaskHolder<NodeJsExec> {
val target = compilation.target
val project = target.project
val nodeJs = providedNodeJs ?: NodeJsRootPlugin.apply(project).root
return project.createOrRegisterTask(name) {
it.nodeJs = nodeJs
it.compilation = compilation
it.executable = nodeJs.environment.nodeExecutable
it.dependsOn(nodeJs.npmInstallTask)
val compileKotlinTask = compilation.compileKotlinTask
it.dependsOn(nodeJs.npmInstallTask, compileKotlinTask)
it.args(compileKotlinTask.outputFile)
it.configuration()
}
}
} }
} }
@@ -9,9 +9,9 @@ import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.NpmVersions import org.jetbrains.kotlin.gradle.targets.js.NpmVersions
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
open class NodeJsExtension(val project: Project) { open class NodeJsExtension(val project: Project, root: NodeJsRootExtension?) {
val root: NodeJsRootExtension @Suppress("LeakingThis")
get() = this as? NodeJsRootExtension ?: NodeJsRootExtension[project] val root: NodeJsRootExtension = root ?: this as NodeJsRootExtension
internal val packageJsonHandlers = mutableListOf<PackageJson.() -> Unit>() internal val packageJsonHandlers = mutableListOf<PackageJson.() -> Unit>()
@@ -21,11 +21,11 @@ open class NodeJsExtension(val project: Project) {
fun packageJson(handler: PackageJson.() -> Unit) { fun packageJson(handler: PackageJson.() -> Unit) {
packageJsonHandlers.add(handler) packageJsonHandlers.add(handler)
} }
companion object {
operator fun get(project: Project) = NodeJsPlugin.apply(project)
}
} }
val Project.nodeJs: NodeJsExtension val Project.nodeJs: NodeJsExtension
get() = NodeJsExtension[this] get() {
val project = this
project.plugins.apply(NodeJsPlugin::class.java)
return project.extensions.getByName(NodeJsRootExtension.EXTENSION_NAME) as NodeJsExtension
}
@@ -1,62 +1,21 @@
/*
* Copyright 2010-2019 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.nodejs package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.Delete
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.Companion.EXTENSION_NAME
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
open class NodeJsPlugin : Plugin<Project> { class NodeJsPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run { override fun apply(target: Project) {
check(project == project.rootProject) { val root = NodeJsRootPlugin.apply(target.rootProject) as NodeJsRootExtension
"NodeJsPlugin can be applied only to root project"
if (target != target.rootProject) {
target.extensions.create(NodeJsRootExtension.EXTENSION_NAME, NodeJsExtension::class.java, target, root)
} }
this.extensions.create(EXTENSION_NAME, NodeJsRootExtension::class.java, this) root.requireResolver().addProject(target)
val setupTask = tasks.create(NodeJsSetupTask.NAME, NodeJsSetupTask::class.java) {
it.group = TASKS_GROUP_NAME
it.description = "Download and install a local node/npm version"
}
tasks.create(KotlinNpmInstallTask.NAME, KotlinNpmInstallTask::class.java) {
it.dependsOn(setupTask)
it.outputs.upToDateWhen { false }
it.group = TASKS_GROUP_NAME
it.description = "Find, download and link NPM dependencies and projects"
}
setupCleanNodeModulesTask(project)
allprojects {
if (it != project) {
it.extensions.create(EXTENSION_NAME, NodeJsExtension::class.java, this)
}
}
} }
}
private fun setupCleanNodeModulesTask(project: Project) {
project.tasks.create("cleanKotlinNodeModules", Delete::class.java) {
it.description = "Deletes nodeJs projects created during build"
it.group = BasePlugin.BUILD_GROUP
it.delete.add(project.nodeJs.root.rootPackageDir)
}
project.tasks.create("cleanKotlinGradleNodeModules", Delete::class.java) {
it.description = "Deletes node modules imported from gradle external modules"
it.group = BasePlugin.BUILD_GROUP
it.delete.add(project.nodeJs.root.nodeModulesGradleCacheDir)
}
}
companion object {
const val TASKS_GROUP_NAME: String = "nodeJs"
fun apply(project: Project): NodeJsExtension {
val rootProject = project.rootProject
rootProject.plugins.apply(NodeJsPlugin::class.java)
return rootProject.extensions.getByName(EXTENSION_NAME) as NodeJsExtension
}
}
}
@@ -2,12 +2,21 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn
import java.io.File import java.io.File
open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) { open class NodeJsRootExtension(project: Project) : NodeJsExtension(project, null) {
init {
check(project.rootProject == project)
}
private val gradleHome = project.gradle.gradleUserHomeDir.also { private val gradleHome = project.gradle.gradleUserHomeDir.also {
project.logger.kotlinInfo("Storing cached files in $it") project.logger.kotlinInfo("Storing cached files in $it")
} }
@@ -75,9 +84,71 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) {
} }
} }
internal val resolutionState: ResolutionState
get() = _resolutionState
@Volatile
private var _resolutionState: ResolutionState = ResolutionState.Resolving(KotlinRootNpmResolver(this))
internal sealed class ResolutionState : ResolutionStateData {
class Resolving(val resolver: KotlinRootNpmResolver) : ResolutionState(), ResolutionStateData by resolver
class Resolved(val resolved: KotlinRootNpmResolution) : ResolutionState(), ResolutionStateData by resolved
}
interface ResolutionStateData {
val compilations: Collection<KotlinJsCompilation>
}
internal fun requireResolver(): KotlinRootNpmResolver =
(resolutionState as? ResolutionState.Resolving ?: error("NPM Dependencies already resolved and installed")).resolver
internal fun resolveIfNeeded(project: Project): KotlinProjectNpmResolution {
val state0 = _resolutionState
val resolution = when (state0) {
is ResolutionState.Resolved -> state0.resolved
is ResolutionState.Resolving -> {
synchronized(this) {
val state1 = _resolutionState
when (state1) {
is ResolutionState.Resolved -> state1.resolved
is ResolutionState.Resolving -> state1.resolver.close().also {
_resolutionState = ResolutionState.Resolved(it)
}
}
}
}
}
return resolution[project]
}
internal fun getAlreadyResolvedOrNull(project: Project): KotlinProjectNpmResolution? {
val state0 = resolutionState
return when (state0) {
is ResolutionState.Resolved -> state0.resolved[project]
is ResolutionState.Resolving -> null
}
}
internal fun requireResolved(project: Project, reason: String = ""): KotlinProjectNpmResolution =
getAlreadyResolvedOrNull(project) ?: error("NPM dependencies should be resolved $reason")
internal fun checkRequiredDependencies(project: Project, target: RequiresNpmDependencies) {
val requestedTaskDependencies = requireResolved(
project,
"before $target execution"
).taskRequirements
val targetRequired = requestedTaskDependencies[target]?.toSet() ?: setOf()
target.requiredNpmDependencies.forEach {
check(it in targetRequired) {
"$it required by $target was not found resolved at the time of nodejs package manager call. " +
"This may be caused by changing $target configuration after npm dependencies resolution."
}
}
}
companion object { companion object {
const val EXTENSION_NAME: String = "kotlinNodeJs" const val EXTENSION_NAME: String = "kotlinNodeJs"
operator fun get(project: Project) = NodeJsPlugin.apply(project.rootProject) as NodeJsRootExtension
} }
} }
@@ -0,0 +1,41 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.Delete
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.Companion.EXTENSION_NAME
import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask
open class NodeJsRootPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run {
project.plugins.apply(BasePlugin::class.java)
check(project == project.rootProject) {
"NodeJsRootPlugin can be applied only to root project"
}
val root = this.extensions.create(EXTENSION_NAME, NodeJsRootExtension::class.java, this)
val setupTask = tasks.create(NodeJsSetupTask.NAME, NodeJsSetupTask::class.java) {
it.group = TASKS_GROUP_NAME
it.description = "Download and install a local node/npm version"
}
tasks.create(KotlinNpmInstallTask.NAME, KotlinNpmInstallTask::class.java) {
it.dependsOn(setupTask)
it.group = TASKS_GROUP_NAME
it.description = "Find, download and link NPM dependencies and projects"
}
}
companion object {
const val TASKS_GROUP_NAME: String = "nodeJs"
fun apply(project: Project): NodeJsExtension {
val rootProject = project.rootProject
rootProject.plugins.apply(NodeJsRootPlugin::class.java)
return rootProject.extensions.getByName(EXTENSION_NAME) as NodeJsExtension
}
}
}
@@ -11,7 +11,7 @@ import java.io.File
import java.net.URI import java.net.URI
open class NodeJsSetupTask : DefaultTask() { open class NodeJsSetupTask : DefaultTask() {
private val settings = project.nodeJs.root private val settings get() = project.nodeJs.root
private val env by lazy { settings.environment } private val env by lazy { settings.environment }
val ivyDependency: String val ivyDependency: String
@@ -21,6 +21,7 @@ open class NodeJsSetupTask : DefaultTask() {
@OutputDirectory get() = env.nodeDir @OutputDirectory get() = env.nodeDir
init { init {
@Suppress("LeakingThis")
onlyIf { onlyIf {
settings.download && !env.nodeBinDir.isDirectory settings.download && !env.nodeBinDir.isDirectory
} }
@@ -18,7 +18,7 @@ import java.io.File
internal class GradleNodeModuleBuilder( internal class GradleNodeModuleBuilder(
val project: Project, val project: Project,
val dependency: ResolvedDependency, val dependency: ResolvedDependency,
val artifacts: Set<ResolvedArtifact>, val artifacts: Collection<ResolvedArtifact>,
val cache: GradleNodeModulesCache val cache: GradleNodeModulesCache
) { ) {
var srcPackageJsonFile: File? = null var srcPackageJsonFile: File? = null
@@ -6,44 +6,43 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
/** /**
* Cache for storing already created [GradleNodeModule]s * Cache for storing already created [GradleNodeModule]s
*/ */
internal class GradleNodeModulesCache(val project: Project) : AutoCloseable { internal class GradleNodeModulesCache(val nodeJs: NodeJsRootExtension) : AutoCloseable {
companion object { companion object {
const val STATE_FILE_NAME = ".visited" const val STATE_FILE_NAME = ".visited"
} }
internal val dir = project.nodeJs.root.nodeModulesGradleCacheDir val project: Project get() = nodeJs.project
internal val dir = nodeJs.root.nodeModulesGradleCacheDir
private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "8") private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "8")
fun ensureImported( @Synchronized
artifact: ResolvedArtifact, fun get(
dependency: ResolvedDependency, dependency: ResolvedDependency,
artifacts: MutableSet<ResolvedArtifact>, artifact: ResolvedArtifact
result: NpmGradleDependencies ): GradleNodeModule? {
) {
val key = cache.getOrCompute(artifact.file) { val key = cache.getOrCompute(artifact.file) {
val module = GradleNodeModuleBuilder(project, dependency, artifacts, this) val module = GradleNodeModuleBuilder(project, dependency, listOf(artifact), this)
module.visitArtifacts() module.visitArtifacts()
module.rebuild()?.let { module.rebuild()?.let {
it.name + ":" + it.version it.name + ":" + it.version
} }
} }
if (key != null) { return if (key != null) {
val (name, version) = key.split(":") val (name, version) = key.split(":")
result.externalModules.add(GradleNodeModule(name, version, importedPackageDir(dir, name, version))) GradleNodeModule(name, version, importedPackageDir(dir, name, version))
} } else null
} }
@Synchronized
override fun close() { override fun close() {
cache.close() cache.close()
} }
@@ -6,7 +6,7 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
/** /**
* NodeJS package manager API * NodeJS package manager API
@@ -14,11 +14,11 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage
interface NpmApi { interface NpmApi {
fun setup(project: Project) fun setup(project: Project)
fun resolveProject(resolvedNpmProject: NpmProjectPackage) fun resolveProject(resolvedNpmProject: KotlinCompilationNpmResolution)
fun resolveRootProject( fun resolveRootProject(
rootProject: Project, rootProject: Project,
subProjects: Collection<NpmProjectPackage> subProjects: Collection<KotlinCompilationNpmResolution>
) )
companion object { companion object {
@@ -16,9 +16,10 @@ import org.gradle.api.internal.artifacts.dependencies.SelfResolvingDependencyInt
import org.gradle.api.tasks.TaskDependency import org.gradle.api.tasks.TaskDependency
import org.gradle.internal.component.local.model.DefaultLibraryBinaryIdentifier import org.gradle.internal.component.local.model.DefaultLibraryBinaryIdentifier
import org.jetbrains.kotlin.gradle.internal.isInIdeaSync import org.jetbrains.kotlin.gradle.internal.isInIdeaSync
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import java.io.File import java.io.File
data class NpmDependency( data class NpmDependency(
@@ -83,14 +84,16 @@ data class NpmDependency(
} }
} }
private fun resolveProject(): NpmProjectPackage? { private fun resolveProject(): KotlinCompilationNpmResolution? {
val nodeJs = project.nodeJs.root
val resolvedProject = val resolvedProject =
if (isInIdeaSync) KotlinNpmResolver.resolveIfNeeded(project) if (isInIdeaSync) nodeJs.resolveIfNeeded(project)
else KotlinNpmResolver.getAlreadyResolvedOrNull(project) else nodeJs.getAlreadyResolvedOrNull(project)
return when (resolvedProject) { return when (resolvedProject) {
null -> null null -> null
else -> findIn(resolvedProject) ?: error("Project hierarchy is already resolved in NPM without $this") else -> findIn(resolvedProject) ?: error("NPM project resolved without $this")
} }
} }
@@ -1,21 +0,0 @@
/*
* Copyright 2010-2019 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
import org.gradle.api.Project
/**
* Gradle dependencies for [NpmProject].
*/
class NpmGradleDependencies {
val internalModules = mutableSetOf<Project>()
val externalModules = mutableSetOf<GradleNodeModule>()
fun addAll(other: NpmGradleDependencies) {
internalModules.addAll(other.internalModules)
externalModules.addAll(other.externalModules)
}
}
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinPackageJsonTask
import java.io.File import java.io.File
val KotlinJsCompilation.npmProject: NpmProject val KotlinJsCompilation.npmProject: NpmProject
@@ -22,14 +22,17 @@ val KotlinJsCompilation.npmProject: NpmProject
* Basic info for [NpmProject] created from [compilation]. * Basic info for [NpmProject] created from [compilation].
* This class contains only basic info. * This class contains only basic info.
* *
* More info can be obtained from [NpmProjectPackage], which is available after project resolution (after [NpmResolveTask] execution). * More info can be obtained from [KotlinCompilationNpmResolution], which is available after project resolution (after [KotlinNpmInstallTask] execution).
*/ */
open class NpmProject(val compilation: KotlinJsCompilation) { open class NpmProject(val compilation: KotlinJsCompilation) {
val name: String val name: String
get() = buildNpmProjectName() get() = buildNpmProjectName()
val nodeJs
get() = project.nodeJs.root
val dir: File val dir: File
get() = project.nodeJs.root.projectPackagesDir.resolve(name) get() = nodeJs.projectPackagesDir.resolve(name)
val target: KotlinTarget val target: KotlinTarget
get() = compilation.target get() = compilation.target
@@ -46,6 +49,9 @@ open class NpmProject(val compilation: KotlinJsCompilation) {
val packageJsonTaskName: String val packageJsonTaskName: String
get() = compilation.disambiguateName("packageJson") get() = compilation.disambiguateName("packageJson")
val packageJsonTask: KotlinPackageJsonTask
get() = project.tasks.getByName(packageJsonTaskName) as KotlinPackageJsonTask
val main: String val main: String
get() = "kotlin/$name.js" get() = "kotlin/$name.js"
@@ -54,11 +60,11 @@ open class NpmProject(val compilation: KotlinJsCompilation) {
} }
private val rootNodeModules: NpmProjectModules? private val rootNodeModules: NpmProjectModules?
get() = NpmProjectModules(project.nodeJs.root.rootPackageDir) get() = NpmProjectModules(nodeJs.rootPackageDir)
fun useTool(exec: ExecSpec, tool: String, vararg args: String) { fun useTool(exec: ExecSpec, tool: String, vararg args: String) {
exec.workingDir = dir exec.workingDir = dir
exec.executable = project.nodeJs.root.environment.nodeExecutable exec.executable = nodeJs.environment.nodeExecutable
exec.args = listOf(require(tool)) + args exec.args = listOf(require(tool)) + args
} }
@@ -66,7 +72,7 @@ open class NpmProject(val compilation: KotlinJsCompilation) {
* Require [request] nodejs module and return canonical path to it's main js file. * Require [request] nodejs module and return canonical path to it's main js file.
*/ */
fun require(request: String): String { fun require(request: String): String {
KotlinNpmResolver.requireResolved(project) nodeJs.requireResolved(project)
return modules.require(request) return modules.require(request)
} }
@@ -7,8 +7,9 @@ package org.jetbrains.kotlin.gradle.targets.js.npm
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.StringWriter import java.io.StringWriter
@@ -18,10 +19,11 @@ import java.nio.file.Files
* Handles simple case, when there are no npm depenencies required, but some tasks steel needs * Handles simple case, when there are no npm depenencies required, but some tasks steel needs
* node_modules with symlinked packages and packages_imported * node_modules with symlinked packages and packages_imported
*/ */
class NpmSimpleLinker(val rootProject: Project) { class NpmSimpleLinker(val nodeJs: NodeJsRootExtension) {
private val rootProjectNodeModules = rootProject.nodeJs.root.rootPackageDir.resolve(NpmProject.NODE_MODULES) private val rootProject = nodeJs.project
private val rootProjectNodeModules = nodeJs.rootPackageDir.resolve(NpmProject.NODE_MODULES)
fun link(projects: Collection<NpmProjectPackage>) { fun link(projects: Collection<KotlinCompilationNpmResolution>) {
rootProjectNodeModules.listFiles()?.forEach { rootProjectNodeModules.listFiles()?.forEach {
val path = it.toPath() val path = it.toPath()
when { when {
@@ -43,7 +45,7 @@ class NpmSimpleLinker(val rootProject: Project) {
// packages_imported // packages_imported
projects.flatMapTo(mutableSetOf()) { projects.flatMapTo(mutableSetOf()) {
it.gradleDependencies.externalModules.map { gradleNodeModule -> gradleNodeModule } it.externalGradleDependencies
}.forEach { }.forEach {
fsLinker.link(getNodeModulePath(it.name), it.path.canonicalFile) fsLinker.link(getNodeModulePath(it.name), it.path.canonicalFile)
} }
@@ -59,10 +61,10 @@ class NpmSimpleLinker(val rootProject: Project) {
} }
fun createFSLinker(): FSLinker = fun createFSLinker(): FSLinker =
if (rootProject.nodeJs.root.environment.isWindows) WindowsJunctionFsLinker(rootProject, rootProjectNodeModules) if (nodeJs.environment.isWindows) WindowsJunctionFsLinker(rootProject, rootProjectNodeModules, nodeJs)
else JavaFsLinker() else JavaFsLinker()
class WindowsJunctionFsLinker(val project: Project, val dir: File) : FSLinker { class WindowsJunctionFsLinker(val project: Project, val dir: File, val nodeJs: NodeJsRootExtension) : FSLinker {
val js = StringBuilder() val js = StringBuilder()
init { init {
@@ -84,7 +86,7 @@ class NpmSimpleLinker(val rootProject: Project) {
override fun execute() { override fun execute() {
val result = project.exec { exec -> val result = project.exec { exec ->
exec.workingDir = dir exec.workingDir = dir
exec.executable = project.nodeJs.root.environment.nodeExecutable exec.executable = nodeJs.environment.nodeExecutable
exec.standardInput = ByteArrayInputStream(js.toString().toByteArray()) exec.standardInput = ByteArrayInputStream(js.toString().toByteArray())
exec.standardOutput = System.`out` exec.standardOutput = System.`out`
exec.errorOutput = System.err exec.errorOutput = System.err
@@ -6,18 +6,19 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolved package org.jetbrains.kotlin.gradle.targets.js.npm.resolved
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModule
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmGradleDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
/** /**
* [NpmProject] full package info * Resolved [NpmProject]
*/ */
class NpmProjectPackage( class KotlinCompilationNpmResolution(
val project: Project, val project: Project,
val npmProject: NpmProject, val npmProject: NpmProject,
val npmDependencies: Collection<NpmDependency>, val internalDependencies: Collection<KotlinCompilationNpmResolution>,
val gradleDependencies: NpmGradleDependencies, val externalGradleDependencies: Collection<GradleNodeModule>,
val externalNpmDependencies: Collection<NpmDependency>,
val packageJson: PackageJson val packageJson: PackageJson
) )
@@ -1,12 +0,0 @@
/*
* Copyright 2010-2019 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
class KotlinNpmResolution(private val projects: Map<Project, KotlinProjectNpmResolution>) {
operator fun get(project: Project) = projects[project] ?: KotlinProjectNpmResolution.empty(project)
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolved package org.jetbrains.kotlin.gradle.targets.js.npm.resolved
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
@@ -16,14 +15,13 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
*/ */
class KotlinProjectNpmResolution( class KotlinProjectNpmResolution(
val project: Project, val project: Project,
val npmProjects: List<NpmProjectPackage>, val npmProjects: List<KotlinCompilationNpmResolution>,
val taskRequirements: Map<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>> val taskRequirements: Map<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>
) { ) {
val npmProjectsByCompilation: Map<KotlinJsCompilation, NpmProjectPackage> = npmProjects.associateBy { it.npmProject.compilation } val npmProjectsByNpmDependency: Map<NpmDependency, KotlinCompilationNpmResolution> =
val npmProjectsByNpmDependency: Map<NpmDependency, NpmProjectPackage> = mutableMapOf<NpmDependency, KotlinCompilationNpmResolution>().also { result ->
mutableMapOf<NpmDependency, NpmProjectPackage>().also { result ->
npmProjects.forEach { npmPackage -> npmProjects.forEach { npmPackage ->
npmPackage.npmDependencies.forEach { npmDependency -> npmPackage.externalNpmDependencies.forEach { npmDependency ->
result[npmDependency] = npmPackage result[npmDependency] = npmPackage
} }
} }
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2019 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.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
class KotlinRootNpmResolution(
val rootProject: Project,
private val projects: Map<Project, KotlinProjectNpmResolution>
) : NodeJsRootExtension.ResolutionStateData {
operator fun get(project: Project) = projects[project] ?: KotlinProjectNpmResolution.empty(project)
override val compilations: Collection<KotlinJsCompilation>
get() = projects.values.flatMap { it.npmProjects.map { it.npmProject.compilation } }
}
@@ -10,27 +10,56 @@ import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Usage import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.* import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.fixSemver
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import java.io.File
import java.io.Serializable
internal class KotlinCompilationNpmResolver( internal class KotlinCompilationNpmResolver(
val projectResolver: KotlinProjectNpmResolver, val projectResolver: KotlinProjectNpmResolver,
val compilation: KotlinJsCompilation val compilation: KotlinJsCompilation
) { ) {
val resolver = projectResolver.resolver val resolver = projectResolver.resolver
val npmProject = compilation.npmProject
val nodeJs get() = resolver.nodeJs
val target get() = compilation.target val target get() = compilation.target
val project get() = target.project val project get() = target.project
override fun toString(): String = "KotlinCompilationNpmResolver(${npmProject.name})"
val aggregatedConfiguration: Configuration by lazy { val aggregatedConfiguration: Configuration by lazy {
createAggregatedConfiguration() createAggregatedConfiguration()
} }
val projectPackage by lazy { val packageJsonProducer: PackageJsonProducer by lazy {
createPackageJson(aggregatedConfiguration) val visitor = ConfigurationVisitor()
visitor.visit(aggregatedConfiguration)
visitor.toPackageJsonProducer()
}
private var closed = false
private var resolution: KotlinCompilationNpmResolution? = null
fun resolve() {
check(!closed) { "$this already closed" }
check(resolution == null) { "$this already resolved" }
resolution = packageJsonProducer.createPackageJson()
}
fun close(): KotlinCompilationNpmResolution? {
check(!closed) { "$this already closed" }
closed = true
return resolution
} }
private fun createAggregatedConfiguration(): Configuration { private fun createAggregatedConfiguration(): Configuration {
@@ -58,8 +87,8 @@ internal class KotlinCompilationNpmResolver(
} }
private fun createNpmToolsConfiguration(): Configuration? { private fun createNpmToolsConfiguration(): Configuration? {
val taskRequirements = projectResolver.requiredFromTasksByCompilation[compilation] val taskRequirements = projectResolver.getTaskRequirements(compilation)
?.takeIf { it.isNotEmpty() } ?: return null if (taskRequirements.isEmpty()) return null
val toolsConfiguration = project.configurations.create("${compilation.name}NpmTools") val toolsConfiguration = project.configurations.create("${compilation.name}NpmTools")
@@ -77,93 +106,130 @@ internal class KotlinCompilationNpmResolver(
return toolsConfiguration return toolsConfiguration
} }
private fun createPackageJson(configuration: Configuration): NpmProjectPackage { data class ExternalGradleDependency(
val npmProject = compilation.npmProject val dependency: ResolvedDependency,
val name = npmProject.name val artifact: ResolvedArtifact
val packageJson = PackageJson( )
name,
fixSemver(project.version.toString())
)
val npmDependencies = mutableSetOf<NpmDependency>()
val gradleDependencies = NpmGradleDependencies()
packageJson.main = npmProject.main inner class ConfigurationVisitor {
private val internalDependencies = mutableSetOf<KotlinCompilationNpmResolver>()
private val externalGradleDependencies = mutableSetOf<ExternalGradleDependency>()
private val externalNpmDependencies = mutableSetOf<NpmDependency>()
collectDependenciesFromConfiguration(configuration, gradleDependencies) fun visit(configuration: Configuration) {
configuration.allDependencies.forEach { dependency ->
when (dependency) {
is NpmDependency -> npmDependencies.add(dependency)
}
}
npmDependencies.forEach {
packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version)
}
gradleDependencies.externalModules.forEach {
packageJson.dependencies[it.name] = it.version
}
gradleDependencies.internalModules.forEach { target ->
val targetResolver = resolver.findDependentResolver(project, target)
if (targetResolver != null) {
val targetPackageJson = targetResolver.projectPackage.packageJson
packageJson.dependencies[targetPackageJson.name] = targetPackageJson.version
}
}
project.nodeJs.packageJsonHandlers.forEach {
it(packageJson)
}
val npmPackage = NpmProjectPackage(
project,
npmProject,
npmDependencies,
gradleDependencies,
packageJson
)
npmPackage.packageJson.saveTo(npmProject.packageJsonFile)
return npmPackage
}
fun chooseVersion(oldVersion: String?, newVersion: String): String =
oldVersion ?: newVersion // todo: real versions conflict resolution
private fun collectDependenciesFromConfiguration(configuration: Configuration, result: NpmGradleDependencies) {
if (configuration.isCanBeResolved) {
configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach { configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach {
visitDependency(it, result) visitDependency(it)
}
configuration.allDependencies.forEach { dependency ->
when (dependency) {
is NpmDependency -> externalNpmDependencies.add(dependency)
}
}
if (compilation.name == KotlinCompilation.TEST_COMPILATION_NAME) {
val main = compilation.target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME) as KotlinJsCompilation
internalDependencies.add(projectResolver[main])
} }
} }
}
private fun visitDependency(dependency: ResolvedDependency, result: NpmGradleDependencies) { private fun visitDependency(dependency: ResolvedDependency) {
visitArtifacts(dependency, dependency.moduleArtifacts, result) visitArtifacts(dependency, dependency.moduleArtifacts)
dependency.children.forEach { dependency.children.forEach {
visitDependency(it, result) visitDependency(it)
}
} }
private fun visitArtifacts(
dependency: ResolvedDependency,
artifacts: MutableSet<ResolvedArtifact>
) {
artifacts.forEach { artifact ->
val componentIdentifier = artifact.id.componentIdentifier
if (componentIdentifier is ProjectComponentIdentifier) {
val dependentProject = project.findProject(componentIdentifier.projectPath)
?: error("Cannot find project ${componentIdentifier.projectPath}")
val dependentResolver = resolver.findDependentResolver(project, dependentProject)
if (dependentResolver != null) {
internalDependencies.add(dependentResolver)
}
} else {
externalGradleDependencies.add(ExternalGradleDependency(dependency, artifact))
}
}
}
fun toPackageJsonProducer() = PackageJsonProducer(internalDependencies, externalGradleDependencies, externalNpmDependencies)
} }
private fun visitArtifacts( class PackageJsonProducerInputs(
dependency: ResolvedDependency, @get:Input
artifacts: MutableSet<ResolvedArtifact>, val internalDependencies: Collection<String>,
result: NpmGradleDependencies
@get:InputFiles
val externalGradleDependencies: Collection<File>,
@get:Input
val externalDependencies: Collection<String>
) : Serializable
@Suppress("MemberVisibilityCanBePrivate")
inner class PackageJsonProducer(
val internalDependencies: Collection<KotlinCompilationNpmResolver>,
val externalGradleDependencies: Collection<ExternalGradleDependency>,
val externalNpmDependencies: Collection<NpmDependency>
) { ) {
artifacts.forEach { artifact -> val inputs: PackageJsonProducerInputs
val componentIdentifier = artifact.id.componentIdentifier get() = PackageJsonProducerInputs(
if (componentIdentifier is ProjectComponentIdentifier) { internalDependencies.map { it.npmProject.name },
val dependentProject = project.findProject(componentIdentifier.projectPath) externalGradleDependencies.map { it.artifact.file },
?: error("Cannot find project ${componentIdentifier.projectPath}") externalNpmDependencies.map { "${it.key}:${it.version}" }
)
result.internalModules.add(dependentProject) fun createPackageJson(): KotlinCompilationNpmResolution {
} else { val resolvedInternalDependencies = internalDependencies.map {
resolver.gradleNodeModules.ensureImported(artifact, dependency, artifacts, result) it.resolution ?: error("Unresolved dependent npm package: ${this@KotlinCompilationNpmResolver} -> $it")
} }
val importedExternalGradleDependencies = externalGradleDependencies.mapNotNull {
resolver.gradleNodeModules.get(it.dependency, it.artifact)
}
val packageJson = PackageJson(
npmProject.name,
fixSemver(project.version.toString())
)
resolvedInternalDependencies.forEach {
packageJson.dependencies[it.packageJson.name] = it.packageJson.version
}
importedExternalGradleDependencies.forEach {
packageJson.dependencies[it.name] = it.version
}
externalNpmDependencies.forEach {
packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version)
}
nodeJs.packageJsonHandlers.forEach {
it(packageJson)
}
packageJson.saveTo(npmProject.packageJsonFile)
return KotlinCompilationNpmResolution(
project,
npmProject,
resolvedInternalDependencies,
importedExternalGradleDependencies,
externalNpmDependencies,
packageJson
)
} }
private fun chooseVersion(oldVersion: String?, newVersion: String): String =
oldVersion ?: newVersion // todo: real versions conflict resolution
} }
} }
@@ -1,160 +0,0 @@
/*
* Copyright 2010-2019 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.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage
/**
* Generates `package.json` file for [NpmProject] with npm or js dependencies and
* runs selected [NodeJsRootExtension.packageManager] to download and install all of it's dependencies.
*
* All [NpmDependency] for configurations related to kotlin/js will be added to `package.json`.
* For external gradle modules, fake npm packages will be created and added to `package.json`
* as path to directory.
*/
internal class KotlinNpmResolver private constructor(val rootProject: Project) {
companion object {
private sealed class State {
class Resolving(val resolver: KotlinNpmResolver) : State()
class Resolved(val resolved: KotlinNpmResolution) : State()
}
@Volatile
private var initializedRootProject: Project? = null
private var state: State? = null
private fun getState(rootProject: Project): State {
check(rootProject == rootProject.rootProject)
if (initializedRootProject == null) {
synchronized(this) {
if (initializedRootProject == null) {
initializedRootProject = rootProject
state = State.Resolving(KotlinNpmResolver(rootProject))
}
}
}
check(initializedRootProject == rootProject)
return state!!
}
private fun setResolution(resolution: KotlinNpmResolution) {
state = State.Resolved(resolution)
}
fun getResolver(rootProject: Project): KotlinNpmResolver =
(getState(rootProject) as? State.Resolving ?: error("NPM Dependencies already resolved and installed"))
.resolver
fun resolveIfNeeded(project: Project): KotlinProjectNpmResolution {
val state = getState(project.rootProject)
val resolution = when (state) {
is State.Resolved -> state.resolved
is State.Resolving -> state.resolver.installAndClose()
}
return resolution[project]
}
fun getAlreadyResolvedOrNull(project: Project): KotlinProjectNpmResolution? {
val state = getState(project.rootProject)
return when (state) {
is State.Resolved -> state.resolved[project]
is State.Resolving -> null
}
}
fun requireResolved(project: Project, reason: String = ""): KotlinProjectNpmResolution =
getAlreadyResolvedOrNull(project) ?: error("NPM dependencies should be resolved $reason")
fun checkRequiredDependencies(project: Project, target: RequiresNpmDependencies) {
val requestedTaskDependencies = requireResolved(
project,
"before $target execution"
).taskRequirements
val targetRequired = requestedTaskDependencies[target]?.toSet() ?: setOf()
target.requiredNpmDependencies.forEach {
check(it in targetRequired) {
"$it required by $target was not found resolved at the time of nodejs package manager call. " +
"This may be caused by changing $target configuration after npm dependencies resolution."
}
}
}
}
val gradleNodeModules = GradleNodeModulesCache(rootProject)
val projectResolvers = mutableMapOf<Project, KotlinProjectNpmResolver>()
init {
if (rootProject.logger.isDebugEnabled) {
rootProject.logger.debug("KotlinNpmResolver initialization triggered at: ${Thread.currentThread().stackTrace.joinToString("\n")}")
}
rootProject.allprojects.toList().forEach {
projectResolvers[it] = KotlinProjectNpmResolver(it, this)
}
}
fun installAndClose(): KotlinNpmResolution {
if (rootProject.logger.isDebugEnabled) {
rootProject.logger.debug("KotlinNpmResolver installation triggered at: ${Thread.currentThread().stackTrace.joinToString("\n")}")
}
val nodeJs = NodeJsPlugin.apply(rootProject).root
val packageManager = nodeJs.packageManager
val allNpmPackages = projectResolvers.values.flatMap { it.byCompilation.values.map { it.projectPackage } }
removeOutdatedPackages(nodeJs, allNpmPackages)
gradleNodeModules.close()
if (allNpmPackages.any { it.npmDependencies.isNotEmpty() }) {
packageManager.resolveRootProject(rootProject, allNpmPackages)
} else if (projectResolvers.values.any { it.hasNodeModulesDependentTasks }) {
NpmSimpleLinker(rootProject).link(allNpmPackages)
}
val resolution = KotlinNpmResolution(projectResolvers.values.map { it.toResolved() }.associateBy { it.project })
setResolution(resolution)
return resolution
}
private fun removeOutdatedPackages(nodeJs: NodeJsRootExtension, allNpmPackages: List<NpmProjectPackage>) {
val packages = allNpmPackages.mapTo(mutableSetOf()) { it.npmProject.name }
nodeJs.projectPackagesDir.listFiles()?.forEach {
if (it.name !in packages) {
it.deleteRecursively()
}
}
}
fun findDependentResolver(src: Project, target: Project): KotlinCompilationNpmResolver? {
// todo: proper finding using KotlinTargetComponent.findUsageContext
val targetResolver = getResolver(target.rootProject).projectResolvers[target] ?: return null
val mainCompilations =
targetResolver.byCompilation.entries.filter { it.key.name == KotlinCompilation.MAIN_COMPILATION_NAME }
return if (mainCompilations.isNotEmpty()) {
if (mainCompilations.size > 1) {
error(
"Cannot resolve project dependency $src -> $target." +
"Dependency to project with multiple js compilation not supported yet."
)
}
mainCompilations.single().value
} else null
}
}
@@ -18,56 +18,96 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResol
internal class KotlinProjectNpmResolver( internal class KotlinProjectNpmResolver(
val project: Project, val project: Project,
val resolver: KotlinNpmResolver val resolver: KotlinRootNpmResolver
) { ) {
val byCompilation = mutableMapOf<KotlinJsCompilation, KotlinCompilationNpmResolver>() override fun toString(): String = "ProjectNpmResolver($project)"
private val taskRequirements = mutableMapOf<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>() private val byCompilation = mutableMapOf<KotlinJsCompilation, KotlinCompilationNpmResolver>()
val requiredFromTasksByCompilation = mutableMapOf<KotlinJsCompilation, MutableList<RequiresNpmDependencies>>()
var hasNodeModulesDependentTasks = false
private set
init { operator fun get(compilation: KotlinJsCompilation): KotlinCompilationNpmResolver {
visit() check(compilation.target.project == project)
requireContainerListeners()
return byCompilation[compilation] ?: error("$compilation was not registered in $this")
} }
fun visit() { private var closed = false
project.tasks.toList().forEach { task ->
val compilationResolvers: Collection<KotlinCompilationNpmResolver>
get() {
requireContainerListeners()
return byCompilation.values
}
private val taskRequirements = mutableMapOf<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>()
private val requiredFromTasksByCompilation = mutableMapOf<KotlinJsCompilation, MutableList<RequiresNpmDependencies>>()
fun getTaskRequirements(compilation: KotlinJsCompilation): Collection<RequiresNpmDependencies> {
requireContainerListeners()
return requiredFromTasksByCompilation[compilation] ?: listOf()
}
val hasNodeModulesDependentTasks: Boolean
get() {
requireContainerListeners()
return _hasNodeModulesDependentTasks
}
@Volatile
var _hasNodeModulesDependentTasks = false
private set
var listenersInstalled = false
@Synchronized
private fun requireContainerListeners() {
if (!listenersInstalled) {
listenersInstalled = true
addContainerListeners()
}
}
private fun addContainerListeners() {
project.tasks.forEach { task ->
if (task.enabled && task is RequiresNpmDependencies) { if (task.enabled && task is RequiresNpmDependencies) {
addTaskRequirements(task) addTaskRequirements(task)
} }
} }
val kotlin = project.kotlinExtensionOrNull val kotlin = project.kotlinExtensionOrNull
when (kotlin) {
if (kotlin != null) { is KotlinSingleTargetExtension -> addTargetListeners(kotlin.target)
when (kotlin) { is KotlinMultiplatformExtension -> kotlin.targets.forEach {
is KotlinSingleTargetExtension -> visitTarget(kotlin.target) addTargetListeners(it)
is KotlinMultiplatformExtension -> kotlin.targets.forEach {
visitTarget(it)
}
} }
} }
} }
private fun visitTarget(target: KotlinTarget) { private fun addTargetListeners(target: KotlinTarget) {
check(!closed) { resolver.alreadyResolvedMessage("add target $target") }
if (target.platformType == KotlinPlatformType.js) { if (target.platformType == KotlinPlatformType.js) {
target.compilations.toList().forEach { compilation -> target.compilations.forEach { compilation ->
if (compilation is KotlinJsCompilation) { if (compilation is KotlinJsCompilation) {
// compilation may be KotlinWithJavaTarget for old Kotlin2JsPlugin // compilation may be KotlinWithJavaTarget for old Kotlin2JsPlugin
visitCompilation(compilation) addCompilation(compilation)
} }
} }
} }
} }
private fun visitCompilation(compilation: KotlinJsCompilation) { @Synchronized
private fun addCompilation(compilation: KotlinJsCompilation) {
check(!closed) { resolver.alreadyResolvedMessage("add compilation $compilation") }
byCompilation[compilation] = KotlinCompilationNpmResolver(this, compilation) byCompilation[compilation] = KotlinCompilationNpmResolver(this, compilation)
} }
@Synchronized
private fun addTaskRequirements(task: RequiresNpmDependencies) { private fun addTaskRequirements(task: RequiresNpmDependencies) {
if (!hasNodeModulesDependentTasks && task.nodeModulesRequired) { check(!closed) { resolver.alreadyResolvedMessage("create task $task that requires npm dependencies") }
hasNodeModulesDependentTasks = true
if (!_hasNodeModulesDependentTasks && task.nodeModulesRequired) {
_hasNodeModulesDependentTasks = true
} }
val requirements = task.requiredNpmDependencies.toList() val requirements = task.requiredNpmDependencies.toList()
@@ -79,9 +119,14 @@ internal class KotlinProjectNpmResolver(
.add(task) .add(task)
} }
fun toResolved() = KotlinProjectNpmResolution( fun close(): KotlinProjectNpmResolution {
project, check(!closed)
byCompilation.values.map { it.projectPackage }, closed = true
taskRequirements
) return KotlinProjectNpmResolution(
project,
byCompilation.values.mapNotNull { it.close() },
taskRequirements
)
}
} }
@@ -0,0 +1,96 @@
/*
* Copyright 2010-2019 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.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmSimpleLinker
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution
/**
* Generates `package.json` file for [NpmProject] with npm or js dependencies and
* runs selected [NodeJsRootExtension.packageManager] to download and install all of it's dependencies.
*
* All [NpmDependency] for configurations related to kotlin/js will be added to `package.json`.
* For external gradle modules, fake npm packages will be created and added to `package.json`
* as path to directory.
*/
internal class KotlinRootNpmResolver internal constructor(val nodeJs: NodeJsRootExtension) : NodeJsRootExtension.ResolutionStateData {
val rootProject: Project
get() = nodeJs.project
private var closed: Boolean = false
val gradleNodeModules = GradleNodeModulesCache(nodeJs)
private val projectResolvers = mutableMapOf<Project, KotlinProjectNpmResolver>()
fun alreadyResolvedMessage(action: String) = "Cannot $action. NodeJS projects already resolved."
@Synchronized
fun addProject(target: Project) {
check(!closed) { alreadyResolvedMessage("add new project: $target") }
projectResolvers[target] = KotlinProjectNpmResolver(target, this)
}
operator fun get(project: Project) = projectResolvers[project] ?: error("$project is not configured for JS usage")
override val compilations: Collection<KotlinJsCompilation>
get() = projectResolvers.values.flatMap { it.compilationResolvers.map { it.compilation } }
fun findDependentResolver(src: Project, target: Project): KotlinCompilationNpmResolver? {
// todo: proper finding using KotlinTargetComponent.findUsageContext
val targetResolver = this[target]
val mainCompilations = targetResolver.compilationResolvers.filter { it.compilation.name == KotlinCompilation.MAIN_COMPILATION_NAME }
return if (mainCompilations.isNotEmpty()) {
if (mainCompilations.size > 1) {
error(
"Cannot resolve project dependency $src -> $target." +
"Dependency to project with multiple js compilation not supported yet."
)
}
mainCompilations.single()
} else null
}
/**
* Don't use directly, use [NodeJsRootExtension.resolveIfNeeded] instead.
*/
internal fun close(): KotlinRootNpmResolution {
check(!closed)
closed = true
val projectResolutions = projectResolvers.values.map { it.close() }.associateBy { it.project }
val allNpmPackages = projectResolutions.values.flatMap { it.npmProjects }
removeOutdatedPackages(nodeJs, allNpmPackages)
gradleNodeModules.close()
if (allNpmPackages.any { it.externalNpmDependencies.isNotEmpty() }) {
nodeJs.packageManager.resolveRootProject(rootProject, allNpmPackages)
} else if (projectResolvers.values.any { it.hasNodeModulesDependentTasks }) {
NpmSimpleLinker(nodeJs).link(allNpmPackages)
}
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()
}
}
}
}
@@ -7,30 +7,30 @@ package org.jetbrains.kotlin.gradle.targets.js.npm.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver
import java.io.File import java.io.File
open class KotlinNpmInstallTask : DefaultTask() { open class KotlinNpmInstallTask : DefaultTask() {
private val resolver: KotlinNpmResolver
get() = KotlinNpmResolver.getResolver(project)
init { init {
check(project == project.rootProject) check(project == project.rootProject)
} }
@Suppress("unused")
@get:InputFiles @get:InputFiles
val packageJsonFiles: Collection<File> val packageJsonFiles: Collection<File>
get() = resolver.projectResolvers.values.flatMap { projectResolver -> get() = project.nodeJs.root.resolutionState.compilations.map { it.npmProject.packageJsonFile }
projectResolver.byCompilation.keys.map { compilation ->
compilation.npmProject.packageJsonFile @get:OutputFile
} val yarnLock: File
} get() = project.nodeJs.root.rootPackageDir.resolve("yarn.lock")
@TaskAction @TaskAction
fun resolve() { fun resolve() {
resolver.installAndClose() project.nodeJs.root.resolveIfNeeded(project)
} }
companion object { companion object {
@@ -6,34 +6,79 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.tasks package org.jetbrains.kotlin.gradle.targets.js.npm.tasks
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.plugin.TaskHolder
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinNpmResolver import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import java.io.File import java.io.File
class KotlinPackageJsonTask : DefaultTask() { open class KotlinPackageJsonTask : DefaultTask() {
@Internal private lateinit var nodeJs: NodeJsRootExtension
lateinit var compilation: KotlinJsCompilation private lateinit var compilation: KotlinJsCompilation
private val compilationResolver: KotlinCompilationNpmResolver private val compilationResolver
get() = KotlinNpmResolver.getResolver(project.rootProject).projectResolvers[project]!!.byCompilation[compilation]!! get() = nodeJs.requireResolver()[project][compilation]
@get:InputFiles private val producer: KotlinCompilationNpmResolver.PackageJsonProducer
val configuration: Configuration get() = compilationResolver.packageJsonProducer
get() = compilationResolver.aggregatedConfiguration
private fun findDependentTasks(): Collection<KotlinPackageJsonTask> =
producer.internalDependencies.map { dependentResolver ->
dependentResolver.npmProject.packageJsonTask
}
companion object {
fun create(compilation: KotlinJsCompilation): TaskHolder<KotlinPackageJsonTask> {
val target = compilation.target
val project = target.project
val npmProject = compilation.npmProject
val nodeJs = project.nodeJs.root
val rootClean = project.rootProject.tasks.getByName(BasePlugin.CLEAN_TASK_NAME)
val npmInstallTask = nodeJs.npmInstallTask
val packageJsonTaskName = npmProject.packageJsonTaskName
val packageJsonTask = project.createOrRegisterTask<KotlinPackageJsonTask>(packageJsonTaskName) { task ->
task.nodeJs = nodeJs
task.compilation = compilation
task.description = "Create package.json file for $compilation"
task.group = NodeJsRootPlugin.TASKS_GROUP_NAME
@Suppress("UnstableApiUsage")
task.dependsOn(target.project.provider{ task.findDependentTasks() })
task.mustRunAfter(rootClean)
}
npmInstallTask.mustRunAfter(rootClean, packageJsonTask.getTaskOrProvider())
compilation.compileKotlinTask.dependsOn(
npmInstallTask,
packageJsonTask.getTaskOrProvider()
)
return packageJsonTask
}
}
@get:Input
@get:Nested
internal val producerInputs: KotlinCompilationNpmResolver.PackageJsonProducerInputs
get() = producer.inputs
@get:OutputFile @get:OutputFile
val packageJson: File val packageJson: File
get() = compilation.npmProject.packageJsonFile get() = compilationResolver.npmProject.packageJsonFile
@TaskAction @TaskAction
fun resolve() { fun resolve() {
compilationResolver.projectPackage compilationResolver.resolve()
} }
} }
@@ -59,7 +59,7 @@ class KotlinBrowserJs(target: KotlinJsTarget) :
it.dependsOn( it.dependsOn(
target.project.nodeJs.root.npmInstallTask, target.project.nodeJs.root.npmInstallTask,
compileKotlinTask, compileKotlinTask,
compilation.processResourcesTaskName target.project.tasks.getByName(compilation.processResourcesTaskName)
) )
it.bin = "webpack-dev-server/bin/webpack-dev-server.js" it.bin = "webpack-dev-server/bin/webpack-dev-server.js"
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetDsl
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
@@ -30,6 +30,8 @@ abstract class KotlinJsSubTarget(
val testTaskName = disambiguateCamelCased("test") val testTaskName = disambiguateCamelCased("test")
fun configure() { fun configure() {
project.nodeJs
configureTests() configureTests()
configureRun() configureRun()
@@ -66,7 +68,7 @@ abstract class KotlinJsSubTarget(
private fun configureTests(compilation: KotlinJsCompilation) { private fun configureTests(compilation: KotlinJsCompilation) {
// apply plugin (cannot be done at task instantiation time) // apply plugin (cannot be done at task instantiation time)
val nodeJs = NodeJsPlugin.apply(target.project).root val nodeJs = NodeJsRootPlugin.apply(target.project).root
val testJs = project.createOrRegisterTask<KotlinJsTest>(testTaskName) { testJs -> val testJs = project.createOrRegisterTask<KotlinJsTest>(testTaskName) { testJs ->
val compileTask = compilation.compileKotlinTask val compileTask = compilation.compileKotlinTask
@@ -31,39 +31,7 @@ class KotlinNodeJs(target: KotlinJsTarget) :
} }
override fun configureRun(compilation: KotlinJsCompilation) { override fun configureRun(compilation: KotlinJsCompilation) {
val project = target.project val runTaskHolder = NodeJsExec.create(compilation, disambiguateCamelCased("run"))
"true".toBoolean()
val runTaskHolder = project.createOrRegisterTask<NodeJsExec>(disambiguateCamelCased("run")) { runTask ->
runTask.description = "run compiled js in nodejs"
val compileKotlinTask = compilation.compileKotlinTask
runTask.dependsOn(target.project.nodeJs.root.npmInstallTask, compileKotlinTask)
runTask.args(compileKotlinTask.outputFile)
}
addSourceMapSupport(compilation, runTaskHolder)
target.runTask.dependsOn(runTaskHolder.getTaskOrProvider()) target.runTask.dependsOn(runTaskHolder.getTaskOrProvider())
} }
private fun addSourceMapSupport(
compilation: KotlinJsCompilation,
runTaskHolder: TaskHolder<NodeJsExec>
) {
compilation.dependencies {
runtimeOnly(kotlin("test-nodejs-runner"))
}
target.project.nodeJs.root.npmInstallTask.doLast {
runTaskHolder.configure { runTask ->
runTask.args(
"--require",
compilation.npmProject.require("kotlin-test-nodejs-runner/kotlin-nodejs-source-map-support")
)
}
}
}
} }
@@ -12,9 +12,9 @@ import org.gradle.process.internal.DefaultProcessForkOptions
import org.gradle.util.ConfigureUtil import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma
@@ -92,14 +92,14 @@ open class KotlinJsTest : KotlinTest(), RequiresNpmDependencies {
} }
override fun executeTests() { override fun executeTests() {
KotlinNpmResolver.checkRequiredDependencies(project, this) project.nodeJs.root.checkRequiredDependencies(project, this)
super.executeTests() super.executeTests()
} }
override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec { override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec {
val forkOptions = DefaultProcessForkOptions(fileResolver) val forkOptions = DefaultProcessForkOptions(fileResolver)
forkOptions.workingDir = compilation.npmProject.dir forkOptions.workingDir = compilation.npmProject.dir
forkOptions.executable = NodeJsPlugin.apply(project).root.environment.nodeExecutable forkOptions.executable = NodeJsRootPlugin.apply(project).root.environment.nodeExecutable
val nodeJsArgs = mutableListOf<String>() val nodeJsArgs = mutableListOf<String>()
@@ -17,7 +17,6 @@ import org.gradle.process.internal.ExecHandleFactory
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.NpmPackageVersion import org.jetbrains.kotlin.gradle.targets.js.NpmPackageVersion
import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinNpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.testing.internal.reportsDir import org.jetbrains.kotlin.gradle.testing.internal.reportsDir
@@ -127,7 +126,7 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies {
@TaskAction @TaskAction
fun doExecute() { fun doExecute() {
KotlinNpmResolver.checkRequiredDependencies(project, this) project.nodeJs.root.checkRequiredDependencies(project, this)
val runner = createRunner() val runner = createRunner()
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
object Yarn : NpmApi { object Yarn : NpmApi {
private fun getDelegate(project: Project): NpmApi = private fun getDelegate(project: Project): NpmApi =
@@ -17,9 +17,9 @@ object Yarn : NpmApi {
override fun setup(project: Project) = override fun setup(project: Project) =
getDelegate(project.rootProject).setup(project) getDelegate(project.rootProject).setup(project)
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) = override fun resolveProject(resolvedNpmProject: KotlinCompilationNpmResolution) =
getDelegate(resolvedNpmProject.project).resolveProject(resolvedNpmProject) getDelegate(resolvedNpmProject.project).resolveProject(resolvedNpmProject)
override fun resolveRootProject(rootProject: Project, subProjects: Collection<NpmProjectPackage>) = override fun resolveRootProject(rootProject: Project, subProjects: Collection<KotlinCompilationNpmResolution>) =
getDelegate(rootProject.project).resolveRootProject(rootProject, subProjects) getDelegate(rootProject.project).resolveRootProject(rootProject, subProjects)
} }
@@ -7,10 +7,9 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.internal.execWithProgress import org.jetbrains.kotlin.gradle.internal.execWithProgress
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import java.io.File import java.io.File
abstract class YarnBasics : NpmApi { abstract class YarnBasics : NpmApi {
@@ -24,7 +23,7 @@ abstract class YarnBasics : NpmApi {
description: String, description: String,
vararg args: String vararg args: String
) { ) {
val nodeJsEnv = NodeJsPlugin.apply(project).root.environment val nodeJsEnv = NodeJsRootPlugin.apply(project).root.environment
val yarnEnv = YarnPlugin.apply(project).environment val yarnEnv = YarnPlugin.apply(project).environment
project.execWithProgress(description) { exec -> project.execWithProgress(description) { exec ->
@@ -9,19 +9,19 @@ import com.google.gson.Gson
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModule import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModule
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import java.io.File import java.io.File
class YarnImportedPackagesVersionResolver( class YarnImportedPackagesVersionResolver(
private val rootProject: Project, private val rootProject: Project,
private val npmProjects: Collection<NpmProjectPackage>, private val npmProjects: Collection<KotlinCompilationNpmResolution>,
private val nodeJsWorldDir: File private val nodeJsWorldDir: File
) { ) {
private val resolvedVersion = mutableMapOf<String, String>() private val resolvedVersion = mutableMapOf<String, String>()
private val importedProjectWorkspaces = mutableListOf<String>() private val importedProjectWorkspaces = mutableListOf<String>()
private val externalModules = npmProjects.flatMapTo(mutableSetOf()) { private val externalModules = npmProjects.flatMapTo(mutableSetOf()) {
it.gradleDependencies.externalModules it.externalGradleDependencies
} }
fun resolveAndUpdatePackages(): MutableList<String> { fun resolveAndUpdatePackages(): MutableList<String> {
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
open class YarnPlugin : Plugin<Project> { open class YarnPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run { override fun apply(project: Project): Unit = project.run {
@@ -15,7 +15,7 @@ open class YarnPlugin : Plugin<Project> {
"YarnPlugin can be applied only to root project" "YarnPlugin can be applied only to root project"
} }
val nodeJs = NodeJsPlugin.apply(this).root val nodeJs = NodeJsRootPlugin.apply(this).root
this.extensions.create(YarnRootExtension.YARN, YarnRootExtension::class.java, this) this.extensions.create(YarnRootExtension.YARN, YarnRootExtension::class.java, this)
tasks.create(YarnSetupTask.NAME, YarnSetupTask::class.java) { tasks.create(YarnSetupTask.NAME, YarnSetupTask::class.java) {
@@ -7,8 +7,7 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
open class YarnRootExtension(val project: Project) { open class YarnRootExtension(val project: Project) {
private val gradleHome = project.gradle.gradleUserHomeDir.also { private val gradleHome = project.gradle.gradleUserHomeDir.also {
@@ -29,7 +28,7 @@ open class YarnRootExtension(val project: Project) {
get() = !disableWorkspaces get() = !disableWorkspaces
internal fun executeSetup() { internal fun executeSetup() {
NodeJsPlugin.apply(project).root.executeSetup() NodeJsRootPlugin.apply(project).root.executeSetup()
val env = environment val env = environment
if (!env.home.isDirectory) { if (!env.home.isDirectory) {
@@ -9,7 +9,7 @@ import de.undercouch.gradle.tasks.download.DownloadAction
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputDirectory
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import java.io.File import java.io.File
import javax.xml.ws.Action import javax.xml.ws.Action
@@ -26,7 +26,7 @@ open class YarnSetupTask : DefaultTask() {
@OutputDirectory get() = env.home @OutputDirectory get() = env.home
init { init {
group = NodeJsPlugin.TASKS_GROUP_NAME group = NodeJsRootPlugin.TASKS_GROUP_NAME
description = "Download and install a local yarn version" description = "Download and install a local yarn version"
onlyIf { onlyIf {
@@ -7,19 +7,19 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
object YarnSimple : YarnBasics() { object YarnSimple : YarnBasics() {
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) { override fun resolveProject(resolvedNpmProject: KotlinCompilationNpmResolution) {
setup(resolvedNpmProject.project.rootProject) setup(resolvedNpmProject.project.rootProject)
val project = resolvedNpmProject.project val project = resolvedNpmProject.project
YarnUpToDateCheck(resolvedNpmProject.npmProject).updateIfNeeded { YarnUpToDateCheck(resolvedNpmProject.npmProject).updateIfNeeded {
yarnExec(project, resolvedNpmProject.npmProject.dir, NpmApi.resolveOperationDescription("yarn for ${project.path}")) yarnExec(project, resolvedNpmProject.npmProject.dir, NpmApi.resolveOperationDescription("yarn for ${project.path}"))
yarnLockReadTransitiveDependencies(resolvedNpmProject.npmProject.dir, resolvedNpmProject.npmDependencies) yarnLockReadTransitiveDependencies(resolvedNpmProject.npmProject.dir, resolvedNpmProject.externalNpmDependencies)
} }
} }
override fun resolveRootProject(rootProject: Project, subProjects: Collection<NpmProjectPackage>) = Unit override fun resolveRootProject(rootProject: Project, subProjects: Collection<KotlinCompilationNpmResolution>) = Unit
} }
@@ -8,13 +8,13 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.* import org.jetbrains.kotlin.gradle.targets.js.npm.*
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.NpmProjectPackage import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution
import java.io.File import java.io.File
object YarnWorkspaces : YarnBasics() { object YarnWorkspaces : YarnBasics() {
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) = Unit override fun resolveProject(resolvedNpmProject: KotlinCompilationNpmResolution) = Unit
override fun resolveRootProject(rootProject: Project, subProjects: Collection<NpmProjectPackage>) { override fun resolveRootProject(rootProject: Project, subProjects: Collection<KotlinCompilationNpmResolution>) {
check(rootProject == rootProject.rootProject) check(rootProject == rootProject.rootProject)
setup(rootProject) setup(rootProject)
resolveWorkspaces(rootProject, subProjects) resolveWorkspaces(rootProject, subProjects)
@@ -22,7 +22,7 @@ object YarnWorkspaces : YarnBasics() {
private fun resolveWorkspaces( private fun resolveWorkspaces(
rootProject: Project, rootProject: Project,
npmProjects: Collection<NpmProjectPackage> npmProjects: Collection<KotlinCompilationNpmResolution>
) { ) {
val upToDateChecks = npmProjects.map { val upToDateChecks = npmProjects.map {
YarnUpToDateCheck(it.npmProject) YarnUpToDateCheck(it.npmProject)
@@ -35,7 +35,7 @@ object YarnWorkspaces : YarnBasics() {
saveRootProjectWorkspacesPackageJson(rootProject, npmProjects, nodeJsWorldDir) saveRootProjectWorkspacesPackageJson(rootProject, npmProjects, nodeJsWorldDir)
yarnExec(rootProject, nodeJsWorldDir, NpmApi.resolveOperationDescription("yarn")) yarnExec(rootProject, nodeJsWorldDir, NpmApi.resolveOperationDescription("yarn"))
yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.npmDependencies }) yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.externalNpmDependencies })
upToDateChecks.forEach { upToDateChecks.forEach {
it.commit() it.commit()
@@ -44,7 +44,7 @@ object YarnWorkspaces : YarnBasics() {
private fun saveRootProjectWorkspacesPackageJson( private fun saveRootProjectWorkspacesPackageJson(
rootProject: Project, rootProject: Project,
npmProjects: Collection<NpmProjectPackage>, npmProjects: Collection<KotlinCompilationNpmResolution>,
nodeJsWorldDir: File nodeJsWorldDir: File
) { ) {
val rootPackageJson = PackageJson(rootProject.name, rootProject.version.toString()) val rootPackageJson = PackageJson(rootProject.name, rootProject.version.toString())
@@ -1 +0,0 @@
implementation-class=org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
@@ -1 +0,0 @@
implementation-class=org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin