Gradle, JS: new nodejs project layout
#KT-30530
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.internal
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun File.ensureParentDirsCreated() {
|
||||
val parentFile = parentFile
|
||||
if (!parentFile.exists()) {
|
||||
check(parentFile.mkdirs()) {
|
||||
"Cannot create parent directories for $this"
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -11,10 +11,10 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
|
||||
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.tasks.KotlinTasksProvider
|
||||
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
|
||||
|
||||
open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
|
||||
KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) {
|
||||
@@ -28,6 +28,8 @@ open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
|
||||
super.configureCompilations(platformTarget)
|
||||
|
||||
platformTarget.compilations.all {
|
||||
platformTarget.project.npmProject.configureCompilation(it)
|
||||
|
||||
it.compileKotlinTask.kotlinOptions {
|
||||
moduleKind = "commonjs"
|
||||
sourceMap = true
|
||||
|
||||
+12
@@ -5,6 +5,7 @@ import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.BasePlugin
|
||||
import org.gradle.api.tasks.Delete
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.Companion.NODE_JS
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
|
||||
|
||||
open class NodeJsPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project): Unit = project.run {
|
||||
@@ -35,6 +36,17 @@ open class NodeJsPlugin : Plugin<Project> {
|
||||
|
||||
project.tasks.maybeCreate(BasePlugin.CLEAN_TASK_NAME).dependsOn(it)
|
||||
}
|
||||
|
||||
project.tasks.create("cleanGradleNodeModules", Delete::class.java) {
|
||||
it.description = "Deletes node_modules_gradle"
|
||||
it.group = BasePlugin.BUILD_GROUP
|
||||
|
||||
it.doLast {
|
||||
project.npmProject.gradleNodeModulesDir.deleteRecursively()
|
||||
}
|
||||
|
||||
project.tasks.maybeCreate(BasePlugin.CLEAN_TASK_NAME).dependsOn(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+2
-5
@@ -2,6 +2,7 @@ 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.targets.js.npm.NpmProjectLayout
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
|
||||
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn
|
||||
import java.io.File
|
||||
@@ -19,10 +20,7 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) {
|
||||
|
||||
var nodeCommand = "node"
|
||||
|
||||
var npmCustomVersion: String? = null
|
||||
var npmCommand = "npm"
|
||||
|
||||
var manageNodeModules: Boolean = false
|
||||
var layout: NpmProjectLayout = NpmProjectLayout.ROOT_PROJECT_BUILD_DIR
|
||||
var packageManager: NpmApi = Yarn
|
||||
|
||||
val nodeJsSetupTask: NodeJsSetupTask
|
||||
@@ -51,7 +49,6 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) {
|
||||
nodeDir = nodeDir,
|
||||
nodeBinDir = nodeBinDir,
|
||||
nodeExecutable = getExecutable("node", nodeCommand, "exe"),
|
||||
npmExecutable = getExecutable("npm", npmCommand, "cmd"),
|
||||
platformName = platform,
|
||||
architectureName = architecture,
|
||||
ivyDependency = getIvyDependency()
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ internal class GradleNodeModuleBuilder(
|
||||
val project: Project,
|
||||
val dependency: ResolvedDependency,
|
||||
val artifacts: Set<ResolvedArtifact>,
|
||||
val cache: GradleNodeModules
|
||||
val cache: GradleNodeModulesBuilder
|
||||
) {
|
||||
var srcPackageJsonFile: File? = null
|
||||
val files = mutableListOf<File>()
|
||||
|
||||
+9
-48
@@ -8,23 +8,19 @@ package org.jetbrains.kotlin.gradle.targets.js.npm
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.ResolvedArtifact
|
||||
import org.gradle.api.artifacts.ResolvedDependency
|
||||
import org.gradle.api.file.CopySpec
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
|
||||
import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.targets.js.internal.RewriteSourceMapFilterReader
|
||||
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
|
||||
|
||||
internal class GradleNodeModules(val project: Project) : AutoCloseable {
|
||||
internal class GradleNodeModulesBuilder(val project: Project) : AutoCloseable {
|
||||
companion object {
|
||||
const val STATE_FILE_NAME = ".visited"
|
||||
}
|
||||
|
||||
internal val dir = project.buildDir.resolve("node_modules_gradle")
|
||||
private val nodeModulesDir = project.npmProject.nodeModulesDir
|
||||
private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "5")
|
||||
private val visited = mutableSetOf<ResolvedDependency>()
|
||||
private val doLast = mutableListOf<() -> Unit>()
|
||||
|
||||
val modules
|
||||
get() = cache.targets.map {
|
||||
@@ -33,29 +29,16 @@ internal class GradleNodeModules(val project: Project) : AutoCloseable {
|
||||
|
||||
fun visitCompilation(compilation: KotlinCompilation<KotlinCommonOptions>) {
|
||||
val project = compilation.target.project
|
||||
val kotlin2JsCompile = compilation.compileKotlinTask as Kotlin2JsCompile
|
||||
|
||||
|
||||
// classpath
|
||||
compilation.relatedConfigurationNames.forEach {
|
||||
val configuration = project.configurations.getByName(it)
|
||||
compilation.relatedConfigurationNames.forEach { configurationName ->
|
||||
val configuration = project.configurations.getByName(configurationName)
|
||||
if (configuration.isCanBeResolved) {
|
||||
configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach {
|
||||
visitDependency(compilation, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// output
|
||||
doLast.add {
|
||||
// we should do it only after node package manger work (as it can clean files)
|
||||
if (kotlin2JsCompile.state.executed) {
|
||||
visitCompile(project, kotlin2JsCompile)
|
||||
} else {
|
||||
kotlin2JsCompile.doLast {
|
||||
visitCompile(project, kotlin2JsCompile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun visitDependency(
|
||||
@@ -82,37 +65,15 @@ internal class GradleNodeModules(val project: Project) : AutoCloseable {
|
||||
}
|
||||
|
||||
artifacts.forEach { artifact ->
|
||||
cache.getOrCompute(artifact.file) {
|
||||
lazyDirName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun visitCompile(project: Project, kotlin2JsCompile: Kotlin2JsCompile) {
|
||||
project.copy { copy ->
|
||||
copy.from(kotlin2JsCompile.outputFile)
|
||||
copy.from(kotlin2JsCompile.outputFile.path + ".map")
|
||||
copy.into(nodeModulesDir)
|
||||
copy.withSourceMapRewriter()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CopySpec.withSourceMapRewriter() {
|
||||
eachFile {
|
||||
if (it.name.endsWith(".js.map")) {
|
||||
it.filter(
|
||||
mapOf(
|
||||
"srcSourceRoot" to it.file.parentFile,
|
||||
"targetSourceRoot" to nodeModulesDir
|
||||
),
|
||||
RewriteSourceMapFilterReader::class.java
|
||||
)
|
||||
if (artifact.id.componentIdentifier !is ProjectComponentIdentifier) {
|
||||
cache.getOrCompute(artifact.file) {
|
||||
lazyDirName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
doLast.forEach { it() }
|
||||
cache.close()
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -30,7 +30,7 @@ interface NpmApi {
|
||||
allWorkspaces: Collection<NpmResolver.NpmPackage>
|
||||
): Boolean = false
|
||||
|
||||
open fun getHoistGradleNodeModules(project: Project) = false
|
||||
open fun shouldHoistGradleNodeModules(project: Project) = false
|
||||
|
||||
companion object {
|
||||
fun resolveOperationDescription(packageManagerTitle: String): String =
|
||||
|
||||
+2
-11
@@ -40,7 +40,7 @@ data class NpmDependency(
|
||||
fun visit(item: NpmDependency) {
|
||||
if (item in visited) return
|
||||
visited.add(item)
|
||||
item.tryFindNodeModule()?.let {
|
||||
item.project.npmProject.findModuleEntry(item.key)?.let {
|
||||
all.add(it)
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ data class NpmDependency(
|
||||
return all
|
||||
}
|
||||
|
||||
internal fun resolveProject(): NpmResolver.ResolvedProject? {
|
||||
private fun resolveProject(): NpmResolver.ResolvedProject? {
|
||||
val result = NpmResolver.resolve(project)
|
||||
|
||||
return when (result) {
|
||||
@@ -72,15 +72,6 @@ data class NpmDependency(
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryFindNodeModule(): File? {
|
||||
var p = project
|
||||
do {
|
||||
val result = NpmProjectLayout[p].nodeModulesDir.resolve(key)
|
||||
if (result.exists()) return result
|
||||
p = project.parent ?: return null
|
||||
} while (true)
|
||||
}
|
||||
|
||||
val key: String = if (org == null) name else "@$org/$name"
|
||||
|
||||
override fun toString() = "$key: $version"
|
||||
|
||||
+26
-22
@@ -10,48 +10,54 @@ import com.google.gson.JsonObject
|
||||
import groovy.transform.TailRecursive
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
|
||||
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
|
||||
import java.io.File
|
||||
|
||||
class NpmProject(
|
||||
open class NpmProject(
|
||||
val project: Project,
|
||||
val nodeWorkDir: File,
|
||||
val managed: Boolean
|
||||
val searchInParents: Boolean
|
||||
) {
|
||||
val nodeModulesDir
|
||||
get() = nodeWorkDir.resolve(NODE_MODULES)
|
||||
|
||||
val packageJsonFile: File
|
||||
get() {
|
||||
return nodeWorkDir.resolve(PACKAGE_JSON)
|
||||
}
|
||||
get() = nodeWorkDir.resolve(PACKAGE_JSON)
|
||||
|
||||
open val compileOutputCopyDest: File?
|
||||
get() = nodeModulesDir
|
||||
|
||||
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
|
||||
exec.args = listOf(getModuleEntryPath(tool)) + args
|
||||
}
|
||||
|
||||
val hoistGradleNodeModules: Boolean
|
||||
get() = project.nodeJs.root.packageManager.getHoistGradleNodeModules(project)
|
||||
get() = project.nodeJs.root.packageManager.shouldHoistGradleNodeModules(project)
|
||||
|
||||
val gradleNodeModulesDir: File
|
||||
get() =
|
||||
if (hoistGradleNodeModules) project.rootProject.npmProject.nodeModulesDir
|
||||
else nodeModulesDir
|
||||
|
||||
fun moduleOutput(compilationTask: Kotlin2JsCompile): File {
|
||||
return gradleNodeModulesDir.resolve(compilationTask.outputFile.name)
|
||||
open fun moduleOutput(compilationTask: Kotlin2JsCompile): File {
|
||||
return compileOutputCopyDest?.resolve(compilationTask.outputFile.name) ?: compilationTask.outputFile
|
||||
}
|
||||
|
||||
fun getModuleEntryPath(name: String): String {
|
||||
return findModuleEntry(name)?.canonicalPath ?: error("Cannot find node module $name in $this")
|
||||
}
|
||||
|
||||
@TailRecursive
|
||||
fun findModule(name: String, src: NpmProject = this): String {
|
||||
fun findModuleEntry(name: String): File? {
|
||||
val file = nodeModulesDir.resolve(name)
|
||||
if (file.isFile) return file.canonicalPath
|
||||
if (file.isFile) return file
|
||||
if (file.isDirectory) {
|
||||
val packageJsonFile = file.resolve("package.json")
|
||||
val main: String = (if (packageJsonFile.isFile) {
|
||||
@@ -59,18 +65,22 @@ class NpmProject(
|
||||
Gson().fromJson(it, JsonObject::class.java)
|
||||
}
|
||||
|
||||
packageJson["main"] as? String? ?: packageJson["module"] as? String? ?: packageJson["browser"] as? String?
|
||||
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
|
||||
if (mainFile.isFile) return mainFile
|
||||
}
|
||||
|
||||
val parent = project.parent
|
||||
return if (!managed || parent == null) error("Cannot find node module $name in $src")
|
||||
else NpmProject[parent].findModule(name, src)
|
||||
return if (!searchInParents || parent == null) null
|
||||
else NpmProject[parent].findModuleEntry(name)
|
||||
}
|
||||
|
||||
open fun configureCompilation(compilation: KotlinJsCompilation) = Unit
|
||||
|
||||
override fun toString() = "NpmProject($nodeWorkDir)"
|
||||
|
||||
companion object {
|
||||
@@ -80,13 +90,7 @@ class NpmProject(
|
||||
operator fun get(project: Project): NpmProject {
|
||||
val nodeJsRootExtension = NodeJsPlugin.apply(project)
|
||||
|
||||
val manageNodeModules = nodeJsRootExtension.root.manageNodeModules
|
||||
|
||||
val nodeWorkDir =
|
||||
if (manageNodeModules) project.projectDir
|
||||
else project.buildDir
|
||||
|
||||
return NpmProject(project, nodeWorkDir, manageNodeModules)
|
||||
return nodeJsRootExtension.root.layout.newNpmProject(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-61
@@ -5,76 +5,59 @@
|
||||
|
||||
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.process.ExecSpec
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
|
||||
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
|
||||
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
|
||||
import java.io.File
|
||||
|
||||
class NpmProjectLayout(
|
||||
val project: Project,
|
||||
val nodeWorkDir: File,
|
||||
val managed: Boolean
|
||||
) {
|
||||
val nodeModulesDir
|
||||
get() = nodeWorkDir.resolve(NODE_MODULES)
|
||||
enum class NpmProjectLayout {
|
||||
PROJECT_DIR {
|
||||
override val allowYarnWorkspaces: Boolean
|
||||
get() = true
|
||||
|
||||
val packageJsonFile: File
|
||||
get() {
|
||||
return nodeWorkDir.resolve(PACKAGE_JSON)
|
||||
}
|
||||
override fun newNpmProject(project: Project) =
|
||||
NpmProject(project, project.projectDir, searchInParents = true)
|
||||
},
|
||||
ROOT_PROJECT_BUILD_DIR {
|
||||
override val allowYarnWorkspaces: Boolean
|
||||
get() = true
|
||||
|
||||
fun useTool(exec: ExecSpec, tool: String, vararg args: String) {
|
||||
NpmResolver.resolve(project)
|
||||
override fun newNpmProject(project: Project): NpmProject {
|
||||
val nodeJsWorldDir = project.rootProject.buildDir.resolve("nodejs-project")
|
||||
|
||||
exec.workingDir = nodeWorkDir
|
||||
exec.executable = project.nodeJs.root.environment.nodeExecutable
|
||||
exec.args = listOf(findModule(tool)) + args
|
||||
}
|
||||
|
||||
@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)
|
||||
val workDir =
|
||||
if (project == project.rootProject) nodeJsWorldDir
|
||||
else {
|
||||
val projectPackage = project.path.removePrefix(":").replace(":", File.separator)
|
||||
nodeJsWorldDir.resolve("packages").resolve(projectPackage)
|
||||
}
|
||||
|
||||
packageJson["main"] as? String? ?: packageJson["module"] as? String? ?: packageJson["browser"] as? String?
|
||||
} else null) ?: "index.js"
|
||||
return object : NpmProject(
|
||||
project,
|
||||
workDir,
|
||||
searchInParents = true
|
||||
) {
|
||||
override val compileOutputCopyDest: File?
|
||||
get() = nodeWorkDir
|
||||
|
||||
val mainFile = file.resolve(main)
|
||||
if (mainFile.isFile) return mainFile.canonicalPath
|
||||
override fun configureCompilation(compilation: KotlinJsCompilation) {
|
||||
// val moduleName = compilation.compileKotlinTask.moduleName
|
||||
// workDir.mkdirs()
|
||||
// compilation.kotlinOptions.outputFile = workDir.resolve("$moduleName.js").absolutePath
|
||||
}
|
||||
|
||||
// override fun moduleOutput(compilationTask: Kotlin2JsCompile) =
|
||||
// compilationTask.outputFile
|
||||
}
|
||||
}
|
||||
},
|
||||
BUILD_DIR {
|
||||
override fun newNpmProject(project: Project) =
|
||||
NpmProject(project, project.buildDir, searchInParents = false)
|
||||
};
|
||||
|
||||
val parent = project.parent
|
||||
return if (!managed || parent == null) error("Cannot find node module $name in $src")
|
||||
else NpmProjectLayout[parent].findModule(name, src)
|
||||
}
|
||||
open val allowYarnWorkspaces: Boolean
|
||||
get() = false
|
||||
|
||||
companion object {
|
||||
const val PACKAGE_JSON = "package.json"
|
||||
const val NODE_MODULES = "node_modules"
|
||||
|
||||
operator fun get(project: Project): NpmProjectLayout {
|
||||
val nodeJsRootExtension = NodeJsPlugin.apply(project)
|
||||
|
||||
val manageNodeModules = nodeJsRootExtension.root.manageNodeModules
|
||||
|
||||
val nodeWorkDir =
|
||||
if (manageNodeModules) project.projectDir
|
||||
else project.buildDir
|
||||
|
||||
return NpmProjectLayout(project, nodeWorkDir, manageNodeModules)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Project.npmProject
|
||||
get() = NpmProjectLayout[this]
|
||||
abstract fun newNpmProject(project: Project): NpmProject
|
||||
}
|
||||
+91
-47
@@ -8,25 +8,28 @@ 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.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.targets.js.internal.RewriteSourceMapFilterReader
|
||||
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
|
||||
|
||||
/**
|
||||
* [NpmResolver] runs selected [NodeJsRootExtension.packageManager] on root project
|
||||
* with configured `package.json` files in all projects.
|
||||
* Generates `package.json` file for projects with npm or js dependencies and
|
||||
* runs selected [NodeJsRootExtension.packageManager] to download and install it.
|
||||
*
|
||||
* `package.json`
|
||||
*
|
||||
* - compile(npm(...)) dependencies
|
||||
* - compile(npm(...)) dependencies from external modules
|
||||
* - compile(non npm(...)) dependencies transitively
|
||||
* All [NpmDependency] for configurations related to kotlin/js will be added to `package.json`.
|
||||
* For external gradle modules, fake npm packages will be created and added to `package.json`
|
||||
* as path to directory.
|
||||
*/
|
||||
internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
companion object {
|
||||
@@ -55,8 +58,9 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
}
|
||||
|
||||
private val nodeJs = NodeJsPlugin.apply(rootProject).root
|
||||
private val npmProject = rootProject.npmProject
|
||||
private val packageManager = nodeJs.packageManager
|
||||
private val hoistGradleNodeModules = packageManager.getHoistGradleNodeModules(rootProject)
|
||||
private val hoistGradleNodeModules = npmProject.hoistGradleNodeModules
|
||||
private val npmPackages = mutableListOf<NpmPackage>()
|
||||
private val gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
@@ -82,7 +86,9 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
val npmDependencies: Set<NpmDependency>
|
||||
) {
|
||||
fun savePackageJson(gson: Gson) {
|
||||
NpmProjectLayout[project].packageJsonFile.writer().use {
|
||||
val packageJsonFile = NpmProject[project].packageJsonFile
|
||||
packageJsonFile.ensureParentDirsCreated()
|
||||
packageJsonFile.writer().use {
|
||||
gson.toJson(packageJson, it)
|
||||
}
|
||||
}
|
||||
@@ -98,7 +104,7 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
|
||||
private fun resolve(
|
||||
project: Project,
|
||||
parentGradleNodeModules: GradleNodeModulesSync?
|
||||
parentGradleModules: GradleNodeModulesBuilder?
|
||||
): Boolean {
|
||||
val existedProcess = ProjectData[project]
|
||||
if (existedProcess != null) {
|
||||
@@ -112,15 +118,15 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
ProjectData[project] = it
|
||||
}
|
||||
|
||||
val gradleNodeModules =
|
||||
if (hoistGradleNodeModules && parentGradleNodeModules != null) parentGradleNodeModules
|
||||
else newGradleNodeModulesSync(project)
|
||||
val gradleModules =
|
||||
if (hoistGradleNodeModules && parentGradleModules != null) parentGradleModules
|
||||
else GradleNodeModulesBuilder(project)
|
||||
|
||||
project.subprojects.forEach {
|
||||
resolve(it, gradleNodeModules)
|
||||
resolve(it, gradleModules)
|
||||
}
|
||||
|
||||
val npmPackage = extractNpmPackage(project, gradleNodeModules)
|
||||
val npmPackage = extractNpmPackage(project, gradleModules)
|
||||
if (npmPackage != null) {
|
||||
npmPackage.savePackageJson(gson)
|
||||
|
||||
@@ -137,70 +143,60 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
}
|
||||
}
|
||||
|
||||
if (gradleNodeModules != parentGradleNodeModules) {
|
||||
gradleNodeModules.sync()
|
||||
if (gradleModules != parentGradleModules) {
|
||||
gradleModules.close()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun newGradleNodeModulesSync(project: Project): GradleNodeModulesSync {
|
||||
return GradleNodeModulesSync(project).also {
|
||||
it.loadOldState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractNpmPackage(
|
||||
project: Project,
|
||||
gradleComponentsSync: GradleNodeModulesSync
|
||||
gradleComponents: GradleNodeModulesBuilder
|
||||
): NpmPackage? {
|
||||
val packageJson = PackageJson(project.name, project.version.toString())
|
||||
val npmDependencies = mutableSetOf<NpmDependency>()
|
||||
|
||||
visitNpmDependencies(project, npmDependencies, gradleComponentsSync, packageJson)
|
||||
visitNpmDependencies(project, npmDependencies, gradleComponents, packageJson)
|
||||
|
||||
if (!hoistGradleNodeModules || project == project.rootProject) {
|
||||
gradleComponents.modules.forEach {
|
||||
val relativePath = it.path.relativeTo(NpmProject[project].nodeWorkDir)
|
||||
packageJson.dependencies[it.name] = "file:$relativePath"
|
||||
}
|
||||
}
|
||||
|
||||
project.nodeJs.packageJsonHandlers.forEach {
|
||||
it(packageJson)
|
||||
}
|
||||
|
||||
val packageJsonRequired = if (project == rootProject) {
|
||||
packageManager.hookRootPackage(rootProject, packageJson, npmPackages)
|
||||
} else false
|
||||
val packageJsonRequired =
|
||||
if (project == rootProject) packageManager.hookRootPackage(rootProject, packageJson, npmPackages)
|
||||
else false
|
||||
|
||||
return if (packageJsonRequired || !packageJson.empty) NpmPackage(project, packageJson, npmDependencies) else null
|
||||
return if (!packageJsonRequired && packageJson.empty) null
|
||||
else NpmPackage(project, packageJson, npmDependencies)
|
||||
}
|
||||
|
||||
private fun visitNpmDependencies(
|
||||
project: Project,
|
||||
npmDependencies: MutableSet<NpmDependency>,
|
||||
gradleComponentsSync: GradleNodeModulesSync,
|
||||
gradleComponents: GradleNodeModulesBuilder,
|
||||
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 KotlinSingleTargetExtension -> visitTarget(kotlin.target, project, npmDependencies, gradleComponents, packageJson)
|
||||
is KotlinMultiplatformExtension -> kotlin.targets.forEach {
|
||||
visitTarget(it, project, npmDependencies, gradleComponentsSync, transitiveDependencies)
|
||||
visitTarget(it, project, npmDependencies, gradleComponents, packageJson)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,8 +207,8 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
target: KotlinTarget,
|
||||
project: Project,
|
||||
npmDependencies: MutableSet<NpmDependency>,
|
||||
gradleComponentsSync: GradleNodeModulesSync,
|
||||
transitiveDependencies: MutableList<GradleNodeModulesSync.TransitiveNpmDependency>
|
||||
gradleComponents: GradleNodeModulesBuilder,
|
||||
packageJson: PackageJson
|
||||
) {
|
||||
if (target.platformType == KotlinPlatformType.js) {
|
||||
target.compilations.toList().forEach { compilation ->
|
||||
@@ -224,7 +220,55 @@ internal class NpmResolver private constructor(val rootProject: Project) {
|
||||
}
|
||||
}
|
||||
|
||||
gradleComponentsSync.visitCompilation(compilation, project, transitiveDependencies)
|
||||
gradleComponents.visitCompilation(compilation)
|
||||
|
||||
if (compilation is KotlinJsCompilation) {
|
||||
visitKotlinJsCompilation(project, compilation)
|
||||
|
||||
if (compilation.name == "main") {
|
||||
packageJson.main = compilation.compileKotlinTask.outputFile.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun visitKotlinJsCompilation(
|
||||
project: Project,
|
||||
compilation: KotlinJsCompilation
|
||||
) {
|
||||
val npmProject = project.npmProject
|
||||
val kotlin2JsCompile = compilation.compileKotlinTask
|
||||
if (npmProject.compileOutputCopyDest != null) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -10,7 +10,8 @@ class PackageJson(
|
||||
var version: String
|
||||
) {
|
||||
val empty: Boolean
|
||||
get() = private == null &&
|
||||
get() = main == null &&
|
||||
private == null &&
|
||||
workspaces == null &&
|
||||
dependencies.isEmpty() &&
|
||||
devDependencies.isEmpty()
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ class KotlinNodeJsTestRunner : KotlinJsTestFramework {
|
||||
|
||||
val args = nodeJsArgs +
|
||||
testRuntimeNodeModules.map {
|
||||
npmProjectLayout.findModule(it)
|
||||
npmProjectLayout.getModuleEntryPath(it)
|
||||
} +
|
||||
cliArgs.toList()
|
||||
|
||||
|
||||
+7
-4
@@ -37,14 +37,11 @@ open class KotlinWebpack : DefaultTask() {
|
||||
open lateinit var entry: File
|
||||
|
||||
open val configFile: File
|
||||
@OutputFile get() = project.buildDir.resolve("webpack.config.js")
|
||||
@OutputFile get() = project.npmProject.nodeWorkDir.resolve("webpack.config.js")
|
||||
|
||||
@Input
|
||||
var saveEvaluatedConfigFile: Boolean = true
|
||||
|
||||
open val evaluatedConfigFile: File
|
||||
@OutputFile get() = project.buildDir.resolve("webpack.config.evaluated.js")
|
||||
|
||||
open val outputPath: File
|
||||
@OutputDirectory get() = project.buildDir.resolve("lib")
|
||||
|
||||
@@ -57,10 +54,14 @@ open class KotlinWebpack : DefaultTask() {
|
||||
open val reportDir: File
|
||||
@OutputDirectory get() = project.reportsDir.resolve("webpack").resolve(entry.nameWithoutExtension)
|
||||
|
||||
open val evaluatedConfigFile: File
|
||||
@OutputFile get() = reportDir.resolve("webpack.config.evaluated.js")
|
||||
|
||||
@Input
|
||||
var bin: String = "webpack"
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
var devServer: KotlinWebpackConfig.DevServer? = null
|
||||
|
||||
internal fun createRunner() = KotlinWebpackRunner(
|
||||
@@ -125,6 +126,8 @@ open class KotlinWebpack : DefaultTask() {
|
||||
it.dependsOn(compileKotlinTask)
|
||||
|
||||
it.entry = npmProject.moduleOutput(compileKotlinTask)
|
||||
|
||||
it.outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
compilation.dependencies {
|
||||
|
||||
+9
-3
@@ -13,6 +13,7 @@
|
||||
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
|
||||
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
|
||||
@@ -26,9 +27,13 @@ internal class KotlinWebpackRunner(
|
||||
val bin: String,
|
||||
val config: KotlinWebpackConfig
|
||||
) {
|
||||
fun execute() {
|
||||
fun execute(): ExecResult {
|
||||
val exec = start()
|
||||
exec.waitForFinish()
|
||||
val result = exec.waitForFinish()
|
||||
check(result.exitValue == 0) {
|
||||
"Webpack exited with non zero exit code (${result.exitValue})"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun start(): ExecHandle {
|
||||
@@ -43,12 +48,13 @@ internal class KotlinWebpackRunner(
|
||||
config.save(configFile)
|
||||
|
||||
val execFactory = execHandleFactory.newExec()
|
||||
|
||||
val args = mutableListOf<String>("--config", configFile.absolutePath)
|
||||
if (config.showProgress) {
|
||||
args.add("--progress")
|
||||
}
|
||||
|
||||
npmProjectLayout.useTool(execFactory, ".bin/$bin")
|
||||
npmProjectLayout.useTool(execFactory, ".bin/$bin", *args.toTypedArray())
|
||||
val exec = execFactory.build()
|
||||
exec.start()
|
||||
return exec
|
||||
|
||||
+2
-2
@@ -103,7 +103,7 @@ object Yarn : NpmApi {
|
||||
rootPackageJson.private = true
|
||||
rootPackageJson.workspaces = allWorkspaces
|
||||
.filter { it.project != rootProject }
|
||||
.map { it.project.projectDir.relativeTo(rootProject.rootDir).path }
|
||||
.map { it.project.npmProject.nodeWorkDir.relativeTo(rootProject.npmProject.nodeWorkDir).path }
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -111,7 +111,7 @@ object Yarn : NpmApi {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getHoistGradleNodeModules(project: Project): Boolean =
|
||||
override fun shouldHoistGradleNodeModules(project: Project): Boolean =
|
||||
project.rootProject.yarn.useWorkspaces
|
||||
|
||||
@Suppress("EXPOSED_PARAMETER_TYPE")
|
||||
|
||||
+11
-3
@@ -9,6 +9,7 @@ import org.gradle.api.Project
|
||||
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.targets.js.npm.npmProject
|
||||
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn.packageJsonHashFile
|
||||
|
||||
@@ -18,17 +19,24 @@ class YarnAvoidance(val project: Project) {
|
||||
if (packageJsonHashFile.exists()) packageJsonHashFile.readText() else null
|
||||
}
|
||||
|
||||
private val npmProject = project.npmProject
|
||||
private val packageJsonFile = npmProject.packageJsonFile
|
||||
|
||||
private val hash by lazy {
|
||||
val hasher = (project as ProjectInternal).services.get(FileHasher::class.java)
|
||||
hasher.hash(project.npmProject.packageJsonFile).toByteArray().toHexString()
|
||||
hasher.hash(packageJsonFile).toByteArray().toHexString()
|
||||
}
|
||||
|
||||
val upToDate: Boolean
|
||||
get() = prevHash == hash
|
||||
get() = packageJsonFile.exists() &&
|
||||
npmProject.nodeModulesDir.isDirectory &&
|
||||
prevHash == hash
|
||||
|
||||
fun commit() {
|
||||
if (!upToDate) {
|
||||
project.packageJsonHashFile.writeText(hash)
|
||||
val packageJsonHashFile = project.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() = project.nodeJs.root.manageNodeModules && !disableWorkspaces
|
||||
get() = !disableWorkspaces && project.nodeJs.root.layout.allowYarnWorkspaces
|
||||
|
||||
internal fun executeSetup() {
|
||||
NodeJsPlugin.apply(project).root.executeSetup()
|
||||
|
||||
Reference in New Issue
Block a user