Provide type of project in IProject

This commit is contained in:
Akash Yadav
2022-05-27 11:10:12 +05:30
parent fd9ce03e96
commit 8e13f4c59e
14 changed files with 11216 additions and 126 deletions
Binary file not shown.
@@ -18,6 +18,7 @@
package com.itsaky.androidide.projects
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
@@ -43,6 +44,7 @@ class CachingProject(val project: IProject) : IProject {
private val mProjectDir: File by lazy { this.project.projectDir.get() }
private val mBuildDir: File by lazy { this.project.buildDir.get() }
private val mBuildScript: File by lazy { this.project.buildScript.get() }
private val mProjectType: Type by lazy { this.project.type.get() }
private var mFirstAppModule: IdeAndroidModule? = null
private val mCachedVariants: MutableMap<VariantDataRequest, SimpleVariantData> = mutableMapOf()
@@ -64,6 +66,10 @@ class CachingProject(val project: IProject) : IProject {
return CompletableFuture.completedFuture(mProjectPath)
}
override fun getType(): CompletableFuture<Type> {
return CompletableFuture.completedFuture(mProjectType)
}
override fun getProjectDir(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mProjectDir)
}
@@ -16,10 +16,6 @@
*/
package com.itsaky.androidide.projects
import com.android.builder.model.v2.ide.LibraryType.ANDROID_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.JAVA_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.PROJECT
import com.android.builder.model.v2.ide.LibraryType.RELOCATED
import com.itsaky.androidide.app.StudioApp
import com.itsaky.androidide.services.BuildService
import com.itsaky.androidide.tooling.api.IProject
@@ -27,6 +23,7 @@ import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.model.IdeModule
import com.itsaky.androidide.tooling.api.util.ProjectDataCollector
import com.itsaky.androidide.utils.ILogger
import com.itsaky.lsp.java.models.JavaServerConfiguration
import java.io.File
@@ -135,29 +132,6 @@ object ProjectManager {
}
}
fun collectResDirectories(android: IdeAndroidModule): List<File> {
val main = android.mainSourceSet
if (main == null) {
log.error("No main source set found in application module: ${android.name}")
return emptyList()
}
val dirs = mutableListOf<File>()
if (main.sourceProvider.resDirectories != null) {
dirs.addAll(main.sourceProvider.resDirectories!!)
}
val dependencies =
collectProjectDependencies(rootProject!!, android)
.filterIsInstance(IdeAndroidModule::class.java)
for (dependency in dependencies) {
dirs.addAll(collectResDirectories(dependency))
}
return dirs
}
fun notifyProjectUpdate() {
log.debug("Updating class paths in language servers...")
getApplicationModule().whenCompleteAsync { it, _ ->
@@ -174,103 +148,28 @@ object ProjectManager {
}
}
private fun collectClassPaths(app: IdeAndroidModule): Set<Path> {
fun collectResDirectories(android: IdeAndroidModule) =
ProjectDataCollector.collectResDirectories(this.rootProject!!, android)
val libraries = app.debugLibraries
val paths = mutableSetOf<Path>()
val modules = this.rootProject!!.listModules().get()
for (value in libraries) {
if (value.type == RELOCATED) {
// this library does not have any artifacts
continue
}
fun collectClassPaths(app: IdeAndroidModule) =
ProjectDataCollector.collectClassPaths(this.rootProject!!, app)
if (value.type == ANDROID_LIBRARY) {
paths.addAll(value.androidLibraryData!!.compileJarFiles.map { it.toPath() })
} else if (value.type == JAVA_LIBRARY) {
paths.add(value.artifact!!.toPath())
} else {
val projectPath = value.projectInfo!!.projectPath
val module = modules.firstOrNull { it.path == projectPath } ?: continue
if (module.classPaths.isNotEmpty()) {
paths.addAll(module.classPaths.map { it.toPath() })
}
}
}
fun collectSourceDirs(app: IdeAndroidModule): Set<Path> =
ProjectDataCollector.collectSourceDirs(this.rootProject!!, app)
return paths
}
@Suppress("unused")
fun collectProjectDependencies(project: IProject, app: IdeAndroidModule) =
ProjectDataCollector.collectProjectDependencies(project, app)
private fun collectSourceDirs(app: IdeAndroidModule): Set<Path> {
val sourcePaths = mutableSetOf<File>()
val projectSources = collectSourceDirs(collectProjectDependencies(rootProject!!, app))
sourcePaths.addAll(projectSources)
sourcePaths.addAll(collectSources(app))
return sourcePaths.map { it.toPath() }.toSet()
}
@Suppress("unused")
fun collectSourceDirs(projects: List<IdeModule>) =
ProjectDataCollector.collectSourceDirs(projects)
fun collectProjectDependencies(project: IProject, app: IdeAndroidModule): List<IdeModule> {
@Suppress("unused")
fun collectSources(java: IdeJavaModule) = ProjectDataCollector.collectSources(java)
return app.debugLibraries
.filter { it.type == PROJECT }
.map { project.findByPath(it.projectInfo!!.projectPath) }
.filterIsInstance(IdeModule::class.java)
}
fun collectSourceDirs(projects: List<IdeModule>): Set<File> {
val sources = mutableSetOf<File>()
for (project in projects) {
if (project is IdeJavaModule) {
sources.addAll(collectSources(project))
} else if (project is IdeAndroidModule) {
sources.addAll(collectSources(project))
}
}
return sources
}
fun collectSourceDirs(vararg projects: IdeModule): List<File> {
val sources = mutableListOf<File>()
for (project in projects) {
if (project is IdeJavaModule) {
sources.addAll(collectSources(project))
} else if (project is IdeAndroidModule) {
sources.addAll(collectSources(project))
}
}
return sources
}
fun collectSources(java: IdeJavaModule): List<File> {
val sources = mutableListOf<File>()
for (root in java.contentRoots) {
sources.addAll(root.sourceDirectories.map { it.directory })
}
return sources
}
fun collectSources(android: IdeAndroidModule): List<File> {
if (android.mainSourceSet == null) {
return mutableListOf()
}
// src/main/java
val sources = android.mainSourceSet!!.sourceProvider.javaDirectories.toMutableList()
// src/main/kotlin
sources.addAll(android.mainSourceSet!!.sourceProvider.kotlinDirectories)
// build/generated/**
// AIDL, ViewBinding, Renderscript, BuildConfig i.e every generated source sources
val debugVariant = android.simpleVariants.firstOrNull { it.name == "debug" }
if (debugVariant != null) {
sources.addAll(debugVariant.mainArtifact.generatedSourceFolders)
}
return sources
}
@Suppress("unused")
fun collectSources(android: IdeAndroidModule) = ProjectDataCollector.collectSources(android)
fun findModuleForFile(file: File): CompletableFuture<IdeGradleProject?> {
if (!checkInit()) {
@@ -18,6 +18,8 @@
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
@@ -65,6 +67,10 @@ class InternalForwardingProject(
if (this.project != null) this.project!!.projectDir
else CompletableFuture.completedFuture(File(projectPath))
override fun getType(): CompletableFuture<Type> =
if (this.project != null) this.project!!.type
else CompletableFuture.completedFuture(Unknown)
override fun getBuildDir(): CompletableFuture<File> =
if (this.project != null) this.project!!.buildDir
else CompletableFuture.completedFuture(File(projectPath))
@@ -213,7 +213,7 @@ public class ProjectReader {
System.err.println("Fetching project model...");
final var android = controller.findModel(gradle, AndroidProject.class);
final var module = buildAndroidModuleProject(gradle, android, basicAndroid.getProjectType());
module.setBoothclasspaths(basicAndroid.getBootClasspath());
module.setBootClassPaths (basicAndroid.getBootClasspath());
module.setMainSourceSet(
basicAndroid.getMainSourceSet() == null
? null
@@ -18,17 +18,23 @@
package com.itsaky.androidide.tooling.impl
import com.android.builder.model.v2.ide.LibraryType.PROJECT
import com.android.builder.model.v2.ide.ProjectType
import com.android.builder.model.v2.ide.ProjectType.APPLICATION
import com.google.common.truth.Truth.assertThat
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.itsaky.androidide.models.LogLine
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.IToolingApiClient
import com.itsaky.androidide.tooling.api.IToolingApiServer
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.api.messages.result.BuildResult
import com.itsaky.androidide.tooling.api.messages.result.GradleWrapperCheckResult
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher
import com.itsaky.androidide.tooling.events.ProgressEvent
@@ -55,6 +61,10 @@ class ToolingApiImplTest {
server.initialize(InitializeProjectMessage(getTestProject().absolutePath)).get()
verifyProjectProps(project, server)
}
private fun verifyProjectProps(project: IProject, server: IToolingApiServer) {
assertThat(project).isNotNull()
// As the returned project is just a proxy,
// project instanceOf IdeGradleProject will always return false
@@ -71,7 +81,7 @@ class ToolingApiImplTest {
assertThat(app.javaCompileOptions.targetCompatibility).isEqualTo("11")
assertThat(app.javaCompileOptions.isCoreLibraryDesugaringEnabled).isFalse()
assertThat(app.projectType).isEqualTo(ProjectType.APPLICATION)
assertThat(app.projectType).isEqualTo(APPLICATION)
assertThat(app.packageName).isEqualTo("com.itsaky.test.app")
assertThat(app.viewBindingOptions).isNotNull()
@@ -95,7 +105,7 @@ class ToolingApiImplTest {
assertThat(project.findByPath(":does-not-exist").get()).isNull()
}
private fun launchServer(client: IToolingApiClient): Pair<IToolingApiServer, IProject> {
val builder = ProcessBuilder("java", "-jar", "./build/libs/tooling-api-all.jar")
builder.environment()["ANDROID_SDK_ROOT"] = findAndroidHome()
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
buildscript {
ext {
build_gradle_version = '7.2.0-rc02'
build_gradle_version = '7.2.0'
}
project.ext {
@@ -62,6 +62,10 @@ public interface IProject {
@JsonRequest
CompletableFuture<File> getProjectDir();
@NotNull
@JsonRequest
CompletableFuture<Type> getType();
@NotNull
@JsonRequest
CompletableFuture<File> getBuildDir();
@@ -101,4 +105,26 @@ public interface IProject {
@NotNull
@JsonRequest
CompletableFuture<List<IdeAndroidModule>> 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
}
}
@@ -27,11 +27,14 @@ import com.itsaky.androidide.builder.model.DefaultSourceSetContainer
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.IProject.Type
import com.itsaky.androidide.tooling.api.IProject.Type.Android
import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData
import java.io.File
import java.io.Serializable
import org.eclipse.lemminx.dom.DOMParser
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager
import java.util.concurrent.*
/**
* Default implementation of [IdeAndroidModule].
@@ -73,7 +76,7 @@ open class IdeAndroidModule(
return field
}
var boothclasspaths: Collection<File> = emptyList()
var bootClassPaths: Collection<File> = emptyList()
var mainSourceSet: DefaultSourceSetContainer? = null
@Deprecated(
@@ -167,6 +170,10 @@ open class IdeAndroidModule(
const val FD_OUTPUTS = "outputs"
const val FD_GENERATED = "generated"
}
override fun getType(): CompletableFuture<Type> {
return CompletableFuture.completedFuture(Android)
}
@Deprecated(
"Use getClasspath() instead.",
@@ -17,6 +17,8 @@
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
@@ -64,6 +66,10 @@ open class IdeGradleProject(
return CompletableFutures.computeAsync { this.projectDir }
}
override fun getType(): CompletableFuture<Type> {
return CompletableFuture.completedFuture(Gradle)
}
override fun getBuildDir(): CompletableFuture<File> {
return CompletableFutures.computeAsync { this.buildDir }
}
@@ -128,7 +134,7 @@ open class IdeGradleProject(
if (this is IdeAndroidModule) {
return CompletableFuture.completedFuture(this)
}
return findAndroidModules().thenApply { modules ->
val application =
modules.stream().filter { it != null && it.projectType == APPLICATION }.findFirst()
@@ -17,8 +17,11 @@
package com.itsaky.androidide.tooling.api.model
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
@@ -40,7 +43,7 @@ open class IdeJavaModule(
val contentRoots: List<JavaContentRoot>,
/** Dependencies of this project. */
val javaDependencies: List<JavaModuleDependency>
private val javaDependencies: List<JavaModuleDependency>
) :
IdeGradleProject(name, description, path, projectDir, buildDir, buildScript, parent, tasks),
IdeModule,
@@ -49,6 +52,10 @@ open class IdeJavaModule(
override fun getDependencies() = javaDependencies
override fun getType(): CompletableFuture<Type> {
return CompletableFuture.completedFuture(Java)
}
@Deprecated("Use getClasspath() instead.")
override fun getGeneratedJar(variant: String): File {
var jar = File(buildDir, "libs/$name.jar")
@@ -0,0 +1,159 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.tooling.api.util
import com.android.builder.model.v2.ide.LibraryType.ANDROID_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.JAVA_LIBRARY
import com.android.builder.model.v2.ide.LibraryType.PROJECT
import com.android.builder.model.v2.ide.LibraryType.RELOCATED
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.model.IdeModule
import com.itsaky.androidide.utils.ILogger
import java.io.File
import java.nio.file.Path
/**
* Provides API to collect data from projects.
*
* @author Akash Yadav
*/
object ProjectDataCollector {
private val log = ILogger.newInstance(javaClass.simpleName)
fun collectResDirectories(rootProject: IProject, android: IdeAndroidModule): List<File> {
val main = android.mainSourceSet
if (main == null) {
log.error("No main source set found in application module: ${android.name}")
return emptyList()
}
val dirs = mutableListOf<File>()
if (main.sourceProvider.resDirectories != null) {
dirs.addAll(main.sourceProvider.resDirectories!!)
}
val dependencies =
collectProjectDependencies(rootProject, android)
.filterIsInstance(IdeAndroidModule::class.java)
for (dependency in dependencies) {
dirs.addAll(collectResDirectories(rootProject, dependency))
}
return dirs
}
fun collectClassPaths(rootProject: IProject, app: IdeAndroidModule): Set<Path> {
val libraries = app.debugLibraries
val paths = mutableSetOf<Path>()
val modules = rootProject.listModules().get()
for (value in libraries) {
if (value.type == RELOCATED) {
// this library does not have any artifacts
continue
}
if (value.type == ANDROID_LIBRARY) {
paths.addAll(value.androidLibraryData!!.compileJarFiles.map { it.toPath() })
} else if (value.type == JAVA_LIBRARY) {
paths.add(value.artifact!!.toPath())
} else {
val projectPath = value.projectInfo!!.projectPath
val module = modules.firstOrNull { it.path == projectPath } ?: continue
if (module.classPaths.isNotEmpty()) {
paths.addAll(module.classPaths.map { it.toPath() })
}
}
}
return paths
}
fun collectSourceDirs(rootProject: IProject, app: IdeAndroidModule): Set<Path> {
val sourcePaths = mutableSetOf<File>()
val projectSources = collectSourceDirs(collectProjectDependencies(rootProject, app))
sourcePaths.addAll(projectSources)
sourcePaths.addAll(collectSources(app))
return sourcePaths.map { it.toPath() }.toSet()
}
fun collectProjectDependencies(project: IProject, app: IdeAndroidModule): List<IdeModule> {
return app.debugLibraries
.filter { it.type == PROJECT }
.map { project.findByPath(it.projectInfo!!.projectPath) }
.filterIsInstance(IdeModule::class.java)
}
fun collectSourceDirs(projects: List<IdeModule>): Set<File> {
val sources = mutableSetOf<File>()
for (project in projects) {
if (project is IdeJavaModule) {
sources.addAll(collectSources(project))
} else if (project is IdeAndroidModule) {
sources.addAll(collectSources(project))
}
}
return sources
}
fun collectSourceDirs(vararg projects: IdeModule): List<File> {
val sources = mutableListOf<File>()
for (project in projects) {
if (project is IdeJavaModule) {
sources.addAll(collectSources(project))
} else if (project is IdeAndroidModule) {
sources.addAll(collectSources(project))
}
}
return sources
}
fun collectSources(java: IdeJavaModule): List<File> {
val sources = mutableListOf<File>()
for (root in java.contentRoots) {
sources.addAll(root.sourceDirectories.map { it.directory })
}
return sources
}
fun collectSources(android: IdeAndroidModule): List<File> {
if (android.mainSourceSet == null) {
return mutableListOf()
}
// src/main/java
val sources = android.mainSourceSet!!.sourceProvider.javaDirectories.toMutableList()
// src/main/kotlin
sources.addAll(android.mainSourceSet!!.sourceProvider.kotlinDirectories)
// build/generated/**
// AIDL, ViewBinding, Renderscript, BuildConfig i.e every generated source sources
val debugVariant = android.simpleVariants.firstOrNull { it.name == "debug" }
if (debugVariant != null) {
sources.addAll(debugVariant.mainArtifact.generatedSourceFolders)
}
return sources
}
}