Gradle: webpack

#KT-31013 Fixed
	#KT-31014 Fixed
This commit is contained in:
Sergey Rostov
2019-04-11 15:46:52 +03:00
parent 5372fcef69
commit 39ce257e64
4 changed files with 128 additions and 49 deletions
@@ -8,9 +8,11 @@ package org.jetbrains.kotlin.gradle.targets.js
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
class KotlinJsTargetConfigurator(kotlinPluginVersion: String) : class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) { KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) {
@@ -25,7 +27,7 @@ class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
platformTarget.compilations.all { platformTarget.compilations.all {
it.compileKotlinTask.kotlinOptions { it.compileKotlinTask.kotlinOptions {
moduleKind = "commonjs" moduleKind = "umd"
sourceMap = true sourceMap = true
sourceMapEmbedSources = null sourceMapEmbedSources = null
} }
@@ -34,7 +36,7 @@ class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
override fun configureTest(target: KotlinOnlyTarget<KotlinJsCompilation>) { override fun configureTest(target: KotlinOnlyTarget<KotlinJsCompilation>) {
val project = target.project val project = target.project
val npmProject = NpmProject[project] val npmProject = NpmProjectLayout[project]
target.compilations.all { compilation -> target.compilations.all { compilation ->
if (isTestCompilation(compilation)) { if (isTestCompilation(compilation)) {
@@ -5,11 +5,21 @@
package org.jetbrains.kotlin.gradle.targets.js.npm package org.jetbrains.kotlin.gradle.targets.js.npm
import com.google.gson.Gson
import com.google.gson.JsonObject
import groovy.transform.TailRecursive
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.process.ExecSpec
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import java.io.File import java.io.File
class NpmProjectLayout(val nodeWorkDir: File) { class NpmProjectLayout(
val project: Project,
val nodeWorkDir: File,
val managed: Boolean
) {
val nodeModulesDir val nodeModulesDir
get() = nodeWorkDir.resolve(NODE_MODULES) get() = nodeWorkDir.resolve(NODE_MODULES)
@@ -18,18 +28,66 @@ class NpmProjectLayout(val nodeWorkDir: File) {
return nodeWorkDir.resolve(PACKAGE_JSON) return nodeWorkDir.resolve(PACKAGE_JSON)
} }
fun useTool(exec: ExecSpec, tool: String, vararg args: String) {
NpmResolver.resolve(project)
exec.workingDir = nodeWorkDir
exec.executable = project.nodeJs.root.environment.nodeExecutable
exec.args = listOf(findModule(tool)) + args
}
val hoistGradleNodeModules: Boolean
get() = project.nodeJs.root.packageManager.getHoistGradleNodeModules(project)
val gradleNodeModulesDir: File
get() =
if (hoistGradleNodeModules) project.rootProject.npmProject.nodeModulesDir
else nodeModulesDir
fun moduleOutput(compilationTask: Kotlin2JsCompile): File {
return gradleNodeModulesDir.resolve(compilationTask.outputFile.name)
}
@TailRecursive
fun findModule(name: String, src: NpmProjectLayout = this): String {
val file = nodeModulesDir.resolve(name)
if (file.isFile) return file.canonicalPath
if (file.isDirectory) {
val packageJsonFile = file.resolve("package.json")
val main: String = (if (packageJsonFile.isFile) {
val packageJson = packageJsonFile.reader().use {
Gson().fromJson(it, JsonObject::class.java)
}
packageJson["main"] as? String? ?: packageJson["module"] as? String? ?: packageJson["browser"] as? String?
} else null) ?: "index.js"
val mainFile = file.resolve(main)
if (mainFile.isFile) return mainFile.canonicalPath
}
val parent = project.parent
return if (!managed || parent == null) error("Cannot find node module $name in $src")
else NpmProjectLayout[parent].findModule(name, src)
}
companion object { companion object {
const val PACKAGE_JSON = "package.json" const val PACKAGE_JSON = "package.json"
const val NODE_MODULES = "node_modules" const val NODE_MODULES = "node_modules"
operator fun get(project: Project): NpmProjectLayout { operator fun get(project: Project): NpmProjectLayout {
val manageNodeModules = NodeJsPlugin[project].manageNodeModules val nodeJsRootExtension = NodeJsPlugin.apply(project)
val manageNodeModules = nodeJsRootExtension.root.manageNodeModules
val nodeWorkDir = val nodeWorkDir =
if (manageNodeModules) project.rootDir if (manageNodeModules) project.projectDir
else project.buildDir else project.buildDir
return NpmProjectLayout(nodeWorkDir) return NpmProjectLayout(project, nodeWorkDir, manageNodeModules)
} }
} }
} }
val Project.npmProject
get() = NpmProjectLayout[this]
@@ -10,12 +10,12 @@ import com.google.gson.GsonBuilder
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExtension
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin 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.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.targets.js.npm.NpmResolver.ResolutionCallResult.*
/** /**
@@ -40,7 +40,11 @@ internal class NpmResolver private constructor(val rootProject: Project) {
val resolved = process?.resolved val resolved = process?.resolved
return if (resolved != null) AlreadyResolved(resolved) return if (resolved != null) AlreadyResolved(resolved)
else ResolvedNow(NpmResolver(rootProject).resolve(rootProject, null)!!) else {
val resolver = NpmResolver(rootProject)
check(resolver.resolve(rootProject, null))
ResolvedNow(ResolvedProject(resolver.npmPackages))
}
} }
} }
@@ -50,9 +54,10 @@ internal class NpmResolver private constructor(val rootProject: Project) {
class ResolvedNow(val resolution: ResolvedProject) : ResolutionCallResult() class ResolvedNow(val resolution: ResolvedProject) : ResolutionCallResult()
} }
private val nodeJs = NodeJsPlugin[rootProject] private val nodeJs = NodeJsPlugin.apply(rootProject).root
private val npmProject = rootProject.npmProject
private val packageManager = nodeJs.packageManager private val packageManager = nodeJs.packageManager
private val hoistGradleNodeModules = packageManager.hoistGradleNodeModules private val hoistGradleNodeModules = npmProject.hoistGradleNodeModules
private val npmPackages = mutableListOf<NpmPackage>() private val npmPackages = mutableListOf<NpmPackage>()
private val gson = GsonBuilder() private val gson = GsonBuilder()
.setPrettyPrinting() .setPrettyPrinting()
@@ -66,7 +71,11 @@ internal class NpmResolver private constructor(val rootProject: Project) {
} }
} }
class ResolvedProject(val dependencies: Set<NpmDependency>) class ResolvedProject(val npmPackages: Collection<NpmPackage>) {
val dependencies by lazy {
npmPackages.flatMapTo(mutableSetOf()) { it.npmDependencies }
}
}
class NpmPackage( class NpmPackage(
val project: Project, val project: Project,
@@ -91,14 +100,14 @@ internal class NpmResolver private constructor(val rootProject: Project) {
private fun resolve( private fun resolve(
project: Project, project: Project,
parentGradleNodeModules: GradleNodeModulesSync? parentGradleNodeModules: GradleNodeModulesSync?
): ResolvedProject? { ): Boolean {
val existedProcess = ProjectData[project] val existedProcess = ProjectData[project]
val process = if (existedProcess != null) { if (existedProcess != null) {
if (existedProcess.resolved != null) error("yarn dependencies for $project already resolved") if (existedProcess.resolved != null) error("yarn dependencies for $project already resolved")
else { else {
// called inside resolution of classpath (from visitTarget) // called inside resolution of classpath (from visitTarget)
// we should return as we are already in progress // we should return as we are already in progress
return null return false
} }
} else ProjectData().also { } else ProjectData().also {
ProjectData[project] = it ProjectData[project] = it
@@ -133,10 +142,7 @@ internal class NpmResolver private constructor(val rootProject: Project) {
gradleNodeModules.sync() gradleNodeModules.sync()
} }
val npmDependencies = npmPackage?.npmDependencies ?: setOf() return true
return ResolvedProject(npmDependencies).also {
process.resolved = it
}
} }
private fun newGradleNodeModulesSync(project: Project): GradleNodeModulesSync { private fun newGradleNodeModulesSync(project: Project): GradleNodeModulesSync {
@@ -150,40 +156,53 @@ internal class NpmResolver private constructor(val rootProject: Project) {
gradleComponentsSync: GradleNodeModulesSync gradleComponentsSync: GradleNodeModulesSync
): NpmPackage? { ): NpmPackage? {
val packageJson = PackageJson(project.name, project.version.toString()) val packageJson = PackageJson(project.name, project.version.toString())
val transitiveDependencies = mutableListOf<GradleNodeModulesSync.TransitiveNpmDependency>()
val kotlin = project.kotlinExtension
val npmDependencies = mutableSetOf<NpmDependency>() val npmDependencies = mutableSetOf<NpmDependency>()
when (kotlin) {
is KotlinSingleTargetExtension -> visitTarget(
kotlin.target,
project,
npmDependencies,
gradleComponentsSync,
transitiveDependencies
)
is KotlinMultiplatformExtension -> kotlin.targets.forEach {
visitTarget(it, project, npmDependencies, gradleComponentsSync, transitiveDependencies)
}
}
npmDependencies.forEach { visitNpmDependencies(project, npmDependencies, gradleComponentsSync, packageJson)
packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version)
}
transitiveDependencies.forEach { project.nodeJs.packageJsonHandlers.forEach {
packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version)
}
NodeJsExtension[project].packageJsonHandlers.forEach {
it(packageJson) it(packageJson)
} }
if (project == rootProject) { val packageJsonRequired = if (project == rootProject) {
packageManager.hookRootPackage(rootProject, packageJson, npmPackages) packageManager.hookRootPackage(rootProject, packageJson, npmPackages)
} } else false
return if (!packageJson.empty) NpmPackage(project, packageJson, npmDependencies) else null return if (packageJsonRequired || !packageJson.empty) NpmPackage(project, packageJson, npmDependencies) else null
}
private fun visitNpmDependencies(
project: Project,
npmDependencies: MutableSet<NpmDependency>,
gradleComponentsSync: GradleNodeModulesSync,
packageJson: PackageJson
) {
val kotlin = project.kotlinExtensionOrNull
if (kotlin != null) {
val transitiveDependencies = mutableListOf<GradleNodeModulesSync.TransitiveNpmDependency>()
when (kotlin) {
is KotlinSingleTargetExtension -> visitTarget(
kotlin.target,
project,
npmDependencies,
gradleComponentsSync,
transitiveDependencies
)
is KotlinMultiplatformExtension -> kotlin.targets.forEach {
visitTarget(it, project, npmDependencies, gradleComponentsSync, transitiveDependencies)
}
}
npmDependencies.forEach {
packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version)
}
transitiveDependencies.forEach {
packageJson.dependencies[it.key] = chooseVersion(packageJson.dependencies[it.key], it.version)
}
}
} }
private fun chooseVersion(oldVersion: String?, newVersion: String): String = private fun chooseVersion(oldVersion: String?, newVersion: String): String =
@@ -11,7 +11,7 @@ import org.gradle.api.internal.file.FileResolver
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.gradle.process.internal.ExecHandleFactory import org.gradle.process.internal.ExecHandleFactory
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
@@ -64,7 +64,7 @@ open class KotlinWebpack : DefaultTask() {
NpmResolver.resolve(project) NpmResolver.resolve(project)
val npmProjectLayout = NpmProject[project] val npmProjectLayout = NpmProjectLayout[project]
configFile.writeText(buildConfig()) configFile.writeText(buildConfig())