Skip to content

Support multiple configurations in one diff #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 86 additions & 9 deletions src/main/kotlin/com/jakewharton/gradle/dependencies/treeDiff.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,91 @@ import java.util.regex.Pattern

@JvmName("diff")
fun dependencyTreeDiff(old: String, new: String): String {
val oldPaths = findDependencyPaths(old)
val newPaths = findDependencyPaths(new)
val olds = dependencies(old)
val news = dependencies(new)

val addedConfigurations = news.keys - olds.keys
val removedConfigurations = olds.keys - news.keys
val matchingConfigurations = news.keys intersect olds.keys

val added = addedConfigurations
.associateWith { dependencyTreeDiff(emptyList(), news.getValue(it)) }
.mapKeys { "+${it.key}" }
.mapValues { it.value.takeIf(String::isNotEmpty) ?: "No dependencies\n" }
val removed = removedConfigurations
.associateWith { dependencyTreeDiff(olds.getValue(it), emptyList()) }
.mapKeys { "-${it.key}" }
.mapValues { it.value.takeIf(String::isNotEmpty) ?: "No dependencies\n" }
val modified = matchingConfigurations
.associateWith { dependencyTreeDiff(olds.getValue(it), news.getValue(it)) }
// Ignore configurations that didn't change at all.
.filter { it.value != "" }

return if (modified.size == 1 && added.isEmpty() && removed.isEmpty()) {
modified.values.single()
} else {
(modified + added + removed)
.entries
.joinToString(separator = "\n") { (configuration, diff) ->
"${configuration}\n${diff}"
}
}
}

private enum class DependencyScanState {
LOOKING,
SCANNING,
}

private val newlineRegex = Pattern.compile("(\\r\\n|\\n|\\r)")

private fun dependencies(text: String): Map<String, List<String>> {
val configurations = mutableMapOf<String, List<String>>()
var state = DependencyScanState.LOOKING
var configuration = "String to Satisfy Kotlin Compiler"
val dependencies = mutableListOf<String>()
newlineRegex
.split(text, -1)
.fold("<unknown configuration>") { prev, curr ->
when (state) {
DependencyScanState.LOOKING -> {
if (curr.startsWith("+--- ") || curr.startsWith("\\---") || curr == "No dependencies" ) {
// Found first line of dependencies, save configuration and collect all of them.
configuration = prev
dependencies.add(curr)
state = DependencyScanState.SCANNING
} else {
// Continue `reduce`, skipping over unknown lines.
}
}

DependencyScanState.SCANNING -> {
if (curr.isEmpty()) {
val cleanDependencies = if (dependencies.size == 1 && dependencies[0] == "No dependencies") {
// Remove dependencies from configurations with only "No dependencies" so tree diff doesn't process it.
emptyList()
} else {
// Make a copy of the mutable list to prevent leaking mutations into result.
dependencies.toList()
}
if (configurations.putIfAbsent(configuration, cleanDependencies) != null) {
error("Unsupported input: multiple unknown configurations")
}
dependencies.clear()
state = DependencyScanState.LOOKING
} else {
dependencies.add(curr)
}
}
}
curr
}
return configurations
}

private fun dependencyTreeDiff(oldLines: List<String>, newLines: List<String>): String {
val oldPaths = findDependencyPaths(oldLines)
val newPaths = findDependencyPaths(newLines)

val removedTree = buildTree(oldPaths - newPaths)
val addedTree = buildTree(newPaths - oldPaths)
Expand All @@ -18,13 +101,7 @@ fun dependencyTreeDiff(old: String, new: String): String {
}
}

private val newlineRegex = Pattern.compile("(\r\n|\n|\r)")

