1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 08:27:12 -07:00

Added command "/v1/getDependencyGraph" for the IDEA plug-in.

This commit is contained in:
Cedric Beust 2016-07-31 23:11:26 -08:00
parent 5577c47185
commit c0b685a044
8 changed files with 232 additions and 35 deletions

View file

@ -14,21 +14,21 @@ open class TaskResult2<T>(success: Boolean, errorMessage: String?, val value: T)
override fun toString() = com.beust.kobalt.misc.toString("TaskResult", "value", value, "success", success)
}
class Node<T>(val value: T) {
override fun hashCode() = value!!.hashCode()
override fun equals(other: Any?) : Boolean {
val result = if (other is Node<*>) other.value == value else false
return result
}
override fun toString() = value.toString()
}
class DynamicGraph<T> {
val VERBOSE = 2
val values : Collection<T> get() = nodes.map { it.value }
val nodes = hashSetOf<Node<T>>()
private val dependedUpon = HashMultimap.create<Node<T>, Node<T>>()
private val dependingOn = HashMultimap.create<Node<T>, Node<T>>()
val nodes = hashSetOf<PrivateNode<T>>()
private val dependedUpon = HashMultimap.create<PrivateNode<T>, PrivateNode<T>>()
private val dependingOn = HashMultimap.create<PrivateNode<T>, PrivateNode<T>>()
class PrivateNode<T>(val value: T) {
override fun hashCode() = value!!.hashCode()
override fun equals(other: Any?) : Boolean {
val result = if (other is PrivateNode<*>) other.value == value else false
return result
}
override fun toString() = value.toString()
}
companion object {
fun <T> transitiveClosure(root: T, childrenFor: (T) -> List<T>) : List<T> {
@ -51,22 +51,50 @@ class DynamicGraph<T> {
}
return result
}
class Node<T>(val value: T, val children: List<Node<T>>) {
fun dump(root : Node<T> = this, indent: String = "") : String {
return StringBuffer().apply {
append(indent).append(root.value).append("\n")
root.children.forEach {
append(dump(it, indent + " "))
}
}.toString()
}
}
fun <T> transitiveClosureGraph(roots: List<T>, childrenFor: (T) -> List<T>) : List<Node<T>>
= roots.map { transitiveClosureGraph(it, childrenFor) }
fun <T> transitiveClosureGraph(root: T, childrenFor: (T) -> List<T>, seen: HashSet<T> = hashSetOf()) : Node<T> {
val children = arrayListOf<Node<T>>()
childrenFor(root).forEach { child ->
if (! seen.contains(child)) {
val c = transitiveClosureGraph(child, childrenFor)
children.add(c)
seen.add(child)
}
}
return Node(root, children)
}
}
fun childrenOf(v: T) : Collection<T> = dependedUpon[PrivateNode(v)].map { it.value }
fun transitiveClosure(root: T)
= transitiveClosure(root) { element -> dependedUpon[Node(element)].map { it.value } }
= transitiveClosure(root) { element -> dependedUpon[PrivateNode(element)].map { it.value } }
fun addNode(t: T) = synchronized(nodes) {
nodes.add(Node(t))
nodes.add(PrivateNode(t))
}
fun removeNode(t: T) = synchronized(nodes) {
log(VERBOSE, " Removing node $t")
Node(t).let { node ->
PrivateNode(t).let { node ->
nodes.remove(node)
dependingOn.removeAll(node)
val set = dependedUpon.keySet()
val toReplace = arrayListOf<Pair<Node<T>, Collection<Node<T>>>>()
val toReplace = arrayListOf<Pair<PrivateNode<T>, Collection<PrivateNode<T>>>>()
set.forEach { du ->
val l = ArrayList(dependedUpon[du])
l.remove(node)
@ -82,10 +110,10 @@ class DynamicGraph<T> {
* Make "from" depend on "to" ("from" is no longer free).
*/
fun addEdge(from: T, to: T) {
val fromNode = Node(from)
val fromNode = PrivateNode(from)
nodes.add(fromNode)
val toNode = Node(to)
nodes.add(Node(to))
val toNode = PrivateNode(to)
nodes.add(PrivateNode(to))
dependingOn.put(toNode, fromNode)
dependedUpon.put(fromNode, toNode)
}
@ -109,7 +137,7 @@ class DynamicGraph<T> {
fun dump() : String {
val result = StringBuffer()
result.append("************ Graph dump ***************\n")
val free = arrayListOf<Node<T>>()
val free = arrayListOf<PrivateNode<T>>()
nodes.forEach { node ->
val d = dependedUpon.get(node)
if (d == null || d.isEmpty()) {

View file

@ -2,7 +2,9 @@ package com.beust.kobalt.app.remote
import com.beust.kobalt.Args
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.api.Project
import com.beust.kobalt.app.BuildFileCompiler
import com.beust.kobalt.internal.DynamicGraph
import com.beust.kobalt.internal.PluginInfo
import com.beust.kobalt.internal.TaskManager
import com.beust.kobalt.internal.build.BuildFile
@ -24,8 +26,9 @@ interface IProgressListener {
class DependencyData @Inject constructor(val executors: KobaltExecutors, val dependencyManager: DependencyManager,
val buildFileCompilerFactory: BuildFileCompiler.IFactory, val pluginInfo: PluginInfo,
val taskManager: TaskManager) {
fun dependenciesDataFor(buildFilePath: String, args: Args, progressListener: IProgressListener? = null)
: GetDependenciesData {
fun dependenciesDataFor(buildFilePath: String, args: Args, progressListener: IProgressListener? = null,
useGraph : Boolean = false): GetDependenciesData {
val projectDatas = arrayListOf<ProjectData>()
fun toDependencyData(d: IClasspathDependency, scope: String): DependencyData {
@ -44,16 +47,48 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep
FileDependency(it.absolutePath)
}
fun compileDependencies(project: Project, name: String): List<DependencyData> {
val result =
(pluginDependencies +
allDeps(project.compileDependencies, name) +
allDeps(project.compileProvidedDependencies, name))
.map { toDependencyData(it, "compile") }
return result
}
fun toDependencyData2(scope: String, node: DynamicGraph.Companion.Node<IClasspathDependency>): DependencyData {
val d = node.value
val dep = dependencyManager.create(d.id)
return DependencyData(d.id, scope, dep.jarFile.get().absolutePath,
node.children.map { toDependencyData2(scope, it) })
}
fun compileDependenciesGraph(project: Project, name: String): List<DependencyData> {
val depLambda = { dep : IClasspathDependency -> dep.directDependencies() }
val result =
(DynamicGraph.Companion.transitiveClosureGraph(pluginDependencies, depLambda) +
DynamicGraph.Companion.transitiveClosureGraph(project.compileDependencies, depLambda) +
DynamicGraph.Companion.transitiveClosureGraph(project.compileProvidedDependencies, depLambda))
.map { toDependencyData2("compile", it)}
return result
}
fun testDependencies(project: Project, name: String): List<DependencyData> {
return allDeps(project.testDependencies, name).map { toDependencyData(it, "testCompile") }
}
fun testDependenciesGraph(project: Project, name: String): List<DependencyData> {
val depLambda = { dep : IClasspathDependency -> dep.directDependencies() }
return DynamicGraph.Companion.transitiveClosureGraph(project.testDependencies, depLambda)
.map { toDependencyData2("compile", it)}
}
val allTasks = hashSetOf<TaskData>()
projectResult.projects.withIndex().forEach { wi ->
val project = wi.value
val name = project.name
progressListener?.onProgress(message = "Synchronizing project ${project.name} "
+ (wi.index + 1) + "/" + projectResult.projects.size)
val compileDependencies = pluginDependencies.map { toDependencyData(it, "compile") } +
allDeps(project.compileDependencies, name).map { toDependencyData(it, "compile") } +
allDeps(project.compileProvidedDependencies, name).map { toDependencyData(it, "compile") }
val testDependencies = allDeps(project.testDependencies, name).map { toDependencyData(it, "testCompile") }
val dependentProjects = project.dependsOn.map { it.name }
@ -64,6 +99,13 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep
TaskData(it.name, it.doc, it.group)
}
allTasks.addAll(projectTasks)
val compileDependencies =
if (useGraph) compileDependenciesGraph(project, project.name)
else compileDependencies(project, project.name)
val testDependencies =
if (useGraph) testDependenciesGraph(project, project.name)
else testDependencies(project, project.name)
projectDatas.add(ProjectData(project.name, project.directory, dependentProjects,
compileDependencies, testDependencies,
sources.second.toSet(), tests.second.toSet(), sources.first.toSet(), tests.first.toSet(),
@ -77,7 +119,8 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep
// use these same classes.
//
class DependencyData(val id: String, val scope: String, val path: String)
class DependencyData(val id: String, val scope: String, val path: String,
val children: List<DependencyData> = emptyList())
data class TaskData(val name: String, val description: String, val group: String) {
override fun toString() = name
}

View file

@ -14,6 +14,7 @@ import javax.inject.Inject
* { "name" : "getDependencies", "buildFile": "/Users/beust/kotlin/kobalt/kobalt/src/Build.kt" }
* The response is a GetDependenciesData.
*/
@Deprecated(message = "Only used by old server, to be deleted")
class GetDependenciesCommand @Inject constructor(val args: Args, val dependencyData: DependencyData) : ICommand {
override val name = "getDependencies"

View file

@ -0,0 +1,100 @@
package com.beust.kobalt.app.remote
import com.beust.kobalt.Args
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.app.ProjectFinder
import com.beust.kobalt.internal.build.BuildFile
import com.beust.kobalt.internal.eventbus.ArtifactDownloadedEvent
import com.google.common.eventbus.EventBus
import com.google.common.eventbus.Subscribe
import com.google.gson.Gson
import org.eclipse.jetty.websocket.api.RemoteEndpoint
import org.eclipse.jetty.websocket.api.Session
import org.eclipse.jetty.websocket.api.WebSocketListener
import java.nio.file.Paths
/**
* Manage the websocket endpoint "/v1/getDependencyGraph".
*/
class GetDependencyGraphHandler : WebSocketListener {
// The SparkJava project refused to merge https://github.com/perwendel/spark/pull/383
// so I have to do dependency injections manually :-(
val projectFinder = Kobalt.INJECTOR.getInstance(ProjectFinder::class.java)
var session: Session? = null
override fun onWebSocketClose(code: Int, reason: String?) {
println("ON CLOSE $code reason: $reason")
}
override fun onWebSocketError(cause: Throwable?) {
cause?.printStackTrace()
throw UnsupportedOperationException()
}
fun <T> sendWebsocketCommand(endpoint: RemoteEndpoint, commandName: String, payload: T) {
endpoint.sendString(Gson().toJson(WebSocketCommand(commandName, payload = Gson().toJson(payload))))
}
override fun onWebSocketConnect(s: Session) {
session = s
val buildFileParams = s.upgradeRequest.parameterMap["buildFile"]
if (buildFileParams != null) {
val buildFile = buildFileParams[0]
fun <T> getInstance(cls: Class<T>) : T = Kobalt.INJECTOR.getInstance(cls)
val result = if (buildFile != null) {
// Track all the downloads that this dependency call might trigger and
// send them as a progress message to the web socket
val eventBus = getInstance(EventBus::class.java)
val busListener = object {
@Subscribe
fun onArtifactDownloaded(event: ArtifactDownloadedEvent) {
sendWebsocketCommand(s.remote, ProgressCommand.NAME,
ProgressCommand(null, "Downloaded " + event.artifactId))
}
}
eventBus.register(busListener)
// Get the dependencies for the requested build file and send progress to the web
// socket for each project
try {
val dependencyData = getInstance(DependencyData::class.java)
val args = getInstance(Args::class.java)
val allProjects = projectFinder.initForBuildFile(BuildFile(Paths.get(buildFile), buildFile),
args)
dependencyData.dependenciesDataFor(buildFile, args, object : IProgressListener {
override fun onProgress(progress: Int?, message: String?) {
sendWebsocketCommand(s.remote, ProgressCommand.NAME, ProgressCommand(progress, message))
}
}, useGraph = true)
} catch(ex: Throwable) {
ex.printStackTrace()
val errorMessage = ex.stackTrace.map { it.toString() }.joinToString("\n<p>")
DependencyData.GetDependenciesData(errorMessage = errorMessage)
} finally {
SparkServer.cleanUpCallback()
eventBus.unregister(busListener)
}
} else {
DependencyData.GetDependenciesData(
errorMessage = "buildFile wasn't passed in the query parameter")
}
sendWebsocketCommand(s.remote, DependencyData.GetDependenciesData.NAME, result)
s.close()
}
}
override fun onWebSocketText(message: String?) {
println("RECEIVED TEXT: $message")
session?.remote?.sendString("Response: $message")
}
override fun onWebSocketBinary(payload: ByteArray?, offset: Int, len: Int) {
println("RECEIVED BINARY: $payload")
}
}

View file

@ -4,16 +4,16 @@ import com.beust.kobalt.Args
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.app.MainModule
import com.beust.kobalt.internal.KobaltSettings
import com.beust.kobalt.internal.remote.ICommand
import com.google.gson.Gson
enum class Command(val n: Int, val command: ICommand) {
GET_DEPENDENCIES(1, Kobalt.INJECTOR.getInstance(GetDependenciesCommand::class.java));
companion object {
val commandMap = hashMapOf<Int, ICommand>()
fun commandFor(n: Int) = values().filter { it.n == n }[0].command
}
}
//enum class Command(val n: Int, val command: ICommand) {
// GET_DEPENDENCIES(1, Kobalt.INJECTOR.getInstance(GetDependenciesCommand::class.java)),
// GET_DEPENDENCIES_GRAPH(2, Kobalt.INJECTOR.getInstance(GetDependenciesGraphCommand::class.java));
// companion object {
// val commandMap = hashMapOf<Int, ICommand>()
// fun commandFor(n: Int) = values().filter { it.n == n }[0].command
// }
//}
class KobaltHub(val dependencyData: DependencyData) {
val args = Args()
@ -23,6 +23,9 @@ class KobaltHub(val dependencyData: DependencyData) {
when(n) {
1 -> Gson().toJson(
dependencyData.dependenciesDataFor("/Users/beust/kotlin/klaxon/kobalt/src/Build.kt", args))
2 -> Gson().toJson(
dependencyData.dependenciesDataFor("/Users/beust/kotlin/klaxon/kobalt/src/Build.kt", args,
useGraph = true))
else -> throw RuntimeException("Unknown command")
}
println("Data: $data")

View file

@ -15,6 +15,7 @@ import java.io.PrintWriter
import java.net.ServerSocket
import java.net.SocketException
@Deprecated(message = "Replaced by Websocket server, to be deleted")
class OldServer(val initCallback: (String) -> List<Project>, val cleanUpCallback: () -> Unit)
: KobaltServer.IServer, ICommandSender {
val pending = arrayListOf<CommandData>()

View file

@ -49,6 +49,7 @@ class SparkServer(val initCallback: (String) -> List<Project>, val cleanUpCallba
log.debug("RUNNING")
Spark.port(port)
Spark.webSocket("/v1/getDependencies", GetDependenciesHandler::class.java)
Spark.webSocket("/v1/getDependencyGraph", GetDependencyGraphHandler::class.java)
Spark.get("/ping", { req, res -> """ { "result" : "ok" } """ })
Spark.get("/quit", { req, res ->
Executors.newFixedThreadPool(1).let { executor ->
@ -96,6 +97,7 @@ class SparkServer(val initCallback: (String) -> List<Project>, val cleanUpCallba
/**
* Manage the websocket endpoint "/v1/getDependencies".
*/
@Deprecated(message = "Replaced with GetDependencyGraphHandler")
class GetDependenciesHandler : WebSocketListener {
// The SparkJava project refused to merge https://github.com/perwendel/spark/pull/383
// so I have to do dependency injections manually :-(

View file

@ -185,4 +185,23 @@ class DynamicGraphTest {
}
}
@Test
fun transitiveClosureGraphTest() {
val graph = DynamicGraph<String>().apply {
// a -> b
// b -> c, d
// e
addEdge("a", "b")
addEdge("b", "c")
addEdge("b", "d")
addNode("e")
}
val closure = DynamicGraph.transitiveClosureGraph("a", { s -> graph.childrenOf(s).toList() } )
assertThat(closure.value).isEqualTo("a")
val ca = closure.children
assertThat(ca.map { it.value }).isEqualTo(listOf("b"))
val cb = ca[0].children
assertThat(cb.map { it.value }).isEqualTo(listOf("d", "c"))
}
}