Skip to content

Commit

Permalink
Handle version conditions in version definitions
Browse files Browse the repository at this point in the history
ref #17
  • Loading branch information
hvisser committed Jan 30, 2022
1 parent 7575240 commit 626e273
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 28 deletions.
Expand Up @@ -29,7 +29,7 @@ class VersionCatalogParser {
val mapper = TomlMapper()
val catalog = inputStream.use { mapper.readValue(it, Map::class.java) as Map<String, Any> }

val versions = catalog.getTable("versions")?.toTypedMap<String>() ?: emptyMap()
val versions = catalog.getTable("versions")?.toVersionDefinitionMap() ?: emptyMap()
val libraries = catalog.getTable("libraries")?.toDependencyMap() ?: emptyMap()
val bundles = catalog.getTable("bundles")?.toTypedMap<List<String>>() ?: emptyMap()
val plugins = catalog.getTable("plugins")?.toPluginDependencyMap() ?: emptyMap()
Expand Down Expand Up @@ -101,6 +101,16 @@ private fun Map<String, Any>.toDependencyMap(): Map<String, Library> = toTypedMa
}
}

private fun Map<String, Any>.toVersionDefinitionMap(): Map<String, VersionDefinition> {
return mapValues {
when (val version = it.value.toDependencyVersion()) {
null -> throw IllegalStateException("Unable to parse version ${it.value}")
is VersionDefinition.Reference -> throw IllegalStateException("Version specified cannot be a reference")
else -> version
}
}
}