private fun findDependencyPaths(text: String): Set<List<String>> {
val dependencyLines = newlineRegex.split(text)
.dropWhile { !it.startsWith("+--- ") && !it.startsWith("\\---") }
.takeWhile { it.isNotEmpty() }

private fun findDependencyPaths(dependencyLines: List<String>): Set<List<String>> {
val dependencyPaths = mutableSetOf<List<String>>()
val stack = ArrayDeque<String>()
for (dependencyLine in dependencyLines) {
Expand Down
198 changes: 198 additions & 0 deletions src/test/fixtures/configuration-differences/expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
apiDependenciesMetadata
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

compileClasspath - Compile classpath for compilation 'main' (target (jvm)).
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

implementationDependenciesMetadata
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

kotlinCompilerClasspath
-\--- org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- | \--- org.jetbrains:annotations:13.0
- +--- org.jetbrains.kotlin:kotlin-script-runtime:1.4.0
- +--- org.jetbrains.kotlin:kotlin-reflect:1.4.0
- | \--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0 (*)
- +--- org.jetbrains.kotlin:kotlin-daemon-embeddable:1.4.0
- \--- org.jetbrains.intellij.deps:trove4j:1.0.20181211
+\--- org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-script-runtime:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-reflect:1.5.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ +--- org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.0
+ \--- org.jetbrains.intellij.deps:trove4j:1.0.20181211

kotlinCompilerPluginClasspath
-\--- org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.4.0
- +--- org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.4.0
- | +--- org.jetbrains.kotlin:kotlin-scripting-common:1.4.0
- | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- | | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- | | | \--- org.jetbrains:annotations:13.0
- | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7
- | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.4.0 (*)
- | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.71 -> 1.4.0
- | +--- org.jetbrains.kotlin:kotlin-scripting-jvm:1.4.0
- | | +--- org.jetbrains.kotlin:kotlin-script-runtime:1.4.0
- | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0 (*)
- | | \--- org.jetbrains.kotlin:kotlin-scripting-common:1.4.0 (*)
- | +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0 (*)
- | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7 (*)
- \--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0 (*)

kotlinKlibCommonizerClasspath
-\--- org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- | \--- org.jetbrains:annotations:13.0
- \--- org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0 (*)
- +--- org.jetbrains.kotlin:kotlin-script-runtime:1.4.0
- +--- org.jetbrains.kotlin:kotlin-reflect:1.4.0
- | \--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0 (*)
- +--- org.jetbrains.kotlin:kotlin-daemon-embeddable:1.4.0
- \--- org.jetbrains.intellij.deps:trove4j:1.0.20181211
+\--- org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ +--- org.jetbrains.kotlin:kotlin-script-runtime:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-reflect:1.5.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ +--- org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.0
+ \--- org.jetbrains.intellij.deps:trove4j:1.0.20181211

runtimeClasspath - Runtime classpath of compilation 'main' (target (jvm)).
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

testCompileClasspath - Compile classpath for compilation 'test' (target (jvm)).
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

testImplementationDependenciesMetadata
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

testRuntimeClasspath - Runtime classpath of compilation 'test' (target (jvm)).
-\--- org.jetbrains.kotlin:kotlin-stdlib:1.4.0
- +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.4.0
- \--- org.jetbrains:annotations:13.0
+\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | +--- org.jetbrains:annotations:13.0
+ | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

+apiElements-published (n)
No dependencies

+kotlinCompilerPluginClasspathMain - Kotlin compiler plugins for compilation 'main' (target (jvm))
+\--- org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.0
+ | +--- org.jetbrains.kotlin:kotlin-scripting-common:1.5.0
+ | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | | | +--- org.jetbrains:annotations:13.0
+ | | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8
+ | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.5.0 (*)
+ | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.71 -> 1.5.0
+ | +--- org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.0
+ | | +--- org.jetbrains.kotlin:kotlin-script-runtime:1.5.0
+ | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ | | \--- org.jetbrains.kotlin:kotlin-scripting-common:1.5.0 (*)
+ | +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8 (*)
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

+kotlinCompilerPluginClasspathTest - Kotlin compiler plugins for compilation 'test' (target (jvm))
+\--- org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.0
+ +--- org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.0
+ | +--- org.jetbrains.kotlin:kotlin-scripting-common:1.5.0
+ | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0
+ | | | +--- org.jetbrains:annotations:13.0
+ | | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.0
+ | | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8
+ | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.5.0 (*)
+ | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.71 -> 1.5.0
+ | +--- org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.0
+ | | +--- org.jetbrains.kotlin:kotlin-script-runtime:1.5.0
+ | | +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ | | \--- org.jetbrains.kotlin:kotlin-scripting-common:1.5.0 (*)
+ | +--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)
+ | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8 (*)
+ \--- org.jetbrains.kotlin:kotlin-stdlib:1.5.0 (*)

+runtimeElements-published (n)
No dependencies

--api (n)
No dependencies

--runtime (n)
No dependencies

-compile - Dependencies for compilation 'main' (target (jvm)) (deprecated, use 'implementation ' instead).
No dependencies

-runtime - Runtime dependencies for compilation 'main' (target (jvm)) (deprecated, use 'runtimeOnly ' instead).
No dependencies

-testCompile - Dependencies for compilation 'test' (target (jvm)) (deprecated, use 'testImplementation ' instead).
No dependencies

-testRuntime - Runtime dependencies for compilation 'test' (target (jvm)) (deprecated, use 'testRuntimeOnly ' instead).
No dependencies
Loading