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 6f8ad7a75..e56124e57 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 @@ -412,7 +412,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin public CompletableFuture executeTasks(@NonNull String... tasks) { checkServerStarted(); final var message = - new TaskExecutionMessage(":", Arrays.asList(tasks), getGradleInstallationDir()); + new TaskExecutionMessage(IProject.ROOT_PROJECT_PATH, Arrays.asList(tasks), getGradleInstallationDir()); return performBuildTasks(server.executeTasks(message)); } diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt index 895a2334e..8ef05fffb 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt @@ -19,16 +19,16 @@ package com.itsaky.androidide.projects import androidx.annotation.RestrictTo import androidx.annotation.RestrictTo.Scope.LIBRARY +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.IProject.Type -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 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.utils.ILogger -import java.io.File import java.util.concurrent.* /** @@ -41,156 +41,66 @@ open class CachingProject(val project: IProject) : IProject { private val log = ILogger.newInstance(javaClass.simpleName) - private val mName: String by lazy { this.project.name.get() ?: "" } - private val mDescription: String by lazy { this.project.description.get() ?: "" } - private val mProjectPath: String by lazy { this.project.projectPath.get() ?: "" } - private val mProjectDir: File by lazy { - this.project.projectDir.get() ?: File(IProject.FILE_PATH_NOT_AVAILABLE) - } - private val mBuildDir: File by lazy { - this.project.buildDir.get() ?: File(IProject.FILE_PATH_NOT_AVAILABLE) - } - private val mBuildScript: File by lazy { - this.project.buildScript.get() ?: File(IProject.FILE_PATH_NOT_AVAILABLE) - } - private val mProjectType: Type by lazy { this.project.type.get() ?: Type.Gradle } - private val mTasks = mutableListOf() + private val projects = mutableListOf() + private val syncIssues = mutableMapOf() - private var mFirstAppModule: AndroidModule? = null - private val mCachedVariants: MutableMap = mutableMapOf() - private val mCachedProjects: MutableMap = mutableMapOf() - private val mModules: MutableList = mutableListOf() - - override fun isProjectInitialized(): CompletableFuture { - return CompletableFuture.completedFuture(true) - } - - override fun getName(): CompletableFuture { - return CompletableFuture.completedFuture(this.mName) - } - - override fun getDescription(): CompletableFuture { - return CompletableFuture.completedFuture(mDescription) - } - - override fun getProjectPath(): CompletableFuture { - return CompletableFuture.completedFuture(mProjectPath) - } - - override fun getType(): CompletableFuture { - return CompletableFuture.completedFuture(mProjectType) - } - - override fun getProjectDir(): CompletableFuture { - return CompletableFuture.completedFuture(mProjectDir) - } - - override fun getBuildDir(): CompletableFuture { - return CompletableFuture.completedFuture(mBuildDir) - } - - override fun getBuildScript(): CompletableFuture { - return CompletableFuture.completedFuture(mBuildScript) - } - - override fun getTasks(): CompletableFuture> { - if (mTasks.isNotEmpty()) { - return CompletableFuture.completedFuture(mTasks) - } - - return this.project.tasks.whenComplete { tasks, throwable -> - if (throwable != null) { - log.warn("Unable to get tasks of project '$mName'", throwable) + override fun getProjects(): CompletableFuture> { + return if (this.projects.isNotEmpty()) { + log.info("Using cached project metadata...") + CompletableFuture.completedFuture(this.projects) + } else this.project.getProjects().whenComplete { projects, err -> + if (err != null || projects == null) { + log.debug("Unable to fetch project metadata from tooling server", err) return@whenComplete } - if (tasks == null || tasks.isEmpty()) { - log.warn("No tasks found in project '$mName'") + if (projects.isEmpty()) { + log.debug("Empty project metadata returned by tooling server. Ignoring...") return@whenComplete } - mTasks.clear() - mTasks.addAll(tasks) + this.projects.clear() + this.projects.addAll(projects) } } - override fun listModules(): CompletableFuture> { - return if (this.mModules.isNotEmpty()) { - log.debug("Using cached module data...") - CompletableFuture.completedFuture(mModules) - } else { - return this.project.listModules().whenComplete { modules, err -> - if (err != null || modules == null) { - log.debug("Unable to fetch module data from tooling server", err) - return@whenComplete - } - - if (modules.isEmpty()) { - log.debug("Empty module data returned by tooling server. Ignoring...") - return@whenComplete - } - - mModules.clear() - mModules.addAll(modules) - } - } - } - - override fun getVariantData(request: VariantDataRequest): CompletableFuture { - val cached = mCachedVariants[request] - if (cached != null) { - return CompletableFuture.completedFuture(cached) - } - - return this.project.getVariantData(request).whenComplete { t, u -> - if (t == null) { - log.warn( - "Unable to get variant data for " + - "variant ${request.variantName} ('${request.projectPath}') from tooling server", - u - ) + override fun getProjectSyncIssues(): CompletableFuture> { + return if (this.syncIssues.isNotEmpty()) { + log.info("Using cached sync issues...") + CompletableFuture.completedFuture(this.syncIssues) + } else this.project.getProjectSyncIssues().whenComplete { syncIssues, err -> + if (err != null || syncIssues == null) { + log.debug("Unable to fetch project sync issues from tooling server", err) return@whenComplete } - mCachedVariants[request] = t - } - } - - override fun findByPath(path: String): CompletableFuture { - if (this.mCachedProjects.containsKey(path)) { - return CompletableFuture.completedFuture(mCachedProjects[path]!!) - } - - return this.project.findByPath(path).whenComplete { project, throwable -> - if (throwable != null) { - log.warn("Unable to find project with path: '$path'", throwable) + if (syncIssues.isEmpty()) { + log.debug("No sync issues.") return@whenComplete } - if (project == null) { - log.warn("Project with path '$path' not found") - return@whenComplete - } - - mCachedProjects[path] = project + this.syncIssues.clear() + this.syncIssues.putAll(syncIssues) } } - override fun findAndroidModules(): CompletableFuture> { - return this.project.findAndroidModules() + override fun selectProject(param: StringParameter): CompletableFuture { + return project.selectProject(param) } - override fun findFirstAndroidModule(): CompletableFuture { - return this.project.findFirstAndroidModule() + override fun getType(): CompletableFuture { + return project.getType() } - override fun findFirstAndroidAppModule(): CompletableFuture { - if (mFirstAppModule != null) { - return CompletableFuture.completedFuture(mFirstAppModule) - } - - return this.project.findFirstAndroidAppModule().whenComplete { module, _ -> - this.mFirstAppModule = module - } + override fun asGradleProject(): IGradleProject { + return project.asGradleProject() } -} + + override fun asAndroidProject(): IAndroidProject { + return project.asAndroidProject() + } + + override fun asJavaProject(): IJavaProject { + return project.asJavaProject() + } +} \ No newline at end of file diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt index 81f4c279f..5e4a0fc1f 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt @@ -32,12 +32,11 @@ 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.projects.ProjectManager -import com.itsaky.androidide.tooling.api.IProject.Type.Android -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.Companion.FD_INTERMEDIATES -import com.itsaky.androidide.tooling.api.model.GradleTask -import com.itsaky.androidide.tooling.api.model.util.findPackageName +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.api.ProjectType.Android +import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata +import com.itsaky.androidide.tooling.api.models.GradleTask +import com.itsaky.androidide.tooling.api.util.findPackageName import com.itsaky.androidide.utils.ILogger import com.itsaky.androidide.xml.resources.ResourceTableRegistry import com.itsaky.androidide.xml.versions.ApiVersions @@ -68,7 +67,7 @@ import java.util.concurrent.CompletableFuture * @param projectType The type of Android project. See [ProjectType]. * @param mainSourceSet The main source of this module. * @param flags The Android Gradle Plugin flags. No-op currently. - * @param javaCompileOptions The java compilation options as configured in buildscript. + * @param compilerSettings The java compilation options as configured in buildscript. * @param viewBindingOptions The view binding options of this module. * @param bootClassPaths The boot class paths of the project. Usually contains the path to the * `android.jar` file. @@ -97,15 +96,15 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. val projectType: ProjectType, val mainSourceSet: DefaultSourceSetContainer?, val flags: DefaultAndroidGradlePluginProjectFlags, - val javaCompileOptions: DefaultJavaCompileOptions, + override val compilerSettings: DefaultJavaCompileOptions, val viewBindingOptions: DefaultViewBindingOptions, val bootClassPaths: Collection, val libraries: Set, val libraryMap: Map, - val dynamicFeatures: Collection?, val lintCheckJars: List, val modelSyncFiles: List, - val variants: List = listOf() + val variants: List = listOf(), + val classesJar: File? ) : ModuleProject( name, @@ -114,27 +113,24 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. projectDir, buildDir, buildScript, - tasks, - javaCompileOptions - ), - WithModuleData { + tasks + ) { private val log = ILogger.newInstance(javaClass.simpleName) - override var moduleData: SimpleModuleData? = null init { type = Android } - override fun getGeneratedJar(variant: String): File { - return File(buildDir, "${FD_INTERMEDIATES}/compile_library_classes_jar/$variant/classes.jar") + fun getGeneratedJar(variant: String): File { + return classesJar ?: File("does-not-exist.jar") } override fun getClassPaths(): Set { return getModuleClasspaths() } - fun getVariant(name: String): SimpleVariantData? { + fun getVariant(name: String): BasicAndroidVariantMetadata? { return this.variants.firstOrNull { it.name == name } } @@ -355,8 +351,8 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. libraryMap.values .filter { library -> library.type == ANDROID_LIBRARY && - library.androidLibraryData!!.resFolder.exists() && - library.findPackageName() != UNKNOWN_PACKAGE + library.androidLibraryData!!.resFolder.exists() && + library.findPackageName() != UNKNOWN_PACKAGE } .mapNotNull { library -> ResourceTableRegistry.getInstance() diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/JavaModule.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/JavaModule.kt index 459d33543..8846d4549 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/JavaModule.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/JavaModule.kt @@ -19,12 +19,12 @@ package com.itsaky.androidide.projects.api import com.itsaky.androidide.builder.model.IJavaCompilerSettings import com.itsaky.androidide.projects.ProjectManager -import com.itsaky.androidide.tooling.api.IProject.Type.Java -import com.itsaky.androidide.tooling.api.model.GradleTask -import com.itsaky.androidide.tooling.api.model.JavaContentRoot -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.ProjectType.Java +import com.itsaky.androidide.tooling.api.models.JavaModuleExternalDependency +import com.itsaky.androidide.tooling.api.models.JavaModuleProjectDependency +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 java.io.File /** @@ -51,9 +51,10 @@ class JavaModule( buildDir: File, buildScript: File, tasks: List, - compilerSettings: IJavaCompilerSettings, + override val compilerSettings: IJavaCompilerSettings, val contentRoots: List, val dependencies: List, + val classesJar: File? ) : ModuleProject( name, @@ -62,11 +63,11 @@ class JavaModule( projectDir, buildDir, buildScript, - tasks, - compilerSettings + tasks ) { companion object { + const val SCOPE_COMPILE = "COMPILE" const val SCOPE_RUNTIME = "RUNTIME" } @@ -75,19 +76,6 @@ class JavaModule( type = 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(): Set { return getModuleClasspaths() } @@ -107,7 +95,7 @@ class JavaModule( } override fun getModuleClasspaths(): Set { - return mutableSetOf(getGeneratedJar("")) + return mutableSetOf(classesJar ?: File("does-not-exist.jar")) } override fun getCompileClasspaths(): Set { @@ -116,7 +104,7 @@ class JavaModule( classpaths.addAll(getDependencyClasspaths()) return classpaths } - + override fun getCompileModuleProjects(): List { val root = ProjectManager.rootProject ?: return emptyList() return this.dependencies @@ -125,8 +113,9 @@ class JavaModule( .mapNotNull { root.findByPath(it.projectPath) } .filterIsInstance(ModuleProject::class.java) } - + fun getDependencyClasspaths(): Set { - return this.dependencies.filterIsInstance().mapNotNull { it.jarFile }.toHashSet() + return this.dependencies.filterIsInstance() + .mapNotNull { it.jarFile }.toHashSet() } } diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt index 8e05ea3ff..81b32b2af 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt @@ -23,7 +23,8 @@ import com.itsaky.androidide.javac.services.fs.CacheFSInfoSingleton import com.itsaky.androidide.lookup.Lookup import com.itsaky.androidide.projects.classpath.JarFsClasspathReader import com.itsaky.androidide.projects.util.BootClasspathProvider -import com.itsaky.androidide.tooling.api.model.GradleTask +import com.itsaky.androidide.tooling.api.IModuleProject +import com.itsaky.androidide.tooling.api.models.GradleTask import com.itsaky.androidide.utils.ClassTrie import com.itsaky.androidide.utils.DocumentUtils import com.itsaky.androidide.utils.ILogger @@ -46,20 +47,24 @@ abstract class ModuleProject( projectDir: File, buildDir: File, buildScript: File, - tasks: List, - override val compilerSettings: IJavaCompilerSettings + tasks: List ) : - Project(name, description, path, projectDir, buildDir, buildScript, tasks), - com.itsaky.androidide.tooling.api.model.ModuleProject { + Project(name, description, path, projectDir, buildDir, buildScript, tasks) { private val log = ILogger.newInstance(javaClass.simpleName) + abstract val compilerSettings: IJavaCompilerSettings + companion object { - @JvmStatic val COMPLETION_MODULE_KEY = Lookup.Key() + + @JvmStatic + val COMPLETION_MODULE_KEY = Lookup.Key() } - @JvmField val compileJavaSourceClasses = SourceClassTrie() - @JvmField val compileClasspathClasses = ClassTrie() + @JvmField + val compileJavaSourceClasses = SourceClassTrie() + @JvmField + val compileClasspathClasses = ClassTrie() /** * Get the source directories of this module (non-transitive i.e for this module only). @@ -76,6 +81,12 @@ abstract class ModuleProject( */ abstract fun getCompileSourceDirectories(): Set + /** + * Get the classpaths for this module project. The returned list always included the + * `classes.jar`. + */ + abstract fun getClassPaths() : Set + /** * Get the JAR files for this module. This does not include JAR files of any dependencies. * diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt index 44dba1bc2..5c2a72ab6 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt @@ -19,9 +19,8 @@ package com.itsaky.androidide.projects.api import com.android.builder.model.v2.ide.ProjectType.APPLICATION import com.itsaky.androidide.tooling.api.IProject -import com.itsaky.androidide.tooling.api.IProject.Type -import com.itsaky.androidide.tooling.api.IProject.Type.Gradle -import com.itsaky.androidide.tooling.api.model.GradleTask +import com.itsaky.androidide.tooling.api.ProjectType +import com.itsaky.androidide.tooling.api.models.GradleTask import java.io.File import java.nio.file.Path import java.util.concurrent.* @@ -53,7 +52,7 @@ open class Project( val subModules: List = mutableListOf() ) { - var type: Type = Gradle + var type: ProjectType = ProjectType.Gradle protected set /** diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt index 23a73fc02..78fbdbe1d 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt @@ -17,17 +17,17 @@ package com.itsaky.androidide.projects.util -import com.android.builder.model.v2.ide.ProjectType.LIBRARY -import com.itsaky.androidide.builder.model.DefaultViewBindingOptions import com.itsaky.androidide.projects.api.AndroidModule import com.itsaky.androidide.projects.api.JavaModule import com.itsaky.androidide.projects.api.Project +import com.itsaky.androidide.tooling.api.IAndroidProject +import com.itsaky.androidide.tooling.api.IJavaProject import com.itsaky.androidide.tooling.api.IProject -import com.itsaky.androidide.tooling.api.IProject.Type.Android -import com.itsaky.androidide.tooling.api.IProject.Type.Gradle -import com.itsaky.androidide.tooling.api.IProject.Type.Java -import com.itsaky.androidide.tooling.api.IProject.Type.Unknown -import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData +import com.itsaky.androidide.tooling.api.ProjectType +import com.itsaky.androidide.tooling.api.models.AndroidProjectMetadata +import com.itsaky.androidide.tooling.api.models.BasicProjectMetadata +import com.itsaky.androidide.tooling.api.models.JavaProjectMetadata +import com.itsaky.androidide.tooling.api.models.params.StringParameter import com.itsaky.androidide.utils.ILogger import java.util.concurrent.CopyOnWriteArrayList @@ -42,19 +42,32 @@ class ProjectTransformer { fun transform(project: IProject): Project? { try { - val path = project.projectPath.get() + val allProjects = project.getProjects().get() + val selectionResult = project.selectProject(StringParameter(IProject.ROOT_PROJECT_PATH)).get() + check(selectionResult.isSuccessful) { + "Cannot find root project with path '${IProject.ROOT_PROJECT_PATH}'" + } + + val rootProject = when (project.getType().get()) { + ProjectType.Gradle -> project.asGradleProject() + ProjectType.Android -> project.asGradleProject() + else -> throw IllegalStateException( + "Root project must be either an Android project or a Gradle project") + } + + val metadata = rootProject.getMetadata().get() return Project( - name = project.name.get(), - description = project.description.get() ?: "", - path = path, - projectDir = project.projectDir.get(), - buildDir = project.buildDir.get(), - buildScript = project.buildScript.get(), + name = metadata.name ?: IProject.PROJECT_UNKNOWN, + description = metadata.description ?: "", + path = metadata.projectPath, + projectDir = metadata.projectDir, + buildDir = metadata.buildDir, + buildScript = metadata.buildScript, // As these lists will never change, we could make these thread-safe with // CopyOnWriteArrayList - tasks = CopyOnWriteArrayList(project.tasks.get() ?: listOf()), - subModules = CopyOnWriteArrayList(transform(project.listModules().get(), project)) + tasks = CopyOnWriteArrayList(rootProject.getTasks().get() ?: listOf()), + subModules = CopyOnWriteArrayList(transform(allProjects, project)) ) } catch (error: Throwable) { log.error("Unable to transform project", error) @@ -63,52 +76,56 @@ class ProjectTransformer { } private fun transform( - project: com.itsaky.androidide.tooling.api.model.AndroidModule + project: IAndroidProject ): AndroidModule { + val metadata = project.getMetadata().get() as AndroidProjectMetadata + val libraryMap = project.getLibraryMap().get() return AndroidModule( - name = project.name, - description = project.description ?: "", - path = project.projectPath, - projectDir = project.projectDir, - buildDir = project.buildDir, - buildScript = project.buildScript, - tasks = project.tasks, - packageName = project.packageName, - resourcePrefix = project.resourcePrefix, - namespace = project.namespace, - androidTestNamespace = project.androidTestNamespace, - testFixtureNamespace = project.testFixturesNamespace, - projectType = project.projectType ?: LIBRARY, - mainSourceSet = project.mainSourceSet, - flags = project.flags, - javaCompileOptions = project.javaCompileOptions, - viewBindingOptions = project.viewBindingOptions ?: DefaultViewBindingOptions(), - bootClassPaths = project.bootClassPaths, - libraries = project.libraries, - libraryMap = project.libraryMap, - dynamicFeatures = project.dynamicFeatures, - lintCheckJars = project.lintChecksJars, - modelSyncFiles = project.modelSyncFiles, - variants = project.simpleVariants + name = metadata.name ?: IProject.PROJECT_UNKNOWN, + description = metadata.description ?: "", + path = metadata.projectPath, + projectDir = metadata.projectDir, + buildDir = metadata.buildDir, + buildScript = metadata.buildScript, + tasks = project.getTasks().get(), + packageName = metadata.packageName, + resourcePrefix = metadata.resourcePrefix, + namespace = metadata.namespace, + androidTestNamespace = metadata.androidTestNamespace, + testFixtureNamespace = metadata.testFixtureNamespace, + projectType = metadata.androidType, + mainSourceSet = project.getMainSourceSet().get(), + flags = metadata.flags, + compilerSettings = metadata.javaCompileOptions, + viewBindingOptions = metadata.viewBindingOptions, + bootClassPaths = project.getBootClasspaths().get(), + libraries = libraryMap.keys, + libraryMap = libraryMap, + lintCheckJars = project.getLintCheckJars().get(), + modelSyncFiles = project.getModelSyncFiles().get(), + variants = project.getVariants().get(), + classesJar = metadata.classesJar ) } - private fun transform(project: com.itsaky.androidide.tooling.api.model.JavaModule): JavaModule { + private fun transform(project: IJavaProject): JavaModule { + val metadata = project.getMetadata() as JavaProjectMetadata return JavaModule( - name = project.name, - description = project.description ?: "", - path = project.projectPath, - projectDir = project.projectDir, - buildDir = project.buildDir, - buildScript = project.buildScript, - tasks = project.tasks, - contentRoots = project.contentRoots, - dependencies = project.javaDependencies, - compilerSettings = project.compilerSettings + name = metadata.name ?: IProject.PROJECT_UNKNOWN, + description = metadata.description ?: "", + path = metadata.projectPath, + projectDir = metadata.projectDir, + buildDir = metadata.buildDir, + buildScript = metadata.buildScript, + tasks = project.getTasks().get(), + contentRoots = project.getContentRoots().get(), + dependencies = project.getDependencies().get(), + compilerSettings = metadata.compilerSettings, + classesJar = metadata.classesJar ) } - private fun transform(modules: MutableList, root: IProject): List { + private fun transform(modules: List, root: IProject): List { return mutableListOf().apply { for (module in modules) { add(createProject(module, root)) @@ -116,17 +133,19 @@ class ProjectTransformer { } } - private fun createProject(module: SimpleModuleData, root: IProject): Project { - val project = - root.findByPath(module.path).get() - ?: throw java.lang.IllegalStateException("Invalid module data") - val type = project.type.get() ?: throw java.lang.IllegalStateException("Invalid module data") + private fun createProject(moduleMetadata: BasicProjectMetadata, root: IProject): Project { + val selectionResult = root.selectProject(StringParameter(moduleMetadata.projectPath)).get() + check(selectionResult.isSuccessful) { + "Selection failed for project '${moduleMetadata.projectPath}' but it is included in all projects." + } + + val type = root.getType().get() ?: throw java.lang.IllegalStateException("Invalid module data") return when (type) { - Gradle, - Unknown -> transform(project)!! - Android -> transform(project as com.itsaky.androidide.tooling.api.model.AndroidModule) - Java -> transform(project as com.itsaky.androidide.tooling.api.model.JavaModule) + ProjectType.Gradle, + ProjectType.Unknown -> transform(project)!! + ProjectType.Android -> transform(project as IAndroidProject) + ProjectType.Java -> transform(project as IJavaProject) } } } 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 655a3d6de..df6d4d1c9 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 @@ -71,9 +71,9 @@ class ModuleProjectTest { assertThat(app.compilerSettings.javaSourceVersion).isEqualTo("11") assertThat(app.compilerSettings.javaBytecodeVersion).isEqualTo("11") assertThat(app.compilerSettings.javaSourceVersion) - .isEqualTo(app.javaCompileOptions.sourceCompatibility) + .isEqualTo(app.compilerSettings.sourceCompatibility) assertThat(app.compilerSettings.javaBytecodeVersion) - .isEqualTo(app.javaCompileOptions.targetCompatibility) + .isEqualTo(app.compilerSettings.targetCompatibility) val anotherAndroidLib = root.findByPath(":another-android-library") assertThat(anotherAndroidLib).isNotNull() 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 91614bf49..b6fc25569 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 @@ -148,7 +148,7 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : var projectPath = message.projectPath if (projectPath == null) { - projectPath = ":" + projectPath = IProject.ROOT_PROJECT_PATH } val project = this.project!!.run { 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 index be5f68cdf..83f8326e9 100644 --- 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 @@ -158,18 +158,16 @@ internal class AndroidProjectImpl( return CompletableFuture.supplyAsync { copy(androidProject.modelSyncFiles) } } - override fun getClassesJar(): CompletableFuture { + private fun getClassesJar(): File { // 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") - } + return 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()) + add(getClassesJar()) getVariant(StringParameter(variant)).get()?.mainArtifact?.classJars?.let(this::addAll) } } @@ -193,7 +191,8 @@ internal class AndroidProjectImpl( androidProject.resourcePrefix, androidProject.namespace, androidProject.androidTestNamespace, - androidProject.testFixturesNamespace + androidProject.testFixturesNamespace, + getClassesJar() ) } } 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 index 637ff176a..b94679d51 100644 --- 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 @@ -105,32 +105,30 @@ internal class JavaProjectImpl( moduleVersion.group, moduleVersion.name, moduleVersion.version) } - override fun getClassesJar(): CompletableFuture { - return CompletableFuture.supplyAsync { - val metadata = getMetadata().get() - var jar = File(metadata.buildDir, "libs/${metadata.name}.jar") - if (jar.exists()) { - return@supplyAsync jar - } - - jar = - File(metadata.buildDir, "libs").listFiles()?.first { metadata.name?.let(it.name::startsWith) ?: false } - ?: File("module-jar-does-not-exist.jar") - - return@supplyAsync jar + private fun getClassesJar(): File { + val metadata = getMetadata().get() + var jar = File(metadata.buildDir, "libs/${metadata.name}.jar") + if (jar.exists()) { + return jar } + + jar = + File(metadata.buildDir, "libs").listFiles()?.first { metadata.name?.let(it.name::startsWith) ?: false } + ?: File("module-jar-does-not-exist.jar") + + return jar } override fun getClasspaths(): CompletableFuture> { return CompletableFuture.supplyAsync { - getDependencies().get().mapNotNull { it.jarFile }.toMutableList().apply { add(getClassesJar().get()) } + getDependencies().get().mapNotNull { it.jarFile }.toMutableList().apply { add(getClassesJar()) } } } override fun getMetadata(): CompletableFuture { return CompletableFuture.supplyAsync { val base = super.getMetadata().get() - return@supplyAsync JavaProjectMetadata(base, compilerSettings) + return@supplyAsync JavaProjectMetadata(base, compilerSettings, getClassesJar()) } } } \ 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 index 155531e1b..ae89b306f 100644 --- 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 @@ -52,7 +52,7 @@ internal class ProjectImpl(var rootProject: IGradleProject? = null, } private fun getProject(path: String): IGradleProject? { - return if (":" == path) rootProject else projects.find { + return if (IProject.ROOT_PROJECT_PATH == path) rootProject else projects.find { it.getMetadata().get().projectPath == path } } 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 index 54148f880..a4e8cb2c1 100644 --- 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 @@ -92,11 +92,6 @@ internal class ForwardingProject(var project: IGradleProject? = null) : IGradleP UnsupportedOperationException()) } - override fun getClassesJar(): CompletableFuture { - return this.javaProject?.getClassesJar() ?: CompletableFuture.failedFuture( - UnsupportedOperationException()) - } - override fun getClasspaths(): CompletableFuture> { return this.javaProject?.getClasspaths() ?: CompletableFuture.failedFuture( UnsupportedOperationException()) 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 index 76cb1d635..55d6a76e0 100644 --- 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 @@ -42,9 +42,9 @@ object RootModelBuilder : AbstractModelBuilder - /** * Get the classpaths for this module project. The returned list always included the - * `classes.jar` file from [getClassesJar]. + * `classes.jar`. */ 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.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt index 507e4f47c..222217642 100644 --- 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 @@ -44,4 +44,17 @@ interface IProject : IProjectQueries { */ @JsonRequest fun getProjectSyncIssues(): CompletableFuture> + + companion object { + + /** + * Gradle project path for the root project. + */ + const val ROOT_PROJECT_PATH = ":" + + /** + * Name that can be used for project whose [BasicProjectMetadata.name] property is null. + */ + const val PROJECT_UNKNOWN = "Unknown" + } } \ 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 index 3d6c32090..5ea1ac80e 100644 --- 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 @@ -65,17 +65,17 @@ interface IProjectQueries { * Get the selected project as Gradle project. */ @JsonDelegate - fun asGradleProject(): IGradleProject? + fun asGradleProject(): IGradleProject /** * Get the selected project as Android project. */ @JsonDelegate - fun asAndroidProject(): IAndroidProject? + fun asAndroidProject(): IAndroidProject /** * Get the selected project as Java project. */ @JsonDelegate - fun asJavaProject(): IJavaProject? + fun asJavaProject(): IJavaProject } \ 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 index 20c576eae..e21fc00db 100644 --- 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 @@ -21,6 +21,7 @@ import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlag import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions import com.itsaky.androidide.builder.model.DefaultViewBindingOptions import com.itsaky.androidide.tooling.api.ProjectType +import com.itsaky.androidide.tooling.api.models.result.IModuleMetadata import java.io.File /** @@ -44,8 +45,10 @@ class AndroidProjectMetadata( val resourcePrefix: String?, val namespace: String?, val androidTestNamespace: String?, - val testFixtureNamespace: String? -) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type) { + val testFixtureNamespace: String?, + override val classesJar: File? +) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type), + IModuleMetadata { constructor( base: ProjectMetadata, @@ -57,7 +60,8 @@ class AndroidProjectMetadata( resourcePrefix: String?, namespace: String?, androidTestNamespace: String?, - testFixtureNamespace: String?) : + testFixtureNamespace: String?, + classesJar: File?) : this( base.name, @@ -75,7 +79,8 @@ class AndroidProjectMetadata( resourcePrefix, namespace, androidTestNamespace, - testFixtureNamespace + testFixtureNamespace, + classesJar ) } diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaProjectMetadata.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaProjectMetadata.kt index 237c2e08b..23cf97d42 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaProjectMetadata.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/JavaProjectMetadata.kt @@ -19,6 +19,7 @@ package com.itsaky.androidide.tooling.api.models import com.itsaky.androidide.builder.model.IJavaCompilerSettings import com.itsaky.androidide.tooling.api.ProjectType +import com.itsaky.androidide.tooling.api.models.result.IModuleMetadata import java.io.File /** @@ -35,10 +36,11 @@ class JavaProjectMetadata( description: String?, buildScript: File, type: ProjectType, - val compilerSettings: IJavaCompilerSettings -) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type) { + val compilerSettings: IJavaCompilerSettings, + override val classesJar: File? +) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type), IModuleMetadata { - constructor(base: ProjectMetadata, compilerSettings: IJavaCompilerSettings) : + constructor(base: ProjectMetadata, compilerSettings: IJavaCompilerSettings, classesJar: File?) : this( base.name, base.projectPath, @@ -47,6 +49,7 @@ class JavaProjectMetadata( base.description, base.buildScript, base.type, - compilerSettings + compilerSettings, + classesJar ) } \ No newline at end of file diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/IModuleMetadata.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/IModuleMetadata.kt new file mode 100644 index 000000000..9cf7cda77 --- /dev/null +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/result/IModuleMetadata.kt @@ -0,0 +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.api.models.result + +import java.io.File + +/** + * Common metadata for module projects. + * + * @author Akash Yadav + */ +interface IModuleMetadata { + + /** + * The JAR file for this module project. + */ + val classesJar: File? +} \ No newline at end of file