feat(projects): implement project deserialization and transformation (1/2)

This commit is contained in:
Akash Yadav
2023-08-06 15:30:09 +05:30
parent 823943625e
commit e38348c9ff
21 changed files with 274 additions and 309 deletions
@@ -412,7 +412,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin
public CompletableFuture<TaskExecutionResult> 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));
}
@@ -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<GradleTask>()
private val projects = mutableListOf<BasicProjectMetadata>()
private val syncIssues = mutableMapOf<String, DefaultProjectSyncIssues>()
private var mFirstAppModule: AndroidModule? = null
private val mCachedVariants: MutableMap<VariantDataRequest, SimpleVariantData> = mutableMapOf()
private val mCachedProjects: MutableMap<String, IdeGradleProject> = mutableMapOf()
private val mModules: MutableList<SimpleModuleData> = mutableListOf()
override fun isProjectInitialized(): CompletableFuture<Boolean> {
return CompletableFuture.completedFuture(true)
}
override fun getName(): CompletableFuture<String> {
return CompletableFuture.completedFuture(this.mName)
}
override fun getDescription(): CompletableFuture<String> {
return CompletableFuture.completedFuture(mDescription)
}
override fun getProjectPath(): CompletableFuture<String> {
return CompletableFuture.completedFuture(mProjectPath)
}
override fun getType(): CompletableFuture<Type> {
return CompletableFuture.completedFuture(mProjectType)
}
override fun getProjectDir(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mProjectDir)
}
override fun getBuildDir(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mBuildDir)
}
override fun getBuildScript(): CompletableFuture<File> {
return CompletableFuture.completedFuture(mBuildScript)
}
override fun getTasks(): CompletableFuture<MutableList<GradleTask>> {
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<List<BasicProjectMetadata>> {
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<MutableList<SimpleModuleData>> {
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<SimpleVariantData> {
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<Map<String, DefaultProjectSyncIssues>> {
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<IdeGradleProject> {
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<MutableList<AndroidModule>> {
return this.project.findAndroidModules()
override fun selectProject(param: StringParameter): CompletableFuture<SelectProjectResult> {
return project.selectProject(param)
}
override fun findFirstAndroidModule(): CompletableFuture<AndroidModule> {
return this.project.findFirstAndroidModule()
override fun getType(): CompletableFuture<ProjectType> {
return project.getType()
}
override fun findFirstAndroidAppModule(): CompletableFuture<AndroidModule> {
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()
}
}
@@ -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<File>,
val libraries: Set<String>,
val libraryMap: Map<String, DefaultLibrary>,
val dynamicFeatures: Collection<String>?,
val lintCheckJars: List<File>,
val modelSyncFiles: List<DefaultModelSyncFile>,
val variants: List<SimpleVariantData> = listOf()
val variants: List<BasicAndroidVariantMetadata> = 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<File> {
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()
@@ -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<GradleTask>,
compilerSettings: IJavaCompilerSettings,
override val compilerSettings: IJavaCompilerSettings,
val contentRoots: List<JavaContentRoot>,
val dependencies: List<JavaModuleDependency>,
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<File> {
return getModuleClasspaths()
}
@@ -107,7 +95,7 @@ class JavaModule(
}
override fun getModuleClasspaths(): Set<File> {
return mutableSetOf(getGeneratedJar(""))
return mutableSetOf(classesJar ?: File("does-not-exist.jar"))
}
override fun getCompileClasspaths(): Set<File> {
@@ -116,7 +104,7 @@ class JavaModule(
classpaths.addAll(getDependencyClasspaths())
return classpaths
}
override fun getCompileModuleProjects(): List<ModuleProject> {
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<File> {
return this.dependencies.filterIsInstance<JavaModuleExternalDependency>().mapNotNull { it.jarFile }.toHashSet()
return this.dependencies.filterIsInstance<JavaModuleExternalDependency>()
.mapNotNull { it.jarFile }.toHashSet()
}
}
@@ -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<GradleTask>,
override val compilerSettings: IJavaCompilerSettings
tasks: List<GradleTask>
) :
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<ModuleProject>()
@JvmStatic
val COMPLETION_MODULE_KEY = Lookup.Key<ModuleProject>()
}
@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<File>
/**
* Get the classpaths for this module project. The returned list always included the
* `classes.jar`.
*/
abstract fun getClassPaths() : Set<File>
/**
* Get the JAR files for this module. This does not include JAR files of any dependencies.
*
@@ -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<Project> = mutableListOf()
) {
var type: Type = Gradle
var type: ProjectType = ProjectType.Gradle
protected set
/**
@@ -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<SimpleModuleData>, root: IProject): List<Project> {
private fun transform(modules: List<BasicProjectMetadata>, root: IProject): List<Project> {
return mutableListOf<Project>().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)
}
}
}
@@ -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()
@@ -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 {
@@ -158,18 +158,16 @@ internal class AndroidProjectImpl(
return CompletableFuture.supplyAsync { copy(androidProject.modelSyncFiles) }
}
override fun getClassesJar(): CompletableFuture<File> {
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<List<File>> {
return CompletableFuture.supplyAsync {
mutableListOf<File>().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()
)
}
}
@@ -105,32 +105,30 @@ internal class JavaProjectImpl(
moduleVersion.group, moduleVersion.name, moduleVersion.version)
}
override fun getClassesJar(): CompletableFuture<File> {
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<List<File>> {
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<ProjectMetadata> {
return CompletableFuture.supplyAsync {
val base = super.getMetadata().get()
return@supplyAsync JavaProjectMetadata(base, compilerSettings)
return@supplyAsync JavaProjectMetadata(base, compilerSettings, getClassesJar())
}
}
}
@@ -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
}
}
@@ -92,11 +92,6 @@ internal class ForwardingProject(var project: IGradleProject? = null) : IGradleP
UnsupportedOperationException())
}
override fun getClassesJar(): CompletableFuture<File> {
return this.javaProject?.getClassesJar() ?: CompletableFuture.failedFuture(
UnsupportedOperationException())
}
override fun getClasspaths(): CompletableFuture<List<File>> {
return this.javaProject?.getClasspaths() ?: CompletableFuture.failedFuture(
UnsupportedOperationException())
@@ -42,9 +42,9 @@ object RootModelBuilder : AbstractModelBuilder<ProjectConnectionAndAndroidVarian
val ideaModules = ideaProject.modules
val modulePaths = mapOf(*ideaModules.map { it.name to it.gradleProject.path }.toTypedArray())
val rootModule = ideaModules.find { it.gradleProject.path == ":" }
val rootModule = ideaModules.find { it.gradleProject.path == IProject.ROOT_PROJECT_PATH }
?: throw ModelBuilderException(
"No GradleProject model is associated with project path: ':'")
"No GradleProject model is associated with project path: '${IProject.ROOT_PROJECT_PATH}'")
val rootProjectVersions = getAndroidVersions(rootModule, controller)
@@ -59,7 +59,7 @@ class MultiModuleAndroidProjectTest {
val isInitialized = server.isServerInitialized().get()
assertThat(isInitialized).isTrue()
var selectionResult = project.selectProject(StringParameter(":")).get()
var selectionResult = project.selectProject(StringParameter(IProject.ROOT_PROJECT_PATH)).get()
assertThat(selectionResult.isSuccessful).isTrue()
assertThat(project.getType().get()).isEqualTo(ProjectType.Gradle)
@@ -27,14 +27,9 @@ import java.util.concurrent.CompletableFuture
*/
interface IModuleProject : IGradleProject {
/**
* Get the `classes.jar` file for this module project.
*/
fun getClassesJar(): CompletableFuture<File>
/**
* Get the classpaths for this module project. The returned list always included the
* `classes.jar` file from [getClassesJar].
* `classes.jar`.
*/
fun getClasspaths(): CompletableFuture<List<File>>
}
@@ -44,4 +44,17 @@ interface IProject : IProjectQueries {
*/
@JsonRequest
fun getProjectSyncIssues(): CompletableFuture<Map<String, DefaultProjectSyncIssues>>
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"
}
}
@@ -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
}
@@ -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
)
}
@@ -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
)
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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?
}