-
Notifications
You must be signed in to change notification settings - Fork 81
Expand file tree
/
Copy pathModuleGraphParser.kt
More file actions
111 lines (99 loc) · 3.29 KB
/
ModuleGraphParser.kt
File metadata and controls
111 lines (99 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package com.bazel_diff.bazel
import com.google.gson.JsonObject
import com.google.gson.JsonParser
/**
* Data class representing a module in the dependency graph.
*/
data class Module(
val key: String,
val name: String,
val version: String,
val apparentName: String
)
/**
* Parses and compares Bazel module graphs to detect changes.
*
* Instead of including the entire module graph in the hash seed (which causes all targets
* to rehash when MODULE.bazel changes), this class identifies which specific modules changed
* so we can query only the targets that depend on those modules.
*/
class ModuleGraphParser {
/**
* Parses the JSON output from `bazel mod graph --output=json`.
*
* Tolerates a non-JSON prefix (e.g. leaked stderr from bazel-diff
* 17.0.1..18.0.5, which captured stderr into moduleGraphJson via
* Process.kt's captureAll -> ProcessBuilder.redirectErrorStream(true)).
*
* @param json The JSON string from bazel mod graph
* @return A map of module keys to Module objects
*/
fun parseModuleGraph(json: String): Map<String, Module> {
val modules = mutableMapOf<String, Module>()
try {
val root = try {
JsonParser.parseString(json).asJsonObject
} catch (_: Exception) {
val start = json.indexOf('{')
if (start < 0) return emptyMap()
JsonParser.parseString(json.substring(start)).asJsonObject
}
extractModules(root, modules)
} catch (e: Exception) {
// If parsing fails, return empty map
return emptyMap()
}
return modules
}
private fun extractModules(obj: JsonObject, modules: MutableMap<String, Module>) {
val key = obj.get("key")?.asString
val name = obj.get("name")?.asString
val version = obj.get("version")?.asString
val apparentName = obj.get("apparentName")?.asString
if (key != null && name != null && version != null && apparentName != null) {
modules[key] = Module(key, name, version, apparentName)
}
// Recursively extract from dependencies
obj.get("dependencies")?.asJsonArray?.forEach { dep ->
if (dep.isJsonObject) {
extractModules(dep.asJsonObject, modules)
}
}
}
/**
* Compares two module graphs and returns the keys of modules that changed.
*
* A module is considered changed if:
* - It exists in the new graph but not the old graph (added)
* - It exists in the old graph but not the new graph (removed)
* - It exists in both but has a different version
*
* @param oldGraph Module graph from the starting revision
* @param newGraph Module graph from the final revision
* @return Set of module keys that changed
*/
fun findChangedModules(
oldGraph: Map<String, Module>,
newGraph: Map<String, Module>
): Set<String> {
val changed = mutableSetOf<String>()
// Find added and version-changed modules
newGraph.forEach { (key, newModule) ->
val oldModule = oldGraph[key]
if (oldModule == null) {
// Module was added
changed.add(key)
} else if (oldModule.version != newModule.version) {
// Module version changed
changed.add(key)
}
}
// Find removed modules
oldGraph.keys.forEach { key ->
if (!newGraph.containsKey(key)) {
changed.add(key)
}
}
return changed
}
}