Gradle, JS: new nodejs project layout

#KT-30530
This commit is contained in:
Sergey Rostov
2019-04-23 10:13:45 +03:00
parent eb7d09a562
commit f28ff64e9f
18 changed files with 241 additions and 212 deletions
@@ -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"
}
}
}
@@ -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
@@ -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,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()
@@ -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>()
@@ -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()
}
}
@@ -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 =
@@ -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"
@@ -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)
}
}
}
@@ -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
}
@@ -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
)
}
}
}
@@ -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()
@@ -54,7 +54,7 @@ class KotlinNodeJsTestRunner : KotlinJsTestFramework {
val args = nodeJsArgs +
testRuntimeNodeModules.map {
npmProjectLayout.findModule(it)
npmProjectLayout.getModuleEntryPath(it)
} +
cliArgs.toList()
@@ -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 {
@@ -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
@@ -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")
@@ -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)
}
}
@@ -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()