[Gradle, JS] Fix yarn.lock resolution in case of duplicated dependencies

^KT-42222 fixed
^KT-40986 fixed
^KT-39838 fixed
This commit is contained in:
Ilya Goncharov
2020-09-28 15:15:04 +03:00
parent 34b6003371
commit 024771028c
3 changed files with 44 additions and 24 deletions
@@ -19,12 +19,11 @@ fun versionToNpmRanges(version: String): Set<NpmRange> {
.visit(parser.rangeSet())!! .visit(parser.rangeSet())!!
} }
fun buildNpmVersion( fun includedRange(
includedVersions: List<String>, includedVersions: List<String>,
excludedVersions: List<String>,
includedWithCaret: Boolean = false includedWithCaret: Boolean = false
): String { ): NpmRange =
val includedRange: NpmRange? = try { try {
includedVersions includedVersions
.flatMap { versionToNpmRanges(it) } .flatMap { versionToNpmRanges(it) }
.map { if (includedWithCaret) it.caretizeSingleVersion() else it } .map { if (includedWithCaret) it.caretizeSingleVersion() else it }
@@ -33,13 +32,18 @@ fun buildNpmVersion(
requireNotNull(intersection) { requireNotNull(intersection) {
"Included versions have no intersection $includedVersions" "Included versions have no intersection $includedVersions"
} }
intersection!! intersection
} }
} catch (e: UnsupportedOperationException) { } catch (e: UnsupportedOperationException) {
throw InvalidUserDataException("No ranges for included versions $includedVersions") throw InvalidUserDataException("No ranges for included versions $includedVersions")
} }
includedRange!! fun buildNpmVersion(
includedVersions: List<String>,
excludedVersions: List<String>,
includedWithCaret: Boolean = false
): String {
val includedRange: NpmRange = includedRange(includedVersions, includedWithCaret)
if (excludedVersions.isEmpty()) return includedRange.toString() if (excludedVersions.isEmpty()) return includedRange.toString()
@@ -9,6 +9,7 @@ import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes import com.google.gson.FieldAttributes
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import org.gradle.api.GradleException
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import java.io.File import java.io.File
@@ -126,7 +127,7 @@ fun packageJson(
npmDependencies.forEach { npmDependencies.forEach {
val module = it.key val module = it.key
dependencies[it.key] = chooseVersion(dependencies[module], it.version) dependencies[module] = chooseVersion(module, dependencies[module], it.version)
} }
npmDependencies.forEach { npmDependencies.forEach {
@@ -146,12 +147,19 @@ fun packageJson(
return packageJson return packageJson
} }
// TODO: real versions conflict resolution private fun chooseVersion(
private fun chooseVersion(oldVersion: String?, newVersion: String): String { module: String,
// https://yarnpkg.com/lang/en/docs/dependency-versions/#toc-x-ranges oldVersion: String?,
if (oldVersion == "*") { newVersion: String
): String {
if (oldVersion == null) {
return newVersion return newVersion
} }
return oldVersion ?: newVersion return (includedRange(listOf(oldVersion)) intersect includedRange(listOf(newVersion)))?.toString()
?: throw GradleException(
"""
There is already declared version of '$module' with version '$oldVersion' which does not intersects with another declared version '${newVersion}'
""".trimIndent()
)
} }
@@ -5,8 +5,10 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import com.github.gundy.semver4j.SemVer import org.gradle.api.GradleException
import org.jetbrains.kotlin.gradle.targets.js.npm.FILE_VERSION_PREFIX import org.jetbrains.kotlin.gradle.targets.js.npm.FILE_VERSION_PREFIX
import org.jetbrains.kotlin.gradle.targets.js.npm.includedRange
import org.jetbrains.kotlin.gradle.targets.js.npm.intersect
import java.io.File import java.io.File
internal class YarnEntryRegistry(private val lockFile: File) { internal class YarnEntryRegistry(private val lockFile: File) {
@@ -16,21 +18,27 @@ internal class YarnEntryRegistry(private val lockFile: File) {
fun find(packageKey: String, version: String): YarnLock.Entry { fun find(packageKey: String, version: String): YarnLock.Entry {
val key = dependencyKey(packageKey, version) val key = dependencyKey(packageKey, version)
var entry = entryMap[key] val entry = entryMap[key]
if (entry == null && version == "*") { if (entry != null) {
val searchKey = dependencyKey(packageKey, "") return entry
entry = entryMap.entries }
.filter { it.key.startsWith(searchKey) }
.firstOrNull { val versionRange = includedRange(listOf(version))
SemVer.satisfies(it.key.removePrefix(searchKey), "*")
entryMap.entries
.firstOrNull { (_, entry) ->
if (entry.version == null) {
false
} else {
(versionRange intersect includedRange(listOf(entry.version))) != null
} }
?.value }
} ?.let { return it.value }
return checkNotNull(entry) { throw GradleException(
"Cannot find $key in yarn.lock" "Cannot find $key in yarn.lock"
} )
} }
private val YarnLock.Entry.dependencyKey: String private val YarnLock.Entry.dependencyKey: String