Gradle, js: create npm packages per compilation
#KT-30530
This commit is contained in:
+9
-5
@@ -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")
|
||||
}
|
||||
+2
-2
@@ -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<PackageJson.() -> Unit>()
|
||||
|
||||
val versions = NpmPackages()
|
||||
val versions = NpmVersions()
|
||||
|
||||
@Suppress("unused")
|
||||
fun packageJson(handler: PackageJson.() -> Unit) {
|
||||
|
||||
+4
-15
@@ -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<Project> {
|
||||
override fun apply(project: Project): Unit = project.run {
|
||||
@@ -33,25 +32,15 @@ open class NodeJsPlugin : Plugin<Project> {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -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
|
||||
|
||||
+3
@@ -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)
|
||||
+3
@@ -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,
|
||||
|
||||
+5
-1
@@ -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<ResolvedDependency>()
|
||||
|
||||
|
||||
+5
-22
@@ -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<NpmResolver.NpmPackage>
|
||||
subProjects: MutableList<NpmProjectPackage>
|
||||
)
|
||||
|
||||
/**
|
||||
* 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<NpmResolver.NpmPackage>
|
||||
): 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"
|
||||
|
||||
+35
-29
@@ -28,12 +28,14 @@ data class NpmDependency(
|
||||
SelfResolvingDependencyInternal,
|
||||
ResolvableDependency,
|
||||
FileCollectionDependency {
|
||||
|
||||
override fun getGroup(): String? = org
|
||||
|
||||
internal val dependencies = mutableSetOf<NpmDependency>()
|
||||
|
||||
override fun resolve(): MutableSet<File> {
|
||||
resolveProject() ?: return mutableSetOf()
|
||||
override fun resolve(transitive: Boolean): MutableSet<File> {
|
||||
val npmPackage = resolveProject() ?: return mutableSetOf()
|
||||
val npmProject = npmPackage.npmProject
|
||||
|
||||
val all = mutableSetOf<File>()
|
||||
val visited = mutableSetOf<NpmDependency>()
|
||||
@@ -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<File> {
|
||||
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<File> = resolve()
|
||||
|
||||
override fun getFiles(): FileCollection = project.files(resolve())
|
||||
override fun getFiles(): FileCollection = project.files(resolve(true))
|
||||
|
||||
override fun getName() = name
|
||||
|
||||
|
||||
+58
-34
@@ -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]
|
||||
}
|
||||
+3
@@ -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<Project>()
|
||||
val externalModules = mutableSetOf<GradleNodeModule>()
|
||||
|
||||
-47
@@ -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
|
||||
}
|
||||
+4
-1
@@ -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.
|
||||
|
||||
+18
@@ -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<NpmDependency>,
|
||||
val packageJson: PackageJson
|
||||
)
|
||||
+164
@@ -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<NpmProjectPackage>()
|
||||
private val byCompilation = mutableMapOf<KotlinJsCompilation, NpmProjectPackage>()
|
||||
private val byNpmDependency = mutableMapOf<NpmDependency, NpmProjectPackage>()
|
||||
private val taskRequirements = mutableMapOf<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>()
|
||||
private val requiredFromTasksByCompilation = mutableMapOf<KotlinJsCompilation, MutableList<Dependency>>()
|
||||
|
||||
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<NpmDependency>()
|
||||
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<NpmDependency>,
|
||||
gradleDeps: NpmProjectGradleDeps
|
||||
) {
|
||||
resolver.gradleNodeModules.collectDependenciesFromConfiguration(configuration, gradleDeps)
|
||||
|
||||
configuration.allDependencies.forEach { dependency ->
|
||||
when (dependency) {
|
||||
is NpmDependency -> npmDependencies.add(dependency)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -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<NpmProjectPackage>,
|
||||
val npmProjectsByCompilation: Map<KotlinJsCompilation, NpmProjectPackage>,
|
||||
val npmProjectsByNpmDependency: Map<NpmDependency, NpmProjectPackage>,
|
||||
|
||||
val taskRequirements: Map<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>
|
||||
)
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+69
-253
@@ -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<NpmPackage>()
|
||||
private val requiredByTasks = mutableMapOf<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>()
|
||||
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<NpmProjectPackage>()
|
||||
|
||||
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<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>
|
||||
)
|
||||
fun resolve() {
|
||||
resolve(rootProject)
|
||||
if (allNpmPackages.isNotEmpty()) {
|
||||
removeOutdatedPackages()
|
||||
packageManager.resolveRootProject(rootProject, allNpmPackages)
|
||||
}
|
||||
|
||||
class NpmPackage(
|
||||
val project: Project,
|
||||
val packageJson: PackageJson,
|
||||
val npmDependencies: Set<NpmDependency>
|
||||
) {
|
||||
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<NpmDependency>()
|
||||
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<NpmDependency>,
|
||||
gradleDeps: NpmProjectGradleDeps
|
||||
) {
|
||||
val requiredDependencies = mutableListOf<Dependency>()
|
||||
|
||||
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<NpmDependency>,
|
||||
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<NpmDependency>,
|
||||
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<NpmDependency>,
|
||||
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()
|
||||
}
|
||||
|
||||
+34
@@ -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<String>? = null
|
||||
|
||||
val devDependencies = mutableMapOf<String, String>()
|
||||
val dependencies = mutableMapOf<String, String>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -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<RequiredKotlinJsDependency>
|
||||
}
|
||||
+2
-5
@@ -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<KotlinWebpack>(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,
|
||||
|
||||
+7
-1
@@ -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<String> {
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
+5
-7
@@ -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<NodeJsExec>,
|
||||
project: Project
|
||||
runTaskHolder: TaskHolder<NodeJsExec>
|
||||
) {
|
||||
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")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -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<String> = 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<String>
|
||||
@Internal get() = listOf("./" + compilation.npmProject.main)
|
||||
|
||||
override val requiredNpmDependencies: Collection<RequiredKotlinJsDependency>
|
||||
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<String>()
|
||||
|
||||
+9
-5
@@ -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<NpmPackageVersion>()
|
||||
|
||||
@@ -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()
|
||||
|
||||
+10
-6
@@ -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
|
||||
} +
|
||||
|
||||
+9
-10
@@ -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<String>
|
||||
): 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()
|
||||
|
||||
|
||||
+15
-7
@@ -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()
|
||||
|
||||
+2
-7
@@ -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
|
||||
|
||||
+39
-48
@@ -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<NpmResolver.NpmPackage>
|
||||
): 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<NpmResolver.NpmPackage>
|
||||
) {
|
||||
override fun resolveRootProject(rootProject: Project, subProjects: MutableList<NpmProjectPackage>) {
|
||||
check(rootProject == rootProject.rootProject)
|
||||
|
||||
if (rootProject.yarn.useWorkspaces) {
|
||||
resolveWorkspaces(rootProject, subprojects)
|
||||
resolveWorkspaces(rootProject, subProjects)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveWorkspaces(
|
||||
rootProject: Project,
|
||||
subprojects: MutableList<NpmResolver.NpmPackage>
|
||||
npmProjects: MutableList<NpmProjectPackage>
|
||||
) {
|
||||
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<NpmProjectPackage>,
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
-4
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user