Skip to content

Commit

Permalink
Merge pull request #81 from IntershopCommunicationsAG/81089-SupportTo…
Browse files Browse the repository at this point in the history
…ResolveLibraryVersionConflicts

feat: support to resolve library version conflicts (#81089)
  • Loading branch information
skiesewetter-intershop authored Dec 7, 2022
2 parents 783bc46 + 9b7708b commit a137a76
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 26 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ dependencies {
implementation(localGroovy())

compileOnly("org.apache.ant:ant:1.10.12")
implementation("com.intershop.version:semantic-version:1.0.0")

testImplementation("com.intershop.gradle.test:test-gradle-plugin:4.1.2")
testImplementation(gradleTestKit())
Expand Down
122 changes: 96 additions & 26 deletions src/main/kotlin/com/intershop/gradle/icm/tasks/CreateLibList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
package com.intershop.gradle.icm.tasks

import com.intershop.gradle.icm.utils.DependencyListUtil
import com.intershop.version.semantic.SemanticVersion
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
Expand All @@ -31,17 +31,15 @@ import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.FileInputStream
import java.util.*
import java.util.Properties
import javax.inject.Inject

/**
* Collects all libraries (recursively through all (sub-)projects) and write IDs to a file
*/
open class CreateLibList @Inject constructor(
objectFactory: ObjectFactory ) : DefaultTask() {

private val copiedLibrariesDirectoryProperty: Property<Directory> =
project.objects.directoryProperty().convention(project.layout.buildDirectory.dir(BUILD_FOLDER))
objectFactory: ObjectFactory,
) : DefaultTask() {

companion object {
const val DEFAULT_NAME = "CreateLibraries"
Expand All @@ -50,6 +48,7 @@ open class CreateLibList @Inject constructor(
fun getName(type: String): String {
return "${type.lowercase()}${DEFAULT_NAME}"
}

fun getOutputPath(type: String): String {
return "librarylist/${type.lowercase()}/file.list"
}
Expand Down Expand Up @@ -77,28 +76,28 @@ open class CreateLibList @Inject constructor(
*/
@TaskAction
fun execute() {
val dependencies = mutableSetOf<String>()
// map group+name to another map mapping -> version to cartridgeNames (space separated list)
// e.g. "aopalliance:aopalliance" -> {
// "1.0" -> "ac_order_export_xml_b2b ac_cxml_order_injection",
// "1.0.1" -> "ac_order_export_xml_b2b ac_cxml_order_injection"
// }
val dependenciesVersions = mutableMapOf<String, MutableMap<String, String>>()
cartridgeDescriptors.get().forEach {
val props = getCartridgeProperties(it)
dependencies.addAll(getLibraryIDs(props).apply {
this.forEach {
val groupAndName = it.substringBeforeLast(':')
val versionInProjects = dependenciesVersions.computeIfAbsent(groupAndName, { mutableMapOf() })
versionInProjects.compute(
it
) { _, p -> if (null == p) getCartridgeName(props) else p + " " + getCartridgeName(props) }
getLibraryIDs(props).forEach { libraryID ->
val groupAndName = libraryID.substringBeforeLast(':')
val version = libraryID.substringAfterLast(':')
val versionToCartridges = dependenciesVersions.computeIfAbsent(groupAndName) { mutableMapOf() }
versionToCartridges.compute(version) { _, cartNames ->
if (null == cartNames) getCartridgeName(props) else cartNames + " " + getCartridgeName(props)
}
})
}
}

val conflicts = dependenciesVersions.filter { e -> 1 < e.value.size }.map { it.value }.toList()
if (conflicts.isNotEmpty()) {
throw GradleException(
"Unable to process libraries. Dependencies ${conflicts}" +
" are required by cartridge-projects in non-unique versions."
)
}
// convert dependenciesVersions into a list of dependencyIds resolving version conflicts if required
val dependencies = dependenciesVersions.toSortedMap().map { (groupAndName, versionToCartridges) ->
toDependencyId(groupAndName, resolveVersionConflict(groupAndName, versionToCartridges))
}.toMutableList()

exludeLibraryLists.get().forEach {
val excludeList = DependencyListUtil.getIDList(environmentType.get(), it)
Expand All @@ -108,7 +107,7 @@ open class CreateLibList @Inject constructor(
val sortedDeps = dependencies.toList().sorted()
val listFile = libraryListFile.asFile.get()

if(listFile.exists()) {
if (listFile.exists()) {
listFile.delete()
}

Expand All @@ -119,20 +118,91 @@ open class CreateLibList @Inject constructor(
}
}

/**
* Resolves a version conflict if required (more than 1 version referenced). The conflict is resolved by choosing
* the highest version except the version gap is a major update - then a GradleException is thrown. If the gap is
* a minor update a warning is logged, on a patch update an info message is logged.
* ATTENTION: this method requires the versions to be semantic version parseable by
* com.vdurmont.semver4j.Semver.Semver(java.lang.String)
*/
private fun resolveVersionConflict(groupAndName: String, versionToCartridges: Map<String, String>): String {
// not a conflict at all ?
if (versionToCartridges.size == 1) {
return versionToCartridges.keys.first()
}

project.logger.debug("Trying to resolve a version conflict for dependency '{}' requiring the versions {}",
groupAndName, versionToCartridges.keys)

// parse to org.gradle.util.internal.VersionNumber
val versions = versionToCartridges.keys.map { versionStr ->
try {
SemanticVersion.valueOf(versionStr)
} catch (e: Exception) {
throw GradleException(
"The version string '$versionStr' can not be parsed therefore the conflict resolution must " +
"be done manually: The dependency '$groupAndName' is required in ${versionToCartridges.size} " +
"different versions by the following cartridges: " +
"$versionToCartridges", e)
}
}.sortedDescending()

// check for major/minor/patch version jump
var prev: SemanticVersion? = null
for (curr in versions) {
if (prev == null) {
prev = curr
continue // skip first iteration (nothing to compare)
}
if (prev.major != curr.major) {
throw GradleException(
"There's a major version conflict for dependency '$groupAndName': ${prev.version} <-> " +
"${curr.version}. Please resolve this conflict analyzing the dependencies of the following " +
"cartridges: $versionToCartridges")
}
val chosen = maxOf(prev, curr)
if (prev.minor != curr.minor) {
project.logger.warn(
"There's a minor version conflict for dependency '{}': {} <-> {}. Version {} is chosen. If " +
"this is not the correct version please resolve this conflict by analyzing the dependencies " +
"of the following cartridges: {}",
groupAndName, prev.version, curr.version, chosen.version, versionToCartridges)
}
if (prev.patch != curr.patch) {
project.logger.info(
"There's a patch version conflict for dependency '{}': {} <-> {}. Version {} is chosen. If " +
"this is not the correct version please resolve this conflict by analyzing the dependencies " +
"of the following cartridges: {}",
groupAndName, prev.version, curr.version, chosen.version, versionToCartridges)
}
prev = curr
}

// finally take the first (highest) version
val chosen = versions.first().version
project.logger.debug("Resolved the version conflict for dependency '{}' choosing version {}",
groupAndName, chosen)
return chosen
}

private fun toDependencyId(groupAndName: String, version: String): String {
return "$groupAndName:$version"
}

private fun getCartridgeProperties(propsFile: RegularFile): Properties {
val props = Properties()
FileInputStream(propsFile.asFile).use {
props.load(it)
}
return props
}
private fun getCartridgeName(props:Properties): String {

private fun getCartridgeName(props: Properties): String {
return props["cartridge.name"].toString()
}

private fun getLibraryIDs(props:Properties): Set<String> {
private fun getLibraryIDs(props: Properties): Set<String> {
val dependsOnLibs = props["cartridge.dependsOnLibs"].toString()
return if (dependsOnLibs.isEmpty()) setOf() else dependsOnLibs.split(";").toSet()
}
}

0 comments on commit a137a76

Please sign in to comment.