From de5e55c86e623d9a246309f7a0c1c65f4a664296 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 3 Aug 2023 19:33:16 +0530 Subject: [PATCH] feat(tooling-api): new tooling API model implementation --- .idea/compiler.xml | 6 +- .../services/builder/GradleBuildService.java | 2 +- .../com/itsaky/androidide/lsp/api/LSPTest.kt | 5 +- settings.gradle.kts | 2 +- .../projects/api/ModuleProjectTest.kt | 2 +- .../itsaky/androidide/tooling/impl/Main.java | 6 +- .../tooling/impl/ToolingApiServerImpl.kt | 40 +- .../impl/internal/AndroidProjectImpl.kt | 226 +++++ .../impl/internal/GradleProjectImpl.kt | 69 ++ .../tooling/impl/internal/JavaProjectImpl.kt | 53 + .../tooling/impl/internal/ProjectImpl.kt | 103 ++ .../internal/forwarding/ForwardingProject.kt | 115 +++ .../impl/model/InternalForwardingProject.kt | 106 -- .../tooling/impl/sync/AbstractModelBuilder.kt | 106 ++ .../impl/sync/AndroidProjectModelBuilder.kt | 49 + .../impl/sync/GradleProjectModelBuilder.kt | 34 + .../tooling/impl/sync/IModelBuilder.kt | 38 + .../impl/sync/JavaProjectModelBuilder.kt | 34 + .../impl/sync/ModelBuilderException.java | 41 + .../impl/sync/ModuleProjectModelBuilder.kt | 36 + .../sync/NotAnAndroidProjectException.java | 35 + .../tooling/impl/sync/RootModelBuilder.kt | 96 ++ .../androidide/tooling/impl/sync/types.kt | 26 + .../tooling/impl/util/AnnotationUtil.java | 160 +++ .../tooling/impl/util/ProjectReader.java | 913 ++++++++---------- .../impl/MultiModuleAndroidProjectTest.kt | 161 +-- .../tooling-api-model/build.gradle.kts | 90 +- .../libs/builder-model-7.2.0-rc01.jar | Bin 109871 -> 0 bytes .../AndroidModule.kt => IAndroidProject.kt} | 379 ++++---- .../androidide/tooling/api/IGradleProject.kt | 53 + .../ModuleProject.kt => IJavaProject.kt} | 91 +- .../androidide/tooling/api/IModuleProject.kt | 40 + .../androidide/tooling/api/IProject.java | 126 --- .../itsaky/androidide/tooling/api/IProject.kt | 47 + .../androidide/tooling/api/IProjectQueries.kt | 81 ++ .../androidide/tooling/api/ProjectType.kt | 44 + .../tooling/api/model/IdeGradleProject.kt | 141 --- .../tooling/api/model/JavaModule.kt | 75 -- .../tooling/api/model/util/ProjectBuilder.kt | 118 --- .../AndroidArtifactMetadata.kt} | 88 +- .../api/models/AndroidProjectMetadata.kt | 82 ++ .../AndroidVariantMetadata.kt} | 68 +- .../BasicAndroidVariantMetadata.kt} | 61 +- .../BasicProjectMetadata.kt} | 76 +- .../api/{model => models}/GradleArtifact.kt | 4 +- .../api/{model => models}/GradleTask.kt | 74 +- .../api/{model => models}/JavaContentRoot.kt | 2 +- .../JavaModuleCompilerSettings.kt | 4 +- .../{model => models}/JavaModuleDependency.kt | 2 +- .../JavaModuleExternalDependency.kt | 2 +- .../JavaModuleProjectDependency.kt | 2 +- .../{model => models}/JavaSourceDirectory.kt | 2 +- .../tooling/api/models/Launchable.kt} | 63 +- .../tooling/api/models/ProjectMetadata.kt | 39 + .../params/StringParameter.kt} | 46 +- .../api/models/result/SelectProjectResult.kt | 27 + .../util/AndroidModulePropertyCopier.kt | 788 ++++++--------- .../tooling/api/{model => }/util/utils.kt | 12 +- .../tooling/testing/ToolingApiTestLauncher.kt | 1 + .../tooling/api/IToolingApiServer.kt | 3 +- .../api/messages/InitializeProjectMessage.kt | 2 +- .../tooling/api/util/ToolingApiLauncher.java | 240 ----- .../tooling/api/util/ToolingApiLauncher.kt | 227 +++++ .../androidide/inflater/XmlInflaterTest.kt | 2 +- 64 files changed, 3230 insertions(+), 2436 deletions(-) create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/AndroidProjectImpl.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/GradleProjectImpl.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/JavaProjectImpl.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/forwarding/ForwardingProject.kt delete mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/GradleProjectModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/IModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/JavaProjectModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModelBuilderException.java create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/NotAnAndroidProjectException.java create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/AnnotationUtil.java delete mode 100644 subprojects/tooling-api-model/libs/builder-model-7.2.0-rc01.jar rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model/AndroidModule.kt => IAndroidProject.kt} (56%) create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IGradleProject.kt rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model/ModuleProject.kt => IJavaProject.kt} (52%) create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IModuleProject.kt delete mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProjectQueries.kt create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/ProjectType.kt delete mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt delete mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModule.kt delete mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{messages/result/SimpleArtifact.kt => models/AndroidArtifactMetadata.kt} (88%) create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidProjectMetadata.kt rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{messages/result/SimpleVariantData.kt => models/AndroidVariantMetadata.kt} (66%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model/IdeLaunchable.kt => models/BasicAndroidVariantMetadata.kt} (71%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{messages/result/SimpleModuleData.kt => models/BasicProjectMetadata.kt} (66%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/GradleArtifact.kt (92%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/GradleTask.kt (71%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/JavaContentRoot.kt (92%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/JavaModuleCompilerSettings.kt (96%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/JavaModuleDependency.kt (92%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/JavaModuleExternalDependency.kt (93%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/JavaModuleProjectDependency.kt (93%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => models}/JavaSourceDirectory.kt (92%) rename subprojects/{tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt => tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/Launchable.kt} (62%) create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/ProjectMetadata.kt rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{messages/VariantDataRequest.kt => models/params/StringParameter.kt} (77%) create mode 100644 subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/SelectProjectResult.kt rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => }/util/AndroidModulePropertyCopier.kt (62%) rename subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/{model => }/util/utils.kt (88%) delete mode 100644 subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java create mode 100644 subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 0c719c9df..a4d6bfde3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -122,9 +122,9 @@ - - - + + + diff --git a/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.java b/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.java index 6319691d3..6f8ad7a75 100644 --- a/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.java +++ b/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.java @@ -403,7 +403,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin @Override public CompletableFuture initializeProject(@NonNull String rootDir) { checkServerStarted(); - final var message = new InitializeProjectMessage(rootDir, getGradleInstallationDir()); + final var message = new InitializeProjectMessage(rootDir, "debug", getGradleInstallationDir()); return performBuildTasks(server.initialize(message)); } diff --git a/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt b/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt index 01319620a..e43b95f6b 100644 --- a/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt +++ b/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt @@ -37,7 +37,6 @@ import com.itsaky.androidide.tooling.api.IProject import com.itsaky.androidide.tooling.api.IToolingApiServer import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher -import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher.MultiVersionTestClient import com.itsaky.androidide.utils.Environment import com.itsaky.androidide.utils.FileProvider import com.itsaky.androidide.utils.ILogger @@ -52,7 +51,6 @@ import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config import java.io.File import java.nio.file.Path -import kotlin.io.path.pathString /** * Runs tests for a language server. @@ -94,7 +92,8 @@ abstract class LSPTest { Lookup.getDefault().update(BuildService.KEY_PROJECT_PROXY, project) server - .initialize(InitializeProjectMessage(FileProvider.testProjectRoot().toFile().absolutePath)) + .initialize(InitializeProjectMessage(FileProvider.testProjectRoot().toFile().absolutePath, + "debug")) .get() Environment.ANDROID_JAR = FileProvider.resources().resolve("android.jar").toFile() diff --git a/settings.gradle.kts b/settings.gradle.kts index 9f13c4414..8d45d11c4 100755 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -96,4 +96,4 @@ include( ":subprojects:tooling-api-testing", ":subprojects:xml-dom", ":subprojects:xml-utils" -) \ No newline at end of file +) diff --git a/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt b/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt index 74c79daf0..655a3d6de 100644 --- a/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt +++ b/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt @@ -45,7 +45,7 @@ class ModuleProjectTest { @Test fun test() { val (server, project) = ToolingApiTestLauncher().launchServer() - server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get() + server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString, "debug")).get() Lookup.getDefault().register(BuildService.KEY_PROJECT_PROXY, project) diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java index 99ef6757d..3c8995273 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/Main.java @@ -20,10 +20,10 @@ package com.itsaky.androidide.tooling.impl; import static com.itsaky.androidide.utils.ILogger.newInstance; import com.itsaky.androidide.models.LogLine; -import com.itsaky.androidide.tooling.api.IProject; +import com.itsaky.androidide.tooling.api.IGradleProject; import com.itsaky.androidide.tooling.api.IToolingApiClient; import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher; -import com.itsaky.androidide.tooling.impl.model.InternalForwardingProject; +import com.itsaky.androidide.tooling.impl.internal.ProjectImpl; import com.itsaky.androidide.tooling.impl.progress.ForwardingProgressListener; import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.JvmLogger; @@ -51,7 +51,7 @@ public class Main { public static void main(String[] args) { LOG.debug("Starting Tooling API server..."); - final var project = new InternalForwardingProject(null, IProject.FILE_PATH_NOT_AVAILABLE); + final var project = new ProjectImpl(); final var server = new ToolingApiServerImpl(project); final var launcher = ToolingApiLauncher.newServerLauncher(server, project, System.in, System.out); diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt index 922741ecd..91614bf49 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt @@ -18,6 +18,7 @@ package com.itsaky.androidide.tooling.impl import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues +import com.itsaky.androidide.tooling.api.IProject import com.itsaky.androidide.tooling.api.IToolingApiClient import com.itsaky.androidide.tooling.api.IToolingApiServer import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage @@ -38,13 +39,12 @@ import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Fai import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_BUILD_ARGUMENT import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_CONFIGURATION import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_GRADLE_VERSION -import com.itsaky.androidide.tooling.api.model.IdeGradleProject -import com.itsaky.androidide.tooling.impl.model.InternalForwardingProject -import com.itsaky.androidide.tooling.impl.util.ProjectReader +import com.itsaky.androidide.tooling.api.models.params.StringParameter +import com.itsaky.androidide.tooling.impl.internal.ProjectImpl +import com.itsaky.androidide.tooling.impl.sync.ModelBuilderException +import com.itsaky.androidide.tooling.impl.sync.RootModelBuilder import com.itsaky.androidide.tooling.impl.util.StopWatch import com.itsaky.androidide.utils.ILogger -import java.io.File -import java.util.concurrent.* import org.eclipse.lsp4j.jsonrpc.CompletableFutures import org.gradle.tooling.BuildCancelledException import org.gradle.tooling.BuildException @@ -54,30 +54,29 @@ import org.gradle.tooling.GradleConnector import org.gradle.tooling.UnsupportedVersionException import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException +import java.io.File +import java.util.concurrent.* /** * Implementation for the Gradle Tooling API server. * * @author Akash Yadav */ -internal class ToolingApiServerImpl(private val forwardingProject: InternalForwardingProject) : +internal class ToolingApiServerImpl(private val project: ProjectImpl) : IToolingApiServer { private var initialized = false private var client: IToolingApiClient? = null private var connector: GradleConnector? = null - private var project: IdeGradleProject? = null private var buildCancellationToken: CancellationTokenSource? = null private val log = ILogger.newInstance(javaClass.simpleName) override fun initialize(params: InitializeProjectMessage): CompletableFuture { - forwardingProject.projectPath = params.directory return CompletableFutures.computeAsync { try { if (initialized && connector != null) { connector?.disconnect() connector = null - project = null } Main.checkGradleWrapper() @@ -106,14 +105,16 @@ internal class ToolingApiServerImpl(private val forwardingProject: InternalForwa stopWatch.lapFromLast("Project connection established") val issues: MutableMap = mutableMapOf() - this.project = ProjectReader.read(connection, issues) - stopWatch.lapFromLast("Project read ${if(this.project == null) "failed" else "successful"}") + val project = RootModelBuilder.build(connection to "debug") as? ProjectImpl? + project ?: throw ModelBuilderException("Failed to build project model") + + this.project.rootProject = project.rootProject + this.project.projects = project.projects + stopWatch.lapFromLast("Project read successful") connection.close() stopWatch.log() - this.forwardingProject.project = this.project - initialized = true notifyBuildSuccess(emptyList()) @@ -131,7 +132,7 @@ internal class ToolingApiServerImpl(private val forwardingProject: InternalForwa return CompletableFuture.supplyAsync { initialized } } - override fun getRootProject(): CompletableFuture { + override fun getRootProject(): CompletableFuture { return CompletableFutures.computeAsync { assertProjectInitialized() return@computeAsync this.project @@ -150,11 +151,12 @@ internal class ToolingApiServerImpl(private val forwardingProject: InternalForwa projectPath = ":" } - val project = - this.project!!.findByPath(projectPath).get() - ?: return@computeAsync TaskExecutionResult(false, PROJECT_NOT_FOUND) + val project = this.project!!.run { + selectProject(StringParameter(projectPath)).get() + asGradleProject() + } ?: return@computeAsync TaskExecutionResult(false, PROJECT_NOT_FOUND) - this.connector!!.forProjectDirectory(project.projectDir) + this.connector!!.forProjectDirectory(project.getMetadata().get().projectDir) setupConnectorForGradleInstallation(this.connector!!, message.gradleInstallation) val connection = this.connector!!.connect() @@ -250,9 +252,7 @@ internal class ToolingApiServerImpl(private val forwardingProject: InternalForwa this.connector = null this.client = null - this.project = null this.buildCancellationToken = null - this.forwardingProject.project = null Main.future = null Main.client = null diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/AndroidProjectImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/AndroidProjectImpl.kt new file mode 100644 index 000000000..9615b7000 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/AndroidProjectImpl.kt @@ -0,0 +1,226 @@ +/* + * 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.internal + +import com.android.builder.model.v2.ide.AndroidArtifact +import com.android.builder.model.v2.ide.GraphItem +import com.android.builder.model.v2.ide.Library +import com.android.builder.model.v2.ide.Variant +import com.android.builder.model.v2.models.AndroidProject +import com.android.builder.model.v2.models.BasicAndroidProject +import com.android.builder.model.v2.models.VariantDependencies +import com.itsaky.androidide.builder.model.DefaultLibrary +import com.itsaky.androidide.builder.model.DefaultModelSyncFile +import com.itsaky.androidide.builder.model.DefaultSourceProvider +import com.itsaky.androidide.builder.model.DefaultSourceSetContainer +import com.itsaky.androidide.builder.model.DefaultViewBindingOptions +import com.itsaky.androidide.builder.model.UNKNOWN_PACKAGE +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.api.models.AndroidArtifactMetadata +import com.itsaky.androidide.tooling.api.models.AndroidProjectMetadata +import com.itsaky.androidide.tooling.api.models.AndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.ProjectMetadata +import com.itsaky.androidide.tooling.api.models.params.StringParameter +import com.itsaky.androidide.tooling.api.util.AndroidModulePropertyCopier +import com.itsaky.androidide.tooling.api.util.AndroidModulePropertyCopier.copy +import com.itsaky.androidide.tooling.api.util.extractPackageName +import org.gradle.tooling.model.GradleProject +import java.io.File +import java.io.Serializable +import java.util.concurrent.CompletableFuture + +/** + * @author Akash Yadav + */ +internal class AndroidProjectImpl( + gradleProject: GradleProject, + private val variant: String, + private val basicAndroidProject: BasicAndroidProject, + private val androidProject: AndroidProject, + private val variantDependencies: VariantDependencies +) : GradleProjectImpl(gradleProject), IAndroidProject, Serializable { + + private val serialVersionUID = 1L + + private var shouldLookupPackage = true + var packageName: String = UNKNOWN_PACKAGE + get() { + if (field == UNKNOWN_PACKAGE && shouldLookupPackage) { + findPackageName() + } + + return field + } + + + + override fun getVariants(): CompletableFuture> { + return CompletableFuture.supplyAsync { + androidProject.variants.map { + BasicAndroidVariantMetadata(it.name, it.mainArtifact.toMetadata(it.name)) + } + } + } + + private fun AndroidArtifact.toMetadata(name: String): AndroidArtifactMetadata { + return AndroidArtifactMetadata( + name = name, + resGenTaskName = resGenTaskName, + assembleTaskOutputListingFile = assembleTaskOutputListingFile, + generatedResourceFolders = generatedResourceFolders, + generatedSourceFolders = generatedSourceFolders, maxSdkVersion = maxSdkVersion, + minSdkVersion = minSdkVersion.apiLevel, signingConfigName = signingConfigName, + sourceGenTaskName = sourceGenTaskName, assembleTaskName = assembleTaskName, + classJars = classesFolders.filter { it.name.endsWith(".jar") }, + compileTaskName = compileTaskName, + targetSdkVersionOverride = targetSdkVersionOverride?.apiLevel ?: -1 + ) + } + + override fun getVariant(param: StringParameter): CompletableFuture { + return CompletableFuture.supplyAsync { + androidProject.variants.find { it.name == param.value }?.toMetadata() + } + } + + private fun Variant.toMetadata(): AndroidVariantMetadata { + return AndroidVariantMetadata( + name = name, + mainArtifact = mainArtifact.toMetadata(name), + otherArtifacts = mutableMapOf() + ) + } + + override fun getBootClasspaths(): CompletableFuture> { + return CompletableFuture.supplyAsync { + basicAndroidProject.bootClasspath + } + } + + override fun getLibraryMap(): CompletableFuture> { + return CompletableFuture.supplyAsync { + val seen = HashMap() + val compileDependencies = variantDependencies.mainArtifact.compileDependencies + val libraries = variantDependencies.libraries + for (dependency in compileDependencies) { + seen[dependency.key] ?: fillLibrary(dependency, libraries, seen) + } + seen + } + } + + private fun fillLibrary( + item: GraphItem, + libraries: Map, + seen: HashMap + ): DefaultLibrary? { + + val lib = libraries[item.key] ?: return null + val library = copy(lib) + + for (dependency in item.dependencies) { + val dep = fillLibrary(dependency, libraries, seen)!! + library.dependencies.add(dep.key) + } + + seen[item.key] = library + + return library + } + + override fun getMainSourceSet(): CompletableFuture { + return CompletableFuture.supplyAsync { + basicAndroidProject.mainSourceSet?.let(AndroidModulePropertyCopier::copy) + } + } + + override fun getLintCheckJars(): CompletableFuture> { + return CompletableFuture.supplyAsync { androidProject.lintChecksJars } + } + + override fun getModelSyncFiles(): CompletableFuture> { + return CompletableFuture.supplyAsync { copy(androidProject.modelSyncFiles) } + } + + override fun getClassesJar(): CompletableFuture { + // TODO(itsaky): this should handle product flavors as well + return CompletableFuture.supplyAsync { + File(gradleProject.buildDirectory, + "${IAndroidProject.FD_INTERMEDIATES}/compile_library_classes_jar/$variant/classes.jar") + } + } + + override fun getClasspaths(): CompletableFuture> { + return CompletableFuture.supplyAsync { + mutableListOf().apply { + add(getClassesJar().get()) + getVariant(StringParameter(variant)).get()?.mainArtifact?.classJars?.let(this::addAll) + } + } + } + + override fun getMetadata(): CompletableFuture { + return CompletableFuture.supplyAsync { + val gradleMetadata = super.getMetadata().get() + + val viewBindingOptions = androidProject.viewBindingOptions?.let( + AndroidModulePropertyCopier::copy) + ?: DefaultViewBindingOptions() + + return@supplyAsync AndroidProjectMetadata( + gradleMetadata as ProjectMetadata, + packageName, + basicAndroidProject.projectType, + copy(androidProject.flags), + copy(androidProject.javaCompileOptions), + viewBindingOptions, + androidProject.resourcePrefix, + androidProject.namespace, + androidProject.androidTestNamespace, + androidProject.testFixturesNamespace + ) + } + } + + private fun findPackageName() { + + val namespace = androidProject.namespace + if (namespace.isNotBlank()) { + this.packageName = namespace + this.shouldLookupPackage = false + return + } + + val mainSourceSet = basicAndroidProject.mainSourceSet + if (mainSourceSet == null) { + shouldLookupPackage = false + return + } + + val manifestFile = mainSourceSet.sourceProvider.manifestFile + if (manifestFile == DefaultSourceProvider.NoFile) { + shouldLookupPackage = false + return + } + + this.packageName = extractPackageName(manifestFile) ?: UNKNOWN_PACKAGE + this.shouldLookupPackage = false + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/GradleProjectImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/GradleProjectImpl.kt new file mode 100644 index 000000000..5f4a29b8e --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/GradleProjectImpl.kt @@ -0,0 +1,69 @@ +/* + * 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.internal + +import com.itsaky.androidide.tooling.api.IGradleProject +import com.itsaky.androidide.tooling.api.ProjectType +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.GradleTask +import com.itsaky.androidide.tooling.api.models.ProjectMetadata +import org.gradle.tooling.model.GradleProject +import java.io.Serializable +import java.util.concurrent.CompletableFuture + +/** + * @author Akash Yadav + */ +internal open class GradleProjectImpl( + protected val gradleProject: GradleProject +) : IGradleProject, Serializable { + + private val serialVersionUID = 1L + + override fun getMetadata(): CompletableFuture { + return CompletableFuture.supplyAsync { + ProjectMetadata( + gradleProject.name, + gradleProject.path, + gradleProject.projectDirectory, + gradleProject.buildDirectory, + gradleProject.description, + gradleProject.buildScript.sourceFile, + ProjectType.Gradle + ) + } + } + + override fun getTasks(): CompletableFuture> { + return CompletableFuture.supplyAsync { + mutableListOf().apply { + for (task in gradleProject.tasks) { + add(GradleTask( + task.name, + task.description, + task.group, + task.path, + task.displayName, + task.isPublic, + task.project.path + )) + } + } + } + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/JavaProjectImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/JavaProjectImpl.kt new file mode 100644 index 000000000..ccc0ad157 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/JavaProjectImpl.kt @@ -0,0 +1,53 @@ +/* + * 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.internal + +import com.itsaky.androidide.tooling.api.IJavaProject +import com.itsaky.androidide.tooling.api.models.GradleTask +import com.itsaky.androidide.tooling.api.models.JavaContentRoot +import com.itsaky.androidide.tooling.api.models.JavaModuleDependency +import com.itsaky.androidide.tooling.api.models.ProjectMetadata +import org.gradle.tooling.model.GradleProject +import java.io.File +import java.io.Serializable +import java.util.concurrent.CompletableFuture + +/** + * @author Akash Yadav + */ +internal class JavaProjectImpl(gradleProject: GradleProject) : GradleProjectImpl(gradleProject), + IJavaProject, Serializable { + + private val serialVersionUID = 1L + + override fun getContentRoots(): CompletableFuture> { + TODO("Not yet implemented") + } + + override fun getDependencies(): CompletableFuture> { + TODO("Not yet implemented") + } + + override fun getClassesJar(): CompletableFuture { + TODO("Not yet implemented") + } + + override fun getClasspaths(): CompletableFuture> { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt new file mode 100644 index 000000000..155531e1b --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt @@ -0,0 +1,103 @@ +/* + * 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.internal + +import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.api.IGradleProject +import com.itsaky.androidide.tooling.api.IJavaProject +import com.itsaky.androidide.tooling.api.IProject +import com.itsaky.androidide.tooling.api.ProjectType +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.params.StringParameter +import com.itsaky.androidide.tooling.api.models.result.SelectProjectResult +import com.itsaky.androidide.tooling.impl.internal.forwarding.ForwardingProject +import java.io.Serializable +import java.util.concurrent.CompletableFuture + +/** + * @author Akash Yadav + */ +@Suppress("JoinDeclarationAndAssignment") +internal class ProjectImpl(var rootProject: IGradleProject? = null, + var projects: List = emptyList()) : IProject, Serializable { + + private val serialVersionUID = 1L + + @Transient + private var _lock: Any? = null + private val lock: Any + get() = _lock ?: Any().also { _lock = it } + + @Transient + private val selectedProject: ForwardingProject + + init { + this.selectedProject = ForwardingProject() + } + + private fun getProject(path: String): IGradleProject? { + return if (":" == path) rootProject else projects.find { + it.getMetadata().get().projectPath == path + } + } + + override fun getProjects(): CompletableFuture> { + return CompletableFuture.supplyAsync { + projects.map { it.getMetadata().get() } + } + } + + override fun getProjectSyncIssues(): CompletableFuture> { + return CompletableFuture.completedFuture(emptyMap()) + } + + override fun selectProject(param: StringParameter): CompletableFuture { + return CompletableFuture.supplyAsync { + synchronized(lock) { + this.selectedProject.project = getProject(param.value) + SelectProjectResult(this.selectedProject.project != null) + } + } + } + + override fun getType(): CompletableFuture { + return CompletableFuture.supplyAsync { + synchronized(lock) { + return@supplyAsync when (this.selectedProject.project) { + is IAndroidProject -> ProjectType.Android + is IJavaProject -> ProjectType.Java + is IGradleProject -> ProjectType.Gradle + else -> ProjectType.Unknown + } + } + } + } + + override fun asGradleProject(): IGradleProject { + return this.selectedProject + } + + override fun asAndroidProject(): IAndroidProject { + return this.selectedProject + } + + override fun asJavaProject(): IJavaProject { + return this.selectedProject + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/forwarding/ForwardingProject.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/forwarding/ForwardingProject.kt new file mode 100644 index 000000000..05aa073cb --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/forwarding/ForwardingProject.kt @@ -0,0 +1,115 @@ +/* + * 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.internal.forwarding + +import com.itsaky.androidide.builder.model.DefaultLibrary +import com.itsaky.androidide.builder.model.DefaultModelSyncFile +import com.itsaky.androidide.builder.model.DefaultSourceSetContainer +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.api.IGradleProject +import com.itsaky.androidide.tooling.api.IJavaProject +import com.itsaky.androidide.tooling.api.models.AndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.GradleTask +import com.itsaky.androidide.tooling.api.models.JavaContentRoot +import com.itsaky.androidide.tooling.api.models.JavaModuleDependency +import com.itsaky.androidide.tooling.api.models.ProjectMetadata +import com.itsaky.androidide.tooling.api.models.params.StringParameter +import java.io.File +import java.util.concurrent.CompletableFuture + +/** + * @author Akash Yadav + */ +@Suppress("NewApi") +internal class ForwardingProject(var project: IGradleProject? = null) : IGradleProject, + IAndroidProject, IJavaProject { + + private val androidProject: IAndroidProject? + get() = this.project as? IAndroidProject? + + private val javaProject: IJavaProject? + get() = this.project as? IJavaProject? + + + override fun getContentRoots(): CompletableFuture> { + return this.javaProject?.getContentRoots() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getDependencies(): CompletableFuture> { + return this.javaProject?.getDependencies() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getVariants(): CompletableFuture> { + return this.androidProject?.getVariants() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getVariant(param: StringParameter): CompletableFuture { + return this.androidProject?.getVariant(param) ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getBootClasspaths(): CompletableFuture> { + return this.androidProject?.getBootClasspaths() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getLibraryMap(): CompletableFuture> { + return this.androidProject?.getLibraryMap() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getMainSourceSet(): CompletableFuture { + return this.androidProject?.getMainSourceSet() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getLintCheckJars(): CompletableFuture> { + return this.androidProject?.getLintCheckJars() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getModelSyncFiles(): CompletableFuture> { + return this.androidProject?.getModelSyncFiles() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getClassesJar(): CompletableFuture { + return this.javaProject?.getClassesJar() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getClasspaths(): CompletableFuture> { + return this.javaProject?.getClasspaths() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getMetadata(): CompletableFuture { + return this.project?.getMetadata() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } + + override fun getTasks(): CompletableFuture> { + return this.project?.getTasks() ?: CompletableFuture.failedFuture( + UnsupportedOperationException()) + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt deleted file mode 100644 index ef184d436..000000000 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/model/InternalForwardingProject.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.model - -import com.itsaky.androidide.tooling.api.IProject -import com.itsaky.androidide.tooling.api.IProject.Type -import com.itsaky.androidide.tooling.api.IProject.Type.Unknown -import com.itsaky.androidide.tooling.api.IToolingApiClient -import com.itsaky.androidide.tooling.api.messages.VariantDataRequest -import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData -import com.itsaky.androidide.tooling.api.model.AndroidModule -import com.itsaky.androidide.tooling.api.model.IdeGradleProject -import com.itsaky.androidide.tooling.api.model.GradleTask -import com.itsaky.androidide.utils.ILogger -import java.io.File -import java.util.concurrent.* - -/** - * An [IProject] implementation which forwards all calls to another [IProject]. - * - * We provide [IProject] as a service to the [IToolingApiClient]. As there is no real project - * instance available when the server starts, we use this implementation. When the project is - * initialized, the Tooling API server updates the project instance here. Once the project instance - * is available, the client can fetch real information about the project. - * - * @author Akash Yadav - */ -class InternalForwardingProject( - var project: IProject?, - var projectPath: String = IProject.FILE_PATH_NOT_AVAILABLE -) : IProject { - - private val log = ILogger.newInstance(javaClass.simpleName) - - override fun isProjectInitialized(): CompletableFuture { - return CompletableFuture.completedFuture(this.project != null) - } - - override fun getName(): CompletableFuture = - if (this.project != null) this.project!!.name else CompletableFuture.completedFuture("") - - override fun getDescription(): CompletableFuture = - if (this.project != null) this.project!!.description else CompletableFuture.completedFuture("") - - override fun getProjectPath(): CompletableFuture = - if (this.project != null) this.project!!.projectPath else CompletableFuture.completedFuture("") - - override fun getProjectDir(): CompletableFuture = - if (this.project != null) this.project!!.projectDir - else CompletableFuture.completedFuture(File(projectPath)) - - override fun getType(): CompletableFuture = - if (this.project != null) this.project!!.type else CompletableFuture.completedFuture(Unknown) - - override fun getBuildDir(): CompletableFuture = - if (this.project != null) this.project!!.buildDir - else CompletableFuture.completedFuture(File(projectPath)) - - override fun getBuildScript(): CompletableFuture = - if (this.project != null) this.project!!.buildScript - else CompletableFuture.completedFuture(File(projectPath)) - - override fun getTasks(): CompletableFuture> = - if (this.project != null) this.project!!.tasks - else CompletableFuture.completedFuture(mutableListOf()) - - override fun listModules(): CompletableFuture> = - if (this.project != null) project!!.listModules() - else CompletableFuture.completedFuture(mutableListOf()) - - override fun findByPath(path: String): CompletableFuture = - if (this.project != null) this.project!!.findByPath(path) - else CompletableFuture.completedFuture(null) - - override fun findAndroidModules(): CompletableFuture> = - if (this.project != null) this.project!!.findAndroidModules() - else CompletableFuture.completedFuture(mutableListOf()) - - override fun findFirstAndroidModule(): CompletableFuture = - if (this.project != null) this.project!!.findFirstAndroidModule() - else CompletableFuture.completedFuture(null) - - override fun findFirstAndroidAppModule(): CompletableFuture = - if (this.project != null) this.project!!.findFirstAndroidAppModule() - else CompletableFuture.completedFuture(null) - - override fun getVariantData(request: VariantDataRequest): CompletableFuture = - if (this.project != null) this.project!!.getVariantData(request) - else CompletableFuture.completedFuture(null) -} diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt new file mode 100644 index 000000000..7e7af8edc --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt @@ -0,0 +1,106 @@ +/* + * 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.sync + +import com.android.builder.model.v2.models.Versions +import com.itsaky.androidide.tooling.impl.Main +import com.itsaky.androidide.utils.ILogger +import com.itsaky.androidide.utils.LogUtils +import org.gradle.tooling.BuildController +import org.gradle.tooling.model.Model + +/** + * Abstract class for [IModelBuilder] implementations. + * + * @property androidVariant The name of the variant for which the Android models will be built. + * @author Akash Yadav + */ +abstract class AbstractModelBuilder(protected val androidVariant: String = VARIANT_DEBUG) : + IModelBuilder { + + companion object { + + /** + * Default variant name used for [AbstractModelBuilder.androidVariant]. + */ + const val VARIANT_DEBUG = "debug" + + /** + * Checks the Android Gradle Plugin version from the given [Versions] model and compares + * it with [Main.MIN_SUPPORTED_AGP_VERSION]. If the version is less than the [Main.MIN_SUPPORTED_AGP_VERSION], + * throws an [UnsupportedOperationException]. + * + * @param versions The [Versions] model. + */ + @JvmStatic + protected fun assertMinimumAgp(versions: Versions) { + if (versions.agp < Main.MIN_SUPPORTED_AGP_VERSION) { + throw ModelBuilderException( + "Android Gradle Plugin version " + + versions.agp + + " is not supported by AndroidIDE. " + + "Please update your project to use at least v" + + Main.MIN_SUPPORTED_AGP_VERSION + + " of Android Gradle Plugin to build this project.") + } + } + + /** + * Get the [Versions] information about Android projects. This returns `null` if + * the project is not an Android project. + * + * @param model The model element, usually a project. + * @param controller The build controller that is used for finding the model. + * @return The [Versions] model if available, `null` otherwise. + */ + @JvmStatic + protected fun getAndroidVersions(model: Model, controller: BuildController): Versions? { + return controller.findModel(model, Versions::class.java) + } + + @JvmStatic + protected fun log(vararg objects: Any?) { + buildLog(*objects) + } + + /** + * Logs the given objects to the error stream. + * + * @param objects The objects to log. + */ + protected fun buildLog(vararg objects: Any?) { + System.err.println(generateMessage(*objects)) + } + + /** + * Generates the log message for the given objects. This works similar to + * [ generateMessage(Object...)][com.itsaky.androidide.utils.ILogger.generateMessage] in [ILogger][com.itsaky.androidide.utils.ILogger]. + * + * @param objects The objects to print in the message. + * @return The generated message. + */ + protected fun generateMessage(vararg objects: Any?): String { + val sb = StringBuilder() + for (msg in objects) { + sb.append(if (msg is Throwable) "\n" else ILogger.MSG_SEPARATOR) + sb.append(if (msg is Throwable) LogUtils.getFullStackTrace( + msg as Throwable?) else msg) + } + return sb.toString() + } + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt new file mode 100644 index 000000000..9a5a8c037 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt @@ -0,0 +1,49 @@ +/* + * 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.sync + +import com.android.builder.model.v2.models.AndroidProject +import com.android.builder.model.v2.models.BasicAndroidProject +import com.android.builder.model.v2.models.ModelBuilderParameter +import com.android.builder.model.v2.models.VariantDependencies +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.impl.internal.AndroidProjectImpl + +/** + * Builds model for Android application and library projects. + * + * @author Akash Yadav + */ +class AndroidProjectModelBuilder(androidVariant: String) : + AbstractModelBuilder(androidVariant) { + + override fun build(param: BuildControllderAndIdeaModule): IAndroidProject { + val (controller, module) = param + val basicModel = controller.findModel(module, BasicAndroidProject::class.java) + val androidModel = controller.findModel(module, AndroidProject::class.java) + val variantDependencies = controller.findModel(module, VariantDependencies::class.java, + ModelBuilderParameter::class.java) { it.variantName = androidVariant } + + return AndroidProjectImpl( + module.gradleProject, + androidVariant, + basicModel, + androidModel, + variantDependencies + ) + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/GradleProjectModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/GradleProjectModelBuilder.kt new file mode 100644 index 000000000..58dcfacd2 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/GradleProjectModelBuilder.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.impl.sync + +import com.itsaky.androidide.tooling.api.IGradleProject +import com.itsaky.androidide.tooling.impl.internal.GradleProjectImpl +import org.gradle.tooling.model.GradleProject + +/** + * Builds model for root Gradle project (represented with [IGradleProject]. + * + * @author Akash Yadav + */ +class GradleProjectModelBuilder : AbstractModelBuilder() { + + @Throws(ModelBuilderException::class) + override fun build(param: GradleProject): IGradleProject { + return GradleProjectImpl(param) + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/IModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/IModelBuilder.kt new file mode 100644 index 000000000..59a400f38 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/IModelBuilder.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.impl.sync + +/** + * A model builder builds project models when the project is initialized/synced. + * + * @param

The parameter for building the model. + * @param The type of model that is built. + * @author Akash Yadav + */ +interface IModelBuilder { + + /** + * Builds the model. + * + * @param param Parameter for building the model. + * @return The built model. Implementations should throw [ModelBuilderException] instead of + * returning `null`. + * @throws ModelBuilderException If the model could not be built. + */ + @Throws(ModelBuilderException::class) + fun build(param: P): R +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/JavaProjectModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/JavaProjectModelBuilder.kt new file mode 100644 index 000000000..278d4ba2b --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/JavaProjectModelBuilder.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.impl.sync + +import com.itsaky.androidide.tooling.api.IJavaProject +import com.itsaky.androidide.tooling.impl.internal.JavaProjectImpl +import org.gradle.tooling.model.idea.IdeaModule + +/** + * Builds model for Java library projects. + * + * @author Akash Yadav + */ +class JavaProjectModelBuilder : AbstractModelBuilder() { + + override fun build(param: IdeaModule): IJavaProject { + return JavaProjectImpl(param.gradleProject) + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModelBuilderException.java b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModelBuilderException.java new file mode 100644 index 000000000..a1c2dc7eb --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModelBuilderException.java @@ -0,0 +1,41 @@ +/* + * 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.sync; + +/** + * Exception thrown while building project models. + * + * @author Akash Yadav + */ +public class ModelBuilderException extends RuntimeException { + + public ModelBuilderException() { + } + + public ModelBuilderException(String message) { + super(message); + } + + public ModelBuilderException(String message, Throwable cause) { + super(message, cause); + } + + public ModelBuilderException(Throwable cause) { + super(cause); + } +} diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt new file mode 100644 index 000000000..2cb88a23e --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.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.impl.sync + +import com.itsaky.androidide.tooling.api.IModuleProject + +/** + * Builds models for module projects (either Android app/library or Java library projects). + * + * @author Akash Yadav + */ +class ModuleProjectModelBuilder(androidVariant: String = VARIANT_DEBUG) : + AbstractModelBuilder(androidVariant) { + + override fun build(param: BuildControllderAndIdeaModule): IModuleProject { + val (controller, module) = param + val isAndroidProject = getAndroidVersions(module, controller) != null + return if (isAndroidProject) AndroidProjectModelBuilder(androidVariant).build( + controller to module) else JavaProjectModelBuilder().build(module) + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/NotAnAndroidProjectException.java b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/NotAnAndroidProjectException.java new file mode 100644 index 000000000..31c143dea --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/NotAnAndroidProjectException.java @@ -0,0 +1,35 @@ +/* + * 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.sync; + +/** + * Exception thrown to indicate that a project is not an Android project. + * + * @author Akash Yadav + */ +public class NotAnAndroidProjectException extends ModelBuilderException { + + /** + * Creates a new {@link NotAnAndroidProjectException} for the given project name. + * + * @param projectName The name of the project which is not an Android project. + */ + public NotAnAndroidProjectException(String projectName) { + super("Project " + projectName + " is not an Android project"); + } +} diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt new file mode 100644 index 000000000..dd16f4a9c --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt @@ -0,0 +1,96 @@ +/* + * 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.sync + +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.api.IProject +import com.itsaky.androidide.tooling.impl.Main +import com.itsaky.androidide.tooling.impl.Main.finalizeLauncher +import com.itsaky.androidide.tooling.impl.internal.ProjectImpl +import com.itsaky.androidide.utils.ILogger +import org.gradle.tooling.BuildController +import org.gradle.tooling.ConfigurableLauncher +import org.gradle.tooling.model.idea.IdeaProject + +/** + * Utility class to build the project models. + * + * @author Akash Yadav + */ +object RootModelBuilder : AbstractModelBuilder("") { + + override fun build(param: ProjectConnectionAndAndroidVariant): IProject { + val (connection, androidVariant) = param + val executor = connection.action { controller -> + val ideaProject = findIdeaProject(controller) ?: throw ModelBuilderException( + "IdeaProject model could not be created") + + val ideaModules = ideaProject.modules + val rootModule = ideaModules.find { it.gradleProject.path == ":" } + ?: throw ModelBuilderException( + "No GradleProject model is associated with project path: ':'") + + val rootProjectVersions = getAndroidVersions(rootModule, controller) + + val rootProject = if (rootProjectVersions != null) { + // Root project is an Android project + assertMinimumAgp(rootProjectVersions) + AndroidProjectModelBuilder(androidVariant).build(controller to rootModule) + } else { + GradleProjectModelBuilder().build(rootModule.gradleProject) + } + + val projects = ideaModules.map { ideaModule -> + ModuleProjectModelBuilder(androidVariant).build(controller to ideaModule) + } + + ProjectImpl(rootProject, projects) + } + + finalizeLauncher(executor) + applyAndroidModelBuilderProps(executor) + + val logger = ILogger.newInstance("ProjectReader") + logger.warn("Starting build. See build output for more details...") + + if (Main.client != null) { + Main.client.logOutput("Starting build...") + } + + return executor.run().also { + logger.debug("Build action executed. Result:", it) + } + } + + private fun findIdeaProject(controller: BuildController): IdeaProject? { + log("Creating Idea project model...") + return controller.findModel(IdeaProject::class.java)?.also { + log("IdeaProject model created...") + } + } + + private fun applyAndroidModelBuilderProps( + launcher: ConfigurableLauncher<*>) { + launcher.addProperty(IAndroidProject.PROPERTY_BUILD_MODEL_ONLY, true) + launcher.addProperty(IAndroidProject.PROPERTY_INVOKED_FROM_IDE, true) + } + + private fun ConfigurableLauncher<*>.addProperty(property: String, value: Any) { + addArguments(String.format("-P%s=%s", property, value)) + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt new file mode 100644 index 000000000..8459b87a1 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt @@ -0,0 +1,26 @@ +/* + * 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.sync + +import org.gradle.tooling.BuildController +import org.gradle.tooling.ProjectConnection +import org.gradle.tooling.model.idea.IdeaModule + + +typealias BuildControllderAndIdeaModule = Pair +typealias ProjectConnectionAndAndroidVariant = Pair \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/AnnotationUtil.java b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/AnnotationUtil.java new file mode 100644 index 000000000..e3221ba35 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/AnnotationUtil.java @@ -0,0 +1,160 @@ +/* + * 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.util; + +/** + * @author Akash Yadav + */ + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import org.eclipse.lsp4j.jsonrpc.services.JsonDelegate; +import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; + +public final class AnnotationUtil { + + private AnnotationUtil() { + } + + public static void findDelegateSegments(Class clazz, Set> visited, + Consumer acceptor) { + if (clazz == null || !visited.add(clazz)) { + return; + } + findDelegateSegments(clazz.getSuperclass(), visited, acceptor); + for (Class interf : clazz.getInterfaces()) { + findDelegateSegments(interf, visited, acceptor); + } + for (Method method : clazz.getDeclaredMethods()) { + if (isDelegateMethod(method)) { + acceptor.accept(method); + } + } + } + + public static boolean isDelegateMethod(Method method) { + if (!method.isSynthetic()) { + JsonDelegate jsonDelegate = method.getAnnotation(JsonDelegate.class); + if (jsonDelegate != null) { + if (!(method.getParameterCount() == 0 && method.getReturnType().isInterface())) { + throw new IllegalStateException( + "The method " + method.toString() + " is not a proper @JsonDelegate method."); + } + return true; + } + } + return false; + } + + + /** + * Depth first search for annotated methods in hierarchy. + */ + public static void findRpcMethods(Class clazz, Set> visited, + Consumer acceptor) { + if (clazz == null || !visited.add(clazz)) { + return; + } + findRpcMethods(clazz.getSuperclass(), visited, acceptor); + for (Class interf : clazz.getInterfaces()) { + findRpcMethods(interf, visited, acceptor); + } + String segment = getSegment(clazz); + for (Method method : clazz.getDeclaredMethods()) { + MethodInfo methodInfo = createMethodInfo(method, segment); + if (methodInfo != null) { + acceptor.accept(methodInfo); + } + } + } + + protected static String getSegment(Class clazz) { + JsonSegment jsonSegment = clazz.getAnnotation(JsonSegment.class); + return jsonSegment == null ? "" : jsonSegment.value() + "/"; + } + + protected static MethodInfo createMethodInfo(Method method, String segment) { + if (!method.isSynthetic()) { + JsonRequest jsonRequest = method.getAnnotation(JsonRequest.class); + if (jsonRequest != null) { + return createRequestInfo(method, segment, jsonRequest); + } + JsonNotification jsonNotification = method.getAnnotation(JsonNotification.class); + if (jsonNotification != null) { + return createNotificationInfo(method, segment, jsonNotification); + } + } + return null; + } + + protected static MethodInfo createNotificationInfo(Method method, String segment, + JsonNotification jsonNotification) { + MethodInfo methodInfo = createMethodInfo(method, jsonNotification.useSegment(), segment, + jsonNotification.value()); + methodInfo.isNotification = true; + return methodInfo; + } + + protected static MethodInfo createRequestInfo(Method method, String segment, + JsonRequest jsonRequest) { + return createMethodInfo(method, jsonRequest.useSegment(), segment, jsonRequest.value()); + } + + protected static MethodInfo createMethodInfo(Method method, boolean useSegment, String segment, + String value) { + method.setAccessible(true); + + MethodInfo methodInfo = new MethodInfo(); + methodInfo.method = method; + methodInfo.parameterTypes = getParameterTypes(method); + methodInfo.name = getMethodName(method, useSegment, segment, value); + return methodInfo; + } + + protected static String getMethodName(Method method, boolean useSegment, String segment, + String value) { + String name = value != null && value.length() > 0 ? value : method.getName(); + return useSegment ? segment + name : name; + } + + protected static Type[] getParameterTypes(Method method) { + return Arrays.stream(method.getParameters()).map(t -> t.getParameterizedType()) + .toArray(Type[]::new); + } + + public static class MethodInfo { + + private static Type[] EMPTY_TYPE_ARRAY = {}; + public String name; + public Method method; + public Type[] parameterTypes = EMPTY_TYPE_ARRAY; + public boolean isNotification = false; + } + + public static class DelegateInfo { + + public Method method; + public Object delegate; + } +} + diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java index 1278f4da8..e8d05dcc4 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java @@ -17,42 +17,25 @@ package com.itsaky.androidide.tooling.impl.util; -import static com.itsaky.androidide.tooling.impl.Main.finalizeLauncher; import static com.itsaky.androidide.utils.ILogger.MSG_SEPARATOR; import static java.util.Collections.emptyList; import com.android.builder.model.ModelBuilderParameter; -import com.android.builder.model.v2.ide.GraphItem; -import com.android.builder.model.v2.ide.Library; import com.android.builder.model.v2.ide.ProjectType; import com.android.builder.model.v2.models.AndroidProject; import com.android.builder.model.v2.models.BasicAndroidProject; import com.android.builder.model.v2.models.ProjectSyncIssues; import com.android.builder.model.v2.models.VariantDependencies; import com.android.builder.model.v2.models.Versions; -import com.itsaky.androidide.builder.model.DefaultLibrary; import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues; import com.itsaky.androidide.builder.model.IJavaCompilerSettings; -import com.itsaky.androidide.tooling.api.model.AndroidModule; -import com.itsaky.androidide.tooling.api.model.GradleArtifact; -import com.itsaky.androidide.tooling.api.model.GradleTask; -import com.itsaky.androidide.tooling.api.model.IdeGradleProject; -import com.itsaky.androidide.tooling.api.model.JavaContentRoot; -import com.itsaky.androidide.tooling.api.model.JavaModule; -import com.itsaky.androidide.tooling.api.model.JavaModuleCompilerSettings; -import com.itsaky.androidide.tooling.api.model.JavaModuleDependency; -import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency; -import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency; -import com.itsaky.androidide.tooling.api.model.JavaSourceDirectory; -import com.itsaky.androidide.tooling.api.model.util.AndroidModulePropertyCopier; -import com.itsaky.androidide.tooling.api.model.util.ProjectBuilder; import com.itsaky.androidide.tooling.impl.Main; -import com.itsaky.androidide.utils.ILogger; import com.itsaky.androidide.utils.LogUtils; - +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import org.gradle.api.JavaVersion; import org.gradle.tooling.BuildController; -import org.gradle.tooling.ConfigurableLauncher; import org.gradle.tooling.ProjectConnection; import org.gradle.tooling.UnknownModelException; import org.gradle.tooling.model.GradleProject; @@ -63,492 +46,416 @@ import org.gradle.tooling.model.idea.IdeaProject; import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -import kotlin.Pair; - /** * @author Akash Yadav */ public class ProjectReader { - /** All modules mapped by their names (not paths). */ - private static final Map allModules = new ConcurrentHashMap<>(); - - public static IdeGradleProject read( - ProjectConnection connection, Map outIssues) - throws ExecutionException, InterruptedException { - final var buildActionExecutor = - connection.action( - controller -> { - log("Creating Idea project model..."); - final var ideaProject = controller.findModel(IdeaProject.class); - log("IdeaProject model created..."); - return buildGradleProjectModel(ideaProject, controller, outIssues); - }); - finalizeLauncher(buildActionExecutor); - applyAndroidModelBuilderProps(buildActionExecutor); - - final var logger = ILogger.newInstance("ProjectReader"); - logger.warn("Starting build. See build output for more details..."); - - if (Main.client != null) { - Main.client.logOutput("Starting build..."); - } - - final var project = buildActionExecutor.run(); - - // Fetch java module dependencies - final var ideaProject = connection.model(IdeaProject.class).get(); - final var modules = ideaProject.getModules(); - - for (var module : modules) { - allModules.put(module.getName(), module); - } - - for (final var module : modules) { - final var moduleProject = project.findByPath(module.getGradleProject().getPath()).get(); - if (!(moduleProject instanceof JavaModule)) { - continue; - } - - final var dependencies = ((JavaModule) moduleProject).getJavaDependencies(); - dependencies.clear(); - dependencies.addAll(collectJavaDependencies(module)); - } - - allModules.clear(); - - return project; - } - - public static AndroidModule buildAndroidModuleProject( - GradleProject gradle, AndroidProject android, ProjectType type) { - log("Building AndroidModule for project: " + gradle.getName()); - final var builder = new ProjectBuilder(); - final var copier = AndroidModulePropertyCopier.INSTANCE; - builder.setName(gradle.getName()); - builder.setDescription(gradle.getDescription()); - builder.setPath(gradle.getPath()); - builder.setProjectDir(gradle.getProjectDirectory()); - builder.setBuildDir(gradle.getBuildDirectory()); - builder.setBuildScript(gradle.getBuildScript().getSourceFile()); - builder.setSimpleVariants(copier.asSimpleVariants(android.getVariants())); - builder.setJavaCompileOptions(copier.copy(android.getJavaCompileOptions())); - builder.setResourcePrefix(android.getResourcePrefix()); - builder.setDynamicFeatures(android.getDynamicFeatures()); - builder.setViewBindingOptions(copier.copy(android.getViewBindingOptions())); - builder.setFlags(copier.copy(android.getFlags())); - builder.setModelSyncFiles(emptyList()); - builder.setLintChecksJars(android.getLintChecksJars()); - builder.setProjectType(type); - - final var module = builder.buildAndroidModule(); - tryFillNamespaces(android, module); - addTasks(gradle, module); - - return module; - } - - private static void tryFillNamespaces(final AndroidProject android, final AndroidModule module) { - try { - module.setNamespace(android.getNamespace()); - module.setAndroidTestNamespace(android.getAndroidTestNamespace()); - module.setTestFixturesNamespace(android.getTestFixturesNamespace()); - } catch (Throwable err) { - log("Unable to get namespaces of module: '" + module.projectPath + "'.", err.getMessage()); - } - } - - private static void addTasks(GradleProject gradle, IdeGradleProject project) { - for (final var task : gradle.getTasks()) { - project.tasks.add(buildGradleTaskModel(project, task)); - } - } - - private static GradleTask buildGradleTaskModel( - IdeGradleProject project, org.gradle.tooling.model.GradleTask task) { - return new GradleTask( - task.getName(), - task.getDescription(), - task.getGroup(), - task.getPath(), - task.getDisplayName(), - task.isPublic(), - project.projectPath); - } - - private static IdeGradleProject buildModuleProject( - BuildController controller, - final IdeaProject ideaProject, - IdeaModule ideaModule, - Map outIssues) { - return buildModuleProject(controller, ideaProject, ideaModule, outIssues, false); - } - - private static IdeGradleProject buildModuleProject( - BuildController controller, - final IdeaProject ideaProject, - IdeaModule ideaModule, - Map outIssues, - boolean androidOnly) { - - final var gradle = ideaModule.getGradleProject(); - - try { - final var info = createAndroidModelInfo(gradle, controller); - if (info == null) { - throw new NotAnAndroidProjectException(gradle.getName()); - } - - outIssues.put(gradle.getPath(), info.getSyncIssues()); - return info.getProject(); - } catch (Throwable error) { - - if (androidOnly) { - if (error instanceof NotAnAndroidProjectException && gradle.getParent() == null) { - // A root project is not an Android project in most cases - // do not emit misleading logs - return null; - } - - log(error.getMessage()); - return null; - } - - try { - log("Building IdeGradleProject model for project: " + gradle.getPath()); - if (!(error instanceof UnknownModelException)) { - // If the error is something else than UnknownModelException, we should log it - error.printStackTrace(); - } - - return buildJavaModuleProject(gradle, ideaProject, ideaModule); - } catch (Throwable e) { - log("Unable to create model for project"); - e.printStackTrace(); - return null; - } - } - } - - private static ModelInfoContainer createAndroidModelInfo( - GradleProject gradle, BuildController controller) { - final var start = System.currentTimeMillis(); - final var versions = controller.findModel(gradle, Versions.class); - if (versions == null) { - return null; - } - - if (versions.getAgp().compareTo(Main.MIN_SUPPORTED_AGP_VERSION) < 0) { - throw new UnsupportedOperationException( - "Android Gradle Plugin version " - + versions.getAgp() - + " is not supported by AndroidIDE. " - + "Please update your project to use at least v" - + Main.MIN_SUPPORTED_AGP_VERSION - + " of Android Gradle Plugin to build this project."); - } - - final var basicAndroid = controller.findModel(gradle, BasicAndroidProject.class); - - log("Fetching Android project model..."); - final var android = controller.findModel(gradle, AndroidProject.class); - final var module = buildAndroidModuleProject(gradle, android, basicAndroid.getProjectType()); - module.setBootClassPaths(basicAndroid.getBootClasspath()); - module.setMainSourceSet( - basicAndroid.getMainSourceSet() == null - ? null - : AndroidModulePropertyCopier.INSTANCE.copy(basicAndroid.getMainSourceSet())); - - log("Fetching 'debug' dependencies"); - final var variantDependencies = - controller.findModel( - gradle, - VariantDependencies.class, - ModelBuilderParameter.class, - it -> it.setVariantName("debug")); - - final var dependencies = getAllCompileDependencies(variantDependencies); - module.setLibraryMap(dependencies.getFirst()); - module.setLibraries(dependencies.getSecond()); - - log("Fetching project sync issues..."); - final var issues = controller.findModel(gradle, ProjectSyncIssues.class); - final var syncIssues = - issues == null - ? new DefaultProjectSyncIssues(emptyList()) - : AndroidModulePropertyCopier.INSTANCE.copy(issues); - - log( - "Android project model for project '" + gradle.getName() + "' created in", - System.currentTimeMillis() - start, - "ms"); - return new ModelInfoContainer(module, syncIssues); - } - - private static Pair, Set> getAllCompileDependencies( - final VariantDependencies variantDependencies) { - final var seen = new HashMap(); - final var libs = new HashSet(); - final var compileDependencies = variantDependencies.getMainArtifact().getCompileDependencies(); - final var libraries = variantDependencies.getLibraries(); - for (final var dependency : compileDependencies) { - var library = seen.get(dependency.getKey()); - if (library == null) { - library = getLibrary(dependency, libraries, seen); - } - if (library == null) { - continue; - } - - libs.add(library.getKey()); - } - return new Pair<>(seen, libs); - } - - private static DefaultLibrary getLibrary( - final GraphItem item, - final Map libraries, - final HashMap seen) { - final var lib = libraries.get(item.getKey()); - if (lib == null) { - return null; - } - - final var library = AndroidModulePropertyCopier.INSTANCE.copy(lib); - for (final var dependency : item.getDependencies()) { - final DefaultLibrary dep = Objects.requireNonNull(getLibrary(dependency, libraries, seen)); - library.getDependencies().add(dep.getKey()); - } - - seen.put(item.getKey(), library); - return library; - } - - private static JavaModule buildJavaModuleProject( - GradleProject gradle, final IdeaProject ideaProject, IdeaModule idea) { - final var builder = new ProjectBuilder(); - builder.setName(gradle.getName()); - builder.setDescription(gradle.getDescription()); - builder.setPath(gradle.getPath()); - builder.setProjectDir(gradle.getProjectDirectory()); - builder.setBuildDir(gradle.getBuildDirectory()); - builder.setBuildScript(gradle.getBuildScript().getSourceFile()); - builder.setContentRoots(collectContentRoots(idea)); - builder.setJavaCompilerSettings(createCompilerSettings(ideaProject, idea)); - - final var project = builder.buildJavaModule(); - addTasks(gradle, project); - return project; - } - - private static IJavaCompilerSettings createCompilerSettings( - final IdeaProject ideaProject, final IdeaModule module) { - final var javaLanguageSettings = module.getJavaLanguageSettings(); - if (javaLanguageSettings == null) { - return createCompilerSettings(ideaProject); - } - - JavaVersion languageLevel = javaLanguageSettings.getLanguageLevel(); - JavaVersion targetBytecodeVersion = javaLanguageSettings.getTargetBytecodeVersion(); - - if (languageLevel == null || targetBytecodeVersion == null) { - return createCompilerSettings(ideaProject); - } - - final var source = languageLevel.toString(); - final var target = targetBytecodeVersion.toString(); - return new JavaModuleCompilerSettings(source, target); - } - - private static IJavaCompilerSettings createCompilerSettings(final IdeaProject ideaProject) { - final var settings = ideaProject.getJavaLanguageSettings(); - if (settings == null) { - return new JavaModuleCompilerSettings(); - } - - final var source = settings.getLanguageLevel(); - final var target = settings.getTargetBytecodeVersion(); - if (source == null || target == null) { - return new JavaModuleCompilerSettings(); - } - - return new JavaModuleCompilerSettings(source.toString(), target.toString()); - } - - private static void log(Object... messages) { - System.err.println(generateMessage(messages)); - } - - private static String generateMessage(Object... messages) { - StringBuilder sb = new StringBuilder(); - if (messages == null) { - return "null"; - } - - for (Object msg : messages) { - sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR); - sb.append(msg instanceof Throwable ? LogUtils.getFullStackTrace(((Throwable) msg)) : msg); - } - - return sb.toString(); - } - - private static List collectJavaDependencies(IdeaModule idea) { - final var list = new ArrayList(); - for (IdeaDependency dependency : idea.getDependencies()) { - // TODO There might be unresolved dependencies here. We need to handle them too. - if (dependency instanceof IdeaSingleEntryLibraryDependency) { - final var external = (IdeaSingleEntryLibraryDependency) dependency; - final var file = external.getFile(); - final var source = external.getSource(); - final var javadoc = external.getJavadoc(); - final GradleArtifact artifact = getGradleArtifact(external); - list.add( - new JavaModuleExternalDependency( - file, - source, - javadoc, - artifact, - dependency.getScope().getScope(), - dependency.getExported())); - } else if (dependency instanceof IdeaModuleDependency) { - final var project = ((IdeaModuleDependency) dependency); - final var moduleName = project.getTargetModuleName(); - list.add( - new JavaModuleProjectDependency( - moduleName, - findModulePathByName(moduleName), - project.getScope().getScope(), - project.getExported())); - } - } - return list; - } - - @Nullable - private static GradleArtifact getGradleArtifact(final IdeaSingleEntryLibraryDependency external) { - final var moduleVersion = external.getGradleModuleVersion(); - if (moduleVersion == null) { - return null; - } - - return new GradleArtifact( - moduleVersion.getGroup(), moduleVersion.getName(), moduleVersion.getVersion()); - } - - private static String findModulePathByName(final String moduleName) { - final var module = allModules.get(moduleName); - if (module != null) { - return module.getGradleProject().getPath(); - } - return ""; - } - - private static List collectContentRoots(IdeaModule module) { - final var list = new ArrayList(); - for (var contentRoot : module.getContentRoots()) { - final var thisRoot = new JavaContentRoot(); - for (var sourceDir : contentRoot.getSourceDirectories()) { - thisRoot - .getSourceDirectories() - .add(new JavaSourceDirectory(sourceDir.getDirectory(), sourceDir.isGenerated())); - } - - for (var testDir : contentRoot.getTestDirectories()) { - thisRoot - .getTestDirectories() - .add(new JavaSourceDirectory(testDir.getDirectory(), testDir.isGenerated())); - } - - list.add(thisRoot); - } - return list; - } - - private static > void applyAndroidModelBuilderProps( - ConfigurableLauncher launcher) { - addProperty(launcher, AndroidModule.PROPERTY_BUILD_MODEL_ONLY, true); - addProperty(launcher, AndroidModule.PROPERTY_INVOKED_FROM_IDE, true); - } - - private static void addProperty(ConfigurableLauncher launcher, String property, Object value) { - launcher.addArguments(String.format("-P%s=%s", property, value)); - } - - private static IdeGradleProject buildGradleProjectModel( - IdeaProject ideaProject, - BuildController controller, - Map outIssues) { - final var rootModule = - ideaProject.getModules().stream() - .filter(it -> it.getGradleProject().getPath().equals(":")) - .findAny(); - - if (rootModule.isEmpty()) { - throw new IllegalArgumentException( - "No GradleProject model is associated with project path: " + "':'"); - } - - IdeGradleProject project = - buildModuleProject(controller, ideaProject, rootModule.get(), outIssues, true); - final var gradle = rootModule.get().getGradleProject(); - if (project == null) { - final var builder = new ProjectBuilder(); - builder.setName(gradle.getName()); - builder.setDescription(gradle.getDescription()); - builder.setPath(gradle.getPath()); - builder.setProjectDir(gradle.getProjectDirectory()); - builder.setBuildDir(gradle.getBuildDirectory()); - builder.setBuildScript(gradle.getBuildScript().getSourceFile()); - - project = builder.buildGradleProject(); - - // If the root project is an Android project - // Then the tasks have already been added into the project - // So we only add them here if we create a Gradle project model - addTasks(gradle, project); - } else { - log("Root project is an Android project..."); - } - - addModules(ideaProject, project, controller, outIssues); - - return project; - } - - private static void addModules( - IdeaProject ideaProject, - IdeGradleProject project, - BuildController controller, - Map outIssues) { - for (final var module : ideaProject.getModules()) { - final var gradle = module.getGradleProject(); - if (gradle.getPath().equals(":")) { - // Do not try to add the root project again - continue; - } - - final var sub = buildModuleProject(controller, ideaProject, module, outIssues); - if (sub != null) { - project.moduleProjects.add(sub); - } - } - } - - private static class NotAnAndroidProjectException extends UnknownModelException { - - public NotAnAndroidProjectException(String projectName) { - super("Project " + projectName + " is not an Android project"); - } - } +// /** +// * All modules mapped by their names (not paths). +// */ +// private static final Map allModules = new ConcurrentHashMap<>(); +// +// public static IdeGradleProject read( +// ProjectConnection connection, Map outIssues) +// throws ExecutionException, InterruptedException { +// +// final var project = buildActionExecutor.run(); +// +// // Fetch java module dependencies +// final var ideaProject = connection.model(IdeaProject.class).get(); +// final var modules = ideaProject.getModules(); +// +// for (var module : modules) { +// allModules.put(module.getName(), module); +// } +// +// for (final var module : modules) { +// final var moduleProject = project.findByPath(module.getGradleProject().getPath()).get(); +// if (!(moduleProject instanceof JavaModule)) { +// continue; +// } +// +// final var dependencies = ((JavaModule) moduleProject).getJavaDependencies(); +// dependencies.clear(); +// dependencies.addAll(collectJavaDependencies(module)); +// } +// +// allModules.clear(); +// +// return project; +// } +// +// public static AndroidModule buildAndroidModuleProject( +// GradleProject gradle, AndroidProject android, ProjectType type) { +// log("Building AndroidModule for project: " + gradle.getName()); +// final var builder = new ProjectBuilder(); +// final var copier = AndroidModulePropertyCopier.INSTANCE; +// builder.setName(gradle.getName()); +// builder.setDescription(gradle.getDescription()); +// builder.setPath(gradle.getPath()); +// builder.setProjectDir(gradle.getProjectDirectory()); +// builder.setBuildDir(gradle.getBuildDirectory()); +// builder.setBuildScript(gradle.getBuildScript().getSourceFile()); +// builder.setSimpleVariants(copier.asSimpleVariants(android.getVariants())); +// builder.setJavaCompileOptions(copier.copy(android.getJavaCompileOptions())); +// builder.setResourcePrefix(android.getResourcePrefix()); +// builder.setDynamicFeatures(android.getDynamicFeatures()); +// builder.setViewBindingOptions(copier.copy(android.getViewBindingOptions())); +// builder.setFlags(copier.copy(android.getFlags())); +// builder.setModelSyncFiles(emptyList()); +// builder.setLintChecksJars(android.getLintChecksJars()); +// builder.setProjectType(type); +// +// final var module = builder.buildAndroidModule(); +// tryFillNamespaces(android, module); +// addTasks(gradle, module); +// +// return module; +// } +// +// private static void tryFillNamespaces(final AndroidProject android, final AndroidModule module) { +// try { +// module.setNamespace(android.getNamespace()); +// module.setAndroidTestNamespace(android.getAndroidTestNamespace()); +// module.setTestFixturesNamespace(android.getTestFixturesNamespace()); +// } catch (Throwable err) { +// log("Unable to get namespaces of module: '" + module.projectPath + "'.", err.getMessage()); +// } +// } +// +// private static void addTasks(GradleProject gradle, IdeGradleProject project) { +// for (final var task : gradle.getTasks()) { +// project.tasks.add(buildGradleTaskModel(project, task)); +// } +// } +// +// private static GradleTask buildGradleTaskModel( +// IdeGradleProject project, org.gradle.tooling.model.GradleTask task) { +// return new GradleTask( +// task.getName(), +// task.getDescription(), +// task.getGroup(), +// task.getPath(), +// task.getDisplayName(), +// task.isPublic(), +// project.projectPath); +// } +// +// private static IdeGradleProject buildModuleProject( +// BuildController controller, +// final IdeaProject ideaProject, +// IdeaModule ideaModule, +// Map outIssues) { +// return buildModuleProject(controller, ideaProject, ideaModule, outIssues, false); +// } +// +// private static IdeGradleProject buildModuleProject( +// BuildController controller, +// final IdeaProject ideaProject, +// IdeaModule ideaModule, +// Map outIssues, +// boolean androidOnly) { +// +// final var gradle = ideaModule.getGradleProject(); +// +// try { +// final var info = createAndroidModelInfo(gradle, controller); +// if (info == null) { +// throw new NotAnAndroidProjectException(gradle.getName()); +// } +// +// outIssues.put(gradle.getPath(), info.getSyncIssues()); +// return info.getProject(); +// } catch (Throwable error) { +// +// if (androidOnly) { +// if (error instanceof NotAnAndroidProjectException && gradle.getParent() == null) { +// // A root project is not an Android project in most cases +// // do not emit misleading logs +// return null; +// } +// +// log(error.getMessage()); +// return null; +// } +// +// try { +// log("Building IdeGradleProject model for project: " + gradle.getPath()); +// if (!(error instanceof UnknownModelException)) { +// // If the error is something else than UnknownModelException, we should log it +// error.printStackTrace(); +// } +// +// return buildJavaModuleProject(gradle, ideaProject, ideaModule); +// } catch (Throwable e) { +// log("Unable to create model for project"); +// e.printStackTrace(); +// return null; +// } +// } +// } +// +// private static ModelInfoContainer createAndroidModelInfo( +// GradleProject gradle, BuildController controller) { +// final var start = System.currentTimeMillis(); +// final var versions = controller.findModel(gradle, Versions.class); +// if (versions == null) { +// return null; +// } +// +// if (versions.getAgp().compareTo(Main.MIN_SUPPORTED_AGP_VERSION) < 0) { +// throw new UnsupportedOperationException( +// "Android Gradle Plugin version " +// + versions.getAgp() +// + " is not supported by AndroidIDE. " +// + "Please update your project to use at least v" +// + Main.MIN_SUPPORTED_AGP_VERSION +// + " of Android Gradle Plugin to build this project."); +// } +// +// final var basicAndroid = controller.findModel(gradle, BasicAndroidProject.class); +// +// log("Fetching Android project model..."); +// final var android = controller.findModel(gradle, AndroidProject.class); +// final var module = buildAndroidModuleProject(gradle, android, basicAndroid.getProjectType()); +// module.setBootClassPaths(basicAndroid.getBootClasspath()); +// module.setMainSourceSet( +// basicAndroid.getMainSourceSet() == null +// ? null +// : AndroidModulePropertyCopier.INSTANCE.copy(basicAndroid.getMainSourceSet())); +// +// log("Fetching 'debug' dependencies"); +// final var variantDependencies = +// controller.findModel( +// gradle, +// VariantDependencies.class, +// ModelBuilderParameter.class, +// it -> it.setVariantName("debug")); +// +// final var dependencies = getAllCompileDependencies(variantDependencies); +// module.setLibraryMap(dependencies.getFirst()); +// module.setLibraries(dependencies.getSecond()); +// +// log("Fetching project sync issues..."); +// final var issues = controller.findModel(gradle, ProjectSyncIssues.class); +// final var syncIssues = +// issues == null +// ? new DefaultProjectSyncIssues(emptyList()) +// : AndroidModulePropertyCopier.INSTANCE.copy(issues); +// +// log( +// "Android project model for project '" + gradle.getName() + "' created in", +// System.currentTimeMillis() - start, +// "ms"); +// return new ModelInfoContainer(module, syncIssues); +// } +// +// private static JavaModule buildJavaModuleProject( +// GradleProject gradle, final IdeaProject ideaProject, IdeaModule idea) { +// final var builder = new ProjectBuilder(); +// builder.setName(gradle.getName()); +// builder.setDescription(gradle.getDescription()); +// builder.setPath(gradle.getPath()); +// builder.setProjectDir(gradle.getProjectDirectory()); +// builder.setBuildDir(gradle.getBuildDirectory()); +// builder.setBuildScript(gradle.getBuildScript().getSourceFile()); +// builder.setContentRoots(collectContentRoots(idea)); +// builder.setJavaCompilerSettings(createCompilerSettings(ideaProject, idea)); +// +// final var project = builder.buildJavaModule(); +// addTasks(gradle, project); +// return project; +// } +// +// private static IJavaCompilerSettings createCompilerSettings( +// final IdeaProject ideaProject, final IdeaModule module) { +// final var javaLanguageSettings = module.getJavaLanguageSettings(); +// if (javaLanguageSettings == null) { +// return createCompilerSettings(ideaProject); +// } +// +// JavaVersion languageLevel = javaLanguageSettings.getLanguageLevel(); +// JavaVersion targetBytecodeVersion = javaLanguageSettings.getTargetBytecodeVersion(); +// +// if (languageLevel == null || targetBytecodeVersion == null) { +// return createCompilerSettings(ideaProject); +// } +// +// final var source = languageLevel.toString(); +// final var target = targetBytecodeVersion.toString(); +// return new JavaModuleCompilerSettings(source, target); +// } +// +// private static IJavaCompilerSettings createCompilerSettings(final IdeaProject ideaProject) { +// final var settings = ideaProject.getJavaLanguageSettings(); +// if (settings == null) { +// return new JavaModuleCompilerSettings(); +// } +// +// final var source = settings.getLanguageLevel(); +// final var target = settings.getTargetBytecodeVersion(); +// if (source == null || target == null) { +// return new JavaModuleCompilerSettings(); +// } +// +// return new JavaModuleCompilerSettings(source.toString(), target.toString()); +// } +// +// private static void log(Object... messages) { +// System.err.println(generateMessage(messages)); +// } +// +// private static String generateMessage(Object... messages) { +// StringBuilder sb = new StringBuilder(); +// if (messages == null) { +// return "null"; +// } +// +// for (Object msg : messages) { +// sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR); +// sb.append(msg instanceof Throwable ? LogUtils.getFullStackTrace(((Throwable) msg)) : msg); +// } +// +// return sb.toString(); +// } +// +// private static List collectJavaDependencies(IdeaModule idea) { +// final var list = new ArrayList(); +// for (IdeaDependency dependency : idea.getDependencies()) { +// // TODO There might be unresolved dependencies here. We need to handle them too. +// if (dependency instanceof IdeaSingleEntryLibraryDependency) { +// final var external = (IdeaSingleEntryLibraryDependency) dependency; +// final var file = external.getFile(); +// final var source = external.getSource(); +// final var javadoc = external.getJavadoc(); +// final GradleArtifact artifact = getGradleArtifact(external); +// list.add( +// new JavaModuleExternalDependency( +// file, +// source, +// javadoc, +// artifact, +// dependency.getScope().getScope(), +// dependency.getExported())); +// } else if (dependency instanceof IdeaModuleDependency) { +// final var project = ((IdeaModuleDependency) dependency); +// final var moduleName = project.getTargetModuleName(); +// list.add( +// new JavaModuleProjectDependency( +// moduleName, +// findModulePathByName(moduleName), +// project.getScope().getScope(), +// project.getExported())); +// } +// } +// return list; +// } +// +// @Nullable +// private static GradleArtifact getGradleArtifact(final IdeaSingleEntryLibraryDependency external) { +// final var moduleVersion = external.getGradleModuleVersion(); +// if (moduleVersion == null) { +// return null; +// } +// +// return new GradleArtifact( +// moduleVersion.getGroup(), moduleVersion.getName(), moduleVersion.getVersion()); +// } +// +// private static String findModulePathByName(final String moduleName) { +// final var module = allModules.get(moduleName); +// if (module != null) { +// return module.getGradleProject().getPath(); +// } +// return ""; +// } +// +// private static List collectContentRoots(IdeaModule module) { +// final var list = new ArrayList(); +// for (var contentRoot : module.getContentRoots()) { +// final var thisRoot = new JavaContentRoot(); +// for (var sourceDir : contentRoot.getSourceDirectories()) { +// thisRoot +// .getSourceDirectories() +// .add(new JavaSourceDirectory(sourceDir.getDirectory(), sourceDir.isGenerated())); +// } +// +// for (var testDir : contentRoot.getTestDirectories()) { +// thisRoot +// .getTestDirectories() +// .add(new JavaSourceDirectory(testDir.getDirectory(), testDir.isGenerated())); +// } +// +// list.add(thisRoot); +// } +// return list; +// } +// +// private static IdeGradleProject buildGradleProjectModel( +// IdeaProject ideaProject, +// BuildController controller, +// Map outIssues) { +// final var rootModule = +// ideaProject.getModules().stream() +// .filter(it -> it.getGradleProject().getPath().equals(":")) +// .findAny(); +// +// if (rootModule.isEmpty()) { +// throw new IllegalArgumentException( +// "No GradleProject model is associated with project path: " + "':'"); +// } +// +// IdeGradleProject project = +// buildModuleProject(controller, ideaProject, rootModule.get(), outIssues, true); +// final var gradle = rootModule.get().getGradleProject(); +// if (project == null) { +// final var builder = new ProjectBuilder(); +// builder.setName(gradle.getName()); +// builder.setDescription(gradle.getDescription()); +// builder.setPath(gradle.getPath()); +// builder.setProjectDir(gradle.getProjectDirectory()); +// builder.setBuildDir(gradle.getBuildDirectory()); +// builder.setBuildScript(gradle.getBuildScript().getSourceFile()); +// +// project = builder.buildGradleProject(); +// +// // If the root project is an Android project +// // Then the tasks have already been added into the project +// // So we only add them here if we create a Gradle project model +// addTasks(gradle, project); +// } else { +// log("Root project is an Android project..."); +// } +// +// addModules(ideaProject, project, controller, outIssues); +// +// return project; +// } +// +// private static void addModules( +// IdeaProject ideaProject, +// IdeGradleProject project, +// BuildController controller, +// Map outIssues) { +// for (final var module : ideaProject.getModules()) { +// final var gradle = module.getGradleProject(); +// if (gradle.getPath().equals(":")) { +// // Do not try to add the root project again +// continue; +// } +// +// final var sub = buildModuleProject(controller, ideaProject, module, outIssues); +// if (sub != null) { +// project.moduleProjects.add(sub); +// } +// } +// } +// +// private static class NotAnAndroidProjectException extends UnknownModelException { +// +// public NotAnAndroidProjectException(String projectName) { +// super("Project " + projectName + " is not an Android project"); +// } +// } } diff --git a/subprojects/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/MultiModuleAndroidProjectTest.kt b/subprojects/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/MultiModuleAndroidProjectTest.kt index 705004db2..76c79217d 100644 --- a/subprojects/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/MultiModuleAndroidProjectTest.kt +++ b/subprojects/tooling-api-impl/src/test/java/com/itsaky/androidide/tooling/impl/MultiModuleAndroidProjectTest.kt @@ -17,17 +17,16 @@ package com.itsaky.androidide.tooling.impl -import com.android.builder.model.v2.ide.LibraryType.PROJECT +import com.android.builder.model.v2.ide.LibraryType import com.android.builder.model.v2.ide.ProjectType.APPLICATION import com.google.common.truth.Truth.assertThat +import com.itsaky.androidide.tooling.api.IAndroidProject import com.itsaky.androidide.tooling.api.IProject -import com.itsaky.androidide.tooling.api.IProject.Type.Gradle import com.itsaky.androidide.tooling.api.IToolingApiServer +import com.itsaky.androidide.tooling.api.ProjectType import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage -import com.itsaky.androidide.tooling.api.model.AndroidModule -import com.itsaky.androidide.tooling.api.model.JavaModule -import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency -import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency +import com.itsaky.androidide.tooling.api.models.AndroidProjectMetadata +import com.itsaky.androidide.tooling.api.models.params.StringParameter import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher.MultiVersionTestClient import com.itsaky.androidide.utils.FileProvider @@ -44,98 +43,121 @@ class MultiModuleAndroidProjectTest { @Test fun `test simple multi module project initialization`() { val (server, project) = ToolingApiTestLauncher().launchServer() - server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get() + server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString, "debug")).get() doAssertions(project, server) } private fun doAssertions(project: IProject, server: IToolingApiServer) { assertThat(project).isNotNull() - assertThat(project.type.get()).isEqualTo(Gradle) + // As the returned project is just a proxy, // project instanceOf IdeGradleProject will always return false val isInitialized = server.isServerInitialized().get() assertThat(isInitialized).isTrue() - val app = project.findByPath(":app").get() + + var selectionResult = project.selectProject(StringParameter(":")).get() + assertThat(selectionResult.isSuccessful).isTrue() + assertThat(project.getType().get()).isEqualTo(ProjectType.Gradle) + + selectionResult = project.selectProject(StringParameter(":app")).get() + assertThat(selectionResult).isNotNull() + assertThat(selectionResult.isSuccessful).isTrue() + + val app = project.asAndroidProject() assertThat(app).isNotNull() - assertThat(app).isInstanceOf(AndroidModule::class.java) - assertThat((app as AndroidModule).javaCompileOptions).isNotNull() - assertThat(app.javaCompileOptions.sourceCompatibility).isEqualTo("11") - assertThat(app.javaCompileOptions.targetCompatibility).isEqualTo("11") - assertThat(app.javaCompileOptions.javaSourceVersion).isEqualTo("11") - assertThat(app.javaCompileOptions.javaBytecodeVersion).isEqualTo("11") - assertThat(app.javaCompileOptions.isCoreLibraryDesugaringEnabled).isFalse() - assertThat(app.projectType).isEqualTo(APPLICATION) - assertThat(app.packageName).isEqualTo("com.itsaky.test.app") - assertThat(app.viewBindingOptions).isNotNull() - assertThat(app.viewBindingOptions!!.isEnabled).isTrue() + assertThat(app!!).isInstanceOf(IAndroidProject::class.java) + + var metadata = app.getMetadata().get() as AndroidProjectMetadata + assertThat(metadata).isNotNull() + assertThat(metadata.javaCompileOptions.sourceCompatibility).isEqualTo("11") + assertThat(metadata.javaCompileOptions.targetCompatibility).isEqualTo("11") + assertThat(metadata.javaCompileOptions.javaSourceVersion).isEqualTo("11") + assertThat(metadata.javaCompileOptions.javaBytecodeVersion).isEqualTo("11") + assertThat(metadata.javaCompileOptions.isCoreLibraryDesugaringEnabled).isFalse() + assertThat(metadata.androidType).isEqualTo(APPLICATION) + assertThat(metadata.packageName).isEqualTo("com.itsaky.test.app") + assertThat(metadata.viewBindingOptions).isNotNull() + assertThat(metadata.viewBindingOptions.isEnabled).isTrue() + // There are always more than 100 tasks in an android module // Also, the tasks must contain the user defined tasks - assertThat(app.tasks.size).isAtLeast(100) - assertThat(app.tasks.first { it.path == "${app.projectPath}:thisIsATestTask" }).isNotNull() - assertThat(app.libraries).isNotEmpty() + val tasks = app.getTasks().get() + assertThat(tasks.size).isAtLeast(100) + assertThat(tasks.first { it.path == "${metadata.projectPath}:thisIsATestTask" }).isNotNull() + + var libraries = app.getLibraryMap().get() // At least one project library - assertThat(app.libraryMap.values.filter { it.type == PROJECT }).isNotEmpty() + assertThat(libraries.values.filter { it.type == LibraryType.PROJECT }).isNotEmpty() // :app module includes :java-library as a dependency. But it is not transitive assertThat( - app.libraryMap.values.firstOrNull { - it.type == PROJECT && + libraries.values.firstOrNull { + it.type == LibraryType.PROJECT && it.projectInfo!!.projectPath == ":another-java-library" && it.projectInfo!!.attributes["org.gradle.usage"] == "java-api" } ) .isNull() - val androidLib = project.findByPath(":android-library").get() + + selectionResult = project.selectProject(StringParameter(":android-library")).get() + assertThat(selectionResult.isSuccessful).isTrue() + + val androidLib = project.asAndroidProject() assertThat(androidLib).isNotNull() - assertThat(androidLib).isInstanceOf(AndroidModule::class.java) + assertThat(androidLib!!).isInstanceOf(IAndroidProject::class.java) // Make sure that transitive dependencies are included here because :android-library includes // :java-library project which further includes :another-java-libraries project with 'api' // configuration + libraries = androidLib.getLibraryMap().get()!! assertThat( - (androidLib as AndroidModule).libraryMap.values.firstOrNull { - it.type == PROJECT && + libraries.values.firstOrNull { + it.type == LibraryType.PROJECT && it.projectInfo!!.projectPath == ":another-java-library" && it.projectInfo!!.attributes["org.gradle.usage"] == "java-api" } ) .isNotNull() - assertThat(androidLib.javaCompileOptions.javaSourceVersion).isEqualTo("11") - assertThat(androidLib.javaCompileOptions.javaBytecodeVersion).isEqualTo("11") - val javaLibrary = project.findByPath(":java-library").get() - assertThat(javaLibrary).isNotNull() - assertThat(javaLibrary).isInstanceOf(JavaModule::class.java) - assertThat((javaLibrary as JavaModule).compilerSettings.javaSourceVersion).isEqualTo("11") - assertThat(javaLibrary.compilerSettings.javaBytecodeVersion).isEqualTo("11") - assertThat( - javaLibrary.javaDependencies.firstOrNull { - it is JavaModuleExternalDependency && - it.gradleArtifact != null && - it.run { - gradleArtifact!!.group == "io.github.itsaky" && - gradleArtifact!!.name == "nb-javac-android" && - gradleArtifact!!.version == "17.0.0.0" - } - } - ) - .isNotNull() - assertThat( - javaLibrary.javaDependencies.firstOrNull { - it is JavaModuleProjectDependency && it.moduleName == "another-java-library" - } - ) - .isNotNull() - // In case we have multiple dependencies with same name but different path - val nested = - javaLibrary.javaDependencies - .filterIsInstance(JavaModuleProjectDependency::class.java) - .filter { it.moduleName.endsWith("nested-java-library") } - assertThat(nested).hasSize(2) - assertThat(nested[0].projectPath).isNotEqualTo(nested[1].projectPath) - assertThat(project.findByPath(":does-not-exist").get()).isNull() - val anotherJavaLib = project.findByPath(":another-java-library").get() - assertThat(anotherJavaLib).isNotNull() - assertThat(anotherJavaLib).isInstanceOf(JavaModule::class.java) - assertThat((anotherJavaLib as JavaModule).compilerSettings.javaSourceVersion).isEqualTo("1.8") - assertThat(anotherJavaLib.compilerSettings.javaBytecodeVersion).isEqualTo("1.8") + + metadata = androidLib.getMetadata().get() as AndroidProjectMetadata + assertThat(metadata.javaCompileOptions.javaSourceVersion).isEqualTo("11") + assertThat(metadata.javaCompileOptions.javaBytecodeVersion).isEqualTo("11") + + selectionResult = project.selectProject(StringParameter(":java-library")).get() + val javaLibrary = project.asJavaProject() +// assertThat(javaLibrary).isNotNull() +// assertThat(javaLibrary).isInstanceOf(JavaModule::class.java) +// assertThat((javaLibrary as JavaModule).compilerSettings.javaSourceVersion).isEqualTo("11") +// assertThat(javaLibrary.compilerSettings.javaBytecodeVersion).isEqualTo("11") +// assertThat( +// javaLibrary.javaDependencies.firstOrNull { +// it is JavaModuleExternalDependency && +// it.gradleArtifact != null && +// it.run { +// gradleArtifact!!.group == "io.github.itsaky" && +// gradleArtifact!!.name == "nb-javac-android" && +// gradleArtifact!!.version == "17.0.0.0" +// } +// } +// ) +// .isNotNull() +// assertThat( +// javaLibrary.javaDependencies.firstOrNull { +// it is JavaModuleProjectDependency && it.moduleName == "another-java-library" +// } +// ) +// .isNotNull() +// // In case we have multiple dependencies with same name but different path +// val nested = +// javaLibrary.javaDependencies +// .filterIsInstance(JavaModuleProjectDependency::class.java) +// .filter { it.moduleName.endsWith("nested-java-library") } +// assertThat(nested).hasSize(2) +// assertThat(nested[0].projectPath).isNotEqualTo(nested[1].projectPath) +// assertThat(project.findByPath(":does-not-exist").get()).isNull() +// val anotherJavaLib = project.findByPath(":another-java-library").get() +// assertThat(anotherJavaLib).isNotNull() +// assertThat(anotherJavaLib).isInstanceOf(JavaModule::class.java) +// assertThat((anotherJavaLib as JavaModule).compilerSettings.javaSourceVersion).isEqualTo("1.8") +// assertThat(anotherJavaLib.compilerSettings.javaBytecodeVersion).isEqualTo("1.8") } /** @@ -159,7 +181,8 @@ class MultiModuleAndroidProjectTest { client.agpVersion = agpVersion client.gradleVersion = gradleVersion val (server, project) = ToolingApiTestLauncher().launchServer(client = client) - server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get() + server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString, + "debug")).get() doAssertions(project = project, server = server) FileProvider.testProjectRoot().resolve(MultiVersionTestClient.buildFile).deleteExisting() } diff --git a/subprojects/tooling-api-model/build.gradle.kts b/subprojects/tooling-api-model/build.gradle.kts index 1f9687e96..82a6f29ea 100644 --- a/subprojects/tooling-api-model/build.gradle.kts +++ b/subprojects/tooling-api-model/build.gradle.kts @@ -1,45 +1,45 @@ -/* - * 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 . - */ - -@Suppress("JavaPluginLanguageLevel") -plugins { - id ("java-library") - id ("org.jetbrains.kotlin.jvm") -} - -dependencies { - implementation(projects.logger) - - api(projects.subprojects.xmlDom) - api(projects.subprojects.builderModelImpl) - api(libs.common.jsonrpc) - - implementation(libs.common.jkotlin) -} - -tasks.register < Copy > ("copyToTestDir") { - from ("${project.buildDir.absolutePath}/libs/tooling-api-model.jar") - into ("${project.rootProject.file ("tests/test-home/.androidide/init").absolutePath}/") - rename { "model.jar" } - - outputs.upToDateWhen { false } -} - -project.tasks.jar { - finalizedBy ("copyToTestDir") - outputs.upToDateWhen { false } -} +/* + * 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 . + */ + +@Suppress("JavaPluginLanguageLevel") +plugins { + id ("java-library") + id ("org.jetbrains.kotlin.jvm") +} + +dependencies { + implementation(projects.logger) + + api(projects.subprojects.xmlDom) + api(projects.subprojects.builderModelImpl) + api(libs.common.jsonrpc) + + implementation(libs.common.jkotlin) +} + +tasks.register < Copy > ("copyToTestDir") { + from ("${project.buildDir.absolutePath}/libs/tooling-api-model.jar") + into ("${project.rootProject.file ("tests/test-home/.androidide/init").absolutePath}/") + rename { "model.jar" } + + outputs.upToDateWhen { false } +} + +project.tasks.jar { + finalizedBy ("copyToTestDir") + outputs.upToDateWhen { false } +} diff --git a/subprojects/tooling-api-model/libs/builder-model-7.2.0-rc01.jar b/subprojects/tooling-api-model/libs/builder-model-7.2.0-rc01.jar deleted file mode 100644 index fa2966e98090968b55b5517e3a79048654a1efd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109871 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
*{efGhp8KinUi4bA;Rv5HNSo`NO&83xP%{l=Oi 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 diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/AndroidModule.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IAndroidProject.kt similarity index 56% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/AndroidModule.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IAndroidProject.kt index 4641bd8d3..b8e6ed46d 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/AndroidModule.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IAndroidProject.kt @@ -1,209 +1,170 @@ -/* - * 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 com.android.builder.model.v2.ide.ProjectType -import com.android.builder.model.v2.models.AndroidProject -import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags -import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions -import com.itsaky.androidide.builder.model.DefaultLibrary -import com.itsaky.androidide.builder.model.DefaultModelSyncFile -import com.itsaky.androidide.builder.model.DefaultSourceProvider -import com.itsaky.androidide.builder.model.DefaultSourceSetContainer -import com.itsaky.androidide.builder.model.DefaultVariant -import com.itsaky.androidide.builder.model.DefaultViewBindingOptions -import com.itsaky.androidide.builder.model.IJavaCompilerSettings -import com.itsaky.androidide.builder.model.UNKNOWN_PACKAGE -import com.itsaky.androidide.tooling.api.IProject.Type -import com.itsaky.androidide.tooling.api.IProject.Type.Android -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData -import com.itsaky.androidide.tooling.api.model.util.extractPackageName -import java.io.File -import java.io.Serializable -import java.util.concurrent.CompletableFuture - -/** - * Default implementation of [AndroidProject]. - * - * @author Akash Yadav - */ -open class AndroidModule( - name: String, - path: String, - description: String?, - projectDir: File, - buildDir: File, - buildScript: File, - parent: IdeGradleProject?, - tasks: List, - val projectType: ProjectType?, - override val dynamicFeatures: Collection?, - override val flags: DefaultAndroidGradlePluginProjectFlags, - final override val javaCompileOptions: DefaultJavaCompileOptions, - override val resourcePrefix: String?, - @Deprecated("This is resource intensive. Use AndroidModule.simpleVariants instead.") - override val variants: Collection = emptyList(), - override val viewBindingOptions: DefaultViewBindingOptions?, - override val lintChecksJars: List, - override val modelSyncFiles: List, - val simpleVariants: MutableList = mutableListOf() -) : - IdeGradleProject(name, description, path, projectDir, buildDir, buildScript, parent, tasks), - ModuleProject, - AndroidProject, - Serializable { - - private var shouldLookupPackage = true - open var packageName: String = UNKNOWN_PACKAGE - get() { - if (field == UNKNOWN_PACKAGE && shouldLookupPackage) { - findPackageName() - } - - return field - } - var bootClassPaths: Collection = emptyList() - var mainSourceSet: DefaultSourceSetContainer? = null - var libraries = mutableSetOf() - var libraryMap = mutableMapOf() - - override var compilerSettings: IJavaCompilerSettings = javaCompileOptions - - @Suppress("unused") - companion object { - const val ANDROID_NAMESPACE = "http://schemas.android.com/res/android" - - // Injectable properties to use with -P - // Sent by Studio 4.2+ - const val PROPERTY_BUILD_MODEL_ONLY = "android.injected.build.model.v2" - - // Sent by Studio 2.2+ and Android Support plugin running with IDEA from 4.1+ - // This property will enable compatibility checks between Android Support plugin and the - // Android - // Gradle plugin. - // A use case for this property is that by restricting which versions are compatible - // with the plugin, we could safely remove deprecated methods in the builder-model - // interfaces. - const val PROPERTY_ANDROID_SUPPORT_VERSION = "android.injected.studio.version" - - // Sent in when external native projects models requires a refresh. - const val PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL = - "android.injected.refresh.external.native.model" - - // Sent by Studio 2.2+ - // This property is sent when a run or debug is invoked. APK built with this property - // should - // be marked with android:testOnly="true" in the AndroidManifest.xml such that it will be - // rejected by the Play store. - const val PROPERTY_TEST_ONLY = "android.injected.testOnly" - - // Sent by Studio 1.5+ - // The version api level of the target device. - const val PROPERTY_BUILD_API = "android.injected.build.api" - - // The version codename of the target device. Null for released versions, - const val PROPERTY_BUILD_API_CODENAME = "android.injected.build.codename" - const val PROPERTY_BUILD_ABI = "android.injected.build.abi" - const val PROPERTY_BUILD_DENSITY = "android.injected.build.density" - - // Has the effect of telling the Gradle plugin to - // 1) Generate machine-readable errors - // 2) Generate build metadata JSON files - const val PROPERTY_INVOKED_FROM_IDE = "android.injected.invoked.from.ide" - const val PROPERTY_SIGNING_STORE_FILE = "android.injected.signing.store.file" - const val PROPERTY_SIGNING_STORE_PASSWORD = "android.injected.signing.store.password" - const val PROPERTY_SIGNING_KEY_ALIAS = "android.injected.signing.key.alias" - const val PROPERTY_SIGNING_KEY_PASSWORD = "android.injected.signing.key.password" - const val PROPERTY_SIGNING_STORE_TYPE = "android.injected.signing.store.type" - const val PROPERTY_SIGNING_V1_ENABLED = "android.injected.signing.v1-enabled" - const val PROPERTY_SIGNING_V2_ENABLED = "android.injected.signing.v2-enabled" - const val PROPERTY_DEPLOY_AS_INSTANT_APP = "android.injected.deploy.instant-app" - const val PROPERTY_SIGNING_COLDSWAP_MODE = "android.injected.coldswap.mode" - const val PROPERTY_APK_SELECT_CONFIG = "android.inject.apkselect.config" - const val PROPERTY_EXTRACT_INSTANT_APK = "android.inject.bundle.extractinstant" - - /** Version code to be used in the built APK. */ - const val PROPERTY_VERSION_CODE = "android.injected.version.code" - - /** Version name to be used in the built APK. */ - const val PROPERTY_VERSION_NAME = "android.injected.version.name" - - /** - * Location for APKs. If defined as a relative path, then it is resolved against the project's - * path. - */ - const val PROPERTY_APK_LOCATION = "android.injected.apk.location" - - /** - * Location of the build attribution file produced by the gradle plugin to be deserialized and - * used in the IDE build attribution. - */ - const val PROPERTY_ATTRIBUTION_FILE_LOCATION = "android.injected.attribution.file.location" - - /** - * Comma separated list of on-demand dynamic modules or instant app modules names that are - * selected by the user for installation on the device during deployment. - */ - const val PROPERTY_INJECTED_DYNAMIC_MODULES_LIST = "android.injected.modules.install.list" - - const val FD_INTERMEDIATES = "intermediates" - const val FD_LOGS = "logs" - const val FD_OUTPUTS = "outputs" - const val FD_GENERATED = "generated" - } - - override fun getType(): CompletableFuture { - return CompletableFuture.completedFuture(Android) - } - - override fun getGeneratedJar(variant: String): File { - return File(buildDir, "$FD_INTERMEDIATES/compile_library_classes_jar/$variant/classes.jar") - } - - override fun getClassPaths(): Set = - mutableSetOf().apply { - add(getGeneratedJar("debug")) - addAll(simpleVariants.first { it.name == "debug" }.mainArtifact.classJars) - } - - private fun findPackageName() { - - if (this.namespace.isNotBlank()) { - this.packageName = namespace - this.shouldLookupPackage = false - return - } - - if (mainSourceSet == null) { - shouldLookupPackage = false - return - } - - val manifestFile = mainSourceSet!!.sourceProvider.manifestFile - if (manifestFile == DefaultSourceProvider.NoFile) { - shouldLookupPackage = false - return - } - - this.packageName = extractPackageName(manifestFile) ?: UNKNOWN_PACKAGE - this.shouldLookupPackage = false - } - - override var androidTestNamespace: String? = null - override var namespace: String = "" - override var testFixturesNamespace: String? = null -} +/* + * 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.builder.model.DefaultLibrary +import com.itsaky.androidide.builder.model.DefaultModelSyncFile +import com.itsaky.androidide.builder.model.DefaultSourceSetContainer +import com.itsaky.androidide.tooling.api.models.AndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.params.StringParameter +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment +import java.io.File +import java.util.concurrent.CompletableFuture + +/** + * Model for an Android project/module. + * + * @author Akash Yadav + */ +@JsonSegment("android") +interface IAndroidProject : IModuleProject { + + /** + * Get the metadata about all variants of this Android project. + */ + @JsonRequest + fun getVariants(): CompletableFuture> + + /** + * Get the metadata about the variant with the given name. + */ + @JsonRequest + fun getVariant(param: StringParameter): CompletableFuture + + /** + * Get the boot classpaths for this Android project. + */ + @JsonRequest + fun getBootClasspaths(): CompletableFuture> + + /** + * Get the map of libraries. Each entry is a unique key representing the library, and allowing + * to match it with GraphItem instances. + * + * @param variant The name of the variant for which the libraries will be fetched. + */ + @JsonRequest + fun getLibraryMap(): CompletableFuture> + + /** + * Get the main source set container for this project. + * + * @return The main source set container or `null` if it is unavailable. + */ + @JsonRequest + fun getMainSourceSet(): CompletableFuture + + /** + * Get the lint check jars for this project. + */ + @JsonRequest + fun getLintCheckJars(): CompletableFuture> + + /** + * Get the list of [DefaultModelSyncFile]s for this project. + * See [ModelSyncFile][com.android.builder.model.v2.ModelSyncFile] for more details. + */ + @JsonRequest + fun getModelSyncFiles(): CompletableFuture> + + @Suppress("unused") + companion object { + + const val ANDROID_NAMESPACE = "http://schemas.android.com/res/android" + + // Injectable properties to use with -P + // Sent by Studio 4.2+ + const val PROPERTY_BUILD_MODEL_ONLY = "android.injected.build.model.v2" + + // Sent by Studio 2.2+ and Android Support plugin running with IDEA from 4.1+ + // This property will enable compatibility checks between Android Support plugin and the + // Android + // Gradle plugin. + // A use case for this property is that by restricting which versions are compatible + // with the plugin, we could safely remove deprecated methods in the builder-model + // interfaces. + const val PROPERTY_ANDROID_SUPPORT_VERSION = "android.injected.studio.version" + + // Sent in when external native projects models requires a refresh. + const val PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL = + "android.injected.refresh.external.native.model" + + // Sent by Studio 2.2+ + // This property is sent when a run or debug is invoked. APK built with this property + // should + // be marked with android:testOnly="true" in the AndroidManifest.xml such that it will be + // rejected by the Play store. + const val PROPERTY_TEST_ONLY = "android.injected.testOnly" + + // Sent by Studio 1.5+ + // The version api level of the target device. + const val PROPERTY_BUILD_API = "android.injected.build.api" + + // The version codename of the target device. Null for released versions, + const val PROPERTY_BUILD_API_CODENAME = "android.injected.build.codename" + const val PROPERTY_BUILD_ABI = "android.injected.build.abi" + const val PROPERTY_BUILD_DENSITY = "android.injected.build.density" + + // Has the effect of telling the Gradle plugin to + // 1) Generate machine-readable errors + // 2) Generate build metadata JSON files + const val PROPERTY_INVOKED_FROM_IDE = "android.injected.invoked.from.ide" + const val PROPERTY_SIGNING_STORE_FILE = "android.injected.signing.store.file" + const val PROPERTY_SIGNING_STORE_PASSWORD = "android.injected.signing.store.password" + const val PROPERTY_SIGNING_KEY_ALIAS = "android.injected.signing.key.alias" + const val PROPERTY_SIGNING_KEY_PASSWORD = "android.injected.signing.key.password" + const val PROPERTY_SIGNING_STORE_TYPE = "android.injected.signing.store.type" + const val PROPERTY_SIGNING_V1_ENABLED = "android.injected.signing.v1-enabled" + const val PROPERTY_SIGNING_V2_ENABLED = "android.injected.signing.v2-enabled" + const val PROPERTY_DEPLOY_AS_INSTANT_APP = "android.injected.deploy.instant-app" + const val PROPERTY_SIGNING_COLDSWAP_MODE = "android.injected.coldswap.mode" + const val PROPERTY_APK_SELECT_CONFIG = "android.inject.apkselect.config" + const val PROPERTY_EXTRACT_INSTANT_APK = "android.inject.bundle.extractinstant" + + /** Version code to be used in the built APK. */ + const val PROPERTY_VERSION_CODE = "android.injected.version.code" + + /** Version name to be used in the built APK. */ + const val PROPERTY_VERSION_NAME = "android.injected.version.name" + + /** + * Location for APKs. If defined as a relative path, then it is resolved against the project's + * path. + */ + const val PROPERTY_APK_LOCATION = "android.injected.apk.location" + + /** + * Location of the build attribution file produced by the gradle plugin to be deserialized and + * used in the IDE build attribution. + */ + const val PROPERTY_ATTRIBUTION_FILE_LOCATION = "android.injected.attribution.file.location" + + /** + * Comma separated list of on-demand dynamic modules or instant app modules names that are + * selected by the user for installation on the device during deployment. + */ + const val PROPERTY_INJECTED_DYNAMIC_MODULES_LIST = "android.injected.modules.install.list" + + const val FD_INTERMEDIATES = "intermediates" + const val FD_LOGS = "logs" + const val FD_OUTPUTS = "outputs" + const val FD_GENERATED = "generated" + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IGradleProject.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IGradleProject.kt new file mode 100644 index 000000000..293527bed --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IGradleProject.kt @@ -0,0 +1,53 @@ +/* + * 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.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.GradleTask +import com.itsaky.androidide.tooling.api.models.ProjectMetadata +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment +import java.util.concurrent.CompletableFuture + +/** + * A model for representing a project which is not an Android or Java project. + * + * @author Akash Yadav + */ +@JsonSegment("gradle") +interface IGradleProject { + + /** + * Get the metadata about the project. This includes basic information about the project. + * + * @see [ProjectMetadata]. + */ + @JsonRequest + fun getMetadata(): CompletableFuture + + /** + * Get this project's tasks. + */ + @JsonRequest + fun getTasks(): CompletableFuture> + + companion object { + + const val FILE_PATH_NOT_AVAILABLE = "" + } +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/ModuleProject.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IJavaProject.kt similarity index 52% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/ModuleProject.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IJavaProject.kt index f5eebd944..631a048f7 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/ModuleProject.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IJavaProject.kt @@ -1,46 +1,45 @@ -/* - * 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 com.itsaky.androidide.builder.model.IJavaCompilerSettings -import java.io.File - -/** - * A module in a Gradle project. - * - * @author Akash Yadav - */ -interface ModuleProject { - - /** Compiler settings for this module. */ - val compilerSettings: IJavaCompilerSettings - - /** - * Get the generated JAR file for this module. - * - * @param variant The build variant name. NOT applicable for [JavaModule]. - */ - fun getGeneratedJar(variant: String): File - - /** - * Get the classpath of this module. For example, the generated JAR file. - * - * @return The class paths. - */ - fun getClassPaths(): Set -} +/* + * 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.models.JavaContentRoot +import com.itsaky.androidide.tooling.api.models.JavaModuleDependency +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment +import java.util.concurrent.CompletableFuture + +/** + * Model for a Java library project. + * + * @author Akash Yadav + */ +@JsonSegment("java") +interface IJavaProject : IModuleProject { + + /** + * Get the content roots for this Java project. + */ + @JsonRequest + fun getContentRoots(): CompletableFuture> + + /** + * Get the dependencies of this Java project. + */ + @JsonRequest + fun getDependencies() : CompletableFuture> +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IModuleProject.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IModuleProject.kt new file mode 100644 index 000000000..ae485611d --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IModuleProject.kt @@ -0,0 +1,40 @@ +/* + * 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 java.io.File +import java.util.concurrent.CompletableFuture + +/** + * Common APIs for module projects. + * + * @author Akash Yadav + */ +interface IModuleProject : IGradleProject { + + /** + * Get the `classes.jar` file for this module project. + */ + fun getClassesJar(): CompletableFuture + + /** + * Get the classpaths for this module project. The returned list always included the + * `classes.jar` file from [getClassesJar]. + */ + fun getClasspaths(): CompletableFuture> +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java deleted file mode 100644 index 5a7555b6e..000000000 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.VariantDataRequest; -import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData; -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData; -import com.itsaky.androidide.tooling.api.model.AndroidModule; -import com.itsaky.androidide.tooling.api.model.GradleTask; -import com.itsaky.androidide.tooling.api.model.IdeGradleProject; - -import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; -import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * Root Gradle project. The getter methods return completed {@link CompletableFuture}s. - * - * @author Akash Yadav - */ -@JsonSegment("project") -public interface IProject { - - String FILE_PATH_NOT_AVAILABLE = ""; - - @NotNull - @JsonRequest - CompletableFuture isProjectInitialized(); - - @NotNull - @JsonRequest - CompletableFuture getName(); - - @NotNull - @JsonRequest - CompletableFuture getDescription(); - - @NotNull - @JsonRequest - CompletableFuture getProjectPath(); - - @NotNull - @JsonRequest - CompletableFuture getProjectDir(); - - @NotNull - @JsonRequest - CompletableFuture getType(); - - @NotNull - @JsonRequest - CompletableFuture getBuildDir(); - - @NotNull - @JsonRequest - CompletableFuture getBuildScript(); - - @NotNull - @JsonRequest - CompletableFuture> getTasks(); - - @NotNull - @JsonRequest - CompletableFuture> listModules(); - - @NotNull - @JsonRequest - CompletableFuture getVariantData(@NotNull VariantDataRequest request); - - @NotNull - @JsonRequest - CompletableFuture findByPath(@NotNull String path); - - @NotNull - @JsonRequest - CompletableFuture findFirstAndroidModule(); - - @NotNull - @JsonRequest - CompletableFuture findFirstAndroidAppModule(); - - @NotNull - @JsonRequest - CompletableFuture> findAndroidModules(); - - /** Type of the {@link IProject}. */ - enum Type { - - /** A simple Gradle project. Only root projects are represented by this type. */ - Gradle, - - /** - * An Android project. Mostly module projects are of this type. But in some cases, this type can - * also be applied to a root Gradle project. - */ - Android, - - /** - * A Java project. Usually, module projects which are not {@link Type#Android} type are of this - * type. - */ - Java, - - /** An unknown project type. */ - Unknown - } -} diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt new file mode 100644 index 000000000..507e4f47c --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt @@ -0,0 +1,47 @@ +/* + * 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.builder.model.DefaultProjectSyncIssues +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment +import java.util.concurrent.CompletableFuture + +/** + * A service providing access to the whole Gradle project and its structure. + * + * This class + * + * @author Akash Yadav + */ +@JsonSegment("root") +interface IProject : IProjectQueries { + + /** + * Get all the projects included in this root project. + */ + @JsonRequest + fun getProjects(): CompletableFuture> + + /** + * Get the project sync issues. + */ + @JsonRequest + fun getProjectSyncIssues(): CompletableFuture> +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProjectQueries.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProjectQueries.kt new file mode 100644 index 000000000..3d6c32090 --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProjectQueries.kt @@ -0,0 +1,81 @@ +/* + * 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.models.params.StringParameter +import com.itsaky.androidide.tooling.api.models.result.SelectProjectResult +import org.eclipse.lsp4j.jsonrpc.services.JsonDelegate +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest +import java.util.concurrent.CompletableFuture + +/** + * Transmiting the whole project models over the communication streams results in a bad performance. + * If the project is huge, the IDE may even run out of allocated memory. + * + * In the previous tooling API implementation, we used to fetch project models from Gradle, copy their + * data to our own models, then send it to the client. This resulted in a bad performance and + * increased memory usage. To overcome this, we keep a reference to the project models we get from + * from the Android Gradle Plugin and compute only the requested information from those models. + * + * With LSP4J's JsonRpc implementation, it is not possible to create a delegate method in a + * service which can accept parameters. What this means is that if a method is annotated with + * [JsonDelegate][org.eclipse.lsp4j.jsonrpc.services.JsonDelegate], it cannot accept any parameters. + * + * As we cannot send the whole project model to the client for the reasons mentioned above, we use + * another approach in this implementation. The client first selects the project it wants to fetch + * information about. After this, when the delegate methods are called, it fetches the information + * about the selected project. + * + * TODO(itsaky): Find a better approach to this issue. + * + * @author Akash Yadav + */ +interface IProjectQueries { + + /** + * Select the project to work with. This overwrites the existing selection. + * + * @param param A [StringParameter] with the project's path as the value. + */ + @JsonRequest + fun selectProject(param: StringParameter): CompletableFuture + + /** + * Get the type of selected project. + */ + @JsonRequest + fun getType(): CompletableFuture + + /** + * Get the selected project as Gradle project. + */ + @JsonDelegate + fun asGradleProject(): IGradleProject? + + /** + * Get the selected project as Android project. + */ + @JsonDelegate + fun asAndroidProject(): IAndroidProject? + + /** + * Get the selected project as Java project. + */ + @JsonDelegate + fun asJavaProject(): IJavaProject? +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/ProjectType.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/ProjectType.kt new file mode 100644 index 000000000..98832d7be --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/ProjectType.kt @@ -0,0 +1,44 @@ +/* + * 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 + +/** + * The type of [IGradleProject]. + * + * @author Akash Yadav + */ +enum class ProjectType { + + /** A simple Gradle project. Only root projects are represented by this type. */ + Gradle, + + /** + * An Android project. Mostly module projects are of this type. But in some cases, this type can + * also be applied to a root Gradle project. + */ + Android, + + /** + * A Java project. Usually, module projects which are not {@link Type#Android} type are of this + * type. + */ + Java, + + /** An unknown project type. */ + Unknown +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt deleted file mode 100644 index 8c91be7c4..000000000 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeGradleProject.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 com.android.builder.model.v2.ide.ProjectType.APPLICATION -import com.itsaky.androidide.tooling.api.IProject.Type -import com.itsaky.androidide.tooling.api.IProject.Type.Gradle -import com.itsaky.androidide.tooling.api.messages.VariantDataRequest -import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData -import org.eclipse.lsp4j.jsonrpc.CompletableFutures -import java.io.File -import java.io.Serializable -import java.util.concurrent.* - -/** - * A root Gradle project. - * @author Akash Yadav - */ -open class IdeGradleProject( - @JvmField val name: String, - @JvmField val description: String?, - @JvmField val projectPath: String, - @JvmField val projectDir: File, - @JvmField val buildDir: File, - @JvmField val buildScript: File, - @JvmField val parent: IdeGradleProject?, - @JvmField val tasks: List, -) : com.itsaky.androidide.tooling.api.IProject, Serializable { - private val serialVersionUID = 1L - - private val gsonType: String = javaClass.name - @JvmField val moduleProjects: MutableList = mutableListOf() - - override fun isProjectInitialized(): CompletableFuture { - return CompletableFuture.completedFuture(true) - } - - override fun getName(): CompletableFuture { - return CompletableFutures.computeAsync { this.name } - } - - override fun getDescription(): CompletableFuture { - return CompletableFutures.computeAsync { this.description } - } - - override fun getProjectPath(): CompletableFuture { - return CompletableFutures.computeAsync { this.projectPath } - } - - override fun getProjectDir(): CompletableFuture { - return CompletableFutures.computeAsync { this.projectDir } - } - - override fun getType(): CompletableFuture { - return CompletableFuture.completedFuture(Gradle) - } - - override fun getBuildDir(): CompletableFuture { - return CompletableFutures.computeAsync { this.buildDir } - } - - override fun getBuildScript(): CompletableFuture { - return CompletableFutures.computeAsync { this.buildScript } - } - - override fun getTasks(): CompletableFuture> { - return CompletableFutures.computeAsync { this.tasks.toMutableList() } - } - - override fun listModules(): CompletableFuture> { - return CompletableFutures.computeAsync { - return@computeAsync this.moduleProjects - .map { - SimpleModuleData( - name = it.name, - path = it.projectPath, - projectDir = it.projectDir, - classPaths = if (it is ModuleProject) it.getClassPaths() else emptySet() - ) - } - .toMutableList() - } - } - - override fun getVariantData(request: VariantDataRequest): CompletableFuture { - return findByPath(request.projectPath).thenApply { project -> - if (project !is AndroidModule) { - return@thenApply null - } - - return@thenApply project.simpleVariants.firstOrNull { it.name == request.variantName } - } - } - - override fun findByPath(path: String): CompletableFuture { - return CompletableFutures.computeAsync { - if (path == this.projectPath) { - return@computeAsync this - } - - return@computeAsync moduleProjects.firstOrNull { it.projectPath == path } - } - } - - override fun findAndroidModules(): CompletableFuture> { - return CompletableFutures.computeAsync { - moduleProjects.filterIsInstance(AndroidModule::class.java) - } - } - - override fun findFirstAndroidModule(): CompletableFuture { - return findAndroidModules().thenApply { it.firstOrNull() } - } - - override fun findFirstAndroidAppModule(): CompletableFuture { - if (this is AndroidModule) { - return CompletableFuture.completedFuture(this) - } - - return findAndroidModules().thenApply { modules -> - val application = - modules.stream().filter { it != null && it.projectType == APPLICATION }.findFirst() - application.orElse(null) - } - } -} diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModule.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModule.kt deleted file mode 100644 index 7acbb5848..000000000 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModule.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 com.itsaky.androidide.builder.model.IJavaCompilerSettings -import com.itsaky.androidide.tooling.api.IProject.Type -import com.itsaky.androidide.tooling.api.IProject.Type.Java -import java.io.File -import java.io.Serializable -import java.util.concurrent.* - -/** - * A java library model. Modules represented by this model does not apply any of the Android - * plugins. - * - * @author Akash Yadav - */ -open class JavaModule( - name: String, - path: String, - description: String?, - projectDir: File, - buildDir: File, - buildScript: File, - parent: IdeGradleProject?, - tasks: List, - - /** Compiler settings for this module. */ - override val compilerSettings: IJavaCompilerSettings, - - /** * Source directories of this project. */ - val contentRoots: List, - - /** Dependencies of this project. */ - val javaDependencies: MutableList -) : - IdeGradleProject(name, description, path, projectDir, buildDir, buildScript, parent, tasks), - ModuleProject, - Serializable { - - override fun getType(): CompletableFuture { - return CompletableFuture.completedFuture(Java) - } - - override fun getGeneratedJar(variant: String): File { - var jar = File(buildDir, "libs/$name.jar") - if (jar.exists()) { - return jar - } - - jar = - File(buildDir, "libs").listFiles()?.first { it.name.startsWith(this.name) } - ?: File("module-jar-does-not-exist.jar") - - return jar - } - - override fun getClassPaths() = - javaDependencies.mapNotNull { it.jarFile }.toMutableSet().apply { add(getGeneratedJar("")) } -} diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt deleted file mode 100644 index d0d33490b..000000000 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/ProjectBuilder.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.util - -import com.android.builder.model.v2.ide.ProjectType -import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags -import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions -import com.itsaky.androidide.builder.model.DefaultModelSyncFile -import com.itsaky.androidide.builder.model.DefaultViewBindingOptions -import com.itsaky.androidide.builder.model.IJavaCompilerSettings -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData -import com.itsaky.androidide.tooling.api.model.AndroidModule -import com.itsaky.androidide.tooling.api.model.GradleTask -import com.itsaky.androidide.tooling.api.model.IdeGradleProject -import com.itsaky.androidide.tooling.api.model.JavaContentRoot -import com.itsaky.androidide.tooling.api.model.JavaModule -import com.itsaky.androidide.tooling.api.model.JavaModuleCompilerSettings -import com.itsaky.androidide.tooling.api.model.JavaModuleDependency -import java.io.File - -/** - * Builds instances of [IdeGradleProject]. - * - * @author Akash Yadav - */ -class ProjectBuilder { - var name: String = "" - var description: String? = null - var path: String = ":" - var projectDir: File = File(".") - var buildDir: File = File(".") - var buildScript: File = File(".") - var parent: IdeGradleProject? = null - var modules: List = mutableListOf() - var tasks: List = mutableListOf() - var dynamicFeatures: Collection? = mutableListOf() - var flags: DefaultAndroidGradlePluginProjectFlags = - DefaultAndroidGradlePluginProjectFlags(emptyMap()) - - // For Android modules only - var javaCompileOptions: DefaultJavaCompileOptions = DefaultJavaCompileOptions() - - // For Java modules - var javaCompilerSettings: IJavaCompilerSettings = JavaModuleCompilerSettings() - - var resourcePrefix: String? = "" - var viewBindingOptions: DefaultViewBindingOptions? = null - var modelSyncFiles: List = emptyList() - var lintChecksJars: List = mutableListOf() - var contentRoots: List = mutableListOf() - var javaDependencies: MutableList = mutableListOf() - var projectType: ProjectType? = null - var simpleVariants: MutableList = mutableListOf() - - fun buildGradleProject(): IdeGradleProject { - return IdeGradleProject( - name, - description, - path, - projectDir, - buildDir, - buildScript, - parent, - tasks - ) - } - - fun buildJavaModule(): JavaModule { - return JavaModule( - name, - path, - description, - projectDir, - buildDir, - buildScript, - parent, - tasks, - javaCompilerSettings, - contentRoots, - javaDependencies - ) - } - - fun buildAndroidModule(): AndroidModule = - AndroidModule( - name = name, - path = path, - description = description, - projectDir = projectDir, - buildDir = buildDir, - buildScript = buildScript, - parent = parent, - tasks = tasks, - projectType = projectType, - dynamicFeatures = dynamicFeatures, - flags = flags, - javaCompileOptions = javaCompileOptions, - resourcePrefix = resourcePrefix, - viewBindingOptions = viewBindingOptions, - lintChecksJars = lintChecksJars, - modelSyncFiles = modelSyncFiles, - simpleVariants = simpleVariants - ) -} diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleArtifact.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidArtifactMetadata.kt similarity index 88% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleArtifact.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidArtifactMetadata.kt index 2a8ff8379..a7c236d26 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleArtifact.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidArtifactMetadata.kt @@ -1,44 +1,44 @@ -/* - * 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.result - -import java.io.File -import java.io.Serializable - -/** - * Artifact in a [SimpleVariantData]. - * - * @author Akash Yadav - */ -data class SimpleArtifact( - val name: String, - val resGenTaskName: String?, - val assembleTaskOutputListingFile: File?, - val generatedResourceFolders: Collection, - val generatedSourceFolders: Collection, - val maxSdkVersion: Int?, - val minSdkVersion: Int, - val signingConfigName: String?, - val sourceGenTaskName: String, - val assembleTaskName: String, - val classJars: List, - val compileTaskName: String, - val targetSdkVersionOverride: Int -) : Serializable { - private val serialVersionUID = 1L -} +/* + * 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.models + +import java.io.File +import java.io.Serializable + +/** + * Metadata about variant artifacts in an Android project. + * + * @author Akash Yadav + */ +data class AndroidArtifactMetadata( + val name: String, + val resGenTaskName: String?, + val assembleTaskOutputListingFile: File?, + val generatedResourceFolders: Collection, + val generatedSourceFolders: Collection, + val maxSdkVersion: Int?, + val minSdkVersion: Int, + val signingConfigName: String?, + val sourceGenTaskName: String, + val assembleTaskName: String, + val classJars: List, + val compileTaskName: String, + val targetSdkVersionOverride: Int +) : Serializable { + private val serialVersionUID = 1L +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidProjectMetadata.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidProjectMetadata.kt new file mode 100644 index 000000000..20c576eae --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidProjectMetadata.kt @@ -0,0 +1,82 @@ +/* + * 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.models + +import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags +import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions +import com.itsaky.androidide.builder.model.DefaultViewBindingOptions +import com.itsaky.androidide.tooling.api.ProjectType +import java.io.File + +/** + * Metadata about an Android project. + * + * @author Akash Yadav + */ +class AndroidProjectMetadata( + name: String?, + path: String, + projectDir: File, + buildDir: File, + description: String?, + buildScript: File, + type: ProjectType, + val packageName: String, + val androidType: AndroidProjectType, + val flags: DefaultAndroidGradlePluginProjectFlags, + val javaCompileOptions: DefaultJavaCompileOptions, + val viewBindingOptions: DefaultViewBindingOptions, + val resourcePrefix: String?, + val namespace: String?, + val androidTestNamespace: String?, + val testFixtureNamespace: String? +) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type) { + + constructor( + base: ProjectMetadata, + packageName: String, + androidType: AndroidProjectType, + flags: DefaultAndroidGradlePluginProjectFlags, + javaCompileOptiosn: DefaultJavaCompileOptions, + viewBindingOptions: DefaultViewBindingOptions, + resourcePrefix: String?, + namespace: String?, + androidTestNamespace: String?, + testFixtureNamespace: String?) : + + this( + base.name, + base.projectPath, + base.projectDir, + base.buildDir, + base.description, + base.buildScript, + base.type, + packageName, + androidType, + flags, + javaCompileOptiosn, + viewBindingOptions, + resourcePrefix, + namespace, + androidTestNamespace, + testFixtureNamespace + ) +} + +typealias AndroidProjectType = com.android.builder.model.v2.ide.ProjectType \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleVariantData.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidVariantMetadata.kt similarity index 66% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleVariantData.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidVariantMetadata.kt index 0e84bd97d..b652d7150 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleVariantData.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/AndroidVariantMetadata.kt @@ -1,33 +1,35 @@ -/* - * 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.result - -import java.io.Serializable - -/** - * Contains simple data about a variant in an Android module project. - * - * @author Akash Yadav - */ -data class SimpleVariantData( - val name: String, - val mainArtifact: SimpleArtifact, - val otherArtifacts: Map -) : Serializable { - private val serialVersionUID = 1L -} +/* + * 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.models + +import java.io.Serializable + +/** + * Metadata about a variant in an Android project. + * + * @property otherArtifacts Artifacts of this variant other than the [mainArtifact]. + * @author Akash Yadav + */ + +open class AndroidVariantMetadata( + name: String, + mainArtifact: AndroidArtifactMetadata, + val otherArtifacts: Map +) : BasicAndroidVariantMetadata(name, mainArtifact), Serializable { + private val serialVersionUID = 1L +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeLaunchable.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BasicAndroidVariantMetadata.kt similarity index 71% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeLaunchable.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BasicAndroidVariantMetadata.kt index c585e8a8b..2c1402da7 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/IdeLaunchable.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BasicAndroidVariantMetadata.kt @@ -1,29 +1,32 @@ -/* - * 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.Serializable - -/** - * Default implementation for [IdeLaunchable]. - * - * @author Akash Yadav - */ -open class IdeLaunchable(val displayName: String?, val isPublic: Boolean?) : Serializable { - protected val gsonType: String = javaClass.name - private val serialVersionUID = 1L -} +/* + * 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.models + +import java.io.Serializable + +/** + * Basic metadata about a variant in an Android project. + * + * @property name The name of the variant. + * @property mainArtifact Metadata about the main artifact of this variant. + * @author Akash Yadav + */ +open class BasicAndroidVariantMetadata(val name: String, val mainArtifact: AndroidArtifactMetadata) : Serializable { + protected val gsonType: String = javaClass.name + private val serialVersionUID = 1L +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleModuleData.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BasicProjectMetadata.kt similarity index 66% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleModuleData.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BasicProjectMetadata.kt index af7bb2a00..9e37995f3 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/result/SimpleModuleData.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BasicProjectMetadata.kt @@ -1,35 +1,41 @@ -/* - * 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.result - -import java.io.File -import java.io.Serializable - -/** - * Container for basic data about module projects. - * - * @author Akash Yadav - */ -data class SimpleModuleData( - val name: String, - val path: String, - val projectDir: File, - val classPaths: Set = emptySet() -) : Serializable { - private val serialVersionUID = 1L -} +/* + * 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.models + +import java.io.File +import java.io.Serializable + +/** + * Basic (minimal) metadata about a project. + * + * @property name The name of the module. + * @property projectPath The project's path. + * @property projectDir The project directory. + * @property buildDir The project's build directory. + * @author Akash Yadav + */ +open class BasicProjectMetadata( + val name: String?, + val projectPath: String, + val projectDir: File, + val buildDir: File +) : Serializable { + + protected val gsonType: String = javaClass.name + private val serialVersionUID = 1L +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/GradleArtifact.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/GradleArtifact.kt similarity index 92% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/GradleArtifact.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/GradleArtifact.kt index 78458519e..1d8c5ea16 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/GradleArtifact.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/GradleArtifact.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models /** * Represents a Gradle dependency artifact. @@ -23,4 +23,4 @@ package com.itsaky.androidide.tooling.api.model * @author Akash Yadav */ data class GradleArtifact(val group: String, val name: String, val version: String) : - java.io.Serializable + java.io.Serializable \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/GradleTask.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/GradleTask.kt similarity index 71% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/GradleTask.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/GradleTask.kt index d81fb5a0e..e94460196 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/GradleTask.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/GradleTask.kt @@ -1,34 +1,40 @@ -/* - * 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.Serializable - -/** - * Default implementation for [GradleTask]. - * - * @author Akash Yadav - */ -open class GradleTask( - val name: String, - val description: String?, - val group: String?, - val path: String, - displayName: String?, - isPublic: Boolean?, - val projectPath: String? -) : IdeLaunchable(displayName, isPublic), Serializable +/* + * 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.models + +import java.io.Serializable + +/** + * A Gradle task. + * + * @property name The name of the task. + * @property description The task description. + * @property group The task group. + * @property path The task path (such as `:app:build`). + * @property projectPath The path of the project in which this task is included. + * @author Akash Yadav + */ +open class GradleTask( + val name: String, + val description: String?, + val group: String?, + val path: String, + displayName: String?, + isPublic: Boolean?, + val projectPath: String? +) : Launchable(displayName, isPublic ?: false), Serializable \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaContentRoot.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaContentRoot.kt similarity index 92% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaContentRoot.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaContentRoot.kt index 717b90725..eefeba3cb 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaContentRoot.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaContentRoot.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models import java.io.Serializable diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleCompilerSettings.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleCompilerSettings.kt similarity index 96% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleCompilerSettings.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleCompilerSettings.kt index dd9f92e90..99c035fa9 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleCompilerSettings.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleCompilerSettings.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models import com.itsaky.androidide.builder.model.IJavaCompilerSettings @@ -37,4 +37,4 @@ class JavaModuleCompilerSettings( // Using the 'javax' reference will result a 'ClassNotFoundException' while deserializing the // JSONRpc data received from the Tooling API server internal constructor() : this("RELEASE_11", "RELEASE_11") -} +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleDependency.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleDependency.kt similarity index 92% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleDependency.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleDependency.kt index ed81a54b1..ad578cee1 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleDependency.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleDependency.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models import java.io.File import java.io.Serializable diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleExternalDependency.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleExternalDependency.kt similarity index 93% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleExternalDependency.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleExternalDependency.kt index 6c27d250b..40d2ee6cb 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleExternalDependency.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleExternalDependency.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models import java.io.File import java.io.Serializable diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleProjectDependency.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleProjectDependency.kt similarity index 93% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleProjectDependency.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleProjectDependency.kt index 2a2f6f03b..1583af49a 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaModuleProjectDependency.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaModuleProjectDependency.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models import java.io.Serializable diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaSourceDirectory.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaSourceDirectory.kt similarity index 92% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaSourceDirectory.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaSourceDirectory.kt index 907ef41ea..d2cd92b66 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/JavaSourceDirectory.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaSourceDirectory.kt @@ -15,7 +15,7 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model +package com.itsaky.androidide.tooling.api.models import java.io.File import java.io.Serializable diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/Launchable.kt similarity index 62% rename from subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/Launchable.kt index 0c93b1eab..22c4ebd33 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ModelInfoContainer.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/Launchable.kt @@ -1,30 +1,33 @@ -/* - * 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.util - -import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues -import com.itsaky.androidide.tooling.api.model.AndroidModule -import java.io.Serializable - -/** @author Akash Yadav */ -data class ModelInfoContainer( - val project: AndroidModule, - val syncIssues: DefaultProjectSyncIssues, -) : Serializable { - private val serialVersionUID = 1L -} +/* + * 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.models + +import java.io.Serializable + +/** + * Model containing information about anything which can be launched by the users (e.g. Gradle tasks). + * + * @property displayName The display name of the launchable. + * @property isPublic Whether the launchable is publicly accessible. + * @author Akash Yadav + */ +open class Launchable(val displayName: String?, val isPublic: Boolean) : Serializable { + + protected val gsonType: String = javaClass.name + private val serialVersionUID = 1L +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/ProjectMetadata.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/ProjectMetadata.kt new file mode 100644 index 000000000..fa8b414c4 --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/ProjectMetadata.kt @@ -0,0 +1,39 @@ +/* + * 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.models + +import com.itsaky.androidide.tooling.api.ProjectType +import java.io.File + +/** + * Metadata about a project. + * + * @property description The project description. + * @property buildScript The build script file (`build.gradle[.kts]`). + * @property type The type of the project. See constants in [ProjectType] for more details. + * @author Akash Yadav + */ +open class ProjectMetadata( + name: String?, + path: String, + projectDir: File, + buildDir: File, + val description: String?, + val buildScript: File, + val type: ProjectType +) : BasicProjectMetadata(name, path, projectDir, buildDir) \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/VariantDataRequest.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/params/StringParameter.kt similarity index 77% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/VariantDataRequest.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/params/StringParameter.kt index 26ca64dd1..3d22a2271 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/messages/VariantDataRequest.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/params/StringParameter.kt @@ -1,21 +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 - -/** @author Akash Yadav */ -data class VariantDataRequest(val variantName: String, val projectPath: String) +/* + * 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.models.params + +/** + * Use for requests/notification which accept one string parameter. + * + * @author Akash Yadav + */ +data class StringParameter(val value: String) diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/SelectProjectResult.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/SelectProjectResult.kt new file mode 100644 index 000000000..adb550b54 --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/SelectProjectResult.kt @@ -0,0 +1,27 @@ +/* + * 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.models.result + +/** + * Result sent to the client when selecting a project in [IProjectQueries][com.itsaky.androidide.tooling.api.IProjectQueries]. + * + * @param isSuccessful Whether the project selection was successful. If the value is `false`, it is + * because the project with the provided path was not found. + * @author Akash Yadav + */ +data class SelectProjectResult(val isSuccessful: Boolean) \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt similarity index 62% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt index 1c6f7b7df..ee115d759 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/AndroidModulePropertyCopier.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt @@ -1,481 +1,307 @@ -/* - * 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.util - -import com.android.builder.model.v2.CustomSourceDirectory -import com.android.builder.model.v2.ModelSyncFile -import com.android.builder.model.v2.ide.AndroidArtifact -import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags -import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags.BooleanFlag -import com.android.builder.model.v2.ide.AndroidLibraryData -import com.android.builder.model.v2.ide.ApiVersion -import com.android.builder.model.v2.ide.ArtifactDependencies -import com.android.builder.model.v2.ide.BundleInfo -import com.android.builder.model.v2.ide.GraphItem -import com.android.builder.model.v2.ide.JavaArtifact -import com.android.builder.model.v2.ide.JavaCompileOptions -import com.android.builder.model.v2.ide.Library -import com.android.builder.model.v2.ide.LibraryInfo -import com.android.builder.model.v2.ide.ProjectInfo -import com.android.builder.model.v2.ide.SourceProvider -import com.android.builder.model.v2.ide.SourceSetContainer -import com.android.builder.model.v2.ide.SyncIssue -import com.android.builder.model.v2.ide.TestInfo -import com.android.builder.model.v2.ide.TestedTargetVariant -import com.android.builder.model.v2.ide.UnresolvedDependency -import com.android.builder.model.v2.ide.Variant -import com.android.builder.model.v2.ide.ViewBindingOptions -import com.android.builder.model.v2.models.ProjectSyncIssues -import com.android.builder.model.v2.models.VariantDependencies -import com.itsaky.androidide.builder.model.DefaultAndroidArtifact -import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags -import com.itsaky.androidide.builder.model.DefaultAndroidLibraryData -import com.itsaky.androidide.builder.model.DefaultApiVersion -import com.itsaky.androidide.builder.model.DefaultArtifactDependencies -import com.itsaky.androidide.builder.model.DefaultBundleInfo -import com.itsaky.androidide.builder.model.DefaultCustomSourceDirectory -import com.itsaky.androidide.builder.model.DefaultGraphItem -import com.itsaky.androidide.builder.model.DefaultJavaArtifact -import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions -import com.itsaky.androidide.builder.model.DefaultLibrary -import com.itsaky.androidide.builder.model.DefaultLibraryInfo -import com.itsaky.androidide.builder.model.DefaultModelSyncFile -import com.itsaky.androidide.builder.model.DefaultProjectInfo -import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues -import com.itsaky.androidide.builder.model.DefaultSourceProvider -import com.itsaky.androidide.builder.model.DefaultSourceSetContainer -import com.itsaky.androidide.builder.model.DefaultSyncIssue -import com.itsaky.androidide.builder.model.DefaultTestInfo -import com.itsaky.androidide.builder.model.DefaultTestedTargetVariant -import com.itsaky.androidide.builder.model.DefaultUnresolvedDependency -import com.itsaky.androidide.builder.model.DefaultVariant -import com.itsaky.androidide.builder.model.DefaultVariantDependencies -import com.itsaky.androidide.builder.model.DefaultViewBindingOptions -import com.itsaky.androidide.tooling.api.messages.result.SimpleArtifact -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData -import com.itsaky.androidide.tooling.api.model.AndroidModule -import com.itsaky.androidide.utils.ILogger - -/** - * As the data is sent over streams, and the instances of properties specified in [AndroidModule] - * are just proxy classes, we need to make copy of those properties so that they can ben serialized - * by Gson. - * - * This class handles the work of making copy of those properties. - * - * @author Akash Yadav - */ -object AndroidModulePropertyCopier { - - private val log = ILogger.newInstance(javaClass.simpleName) - - fun copy(module: AndroidModule): AndroidModule { - @Suppress("DEPRECATION") - return AndroidModule( - module.name, - module.projectPath, - module.description, - module.projectDir, - module.buildDir, - module.buildScript, - module.parent, - module.tasks, - module.projectType, - module.dynamicFeatures, - copy(module.flags), - copy(module.javaCompileOptions), - module.resourcePrefix, - copy(module.variants), - copy(module.viewBindingOptions), - module.lintChecksJars, - copy(module.modelSyncFiles) - ) - } - - fun copy(viewBindingOptions: ViewBindingOptions?): DefaultViewBindingOptions? { - return when (viewBindingOptions) { - null -> null - else -> DefaultViewBindingOptions().apply { isEnabled = viewBindingOptions.isEnabled } - } - } - - @JvmName("copyVariants") - fun copy(variants: Collection): Collection { - val new = mutableListOf() - for (variant in variants) { - new.add(copy(variant)) - } - return new - } - - fun copy(variant: Variant): DefaultVariant = - DefaultVariant().apply { - androidTestArtifact = copy(variant.androidTestArtifact) - displayName = variant.displayName - isInstantAppCompatible = variant.isInstantAppCompatible - mainArtifact = copy(variant.mainArtifact)!! - name = variant.name - testFixturesArtifact = copy(variant.testFixturesArtifact) - testedTargetVariant = copy(variant.testedTargetVariant) - unitTestArtifact = copy(variant.unitTestArtifact) - desugaredMethods = emptyList() - } - fun copy(artifact: JavaArtifact?): DefaultJavaArtifact? { - if (artifact == null) { - return null - } - - return DefaultJavaArtifact().apply { - modelSyncFiles = emptyList() - assembleTaskName = artifact.assembleTaskName - classesFolders = artifact.classesFolders - compileTaskName = artifact.compileTaskName - generatedSourceFolders = artifact.generatedSourceFolders - ideSetupTaskNames = artifact.ideSetupTaskNames - mockablePlatformJar = artifact.mockablePlatformJar - runtimeResourceFolder = artifact.runtimeResourceFolder - } - } - - fun copy(variant: TestedTargetVariant?): DefaultTestedTargetVariant? { - if (variant == null) { - return null - } - - return DefaultTestedTargetVariant().apply { - targetProjectPath = variant.targetProjectPath - targetVariant = variant.targetVariant - } - } - - fun copy(artifact: AndroidArtifact?): DefaultAndroidArtifact? { - if (artifact == null) { - return null - } - - return DefaultAndroidArtifact().apply { - applicationId = artifact.applicationId - resGenTaskName = artifact.resGenTaskName - abiFilters = artifact.abiFilters - assembleTaskOutputListingFile = artifact.assembleTaskOutputListingFile - bundleInfo = copy(artifact.bundleInfo) - codeShrinker = artifact.codeShrinker - generatedResourceFolders = artifact.generatedResourceFolders - isSigned = artifact.isSigned - maxSdkVersion = artifact.maxSdkVersion - minSdkVersion = copy(artifact.minSdkVersion)!! - signingConfigName = artifact.signingConfigName - sourceGenTaskName = artifact.sourceGenTaskName - testInfo = copy(artifact.testInfo) - assembleTaskName = artifact.assembleTaskName - classesFolders = artifact.classesFolders - compileTaskName = artifact.compileTaskName - generatedSourceFolders = artifact.generatedSourceFolders - ideSetupTaskNames = artifact.ideSetupTaskNames - targetSdkVersionOverride = copy(artifact.targetSdkVersionOverride) - modelSyncFiles = copy(artifact.modelSyncFiles) - } - } - - @JvmName("copyModelSyncFiles") - fun copy(modelSyncFiles: Collection): List { - val new = mutableListOf() - for (file in modelSyncFiles) { - new.add(copy(file)) - } - - return new - } - - fun copy(file: ModelSyncFile): DefaultModelSyncFile = - DefaultModelSyncFile().apply { - modelSyncType = file.modelSyncType - syncFile = file.syncFile - taskName = file.taskName - } - - fun copy(info: TestInfo?): DefaultTestInfo? { - if (info == null) { - return null - } - - return DefaultTestInfo().apply { - additionalRuntimeApks = info.additionalRuntimeApks - animationsDisabled = info.animationsDisabled - execution = info.execution - instrumentedTestTaskName = info.instrumentedTestTaskName - } - } - - fun copy(version: ApiVersion?): DefaultApiVersion? = - if (version == null) null - else - DefaultApiVersion().apply { - apiLevel = version.apiLevel - codename = version.codename - } - - fun copy(bundleInfo: BundleInfo?): DefaultBundleInfo? { - if (bundleInfo == null) { - return null - } - - return DefaultBundleInfo().apply { - apkFromBundleTaskName = bundleInfo.apkFromBundleTaskName - apkFromBundleTaskOutputListingFile = bundleInfo.apkFromBundleTaskOutputListingFile - bundleTaskName = bundleInfo.bundleTaskName - bundleTaskOutputListingFile = bundleInfo.bundleTaskOutputListingFile - } - } - - fun copy(javaCompileOptions: JavaCompileOptions): DefaultJavaCompileOptions { - return DefaultJavaCompileOptions().apply { - encoding = javaCompileOptions.encoding - isCoreLibraryDesugaringEnabled = javaCompileOptions.isCoreLibraryDesugaringEnabled - sourceCompatibility = javaCompileOptions.sourceCompatibility - targetCompatibility = javaCompileOptions.targetCompatibility - } - } - - fun copy( - @Suppress("UNUSED_PARAMETER") flags: AndroidGradlePluginProjectFlags - ): DefaultAndroidGradlePluginProjectFlags { - val flagMap: MutableMap = mutableMapOf() - // log.debug("Flags:", flags) - // - // flagMap[APPLICATION_R_CLASS_CONSTANT_IDS] = - // flags.getFlagValue(APPLICATION_R_CLASS_CONSTANT_IDS.name) - // flagMap[JETPACK_COMPOSE] = flags.getFlagValue(JETPACK_COMPOSE.name) - // flagMap[ML_MODEL_BINDING] = flags.getFlagValue(ML_MODEL_BINDING.name) - // flagMap[TEST_R_CLASS_CONSTANT_IDS] = - // flags.getFlagValue(TEST_R_CLASS_CONSTANT_IDS.name) - // flagMap[TRANSITIVE_R_CLASS] = flags.getFlagValue(TRANSITIVE_R_CLASS.name) - // flagMap[UNIFIED_TEST_PLATFORM] = flags.getFlagValue(UNIFIED_TEST_PLATFORM.name) - // - // log.debug("Flag map:", flagMap) - return DefaultAndroidGradlePluginProjectFlags(flagMap) - } - - fun copy(containers: Collection): Collection { - val new = mutableListOf() - - for (container in containers) { - new.add(copy(container)) - } - - return new - } - - fun copy(container: SourceSetContainer): DefaultSourceSetContainer { - return DefaultSourceSetContainer().apply { - androidTestSourceProvider = copy(container.androidTestSourceProvider) - sourceProvider = copy(container.sourceProvider)!! - testFixturesSourceProvider = copy(container.testFixturesSourceProvider) - unitTestSourceProvider = copy(container.unitTestSourceProvider) - } - } - - fun copy(provider: SourceProvider?): DefaultSourceProvider? { - if (provider == null) { - return null - } - - return DefaultSourceProvider().apply { - aidlDirectories = provider.aidlDirectories - assetsDirectories = provider.assetsDirectories - customDirectories = copy(provider.customDirectories) - javaDirectories = provider.javaDirectories - jniLibsDirectories = provider.jniLibsDirectories - kotlinDirectories = provider.kotlinDirectories - manifestFile = provider.manifestFile - mlModelsDirectories = provider.mlModelsDirectories - name = provider.name - renderscriptDirectories = provider.renderscriptDirectories - resDirectories = provider.resDirectories - resourcesDirectories = provider.resourcesDirectories - shadersDirectories = provider.shadersDirectories - } - } - - @JvmName("copyCustomSourceDirectories") - private fun copy( - directories: Collection? - ): Collection? { - return directories?.map { copy(it) } - } - - private fun copy(it: CustomSourceDirectory): DefaultCustomSourceDirectory { - return DefaultCustomSourceDirectory(it.directory, it.sourceTypeName) - } - - fun copy(variantDependencies: VariantDependencies): DefaultVariantDependencies { - return DefaultVariantDependencies().apply { - this.name = variantDependencies.name - this.libraries = copy(variantDependencies.libraries) - this.mainArtifact = copy(variantDependencies.mainArtifact)!! - this.androidTestArtifact = copy(variantDependencies.androidTestArtifact) - this.testFixturesArtifact = copy(variantDependencies.testFixturesArtifact) - this.unitTestArtifact = copy(variantDependencies.unitTestArtifact) - } - } - - private fun copy(libraries: Map): Map { - val new = mutableMapOf() - for ((key, value) in libraries) { - new[key] = copy(value) - } - - return new - } - - fun copy(value: Library): DefaultLibrary { - return DefaultLibrary().apply { - this.androidLibraryData = copy(value.androidLibraryData) - this.artifact = value.artifact - this.key = value.key - this.libraryInfo = copy(value.libraryInfo) - this.lintJar = value.lintJar - this.projectInfo = copy(value.projectInfo) - this.type = value.type - } - } - - private fun copy(info: ProjectInfo?): DefaultProjectInfo? { - return if (info == null) null - else - DefaultProjectInfo( - info.attributes, - info.buildType, - info.capabilities, - info.isTestFixtures, - info.productFlavors, - info.buildId, - info.projectPath - ) - } - - private fun copy(info: LibraryInfo?): DefaultLibraryInfo? { - return if (info == null) null - else - DefaultLibraryInfo( - info.attributes, - info.buildType, - info.capabilities, - info.isTestFixtures, - info.productFlavors, - info.group, - info.name, - info.version - ) - } - - private fun copy(data: AndroidLibraryData?): DefaultAndroidLibraryData? { - return if (data == null) null - else - DefaultAndroidLibraryData( - data.aidlFolder, - data.assetsFolder, - data.compileJarFiles, - data.externalAnnotations, - data.jniFolder, - data.manifest, - data.proguardRules, - data.publicResources, - data.renderscriptFolder, - data.resFolder, - data.resStaticLibrary, - data.runtimeJarFiles, - data.symbolFile - ) - } - - fun copy(artifact: ArtifactDependencies?): DefaultArtifactDependencies? = - DefaultArtifactDependencies().apply { - if (artifact == null) { - return null - } - - this.compileDependencies = copy(artifact.compileDependencies) - this.runtimeDependencies = copy(artifact.runtimeDependencies) - this.unresolvedDependencies = copy(artifact.unresolvedDependencies) - } - - private fun copy(dependencies: List): List { - return dependencies.map { copy(it) } - } - - private fun copy(it: UnresolvedDependency): DefaultUnresolvedDependency { - return DefaultUnresolvedDependency(it.cause, it.name) - } - - @JvmName("copyGraphItems") - fun copy(graphs: List): List { - return graphs.map { copy(it) } - } - - fun copy(graph: GraphItem): DefaultGraphItem = - DefaultGraphItem().apply { - this.key = graph.key - this.dependencies = copy(graph.dependencies) - this.requestedCoordinates = graph.requestedCoordinates - } - - fun copy(issues: ProjectSyncIssues): DefaultProjectSyncIssues = - DefaultProjectSyncIssues(copy(issues.syncIssues)) - - @JvmName("copySyncIssue") - fun copy(syncIssues: Collection): Collection { - return syncIssues.map { copy(it) } - } - - fun copy(issue: SyncIssue) = - DefaultSyncIssue(issue.data, issue.message, issue.multiLineMessage, issue.severity, issue.type) - - fun asSimpleVariants(variants: MutableCollection): MutableList { - return mutableListOf().apply { - for (variant in variants) { - add( - SimpleVariantData( - variant.name, - asSimpleArtifact("main", variant.mainArtifact), - mutableMapOf() - ) - ) - } - } - } - - @Suppress("SameParameterValue") // We might need details about other artifacts in future - private fun asSimpleArtifact(name: String, artifact: AndroidArtifact): SimpleArtifact { - return SimpleArtifact( - name = name, - assembleTaskName = artifact.assembleTaskName, - assembleTaskOutputListingFile = artifact.assembleTaskOutputListingFile, - classJars = artifact.classesFolders.filter { it.name.endsWith(".jar") }, - compileTaskName = artifact.compileTaskName, - generatedResourceFolders = artifact.generatedResourceFolders, - generatedSourceFolders = artifact.generatedSourceFolders, - maxSdkVersion = artifact.maxSdkVersion, - minSdkVersion = artifact.minSdkVersion.apiLevel, - resGenTaskName = artifact.resGenTaskName, - signingConfigName = artifact.signingConfigName, - sourceGenTaskName = artifact.sourceGenTaskName, - targetSdkVersionOverride = artifact.targetSdkVersionOverride?.apiLevel ?: -1 - ) - } -} +/* + * 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.util + +import com.android.builder.model.v2.CustomSourceDirectory +import com.android.builder.model.v2.ModelSyncFile +import com.android.builder.model.v2.ide.AndroidArtifact +import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags +import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags.BooleanFlag +import com.android.builder.model.v2.ide.AndroidLibraryData +import com.android.builder.model.v2.ide.ApiVersion +import com.android.builder.model.v2.ide.ArtifactDependencies +import com.android.builder.model.v2.ide.BundleInfo +import com.android.builder.model.v2.ide.GraphItem +import com.android.builder.model.v2.ide.JavaArtifact +import com.android.builder.model.v2.ide.JavaCompileOptions +import com.android.builder.model.v2.ide.Library +import com.android.builder.model.v2.ide.LibraryInfo +import com.android.builder.model.v2.ide.ProjectInfo +import com.android.builder.model.v2.ide.SourceProvider +import com.android.builder.model.v2.ide.SourceSetContainer +import com.android.builder.model.v2.ide.SyncIssue +import com.android.builder.model.v2.ide.TestInfo +import com.android.builder.model.v2.ide.TestedTargetVariant +import com.android.builder.model.v2.ide.UnresolvedDependency +import com.android.builder.model.v2.ide.Variant +import com.android.builder.model.v2.ide.ViewBindingOptions +import com.android.builder.model.v2.models.ProjectSyncIssues +import com.android.builder.model.v2.models.VariantDependencies +import com.itsaky.androidide.builder.model.DefaultAndroidArtifact +import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlags +import com.itsaky.androidide.builder.model.DefaultAndroidLibraryData +import com.itsaky.androidide.builder.model.DefaultApiVersion +import com.itsaky.androidide.builder.model.DefaultArtifactDependencies +import com.itsaky.androidide.builder.model.DefaultBundleInfo +import com.itsaky.androidide.builder.model.DefaultCustomSourceDirectory +import com.itsaky.androidide.builder.model.DefaultGraphItem +import com.itsaky.androidide.builder.model.DefaultJavaArtifact +import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions +import com.itsaky.androidide.builder.model.DefaultLibrary +import com.itsaky.androidide.builder.model.DefaultLibraryInfo +import com.itsaky.androidide.builder.model.DefaultModelSyncFile +import com.itsaky.androidide.builder.model.DefaultProjectInfo +import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues +import com.itsaky.androidide.builder.model.DefaultSourceProvider +import com.itsaky.androidide.builder.model.DefaultSourceSetContainer +import com.itsaky.androidide.builder.model.DefaultSyncIssue +import com.itsaky.androidide.builder.model.DefaultTestInfo +import com.itsaky.androidide.builder.model.DefaultTestedTargetVariant +import com.itsaky.androidide.builder.model.DefaultUnresolvedDependency +import com.itsaky.androidide.builder.model.DefaultVariant +import com.itsaky.androidide.builder.model.DefaultVariantDependencies +import com.itsaky.androidide.builder.model.DefaultViewBindingOptions +import com.itsaky.androidide.utils.ILogger + +/** + * As the data is sent over streams, and the instances of properties specified in [AndroidModule] + * are just proxy classes, we need to make copy of those properties so that they can ben serialized + * by Gson. + * + * This class handles the work of making copy of those properties. + * + * @author Akash Yadav + */ +object AndroidModulePropertyCopier { + + private val log = ILogger.newInstance(javaClass.simpleName) + + fun copy(viewBindingOptions: ViewBindingOptions?): DefaultViewBindingOptions? { + return when (viewBindingOptions) { + null -> null + else -> DefaultViewBindingOptions().apply { isEnabled = viewBindingOptions.isEnabled } + } + } + + @JvmName("copyModelSyncFiles") + fun copy(modelSyncFiles: Collection): List { + val new = mutableListOf() + for (file in modelSyncFiles) { + new.add(copy(file)) + } + + return new + } + + fun copy(file: ModelSyncFile): DefaultModelSyncFile = + DefaultModelSyncFile().apply { + modelSyncType = file.modelSyncType + syncFile = file.syncFile + taskName = file.taskName + } + + fun copy(version: ApiVersion?): DefaultApiVersion? = + if (version == null) null + else + DefaultApiVersion().apply { + apiLevel = version.apiLevel + codename = version.codename + } + + fun copy(javaCompileOptions: JavaCompileOptions): DefaultJavaCompileOptions { + return DefaultJavaCompileOptions().apply { + encoding = javaCompileOptions.encoding + isCoreLibraryDesugaringEnabled = javaCompileOptions.isCoreLibraryDesugaringEnabled + sourceCompatibility = javaCompileOptions.sourceCompatibility + targetCompatibility = javaCompileOptions.targetCompatibility + } + } + + fun copy( + @Suppress("UNUSED_PARAMETER") flags: AndroidGradlePluginProjectFlags + ): DefaultAndroidGradlePluginProjectFlags { + val flagMap: MutableMap = mutableMapOf() + // log.debug("Flags:", flags) + // + // flagMap[APPLICATION_R_CLASS_CONSTANT_IDS] = + // flags.getFlagValue(APPLICATION_R_CLASS_CONSTANT_IDS.name) + // flagMap[JETPACK_COMPOSE] = flags.getFlagValue(JETPACK_COMPOSE.name) + // flagMap[ML_MODEL_BINDING] = flags.getFlagValue(ML_MODEL_BINDING.name) + // flagMap[TEST_R_CLASS_CONSTANT_IDS] = + // flags.getFlagValue(TEST_R_CLASS_CONSTANT_IDS.name) + // flagMap[TRANSITIVE_R_CLASS] = flags.getFlagValue(TRANSITIVE_R_CLASS.name) + // flagMap[UNIFIED_TEST_PLATFORM] = flags.getFlagValue(UNIFIED_TEST_PLATFORM.name) + // + // log.debug("Flag map:", flagMap) + return DefaultAndroidGradlePluginProjectFlags(flagMap) + } + + fun copy(containers: Collection): Collection { + val new = mutableListOf() + + for (container in containers) { + new.add(copy(container)) + } + + return new + } + + fun copy(container: SourceSetContainer): DefaultSourceSetContainer { + return DefaultSourceSetContainer().apply { + androidTestSourceProvider = copy(container.androidTestSourceProvider) + sourceProvider = copy(container.sourceProvider)!! + testFixturesSourceProvider = copy(container.testFixturesSourceProvider) + unitTestSourceProvider = copy(container.unitTestSourceProvider) + } + } + + fun copy(provider: SourceProvider?): DefaultSourceProvider? { + if (provider == null) { + return null + } + + return DefaultSourceProvider().apply { + aidlDirectories = provider.aidlDirectories + assetsDirectories = provider.assetsDirectories + customDirectories = copy(provider.customDirectories) + javaDirectories = provider.javaDirectories + jniLibsDirectories = provider.jniLibsDirectories + kotlinDirectories = provider.kotlinDirectories + manifestFile = provider.manifestFile + mlModelsDirectories = provider.mlModelsDirectories + name = provider.name + renderscriptDirectories = provider.renderscriptDirectories + resDirectories = provider.resDirectories + resourcesDirectories = provider.resourcesDirectories + shadersDirectories = provider.shadersDirectories + } + } + + @JvmName("copyCustomSourceDirectories") + private fun copy( + directories: Collection? + ): Collection? { + return directories?.map { copy(it) } + } + + private fun copy(it: CustomSourceDirectory): DefaultCustomSourceDirectory { + return DefaultCustomSourceDirectory(it.directory, it.sourceTypeName) + } + + private fun copy(libraries: Map): Map { + val new = mutableMapOf() + for ((key, value) in libraries) { + new[key] = copy(value) + } + + return new + } + + fun copy(value: Library): DefaultLibrary { + return DefaultLibrary().apply { + this.androidLibraryData = copy(value.androidLibraryData) + this.artifact = value.artifact + this.key = value.key + this.libraryInfo = copy(value.libraryInfo) + this.lintJar = value.lintJar + this.projectInfo = copy(value.projectInfo) + this.type = value.type + } + } + + private fun copy(info: ProjectInfo?): DefaultProjectInfo? { + return if (info == null) null + else + DefaultProjectInfo( + info.attributes, + info.buildType, + info.capabilities, + info.isTestFixtures, + info.productFlavors, + info.buildId, + info.projectPath + ) + } + + private fun copy(info: LibraryInfo?): DefaultLibraryInfo? { + return if (info == null) null + else + DefaultLibraryInfo( + info.attributes, + info.buildType, + info.capabilities, + info.isTestFixtures, + info.productFlavors, + info.group, + info.name, + info.version + ) + } + + private fun copy(data: AndroidLibraryData?): DefaultAndroidLibraryData? { + return if (data == null) null + else + DefaultAndroidLibraryData( + data.aidlFolder, + data.assetsFolder, + data.compileJarFiles, + data.externalAnnotations, + data.jniFolder, + data.manifest, + data.proguardRules, + data.publicResources, + data.renderscriptFolder, + data.resFolder, + data.resStaticLibrary, + data.runtimeJarFiles, + data.symbolFile + ) + } + + fun copy(artifact: ArtifactDependencies?): DefaultArtifactDependencies? = + DefaultArtifactDependencies().apply { + if (artifact == null) { + return null + } + + this.compileDependencies = copy(artifact.compileDependencies) + this.runtimeDependencies = copy(artifact.runtimeDependencies) + this.unresolvedDependencies = copy(artifact.unresolvedDependencies) + } + + private fun copy(dependencies: List): List { + return dependencies.map { copy(it) } + } + + private fun copy(it: UnresolvedDependency): DefaultUnresolvedDependency { + return DefaultUnresolvedDependency(it.cause, it.name) + } + + @JvmName("copyGraphItems") + fun copy(graphs: List): List { + return graphs.map { copy(it) } + } + + fun copy(graph: GraphItem): DefaultGraphItem = + DefaultGraphItem().apply { + this.key = graph.key + this.dependencies = copy(graph.dependencies) + this.requestedCoordinates = graph.requestedCoordinates + } + + fun copy(issues: ProjectSyncIssues): DefaultProjectSyncIssues = + DefaultProjectSyncIssues(copy(issues.syncIssues)) + + @JvmName("copySyncIssue") + fun copy(syncIssues: Collection): Collection { + return syncIssues.map { copy(it) } + } + + fun copy(issue: SyncIssue) = + DefaultSyncIssue(issue.data, issue.message, issue.multiLineMessage, issue.severity, issue.type) +} \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/utils.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/utils.kt similarity index 88% rename from subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/utils.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/utils.kt index ba535fb1e..59011fd75 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/model/util/utils.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/utils.kt @@ -15,15 +15,15 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.tooling.api.model.util +package com.itsaky.androidide.tooling.api.util import com.android.builder.model.v2.ide.LibraryType.ANDROID_LIBRARY import com.itsaky.androidide.builder.model.DefaultLibrary import com.itsaky.androidide.builder.model.UNKNOWN_PACKAGE -import com.itsaky.androidide.tooling.api.model.AndroidModule -import java.io.File +import com.itsaky.androidide.tooling.api.IAndroidProject import org.eclipse.lemminx.dom.DOMParser import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager +import java.io.File /** * Find the package name for this library. If this library is not an [ANDROID_LIBRARY] or if error @@ -50,16 +50,16 @@ fun DefaultLibrary.findPackageName(): String { return this.packageName } -/** Extractes package name from the given `AndroidManifest.xml` file. */ +/** Extracts package name from the given `AndroidManifest.xml` file. */ fun extractPackageName(manifestFile: File): String? { val content = manifestFile.readText() val document = DOMParser.getInstance() - .parse(content, AndroidModule.ANDROID_NAMESPACE, URIResolverExtensionManager()) + .parse(content, IAndroidProject.ANDROID_NAMESPACE, URIResolverExtensionManager()) val manifest = document.children.first { it.nodeName == "manifest" } ?: return null val packageAttr = manifest.attributes.getNamedItem("package") if (packageAttr != null) { return packageAttr.nodeValue } return null -} +} \ No newline at end of file diff --git a/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt b/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt index 0acdd46b5..a91a9ad03 100644 --- a/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt +++ b/subprojects/tooling-api-testing/src/main/java/com/itsaky/androidide/tooling/testing/ToolingApiTestLauncher.kt @@ -93,6 +93,7 @@ class ToolingApiTestLauncher { cmd[0] = java.absolutePath } } +// cmd.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000") for (open in opens) { cmd.add("--add-opens=${open.key}/${open.value}=ALL-UNNAMED") } diff --git a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt index 9b123d47c..76b13dc5b 100644 --- a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt +++ b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/IToolingApiServer.kt @@ -22,7 +22,6 @@ import com.itsaky.androidide.tooling.api.messages.TaskExecutionMessage import com.itsaky.androidide.tooling.api.messages.result.BuildCancellationRequestResult import com.itsaky.androidide.tooling.api.messages.result.InitializeResult import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult -import com.itsaky.androidide.tooling.api.model.IdeGradleProject import org.eclipse.lsp4j.jsonrpc.services.JsonNotification import org.eclipse.lsp4j.jsonrpc.services.JsonRequest import org.eclipse.lsp4j.jsonrpc.services.JsonSegment @@ -43,7 +42,7 @@ interface IToolingApiServer { @JsonRequest fun isServerInitialized(): CompletableFuture /** Get the root project. */ - @JsonRequest fun getRootProject(): CompletableFuture + @JsonRequest fun getRootProject(): CompletableFuture /** Execute the tasks specified in the message. */ @JsonRequest diff --git a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectMessage.kt b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectMessage.kt index 79cc93b86..6ff696cdf 100644 --- a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectMessage.kt +++ b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/messages/InitializeProjectMessage.kt @@ -22,4 +22,4 @@ package com.itsaky.androidide.tooling.api.messages * * @author Akash Yadav */ -data class InitializeProjectMessage(val directory: String, val gradleInstallation: String = "") +data class InitializeProjectMessage(val directory: String, val androidVariant: String, val gradleInstallation: String = "") diff --git a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java deleted file mode 100644 index 1e93659d7..000000000 --- a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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.util; - -import com.google.gson.GsonBuilder; -import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions; -import com.itsaky.androidide.builder.model.IJavaCompilerSettings; -import com.itsaky.androidide.tooling.api.IProject; -import com.itsaky.androidide.tooling.api.IToolingApiClient; -import com.itsaky.androidide.tooling.api.IToolingApiServer; -import com.itsaky.androidide.tooling.api.model.AndroidModule; -import com.itsaky.androidide.tooling.api.model.GradleTask; -import com.itsaky.androidide.tooling.api.model.IdeGradleProject; -import com.itsaky.androidide.tooling.api.model.IdeLaunchable; -import com.itsaky.androidide.tooling.api.model.JavaModule; -import com.itsaky.androidide.tooling.api.model.JavaModuleCompilerSettings; -import com.itsaky.androidide.tooling.api.model.JavaModuleDependency; -import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency; -import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency; -import com.itsaky.androidide.tooling.events.OperationDescriptor; -import com.itsaky.androidide.tooling.events.OperationResult; -import com.itsaky.androidide.tooling.events.ProgressEvent; -import com.itsaky.androidide.tooling.events.StatusEvent; -import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationFinishEvent; -import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationOperationDescriptor; -import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationOperationResult; -import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationProgressEvent; -import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationStartEvent; -import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent; -import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor; -import com.itsaky.androidide.tooling.events.download.FileDownloadProgressEvent; -import com.itsaky.androidide.tooling.events.download.FileDownloadResult; -import com.itsaky.androidide.tooling.events.download.FileDownloadStartEvent; -import com.itsaky.androidide.tooling.events.internal.DefaultFinishEvent; -import com.itsaky.androidide.tooling.events.internal.DefaultOperationDescriptor; -import com.itsaky.androidide.tooling.events.internal.DefaultOperationResult; -import com.itsaky.androidide.tooling.events.internal.DefaultProgressEvent; -import com.itsaky.androidide.tooling.events.internal.DefaultStartEvent; -import com.itsaky.androidide.tooling.events.task.TaskExecutionResult; -import com.itsaky.androidide.tooling.events.task.TaskFailureResult; -import com.itsaky.androidide.tooling.events.task.TaskFinishEvent; -import com.itsaky.androidide.tooling.events.task.TaskOperationDescriptor; -import com.itsaky.androidide.tooling.events.task.TaskOperationResult; -import com.itsaky.androidide.tooling.events.task.TaskProgressEvent; -import com.itsaky.androidide.tooling.events.task.TaskSkippedResult; -import com.itsaky.androidide.tooling.events.task.TaskStartEvent; -import com.itsaky.androidide.tooling.events.task.TaskSuccessResult; -import com.itsaky.androidide.tooling.events.test.TestFinishEvent; -import com.itsaky.androidide.tooling.events.test.TestOperationDescriptor; -import com.itsaky.androidide.tooling.events.test.TestOperationResult; -import com.itsaky.androidide.tooling.events.test.TestProgressEvent; -import com.itsaky.androidide.tooling.events.test.TestStartEvent; -import com.itsaky.androidide.tooling.events.transform.TransformFinishEvent; -import com.itsaky.androidide.tooling.events.transform.TransformOperationDescriptor; -import com.itsaky.androidide.tooling.events.transform.TransformOperationResult; -import com.itsaky.androidide.tooling.events.transform.TransformProgressEvent; -import com.itsaky.androidide.tooling.events.transform.TransformStartEvent; -import com.itsaky.androidide.tooling.events.work.WorkItemFinishEvent; -import com.itsaky.androidide.tooling.events.work.WorkItemOperationDescriptor; -import com.itsaky.androidide.tooling.events.work.WorkItemOperationResult; -import com.itsaky.androidide.tooling.events.work.WorkItemProgressEvent; -import com.itsaky.androidide.tooling.events.work.WorkItemStartEvent; - -import org.eclipse.lsp4j.jsonrpc.Launcher; - -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.concurrent.Executors; - -/** - * Utility class for launching {@link IToolingApiClient} and {@link IToolingApiServer}. - * - * @author Akash Yadav - */ -public class ToolingApiLauncher { - - public static Launcher createIOLauncher( - Object local, Class remote, InputStream in, OutputStream out) { - return new Launcher.Builder() - .setInput(in) - .setOutput(out) - .setLocalService(local) - .setRemoteInterface(remote) - .configureGson(ToolingApiLauncher::configureGson) - .create(); - } - - public static void configureGson(GsonBuilder builder) { - builder.registerTypeAdapter(File.class, new FileTypeAdapter()); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(IdeGradleProject.class, "gsonType", true) - .registerSubtype(AndroidModule.class, AndroidModule.class.getName()) - .registerSubtype(JavaModule.class, JavaModule.class.getName()) - .registerSubtype(IdeGradleProject.class, IdeGradleProject.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(IdeLaunchable.class, "gsonType", true) - .registerSubtype(GradleTask.class, GradleTask.class.getName()) - .registerSubtype(IdeLaunchable.class, IdeLaunchable.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(JavaModuleDependency.class, "gsonType", true) - .registerSubtype( - JavaModuleExternalDependency.class, JavaModuleExternalDependency.class.getName()) - .registerSubtype( - JavaModuleProjectDependency.class, JavaModuleProjectDependency.class.getName())); - - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(IJavaCompilerSettings.class) - .registerSubtype( - DefaultJavaCompileOptions.class, DefaultJavaCompileOptions.class.getName()) - .registerSubtype( - JavaModuleCompilerSettings.class, JavaModuleCompilerSettings.class.getName())); - - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(ProgressEvent.class, "gsonType", true) - - // Project configuration - .registerSubtype( - ProjectConfigurationProgressEvent.class, - ProjectConfigurationProgressEvent.class.getName()) - .registerSubtype( - ProjectConfigurationStartEvent.class, - ProjectConfigurationStartEvent.class.getName()) - .registerSubtype( - ProjectConfigurationFinishEvent.class, - ProjectConfigurationFinishEvent.class.getName()) - - // File download - .registerSubtype( - FileDownloadProgressEvent.class, FileDownloadProgressEvent.class.getName()) - .registerSubtype(FileDownloadStartEvent.class, FileDownloadStartEvent.class.getName()) - .registerSubtype(FileDownloadFinishEvent.class, FileDownloadFinishEvent.class.getName()) - - // Task execution - .registerSubtype(TaskProgressEvent.class, TaskProgressEvent.class.getName()) - .registerSubtype(TaskStartEvent.class, TaskStartEvent.class.getName()) - .registerSubtype(TaskFinishEvent.class, TaskFinishEvent.class.getName()) - - // Test execution - .registerSubtype(TestProgressEvent.class, TestProgressEvent.class.getName()) - .registerSubtype(TestStartEvent.class, TestStartEvent.class.getName()) - .registerSubtype(TestFinishEvent.class, TestFinishEvent.class.getName()) - - // Transform - .registerSubtype(TransformProgressEvent.class, TransformProgressEvent.class.getName()) - .registerSubtype(TransformStartEvent.class, TransformStartEvent.class.getName()) - .registerSubtype(TransformFinishEvent.class, TransformFinishEvent.class.getName()) - - // Work item - .registerSubtype(WorkItemProgressEvent.class, WorkItemProgressEvent.class.getName()) - .registerSubtype(WorkItemStartEvent.class, WorkItemStartEvent.class.getName()) - .registerSubtype(WorkItemFinishEvent.class, WorkItemFinishEvent.class.getName()) - - // Default implementations - .registerSubtype(DefaultProgressEvent.class, DefaultProgressEvent.class.getName()) - .registerSubtype(DefaultStartEvent.class, DefaultStartEvent.class.getName()) - .registerSubtype(DefaultFinishEvent.class, DefaultFinishEvent.class.getName()) - - // Status event - .registerSubtype(StatusEvent.class, StatusEvent.class.getName())); - - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(OperationDescriptor.class, "gsonType", true) - .registerSubtype( - ProjectConfigurationOperationDescriptor.class, - ProjectConfigurationOperationDescriptor.class.getName()) - .registerSubtype( - FileDownloadOperationDescriptor.class, - FileDownloadOperationDescriptor.class.getName()) - .registerSubtype(TaskOperationDescriptor.class, TaskOperationDescriptor.class.getName()) - .registerSubtype(TestOperationDescriptor.class, TestOperationDescriptor.class.getName()) - .registerSubtype( - TransformOperationDescriptor.class, TransformOperationDescriptor.class.getName()) - .registerSubtype( - WorkItemOperationDescriptor.class, WorkItemOperationDescriptor.class.getName()) - .registerSubtype( - DefaultOperationDescriptor.class, DefaultOperationDescriptor.class.getName())); - - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(OperationResult.class, "gsonType", true) - .registerSubtype( - ProjectConfigurationOperationResult.class, - ProjectConfigurationOperationResult.class.getName()) - .registerSubtype(FileDownloadResult.class, FileDownloadResult.class.getName()) - .registerSubtype(TaskOperationResult.class, TaskOperationResult.class.getName()) - .registerSubtype(TestOperationResult.class, TestOperationResult.class.getName()) - .registerSubtype( - TransformOperationResult.class, TransformOperationResult.class.getName()) - .registerSubtype(WorkItemOperationResult.class, WorkItemOperationResult.class.getName()) - .registerSubtype(DefaultOperationResult.class, DefaultOperationResult.class.getName())); - - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(TaskOperationResult.class, "gsonType", true) - .registerSubtype(TaskFailureResult.class, TaskFailureResult.class.getName()) - .registerSubtype(TaskSkippedResult.class, TaskSkippedResult.class.getName()) - .registerSubtype(TaskExecutionResult.class, TaskExecutionResult.class.getName()) - .registerSubtype(TaskSuccessResult.class, TaskSuccessResult.class.getName())); - } - - public static Launcher newClientLauncher( - IToolingApiClient client, InputStream in, OutputStream out) { - return newIoLauncher( - new Object[] {client}, new Class[] {IToolingApiServer.class, IProject.class}, in, out); - } - - public static Launcher newIoLauncher( - Object[] locals, Class[] remotes, InputStream in, OutputStream out) { - return new Launcher.Builder<>() - .setInput(in) - .setOutput(out) - .setExecutorService(Executors.newCachedThreadPool()) - .setLocalServices(Arrays.asList(locals)) - .setRemoteInterfaces(Arrays.asList(remotes)) - .configureGson(ToolingApiLauncher::configureGson) - .setClassLoader(locals[0].getClass().getClassLoader()) - .create(); - } - - public static Launcher newServerLauncher( - IToolingApiServer server, IProject project, InputStream in, OutputStream out) { - return newIoLauncher( - new Object[] {server, project}, new Class[] {IToolingApiClient.class}, in, out); - } -} diff --git a/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.kt b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.kt new file mode 100644 index 000000000..94e08109f --- /dev/null +++ b/subprojects/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.kt @@ -0,0 +1,227 @@ +/* + * 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.util + +import com.google.gson.GsonBuilder +import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions +import com.itsaky.androidide.builder.model.IJavaCompilerSettings +import com.itsaky.androidide.tooling.api.IProject +import com.itsaky.androidide.tooling.api.IToolingApiClient +import com.itsaky.androidide.tooling.api.IToolingApiServer +import com.itsaky.androidide.tooling.api.models.AndroidProjectMetadata +import com.itsaky.androidide.tooling.api.models.AndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.GradleTask +import com.itsaky.androidide.tooling.api.models.JavaModuleCompilerSettings +import com.itsaky.androidide.tooling.api.models.JavaModuleDependency +import com.itsaky.androidide.tooling.api.models.JavaModuleExternalDependency +import com.itsaky.androidide.tooling.api.models.JavaModuleProjectDependency +import com.itsaky.androidide.tooling.api.models.Launchable +import com.itsaky.androidide.tooling.api.models.ProjectMetadata +import com.itsaky.androidide.tooling.events.OperationDescriptor +import com.itsaky.androidide.tooling.events.OperationResult +import com.itsaky.androidide.tooling.events.ProgressEvent +import com.itsaky.androidide.tooling.events.StatusEvent +import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationFinishEvent +import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationOperationDescriptor +import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationOperationResult +import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationProgressEvent +import com.itsaky.androidide.tooling.events.configuration.ProjectConfigurationStartEvent +import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent +import com.itsaky.androidide.tooling.events.download.FileDownloadOperationDescriptor +import com.itsaky.androidide.tooling.events.download.FileDownloadProgressEvent +import com.itsaky.androidide.tooling.events.download.FileDownloadResult +import com.itsaky.androidide.tooling.events.download.FileDownloadStartEvent +import com.itsaky.androidide.tooling.events.internal.DefaultFinishEvent +import com.itsaky.androidide.tooling.events.internal.DefaultOperationDescriptor +import com.itsaky.androidide.tooling.events.internal.DefaultOperationResult +import com.itsaky.androidide.tooling.events.internal.DefaultProgressEvent +import com.itsaky.androidide.tooling.events.internal.DefaultStartEvent +import com.itsaky.androidide.tooling.events.task.TaskExecutionResult +import com.itsaky.androidide.tooling.events.task.TaskFailureResult +import com.itsaky.androidide.tooling.events.task.TaskFinishEvent +import com.itsaky.androidide.tooling.events.task.TaskOperationDescriptor +import com.itsaky.androidide.tooling.events.task.TaskOperationResult +import com.itsaky.androidide.tooling.events.task.TaskProgressEvent +import com.itsaky.androidide.tooling.events.task.TaskSkippedResult +import com.itsaky.androidide.tooling.events.task.TaskStartEvent +import com.itsaky.androidide.tooling.events.task.TaskSuccessResult +import com.itsaky.androidide.tooling.events.test.TestFinishEvent +import com.itsaky.androidide.tooling.events.test.TestOperationDescriptor +import com.itsaky.androidide.tooling.events.test.TestOperationResult +import com.itsaky.androidide.tooling.events.test.TestProgressEvent +import com.itsaky.androidide.tooling.events.test.TestStartEvent +import com.itsaky.androidide.tooling.events.transform.TransformFinishEvent +import com.itsaky.androidide.tooling.events.transform.TransformOperationDescriptor +import com.itsaky.androidide.tooling.events.transform.TransformProgressEvent +import com.itsaky.androidide.tooling.events.transform.TransformStartEvent +import com.itsaky.androidide.tooling.events.work.WorkItemFinishEvent +import com.itsaky.androidide.tooling.events.work.WorkItemOperationDescriptor +import com.itsaky.androidide.tooling.events.work.WorkItemOperationResult +import com.itsaky.androidide.tooling.events.work.WorkItemProgressEvent +import com.itsaky.androidide.tooling.events.work.WorkItemStartEvent +import org.eclipse.lsp4j.jsonrpc.Launcher +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.util.concurrent.Executors + +/** + * Utility class for launching [IToolingApiClient] and [IToolingApiServer]. + * + * @author Akash Yadav + */ +object ToolingApiLauncher { + + fun createIOLauncher( + local: Any?, remote: Class?, `in`: InputStream?, out: OutputStream?): Launcher { + return Launcher.Builder() + .setInput(`in`) + .setOutput(out) + .setLocalService(local) + .setRemoteInterface(remote) + .configureGson { configureGson(it) } + .create() + } + + @JvmStatic + fun configureGson(builder: GsonBuilder) { + builder.registerTypeAdapter(File::class.java, FileTypeAdapter()) + builder.runtimeTypeAdapter( + BasicProjectMetadata::class.java, + ProjectMetadata::class.java, + AndroidProjectMetadata::class.java + ) + builder.runtimeTypeAdapter( + BasicAndroidVariantMetadata::class.java, + AndroidVariantMetadata::class.java + ) + builder.runtimeTypeAdapter( + JavaModuleDependency::class.java, + JavaModuleExternalDependency::class.java, + JavaModuleProjectDependency::class.java + ) + builder.runtimeTypeAdapter( + IJavaCompilerSettings::class.java, + DefaultJavaCompileOptions::class.java, + JavaModuleCompilerSettings::class.java + ) + builder.runtimeTypeAdapter( + Launchable::class.java, + GradleTask::class.java + ) + builder.runtimeTypeAdapter( + ProgressEvent::class.java, + ProjectConfigurationProgressEvent::class.java, + ProjectConfigurationStartEvent::class.java, + ProjectConfigurationFinishEvent::class.java, + + FileDownloadProgressEvent::class.java, + FileDownloadStartEvent::class.java, + FileDownloadFinishEvent::class.java, + + TaskProgressEvent::class.java, + TaskStartEvent::class.java, + TaskFinishEvent::class.java, + + TestProgressEvent::class.java, + TestStartEvent::class.java, + TestFinishEvent::class.java, + + TransformProgressEvent::class.java, + TransformStartEvent::class.java, + TransformFinishEvent::class.java, + + WorkItemProgressEvent::class.java, + WorkItemStartEvent::class.java, + WorkItemFinishEvent::class.java, + + DefaultProgressEvent::class.java, + DefaultStartEvent::class.java, + DefaultFinishEvent::class.java, + + StatusEvent::class.java + ) + builder.runtimeTypeAdapter( + OperationDescriptor::class.java, + ProjectConfigurationOperationDescriptor::class.java, + FileDownloadOperationDescriptor::class.java, + TaskOperationDescriptor::class.java, + TestOperationDescriptor::class.java, + TransformOperationDescriptor::class.java, + WorkItemOperationDescriptor::class.java, + DefaultOperationDescriptor::class.java + ) + builder.runtimeTypeAdapter( + OperationResult::class.java, + ProjectConfigurationOperationResult::class.java, + FileDownloadResult::class.java, + TaskOperationResult::class.java, + TestOperationResult::class.java, + WorkItemOperationResult::class.java, + DefaultOperationResult::class.java + ) + builder.runtimeTypeAdapter( + TaskOperationResult::class.java, + TaskFailureResult::class.java, + TaskSkippedResult::class.java, + TaskExecutionResult::class.java, + TaskSuccessResult::class.java + ) + } + + private fun GsonBuilder.runtimeTypeAdapter(baseClass: Class, + vararg subtype: Class) { + registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(baseClass, "gsonType", true) + .registerSubtype(baseClass).also { factory -> + subtype.forEach { subtype -> + factory.registerSubtype(subtype, subtype.name) + } + } + ) + } + + fun newClientLauncher( + client: IToolingApiClient, `in`: InputStream?, out: OutputStream?): Launcher { + return newIoLauncher(arrayOf(client), arrayOf( + IToolingApiServer::class.java, IProject::class.java), `in`, out) + } + + fun newIoLauncher( + locals: Array, remotes: Array?>, `in`: InputStream?, + out: OutputStream?): Launcher { + return Launcher.Builder() + .setInput(`in`) + .setOutput(out) + .setExecutorService(Executors.newCachedThreadPool()) + .setLocalServices(listOf(*locals)) + .setRemoteInterfaces(listOf(*remotes)) + .configureGson { configureGson(it) } + .setClassLoader(locals[0].javaClass.classLoader) + .create() + } + + @JvmStatic + fun newServerLauncher( + server: IToolingApiServer, project: IProject, `in`: InputStream?, + out: OutputStream?): Launcher { + return newIoLauncher(arrayOf(server, project), arrayOf( + IToolingApiClient::class.java), `in`, out) + } +} \ No newline at end of file diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt index 6ebd5fb5d..6cd6cd14a 100644 --- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt +++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt @@ -45,7 +45,7 @@ object XmlInflaterTest { val (server, project) = ToolingApiTestLauncher().launchServer() - server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get() + server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString, "debug")).get() Lookup.getDefault().register(BuildService.KEY_PROJECT_PROXY, project) ProjectManager.setupProject()