KT-45789 Transitive npm dependencies
This commit is contained in:
committed by
teamcity
parent
5a2ec4a0d5
commit
3e6253cede
+12
-1
@@ -5,11 +5,14 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.gradle
|
package org.jetbrains.kotlin.gradle
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonObject
|
||||||
import org.gradle.util.GradleVersion
|
import org.gradle.util.GradleVersion
|
||||||
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
||||||
import org.jetbrains.kotlin.gradle.testbase.*
|
import org.jetbrains.kotlin.gradle.testbase.*
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
import kotlin.io.path.reader
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
// TODO: This suite is failing with deprecation error on Gradle <7.0 versions
|
// TODO: This suite is failing with deprecation error on Gradle <7.0 versions
|
||||||
@@ -34,6 +37,14 @@ class KotlinJsIrLibraryGradlePluginIT : KGPBaseTest() {
|
|||||||
assertFileInProjectExists("build/productionLibrary/js-library.js")
|
assertFileInProjectExists("build/productionLibrary/js-library.js")
|
||||||
assertFileInProjectExists("build/productionLibrary/package.json")
|
assertFileInProjectExists("build/productionLibrary/package.json")
|
||||||
assertFileInProjectExists("build/productionLibrary/main.js")
|
assertFileInProjectExists("build/productionLibrary/main.js")
|
||||||
|
projectPath.resolve("build/productionLibrary/package.json").reader()
|
||||||
|
.use { Gson().fromJson(it, JsonObject::class.java) }
|
||||||
|
.getAsJsonObject("dependencies")
|
||||||
|
?.entrySet()?.associate { (k, v) -> k to v.asString }
|
||||||
|
.let { dependencies ->
|
||||||
|
assertNotNull(dependencies?.get("kotlin")) { "Direct npm dependency missing in package.json" }
|
||||||
|
assertNotNull(dependencies?.get("@js-joda/core")) { "Transitive npm dependency missing in package.json" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,4 +88,4 @@ class KotlinJsIrLibraryGradlePluginIT : KGPBaseTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -74,12 +74,11 @@ fun KGPBaseTest.project(
|
|||||||
localRepoDir?.let { testProject.configureLocalRepository(localRepoDir) }
|
localRepoDir?.let { testProject.configureLocalRepository(localRepoDir) }
|
||||||
if (buildJdk != null) testProject.setupNonDefaultJdk(buildJdk)
|
if (buildJdk != null) testProject.setupNonDefaultJdk(buildJdk)
|
||||||
|
|
||||||
runCatching {
|
val result = runCatching {
|
||||||
testProject.test()
|
testProject.test()
|
||||||
}.onFailure {
|
|
||||||
// A convenient place to place a breakpoint to be able to inspect project output files
|
|
||||||
throw it
|
|
||||||
}
|
}
|
||||||
|
// A convenient place to place a breakpoint to be able to inspect project output files
|
||||||
|
result.getOrThrow()
|
||||||
return testProject
|
return testProject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -24,4 +24,4 @@ kotlin {
|
|||||||
// We need to think about it, when we will support multiple binaries
|
// We need to think about it, when we will support multiple binaries
|
||||||
tasks.named("nodeProductionLibraryPrepare") {
|
tasks.named("nodeProductionLibraryPrepare") {
|
||||||
mustRunAfter("productionExecutableCompileSync")
|
mustRunAfter("productionExecutableCompileSync")
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -15,4 +15,12 @@ kotlin {
|
|||||||
binaries.library()
|
binaries.library()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
}
|
sourceSets {
|
||||||
|
main {
|
||||||
|
dependencies {
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:latest.release")
|
||||||
|
implementation(npm("kotlin", "*"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+24
-1
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
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 java.io.File
|
import java.io.File
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@@ -14,4 +16,25 @@ import java.io.Serializable
|
|||||||
data class GradleNodeModule(val name: String, val version: String, val path: File) : Serializable {
|
data class GradleNodeModule(val name: String, val version: String, val path: File) : Serializable {
|
||||||
val semver: SemVer
|
val semver: SemVer
|
||||||
get() = SemVer.from(version)
|
get() = SemVer.from(version)
|
||||||
}
|
|
||||||
|
@get:Synchronized
|
||||||
|
val dependencies: Set<NpmDependencyDeclaration> by lazy {
|
||||||
|
val pJson = path.resolve("package.json").reader().use {
|
||||||
|
Gson().fromJson(it, JsonObject::class.java)
|
||||||
|
}
|
||||||
|
val normal = pJson.getAsJsonObject("dependencies")
|
||||||
|
val peer = pJson.getAsJsonObject("peerDependencies")
|
||||||
|
val optional = pJson.getAsJsonObject("optionalDependencies")
|
||||||
|
val dev = pJson.getAsJsonObject("devDependencies")
|
||||||
|
mapOf(
|
||||||
|
NpmDependency.Scope.NORMAL to normal,
|
||||||
|
NpmDependency.Scope.PEER to peer,
|
||||||
|
NpmDependency.Scope.OPTIONAL to optional,
|
||||||
|
NpmDependency.Scope.DEV to dev
|
||||||
|
).mapValues { (_, deps) ->
|
||||||
|
deps?.entrySet()?.associate { (k, v) -> k to v.asString }
|
||||||
|
}.mapNotNull { (scope, deps) ->
|
||||||
|
deps?.map { (k, v) -> NpmDependencyDeclaration(scope, k, v, false) }
|
||||||
|
}.flatten().toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+33
-4
@@ -446,6 +446,9 @@ internal class KotlinCompilationNpmResolver(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.filterNotNull()
|
}.filterNotNull()
|
||||||
|
val transitiveNpmDependencies = importedExternalGradleDependencies.flatMap {
|
||||||
|
it.dependencies
|
||||||
|
}.filter { it.scope != NpmDependency.Scope.DEV }
|
||||||
|
|
||||||
val compositeDependencies = internalCompositeDependencies.flatMap { dependency ->
|
val compositeDependencies = internalCompositeDependencies.flatMap { dependency ->
|
||||||
dependency.getPackages()
|
dependency.getPackages()
|
||||||
@@ -456,8 +459,7 @@ internal class KotlinCompilationNpmResolver(
|
|||||||
file
|
file
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}.filterNotNull()
|
||||||
.filterNotNull()
|
|
||||||
|
|
||||||
val toolsNpmDependencies = compilationResolver.rootResolver.taskRequirements
|
val toolsNpmDependencies = compilationResolver.rootResolver.taskRequirements
|
||||||
.getCompilationNpmRequirements(projectPath, compilationResolver.compilationDisambiguatedName)
|
.getCompilationNpmRequirements(projectPath, compilationResolver.compilationDisambiguatedName)
|
||||||
@@ -473,7 +475,8 @@ internal class KotlinCompilationNpmResolver(
|
|||||||
)
|
)
|
||||||
} else emptySet()
|
} else emptySet()
|
||||||
|
|
||||||
val allNpmDependencies = externalNpmDependencies + toolsNpmDependencies + dukatIfNecessary
|
val otherNpmDependencies = toolsNpmDependencies + dukatIfNecessary + transitiveNpmDependencies
|
||||||
|
val allNpmDependencies = disambiguateDependencies(externalNpmDependencies, otherNpmDependencies)
|
||||||
val packageJsonHandlers = if (compilationResolver.compilation != null) {
|
val packageJsonHandlers = if (compilationResolver.compilation != null) {
|
||||||
compilationResolver.compilation.packageJsonHandlers
|
compilationResolver.compilation.packageJsonHandlers
|
||||||
} else {
|
} else {
|
||||||
@@ -511,6 +514,32 @@ internal class KotlinCompilationNpmResolver(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun disambiguateDependencies(
|
||||||
|
direct: Collection<NpmDependencyDeclaration>,
|
||||||
|
others: Collection<NpmDependencyDeclaration>,
|
||||||
|
): Collection<NpmDependencyDeclaration> {
|
||||||
|
val unique = others.groupBy(NpmDependencyDeclaration::name)
|
||||||
|
.filterKeys { k -> direct.none { it.name == k } }
|
||||||
|
.mapNotNull { (name, dependencies) ->
|
||||||
|
dependencies.maxByOrNull { dep ->
|
||||||
|
SemVer.from(dep.version, true)
|
||||||
|
}?.also { selected ->
|
||||||
|
if (dependencies.size > 1) {
|
||||||
|
compilationResolver.project.logger.warn(
|
||||||
|
"""
|
||||||
|
Transitive npm dependency version clash for compilation "${compilationResolver.compilation.name}"
|
||||||
|
Candidates:
|
||||||
|
${dependencies.joinToString("\n") { "\t\t" + it.name + "@" + it.version }}
|
||||||
|
Selected:
|
||||||
|
${selected.name}@${selected.version}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return direct + unique
|
||||||
|
}
|
||||||
|
|
||||||
private fun CompositeDependency.getPackages(): List<File> {
|
private fun CompositeDependency.getPackages(): List<File> {
|
||||||
val packages = includedBuildDir.resolve(projectPackagesDir.relativeTo(rootDir))
|
val packages = includedBuildDir.resolve(projectPackagesDir.relativeTo(rootDir))
|
||||||
return packages
|
return packages
|
||||||
@@ -520,4 +549,4 @@ internal class KotlinCompilationNpmResolver(
|
|||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user