From 5d8f2acf5f3f9095bfade5d69508687351f92ff8 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Wed, 15 May 2019 22:07:04 +0300 Subject: [PATCH] Gradle, js: create npm packages per compilation #KT-30530 --- .../js/{npm/NpmPackages.kt => NpmVersions.kt} | 14 +- .../targets/js/nodejs/NodeJsExtension.kt | 4 +- .../gradle/targets/js/nodejs/NodeJsPlugin.kt | 19 +- .../targets/js/nodejs/NodeJsRootExtension.kt | 13 +- .../gradle/targets/js/npm/GradleNodeModule.kt | 3 + .../targets/js/npm/GradleNodeModuleBuilder.kt | 3 + .../targets/js/npm/GradleNodeModulesCache.kt | 6 +- .../kotlin/gradle/targets/js/npm/NpmApi.kt | 27 +- .../gradle/targets/js/npm/NpmDependency.kt | 64 ++-- .../gradle/targets/js/npm/NpmProject.kt | 92 +++-- .../targets/js/npm/NpmProjectGradleDeps.kt | 3 + .../gradle/targets/js/npm/NpmProjectLayout.kt | 47 --- .../targets/js/npm/NpmProjectModules.kt | 5 +- .../targets/js/npm/NpmProjectPackage.kt | 18 + .../targets/js/npm/NpmProjectVisitor.kt | 164 +++++++++ .../gradle/targets/js/npm/NpmProjects.kt | 23 ++ .../gradle/targets/js/npm/NpmResolveTask.kt | 2 +- .../gradle/targets/js/npm/NpmResolver.kt | 322 ++++-------------- .../gradle/targets/js/npm/PackageJson.kt | 34 ++ .../targets/js/npm/RequiresNpmDependencies.kt | 4 + .../targets/js/subtargets/KotlinBrowserJs.kt | 7 +- .../js/subtargets/KotlinJsSubTarget.kt | 8 +- .../targets/js/subtargets/KotlinNodeJs.kt | 12 +- .../gradle/targets/js/testing/KotlinJsTest.kt | 30 +- .../targets/js/testing/karma/KotlinKarma.kt | 14 +- .../targets/js/testing/mocha/KotlinMocha.kt | 16 +- .../testing/nodejs/KotlinNodeJsTestRunner.kt | 19 +- .../targets/js/webpack/KotlinWebpack.kt | 22 +- .../targets/js/webpack/KotlinWebpackRunner.kt | 9 +- .../kotlin/gradle/targets/js/yarn/Yarn.kt | 87 +++-- .../gradle/targets/js/yarn/YarnAvoidance.kt | 13 +- .../targets/js/yarn/YarnRootExtension.kt | 2 +- 32 files changed, 580 insertions(+), 526 deletions(-) rename libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/{npm/NpmPackages.kt => NpmVersions.kt} (85%) delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectPackage.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectVisitor.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjects.kt diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmPackages.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt similarity index 85% rename from libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmPackages.kt rename to libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt index c9dd9e9435d..fb991d5d7fe 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmPackages.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NpmVersions.kt @@ -3,13 +3,16 @@ * 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 +package org.jetbrains.kotlin.gradle.targets.js import org.gradle.api.Project import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.ExternalModuleDependency +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency -class NpmPackages { +/** + * Package versions used by tasks + */ +class NpmVersions { val webpack = NpmPackageVersion("webpack", "4.29.6") val webpackCli = NpmPackageVersion("webpack-cli", "3.3.0") val webpackBundleAnalyzer = NpmPackageVersion("webpack-bundle-analyzer", "3.3.2") @@ -47,10 +50,11 @@ interface RequiredKotlinJsDependency { } data class NpmPackageVersion(val name: String, var version: String) : RequiredKotlinJsDependency { - override fun createDependency(project: Project) = NpmDependency(project, null, name, version) + override fun createDependency(project: Project): Dependency = + NpmDependency(project, null, name, version) } -data class KotlinGradleNpmPackage(val simpleModuleName: String): RequiredKotlinJsDependency { +data class KotlinGradleNpmPackage(val simpleModuleName: String) : RequiredKotlinJsDependency { override fun createDependency(project: Project): Dependency = project.dependencies.create("org.jetbrains.kotlin:kotlin-$simpleModuleName") } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt index ac6ff57c857..3cac8dee4a4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt @@ -6,7 +6,7 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs import org.gradle.api.Project -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmPackages +import org.jetbrains.kotlin.gradle.targets.js.NpmVersions import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson open class NodeJsExtension(val project: Project) { @@ -15,7 +15,7 @@ open class NodeJsExtension(val project: Project) { internal val packageJsonHandlers = mutableListOf Unit>() - val versions = NpmPackages() + val versions = NpmVersions() @Suppress("unused") fun packageJson(handler: PackageJson.() -> Unit) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt index b52a49bc6f7..432c89ef38e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt @@ -6,7 +6,6 @@ import org.gradle.api.plugins.BasePlugin import org.gradle.api.tasks.Delete import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.Companion.NODE_JS import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolveTask -import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject open class NodeJsPlugin : Plugin { override fun apply(project: Project): Unit = project.run { @@ -33,25 +32,15 @@ open class NodeJsPlugin : Plugin { private fun setupCleanNodeModulesTask(project: Project) { project.tasks.create("cleanNodeModules", Delete::class.java) { - it.description = "Deletes node_modules and package.json file" + it.description = "Deletes nodeJs projects created during build" it.group = BasePlugin.BUILD_GROUP - - it.doLast { - project.nodeJs.root.packageManager.cleanProject(project) - } - - project.tasks.maybeCreate(BasePlugin.CLEAN_TASK_NAME).dependsOn(it) + it.delete.add(project.nodeJs.root.rootPackageDir) } project.tasks.create("cleanGradleNodeModules", Delete::class.java) { - it.description = "Deletes node_modules_gradle" + it.description = "Deletes node modules imported from gradle external modules" it.group = BasePlugin.BUILD_GROUP - - it.doLast { - project.npmProject.gradleNodeModulesDir.deleteRecursively() - } - - project.tasks.maybeCreate(BasePlugin.CLEAN_TASK_NAME).dependsOn(it) + it.delete.add(project.nodeJs.root.nodeModulesGradleCacheDir) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt index 3e91afd0978..c5f6f0cc9e2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt @@ -2,12 +2,9 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs import org.gradle.api.Project import org.jetbrains.kotlin.gradle.logging.kotlinInfo -import org.jetbrains.kotlin.gradle.plugin.TaskHolder -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolveTask import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn -import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import java.io.File open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) { @@ -23,7 +20,6 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) { var nodeCommand = "node" - var layout: NpmProjectLayout = NpmProjectLayout.ROOT_PROJECT_BUILD_DIR var packageManager: NpmApi = Yarn val nodeJsSetupTask: NodeJsSetupTask @@ -32,6 +28,15 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) { val npmResolveTask: NpmResolveTask get() = project.tasks.getByName(NpmResolveTask.NAME) as NpmResolveTask + val nodeModulesGradleCacheDir: File + get() = project.buildDir.resolve("node_modules_gradle") + + val rootPackageDir: File + get() = project.buildDir.resolve("js") + + val projectPackagesDir: File + get() = rootPackageDir.resolve("packages") + internal val environment: NodeJsEnv get() { val platform = NodeJsPlatform.name diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModule.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModule.kt index 37f2d921624..c7216f0e08e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModule.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModule.kt @@ -7,4 +7,7 @@ package org.jetbrains.kotlin.gradle.targets.js.npm import java.io.File +/** + * Fake NodeJS module directory created from Gradle external module + */ class GradleNodeModule(val name: String, val path: File) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt index 06f794bc48d..8f5c3dfa19f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt @@ -12,6 +12,9 @@ import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedDependency import java.io.File +/** + * Creates fake NodeJS module directory from given gradle [dependency]. + */ internal class GradleNodeModuleBuilder( val project: Project, val dependency: ResolvedDependency, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt index c2443dbd087..dfb0c184426 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt @@ -11,13 +11,17 @@ import org.gradle.api.artifacts.ResolvedArtifact 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.targets.js.nodejs.nodeJs +/** + * Cache for storing already created [GradleNodeModule]s + */ internal class GradleNodeModulesCache(val project: Project) : AutoCloseable { companion object { const val STATE_FILE_NAME = ".visited" } - internal val dir = project.buildDir.resolve("node_modules_gradle") + internal val dir = project.nodeJs.root.nodeModulesGradleCacheDir private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "5") private val visited = mutableSetOf() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt index 360427a46bb..a5d9af85e7b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt @@ -7,36 +7,19 @@ package org.jetbrains.kotlin.gradle.targets.js.npm import org.gradle.api.Project +/** + * NodeJS package manager API + */ interface NpmApi { fun setup(project: Project) - @Suppress("EXPOSED_PARAMETER_TYPE") - fun resolveProject(npmPackage: NpmResolver.NpmPackage) + fun resolveProject(resolvedNpmProject: NpmProjectPackage) - @Suppress("EXPOSED_PARAMETER_TYPE") fun resolveRootProject( rootProject: Project, - subprojects: MutableList + subProjects: MutableList ) - /** - * Change contents of root's project `package.json`. - * @return true if `package.json` is required even it is empty - */ - @Suppress("EXPOSED_PARAMETER_TYPE") - fun hookRootPackage( - rootProject: Project, - rootPackageJson: PackageJson, - allWorkspaces: Collection - ): Boolean = false - - fun cleanProject(project: Project) { - val npmProject = project.npmProject - - npmProject.nodeModulesDir.deleteRecursively() - npmProject.packageJsonFile.delete() - } - companion object { fun resolveOperationDescription(packageManagerTitle: String): String = "Resolving NPM dependencies using $packageManagerTitle" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmDependency.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmDependency.kt index 69dcf3998fc..3dd003c954d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmDependency.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmDependency.kt @@ -28,12 +28,14 @@ data class NpmDependency( SelfResolvingDependencyInternal, ResolvableDependency, FileCollectionDependency { + override fun getGroup(): String? = org internal val dependencies = mutableSetOf() - override fun resolve(): MutableSet { - resolveProject() ?: return mutableSetOf() + override fun resolve(transitive: Boolean): MutableSet { + val npmPackage = resolveProject() ?: return mutableSetOf() + val npmProject = npmPackage.npmProject val all = mutableSetOf() val visited = mutableSetOf() @@ -41,9 +43,16 @@ data class NpmDependency( fun visit(item: NpmDependency) { if (item in visited) return visited.add(item) - item.project.npmProject.resolve(item.key)?.let { + + npmProject.resolve(item.key)?.let { all.add(it) } + + if (transitive) { + item.dependencies.forEach { + visit(it) + } + } } visit(this) @@ -51,45 +60,42 @@ data class NpmDependency( return all } - private fun resolveProject(): NpmResolver.ResolvedProject? { + override fun resolve(): MutableSet { + val npmPackage = resolveProject() ?: return mutableSetOf() + return mutableSetOf(npmPackage.npmProject.resolve(key)!!) + } + + override fun resolve(context: DependencyResolveContext) { + val npmPackage = resolveProject() + if (npmPackage != null) { + npmPackage.project.files(npmPackage.npmProject.resolve(key)) + dependencies.forEach { + context.add(it) + } + } + } + + private fun resolveProject(): NpmProjectPackage? { val result = - if (isInIdeaSync) NpmResolver.resolve(project) + if (isInIdeaSync) NpmResolver.resolveIfNeeded(project) else NpmResolver.getAlreadyResolvedOrNull(project) return when (result) { null -> null is AlreadyInProgress -> null - is AlreadyResolved -> { - check(this in result.resolution.npmPackage!!.npmDependencies) { - "Project hierarchy is already resolved in NPM without $this" - } - - result.resolution - } - is ResolvedNow -> { - check(this in result.resolution.npmPackage!!.npmDependencies) { - "$this was not visited during resolution" - } - - result.resolution - } + is AlreadyResolved -> findIn(result.resolution) ?: error("Project hierarchy is already resolved in NPM without $this") + is ResolvedNow -> findIn(result.resolution) ?: error("Cannot find $this after NPM project resolve") } } + private fun findIn(npmProjects: NpmProjects) = + npmProjects.npmProjectsByNpmDependency[this] + val key: String = if (org == null) name else "@$org/$name" override fun toString() = "$key: $version" - override fun resolve(context: DependencyResolveContext) { - resolve() - dependencies.forEach { - context.add(it) - } - } - - override fun resolve(transitive: Boolean): MutableSet = resolve() - - override fun getFiles(): FileCollection = project.files(resolve()) + override fun getFiles(): FileCollection = project.files(resolve(true)) override fun getName() = name diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt index 422fbbe1e25..f44e6dfb267 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProject.kt @@ -7,41 +7,52 @@ package org.jetbrains.kotlin.gradle.targets.js.npm import org.gradle.api.Project import org.gradle.process.ExecSpec -import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import java.io.File -open class NpmProject( - val project: Project, - val nodeWorkDir: File, - val searchInParents: Boolean -) { +val KotlinJsCompilation.npmProject: NpmProject + get() = NpmProject(this) + +/** + * Basic info for [NpmProject] created from [compilation]. + * This class contains only basic info. + * + * More info can be obtained from [NpmProjectPackage], which is available after project resolution (after [NpmResolveTask] execution). + */ +open class NpmProject(val compilation: KotlinJsCompilation) { + val name: String + get() = buildNpmProjectName() + + val dir: File + get() = project.nodeJs.root.projectPackagesDir.resolve(name) + + val target: KotlinTarget + get() = compilation.target + + val project: Project + get() = target.project + val nodeModulesDir - get() = nodeWorkDir.resolve(NODE_MODULES) + get() = dir.resolve(NODE_MODULES) val packageJsonFile: File - get() = nodeWorkDir.resolve(PACKAGE_JSON) + get() = dir.resolve(PACKAGE_JSON) - open val compileOutputCopyDest: File? - get() = nodeModulesDir + val main: String + get() = "kotlin/$name.js" - private val modules = object : NpmProjectModules(nodeWorkDir, nodeModulesDir) { - override val parent get() = if (searchInParents) parentModules else null + private val modules = object : NpmProjectModules(dir, nodeModulesDir) { + override val parent get() = rootNodeModules } - private val parentModules: NpmProjectModules? - get() = project.parent?.npmProject?.modules - - val gradleNodeModulesDir: File - get() = project.rootProject.npmProject.nodeModulesDir - - open fun compileOutput(compilationTask: Kotlin2JsCompile): File { - return compileOutputCopyDest?.resolve(compilationTask.outputFile.name) ?: compilationTask.outputFile - } + private val rootNodeModules: NpmProjectModules? + get() = NpmProjectModules(project.nodeJs.root.rootPackageDir) fun useTool(exec: ExecSpec, tool: String, vararg args: String) { - exec.workingDir = nodeWorkDir + exec.workingDir = dir exec.executable = project.nodeJs.root.environment.nodeExecutable exec.args = listOf(require(tool)) + args } @@ -60,19 +71,32 @@ open class NpmProject( */ internal fun resolve(name: String): File? = modules.resolve(name) - override fun toString() = "NpmProject($nodeWorkDir)" + private fun buildNpmProjectName(): String { + val project = target.project + val name = StringBuilder() + + name.append(project.rootProject.name) + + if (project != project.rootProject) { + name.append("-") + name.append(project.name) + } + + if (target.name.isNotEmpty() && target.name.toLowerCase() != "js") { + name.append("-").append(target.name) + } + + if (compilation.name != KotlinCompilation.MAIN_COMPILATION_NAME) { + name.append("-").append(compilation.name) + } + + return name.toString() + } + + override fun toString() = "NpmProject($name)" companion object { const val PACKAGE_JSON = "package.json" const val NODE_MODULES = "node_modules" - - operator fun get(project: Project): NpmProject { - val nodeJsRootExtension = NodeJsPlugin.apply(project) - - return nodeJsRootExtension.root.layout.newNpmProject(project) - } } -} - -val Project.npmProject - get() = NpmProject[this] \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectGradleDeps.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectGradleDeps.kt index 52379a45f39..f727c67ecac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectGradleDeps.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectGradleDeps.kt @@ -7,6 +7,9 @@ package org.jetbrains.kotlin.gradle.targets.js.npm import org.gradle.api.Project +/** + * Gradle dependencies for [NpmProject]. + */ class NpmProjectGradleDeps { val internalModules = mutableSetOf() val externalModules = mutableSetOf() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt deleted file mode 100644 index 1d55f64d42b..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2010-2019 JetBrains s.r.o. 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 -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile -import java.io.File - -enum class NpmProjectLayout { - PROJECT_DIR { - override val allowYarnWorkspaces: Boolean - get() = true - - override fun newNpmProject(project: Project) = - NpmProject(project, project.projectDir, searchInParents = true) - }, - ROOT_PROJECT_BUILD_DIR { - override val allowYarnWorkspaces: Boolean - get() = true - - override fun newNpmProject(project: Project): NpmProject { - val nodeJsWorldDir = project.rootProject.buildDir.resolve("nodejs-project") - - val workDir = - if (project == project.rootProject) nodeJsWorldDir - else { - val projectPackage = project.path.removePrefix(":").replace(":", File.separator) - nodeJsWorldDir.resolve("packages").resolve(projectPackage) - } - - return NpmProject(project, workDir, searchInParents = true) - } - }, - BUILD_DIR { - override fun newNpmProject(project: Project) = - NpmProject(project, project.buildDir, searchInParents = false) - }; - - open val allowYarnWorkspaces: Boolean - get() = false - - abstract fun newNpmProject(project: Project): NpmProject -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectModules.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectModules.kt index 9bd30333768..69e1417bb16 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectModules.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectModules.kt @@ -11,9 +11,12 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.NODE_MODU import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject.Companion.PACKAGE_JSON import java.io.File +/** + * Search modules in node_modules according to https://nodejs.org/api/modules.html. + */ internal open class NpmProjectModules( val dir: File, - val nodeModulesDir: File = dir.resolve(NODE_MODULES) + private val nodeModulesDir: File = dir.resolve(NODE_MODULES) ) { /** * Require [request] nodejs module and return canonical path to it's main js file. diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectPackage.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectPackage.kt new file mode 100644 index 00000000000..2cc3aed5f6a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectPackage.kt @@ -0,0 +1,18 @@ +/* + * 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 + +/** + * [NpmProject] full package info + */ +class NpmProjectPackage( + val project: Project, + val npmProject: NpmProject, + val npmDependencies: Collection, + val packageJson: PackageJson +) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectVisitor.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectVisitor.kt new file mode 100644 index 00000000000..3709eb9ca40 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectVisitor.kt @@ -0,0 +1,164 @@ +/* + * 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 +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.Dependency +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension +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.targets.js.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension +import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs + +/** + * Visits given gradle [project] for all of its [NpmProject], + * creates `package.json` for them ([NpmProjectPackage]) and runs + * selected [NodeJsRootExtension.packageManager] to download and install all of it's dependencies. + */ +internal class NpmProjectVisitor(val resolver: NpmResolver, val project: Project) { + private val npmProjects = mutableListOf() + private val byCompilation = mutableMapOf() + private val byNpmDependency = mutableMapOf() + private val taskRequirements = mutableMapOf>() + private val requiredFromTasksByCompilation = mutableMapOf>() + + private fun addTaskRequirements(task: RequiresNpmDependencies) { + val requirements = task.requiredNpmDependencies.toList() + + taskRequirements[task] = requirements + + val requiredDependenciesList = requiredFromTasksByCompilation.getOrPut(task.compilation) { mutableListOf() } + requirements.forEach { requiredDependency -> + requiredDependenciesList.add(requiredDependency.createDependency(project)) + } + } + + private fun addNpmProject(resolved: NpmProjectPackage) { + val compilation = resolved.npmProject.compilation + byCompilation[compilation] = resolved + resolved.npmDependencies.forEach { + byNpmDependency[it] = resolved + } + npmProjects.add(resolved) + } + + fun visitProject(): NpmProjects { + visitTasksRequiredDependencies() + + val kotlin = project.kotlinExtensionOrNull + + if (kotlin != null) { + when (kotlin) { + is KotlinSingleTargetExtension -> visitTarget(kotlin.target) + is KotlinMultiplatformExtension -> kotlin.targets.forEach { + visitTarget(it) + } + } + } + + return NpmProjects( + project, + npmProjects, + byCompilation, + byNpmDependency, + taskRequirements + ) + } + + private fun visitTasksRequiredDependencies() { + project.tasks.toList().forEach { task -> + if (task.enabled && task is RequiresNpmDependencies) { + addTaskRequirements(task) + } + } + } + + private fun visitTarget(target: KotlinTarget) { + if (target.platformType == KotlinPlatformType.js) { + target.compilations.toList().forEach { compilation -> + compilation as KotlinJsCompilation + visitCompilation(compilation) + } + } + } + + private fun visitCompilation(compilation: KotlinJsCompilation) { + val project = compilation.target.project + val npmProject = compilation.npmProject + val name = npmProject.name + val packageJson = PackageJson( + name, + project.version.toString() + ) + val npmDependencies = mutableSetOf() + val gradleDeps = NpmProjectGradleDeps() + + compilation.allKotlinSourceSets.forEach { sourceSet -> + sourceSet.relatedConfigurationNames.forEach { configurationName -> + val configuration = project.configurations.getByName(configurationName) + visitConfiguration(configuration, npmDependencies, gradleDeps) + } + } + + packageJson.main = npmProject.main + + val requiredByTasks = requiredFromTasksByCompilation[compilation] + if (requiredByTasks != null && requiredByTasks.isNotEmpty()) { + val configuration = project.configurations.create("$name-jsTools") + requiredByTasks.forEach { + configuration.dependencies.add(it) + } + configuration.resolve() + visitConfiguration(configuration, npmDependencies, gradleDeps) + } + + npmDependencies.forEach { + packageJson.dependencies[it.key] = resolver.chooseVersion(packageJson.dependencies[it.key], it.version) + } + + gradleDeps.externalModules.forEach { + val relativePath = it.path.relativeTo(npmProject.dir) + packageJson.dependencies[it.name] = "file:$relativePath" + } + + gradleDeps.internalModules.forEach { target -> + val resolvedTarget = resolver.findDependentResolvedNpmProject(project, target) + if (resolvedTarget != null) { + packageJson.dependencies[resolvedTarget.packageJson.name] = resolvedTarget.packageJson.version + } + } + + project.nodeJs.packageJsonHandlers.forEach { + it(packageJson) + } + + val npmPackage = NpmProjectPackage(project, npmProject, npmDependencies, packageJson) + npmPackage.packageJson.saveTo(npmProject.packageJsonFile) + + resolver.packageManager.resolveProject(npmPackage) + + addNpmProject(npmPackage) + } + + private fun visitConfiguration( + configuration: Configuration, + npmDependencies: MutableSet, + gradleDeps: NpmProjectGradleDeps + ) { + resolver.gradleNodeModules.collectDependenciesFromConfiguration(configuration, gradleDeps) + + configuration.allDependencies.forEach { dependency -> + when (dependency) { + is NpmDependency -> npmDependencies.add(dependency) + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjects.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjects.kt new file mode 100644 index 00000000000..8ca4c5e8f41 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjects.kt @@ -0,0 +1,23 @@ +/* + * 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 +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency + +/** + * Info about NPM projects inside particular gradle [project]. + */ +class NpmProjects( + val project: Project, + + val npmProjects: List, + val npmProjectsByCompilation: Map, + val npmProjectsByNpmDependency: Map, + + val taskRequirements: Map> +) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolveTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolveTask.kt index a3fee4850da..e7907f689ef 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolveTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolveTask.kt @@ -11,7 +11,7 @@ import org.gradle.api.tasks.TaskAction open class NpmResolveTask : AbstractTask() { @TaskAction fun resolve() { - NpmResolver.resolve(project) + NpmResolver.resolveIfNeeded(project) } companion object { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt index bbab0f6d9be..3cf5934c203 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt @@ -5,29 +5,14 @@ package org.jetbrains.kotlin.gradle.targets.js.npm -import com.google.gson.Gson -import com.google.gson.GsonBuilder import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.Dependency -import org.gradle.api.file.CopySpec -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension -import org.jetbrains.kotlin.gradle.dsl.kotlinExtensionOrNull -import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated -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.internal.RewriteSourceMapFilterReader +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 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.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver.ResolutionCallResult.* -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile /** - * Generates `package.json` file for projects with npm or js dependencies and + * 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`. @@ -36,44 +21,45 @@ import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile */ internal class NpmResolver private constructor(val rootProject: Project) : AutoCloseable { companion object { - fun resolve(project: Project): ResolutionCallResult { + fun resolveIfNeeded(project: Project): ResolutionCallResult { val rootProject = project.rootProject val process = ProjectData[rootProject] - if (process != null && process.resolved == null) return AlreadyInProgress + if (process != null && process.visit == null) return AlreadyInProgress - val resolved = process?.resolved + val resolved = process?.visit - return if (resolved != null) AlreadyResolved(ProjectData[project]!!.resolved!!) + return if (resolved != null) AlreadyResolved(resolved) else { - val resolver = NpmResolver(rootProject) - resolver.resolve(rootProject)!! - resolver.close() - ResolvedNow(ProjectData[project]!!.resolved!!) + run(rootProject) + ResolvedNow(ProjectData[project]!!.visit!!) } } + private fun run(rootProject: Project) { + NpmResolver(rootProject).resolve() + } + fun getAlreadyResolvedOrNull(project: Project): ResolutionCallResult? { val rootProject = project.rootProject val process = ProjectData[rootProject] - if (process != null && process.resolved == null) return AlreadyInProgress - val resolved = process?.resolved - if (resolved != null) return AlreadyResolved(ProjectData[project]!!.resolved!!) + if (process != null && process.visit == null) return AlreadyInProgress + val resolved = process?.visit + if (resolved != null) return AlreadyResolved(ProjectData[project]!!.visit!!) return null } - fun requireResolved(project: Project, reason: String = ""): ResolvedProject = - ProjectData[project.rootProject]?.resolved - ?: error("NPM dependencies should be resolved$reason") + fun requireResolved(project: Project, reason: String = ""): NpmProjects = + ProjectData[project]?.visit ?: error("NPM dependencies should be resolved $reason") fun checkRequiredDependencies(project: Project, target: RequiresNpmDependencies) { - val required = requireResolved(project, "before $target execution").taskDependencies - val targetRequired = required[target]?.toSet() ?: setOf() + 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 after npm dependencies was resolved. " + + "$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." } } @@ -82,21 +68,21 @@ internal class NpmResolver private constructor(val rootProject: Project) : AutoC sealed class ResolutionCallResult { object AlreadyInProgress : ResolutionCallResult() - class AlreadyResolved(val resolution: ResolvedProject) : ResolutionCallResult() - class ResolvedNow(val resolution: ResolvedProject) : ResolutionCallResult() + class AlreadyResolved(val resolution: NpmProjects) : ResolutionCallResult() + class ResolvedNow(val resolution: NpmProjects) : ResolutionCallResult() } - private val nodeJs = NodeJsPlugin.apply(rootProject).root - private val npmProject = rootProject.npmProject - private val gradleNodeModules = GradleNodeModulesCache(rootProject) - private val packageManager = nodeJs.packageManager - private val npmPackages = mutableListOf() - private val requiredByTasks = mutableMapOf>() - private val gson = GsonBuilder() - .setPrettyPrinting() - .create() + val gradleNodeModules = GradleNodeModulesCache(rootProject) + val nodeJs = NodeJsPlugin.apply(rootProject).root + val packageManager by lazy { + val packageManager = nodeJs.packageManager + packageManager.setup(rootProject) + packageManager + } - class ProjectData(var resolved: ResolvedProject? = null) { + private val allNpmPackages = mutableListOf() + + class ProjectData(var visit: NpmProjects? = null) { companion object { private const val KEY = "npmResolverData" operator fun get(project: Project) = project.extensions.findByName(KEY) as ProjectData? @@ -104,39 +90,31 @@ internal class NpmResolver private constructor(val rootProject: Project) : AutoC } } - class ResolvedProject( - val npmPackage: NpmPackage?, - val taskDependencies: Map> - ) + fun resolve() { + resolve(rootProject) + if (allNpmPackages.isNotEmpty()) { + removeOutdatedPackages() + packageManager.resolveRootProject(rootProject, allNpmPackages) + } - class NpmPackage( - val project: Project, - val packageJson: PackageJson, - val npmDependencies: Set - ) { - fun savePackageJson(gson: Gson) { - val packageJsonFile = NpmProject[project].packageJsonFile - packageJsonFile.ensureParentDirsCreated() - packageJsonFile.writer().use { - gson.toJson(packageJson, it) + close() + } + + private fun removeOutdatedPackages() { + val packages = allNpmPackages.mapTo(mutableSetOf()) { it.npmProject.name } + nodeJs.projectPackagesDir.listFiles()?.forEach { + if (it.name !in packages) { + it.deleteRecursively() } } } - private var packageManagerInstalled = false - - private fun requirePackageManagerSetup() { - if (packageManagerInstalled) return - packageManager.setup(rootProject) - packageManagerInstalled = true + private fun getOrResolve(project: Project): NpmProjects { + return ProjectData[project]?.visit ?: resolve(project) } - private fun getOrResolve(project: Project): ResolvedProject { - return (ProjectData[project] ?: resolve(project)!!).resolved!! - } - - private fun resolve(project: Project): ProjectData? { - val result = ProjectData().also { + private fun resolve(project: Project): NpmProjects { + val data = ProjectData().also { ProjectData[project] = it } @@ -144,195 +122,33 @@ internal class NpmResolver private constructor(val rootProject: Project) : AutoC getOrResolve(it) } - val npmPackage = extractNpmPackage(project) - if (npmPackage != null) { - npmPackage.savePackageJson(gson) + val visited = NpmProjectVisitor(this, project).visitProject() + data.visit = visited + allNpmPackages.addAll(visited.npmProjects) - requirePackageManagerSetup() - packageManager.resolveProject(npmPackage) - - npmPackages.add(npmPackage) - } - - result.resolved = ResolvedProject(npmPackage, requiredByTasks) - - if (project == rootProject) { - if (npmPackages.isNotEmpty()) { - requirePackageManagerSetup() - packageManager.resolveRootProject(rootProject, npmPackages) - } - } - - return result + return visited } - private fun extractNpmPackage(project: Project): NpmPackage? { - val packageJson = PackageJson(project.name, project.version.toString()) - val npmDependencies = mutableSetOf() - val gradleDeps = NpmProjectGradleDeps() + fun findDependentResolvedNpmProject(src: Project, target: Project): NpmProjectPackage? { + // todo: proper finding using KotlinTargetComponent.findUsageContext + val resolvedTarget = getOrResolve(target) + val mainCompilations = resolvedTarget.npmProjectsByCompilation.entries.filter { it.key.name == KotlinCompilation.MAIN_COMPILATION_NAME } - visitTasksRequiredDependencies(project, npmDependencies, gradleDeps) - - collectDependencies(project, npmDependencies, gradleDeps, packageJson) - - gradleDeps.externalModules.forEach { - val relativePath = it.path.relativeTo(NpmProject[project].nodeWorkDir) - packageJson.dependencies[it.name] = "file:$relativePath" - } - - gradleDeps.internalModules.forEach { - val npmPackage = getOrResolve(it).npmPackage - if (npmPackage != null) { - packageJson.dependencies[npmPackage.packageJson.name] = npmPackage.packageJson.version - } - } - - project.nodeJs.packageJsonHandlers.forEach { - it(packageJson) - } - - val packageJsonRequired = - if (project == rootProject) packageManager.hookRootPackage(rootProject, packageJson, npmPackages) - else false - - return if (!packageJsonRequired && packageJson.empty) null - else NpmPackage(project, packageJson, npmDependencies) - } - - private fun visitTasksRequiredDependencies( - project: Project, - npmDependencies: MutableSet, - gradleDeps: NpmProjectGradleDeps - ) { - val requiredDependencies = mutableListOf() - - project.tasks.toList().forEach { task -> - if (task.enabled && task is RequiresNpmDependencies) { - val list = task.requiredNpmDependencies.toList() - - requiredByTasks[task] = list - list.forEach { requiredDependency -> - requiredDependencies.add(requiredDependency.createDependency(project)) - } - } - } - - if (requiredDependencies.isNotEmpty()) { - val configuration = project.configurations.create("jsTools") - requiredDependencies.forEach { - configuration.dependencies.add(it) - } - configuration.resolve() - visitConfiguration(configuration, npmDependencies, gradleDeps) - } - } - - private fun collectDependencies( - project: Project, - npmDependencies: MutableSet, - gradleDeps: NpmProjectGradleDeps, - packageJson: PackageJson - ) { - val kotlin = project.kotlinExtensionOrNull - - if (kotlin != null) { - when (kotlin) { - is KotlinSingleTargetExtension -> visitTarget(kotlin.target, project, npmDependencies, gradleDeps, packageJson) - is KotlinMultiplatformExtension -> kotlin.targets.forEach { - visitTarget(it, project, npmDependencies, gradleDeps, packageJson) - } - } - - npmDependencies.forEach { - packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version) - } - } - } - - private fun chooseVersion(oldVersion: String?, newVersion: String): String = - oldVersion ?: newVersion // todo: real versions conflict resolution - - private fun visitTarget( - target: KotlinTarget, - project: Project, - npmDependencies: MutableSet, - gradleDeps: NpmProjectGradleDeps, - packageJson: PackageJson - ) { - if (target.platformType == KotlinPlatformType.js) { - target.compilations.toList().forEach { compilation -> - compilation.relatedConfigurationNames.forEach { - val configuration = project.configurations.getByName(it) - visitConfiguration(configuration, npmDependencies, gradleDeps) - } - - if (compilation is KotlinJsCompilation) { - visitKotlinJsCompilation(project, compilation) - - if (compilation.name == "main") { - packageJson.main = compilation.compileKotlinTask.outputFile.name - } - } - } - } - } - - private fun visitConfiguration( - configuration: Configuration, - npmDependencies: MutableSet, - gradleDeps: NpmProjectGradleDeps - ) { - gradleNodeModules.collectDependenciesFromConfiguration(configuration, gradleDeps) - - configuration.allDependencies.forEach { dependency -> - when (dependency) { - is NpmDependency -> npmDependencies.add(dependency) - } - } - } - - private fun visitKotlinJsCompilation( - project: Project, - compilation: KotlinJsCompilation - ) { - val npmProject = project.npmProject - val kotlin2JsCompile = compilation.compileKotlinTask - if (npmProject.compileOutputCopyDest != null) { - project.whenEvaluated { - if (kotlin2JsCompile.outputFile.exists()) { - visitCompile(project.npmProject, kotlin2JsCompile) - } - } - - kotlin2JsCompile.doLast { - visitCompile(project.npmProject, kotlin2JsCompile) - } - } - } - - private fun visitCompile(npmProject: NpmProject, kotlin2JsCompile: Kotlin2JsCompile) { - npmProject.project.copy { copy -> - copy.from(kotlin2JsCompile.outputFile) - copy.from(kotlin2JsCompile.outputFile.path + ".map") - copy.into(npmProject.compileOutputCopyDest!!) - copy.withSourceMapRewriter(npmProject) - } - } - - private fun CopySpec.withSourceMapRewriter(npmProject: NpmProject) { - eachFile { - if (it.name.endsWith(".js.map")) { - it.filter( - mapOf( - "srcSourceRoot" to it.file.parentFile, - "targetSourceRoot" to npmProject.compileOutputCopyDest!! - ), - RewriteSourceMapFilterReader::class.java + 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 } + fun chooseVersion(oldVersion: String?, newVersion: String): String = + oldVersion ?: newVersion // todo: real versions conflict resolution + override fun close() { gradleNodeModules.close() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt index 5aba936ccd6..3f5750b0b42 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt @@ -5,6 +5,10 @@ package org.jetbrains.kotlin.gradle.targets.js.npm +import com.google.gson.GsonBuilder +import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated +import java.io.File + class PackageJson( var name: String, var version: String @@ -16,10 +20,40 @@ class PackageJson( dependencies.isEmpty() && devDependencies.isEmpty() + val scopedName: ScopedName + get() = scopedName(name) + var private: Boolean? = null + var main: String? = null + var workspaces: Collection? = null val devDependencies = mutableMapOf() val dependencies = mutableMapOf() + + companion object { + fun scopedName(name: String): ScopedName = if (name.contains("/")) ScopedName( + scope = name.substringBeforeLast("/").removePrefix("@"), + name = name.substringAfterLast("/") + ) else ScopedName(scope = null, name = name) + + operator fun invoke(scope: String, name: String, version: String) = + PackageJson(ScopedName(scope, name).toString(), version) + } + + data class ScopedName(val scope: String?, val name: String) { + override fun toString() = if (scope == null) name else "@$scope/$name" + } + + fun saveTo(packageJsonFile: File) { + val gson = GsonBuilder() + .setPrettyPrinting() + .create() + + packageJsonFile.ensureParentDirsCreated() + packageJsonFile.writer().use { + gson.toJson(this, it) + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/RequiresNpmDependencies.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/RequiresNpmDependencies.kt index a9ddff2e0b5..7f487e663aa 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/RequiresNpmDependencies.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/RequiresNpmDependencies.kt @@ -5,6 +5,10 @@ package org.jetbrains.kotlin.gradle.targets.js.npm +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency + interface RequiresNpmDependencies { + val compilation: KotlinJsCompilation val requiredNpmDependencies: Collection } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinBrowserJs.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinBrowserJs.kt index 3e7da383bdc..a09edfd3bb5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinBrowserJs.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinBrowserJs.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBrowserDsl 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.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfigWriter @@ -20,7 +19,6 @@ class KotlinBrowserJs(target: KotlinJsTarget) : KotlinJsSubTarget(target, "browser"), KotlinJsBrowserDsl { - private val versions = project.nodeJs.versions private val webpackTaskName = disambiguateCamelCased("webpack") override fun configureDefaultTestFramework(it: KotlinJsTest) { @@ -39,13 +37,12 @@ class KotlinBrowserJs(target: KotlinJsTarget) : override fun configureRun(compilation: KotlinJsCompilation) { val project = compilation.target.project - val npmProject = project.npmProject project.createOrRegisterTask(disambiguateCamelCased("webpack")) { val compileKotlinTask = compilation.compileKotlinTask it.dependsOn(target.project.nodeJs.root.npmResolveTask, compileKotlinTask) - it.entry = npmProject.compileOutput(compileKotlinTask) + it.compilation = compilation project.tasks.getByName(LifecycleBasePlugin.ASSEMBLE_TASK_NAME).dependsOn(it) } @@ -59,7 +56,7 @@ class KotlinBrowserJs(target: KotlinJsTarget) : ) it.bin = "webpack-dev-server" - it.entry = npmProject.compileOutput(compileKotlinTask) + it.compilation = compilation it.devServer = KotlinWebpackConfigWriter.DevServer( open = true, diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinJsSubTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinJsSubTarget.kt index 7fb29b32a12..10076a21f34 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinJsSubTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinJsSubTarget.kt @@ -13,6 +13,7 @@ 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.NodeJsPlugin 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.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask import org.jetbrains.kotlin.gradle.testing.internal.configureConventions @@ -32,6 +33,11 @@ abstract class KotlinJsSubTarget( fun configure() { configureTests() configureRun() + + target.compilations.all { + val npmProject = it.npmProject + it.compileKotlinTask.kotlinOptions.outputFile = npmProject.dir.resolve(npmProject.main).canonicalPath + } } private fun disambiguate(name: String): MutableList { @@ -72,8 +78,8 @@ abstract class KotlinJsSubTarget( compileTask.outputFile.exists() } + testJs.compilation = compilation testJs.targetName = target.disambiguationClassifier + "," + disambiguationClassifier - testJs.nodeModulesToLoad.add(compileTask.outputFile.nameWithoutExtension) testJs.configureConventions() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinNodeJs.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinNodeJs.kt index 5205f1dd7b7..08e5869ac8c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinNodeJs.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/subtargets/KotlinNodeJs.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.targets.js.subtargets -import org.gradle.api.Project import org.jetbrains.kotlin.gradle.plugin.TaskHolder import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget @@ -35,19 +34,18 @@ class KotlinNodeJs(target: KotlinJsTarget) : val compileKotlinTask = compilation.compileKotlinTask runTask.dependsOn(target.project.nodeJs.root.npmResolveTask, compileKotlinTask) - val npmProject = project.npmProject - runTask.args(npmProject.compileOutput(compileKotlinTask)) + val npmProject = compilation.npmProject + runTask.args(compileKotlinTask.outputFile) } - addSourceMapSupport(compilation, runTaskHolder, project) + addSourceMapSupport(compilation, runTaskHolder) target.runTask.dependsOn(runTaskHolder.getTaskOrProvider()) } private fun addSourceMapSupport( compilation: KotlinJsCompilation, - runTaskHolder: TaskHolder, - project: Project + runTaskHolder: TaskHolder ) { compilation.dependencies { runtimeOnly(kotlin("test-nodejs-runner")) @@ -57,7 +55,7 @@ class KotlinNodeJs(target: KotlinJsTarget) : runTaskHolder.configure { runTask -> runTask.args( "--require", - project.npmProject.require("kotlin-test-nodejs-runner/kotlin-nodejs-source-map-support") + compilation.npmProject.require("kotlin-test-nodejs-runner/kotlin-nodejs-source-map-support") ) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt index 4a27597dfbd..79b6868f370 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt @@ -12,9 +12,12 @@ import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.process.internal.DefaultProcessForkOptions import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin -import org.jetbrains.kotlin.gradle.targets.js.npm.* import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver +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.targets.js.testing.karma.KotlinKarma import org.jetbrains.kotlin.gradle.targets.js.testing.mocha.KotlinMocha import org.jetbrains.kotlin.gradle.targets.js.testing.nodejs.KotlinNodeJsTestRunner @@ -31,27 +34,36 @@ open class KotlinJsTest : KotlinTest(), RequiresNpmDependencies { @Input var debug: Boolean = false - @Input - var nodeModulesToLoad: MutableList = mutableListOf() + @Internal + override lateinit var compilation: KotlinJsCompilation + + val compilationId: String + @Input get() = compilation.let { + val target = it.target + target.project.path + "@" + target.name + ":" + it.compilationName + } + + val nodeModulesToLoad: List + @Internal get() = listOf("./" + compilation.npmProject.main) override val requiredNpmDependencies: Collection get() = testFramework!!.requiredNpmDependencies - fun useNodeJs(body: KotlinNodeJsTestRunner.() -> Unit) = use(KotlinNodeJsTestRunner(project), body) + fun useNodeJs(body: KotlinNodeJsTestRunner.() -> Unit) = use(KotlinNodeJsTestRunner(compilation), body) fun useNodeJs(fn: Closure<*>) { useNodeJs { ConfigureUtil.configure(fn, this) } } - fun useMocha(body: KotlinMocha.() -> Unit) = use(KotlinMocha(project), body) + fun useMocha(body: KotlinMocha.() -> Unit) = use(KotlinMocha(compilation), body) fun useMocha(fn: Closure<*>) { useMocha { ConfigureUtil.configure(fn, this) } } - fun useKarma(body: KotlinKarma.() -> Unit) = use(KotlinKarma(project), body) + fun useKarma(body: KotlinKarma.() -> Unit) = use(KotlinKarma(compilation), body) fun useKarma(fn: Closure<*>) { useKarma { ConfigureUtil.configure(fn, this) @@ -70,17 +82,13 @@ open class KotlinJsTest : KotlinTest(), RequiresNpmDependencies { } override fun executeTests() { - NpmResolver.resolve(project) NpmResolver.checkRequiredDependencies(project, this) super.executeTests() } override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec { val forkOptions = DefaultProcessForkOptions(fileResolver) - - NpmResolver.resolve(project) - - forkOptions.workingDir = NpmProject[project].nodeWorkDir + forkOptions.workingDir = compilation.npmProject.dir forkOptions.executable = NodeJsPlugin.apply(project).root.environment.nodeExecutable val nodeJsArgs = mutableListOf() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt index 4332073b513..0542c4a9328 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt @@ -15,11 +15,12 @@ import org.jetbrains.kotlin.gradle.internal.operation import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClient import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.appendConfigsFromDir import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmPackageVersion -import org.jetbrains.kotlin.gradle.targets.js.npm.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.targets.js.NpmPackageVersion +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency 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 @@ -29,7 +30,10 @@ import org.jetbrains.kotlin.gradle.testing.internal.reportsDir import org.slf4j.Logger import java.io.File -class KotlinKarma(val project: Project) : KotlinJsTestFramework { +class KotlinKarma(override val compilation: KotlinJsCompilation) : KotlinJsTestFramework { + val project: Project + get() = compilation.target.project + private val config: KarmaConfig = KarmaConfig() private val requiredDependencies = mutableSetOf() @@ -183,7 +187,7 @@ class KotlinKarma(val project: Project) : KotlinJsTestFramework { ignoreOutOfRootNodes = true ) - val npmProject = task.project.npmProject + val npmProject = compilation.npmProject val files = task.nodeModulesToLoad.map { npmProject.require(it) } config.files.addAll(files) @@ -194,7 +198,7 @@ class KotlinKarma(val project: Project) : KotlinJsTestFramework { it(task) } - val karmaConfJs = npmProject.nodeWorkDir.resolve("karma.conf.js") + val karmaConfJs = npmProject.dir.resolve("karma.conf.js") karmaConfJs.printWriter().use { confWriter -> confWriter.println("module.exports = function(config) {") confWriter.println() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/mocha/KotlinMocha.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/mocha/KotlinMocha.kt index 1ff0c25ad97..327bce629dd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/mocha/KotlinMocha.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/mocha/KotlinMocha.kt @@ -9,15 +9,19 @@ import org.gradle.api.Project import org.gradle.process.ProcessForkOptions import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs -import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinGradleNpmPackage -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject -import org.jetbrains.kotlin.gradle.targets.js.npm.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.targets.js.KotlinGradleNpmPackage +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency +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 -class KotlinMocha(val project: Project) : KotlinJsTestFramework { +class KotlinMocha(override val compilation: KotlinJsCompilation) : KotlinJsTestFramework { + val project: Project + get() = compilation.target.project + private val versions = project.nodeJs.versions override val settingsState: String @@ -48,11 +52,11 @@ class KotlinMocha(val project: Project) : KotlinJsTestFramework { task.nodeModulesToLoad.single() ) - val npmProjectLayout = NpmProject[task.project] + val npmProject = compilation.npmProject val args = nodeJsArgs + nodeModules.map { - npmProjectLayout.nodeModulesDir.resolve(it).also { file -> + npmProject.nodeModulesDir.resolve(it).also { file -> check(file.isFile) { "Cannot find ${file.canonicalPath}" } }.canonicalPath } + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt index e9a900423e4..90df7619591 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt @@ -6,21 +6,22 @@ package org.jetbrains.kotlin.gradle.targets.js.testing.nodejs import org.gradle.api.Project -import org.gradle.api.tasks.Input import org.gradle.process.ProcessForkOptions import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.internal.parseNodeJsStackTraceAsJvm -import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinGradleNpmPackage -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject -import org.jetbrains.kotlin.gradle.targets.js.npm.RequiredKotlinJsDependency +import org.jetbrains.kotlin.gradle.targets.js.KotlinGradleNpmPackage +import org.jetbrains.kotlin.gradle.targets.js.RequiredKotlinJsDependency import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTestFramework import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.testing.IgnoredTestSuites -class KotlinNodeJsTestRunner(val project: Project) : KotlinJsTestFramework { - @Input +class KotlinNodeJsTestRunner(override val compilation: KotlinJsCompilation) : KotlinJsTestFramework { + val project: Project + get() = compilation.target.project + var ignoredTestSuites: IgnoredTestSuites = IgnoredTestSuites.showWithContents override val settingsState: String @@ -34,7 +35,7 @@ class KotlinNodeJsTestRunner(val project: Project) : KotlinJsTestFramework { forkOptions: ProcessForkOptions, nodeJsArgs: MutableList ): TCServiceMessagesTestExecutionSpec { - val npmProject = project.npmProject + val npmProject = compilation.npmProject val cliArgs = KotlinNodeJsTestRunnerCliArgs( task.nodeModulesToLoad.map { npmProject.require(it) }, @@ -55,11 +56,9 @@ class KotlinNodeJsTestRunner(val project: Project) : KotlinJsTestFramework { "kotlin-test-nodejs-runner/kotlin-nodejs-source-map-support.js" ) - val npmProjectLayout = NpmProject[task.project] - val args = nodeJsArgs + testRuntimeNodeModules.map { - npmProjectLayout.require(it) + npmProject.require(it) } + cliArgs.toList() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt index 97653bba503..0da80aef05f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt @@ -13,8 +13,9 @@ import org.gradle.deployment.internal.DeploymentHandle import org.gradle.deployment.internal.DeploymentRegistry import org.gradle.process.internal.ExecHandle import org.gradle.process.internal.ExecHandleFactory +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmPackageVersion +import org.jetbrains.kotlin.gradle.targets.js.NpmPackageVersion import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject @@ -34,12 +35,20 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies { open val execHandleFactory: ExecHandleFactory get() = injected - @Input - @SkipWhenEmpty - open lateinit var entry: File + @Internal + override lateinit var compilation: KotlinJsCompilation + + val compilationId: String + @Input get() = compilation.let { + val target = it.target + target.project.path + "@" + target.name + ":" + it.compilationName + } + + val entry: File + get() = compilation.compileKotlinTask.outputFile open val configFile: File - @OutputFile get() = project.npmProject.nodeWorkDir.resolve("webpack.config.js") + @OutputFile get() = compilation.npmProject.dir.resolve("webpack.config.js") @Input var saveEvaluatedConfigFile: Boolean = true @@ -89,7 +98,7 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies { } private fun createRunner() = KotlinWebpackRunner( - project, + compilation!!.npmProject, configFile, execHandleFactory, bin, @@ -107,7 +116,6 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies { @TaskAction fun execute() { - NpmResolver.resolve(project) NpmResolver.checkRequiredDependencies(project, this) val runner = createRunner() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt index 9a8d036c0bb..a3141fa6e56 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt @@ -12,7 +12,6 @@ package org.jetbrains.kotlin.gradle.targets.js.webpack -import org.gradle.api.Project import org.gradle.process.ExecResult import org.gradle.process.internal.ExecHandle import org.gradle.process.internal.ExecHandleFactory @@ -21,7 +20,7 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver import java.io.File internal class KotlinWebpackRunner( - val project: Project, + val npmProject: NpmProject, val configFile: File, val execHandleFactory: ExecHandleFactory, val bin: String, @@ -41,10 +40,6 @@ internal class KotlinWebpackRunner( "${this}: Entry file not existed \"${configWriter.entry}\"" } - NpmResolver.resolve(project) - - val npmProjectLayout = NpmProject[project] - configWriter.save(configFile) val execFactory = execHandleFactory.newExec() @@ -54,7 +49,7 @@ internal class KotlinWebpackRunner( args.add("--progress") } - npmProjectLayout.useTool(execFactory, ".bin/$bin", *args.toTypedArray()) + npmProject.useTool(execFactory, ".bin/$bin", *args.toTypedArray()) val exec = execFactory.build() exec.start() return exec diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt index d101e155c9a..6fe4b8c3b4e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.Project import org.jetbrains.kotlin.gradle.internal.execWithProgress import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin +import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs import org.jetbrains.kotlin.gradle.targets.js.npm.* import java.io.File @@ -16,24 +17,22 @@ object Yarn : NpmApi { YarnPlugin.apply(project).executeSetup() } - val Project.packageJsonHashFile: File - get() = buildDir.resolve("package.json.hash") + val NpmProject.packageJsonHashFile: File + get() = dir.resolve("package.json.hash") fun yarnExec( project: Project, + dir: File, description: String, - vararg args: String, - npmProject: NpmProject = NpmProject[project] + vararg args: String ) { val nodeJsEnv = NodeJsPlugin.apply(project).root.environment val yarnEnv = YarnPlugin.apply(project).environment - val nodeWorkDir = npmProject.nodeWorkDir - project.execWithProgress(description) { exec -> exec.executable = nodeJsEnv.nodeExecutable exec.args = listOf(yarnEnv.home.resolve("bin/yarn.js").absolutePath) + args - exec.workingDir = nodeWorkDir + exec.workingDir = dir } } @@ -79,70 +78,62 @@ object Yarn : NpmApi { } } - @Suppress("EXPOSED_PARAMETER_TYPE") - override fun resolveProject(npmPackage: NpmResolver.NpmPackage) { - val project = npmPackage.project + override fun resolveProject(resolvedNpmProject: NpmProjectPackage) { + val project = resolvedNpmProject.project if (!project.yarn.useWorkspaces) { - YarnAvoidance(project).updateIfNeeded { - yarnExec(project, NpmApi.resolveOperationDescription("yarn for ${project.path}")) - yarnLockReadTransitiveDependencies(NpmProject[project].nodeWorkDir, npmPackage.npmDependencies) + YarnAvoidance(resolvedNpmProject.npmProject).updateIfNeeded { + yarnExec(project, resolvedNpmProject.npmProject.dir, NpmApi.resolveOperationDescription("yarn for ${project.path}")) + yarnLockReadTransitiveDependencies(resolvedNpmProject.npmProject.dir, resolvedNpmProject.npmDependencies) } } } - @Suppress("EXPOSED_PARAMETER_TYPE") - override fun hookRootPackage( - rootProject: Project, - rootPackageJson: PackageJson, - allWorkspaces: Collection - ): Boolean { - if (rootProject.yarn.useWorkspaces) { - rootPackageJson.private = true - rootPackageJson.workspaces = allWorkspaces - .filter { it.project != rootProject } - .map { it.project.npmProject.nodeWorkDir.relativeTo(rootProject.npmProject.nodeWorkDir).path } - - return true - } - - return false - } - -// override fun shouldHoistGradleNodeModules(project: Project): Boolean = -// project.rootProject.yarn.useWorkspaces - - @Suppress("EXPOSED_PARAMETER_TYPE") - override fun resolveRootProject( - rootProject: Project, - subprojects: MutableList - ) { + override fun resolveRootProject(rootProject: Project, subProjects: MutableList) { check(rootProject == rootProject.rootProject) if (rootProject.yarn.useWorkspaces) { - resolveWorkspaces(rootProject, subprojects) + resolveWorkspaces(rootProject, subProjects) } } private fun resolveWorkspaces( rootProject: Project, - subprojects: MutableList + npmProjects: MutableList ) { - val upToDateChecks = subprojects.map { - YarnAvoidance(it.project) + val upToDateChecks = npmProjects.map { + YarnAvoidance(it.npmProject) } if (upToDateChecks.all { it.upToDate }) return - yarnExec(rootProject, NpmApi.resolveOperationDescription("yarn")) - yarnLockReadTransitiveDependencies(NpmProject[rootProject].nodeWorkDir, subprojects.flatMap { it.npmDependencies }) + val nodeJsWorldDir = rootProject.nodeJs.root.rootPackageDir + + saveRootProjectWorkspacesPackageJson(rootProject, npmProjects, nodeJsWorldDir) + + yarnExec(rootProject, nodeJsWorldDir, NpmApi.resolveOperationDescription("yarn")) + yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.npmDependencies }) upToDateChecks.forEach { it.commit() } } - override fun cleanProject(project: Project) { - super.cleanProject(project) - project.packageJsonHashFile.delete() + private fun saveRootProjectWorkspacesPackageJson( + rootProject: Project, + npmProjects: MutableList, + nodeJsWorldDir: File + ) { + val rootPackageJson = PackageJson(rootProject.name, rootProject.version.toString()) + rootPackageJson.private = true + rootPackageJson.workspaces = npmProjects + .map { it.npmProject.dir.relativeTo(nodeJsWorldDir).path } + + rootProject.nodeJs.packageJsonHandlers.forEach { + it(rootPackageJson) + } + + rootPackageJson.saveTo( + nodeJsWorldDir.resolve(NpmProject.PACKAGE_JSON) + ) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnAvoidance.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnAvoidance.kt index 4d0ef4819e8..42e41a20bc3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnAvoidance.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnAvoidance.kt @@ -10,16 +10,21 @@ import org.gradle.api.internal.project.ProjectInternal import org.gradle.internal.hash.FileHasher import org.jetbrains.kotlin.daemon.common.toHexString import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation +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.yarn.Yarn.packageJsonHashFile +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile + +class YarnAvoidance(val npmProject: NpmProject) { + val project: Project + get() = npmProject.project -class YarnAvoidance(val project: Project) { private val prevHash by lazy { - val packageJsonHashFile = project.packageJsonHashFile + val packageJsonHashFile = npmProject.packageJsonHashFile if (packageJsonHashFile.exists()) packageJsonHashFile.readText() else null } - private val npmProject = project.npmProject private val packageJsonFile = npmProject.packageJsonFile private val hash by lazy { @@ -34,7 +39,7 @@ class YarnAvoidance(val project: Project) { fun commit() { if (!upToDate) { - val packageJsonHashFile = project.packageJsonHashFile + val packageJsonHashFile = npmProject.packageJsonHashFile packageJsonHashFile.ensureParentDirsCreated() packageJsonHashFile.writeText(hash) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt index 0f082dc5ee8..216e8e32b46 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt @@ -26,7 +26,7 @@ open class YarnRootExtension(val project: Project) { var disableWorkspaces: Boolean = false val useWorkspaces: Boolean - get() = !disableWorkspaces && project.nodeJs.root.layout.allowYarnWorkspaces + get() = !disableWorkspaces internal fun executeSetup() { NodeJsPlugin.apply(project).root.executeSetup()