@Suppress("UNCHECKED_CAST")
private fun Any.toDependencyVersion(): VersionDefinition? = when (this) {
is String -> VersionDefinition.Simple(this)
Expand Down
Expand Up @@ -28,7 +28,7 @@ class VersionCatalogWriter {
if (versionCatalog.versions.isNotEmpty()) {
printWriter.println("[versions]")
for (version in versionCatalog.versions) {
printWriter.println("""${version.key} = "${version.value}"""")
printWriter.println("""${version.key} = ${formatVersion(version.value)}""")
}
}
if (versionCatalog.libraries.isNotEmpty()) {
Expand Down Expand Up @@ -105,4 +105,14 @@ class VersionCatalogWriter {
}
is VersionDefinition.Unspecified -> "{ module = \"${library.module}\" }"
}

private fun formatVersion(versionDefinition: VersionDefinition): String = when (versionDefinition) {
is VersionDefinition.Simple -> "\"${versionDefinition.version}\""
is VersionDefinition.Condition -> {
versionDefinition.definition.entries.joinToString(prefix = "{ ", postfix = " }", separator = ", ") {
"${it.key} = \"${it.value}\""
}
}
else -> throw IllegalStateException("Invalid version definition $versionDefinition")
}
}
56 changes: 42 additions & 14 deletions catalog/src/main/kotlin/nl/littlerobots/vcu/model/VersionCatalog.kt
Expand Up @@ -18,11 +18,27 @@ package nl.littlerobots.vcu.model
import nl.littlerobots.vcu.toml.toTomlKey

data class VersionCatalog(
val versions: Map<String, String>,
val versions: Map<String, VersionDefinition>,
val libraries: Map<String, Library>,
val bundles: Map<String, List<String>>,
val plugins: Map<String, Plugin>
)
) {
/**
* The effective version definition of a [HasVersion], resolving [VersionDefinition.Reference]
*/
internal val HasVersion.resolvedVersion: VersionDefinition
get() {
if (versions.isEmpty()) {
return version
}
val version = version
return if (version is VersionDefinition.Reference) {
versions[version.ref] ?: error("$this references undeclared version: ${version.ref}")
} else {
version
}
}
}

/**
* Create an updated [VersionCatalog] by combining with [catalog]
Expand All @@ -41,22 +57,29 @@ fun VersionCatalog.updateFrom(
val updatedLibraries = catalog.libraries.mapNotNull { entry ->
libraryKeys[entry.value.module]?.let {
val currentLib = this.libraries[it]!!
if (currentLib.version == VersionDefinition.Unspecified) {
it to entry.value.copy(version = VersionDefinition.Unspecified)
} else {
it to entry.value
when (currentLib.resolvedVersion) {
VersionDefinition.Unspecified -> {
it to entry.value.copy(version = VersionDefinition.Unspecified)
}
is VersionDefinition.Condition -> {
// keep the version condition
it to entry.value.copy(version = currentLib.version)
}
else -> {
it to entry.value
}
}
} ?: if (addNew) (entry.key to entry.value) else null
}.toMap()

val libraries = this.libraries.toMutableMap().apply {
putAll(updatedLibraries)
if (purge) {
val modules = catalog.libraries.map { it.value.module }
val modules = catalog.libraries.map { it.value.module }.toSet()
val purgeKeys = libraryKeys.toMutableMap().apply {
keys.removeAll(modules)
}
keys.removeAll(purgeKeys.values)
keys.removeAll(purgeKeys.values.toSet())
}
}

Expand All @@ -67,7 +90,12 @@ fun VersionCatalog.updateFrom(
// for plugins find the id in the current map
val pluginUpdates = catalog.plugins.mapNotNull { entry ->
pluginKeys[entry.value.id]?.let {
it to entry.value
val currentPlugin = requireNotNull(plugins[it])
when (currentPlugin.resolvedVersion) {
// keep condition reference
is VersionDefinition.Condition -> it to currentPlugin
else -> it to entry.value
}
} ?: if (addNew) entry.key to entry.value else null
}.toMap()

Expand Down Expand Up @@ -98,7 +126,7 @@ fun VersionCatalog.updateFrom(
}

private fun VersionCatalog.retainCurrentVersionReferences(
newVersions: MutableMap<String, String>,
newVersions: MutableMap<String, VersionDefinition>,
newLibraries: MutableMap<String, Library>,
newPlugins: MutableMap<String, Plugin>
) {
Expand Down Expand Up @@ -127,7 +155,7 @@ private fun VersionCatalog.retainCurrentVersionReferences(
}.filter {
it.entry.version is VersionDefinition.Simple
}.groupBy {
(it.entry.version as VersionDefinition.Simple).version
(it.entry.version as VersionDefinition.Simple)
}.maxByOrNull {
it.value.size
}
Expand Down Expand Up @@ -182,7 +210,7 @@ fun VersionCatalog.mapPlugins(

private fun VersionCatalog.collectVersionReferenceForGroups(
libraries: MutableMap<String, Library>,
versions: MutableMap<String, String>
versions: MutableMap<String, VersionDefinition>
) {
val groupIdVersionRef = this.libraries.values.filter {
it.version is VersionDefinition.Reference
Expand Down Expand Up @@ -210,7 +238,7 @@ private fun VersionCatalog.collectVersionReferenceForGroups(
else -> existing
}
reference?.let { ref ->
versions[ref] = (entry.value.first().version as VersionDefinition.Simple).version
versions[ref] = (entry.value.first().version as VersionDefinition.Simple)
for (lib in entry.value) {
val libEntry = libraries.entries.first { it.value == lib }
libraries[libEntry.key] = lib.copy(version = VersionDefinition.Reference(ref))
Expand All @@ -235,7 +263,7 @@ internal fun VersionCatalog.pruneVersions(): VersionCatalog {
}.distinct()

val versions = this.versions.toMutableMap().apply {
keys.retainAll(versionReferences)
keys.retainAll(versionReferences.toSet())
}
return copy(versions = versions)
}
Expand Down
Expand Up @@ -25,10 +25,15 @@ import java.io.StringWriter

class VersionCatalogWriterTest {
@Test
fun `writes version definitions`() {
fun `writes simple version definitions`() {
val catalogWriter = VersionCatalogWriter()
val writer = StringWriter()
val catalog = VersionCatalog(versions = mapOf("test" to "1.0"), emptyMap(), emptyMap(), emptyMap())
val catalog = VersionCatalog(
versions = mapOf("test" to VersionDefinition.Simple("1.0")),
emptyMap(),
emptyMap(),
emptyMap()
)
catalogWriter.write(catalog, writer)

assertEquals(
Expand All @@ -40,6 +45,28 @@ class VersionCatalogWriterTest {
)
}

@Test
fun `writes version condition definitions`() {
val catalogWriter = VersionCatalogWriter()
val writer = StringWriter()
val catalog = VersionCatalog(
versions = mapOf("test" to VersionDefinition.Condition(mapOf("strictly" to "1.5.2"))),
emptyMap(),
emptyMap(),
emptyMap()
)
catalogWriter.write(catalog, writer)

assertEquals(
"""
[versions]
test = { strictly = "1.5.2" }
""".trimIndent(),
writer.toString()
)
}

@Test
fun `writes simple library definition`() {
val catalogWriter = VersionCatalogWriter()
Expand Down
Expand Up @@ -99,13 +99,13 @@ class VersionCatalogTest {
),
result.libraries["generated-library-reference-2"]
)
assertEquals("1.0", result.versions["nl-littlerobots-test"])
assertEquals(VersionDefinition.Simple("1.0"), result.versions["nl-littlerobots-test"])
}

@Test
fun `updateCatalog updates version reference for group id if all on the same version`() {
val catalog = VersionCatalog(
mapOf("my-lib" to "1.0"),
mapOf("my-lib" to VersionDefinition.Simple("1.0")),
mapOf(
"generated-library-reference" to Library(
module = "nl.littlerobots.test:example",
Expand Down Expand Up @@ -155,13 +155,13 @@ class VersionCatalogTest {
),
result.libraries["generated-library-reference-2"]
)
assertEquals("1.1", result.versions["my-lib"])
assertEquals(VersionDefinition.Simple("1.1"), result.versions["my-lib"])
}

@Test
fun `updateCatalog updates version reference if defined`() {
val catalog = VersionCatalog(
mapOf("my-lib" to "1.0"),
mapOf("my-lib" to VersionDefinition.Simple("1.0")),
mapOf(
"generated-library-reference" to Library(
module = "nl.littlerobots.test:example",
Expand Down Expand Up @@ -195,13 +195,47 @@ class VersionCatalogTest {
),
result.libraries["generated-library-reference"]
)
assertEquals("1.1", result.versions["my-lib"])
assertEquals(VersionDefinition.Simple("1.1"), result.versions["my-lib"])
}

@Test
fun `updateCatalog maintains version condition reference if defined`() {
val catalog = VersionCatalog(
// the actual condition does not matter
mapOf(
"my-lib" to VersionDefinition.Condition(mapOf()),
"my-plugin" to VersionDefinition.Condition(mapOf())
),
mapOf(
"generated-library-reference" to Library(
module = "nl.littlerobots.test:example",
version = VersionDefinition.Reference("my-lib")
)
),
emptyMap(),
mapOf("my-plugin" to Plugin("test.plugin", version = VersionDefinition.Reference("my-plugin")))
)
val updatedCatalog = VersionCatalog(
emptyMap(),
mapOf(
"generated-library-reference" to Library(
module = "nl.littlerobots.test:example",
version = VersionDefinition.Simple("1.1")
)
),
emptyMap(),
mapOf("my-plugin" to Plugin("test.plugin", version = VersionDefinition.Simple("2.0")))
)

val result = catalog.updateFrom(updatedCatalog, true)

assertEquals(catalog, result)
}

@Test
fun `updateCatalog updates version reference for multiple libraries`() {
val catalog = VersionCatalog(
mapOf("my-lib" to "1.0"),
mapOf("my-lib" to VersionDefinition.Simple("1.0")),
mapOf(
"generated-library-reference" to Library(
module = "nl.littlerobots.test:example",
Expand Down Expand Up @@ -254,7 +288,7 @@ class VersionCatalogTest {

assertEquals(1, result.plugins.size)
assertEquals(Plugin("my.plugin.id", version = VersionDefinition.Reference("my-lib")), result.plugins["plugin"])
assertEquals("1.1", result.versions["my-lib"])
assertEquals(VersionDefinition.Simple("1.1"), result.versions["my-lib"])
}

@Test
Expand Down Expand Up @@ -401,7 +435,13 @@ class VersionCatalogTest {

val result = catalog.pruneVersions()

assertEquals(mapOf("junit" to "4.13.2", "plugin" to "1.0.0"), result.versions)
assertEquals(
mapOf(
"junit" to VersionDefinition.Simple("4.13.2"),
"plugin" to VersionDefinition.Simple("1.0.0")
),
result.versions
)
}

@Test
Expand Down Expand Up @@ -475,6 +515,34 @@ class VersionCatalogTest {
)
}

@Test
fun `keeps complex version definition`() {
val catalog = VersionCatalogParser().parse(
"""
[libraries]
test = { group = "com.mycompany", name = "alternate", version = { require = "1.4" } }
""".trimIndent().byteInputStream()
)

val update = VersionCatalogParser().parse(
"""
[libraries]
test = "com.mycompany:alternate:1.8"
""".trimIndent().byteInputStream()
)

val result = catalog.updateFrom(update)

assertEquals(1, result.libraries.size)
assertEquals(
Library(
module = "com.mycompany:alternate",
version = VersionDefinition.Condition(mapOf("require" to "1.4"))
),
result.libraries["test"]
)
}

@Test
fun `adds plugins from module plugin mapping`() {
val catalog = VersionCatalogParser().parse(
Expand Down Expand Up @@ -534,7 +602,7 @@ class VersionCatalogTest {
@Test
fun `Retains unused version references`() {
val catalog = VersionCatalog(
versions = mapOf("unused" to "1.0.0"),
versions = mapOf("unused" to VersionDefinition.Simple("1.0.0")),
libraries = emptyMap(),
bundles = emptyMap(),
plugins = emptyMap()
Expand All @@ -548,7 +616,7 @@ class VersionCatalogTest {
@Test
fun `Removes unused version references`() {
val catalog = VersionCatalog(
versions = mapOf("unused" to "1.0.0"),
versions = mapOf("unused" to VersionDefinition.Simple("1.0.0")),
libraries = emptyMap(),
bundles = emptyMap(),
plugins = emptyMap()
Expand Down

0 comments on commit 626e273

Please sign in to comment.