diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 44e62788e..a356bb606 100755
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -51,5 +51,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/data/common/tooling-api-all.jar b/app/src/main/assets/data/common/tooling-api-all.jar
new file mode 100644
index 000000000..47b5186c2
Binary files /dev/null and b/app/src/main/assets/data/common/tooling-api-all.jar differ
diff --git a/build-tools/tooling-api-impl/build.gradle b/build-tools/tooling-api-impl/build.gradle
index ee70c2732..c1625f6cf 100644
--- a/build-tools/tooling-api-impl/build.gradle
+++ b/build-tools/tooling-api-impl/build.gradle
@@ -16,6 +16,7 @@
*/
plugins {
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
}
@@ -27,17 +28,42 @@ java {
jar {
manifest {
- attributes(
+ attributes (
'Main-Class': 'com.itsaky.androidide.tooling.impl.Main'
)
}
}
+shadowJar {
+ archiveBaseName.set('tooling-api')
+ archiveClassifier.set('all')
+}
+
+task copyJarToAssets (type: Copy) {
+ from project.file ("${project.buildDir}/libs/tooling-api-all.jar")
+ into project.rootProject.file ("app/src/main/assets/data/common/")
+}
+
+project.tasks.jar.finalizedBy shadowJar
+project.tasks.shadowJar.finalizedBy copyJarToAssets
+
+repositories {
+ maven {
+ url 'https://repo.gradle.org/gradle/libs-releases/'
+ }
+}
+
dependencies {
implementation project (path: ':build-tools:logger')
implementation project (path: ':build-tools:tooling-api')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+ implementation 'org.gradle:gradle-tooling-api:7.3.3'
+
+ testImplementation tests.junit
+ testImplementation tests.google_truth
+
+ runtimeOnly 'org.slf4j:slf4j-api:1.7.30'
}
compileKotlin {
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java
index 7a843119c..5b2dab820 100644
--- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java
@@ -17,5 +17,32 @@
package com.itsaky.androidide.tooling.impl;
+import static com.itsaky.androidide.utils.ILogger.newInstance;
+
+import com.itsaky.androidide.tooling.api.IToolingApiClient;
+import com.itsaky.androidide.utils.ILogger;
+
+import org.eclipse.lsp4j.jsonrpc.Launcher;
+
+import java.util.concurrent.ExecutionException;
+
public class Main {
-}
\ No newline at end of file
+ private static final ILogger LOG = newInstance("ToolingApiMain");
+ public final Object lock = new Object();
+
+ public static void main(String[] args) {
+ LOG.debug("Starting Tooling API server...");
+ final var server = new ToolingApiServerImpl();
+ final var launcher =
+ Launcher.createLauncher(server, IToolingApiClient.class, System.in, System.out);
+ final var future = launcher.startListening();
+ server.connect(launcher.getRemoteProxy());
+
+ LOG.debug("Server started. Will run until shutdown message is received...");
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.error("An error occurred while waiting for shutdown message", e);
+ }
+ }
+}
diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt
new file mode 100644
index 000000000..c535c4daa
--- /dev/null
+++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt
@@ -0,0 +1,56 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.impl
+
+import com.itsaky.androidide.tooling.api.IToolingApiClient
+import com.itsaky.androidide.tooling.api.IToolingApiServer
+import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
+import com.itsaky.androidide.tooling.api.messages.ServerInitializedResponse
+import com.itsaky.androidide.utils.ILogger
+import java.util.concurrent.*
+import org.eclipse.lsp4j.jsonrpc.CompletableFutures
+
+/**
+ * Implementation for the Gradle Tooling API server.
+ *
+ * @author Akash Yadav
+ */
+internal class ToolingApiServerImpl : IToolingApiServer {
+
+ private var initialized = false
+ private var client: IToolingApiClient? = null
+ private val log = ILogger.newInstance(javaClass.simpleName)
+
+ override fun initialize(
+ params: InitializeProjectParams
+ ): CompletableFuture {
+ return CompletableFutures.computeAsync {
+ initialized = true
+ log.debug("Tooling API server initialized")
+ return@computeAsync ServerInitializedResponse()
+ }
+ }
+
+ override fun isInitialized(): CompletableFuture {
+ return CompletableFuture.supplyAsync { initialized }
+ }
+
+ fun connect(client: IToolingApiClient) {
+ this.client = client
+ }
+}
diff --git a/build-tools/tooling-api-impl/src/test/java/ToolingApiImplTest.kt b/build-tools/tooling-api-impl/src/test/java/ToolingApiImplTest.kt
new file mode 100644
index 000000000..ae6f3f8b2
--- /dev/null
+++ b/build-tools/tooling-api-impl/src/test/java/ToolingApiImplTest.kt
@@ -0,0 +1,74 @@
+import com.google.common.truth.Truth.assertThat
+import com.itsaky.androidide.tooling.api.IToolingApiClient
+import com.itsaky.androidide.tooling.api.IToolingApiServer
+import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
+import com.itsaky.androidide.tooling.api.messages.ProjectInitializedParams
+import com.itsaky.androidide.utils.ILogger
+import java.io.BufferedReader
+import java.io.File
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.io.OutputStream
+import org.eclipse.lsp4j.jsonrpc.Launcher
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/** @author Akash Yadav */
+@RunWith(JUnit4::class)
+class ToolingApiImplTest {
+
+ private val log = ILogger.newInstance(javaClass.simpleName)
+
+ @Test
+ fun testLaunch() {
+ val streams = launchServer()
+ val client = TestClient()
+ val launcher =
+ Launcher.createLauncher(
+ client, IToolingApiServer::class.java, streams.first, streams.second)
+
+ launcher.startListening()
+
+ val server = launcher.remoteProxy
+
+ server.initialize(InitializeProjectParams(File("."))).get()
+
+ val isInitialized = server.isInitialized().get()
+
+ log.debug("Is server initialized: $isInitialized")
+ assertThat(isInitialized).isTrue()
+ }
+
+ private fun launchServer(): Pair {
+ val builder = ProcessBuilder("java", "-jar", "./build/libs/tooling-api-all.jar")
+ val proc = builder.start()
+
+ Thread(Reader(proc.errorStream)).start()
+
+ return Pair(proc.inputStream, proc.outputStream)
+ }
+
+ private class TestClient : IToolingApiClient {
+ private val log = ILogger.newInstance(javaClass.simpleName)
+ override fun onProjectInitialized(params: ProjectInitializedParams) {
+ log.logThis()
+ }
+ }
+
+ private class Reader(val input: InputStream) : Runnable {
+ private val log = ILogger.newInstance(javaClass.simpleName)
+ override fun run() {
+ try {
+ val reader = BufferedReader(InputStreamReader(input))
+ var line = reader.readLine()
+ while (line != null) {
+ log.debug(line)
+ line = reader.readLine()
+ }
+ } catch (error: Throwable) {
+ log.error(error)
+ }
+ }
+ }
+}
diff --git a/build-tools/tooling-api/build.gradle b/build-tools/tooling-api/build.gradle
index 610912484..15a3d8693 100644
--- a/build-tools/tooling-api/build.gradle
+++ b/build-tools/tooling-api/build.gradle
@@ -28,6 +28,12 @@ java {
dependencies {
implementation project (path: ':build-tools:logger')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
+
+ testImplementation tests.junit
+ testImplementation tests.google_truth
+
+ api google.gson
+ api 'org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.12.0'
}
repositories {
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiClient.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiClient.kt
new file mode 100644
index 000000000..a1ca5feb0
--- /dev/null
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiClient.kt
@@ -0,0 +1,34 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api
+
+import com.itsaky.androidide.tooling.api.messages.ProjectInitializedParams
+import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
+import org.eclipse.lsp4j.jsonrpc.services.JsonSegment
+
+/**
+ * A client consumes services provided by [IToolingApiServer].
+ *
+ * @author Akash Yadav
+ */
+@JsonSegment("client")
+interface IToolingApiClient {
+
+ @JsonNotification("projectInitialized")
+ fun onProjectInitialized(params: ProjectInitializedParams)
+}
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt
new file mode 100644
index 000000000..ee547b8b0
--- /dev/null
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt
@@ -0,0 +1,38 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api
+
+import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams
+import com.itsaky.androidide.tooling.api.messages.ServerInitializedResponse
+import java.util.concurrent.*
+import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
+import org.eclipse.lsp4j.jsonrpc.services.JsonSegment
+
+/**
+ * A tooling api server provides services related to the Gradle Tooling API.
+ *
+ * @author Akash Yadav
+ */
+@JsonSegment("server")
+interface IToolingApiServer {
+
+ @JsonRequest("initProject")
+ fun initialize(params: InitializeProjectParams): CompletableFuture
+
+ @JsonRequest("isInitialized") fun isInitialized(): CompletableFuture
+}
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProject.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectParams.kt
similarity index 80%
rename from build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProject.kt
rename to build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectParams.kt
index 9310a9cb3..bd7633b90 100644
--- a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProject.kt
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectParams.kt
@@ -17,10 +17,11 @@
package com.itsaky.androidide.tooling.api.messages
-import com.itsaky.androidide.tooling.api.Message
import java.io.File
/**
- * Message sent from client to server to
- * @author Akash Yadav */
-data class InitializeProject(val directory: File) : Message() {}
+ * Message sent from client to server to initialize the tooling API client in the given directory.
+ *
+ * @author Akash Yadav
+ */
+data class InitializeProjectParams(val directory: File)
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ProjectInitializedParams.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ProjectInitializedParams.kt
new file mode 100644
index 000000000..c896c0bcd
--- /dev/null
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ProjectInitializedParams.kt
@@ -0,0 +1,30 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api.messages
+
+import com.itsaky.androidide.tooling.api.model.ProjectDependency
+
+/**
+ * Parameters passed to [com.itsaky.androidide.tooling.api.IToolingApiClient.onProjectInitialized]
+ * to notify that the project has been initialized.
+ *
+ * @author Akash Yadav
+ */
+class ProjectInitializedParams {
+ private val dependencies = mutableListOf()
+}
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/Message.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ServerInitializedResponse.kt
similarity index 72%
rename from build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/Message.kt
rename to build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ServerInitializedResponse.kt
index a1384334d..be7da4bfa 100644
--- a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/Message.kt
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ServerInitializedResponse.kt
@@ -15,19 +15,11 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.tooling.api
-
-import com.itsaky.androidide.utils.ILogger
+package com.itsaky.androidide.tooling.api.messages
/**
- * A message sent from/to client to/from server.
+ * Notification sent by server to notify client that the server has been initialized.
*
* @author Akash Yadav
*/
-abstract class Message {
- protected val log = ILogger.newInstance(javaClass.simpleName)
-
- fun log () {
- log.debug(this)
- }
-}
+class ServerInitializedResponse {}
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ShutDownMessage.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ShutDownMessage.kt
new file mode 100644
index 000000000..662965cec
--- /dev/null
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/ShutDownMessage.kt
@@ -0,0 +1,25 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api.messages
+
+/**
+ * Notifies that the server should shutdown.
+ *
+ * @author Akash Yadav
+ */
+data class ShutDownMessage(val cancelRunningBuilds: Boolean = true) {}
diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/model/ProjectDependency.kt b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/model/ProjectDependency.kt
new file mode 100644
index 000000000..2fb33ec19
--- /dev/null
+++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/model/ProjectDependency.kt
@@ -0,0 +1,36 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api.model
+
+import java.io.File
+
+/**
+ * A project dependency. Holds data about the jar, sources and javadoc files of a dependency. Also
+ * some other useful metadata.
+ *
+ * @author Akash Yadav
+ */
+class ProjectDependency(
+ private val group: String,
+ private val artifact: String,
+ private val version: String,
+ private val jar: File
+) {
+ private val sourcesJar: File? = null
+ private val javadocJar: File? = null
+}
diff --git a/build-tools/tooling-api/src/test/java/com/itsaky/androidide/tooling/api/MessageTest.kt b/build-tools/tooling-api/src/test/java/com/itsaky/androidide/tooling/api/MessageTest.kt
new file mode 100644
index 000000000..6958c26f4
--- /dev/null
+++ b/build-tools/tooling-api/src/test/java/com/itsaky/androidide/tooling/api/MessageTest.kt
@@ -0,0 +1,67 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.tooling.api
+
+import com.google.common.truth.Truth.assertThat
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.itsaky.androidide.utils.ILogger
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/** @author Akash Yadav */
+@RunWith(JUnit4::class)
+class MessageTest {
+
+ private val log = ILogger.newInstance(javaClass.simpleName)
+
+ @Test
+ fun testStringToMessage() {
+ val message =
+ "{'name': 'My name', 'otherName': 'Other other name', 'className': 'com.itsaky.androidide.tooling.api.MessageTest\$MessageToString'}"
+ val obj = Message.fromJson(message)
+ assertThat(obj).isInstanceOf(MessageToString::class.java)
+ assertThat((obj as MessageToString).name).isEqualTo("My name")
+ assertThat(obj.otherName).isEqualTo("Other other name")
+
+ log.debug("obj.name is ${obj.name}")
+ }
+
+ @Test
+ fun testMessageToString() {
+ val message = MessageToString()
+ val string = message.asJsonString()
+ log.debug("MessageToString:$string")
+ assertThat(string)
+ .isEqualTo(
+ "{\"name\":\"Your name\",\"otherName\":\"Other name\",\"className\":\"com.itsaky.androidide.tooling.api.MessageTest\$MessageToString\"}")
+ assertThat((JsonParser.parseString(string) as JsonObject)["name"].asString)
+ .isEqualTo("Your name")
+ assertThat((JsonParser.parseString(string) as JsonObject)["otherName"].asString)
+ .isEqualTo("Other name")
+ }
+
+ private open class Parent: Message() {
+ val otherName: String = "Other name"
+ }
+
+ private class MessageToString : Parent() {
+ val name: String = "Your name"
+ }
+}